Linux netlink 机制

netlink作为一种用户空间和内核空间通信的机制已经有一定年头了,它不光为了内核和用户通信,还可以作为IPC机制进行进程间通信。其实netlink定义了一个框架,人们可以基于这个框架用它来做可以做的任何事情,linux中不乏这些类似的好的框架。它们的共同点就是内核并不管它们能做什么,然而它们真的很强大,往往可以做到的事情很多,这就是内核不问策略只管实现机制,所有策略让用户实现,netlink框架就是用来传递数据的,内核只知道它可以传递数据而不知道为何要传递这些数据也不管这些数据是什么。你甚至可以将它用于真正的网络而不仅仅限于本机,这些都是可以的,它也用到了sk_buff结构体,和网络套接字一样,更好的事情是它并没有触及sk_buff里面的标准字段,而仅仅用了一个扩展的cb字段,cb在sk_buff里面的定义是char cb[40];在netlink模块里面NETLINK_CB宏就是取cb字段的,也就是netlink所用的私有字段,这样的话你就可以用netlink向任何执行实体传输任何数据了,不限于本机。

关于用户空间的netlink套接字很简单,就和传统的网络套接字一样一样的,只不过修改了一些参数罢了。如下:

sd = socket(AF_NETLINK, SOCK_RAW,NETLINK_GENERIC);

就是建立一个用户netlink套接字。之后的bind也是很简单,注意数据结构的意义就是了。这里就不说了,下面详细说一下内核的netlink实现。内核里面建立一个netlink套接字需要如下调用: 
struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len)) 

         struct socket *sock; 
         struct sock *sk; 
         if (unit<0 || unit>=MAX_LINKS) 
                 return NULL; 
         if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)) 
                 return NULL; 
         if (netlink_create(sock, unit) < 0) { 
                 sock_release(sock); 
                 return NULL; 
         } 
         sk = sock->sk; 
         sk->sk_data_ready = netlink_data_ready;  //之所以将sk_data_ready设为新的函数而不用默认的是因为为了实现一些用户策略,比如可以传入自己的input函数,待到有数据的时候自行处理。 
         if (input) 
                 nlk_sk(sk)->data_ready = input; 
         netlink_insert(sk, 0); 
         return sk; 
}

注意该函数的参数input是个回调函数,在有数据的时候内核会调用它。另外sk_data_ready回调函数是套接字标准中定义的,不管什么套接字都有sk_data_ready回调机制。在input中,你可以直接处理收到的数据,也可以不处理,在大量数据传输的情况下,在input中处理是不明智的,正确的方式应该是建立一个内核线程专门接收数据,没有数据的时候该内核线程睡眠,一旦有了数据,input回调函数唤醒这个内核线程就是了。

static void netlink_data_ready(struct sock *sk, int len) 

         struct netlink_opt *nlk = nlk_sk(sk); 
         if (nlk->data_ready) 
                 nlk->data_ready(sk, len);  //这里调用的回调函数就是内核netlink套接字建立的时候传入的那个函数。 
         netlink_rcv_wake(sk);    //告知别的进程该sock上刚完成了一次接收,可能会腾出地方以便接收新的skb 

static inline void netlink_rcv_wake(struct sock *sk) 

         struct netlink_opt *nlk = nlk_sk(sk); 
         if (!skb_queue_len(&sk->sk_receive_queue)) 
                 clear_bit(0, &nlk->state); 
         if (!test_bit(0, &nlk->state)) 
                 wake_up_interruptible(&nlk->wait);  //唤醒可能等待发送的进程 

int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock) 

         struct sock *sk; 
         int err; 
         long timeo; 
         netlink_trim(skb, gfp_any()); 
         timeo = sock_sndtimeo(ssk, nonblock); 
retry: 
         sk = netlink_getsockbypid(ssk, pid); 
... 
         err = netlink_attachskb(sk, skb, nonblock, timeo);  //将sock和sk_buff绑定在一起,在netlink中套接字和skb的绑定与解绑定是很频繁的。 
         if (err == 1) 
                 goto retry; 
         if (err) 
                 return err; 
         return netlink_sendskb(sk, skb, ssk->sk_protocol);  //在套接字sk上传输这个skb,其实就是将这个skb排入了该sk的接收队列的后头。 

int netlink_attachskb(struct sock *sk, struct sk_buff *skb, int nonblock, long timeo) 
{//这个函数将一个sk_buff给了一个套接字sock,也就是skb与sock的绑定,在绑定之前有很多工作要做。 
         struct netlink_opt *nlk; 
         nlk = nlk_sk(sk); 
         if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) { 
                 DECLARE_WAITQUEUE(wait, current); 
... 
                 __set_current_state(TASK_INTERRUPTIBLE); 
                add_wait_queue(&nlk->wait, &wait); 
                 if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf || test_bit(0, &nlk->state)) && 
                     !sock_flag(sk, SOCK_DEAD)) //如果此时这个sock不能接受这个sk,那么就要等待了,正好等在nlk->wait上,待到和该sock相关的进程在netlink_rcv_wake中唤醒之,说明可以过继skb了。 
                         timeo = schedule_timeout(timeo); 
                 __set_current_state(TASK_RUNNING); 
                 remove_wait_queue(&nlk->wait, &wait); 
                 sock_put(sk); 
                 if (signal_pending(current)) { 
                         kfree_skb(skb); 
                         return sock_intr_errno(timeo); 
                 } 
                 return 1; 
         } 
         skb_orphan(skb); 
         skb_set_owner_r(skb, sk);   //该sock正式接受这个sk_buff 
         return 0; 
}

那么谁会调用netlink_attachskb呢?这是显而易见的,在发送的时候,要把一个要发送的消息初始化成一个sk_buff结构体,但是这个skb归谁所有呢?确定绑定的主客双方的过程就是绑定,也就是上面的函数做的事。在netlink的消息发送过程中的第一步就是sock和sk_buff的绑定,于是调用上述绑定函数的就是netlink_sendmsg中调用的netlink_unicast,也就是单播,单播的意思就是只发给一个sock而不是多个,于是要做的就是找到接收此skb的sock,netlink_unicast的参数dst_pid为确定目标sock提供了方向,在netlink_unicast中(见上)正是通过这个dst_pid找到了目标sock。 
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len) 

... 
    err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT); 
out: 
         return err; 

发送是一个主动的过程,因此需要主动寻找目标sock,然后把根据要发送的消息初始化好的sk_buff找机会排入目标sock的接收队列就完事了,如此看来的话,接收更是一个简单的过程了,它只要等着接收就可以了,纯粹就是一个被动的过程了,在对应的netlink_recvmsg中循环接收,没有数据时就睡在一个sock->sk_sleep队列上就是了,一旦有数据过来,该接收进程就被唤醒,具体过程就是,当发送方调用netlink_sendmsg时,后者调用netlink_unicast,然后它进一步调用netlink_sendskb,这个netlink_sendskb最后将sk_buff排入目标接收sock的接收队列后调用目标sock的sk_data_ready,而这个sk_data_ready对于用户空间的netlink就是sock_def_readable,它会唤醒睡眠了sk->sk_sleep上的接收sock进程,而对于内核netlink套接字,其sk_data_ready将会是netlink_data_ready,就是上面所说的那个函数。这个netlink_data_ready里面调用了一个程序设计者传入内核的data_ready回调函数从而可以实现用户策略,再次引入了机制和策略的分离。在netlink_rcv_wake中会判断当前接收队列sk->sk_receive_queue是否已经空了,空了的话证明接收已经完成,这种情况下就要唤醒等待排入队列新skb的发送进程,也就是调用:

if (!skb_queue_len(&sk->sk_receive_queue))  //是否接收已经完成 
    clear_bit(0, &nlk->state);        //完成的话state清位 
if (!test_bit(0, &nlk->state))            //如果清位说明接收完成,那么就唤醒等待发送的进程,这个接收进程可以继续接收了。 其实只有在溢出的情况下才会置位state 
    wake_up_interruptible(&nlk->wait); 
