linux netlink详解

一、功能介绍

    Netlink 是一种IPC(Inter Process Commumicate)机制,它是一种用于内核与用户空间通信的机制,同时它也可以用于进程间通信,
(Netlink 更多用于内核通信,进程之间通信更多使用Unix域套接字,除非需要用到Netlink的广播特性时)。在一般情况下,
用户态和内核态通信会使用三种传统的通信方式: 1 ioctl; 2 sysfs属性文件; 3 proc fs属性文件
但这3种通信方式都是同步通信方式,由用户态主动发起向内核态的通信,内核无法主动发起通信。
而Netlink是一种异步全双工的通信方式,它支持由内核态主动发起通信,内核为Netlink通信提供了一组特殊的API接口,
用户态则基于socket  API,内核发送的数据会保存在接收进程socket 的接收缓存中,由接收进程处理。Netlink 有以下优点:
1 双向全双工异步传输,支持由内核主动发起传输通信,而不需要用户空间出发(例如使用ioctl这类的单工方式)。
  如此用户空间在等待内核某种触发条件满足时就无需不断轮询,而异步接收内核消息即可。
2 支持组播传输,即内核态可以将消息发送给多个接收进程,这样就不用每个进程单独来查询了。

目前已经有许多内核模块使用netlink 机制,其中驱动模型中使用的uevent 就是基于netlink 实现,
netlink的架构框图如下:
在这里插入图片描述

#define NETLINK_ROUTE        0    /* 用于设置和查询路由表等网络核心模块*/
#define NETLINK_UNUSED        1    /* Unused number                */
#define NETLINK_USERSOCK    2    /* Reserved for user mode socket protocols     */
#define NETLINK_FIREWALL    3    /* Unused number, formerly ip_queue        */
#define NETLINK_SOCK_DIAG    4    /* socket monitoring                */
#define NETLINK_NFLOG        5    /* netfilter/iptables ULOG */
#define NETLINK_XFRM        6    /* ipsec */
#define NETLINK_SELINUX        7    /* SELinux event notifications */
#define NETLINK_ISCSI        8    /* Open-iSCSI */
#define NETLINK_AUDIT        9    /* auditing */
#define NETLINK_FIB_LOOKUP    10
#define NETLINK_CONNECTOR    11
#define NETLINK_NETFILTER    12    /* netfilter subsystem */
#define NETLINK_IP6_FW        13
#define NETLINK_DNRTMSG        14    /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT    15    /* Kernel messages to userspace 用于uevent消息通信*/
#define NETLINK_GENERIC        16  //generic netlink
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT    18    /* SCSI Transports */
#define NETLINK_ECRYPTFS    19
#define NETLINK_RDMA        20
#define NETLINK_CRYPTO        21    /* Crypto layer */

对于在实际应用中,可能会有一些定制化的需求,以上这几种专用的协议类型无法满足,这时可以在不超过最大32种类型的基础之上自行添加。但是一般情况下这样做有些不妥,于是内核开发者就设计了一种通用netlink 协议类型(Generic Netlink)NETLINK_GENERIC,它就是一个Netlink复用器,便于用户自行扩展子协议类型。

下面来分析Netlink的具体创建和通信流程。

二、内核netlink初始化注册协议族AF_NETLINK

4.19.190内核
net/netlink/af_netlink.c

 628 static struct proto netlink_proto = {
   
 629     .name     = "NETLINK",
 630     .owner    = THIS_MODULE,
 631     .obj_size = sizeof(struct netlink_sock),              
 632 }; 
 89 struct netlink_table *nl_table __read_mostly;
 90 EXPORT_SYMBOL_GPL(nl_table);// 全局
 
