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, 因
此接收發送數據要注意數據的流向。不過在實際使用中感覺不是很穩定, 流量大時會發生各種奇異的死機現象。


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