以上就是简要的netlink发送和接收过程,netlink之所以如此简洁和高效,靠的就是它的一个巧妙的数据组织形式,内核要在接收到用户netlink套接字数据时快速确定策略和快速定位发送的目标都是问题,linux的netlink实现了一个表结构,其实是一个链结构,只要有netlink形成,不管是内核的还是用户空间的,都要将netlink套接字本身和它的信息一并插入到这个链表结构中,然后在发送时定位目标的时候,只要遍历这个表就可以了,插入代码如下: 
static int netlink_insert(struct sock *sk, u32 pid) 

         int err = -EADDRINUSE; 
         struct sock *osk; 
         struct hlist_node *node; 
         netlink_table_grab(); 
         sk_for_each(osk, node, &nl_table[sk->sk_protocol]) { 
                 if (nlk_sk(osk)->pid == pid) 
                         break; 
         } 
         if (!node) { 
                 err = -EBUSY; 
                 if (nlk_sk(sk)->pid == 0) { 
                         nlk_sk(sk)->pid = pid; 
                         sk_add_node(sk, &nl_table[sk->sk_protocol]); 
                         err = 0; 
                 } 
         } 
         netlink_table_ungrab(); 
         return err; 

相应的,查找代码如下: 
static __inline__ struct sock *netlink_lookup(int protocol, u32 pid) 

         struct sock *sk; 
         struct hlist_node *node; 
         read_lock(&nl_table_lock); 
         sk_for_each(sk, node, &nl_table[protocol]) { 
                 if (nlk_sk(sk)->pid == pid) { 
                         sock_hold(sk); 
                         goto found; 
                 } 
         } 
         sk = NULL; 
found: 
         read_unlock(&nl_table_lock); 
         return sk; 
}

最后看看netlink的广播是如何实现的,其实这里的广播并没有用什么网络协议的知识,和那也没有关系,所谓的广播其实就是将数据发给所有的可以接收的netlink套接字而已。其实就是遍历这个nl_table表,然后抽出其中每一个netlink套接字,经检验可以发送时,就将数据发给它们,实现起来很简单。

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, int allocation) 

         struct sock *sk; 
         struct hlist_node *node; 
         struct sk_buff *skb2 = NULL; 
         int protocol = ssk->sk_protocol; 
         int failure = 0, delivered = 0; 
         netlink_trim(skb, allocation); 
         netlink_lock_table(); 
         sk_for_each(sk, node, &nl_table[protocol]) {   //遍历整个netlink套接字链表 
                 struct netlink_opt *nlk = nlk_sk(sk); 
                 if (ssk == sk)              //不再向自己发送 
                         continue; 
...//简单判断并且递增该sock结构的引用计数,因为下面要用它了。 
                 } else if (netlink_broadcast_deliver(sk, skb2)) { 
... 

static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb) 

         struct netlink_opt *nlk = nlk_sk(sk); 
         if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf && !test_bit(0, &nlk->state)) { 
                 skb_orphan(skb); 
                 skb_set_owner_r(skb, sk);    //将skb直接过继给需要接收的sock结构 
                 skb_queue_tail(&sk->sk_receive_queue, skb); 
                 sk->sk_data_ready(sk, skb->len); //告知需要接收的sock结构有数据到来 
                 return 0; 
         } 
         return -1; 

其实,linux的很多机制都是netlink套接字实现的,比如udev守护进程,netlink机制很好很强大!


原文链接:http://blog.csdn.net/dog250/article/details/5303430

==========================================================================================

==========================================================================================

Linux--Netlink用户态与内核态的交互

Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。

【用户空间】

      Netlink套接字可以使用标准的套接字APIs来创建socket( )、关闭close( )、接收recvmsg( )或者发送sendto( )消息。netlink包含于头文件linux/netlink.h中,socket包含于sys/socket.h中。

      为了创建一个 netlink socket,用户需要使用如下参数调用 socket():
socket(AF_NETLINK, SOCK_RAW, netlink_type); 
     第一个参数必须是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它们俩实际为一个东西,它表示要使用netlink,第二个参数必须是SOCK_RAW或SOCK_DGRAM, 第三个参数指定netlink协议类型,如用户自定义协议类型NETLINK_MYTEST,NETLINK_GENERIC是一个通用的协议类型,它是 专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。

内核预定义的协议类型有:

#define NETLINK_ROUTE           0       /* Routing/device hook                          */
#define NETLINK_W1              1       /* 1-wire subsystem                             */
#define NETLINK_USERSOCK        2       /* Reserved for user mode socket protocols      */
#define NETLINK_FIREWALL        3       /* Firewalling hook                             */
#define NETLINK_INET_DIAG       4       /* INET 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 */
#define NETLINK_GENERIC         16

socket函数返回的套接字,可以交给bind等函数调用:

static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
if(skfd < 0)
{
   printf("can not create a netlink socket\n");
   exit(0);
}

bind函数需要绑定协议地址,netlink的socket地址使用struct sockaddr_nl结构描述:

struct sockaddr_nl
{
   sa_family_t           nl_family;
   unsigned short    nl_pad;
   __u32          nl_pid;
   __u32          nl_groups;
};

       成员nl_family为协议簇AF_NETLINK,成员nl_pad当前没有使用,因此要总是设置为0,成员nl_pid为接收或发送消息的进程的 ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程 ID。成员nl_groups 用于指定多播组,bind 函数用于把调用进程加入到该字段指定的多播组,如果设置为0,表示调用者不加入任何多播组:

struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();    /*设置pid为自己的pid值*/            
local.nl_groups = 0;
/*绑定套接字*/
if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0)
{
    printf("bind() error\n");
   return -1;
}
    
       用户空间可以调用send函数簇向内核发送消息,如sendto、sendmsg等,同样地,也可以使用struct sockaddr_nl来描述一个对端地址,以待send函数来调用,与本地地址稍不同的是,因为对端为内核,所以nl_pid成员需要设置为0:

struct sockaddr_nl kpeer;
memset(&kpeer, 0, sizeof(kpeer));
kpeer.nl_family = AF_NETLINK;
kpeer.nl_pid = 0;
kpeer.nl_groups = 0;
     
      另一个问题就是发内核发送的消息的组成,使用我们发送一个IP网络数据包的话,则数据包结构为“IP包头+IP数据”,同样地,netlink的消息结构 是“netlink消息头部+数据”。Netlink消息头部使用struct nlmsghdr结构来描述:

struct nlmsghdr
{
      __u32 nlmsg_len;   /* Length of message */
      __u16 nlmsg_type; /* Message type*/
      __u16 nlmsg_flags; /* Additional flags */
      __u32 nlmsg_seq;   /* Sequence number */
      __u32 nlmsg_pid;   /* Sending process PID */
};
      字段 nlmsg_len指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小,一般地,我们使用netlink提供的宏NLMSG_LENGTH 来计算这个长度,仅需向NLMSG_LENGTH宏提供要发送的数据的长度,它会自动计算对齐后的总长度:

/*计算包含报头的数据报长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1)& ~(NLMSG_ALIGNTO-1) )
后面还可以看到很多netlink提供的宏,这些宏可以为我们编写netlink宏提供很大的方便。

      字段nlmsg_type用于应用内部定义消息的类型,它对netlink内核实现是透明的,因此大部分情况下设置为0,字段nlmsg_flags用于 设置消息标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。

struct msg_to_kernel   /*自定义消息*/    
{
    struct nlmsghdr hdr;
    struct nlmsgdata data;
};

struct nlmsgdata      /*消息实际数据*/
{
    __u32 uaddr;
    __u16 uport;
};

struct msg_to_kernel message;
memset(&message, 0, sizeof(message));
message.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgdata));           
/*计算消息,数据长度为结构nlmsgdata的大小*/
message.hdr.nlmsg_flags = 0;
message.hdr.nlmsg_type = MYNL_U_PID;    /*设置自定义消息类型*/           
message.hdr.nlmsg_pid = local.nl_pid;   /*设置发送者的PID*/          

       这样,有了本地地址、对端地址和发送的数据,就可以调用发送函数将消息发送给内核了:
/*发送一个请求*/
sendto(skfd, &message, message.hdr.nlmsg_len, 0,(struct sockaddr*)&kpeer, sizeof(kpeer));
    
       当发送完请求后,就可以调用recv函数簇从内核接收数据了,接收到的数据包含了netlink消息首部和要传输的数据:

/*接收的数据包含了netlink消息首部和自定义数据结构*/
struct u_packet_info
{
       struct nlmsghdr hdr;
       struct packet_info icmp_info;
};
struct u_packet_info info;
while(1)
{
      kpeerlen = sizeof(struct sockaddr_nl);
      /*接收内核空间返回的数据*/
      rcvlen = recvfrom(skfd, &info, sizeof(struct u_packet_info),0, (struct sockaddr*)&kpeer, &kpeerlen);
      /*处理接收到的数据*/
      ……
}
      同样地,函数close用于关闭打开的netlink socket。程序中,因为程序一直循环接收处理内核的消息,需要收到用户的关闭信号才会退出,所以关闭套接字的工作放在了自定义的信号函数sig_int中处理:

/*这个信号函数,处理一些程序退出时的动作*/
static void sig_int(int signo)
{
       struct sockaddr_nl kpeer;
       struct msg_to_kernel message;

       memset(&kpeer, 0, sizeof(kpeer));
       kpeer.nl_family = AF_NETLINK;
       kpeer.nl_pid    = 0;
       kpeer.nl_groups = 0;

       memset(&message, 0, sizeof(message));
       message.hdr.nlmsg_len = NLMSG_LENGTH(0);
       message.hdr.nlmsg_flags = 0;
       message.hdr.nlmsg_type = MYNL_CLOSE;
       message.hdr.nlmsg_pid = getpid();

       /*向内核发送一个消息,由nlmsg_type表明,应用程序将关闭*/
       sendto(skfd, &message, message.hdr.nlmsg_len, 0, (struct sockaddr *)(&kpeer), sizeof(kpeer));

       close(skfd);
       exit(0);
}
     这个结束函数中,向内核发送一个退出的消息,然后调用close函数关闭netlink套接字,退出程序。

【内核空间】

      与应用程序内核,内核空间也主要完成三件工作:

    + 创建netlink套接字
    + 接收处理用户空间发送的数据
    + 发送数据至用户空间

       API函数netlink_kernel_create用于创建一个netlinksocket,同时,注册一个回调函数,用于接收处理用户空间的消息:

struct sock * netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));

     参数unit表示netlink协议类型,如NETLINK_MYTEST,参数input则为内核模块定义的netlink消息处理函数,当有消息到达 这个 netlink socket时,该input函数指针就会被引用。函数指针input的参数sk实际上就是函数netlink_kernel_create返回的 struct sock指针,sock实际是socket的一个内核表示数据结构,用户态应用创建的socket在内核中也会有一个struct sock结构来表示。

static int __init init(void)
{
    rwlock_init(&user_proc.lock);      /*初始化读写锁*/          
     /*创建一个netlink socket,协议类型是自定义的NETLINK_MYTEST,kernel_reveive为接受处理函数*/
     nlfd = netlink_kernel_create(NETLINK_MYTEST, kernel_receive);
    if(!nlfd)                /*创建失败*/
{
        printk("can not create a netlink socket\n");
        return -1;
}
   /*注册一个Netfilter 钩子*/
   return nf_register_hook(&imp2_ops);
}
module_init(init);

     用户空间向内核发送了两种自定义消息类型:MYNL_U_PID和MYNL_CLOSE,分别是发送消息和关闭。kernel_receive函数分别处理这两种消息:

DECLARE_MUTEX(receive_sem);   /*初始化信号量*/          

static void kernel_receive(struct sock *sk, int len)
{
   do{
     struct sk_buff *skb;
     if(down_trylock(&receive_sem))   /*获取信号量*/   
         return;
     /*从接收队列中取得skb,然后进行一些基本的长度的合法性校验*/
     while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
     {
         {
             struct nlmsghdr *nlh = NULL;
             struct nlmsgdata *nld = NULL;
             if(skb->len >= sizeof(struct nlmsghdr))
             {
                 nlh = (struct nlmsghdr *)skb->data;/*获取数据中的nlmsghdr 结构的报头*/
                if((nlh->nlmsg_len >= sizeof(struct nlmsghdr))&& (skb->len >= nlh->nlmsg_len))
                {
                      /*长度的全法性校验完成后,处理应用程序自定义消息类型,主要是对用户PID的保存,即为内核保存“把消息发送给谁”*/
                     if(nlh->nlmsg_type == MYNL_U_PID)      /*发送消息*/           
                     {                                               
                          write_lock_bh(&user_proc.pid);
                         user_proc.pid = nlh->nlmsg_pid;
                         user_proc.nldata.uaddr=nld->uaddr;
                         user_proc.nldata.uport=nld->uport;
                         write_unlock_bh(&user_proc.pid);
                  }
                    else if(nlh->nlmsg_type == MYNL_CLOSE)    /*应用程序关闭*/ 
                     {
                          write_lock_bh(&user_proc.pid);
                         if(nlh->nlmsg_pid == user_proc.pid)
                           user_proc.pid = 0;
                         write_unlock_bh(&user_proc.pid);
                    }
             }
         }
    }
     kfree_skb(skb);
   }
   up(&receive_sem);        /*返回信号量*/          
}while(nlfd && nlfd->receive_queue.qlen);
}
     因为内核模块可能同时被多个进程同时调用,所以函数中使用了信号量和锁来进行互斥。skb = skb_dequeue(&sk->receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结构,skb->data指向实际的netlink消息。

      程序中注册了一个Netfilter钩子,钩子函数是get_icmp,它截获ICMP数据包,然后调用send_to_user函数将数据发送给应用空 间进程。发送的数据是info结构变量,它是struct packet_info结构,这个结构包含了来源/目的地址两个成员。

send_to_user 用于将数据发送给用户空间进程,发送调用的是API函数netlink_unicast 完成的:
netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

      参数sk为函数netlink_kernel_create()返回的套接字,参数skb存放待发送的消息,它的data字段指向要发送的netlink 消息结构,而skb的控制块保存了消息的地址信息,参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将 在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。

      向用户空间进程发送的消息包含三个部份:netlink 消息头部、数据部份和控制字段,控制字段包含了内核发送netlink消息时,需要设置的目标地址与源地址,内核中消息是通过sk_buff来管理的, linux/netlink.h中定义了NETLINK_CB宏来方便消息的地址设置:

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
例如:
NETLINK_CB(skb).pid = 0;
NETLINK_CB(skb).dst_pid = 0;
NETLINK_CB(skb).dst_group = 1;

      字段pid表示消息发送者进程ID,也即源地址,对于内核,它为 0, dst_pid 表示消息接收者进程 ID,也即目标地址,如果目标为组或内核,它设置为 0,否则 dst_group 表示目标组地址,如果它目标为某一进程或内核,dst_group 应当设置为 0。

static int send_to_user(struct packet_info *info)
{
     int ret;
     int size;
     unsigned char *old_tail;
     struct sk_buff *skb;
     struct nlmsghdr *nlh;
     struct packet_info *packet;

     /*计算消息总长:消息首部加上数据加度*/
     size = NLMSG_SPACE(sizeof(*info));

     /*分配一个新的套接字缓存*/
     skb = alloc_skb(size, GFP_ATOMIC);
     old_tail = skb->tail;

     /*初始化一个netlink消息首部*/
     nlh = NLMSG_PUT(skb, 0, 0, NL_K_MSG, size-sizeof(*nlh));
     /*跳过消息首部,指向数据区*/
     packet = NLMSG_DATA(nlh);
     /*初始化数据区*/
     memset(packet, 0, sizeof(struct packet_info));
     /*填充待发送的数据*/
     packet->src = info->src;
     packet->dest = info->dest;

     /*计算skb两次长度之差,即netlink的长度总和*/
     nlh->nlmsg_len = skb->tail - old_tail;
     /*设置控制字段*/
     NETLINK_CB(skb).dst_groups = 0;

     /*发送数据*/
     read_lock_bh(&user_proc.lock);
     ret = netlink_unicast(nlfd, skb, user_proc.pid, MSG_DONTWAIT);
     read_unlock_bh(&user_proc.lock);
}
     函数初始化netlink 消息首部,填充数据区,然后设置控制字段,这三部份都包含在skb_buff中,最后调用netlink_unicast函数把数据发送出去。函数中调用 了netlink的一个重要的宏NLMSG_PUT,它用于初始化netlink 消息首部:
#define NLMSG_PUT(skb, pid, seq, type, len) \
({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; \
   __nlmsg_put(skb, pid, seq, type, len); })
static __inline__ struct nlmsghdr *
__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)
{
struct nlmsghdr *nlh;
int size = NLMSG_LENGTH(len);
nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));
nlh->nlmsg_type = type;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = 0;
nlh->nlmsg_pid = pid;
nlh->nlmsg_seq = seq;
return nlh;
}
     这个宏一个需要注意的地方是调用了nlmsg_failure标签,所以在程序中应该定义这个标签。