2745 static int __init netlink_proto_init(void)
2746 {
   
2747     int i;
2748     int err = proto_register(&netlink_proto, 0);
//向内核注册netlink协议,第二个参数表示是否申请slab 将协议加入proto_list 
2749 
2750     if (err != 0)
2751         goto out;
2752 
2753     BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb));
2754 
2755     nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
//创建32组netlink_table 这个表是整个netlink实现的最关键的一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中,由该表统一维护   其定义见下面详解1
2756     if (!nl_table)
2757         goto panic;
2758 
2759     for (i = 0; i < MAX_LINKS; i++) {
   
2760         if (rhashtable_init(&nl_table[i].hash,
2761                     &netlink_rhashtable_params) < 0) {
   
//初始化哈希表,有时间再分析
2762             while (--i > 0)
2763                 rhashtable_destroy(&nl_table[i].hash);
2764             kfree(nl_table);
2765             goto panic;
2766         }
2767     }
2768 
2769     netlink_add_usersock_entry();
//初始化应用层使用的NETLINK_USERSOCK协议类型的netlink(用于应用层进程间通信)
2770 
2771     sock_register(&netlink_family_ops);//将netlink socket 创建 函数注册到内核中,
//即创建netlink socket 时 回调函数为 netlink_family_ops->create 回调  见详解2
2772     register_pernet_subsys(&netlink_net_ops);//见详解3
2773     register_pernet_subsys(&netlink_tap_net_ops);
2774     /* The netlink device handler may be needed early. */
2775     rtnetlink_init();//调用rtnetlink_init()创建NETLINK_ROUTE协议类型的netlink,
//该种类型的netlink才是当初内核设计netlink的初衷,它用来传递网络路由子系统、邻居子系统、
//接口设置、防火墙等消息。至此整个netlink子系统初始化完成。
2776 out:
2777     return err;
2778 panic:
2779     panic("netlink_init: Cannot allocate nl_table\n");
2780 }

详解1:

56 struct netlink_table {
   
 57     struct rhashtable   hash;//hash(哈希表)用来索引同种协议类型的不同netlink套接字实例  //通过portid
 58     struct hlist_head   mc_list;//为多播使用的sock散列表
 59     struct listeners __rcu  *listeners;//监听者掩码
 60     unsigned int        flags;
 61     unsigned int        groups;//协议支持的最大多播组数量
 62     struct mutex        *cb_mutex;
 63     struct module       *module;
 /*定义了一些函数指针,它们会在内核首次创建netlink时被赋值,后续应用层创建和绑定socket时调用到*/
 64     int         (*bind)(struct net *net, int group);
 65     void            (*unbind)(struct net *net, int group);
 66     bool            (*compare)(struct net *net, struct sock *sock);
 67     int         registered;//代表已经经过注册
 68 };

详解2:

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)-》__sys_socket-》sock_create-__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
{
   
	...
	pf = rcu_dereference(net_families[family]);//读者调用它来获得一个被RCU保护的指针。  这里会得到netlink_family_ops,原理如下《而net_families怎么初始化的呢》
        pf->create(net, sock, protocol, kern);
    ...
}
2680 static const struct net_proto_family netlink_family_ops = {
   
2681     .family = PF_NETLINK,     //#define PF_NETLINK  AF_NETLINK                            
2682     .create = netlink_create,                             
2683     .owner  = THIS_MODULE,  /* for consistency 8) */      
2684 };

net_families怎么初始化的
sock_register->rcu_assign_pointer(net_families[ops->family], ops);

详解3:

调用register_pernet_subsys向内核所有的网络命名空间注册”子系统“的初始化和去初始化函数,这里的"子系统”并非指的是netlink子系统,而是一种通用的处理方式,在网络命名空间创建和注销时(后面新创建如用socket时)会调用这里注册的初始化和去初始化函数,当然对于已经存在的网络命名空间,在注册的过程中也会调用其初始化函数。netlink_net_ops定义如下:
2723 static struct pernet_operations __net_initdata netlink_net_ops = {
   
2724     .init = netlink_net_init,   //大概就是在/proc下每个进程的下建立netlimk如下
/*
/proc/1/task/1/net/netlink
/proc/1/net/netlink
/proc/2/task/2/net/netlink
/proc/2/net/netlink 
。。。
*/
                          
2725     .exit = netlink_net_exit,                             
2726 };

三、内核中注册AF_NETLINK协议族的特殊协议

