2019-05-30 02:57:36 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2017-10-05 23:46:53 +03:00
/*
* vsock sock_diag ( 7 ) module
*
* Copyright ( C ) 2017 Red Hat , Inc .
* Author : Stefan Hajnoczi < stefanha @ redhat . com >
*/
# include <linux/module.h>
# include <linux/sock_diag.h>
# include <linux/vm_sockets_diag.h>
# include <net/af_vsock.h>
static int sk_diag_fill ( struct sock * sk , struct sk_buff * skb ,
u32 portid , u32 seq , u32 flags )
{
struct vsock_sock * vsk = vsock_sk ( sk ) ;
struct vsock_diag_msg * rep ;
struct nlmsghdr * nlh ;
nlh = nlmsg_put ( skb , portid , seq , SOCK_DIAG_BY_FAMILY , sizeof ( * rep ) ,
flags ) ;
if ( ! nlh )
return - EMSGSIZE ;
rep = nlmsg_data ( nlh ) ;
rep - > vdiag_family = AF_VSOCK ;
/* Lock order dictates that sk_lock is acquired before
* vsock_table_lock , so we cannot lock here . Simply don ' t take
* sk_lock ; sk is guaranteed to stay alive since vsock_table_lock is
* held .
*/
rep - > vdiag_type = sk - > sk_type ;
rep - > vdiag_state = sk - > sk_state ;
rep - > vdiag_shutdown = sk - > sk_shutdown ;
rep - > vdiag_src_cid = vsk - > local_addr . svm_cid ;
rep - > vdiag_src_port = vsk - > local_addr . svm_port ;
rep - > vdiag_dst_cid = vsk - > remote_addr . svm_cid ;
rep - > vdiag_dst_port = vsk - > remote_addr . svm_port ;
rep - > vdiag_ino = sock_i_ino ( sk ) ;
sock_diag_save_cookie ( sk , rep - > vdiag_cookie ) ;
return 0 ;
}
static int vsock_diag_dump ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct vsock_diag_req * req ;
struct vsock_sock * vsk ;
unsigned int bucket ;
unsigned int last_i ;
unsigned int table ;
struct net * net ;
unsigned int i ;
req = nlmsg_data ( cb - > nlh ) ;
net = sock_net ( skb - > sk ) ;
/* State saved between calls: */
table = cb - > args [ 0 ] ;
bucket = cb - > args [ 1 ] ;
i = last_i = cb - > args [ 2 ] ;
/* TODO VMCI pending sockets? */
spin_lock_bh ( & vsock_table_lock ) ;
/* Bind table (locally created sockets) */
if ( table = = 0 ) {
while ( bucket < ARRAY_SIZE ( vsock_bind_table ) ) {
struct list_head * head = & vsock_bind_table [ bucket ] ;
i = 0 ;
list_for_each_entry ( vsk , head , bound_table ) {
struct sock * sk = sk_vsock ( vsk ) ;
if ( ! net_eq ( sock_net ( sk ) , net ) )
continue ;
if ( i < last_i )
goto next_bind ;
if ( ! ( req - > vdiag_states & ( 1 < < sk - > sk_state ) ) )
goto next_bind ;
if ( sk_diag_fill ( sk , skb ,
NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
NLM_F_MULTI ) < 0 )
goto done ;
next_bind :
i + + ;
}
last_i = 0 ;
bucket + + ;
}
table + + ;
bucket = 0 ;
}
/* Connected table (accepted connections) */
while ( bucket < ARRAY_SIZE ( vsock_connected_table ) ) {
struct list_head * head = & vsock_connected_table [ bucket ] ;
i = 0 ;
list_for_each_entry ( vsk , head , connected_table ) {
struct sock * sk = sk_vsock ( vsk ) ;
/* Skip sockets we've already seen above */
if ( __vsock_in_bound_table ( vsk ) )
continue ;
if ( ! net_eq ( sock_net ( sk ) , net ) )
continue ;
if ( i < last_i )
goto next_connected ;
if ( ! ( req - > vdiag_states & ( 1 < < sk - > sk_state ) ) )
goto next_connected ;
if ( sk_diag_fill ( sk , skb ,
NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
NLM_F_MULTI ) < 0 )
goto done ;
next_connected :
i + + ;
}
last_i = 0 ;
bucket + + ;
}
done :
spin_unlock_bh ( & vsock_table_lock ) ;
cb - > args [ 0 ] = table ;
cb - > args [ 1 ] = bucket ;
cb - > args [ 2 ] = i ;
return skb - > len ;
}
static int vsock_diag_handler_dump ( struct sk_buff * skb , struct nlmsghdr * h )
{
int hdrlen = sizeof ( struct vsock_diag_req ) ;
struct net * net = sock_net ( skb - > sk ) ;
if ( nlmsg_len ( h ) < hdrlen )
return - EINVAL ;
if ( h - > nlmsg_flags & NLM_F_DUMP ) {
struct netlink_dump_control c = {
. dump = vsock_diag_dump ,
} ;
return netlink_dump_start ( net - > diag_nlsk , skb , h , & c ) ;
}
return - EOPNOTSUPP ;
}
static const struct sock_diag_handler vsock_diag_handler = {
. family = AF_VSOCK ,
. dump = vsock_diag_handler_dump ,
} ;
static int __init vsock_diag_init ( void )
{
return sock_diag_register ( & vsock_diag_handler ) ;
}
static void __exit vsock_diag_exit ( void )
{
sock_diag_unregister ( & vsock_diag_handler ) ;
}
module_init ( vsock_diag_init ) ;
module_exit ( vsock_diag_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_NET_PF_PROTO_TYPE ( PF_NETLINK , NETLINK_SOCK_DIAG ,
40 /* AF_VSOCK */ ) ;