在内核中使用函数sock_release来释放函数netlink_kernel_create()创建的netlink socket:

void sock_release(struct socket * sock);
程序在退出模块中释放netlink sockets和netfilter hook:
static void __exit fini(void)
{
if(nlfd)
{
   sock_release(nlfd->socket);                
   /*释放netlink socket*/
}
nf_unregister_hook(&mynl_ops);                
/*撤锁netfilter 钩子*/
}

【Netlink优点】

Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:

1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17。然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。

2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。

3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。

5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。

6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。

[参考文献] http://bbs.chinaunix.net/viewthread.php?tid=822500&extra=page%3D1%26filter%3Ddigest


==================================================================================================

==================================================================================================

Linux内核中netlink协议族的实现(上)----转载  

1. 前言

netlink协议族是Linux内核网络部分的一个固定部分, 一旦在内核配置中选了网络支持就自动带了而不能单独去掉。
netlink的实现源码在net/netlink目录下,主要是net/netlink/af_netlink.c文件。

以下内核代码版本为2.6.19.2, 如无特别说明代码取自net/netlink/af_netlink.c。

2. 数据结构

netlink套接口结构:
/* net/netlink/af_netlink.c */
struct netlink_sock {
 /* struct sock has to be the first member of netlink_sock */
 struct sock  sk;
 u32   pid; // 自己的pid, 通常是0
 u32   dst_pid; // 对方的pid
 u32   dst_group; // 对方的组
 u32   flags; 
 u32   subscriptions;
 u32   ngroups; // 多播组数量
 unsigned long  *groups; // 多播组号
 unsigned long  state;
 wait_queue_head_t wait; // 等待队列,用于处理接收发送包时的top half
 struct netlink_callback *cb;  // 回调结构,包含回调函数
 spinlock_t  cb_lock;
 void   (*data_ready)(struct sock *sk, int bytes); // 数据到达时
                                //的操作, netlink可有不同类型, 如ROUTE, FIREWALL, ARPD等,                                  //每种类型都自己定义的data_ready处理
 struct module  *module;
};
这个结构先是包含一个标准的struct sock结构,后面又跟和netlink相关的特有相关数据,内核中其他协议的sock也是类似定义的, 注意sock结构必须放在第一位,这是为了可以直接将sock的指针转为netlink_sock的指针。
 
netlink sock的表:
struct netlink_table {
 struct nl_pid_hash hash; // 根据pid进行HASH的netlink sock链表, 相当于客户端链表
 struct hlist_head mc_list; // 多播的sock链表
 unsigned long *listeners;  // 监听者标志
 unsigned int nl_nonroot;
 unsigned int groups; // 每个netlink的协议类型可以定义多个组, 8的倍数,最小是32
 struct module *module;
 int registered;
};
最大可有MAX_LINKS(32)个表,处理不同协议类型的netlink套接口, 注意由于是自身的通信, 本机同时作为服务器和客户端, 服务端需要一个套接口对应, 每个客户端也要有一个套接口对应, 多个客户端的套接口形成一个链表.
struct nl_pid_hash {
 struct hlist_head *table; // 链表节点
 unsigned long rehash_time; // 重新计算HASH的时间间隔
 unsigned int mask; 
 unsigned int shift;
 unsigned int entries;  // 链表节点数
 unsigned int max_shift; // 最大幂值
 u32 rnd; // 随机数
};
其他和netlink数据相关的数据结构在include/linux/netlink.h中定义, 不过这些结构更多用在各具体的netlink对象的实现中, 在基本netlink套接口中到是用得不多。

3. af_netlink协议初始化

static int __init netlink_proto_init(void)
{
 struct sk_buff *dummy_skb;
 int i;
 unsigned long max;
 unsigned int order;
// 登记netlink_proto结构, 该结构定义如下:
// static struct proto netlink_proto = {
//  .name   = "NETLINK",
//  .owner   = THIS_MODULE,
//  .obj_size = sizeof(struct netlink_sock),
// };
// 最后一个参数为0, 表示不进行slab的分配, 只是简单的将netlink_proto结构
// 挂接到系统的网络协议链表中,这个结构最主要是告知了netlink sock结构的大小
 int err = proto_register(&netlink_proto, 0);
 if (err != 0)
  goto out;
 BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > sizeof(dummy_skb->cb));
// 分配MAX_LINKS个netlink表结构
 nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);
 if (!nl_table)
  goto panic;
// 以下根据系统内存大小计算最大链表元素个数
// PAGE_SHIFT是每页大小的2的幂,对i386是12,即每页是4K,2^12
// 对于128M内存的机器,max计算值是(128*1024) >> (21-12) = 256
// 对于64M内存的机器,max计算值是(64*1024) >> (23-12) = 32
 if (num_physpages >= (128 * 1024))
  max = num_physpages >> (21 - PAGE_SHIFT);
 else
  max = num_physpages >> (23 - PAGE_SHIFT);
// 根据max再和PAGE_SHIFT计算总内存空间相应的幂值order
 order = get_bitmask_order(max) - 1 + PAGE_SHIFT;
// max是最大节点数
 max = (1UL << order) / sizeof(struct hlist_head);
// order是max对于2的幂数
 order = get_bitmask_order(max > UINT_MAX ? UINT_MAX : max) - 1;
 for (i = 0; i < MAX_LINKS; i++) {
  struct nl_pid_hash *hash = &nl_table[i].hash;
// 为netlink的每个协议类型分配HASH表链表头
  hash->table = nl_pid_hash_alloc(1 * sizeof(*hash->table));
  if (!hash->table) {
   while (i-- > 0)
    nl_pid_hash_free(nl_table[i].hash.table,
       1 * sizeof(*hash->table));
   kfree(nl_table);
   goto panic;
  }
// 初始化HASH表参数
  memset(hash->table, 0, 1 * sizeof(*hash->table));
// 最大幂数
  hash->max_shift = order;
  hash->shift = 0;
  hash->mask = 0;
  hash->rehash_time = jiffies;
 }
// 登记netlink协议族的的操作结构
 sock_register(&netlink_family_ops);
#ifdef CONFIG_PROC_FS
 proc_net_fops_create("netlink", 0, &netlink_seq_fops);
#endif
 /* The netlink device handler may be needed early. */ 
// 初始化路由netlink
 rtnetlink_init();
out:
 return err;
panic:
 panic("netlink_init: Cannot allocate nl_table\n");
}
core_initcall(netlink_proto_init);
 

4. 建立netlink套接口