Rtnetlink 允许对内核路由表进行读和更改,它用于内核与各个子系统之间(路由子系统、IP地址、链接参数等)的通信,用户空间可以通过NET_LINK_ROUTER socket 与内核进行通信,该过程基于标准的netlink消息进行。
./net/core/rtnetlink.c
4872 void __init rtnetlink_init(void)
4873 {
   
4874     if (register_pernet_subsys(&rtnetlink_net_ops))//详解3.1
4875         panic("rtnetlink_init: cannot initialize rtnetlink\n");
4876 
4877     register_netdevice_notifier(&rtnetlink_dev_notifier);
4878 
4879     rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink,     //《== here
4880               rtnl_dump_ifinfo, 0);
4881     rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, 0);
4882     rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, 0);
4883     rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, 0);
4884 
4885     rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, 0);
4886     rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, 0);
4887     rtnl_register(PF_UNSPEC, RTM_GETNETCONF, NULL, rtnl_dump_all, 0);
4888 
4889     rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, 0);
4890     rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, 0);
4891     rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, 0);
4892 
4893     rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, 0);//《== here
4894     rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, 0);
4895     rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, 0);
4896 
4897     rtnl_register(PF_UNSPEC, RTM_GETSTATS, rtnl_stats_get, rtnl_stats_dump,
4898               0);
4899 }

详解3.1

将rtnetlink的init函数和exit函数注册到内核的每个网络命名空间中,对于已经存在的网络命名空间会调用其中个的init函数,即函数rtnetlink_net_init()。rtnetlink_net_ops定义如下;

4867 static struct pernet_operations rtnetlink_net_ops = {
   
4868     .init = rtnetlink_net_init,
4869     .exit = rtnetlink_net_exit,
4870 };

分析函数rtnetlink_net_init
4843 static int __net_init rtnetlink_net_init(struct net *net)
4844 {
   
4845     struct sock *sk;
4846     struct netlink_kernel_cfg cfg = {
    
//首先这里定义了一个 netlink_kernel_cfg结构体实例,该结构体定义位于include\linux\netlink.h
//关于这个结构的详细解释见详解3.1.1
4847         .groups     = RTNLGRP_MAX,
4848         .input      = rtnetlink_rcv,
4849         .cb_mutex   = &rtnl_mutex,
4850         .flags      = NL_CFG_F_NONROOT_RECV,
4851         .bind       = rtnetlink_bind,
4852     };
4853 //设置groups为RTNLGRP_MAX后指定消息接收处理函数为rtnetlink_rcv,并设置flag为NL_CFG_F_NONROOT_RECV,这表明非超级用户可以绑定到多播组,但是没有设置NL_CFG_F_NONROOT_SEND,这表明非超级用户将不能发送组播消息。    
4854     sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
//创建了内核的netlinksock用来接收用户态消息
//调用netlink_kernel_create()向当前的网络命名空间创建NETLINK_ROUTE类型的套接字,并指定定义的那个配置结构cfg。  见详解3.1.2
4855     if (!sk)
4856         return -ENOMEM;
4857     net->rtnl = sk;
4858     return 0;
4859 }

详解3.1.143 /* optional Netlink kernel configuration parameters */
 44 struct netlink_kernel_cfg {
   
 45     unsigned int    groups;//用于指定最大的多播组
 46     unsigned int    flags;
 //可以为NL_CFG_F_NONROOT_RECV或NL_CFG_F_NONROOT_SEND,这两个符号前者用来限定非超级用户是否可以绑定到多播组,后者用来限定非超级用户是否可以发送组播;
 47     void        (*input)(struct sk_buff *skb);
 //用于指定回调函数,该回调函数用于接收和处理来自用户空间的消息(若无需接收来自用户空间的消息可不指定);
 48     struct mutex    *cb_mutex;
 //最后的三个函数指针实现sock的绑定和解绑定等操作,会添加到nl_table对应的项中去。
/*
 以bind为例分析,产生作用的地方:
 netlink_kernel_create -》 nl_table[unit].bind = cfg->bind;
 应用层socket ...(省略部分见上面)->create  //netlink_create
 netlink_create{
 bind = nl_table[protocol].bind;
	nlk->netlink_bind = bind;
}
用户态bind-》  netlink_ops->bind //netlink_bind 
netlink_bind 
{
	if (nlk->netlink_bind && groups) {//看起来和多播有关
	。。。
		nlk->netlink_bind(net, group + 1);
	。。。
	}
}
以rtnetlink_bind为例其仅是检查group为多播组时的权限问题
*/
 49     int     (*bind)(struct net *net, int group);
 50     void        (*unbind)(struct net *net, int group);
 51     bool        (*compare)(struct net *net, struct sock *sk);
 52 };

