2013-03-21 20:33:48 +04:00
# include <linux/module.h>
# include <net/sock.h>
# include <linux/netlink.h>
# include <linux/sock_diag.h>
# include <linux/netlink_diag.h>
2014-08-02 13:47:45 +04:00
# include <linux/rhashtable.h>
2013-03-21 20:33:48 +04:00
# include "af_netlink.h"
static int sk_diag_dump_groups ( struct sock * sk , struct sk_buff * nlskb )
{
struct netlink_sock * nlk = nlk_sk ( sk ) ;
if ( nlk - > groups = = NULL )
return 0 ;
return nla_put ( nlskb , NETLINK_DIAG_GROUPS , NLGRPSZ ( nlk - > ngroups ) ,
nlk - > groups ) ;
}
static int sk_diag_fill ( struct sock * sk , struct sk_buff * skb ,
struct netlink_diag_req * req ,
u32 portid , u32 seq , u32 flags , int sk_ino )
{
struct nlmsghdr * nlh ;
struct netlink_diag_msg * rep ;
struct netlink_sock * nlk = nlk_sk ( sk ) ;
nlh = nlmsg_put ( skb , portid , seq , SOCK_DIAG_BY_FAMILY , sizeof ( * rep ) ,
flags ) ;
if ( ! nlh )
return - EMSGSIZE ;
rep = nlmsg_data ( nlh ) ;
rep - > ndiag_family = AF_NETLINK ;
rep - > ndiag_type = sk - > sk_type ;
rep - > ndiag_protocol = sk - > sk_protocol ;
rep - > ndiag_state = sk - > sk_state ;
rep - > ndiag_ino = sk_ino ;
rep - > ndiag_portid = nlk - > portid ;
rep - > ndiag_dst_portid = nlk - > dst_portid ;
rep - > ndiag_dst_group = nlk - > dst_group ;
sock_diag_save_cookie ( sk , rep - > ndiag_cookie ) ;
if ( ( req - > ndiag_show & NDIAG_SHOW_GROUPS ) & &
sk_diag_dump_groups ( sk , skb ) )
goto out_nlmsg_trim ;
if ( ( req - > ndiag_show & NDIAG_SHOW_MEMINFO ) & &
sock_diag_put_meminfo ( sk , skb , NETLINK_DIAG_MEMINFO ) )
goto out_nlmsg_trim ;
2015-01-17 00:09:00 +03:00
nlmsg_end ( skb , nlh ) ;
return 0 ;
2013-03-21 20:33:48 +04:00
out_nlmsg_trim :
nlmsg_cancel ( skb , nlh ) ;
return - EMSGSIZE ;
}
static int __netlink_diag_dump ( struct sk_buff * skb , struct netlink_callback * cb ,
int protocol , int s_num )
{
2016-08-19 11:21:37 +03:00
struct rhashtable_iter * hti = ( void * ) cb - > args [ 2 ] ;
2013-03-21 20:33:48 +04:00
struct netlink_table * tbl = & nl_table [ protocol ] ;
struct net * net = sock_net ( skb - > sk ) ;
struct netlink_diag_req * req ;
2014-08-02 13:47:45 +04:00
struct netlink_sock * nlsk ;
2013-03-21 20:33:48 +04:00
struct sock * sk ;
2016-08-19 11:21:37 +03:00
int num = 2 ;
int ret = 0 ;
2013-03-21 20:33:48 +04:00
req = nlmsg_data ( cb - > nlh ) ;
2016-08-19 11:21:37 +03:00
if ( s_num > 1 )
goto mc_list ;
2015-01-03 01:00:16 +03:00
2016-08-19 11:21:37 +03:00
num - - ;
2014-08-02 13:47:45 +04:00
2016-08-19 11:21:37 +03:00
if ( ! hti ) {
hti = kmalloc ( sizeof ( * hti ) , GFP_KERNEL ) ;
if ( ! hti )
return - ENOMEM ;
cb - > args [ 2 ] = ( long ) hti ;
}
if ( ! s_num )
rhashtable_walk_enter ( & tbl - > hash , hti ) ;
ret = rhashtable_walk_start ( hti ) ;
if ( ret = = - EAGAIN )
ret = 0 ;
if ( ret )
goto stop ;
while ( ( nlsk = rhashtable_walk_next ( hti ) ) ) {
if ( IS_ERR ( nlsk ) ) {
ret = PTR_ERR ( nlsk ) ;
if ( ret = = - EAGAIN ) {
ret = 0 ;
2013-03-21 20:33:48 +04:00
continue ;
}
2016-08-19 11:21:37 +03:00
break ;
}
2013-03-21 20:33:48 +04:00
2016-08-19 11:21:37 +03:00
sk = ( struct sock * ) nlsk ;
2013-03-21 20:33:48 +04:00
2016-08-19 11:21:37 +03:00
if ( ! net_eq ( sock_net ( sk ) , net ) )
continue ;
if ( sk_diag_fill ( sk , skb , req ,
NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
NLM_F_MULTI ,
sock_i_ino ( sk ) ) < 0 ) {
ret = 1 ;
break ;
2013-03-21 20:33:48 +04:00
}
}
2016-08-19 11:21:37 +03:00
stop :
rhashtable_walk_stop ( hti ) ;
if ( ret )
goto done ;
rhashtable_walk_exit ( hti ) ;
cb - > args [ 2 ] = 0 ;
num + + ;
mc_list :
read_lock ( & nl_table_lock ) ;
2013-03-21 20:33:48 +04:00
sk_for_each_bound ( sk , & tbl - > mc_list ) {
if ( sk_hashed ( sk ) )
continue ;
if ( ! net_eq ( sock_net ( sk ) , net ) )
continue ;
if ( num < s_num ) {
num + + ;
continue ;
}
if ( sk_diag_fill ( sk , skb , req ,
NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
NLM_F_MULTI ,
sock_i_ino ( sk ) ) < 0 ) {
ret = 1 ;
2016-08-19 11:21:37 +03:00
break ;
2013-03-21 20:33:48 +04:00
}
num + + ;
}
2016-08-19 11:21:37 +03:00
read_unlock ( & nl_table_lock ) ;
2013-03-21 20:33:48 +04:00
done :
cb - > args [ 0 ] = num ;
return ret ;
}
static int netlink_diag_dump ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct netlink_diag_req * req ;
int s_num = cb - > args [ 0 ] ;
2016-08-19 11:21:37 +03:00
int err = 0 ;
2013-03-21 20:33:48 +04:00
req = nlmsg_data ( cb - > nlh ) ;
if ( req - > sdiag_protocol = = NDIAG_PROTO_ALL ) {
int i ;
for ( i = cb - > args [ 1 ] ; i < MAX_LINKS ; i + + ) {
2016-08-19 11:21:37 +03:00
err = __netlink_diag_dump ( skb , cb , i , s_num ) ;
if ( err )
2013-03-21 20:33:48 +04:00
break ;
s_num = 0 ;
}
2016-08-19 11:21:37 +03:00
cb - > args [ 1 ] = i ;
2013-03-21 20:33:48 +04:00
} else {
if ( req - > sdiag_protocol > = MAX_LINKS ) {
read_unlock ( & nl_table_lock ) ;
2015-01-12 09:52:23 +03:00
rcu_read_unlock ( ) ;
2013-03-21 20:33:48 +04:00
return - ENOENT ;
}
2016-08-19 11:21:37 +03:00
err = __netlink_diag_dump ( skb , cb , req - > sdiag_protocol , s_num ) ;
2013-03-21 20:33:48 +04:00
}
2016-08-19 11:21:37 +03:00
return err < 0 ? err : skb - > len ;
}
static int netlink_diag_dump_done ( struct netlink_callback * cb )
{
struct rhashtable_iter * hti = ( void * ) cb - > args [ 2 ] ;
if ( cb - > args [ 0 ] = = 1 )
rhashtable_walk_exit ( hti ) ;
2013-03-21 20:33:48 +04:00
2016-08-19 11:21:37 +03:00
kfree ( hti ) ;
return 0 ;
2013-03-21 20:33:48 +04:00
}
static int netlink_diag_handler_dump ( struct sk_buff * skb , struct nlmsghdr * h )
{
int hdrlen = sizeof ( struct netlink_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 = netlink_diag_dump ,
2016-08-19 11:21:37 +03:00
. done = netlink_diag_dump_done ,
2013-03-21 20:33:48 +04:00
} ;
return netlink_dump_start ( net - > diag_nlsk , skb , h , & c ) ;
} else
return - EOPNOTSUPP ;
}
static const struct sock_diag_handler netlink_diag_handler = {
. family = AF_NETLINK ,
. dump = netlink_diag_handler_dump ,
} ;
static int __init netlink_diag_init ( void )
{
return sock_diag_register ( & netlink_diag_handler ) ;
}
static void __exit netlink_diag_exit ( void )
{
sock_diag_unregister ( & netlink_diag_handler ) ;
}
module_init ( netlink_diag_init ) ;
module_exit ( netlink_diag_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_NET_PF_PROTO_TYPE ( PF_NETLINK , NETLINK_SOCK_DIAG , 16 /* AF_NETLINK */ ) ;