4.1  建立对应客户端的套接口
// netlink协议族操作, 在用户程序使用socket打开netlink类型的socket时调用,
// 相应的create函数在__sock_create(net/socket.c)函数中调用:
static struct net_proto_family netlink_family_ops = {
 .family = PF_NETLINK,
 .create = netlink_create,
 .owner = THIS_MODULE, /* for consistency 8) */
};
// 在用户空间每次打开netlink socket时都会调用此函数: 
static int netlink_create(struct socket *sock, int protocol)
{
 struct module *module = NULL;
 struct netlink_sock *nlk;
 unsigned int groups;
 int err = 0;
// sock状态初始化
 sock->state = SS_UNCONNECTED;
// 对netlink sock的类型和协议(实际是netlink_family类型)限制检查
 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
  return -ESOCKTNOSUPPORT;
 if (protocol<0 || protocol >= MAX_LINKS)
  return -EPROTONOSUPPORT;
 netlink_lock_table();
#ifdef CONFIG_KMOD
// 如果相应的netlink协议是模块又没有加载的话先加载该模块
 if (!nl_table[protocol].registered) {
  netlink_unlock_table();
  request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
  netlink_lock_table();
 }
#endif
 if (nl_table[protocol].registered &&
     try_module_get(nl_table[protocol].module))
  module = nl_table[protocol].module;
// groups这个值在函数后面也没见用上, 这句没意义
 groups = nl_table[protocol].groups;
 netlink_unlock_table();
// 真正的建立netlink sock的函数
 if ((err = __netlink_create(sock, protocol)) < 0)
  goto out_module;
 nlk = nlk_sk(sock->sk);
 nlk->module = module;
out:
 return err;
out_module:
 module_put(module);
 goto out;
}

// 基本函数
static int __netlink_create(struct socket *sock, int protocol)
{
 struct sock *sk;
 struct netlink_sock *nlk;
// netlink sock的基本操作
 sock->ops = &netlink_ops;
// 分配sock结构, 通过netlink_proto中的obj_size指出了netlink sock的大小
 sk = sk_alloc(PF_NETLINK, GFP_KERNEL, &netlink_proto, 1);
 if (!sk)
  return -ENOMEM;
// 初始化sock基本数据, 将sock和socket关联起来
 sock_init_data(sock, sk);
// 将普通sock转为netlink sock,实际只是重新定义的一下指针类型,指针本身值不变
 nlk = nlk_sk(sk);
// 初始化sock的锁
 spin_lock_init(&nlk->cb_lock);
// 初始化等待队列
 init_waitqueue_head(&nlk->wait);
// sock的析构函数,释放接收队列中的skb数据包
 sk->sk_destruct = netlink_sock_destruct;
 sk->sk_protocol = protocol;
// 注意这里没有重新定义sk的sk_data_ready函数
// 在sock_init_data()函数中将sk_data_ready定义为sock_def_readable()函数
 return 0;
}

用户空间使用socket(2)系统调用打开netlink类型的套接口时, 在内核中会调用sys_sock()函数, 然后是调用__sock_create()函数, 在其中调用netlink协议族的create()函数, 即netlink_create()函数.
 
4.2 建立服务器端的套接口

以前也介绍过另一个建立netlink sock的函数netlink_kernel_create, 一般是在netlink的各种协议类型模块初始化时调用的, 而不是socket系统调用时调用的, 每个netlink协议初始化是只调用一次, 建立一个内核中的netlink接口, 相当于服务器, 其中也调用了__netlink_create()函数:
/*
 * We export these functions to other modules. They provide a 
 * complete set of kernel non-blocking support for message
 * queueing.
 */
struct sock *
netlink_kernel_create(int unit, unsigned int groups,
                      void (*input)(struct sock *sk, int len),
                      struct module *module)
{
 struct socket *sock;
 struct sock *sk;
 struct netlink_sock *nlk;
 unsigned long *listeners = NULL;
 BUG_ON(!nl_table);
 if (unit<0 || unit>=MAX_LINKS)
  return NULL;
// 这里的lite表示只是简单分配一个socket,没有真正初始化
 if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
  return NULL;
// 用这个lite sock再建立netlink sock
 if (__netlink_create(sock, unit) < 0)
  goto out_sock_release;
 if (groups < 32)
  groups = 32;
// listerns是个位图对应groups中每个元素
 listeners = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
 if (!listeners)
  goto out_sock_release;
 sk = sock->sk;
// 重新定义了sk_data_ready函数
 sk->sk_data_ready = netlink_data_ready;
// 这个是相应的各netlink协议数据处理函数
 if (input)
  nlk_sk(sk)->data_ready = input;
 if (netlink_insert(sk, 0))
  goto out_sock_release;
 nlk = nlk_sk(sk);
 nlk->flags |= NETLINK_KERNEL_SOCKET;
 netlink_table_grab();
// 注册到相应unit的netlink协议表中
 nl_table[unit].groups = groups;
 nl_table[unit].listeners = listeners;
 nl_table[unit].module = module;
// 该标志表示该项被登记
 nl_table[unit].registered = 1;
 netlink_table_ungrab();
 return sk;
out_sock_release:
 kfree(listeners);
 sock_release(sock);
 return NULL;
}

5. netlink套接口的操作

在__netlink_create函数中定义了netlink套接口的操作结构为netlink_ops:
 sock->ops = &netlink_ops;
该结构定义如下:
static const struct proto_ops netlink_ops = {
 .family = PF_NETLINK,
 .owner = THIS_MODULE,
 .release = netlink_release,
 .bind =  netlink_bind,
 .connect = netlink_connect,
 .socketpair = sock_no_socketpair, // 无定义
 .accept = sock_no_accept, // 无定义
 .getname = netlink_getname,
 .poll =  datagram_poll,
 .ioctl = sock_no_ioctl, // 无定义
 .listen = sock_no_listen, // 无定义
 .shutdown = sock_no_shutdown, // 无定义
 .setsockopt = netlink_setsockopt,
 .getsockopt = netlink_getsockopt,
 .sendmsg = netlink_sendmsg,
 .recvmsg = netlink_recvmsg,
 .mmap =  sock_no_mmap, // 无定义
 .sendpage = sock_no_sendpage, // 无定义
};

5.1 释放
在close(2)时调用
static int netlink_release(struct socket *sock)
{
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk;
 if (!sk)
  return 0;
// 将套接口sk从系统sk链表和绑定链表中断开
 netlink_remove(sk);
 nlk = nlk_sk(sk);
 spin_lock(&nlk->cb_lock);
 if (nlk->cb) {
// 释放netlink控制块处理
  if (nlk->cb->done)
   nlk->cb->done(nlk->cb);
  netlink_destroy_callback(nlk->cb);
  nlk->cb = NULL;
 }
 spin_unlock(&nlk->cb_lock);
 /* OK. Socket is unlinked, and, therefore,
    no new packets will arrive */
// 设置sk状态为SOCK_DEAD, 断开sock和sk的互指
 sock_orphan(sk);
 sock->sk = NULL;
// 唤醒所有等待队列
 wake_up_interruptible_all(&nlk->wait);
// 清空写队列
 skb_queue_purge(&sk->sk_write_queue);
 if (nlk->pid && !nlk->subscriptions) {
// 发送释放通知
  struct netlink_notify n = {
      .protocol = sk->sk_protocol,
      .pid = nlk->pid,
       };
  atomic_notifier_call_chain(&netlink_chain,
    NETLINK_URELEASE, &n);
 } 
// 减少模块计数
 if (nlk->module)
  module_put(nlk->module);
// 相当于加锁
 netlink_table_grab();
 if (nlk->flags & NETLINK_KERNEL_SOCKET) {
// 释放内核中的netlink服务器端
  kfree(nl_table[sk->sk_protocol].listeners);
  nl_table[sk->sk_protocol].module = NULL;
  nl_table[sk->sk_protocol].registered = 0;
 } else if (nlk->subscriptions)
  netlink_update_listeners(sk);
// 相当于解锁
 netlink_table_ungrab();
// 释放该netlink sock的多播组
 kfree(nlk->groups);
 nlk->groups = NULL;
// 释放sock
 sock_put(sk);
 return 0;
}