详解3.1.2

 58 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
 59 {
   
 60     return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
 61 }

2010 /*
2011  *  We export these functions to other modules. They provide a
2012  *  complete set of kernel non-blocking support for message
2013  *  queueing.
2014  */
2015 
2016 struct sock *
2017 __netlink_kernel_create(struct net *net, int unit, struct module *module,
2018             struct netlink_kernel_cfg *cfg)
2019 {
   
2020     struct socket *sock;
2021     struct sock *sk;
2022     struct netlink_sock *nlk;
2023     struct listeners *listeners = NULL;
2024     struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
2025     unsigned int groups;
2026 
2027     BUG_ON(!nl_table);
2028 
2029     if (unit < 0 || unit >= MAX_LINKS)
2030         return NULL;
2031 //创建了一个以PF_NETLINK为地址族的 SOCK_DGRAM类型的 socket 套接字,
//其协议类型就是作为参数传入的NETLINK_ROUTE  (PF_NETLINK, SOCK_DGRAM, unit)
2032     if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
2033         return NULL;

2034 //初始化netlink套接字  --------- 详解3.1.2.1
2035     if (__netlink_create(net, sock, cb_mutex, unit, 1) < 0)
2036         goto out_sock_release_nosk;
2037 
//上面两步说明 family type proto 对应唯一socket 唯一sock

2038     sk = sock->sk;
2039 
2040     if (!cfg || cfg->groups < 32)
//默认最小支持32个组播地址(因为后文会看到用户层在绑定地址时最多绑定32个组播地址),但内核也有可能支持大于32个组播地址的情况(Genetlink就属于这种情况)
2041         groups = 32;
2042     else
2043         groups = cfg->groups;
2044 
2045     listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
2046     if (!listeners)
2047         goto out_sock_release;
2048 
2049     sk->sk_data_ready = netlink_data_ready;
2050     if (cfg && cfg->input)
2051         nlk_sk(sk)->netlink_rcv = cfg->input;
2052 //这里将前文中定义的rtnetlink_rcv注册到了nlk_sk(sk)->netlink_rcv中,这样就设置完了内核态的消息处理函数

2053     if (netlink_insert(sk, 0))//将本次创建的这个套接字添加到nl_table中去(其核心是调用__netlink_insert()),注册的套接字是通过nl_table中的哈希表来管理的。
2054         goto out_sock_release;
2055 
2056     nlk = nlk_sk(sk);
2057     nlk->flags |= NETLINK_F_KERNEL_SOCKET;
2058 //设置标识NETLINK_KERNEL_SOCKET表明这个netlink套接字是一个内核套接字
2059     netlink_table_grab();//将进程放到nl_table_wait等待链表上,并调度其它进程。


//接下来继续初始化nl_table表中对应传入 NETLINK_ROUTE协议类型的数组项,首先会判断是否已经先有同样协议类型的已经注册过了,如果有就不再初始化该表项了,直接释放刚才申请的listeners内存空间然后递增注册个数并返回。这里假定是首次注册NETLINK_ROUTE协议类型的套接字,这里依次初始化了nl_table表项中的groups、listeners、cb_mutex、module、bind、unbind、flags和compare字段。通过前文中cfg的实例分析,
2060     if (!nl_table[unit].registered) {
   
//没有进过初始化
2061         nl_table[unit].groups = groups;
2062         rcu_assign_pointer(nl_table[unit].listeners, listeners);
2063         nl_table[unit].cb_mutex = cb_mutex;
2064         nl_table[unit].module = module;
2065         if (cfg) {
   
2066             nl_table[unit].bind = cfg->bind;
2067             nl_table[unit].unbind = cfg->unbind;
2068             nl_table[unit].flags = cfg->flags;
2069             if (cfg->compare)
2070                 nl_table[unit].compare = cfg->compare;
2071         }
2072         nl_table[unit].registered = 1;//已经初始化
2073     } else {
   
//已经注册过
2074         kfree(listeners);
2075         nl_table[unit].registered++;//增加注册个数
2076     }
2077     netlink_table_ungrab();
2078     return sk;在函数的最后返回成功创建的netlink套接字中的sock指针,它会在最先前的rtnetlink_net_init()函数中被保存到net->rtnl中去,注意只有NETLINK_ROUTE协议类型的套接字才会执行这个步骤,因为网络命名空间中专门为其预留了一个sock指针。

2079 
2080 out_sock_release:
2081     kfree(listeners);
2082     netlink_kernel_release(sk);
2083     return NULL;
2084 
2085 out_sock_release_nosk:
2086     sock_release(sock);
2087     return NULL;
2088 }