5.2 绑定bind
绑定通常是针对服务端
static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
{
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk = nlk_sk(sk);
 struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;
 int err;
// 检查一下地址的协议族是否为AF_NETLINK 
 if (nladdr->nl_family != AF_NETLINK)
  return -EINVAL;
 /* Only superuser is allowed to listen multicasts */
 if (nladdr->nl_groups) {
// 指定了多播组, 这是需要root权限
  if (!netlink_capable(sock, NL_NONROOT_RECV))
   return -EPERM;
  if (nlk->groups == NULL) {
// 分配多播组空间
   err = netlink_alloc_groups(sk);
   if (err)
    return err;
  }
 }
 if (nlk->pid) {
// 如果sock的pid非0, 检查是否匹配在nladdr地址结构中指定的pid
  if (nladdr->nl_pid != nlk->pid)
   return -EINVAL;
 } else {
// sock的pid为0, 根据nladdr是否指定pid来执行插入或
  err = nladdr->nl_pid ?
   netlink_insert(sk, nladdr->nl_pid) :
   netlink_autobind(sock);
  if (err)
   return err;
 }
// 非多播情况时就可以返回成功了
 if (!nladdr->nl_groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))
  return 0;
 netlink_table_grab();
// 多播情况下更新sock参数
 netlink_update_subscriptions(sk, nlk->subscriptions +
                                  hweight32(nladdr->nl_groups) -
                                  hweight32(nlk->groups[0]));
 nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | nladdr->nl_groups; 
 netlink_update_listeners(sk);
 netlink_table_ungrab();
 return 0;
}

// 根据pid插入
static int netlink_insert(struct sock *sk, u32 pid)
{
// netlink相应协议的HASH结构
 struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
 struct hlist_head *head;
// 缺省错误为地址已经被使用
 int err = -EADDRINUSE;
 struct sock *osk;
 struct hlist_node *node;
 int len;
 netlink_table_grab();
// 根据pid查找相应HASH链表头
 head = nl_pid_hashfn(hash, pid);
 len = 0;
// 检查pid是否已经在链表中, 有则失败
 sk_for_each(osk, node, head) {
  if (nlk_sk(osk)->pid == pid)
   break;
  len++;
 }
 if (node)
  goto err;
// 缺省错误改为系统忙
 err = -EBUSY;
// 如果sock的pid不为0, 错误, 只有pid为0的sock才能执行该函数
// sock的pid不为0时不会再进行insert操作了
 if (nlk_sk(sk)->pid)
  goto err;

// 缺省错误改为无内存空间
 err = -ENOMEM;
 if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))
  goto err;
// 如果链表不为空而且链表长度数量过长,会调整HASH表,重新获取HASH链表头
// 不过这种情况很少发生
 if (len && nl_pid_hash_dilute(hash, len))
  head = nl_pid_hashfn(hash, pid);
 hash->entries++;
// 将pid赋值给sock的pid参数
 nlk_sk(sk)->pid = pid;
// 将sock节点添加进HASH链表
 sk_add_node(sk, head);
 err = 0;
err:
 netlink_table_ungrab();
 return err;
}

// 未指定pid时的自动绑定
// 实际是选一个没用过的pid后再进行插入操作
static int netlink_autobind(struct socket *sock)
{
// 从socket找到sock
 struct sock *sk = sock->sk;
// netlink相应协议的HASH结构
 struct nl_pid_hash *hash = &nl_table[sk->sk_protocol].hash;
 struct hlist_head *head;
 struct sock *osk;
 struct hlist_node *node;
// pid取为当前进程的组ID
 s32 pid = current->tgid;
 int err;
// 有符号32位数
 static s32 rover = -4097;
retry:
 cond_resched();
 netlink_table_grab();
// 找合适的HASH链表头
 head = nl_pid_hashfn(hash, pid);
 sk_for_each(osk, node, head) {
// 查找链表中是否已经有该pid
  if (nlk_sk(osk)->pid == pid) {
// 存在, 则更新pid, 重新检查, 注意这时的pid是个负数
   /* Bind collision, search negative pid values. */
   pid = rover--;
   if (rover > -4097)
    rover = -4097;
   netlink_table_ungrab();
   goto retry;
  }
 }
 netlink_table_ungrab();
// 此时的pid是一个负数转换为无符号32位数, 将是一个非常大的数
// 执行正常的pid插入
 err = netlink_insert(sk, pid);
 if (err == -EADDRINUSE)
  goto retry;
 /* If 2 threads race to autobind, that is fine.  */
 if (err == -EBUSY)
  err = 0;
 return err;
}
// 更新subscriotions
static void
netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions)
{
 struct netlink_sock *nlk = nlk_sk(sk);
 if (nlk->subscriptions && !subscriptions)
  __sk_del_bind_node(sk);
 else if (!nlk->subscriptions && subscriptions)
  sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list);
 nlk->subscriptions = subscriptions;
}
// 更新listeners
static void
netlink_update_listeners(struct sock *sk)
{
 struct netlink_table *tbl = &nl_table[sk->sk_protocol];
 struct hlist_node *node;
 unsigned long mask;
 unsigned int i;
 for (i = 0; i < NLGRPSZ(tbl->groups)/sizeof(unsigned long); i++) {
  mask = 0;
// 遍历多播链表生成多播组的掩码
  sk_for_each_bound(sk, node, &tbl->mc_list)
   mask |= nlk_sk(sk)->groups[i];
  tbl->listeners[i] = mask;
 }
 /* this function is only called with the netlink table "grabbed", which
  * makes sure updates are visible before bind or setsockopt return. */
}
5.3 连接

连接通常是针对客户端连接服务器
static int netlink_connect(struct socket *sock, struct sockaddr *addr,
      int alen, int flags)
{
 int err = 0;
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk = nlk_sk(sk);
 struct sockaddr_nl *nladdr=(struct sockaddr_nl*)addr;
 if (addr->sa_family == AF_UNSPEC) {
// 目的地址协议族为AF_UNSPEC(未指定), 简单返回成功
  sk->sk_state = NETLINK_UNCONNECTED;
  nlk->dst_pid = 0;
  nlk->dst_group  = 0;
  return 0;
 }
// 限制目的地址协议族类型为AF_NETLINK
 if (addr->sa_family != AF_NETLINK)
  return -EINVAL;
 /* Only superuser is allowed to send multicasts */
// 只有ROOT权限才能多播
 if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_SEND))
  return -EPERM;
// 没指定pid的话自动绑定一个pid
 if (!nlk->pid)
  err = netlink_autobind(sock);
 if (err == 0) {
// 已经指定了pid或者自动绑定成功时设置sock的对方参数, 状态为连接成功
  sk->sk_state = NETLINK_CONNECTED;
  nlk->dst_pid  = nladdr->nl_pid;
  nlk->dst_group  = ffs(nladdr->nl_groups);
 }
 return err;
}
 
5.4 获取sock名称

// 填充sockaddr_nl结构中的数据 
static int netlink_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int
peer)
{
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk = nlk_sk(sk);
 struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
// 协议族
 nladdr->nl_family = AF_NETLINK;
 nladdr->nl_pad = 0;
 *addr_len = sizeof(*nladdr);
 if (peer) {
// 对方sock的pid和groups
  nladdr->nl_pid = nlk->dst_pid;
  nladdr->nl_groups = netlink_group_mask(nlk->dst_group);
 } else {
// 自己sock的pid和groups
  nladdr->nl_pid = nlk->pid;
  nladdr->nl_groups = nlk->groups ? nlk->groups[0] : 0;
 }
 return 0;
}

5.5 poll

poll是用poll(2)或select(2)系统调用选择套接口数据是否准备好时的处理函数,netlink用的是通用
的数据报的poll处理函数dategram_poll(), 说明略。
 
5.6 setsockopt

设置netlink sock的各种控制参数:
static int netlink_setsockopt(struct socket *sock, int level, int optname,
                              char __user *optval, int optlen)
{
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk = nlk_sk(sk);
 int val = 0, err;
// sock层次要为SOL_NETLINK
 if (level != SOL_NETLINK)
  return -ENOPROTOOPT;
// 读取用户空间的设置信息
 if (optlen >= sizeof(int) &&
     get_user(val, (int __user *)optval))
  return -EFAULT;
 switch (optname) {
 case NETLINK_PKTINFO:
// 处理NETLINK_RECV_PKTINFO标志, 非0设置, 0为清除
  if (val)
   nlk->flags |= NETLINK_RECV_PKTINFO;
  else
   nlk->flags &= ~NETLINK_RECV_PKTINFO;
  err = 0;
  break;
 case NETLINK_ADD_MEMBERSHIP:
 case NETLINK_DROP_MEMBERSHIP: {
// 加入或退出多播组
  unsigned int subscriptions;
  int old, new = optname == NETLINK_ADD_MEMBERSHIP ? 1 : 0;
// 检查权限
  if (!netlink_capable(sock, NL_NONROOT_RECV))
   return -EPERM;
// 如果当前sock的多播组为空是分配空间
  if (nlk->groups == NULL) {
   err = netlink_alloc_groups(sk);
   if (err)
    return err;
  }
// 检查数据范围
  if (!val || val - 1 >= nlk->ngroups)
   return -EINVAL;
  netlink_table_grab();
// 原来的状态标志
  old = test_bit(val - 1, nlk->groups);
// 如果old=1, new=0, subscriptions-1
// 如果old=0, new=1, subscriptions+1
  subscriptions = nlk->subscriptions - old + new;
// 设置或清除相应状态标志
  if (new)
   __set_bit(val - 1, nlk->groups);
  else
   __clear_bit(val - 1, nlk->groups);
// 更新sock参数
  netlink_update_subscriptions(sk, subscriptions);
  netlink_update_listeners(sk);
  netlink_table_ungrab();
  err = 0;
  break;
 }
 default:
  err = -ENOPROTOOPT;
 }
 return err;
}

// 分配netlink sock的多播组空间
static int netlink_alloc_groups(struct sock *sk)
{
 struct netlink_sock *nlk = nlk_sk(sk);
 unsigned int groups;
 int err = 0;
 netlink_lock_table();
// 组的数量是内核初始化时固定的, 最小值32, 尽量是8的倍数
 groups = nl_table[sk->sk_protocol].groups;
 if (!nl_table[sk->sk_protocol].registered)
  err = -ENOENT;
 netlink_unlock_table();
 if (err)
  return err;
// NLGRPSZ(groups)进行8字节对齐
 nlk->groups = kzalloc(NLGRPSZ(groups), GFP_KERNEL);
 if (nlk->groups == NULL)
  return -ENOMEM;
 nlk->ngroups = groups;
 return 0;
}

5.7 getsockopt

获取netlink sock的各种控制参数:

static int netlink_getsockopt(struct socket *sock, int level, int optname,
                              char __user *optval, int __user *optlen)
{
 struct sock *sk = sock->sk;
 struct netlink_sock *nlk = nlk_sk(sk);
 int len, val, err;
// sock层次要为SOL_NETLINK
 if (level != SOL_NETLINK)
  return -ENOPROTOOPT;
// 读取用户空间的查询信息
 if (get_user(len, optlen))
  return -EFAULT;
 if (len < 0)
  return -EINVAL;
 switch (optname) {
 case NETLINK_PKTINFO:
// 只提供一种选项信息PKTINFO
  if (len < sizeof(int))
   return -EINVAL;
  len = sizeof(int);
// 看sock标志是否有NETLINK_RECV_PKTINFO返回1或0
  val = nlk->flags & NETLINK_RECV_PKTINFO ? 1 : 0;
  if (put_user(len, optlen) ||
      put_user(val, optval))
   return -EFAULT;
  err = 0;
  break;
 default:
  err = -ENOPROTOOPT;
 }
 return err;
}

5.8 发送消息

从用户层发送数据到内核, 内核的sock是接收方
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock,
      struct msghdr *msg, size_t len)
{
// sock的IO控制块
 struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
// socket -> sock
 struct sock *sk = sock->sk;
// sock -> netlink sock
 struct netlink_sock *nlk = nlk_sk(sk);
 struct sockaddr_nl *addr=msg->msg_name;
 u32 dst_pid;
 u32 dst_group;
 struct sk_buff *skb;
 int err;
// scm: Socket level control messages processing
 struct scm_cookie scm;
// 设置了OOB(out of band)标志, 在TCP中支持,netlink不支持
 if (msg->msg_flags&MSG_OOB)
  return -EOPNOTSUPP;
 if (NULL == siocb->scm)
  siocb->scm = &scm;
// scm这些处理是干什么的以后再看
 err = scm_send(sock, msg, siocb->scm);
 if (err < 0)
  return err;
// 确定目的pid和组
 if (msg->msg_namelen) {
  if (addr->nl_family != AF_NETLINK)
   return -EINVAL;
  dst_pid = addr->nl_pid;
  dst_group = ffs(addr->nl_groups);
  if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND))
   return -EPERM;
 } else {
  dst_pid = nlk->dst_pid;
  dst_group = nlk->dst_group;
 }
// 如果sock的pid为0, 自动绑定一个pid
 if (!nlk->pid) {
  err = netlink_autobind(sock);
  if (err)
   goto out;
 }
 err = -EMSGSIZE;
// 消息长度太大
 if (len > sk->sk_sndbuf - 32)
  goto out;
 err = -ENOBUFS;
// 新生成一个skb数据包
 skb = nlmsg_new(len, GFP_KERNEL);
 if (skb==NULL)
  goto out;
// 设置该skb的netlink控制块参数
 NETLINK_CB(skb).pid = nlk->pid;
 NETLINK_CB(skb).dst_pid = dst_pid;
 NETLINK_CB(skb).dst_group = dst_group;
 NETLINK_CB(skb).loginuid = audit_get_loginuid(current->audit_context);
 selinux_get_task_sid(current, &(NETLINK_CB(skb).sid));
 memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred));
 /* What can I do? Netlink is asynchronous, so that
    we will have to save current capabilities to
    check them, when this message will be delivered
    to corresponding kernel module.   --ANK (980802)
  */
 err = -EFAULT;
// 将发送的信息拷贝到skb的存储区
 if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) {
  kfree_skb(skb);
  goto out;
 }
/* @netlink_send:
 * Save security information for a netlink message so that permission
 * checking can be performed when the message is processed.  The security
 * information can be saved using the eff_cap field of the
 *      netlink_skb_parms structure.  Also may be used to provide fine
 * grained control over message transmission.
 * @sk associated sock of task sending the message.,
 * @skb contains the sk_buff structure for the netlink message.
 * Return 0 if the information was successfully saved and message
 * is allowed to be transmitted.
 */
 err = security_netlink_send(sk, skb);
 if (err) {
  kfree_skb(skb);
  goto out;
 }
// 如果是多播的,先进行广播发送
 if (dst_group) {
// 增加使用者计数, 使skb不会真正释放
  atomic_inc(&skb->users);
  netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL);
 }
// 单播发送
 err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
 return err;
}