详解3.1.2.1:
调用 __netlink_create()函数向内核初始化netlink socket 申请 sock ,sock作为协议相关的通用数据这里将被申请初始化,其实在用户态创建netlink套接字也是间接调用到该函数。//上面有调用流程netlink_create-》__netlink_create

 634 static int __netlink_create(struct net *net, struct socket *sock,
 635                 struct mutex *cb_mutex, int protocol,
 636                 int kern)//是否由内核创建  1代表内核
 637 {
      
 638     struct sock *sk;
 639     struct netlink_sock *nlk;
 640 
 641     sock->ops = &netlink_ops;//将sock的操作函数集指针设置为netlink_ops
 /*
1491 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
1492 {
1493     return __sys_bind(fd, umyaddr, addrlen);
1494 }
__sys_bind
err = sock->ops->bind(sock
*/
 642     
 643     sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);//分配sock结构并进行初始化
 644     if (!sk)
 645         return -ENOMEM;
 646 
 647     sock_init_data(sock, sk);//初始化发送接收消息队列
/*
2501 #define _SK_MEM_PACKETS     256
2502 #define _SK_MEM_OVERHEAD    SKB_TRUESIZE(256)
2503 #define SK_WMEM_MAX     (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
2504 #define SK_RMEM_MAX     (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
*/

 648 
 649     nlk = nlk_sk(sk);//转换得到netlink_sock结构体地址
 650     if (cb_mutex) {
   
 651         nlk->cb_mutex = cb_mutex;
 652     } else {
   
 653         nlk->cb_mutex = &nlk->cb_def_mutex;
 654         mutex_init(nlk->cb_mutex);//初始化互斥锁
 655         lockdep_set_class_and_name(nlk->cb_mutex,
 656                        nlk_cb_mutex_keys + protocol,
 657                        nlk_cb_mutex_key_strings[protocol]);
 658     }
 659     init_waitqueue_head(&nlk->wait);//初始化等待队列
 660 
 661     sk->sk_destruct = netlink_sock_destruct;//设置sk_destruct回调函数
 662     sk->sk_protocol = protocol;//设置和协议类型  //后面根据设置的protocol将创建的sock插入到对应的nl_table
 663     return 0;
 664 }
 
netlink套接字结构:netlink_sock
 22 struct netlink_sock {
   
 23     /* struct sock has to be the first member of netlink_sock */
 24     struct sock     sk;
 25     u32         portid;//表示本套接字自己绑定的id号,对于内核来说它就是0
 26     u32         dst_portid;//表示目的id号
 27     u32         dst_group;
 28     u32         flags;//NETLINK_F_KERNEL_SOCKET表明由内核创建
 29     u32         subscriptions;
 30     u32         ngroups;//表示协议支持多播组数量
 31     unsigned long       *groups;//保存组首地址
 32     unsigned long       state;
 33     size_t          max_recvmsg_len;
 34     wait_queue_head_t   wait;
 35     bool            bound;//指示是否已经绑定通信地址 1代表绑定
 36     bool            cb_running;
 37     int         dump_done_errno;
 38     struct netlink_callback cb;
 39     struct mutex        *cb_mutex;
 40     struct mutex        cb_def_mutex;
 41     void            (*netlink_rcv)(struct sk_buff *skb);//保存接收到用户态数据后的处理函数
 42     int         (*netlink_bind)(struct net *net, int group);//用于协议子协议自身特有的绑定
 43     void            (*netlink_unbind)(struct net *net, int group);//用于协议子协议自身特有的解绑
 44     struct module       *module;
 45 
 46     struct rhash_head   node;
 47     struct rcu_head     rcu;
 48     struct work_struct  work;
 49 };
 50 

3.2 rtnl_register 注册rtnetlink的消息类型

 250 /**
 251  * rtnl_register - Register a rtnetlink message type
 252  * @protocol: Protocol family or PF_UNSPEC   协议族
 253  * @msgtype: rtnetlink message type  
 254  * @doit: Function pointer called for each request message
 255  * @dumpit: Function pointer called for each dump request (NLM_F_DUMP) message
 256  * @flags: rtnl_link_flags to modifiy behaviour of doit/dumpit functions
 257  *
 258  * Registers the specified function pointers (at least one of them has
 259  * to be non-NULL) to be called whenever a request message for the
 260  * specified protocol family and message type is received.
 261  * 注册指定的函数指针(至少其中一个必须是非null),以便在接收到指定协议族和消息类型的请求消息时调用。
 262  * The special protocol family PF_UNSPEC may be used to define fallback 
 263  * function pointers for the case when no entry for the specific protocol 
 264  * family exists.特殊的协议族PF_UNSPEC可以用来定义回调函数指针,当特定的协议族没有入口时。
 265  */
 266 void rtnl_register(int protocol, int msgtype,
 267            rtnl_doit_func doit, rtnl_dumpit_func dumpit,
 268            unsigned int flags)
 269 {
   
 270     int err;
 271 
 272     err = rtnl_register_internal(NULL, protocol, msgtype, doit, dumpit,
 273                      flags);
 274     if (err)
 275         pr_err("Unable to register rtnetlink message handler, "
 276                "protocol = %d, message type = %d\n", protocol, msgtype);
 277 }
//第一个参数:
//第二个参数:协议族
//第三个参数:消息类型msgtype需要大于等于RTM_BASE(16),小于等于__RTM_MAX(103)
//第四个参数:request message的回调函数 指定设备信息
//第五个参数: dump request (NLM_F_DUMP) message的回调函数 dump名字空间里所有接口信息
//第六个参数:flags: rtnl_link_flags to modifiy behaviour of doit/dumpit functions
 171 static int rtnl_register_internal(struct module *owner,
 172                   int protocol, int msgtype,
 173                   rtnl_doit_func doit, rtnl_dumpit_func dumpit,
 174                   unsigned int flags)
 175 {
   
 176     struct rtnl_link *link, *old;
 177     struct rtnl_link __rcu **tab;
 178     int msgindex;
 179     int ret = -ENOBUFS;
 180 
 181     BUG_ON(protocol < 0 || protocol > RTNL_FAMILY_MAX);//129种协议
 182     msgindex = rtm_msgindex(msgtype);//msgindex = msgtype - RTM_BASE;
 183 
 184     rtnl_lock();
 185     tab = rtnl_msg_handlers[protocol];
 186     if (tab == NULL) {
   //每种协议最多103个消息类型
 187         tab = kcalloc(RTM_NR_MSGTYPES, sizeof(void *), GFP_KERNEL);
 188         if (!tab)
 189             goto unlock;
 190 
 191         /* ensures we see the 0 stores */
 192         rcu_assign_pointer(rtnl_msg_handlers[protocol], tab);
 193     }
 194 
 195     old = rtnl_dereference(tab[msgindex]);
 196     if (old) {
   //代表已经注册
 197         link = kmemdup(old, sizeof(*old), GFP_KERNEL);//是根据给定的一段地址空间(这里由void * src和size_t len决定),再分配一个内存空间(分配模式是gfp),并将原地址空间中的内容拷贝到新分配的内存空间中。返回值为新分配的内存空间的起始地址,如果分配不成功则返回NULL
 198         if (!link)
 199             goto unlock;
 200     } else {
   
 201         link = kzalloc(sizeof(*link), GFP_KERNEL);
 202         if (!link)
 203             goto unlock;
 204     }
 205 
 206     WARN_ON(link->owner && link->owner != owner);
 207     link->owner = owner;
 208 
 209     WARN_ON(doit && link->doit && link->doit != doit);//代表重复注册
 210     if (doit)
 211         link->doit = doit;//代表已经注册过了  指定设备的信息
 212     WARN_ON(dumpit && link->dumpit && link->dumpit != dumpit);
 213     if (dumpit)
 214         link->dumpit = dumpit;//代表已经注册过了 指定名字空间里所有接口的信息
 215 
 216     link->flags |= flags;
 217 
 218     /* publish protocol:msgtype */
 219     rcu_assign_pointer(tab[msgindex], link);
 220     ret = 0;
 221     if (old)
 222         kfree_rcu(old, rcu);
 223 unlock:
 224     rtnl_unlock();
 225     return ret;
 226 }