// netlink广播, 发送到组内的全部sock
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,
        u32 group, gfp_t allocation)
{
// netlink广播数据结构信息
 struct netlink_broadcast_data info;
 struct hlist_node *node;
 struct sock *sk;
// 调整skb空间
 skb = netlink_trim(skb, allocation);
// 填充info结构基本参数
 info.exclude_sk = ssk;
 info.pid = pid;
 info.group = group;
 info.failure = 0;
 info.congested = 0;
 info.delivered = 0;
 info.allocation = allocation;
 info.skb = skb;
 info.skb2 = NULL;
 /* While we sleep in clone, do not allow to change socket list */
 netlink_lock_table();
// 遍历多播链表, 分别对每个sock进行单播
 sk_for_each_bound(sk, node, &nl_table[ssk->sk_protocol].mc_list)
  do_one_broadcast(sk, &info);
// 释放skb, 其实没有立即释放, 要先减少使用者数 
 kfree_skb(skb);
 netlink_unlock_table();
// 如果分配了skb2,释放之
 if (info.skb2)
  kfree_skb(info.skb2);
 if (info.delivered) {
  if (info.congested && (allocation & __GFP_WAIT))
   yield();
  return 0;
 }
 if (info.failure)
  return -ENOBUFS;
 return -ESRCH;
}
// 单一广播
static inline int do_one_broadcast(struct sock *sk,
       struct netlink_broadcast_data *p)
{
 struct netlink_sock *nlk = nlk_sk(sk);
 int val;
 if (p->exclude_sk == sk)
  goto out;
// 检查pid和组是否合法
 if (nlk->pid == p->pid || p->group - 1 >= nlk->ngroups ||
     !test_bit(p->group - 1, nlk->groups))
  goto out;
 if (p->failure) {
  netlink_overrun(sk);
  goto out;
 }
 sock_hold(sk);
 if (p->skb2 == NULL) {
  if (skb_shared(p->skb)) {
// 克隆skb
   p->skb2 = skb_clone(p->skb, p->allocation);
  } else {
// 此时skb2不会为NULL的
   p->skb2 = skb_get(p->skb);
   /*
    * skb ownership may have been set when
    * delivered to a previous socket.
    */
   skb_orphan(p->skb2);
  }
 }
 if (p->skb2 == NULL) {
// 如果还是为NULL必然是克隆失败
  netlink_overrun(sk);
  /* Clone failed. Notify ALL listeners. */
  p->failure = 1;
// 否则发送skb2
 } else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {
  netlink_overrun(sk);
 } else {
// 数据正常发送
  p->congested |= val;
  p->delivered = 1;
  p->skb2 = NULL;
 }
 sock_put(sk);
out:
 return 0;
}

static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
{
 struct netlink_sock *nlk = nlk_sk(sk);
// 发送缓冲中要有足够空间
 if (atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf &&
     !test_bit(0, &nlk->state)) {
  skb_set_owner_r(skb, sk);
// 添加到接收队列尾, 由于是本机内部通信, 可以自己找到要发送的目的方,
// 所以直接将数据扔给目的方, 所以是接收队列
  skb_queue_tail(&sk->sk_receive_queue, skb);
// 调用netlink sock的sk_data_ready函数处理, 由此进入内核中netlink各协议
// 的回调处理
  sk->sk_data_ready(sk, skb->len);
  return atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf;
 }
 return -1;
}

// netlink单播
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
{
 struct sock *sk;
 int err;
 long timeo;
// 调整skb大小
 skb = netlink_trim(skb, gfp_any());
// 获取超时时间
 timeo = sock_sndtimeo(ssk, nonblock);
retry:
// ssk是服务器端的sock, 然后根据pid找到客户端的sock
 sk = netlink_getsockbypid(ssk, pid);
 if (IS_ERR(sk)) {
  kfree_skb(skb);
  return PTR_ERR(sk);
 }
// 将数据包附着在客户端sock上
 err = netlink_attachskb(sk, skb, nonblock, timeo, ssk);
 if (err == 1)
  goto retry;
 if (err)
  return err;
// 发送netlink数据包
 return netlink_sendskb(sk, skb, ssk->sk_protocol);
}
/*
 * Attach a skb to a netlink socket.
 * The caller must hold a reference to the destination socket. On error, the
 * reference is dropped. The skb is not send to the destination, just all
 * all error checks are performed and memory in the queue is reserved.
 * Return values:
 * < 0: error. skb freed, reference to sock dropped.
 * 0: continue
 * 1: repeat lookup - reference dropped while waiting for socket memory.
 */
// 注意这个是内核全局函数, 非static
int netlink_attachskb(struct sock *sk, struct sk_buff *skb, int nonblock,
  long timeo, struct sock *ssk)
{
 struct netlink_sock *nlk;
 nlk = nlk_sk(sk);
// 检查接收缓存大小是否足够, 不够的话阻塞等待直到出错或条件满足
 if (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
     test_bit(0, &nlk->state)) {
// 声明当前进程的等待队列
  DECLARE_WAITQUEUE(wait, current);
  if (!timeo) {
   if (!ssk || nlk_sk(ssk)->pid == 0)
    netlink_overrun(sk);
   sock_put(sk);
   kfree_skb(skb);
   return -EAGAIN;
  }
// 设置当前进程状态为可中断的
  __set_current_state(TASK_INTERRUPTIBLE);
// 将sock挂接到等待队列
  add_wait_queue(&nlk->wait, &wait);
// 空间不够的话阻塞, timeo为阻塞超时
  if ((atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||
       test_bit(0, &nlk->state)) &&
      !sock_flag(sk, SOCK_DEAD))
   timeo = schedule_timeout(timeo);
// 进程状态运行
  __set_current_state(TASK_RUNNING);
// 删除等待队列
  remove_wait_queue(&nlk->wait, &wait);
  sock_put(sk);
  if (signal_pending(current)) {
// 阻塞是通过超时解开的,而不是空间条件符合解开, 属于错误状态
   kfree_skb(skb);
   return sock_intr_errno(timeo);
  }
// 返回1, 重新选sock
  return 1;
 }
// 条件满足, 直接将skb的所有者设为该netlink sock
 skb_set_owner_r(skb, sk);
 return 0;
}
// 注意这个是内核全局函数, 非static
int netlink_sendskb(struct sock *sk, struct sk_buff *skb, int protocol)
{
 int len = skb->len;
// 将skb添加到接收队列末尾
 skb_queue_tail(&sk->sk_receive_queue, skb);
// 调用netlink sock的sk_data_ready函数处理
 sk->sk_data_ready(sk, len);
 sock_put(sk);
 return len;
}
 
5.9 接收消息

数据是内核传向用户空间的
static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
      struct msghdr *msg, size_t len,
      int flags)
{
// sock的IO控制块
 struct sock_iocb *siocb = kiocb_to_siocb(kiocb);
// scm
 struct scm_cookie scm;
// socket -> sock
 struct sock *sk = sock->sk;
// sock -> netlink sock
 struct netlink_sock *nlk = nlk_sk(sk);
// 是否是非阻塞的
 int noblock = flags&MSG_DONTWAIT;
 size_t copied;
 struct sk_buff *skb;
 int err;
// 不能带OOB标志
 if (flags&MSG_OOB)
  return -EOPNOTSUPP;
 copied = 0;
// 接收一个数据包
 skb = skb_recv_datagram(sk,flags,noblock,&err);
 if (skb==NULL)
  goto out;
 msg->msg_namelen = 0;
// 收到的实际数据长度
 copied = skb->len;
// 接收缓冲小于数据长度, 设置数据裁剪标志
 if (len < copied) {
  msg->msg_flags |= MSG_TRUNC;
  copied = len;
 }
 skb->h.raw = skb->data;
// 将skb的数据拷贝到接收缓冲区
 err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
 if (msg->msg_name) {
// sock有效, 填写nl sock的数据
  struct sockaddr_nl *addr = (struct sockaddr_nl*)msg->msg_name;
  addr->nl_family = AF_NETLINK;
  addr->nl_pad    = 0;
  addr->nl_pid = NETLINK_CB(skb).pid;
  addr->nl_groups = netlink_group_mask(NETLINK_CB(skb).dst_group);
  msg->msg_namelen = sizeof(*addr);
 }
// 接收数据包信息标志, 将消息头拷贝到用户空间
 if (nlk->flags & NETLINK_RECV_PKTINFO)
  netlink_cmsg_recv_pktinfo(msg, skb);
 if (NULL == siocb->scm) {
  memset(&scm, 0, sizeof(scm));
  siocb->scm = &scm;
 }
 siocb->scm->creds = *NETLINK_CREDS(skb);
 skb_free_datagram(sk, skb);
 if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)
  netlink_dump(sk);
 scm_recv(sock, msg, siocb->scm, flags);
out:
// 接收唤醒
 netlink_rcv_wake(sk);
 return err ? : copied;
}
 
6. 结论

netlink处理代码不是很好懂, 毕竟和其他协议不同之处是内核中同时存在服务器和客户端的sock, 因
此接收发送数据要注意数据的流向。不过在实际使用中感觉不是很稳定, 流量大时会发生各种奇异的死机现象。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章