3.3 doit函数 获取指定设备信息

为了加深理解以RTM_GETLINK rtnl_getlink为例分析,//暂时没时间分析,以后再说

3.3.1rtnl_getlink

3193 static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr *nlh,
3194             struct netlink_ext_ack *extack)
3195 {
          
3196     struct net *net = sock_net(skb->sk);
3197     struct net *tgt_net = net;
3198     struct ifinfomsg *ifm;
3199     char ifname[IFNAMSIZ];
3200     struct nlattr *tb[IFLA_MAX+1];
3201     struct net_device *dev = NULL;
3202     struct sk_buff *nskb;
3203     int netnsid = -1;
3204     int err;
3205     u32 ext_filter_mask = 0;
3206 	//见详解3.3.1.1 nlmsg_parse 将属性流解析到tb缓冲区中,每种类型的指针可以通过tb[type]获得
3207     err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFLA_MAX, ifla_policy, extack);
3208     if (err < 0)
3209         return err;
3210 //验证rtnetlink请求没有传递可能引用不同网络名称空间的附加属性
3211     err = rtnl_ensure_unique_netns(tb, extack, true);
3212     if (err < 0)
3213         return err;
3214 
3215     if (tb[IFLA_IF_NETNSID]) {
   
3216         netnsid = nla_get_s32(tb[IFLA_IF_NETNSID]);
3217         tgt_net = get_target_net(NETLINK_CB(skb).sk, netnsid);
3218         if (IS_ERR(tgt_net))
3219             return PTR_ERR(tgt_net);
3220     }
3221 
3222     if (tb[IFLA_IFNAME])//将此类型attr payload拷贝到ifname里
3223         nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
3224 
3225     if (tb[IFLA_EXT_MASK])
3226         ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
3227 
3228     err = -EINVAL;
3229     ifm = nlmsg_data(nlh);//得到ifm的首地址
3230     if (ifm->ifi_index > 0)//通过ifi_index指定了设备
3231         dev = __dev_get_by_index(tgt_net, ifm->ifi_index);//从适用的网络名字空间中通过index得到网络设备
3232     else if (tb[IFLA_IFNAME])//通过name获取设备
3233         dev = __dev_get_by_name(tgt_net, ifname);
3234     else
3235         goto out;
3236 
3237     err = -ENODEV;
3238     if (dev == NULL)
3239         goto out;
3240 
3241     err = -ENOBUFS;
3242     nskb = nlmsg_new(if_nlmsg_size(dev, ext_filter_mask), GFP_KERNEL);
//nlmsg_new分配一个新的netlink消息,第一个参数payload,为nlmsg消息中payload的大小,会调用alloc_skb申请nlmsghdr+payload
3243     if (nskb == NULL)
3244         goto out;
3245 //见详解3.3.1.2rtnl_fill_ifinfo 将属性信息填充到skb线性数据区,成功返回0
3246     err = rtnl_fill_ifinfo(nskb, dev, net,
3247                    RTM_NEWLINK, NETLINK_CB(s
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值