linux netlink通訊機制

1、什麼是Netlink通訊機制linux

   Netlink套接字是用以實現用戶進程與內核進程通訊的一種特殊的進程間通訊(IPC) ,也是網絡應用程序與內核通訊的最經常使用的接口。shell

    Netlink 是一種特殊的 socket,它是 Linux 所特有的,相似於 BSD 中的AF_ROUTE 但又遠比它的功能強大,目前在Linux 內核中
    使用netlink 進行應用與內核通訊的應用不少; 包括:路由 daemon(NETLINK_ROUTE),用戶態 socket 協議(NETLINK_USERSOCK),
    防火牆(NETLINK_FIREWALL),netfilter 子系統(NETLINK_NETFILTER),內核事件向用戶態通知(NETLINK_KOBJECT_UEVENT),
    通用 netlink(NETLINK_GENERIC)等。
    
    Netlink 是一種在內核與用戶應用間進行雙向數據傳輸的很是好的方式,用戶態應用使用標準的 socket API 就可使用 netlink 提供的強大功能,
    內核態須要使用專門的內核 API 來使用 netlink。
    Netlink 相對於系統調用ioctl 以及 /proc文件系統而言具備如下優勢:
    1,netlink使用簡單,只須要在include/linux/netlink.h中增長一個新類型的 netlink 協議定義便可,(如 #define NETLINK_TEST 20 而後,內核和用戶態應用就能夠當即經過 socket API 使用該 netlink 協議類型進行數據交換);
    2. netlink是一種異步通訊機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接收隊列,而不須要等待接收者收到消息;
    3.使用 netlink 的內核部分能夠採用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴;
    4.netlink 支持多播,內核模塊或應用能夠把消息多播給一個netlink組,屬於該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性;
    5.內核可使用 netlink 首先發起會話;api

2、Netlink經常使用數據結構及函數數組

 用戶態應用使用標準的 socket API有(sendto()),recvfrom(); sendmsg(), recvmsg())緩存

 下面簡單介紹幾種NETLINK用戶態通訊的經常使用數據結構網絡

 一、用戶態數據結構數據結構

   Netlink通訊跟經常使用UDP Socket通訊相似:
 struct sockaddr_nl 是netlink通訊地址跟普通socket struct sockaddr_in相似
  struct sockaddr_nl結構: 異步

1 struct sockaddr_nl {
2     __kernel_sa_family_t    nl_family;  /* AF_NETLINK (跟AF_INET對應)*/
3     unsigned short  nl_pad;     /* zero */
4     __u32       nl_pid;     /* port ID  (通訊端口號)*/
5     __u32       nl_groups;  /* multicast groups mask */
6 };

struct nlmsghd 結構:socket

1 /* struct nlmsghd 是netlink消息頭*/
2 struct nlmsghdr {   
3     __u32       nlmsg_len;  /* Length of message including header */
4     __u16       nlmsg_type; /* Message content */
5     __u16       nlmsg_flags;    /* Additional flags */ 
6     __u32       nlmsg_seq;  /* Sequence number */
7     __u32       nlmsg_pid;  /* Sending process port ID */
8 };

(1)nlmsg_len:整個netlink消息的長度(包含消息頭);
(2)nlmsg_type:消息狀態,內核在include/uapi/linux/netlink.h中定義瞭如下4種通用的消息類型,它們分別是:ide

 1 #define NLMSG_NOOP      0x1 /* Nothing.     */
 2 #define NLMSG_ERROR     0x2 /* Error        */
 3 #define NLMSG_DONE      0x3 /* End of a dump    */
 4 #define NLMSG_OVERRUN       0x4 /* Data lost        */
 5 
 6 #define NLMSG_MIN_TYPE      0x10    /* < 0x10: reserved control messages */
 7 
 8 /*NLMSG_NOOP:不執行任何動做,必須將該消息丟棄;
 9 NLMSG_ERROR:消息發生錯誤;
10 NLMSG_DONE:標識分組消息的末尾;
11 NLMSG_OVERRUN:緩衝區溢出,表示某些消息已經丟失。
12 NLMSG_MIN_TYPEK:預留 */

(3)nlmsg_flags:消息標記,它們用以表示消息的類型,以下

 1 /* Flags values */
 2 
 3 #define NLM_F_REQUEST       1   /* It is request message.   */
 4 #define NLM_F_MULTI     2   /* Multipart message, terminated by NLMSG_DONE */
 5 #define NLM_F_ACK       4   /* Reply with ack, with zero or error code */
 6 #define NLM_F_ECHO      8   /* Echo this request        */
 7 #define NLM_F_DUMP_INTR     16  /* Dump was inconsistent due to sequence change */
 8 
 9 /* Modifiers to GET request */
10 #define NLM_F_ROOT  0x100   /* specify tree root    */
11 #define NLM_F_MATCH 0x200   /* return all matching  */
12 #define NLM_F_ATOMIC    0x400   /* atomic GET       */
13 #define NLM_F_DUMP  (NLM_F_ROOT|NLM_F_MATCH)
14 
15 /* Modifiers to NEW request */
16 #define NLM_F_REPLACE   0x100   /* Override existing        */
17 #define NLM_F_EXCL  0x200   /* Do not touch, if it exists   */
18 #define NLM_F_CREATE    0x400   /* Create, if it does not exist */
19 #define NLM_F_APPEND    0x800   /* Add to end of list       */

(4)nlmsg_seq:消息序列號,用以將消息排隊,有些相似TCP協議中的序號(不徹底同樣),可是netlink的這個字段是可選的,不強制使用;
(5)nlmsg_pid:發送端口的ID號,對於內核來講該值就是0,對於用戶進程來講就是其socket所綁定的ID號。

struct msghdr 結構體

 1 struct iovec {                    /* Scatter/gather array items */
 2      void  *iov_base;              /* Starting address */
 3      size_t iov_len;               /* Number of bytes to transfer */
 4  };
 5   /* iov_base: iov_base指向數據包緩衝區,即參數buff,iov_len是buff的長度。msghdr中容許一次傳遞多個buff,
 6     以數組的形式組織在 msg_iov中,msg_iovlen就記錄數組的長度 (即有多少個buff)
 7   */
 8  struct msghdr {
 9      void         *msg_name;       /* optional address */
10      socklen_t     msg_namelen;    /* size of address */
11      struct iovec *msg_iov;        /* scatter/gather array */
12      size_t        msg_iovlen;     /* # elements in msg_iov */
13      void         *msg_control;    /* ancillary data, see below */
14      size_t        msg_controllen; /* ancillary data buffer len */
15      int           msg_flags;      /* flags on received message */
16  };
17  /* msg_name: 數據的目的地址,網絡包指向sockaddr_in, netlink則指向sockaddr_nl;
18     msg_namelen: msg_name 所表明的地址長度
19     msg_iov: 指向的是緩衝區數組
20     msg_iovlen: 緩衝區數組長度
21     msg_control: 輔助數據,控制信息(發送任何的控制信息)
22     msg_controllen: 輔助信息長度
23     msg_flags: 消息標識
24  */

 2. netlink 內核數據結構、經常使用宏及函數:

netlink消息類型:

 1 #define NETLINK_ROUTE       0   /* Routing/device hook              */
 2 #define NETLINK_UNUSED      1   /* Unused number                */
 3 #define NETLINK_USERSOCK    2   /* Reserved for user mode socket protocols  */
 4 #define NETLINK_FIREWALL    3   /* Unused number, formerly ip_queue     */
 5 #define NETLINK_SOCK_DIAG   4   /* socket monitoring                */
 6 #define NETLINK_NFLOG       5   /* netfilter/iptables ULOG */
 7 #define NETLINK_XFRM        6   /* ipsec */
 8 #define NETLINK_SELINUX     7   /* SELinux event notifications */
 9 #define NETLINK_ISCSI       8   /* Open-iSCSI */
10 #define NETLINK_AUDIT       9   /* auditing */
11 #define NETLINK_FIB_LOOKUP  10  
12 #define NETLINK_CONNECTOR   11
13 #define NETLINK_NETFILTER   12  /* netfilter subsystem */
14 #define NETLINK_IP6_FW      13
15 #define NETLINK_DNRTMSG     14  /* DECnet routing messages */
16 #define NETLINK_KOBJECT_UEVENT  15  /* Kernel messages to userspace */
17 #define NETLINK_GENERIC     16
18 /* leave room for NETLINK_DM (DM Events) */
19 #define NETLINK_SCSITRANSPORT   18  /* SCSI Transports */
20 #define NETLINK_ECRYPTFS    19
21 #define NETLINK_RDMA        20
22 #define NETLINK_CRYPTO      21  /* Crypto layer */
23 
24 #define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG
25 
26 #define MAX_LINKS 32 

netlink經常使用宏

 1 #define NLMSG_ALIGNTO   4U
 2 /* 宏NLMSG_ALIGN(len)用於獲得不小於len且字節對齊的最小數值 */
 3 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
 4 
 5 /* Netlink 頭部長度 */
 6 #define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
 7 
 8 /* 計算消息數據len的真實消息長度(消息體 + 消息頭)*/
 9 #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
10 
11 /* 宏NLMSG_SPACE(len)返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值 */
12 #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
13 
14 /* 宏NLMSG_DATA(nlh)用於取得消息的數據部分的首地址,設置和讀取消息數據部分時須要使用該宏 */
15 #define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
16 
17 /* 宏NLMSG_NEXT(nlh,len)用於獲得下一個消息的首地址, 同時len 變爲剩餘消息的長度 */
18 #define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
19                   (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
20 
21 /* 判斷消息是否 >len */
22 #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
23                (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
24                (nlh)->nlmsg_len <= (len))
25 
26 /* NLMSG_PAYLOAD(nlh,len) 用於返回payload的長度*/
27 #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))

netlink 內核經常使用函數:

netlink_kernel_create內核函數用於建立 內核socket用用戶態通訊 

 1 static inline struct sock *
 2 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
 3 /* net: net指向所在的網絡命名空間, 通常默認傳入的是&init_net(不須要定義);  定義在net_namespace.c(extern struct net init_net);
 4    unit:netlink協議類型
 5    cfg: cfg存放的是netlink內核配置參數(以下)
 6 */
 7 
 8 /* optional Netlink kernel configuration parameters */
 9 struct netlink_kernel_cfg {
10     unsigned int    groups;  
11     unsigned int    flags;  
12     void        (*input)(struct sk_buff *skb); /* input 回調函數 */
13     struct mutex    *cb_mutex; 
14     void        (*bind)(int group); 
15     bool        (*compare)(struct net *net, struct sock *sk);
16 };

 單播netlink_unicast() 和 多播netlink_broadcast()

 1 /* 來發送單播消息 */
 2 extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
 3 /* ssk: netlink socket 
 4    skb: skb buff 指針
 5    portid: 通訊的端口號
 6    nonblock:表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回,而若是爲0,該函數在沒有接收緩存可利用 定時睡眠
 7 */
 8 
 9 /* 用來發送多播消息 */
10 extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
11                  __u32 group, gfp_t allocation);
12 /* ssk: 同上(對應netlink_kernel_create 返回值)、
13    skb: 內核skb buff
14    portid: 端口id
15    group: 是全部目標多播組對應掩碼的"OR"操做的合值。
16    allocation: 指定內核內存分配方式,一般GFP_ATOMIC用於中斷上下文,而GFP_KERNEL用於其餘場合。
17                 這個參數的存在是由於該API可能須要分配一個或多個緩衝區來對多播消息進行clone
18 */

 3、netlink實例
(1)用戶態程序 (sendto(), recvfrom())

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/socket.h>
 4 #include <string.h>
 5 #include <linux/netlink.h>
 6 #include <stdint.h>
 7 #include <unistd.h>
 8 #include <errno.h>
 9 
10 #define NETLINK_TEST    30
11 #define MSG_LEN            125
12 #define MAX_PLOAD        125
13 
14 typedef struct _user_msg_info
15 {
16     struct nlmsghdr hdr;
17     char  msg[MSG_LEN];
18 } user_msg_info;
19 
20 int main(int argc, char **argv)
21 {
22     int skfd;
23     int ret;
24     user_msg_info u_info;
25     socklen_t len;
26     struct nlmsghdr *nlh = NULL;
27     struct sockaddr_nl saddr, daddr;
28     char *umsg = "hello netlink!!";
29 
30     /* 建立NETLINK socket */
31     skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
32     if(skfd == -1)
33     {
34         perror("create socket error\n");
35         return -1;
36     }
37 
38     memset(&saddr, 0, sizeof(saddr));
39     saddr.nl_family = AF_NETLINK; //AF_NETLINK
40     saddr.nl_pid = 100;  //端口號(port ID) 
41     saddr.nl_groups = 0;
42     if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
43     {
44         perror("bind() error\n");
45         close(skfd);
46         return -1;
47     }
48 
49     memset(&daddr, 0, sizeof(daddr));
50     daddr.nl_family = AF_NETLINK;
51     daddr.nl_pid = 0; // to kernel 
52     daddr.nl_groups = 0;
53 
54     nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
55     memset(nlh, 0, sizeof(struct nlmsghdr));
56     nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
57     nlh->nlmsg_flags = 0;
58     nlh->nlmsg_type = 0;
59     nlh->nlmsg_seq = 0;
60     nlh->nlmsg_pid = saddr.nl_pid; //self port
61 
62     memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
63     ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
64     if(!ret)
65     {
66         perror("sendto error\n");
67         close(skfd);
68         exit(-1);
69     }
70     printf("send kernel:%s\n", umsg);
71 
72     memset(&u_info, 0, sizeof(u_info));
73     len = sizeof(struct sockaddr_nl);
74     ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
75     if(!ret)
76     {
77         perror("recv form kernel error\n");
78         close(skfd);
79         exit(-1);
80     }
81 
82     printf("from kernel:%s\n", u_info.msg);
83     close(skfd);
84 
85     free((void *)nlh);
86     return 0;
87 }

Netlink 內核模塊代碼:

  1 /****************************************
  2 * Author: zhangwj
  3 * Date: 2017-01-19
  4 * Filename: netlink_test.c
  5 * Descript: netlink of kernel
  6 * Kernel: 3.10.0-327.22.2.el7.x86_64
  7 * Warning:
  8 ******************************************/
  9 
 10 #include <linux/init.h>
 11 #include <linux/module.h>
 12 #include <linux/types.h>
 13 #include <net/sock.h>
 14 #include <linux/netlink.h>
 15 
 16 #define NETLINK_TEST     30
 17 #define MSG_LEN            125
 18 #define USER_PORT        100
 19 
 20 MODULE_LICENSE("GPL");
 21 MODULE_AUTHOR("zhangwj");
 22 MODULE_DESCRIPTION("netlink example");
 23 
 24 struct sock *nlsk = NULL;
 25 extern struct net init_net;
 26 
 27 int send_usrmsg(char *pbuf, uint16_t len)
 28 {
 29     struct sk_buff *nl_skb;
 30     struct nlmsghdr *nlh;
 31 
 32     int ret;
 33 
 34     /* 建立sk_buff 空間 */
 35     nl_skb = nlmsg_new(len, GFP_ATOMIC);
 36     if(!nl_skb)
 37     {
 38         printk("netlink alloc failure\n");
 39         return -1;
 40     }
 41 
 42     /* 設置netlink消息頭部 */
 43     nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
 44     if(nlh == NULL)
 45     {
 46         printk("nlmsg_put failaure \n");
 47         nlmsg_free(nl_skb);
 48         return -1;
 49     }
 50 
 51     /* 拷貝數據發送 */
 52     memcpy(nlmsg_data(nlh), pbuf, len);
 53     ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);
 54 
 55     return ret;
 56 }
 57 
 58 static void netlink_rcv_msg(struct sk_buff *skb)
 59 {
 60     struct nlmsghdr *nlh = NULL;
 61     char *umsg = NULL;
 62     char *kmsg = "hello users!!!";
 63 
 64     if(skb->len >= nlmsg_total_size(0))
 65     {
 66         nlh = nlmsg_hdr(skb);
 67         umsg = NLMSG_DATA(nlh);
 68         if(umsg)
 69         {
 70             printk("kernel recv from user: %s\n", umsg);
 71             send_usrmsg(kmsg, strlen(kmsg));
 72         }
 73     }
 74 }
 75 
 76 struct netlink_kernel_cfg cfg = { 
 77         .input  = netlink_rcv_msg, /* set recv callback */
 78 };  
 79 
 80 int test_netlink_init(void)
 81 {
 82     /* create netlink socket */
 83     nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
 84     if(nlsk == NULL)
 85     {   
 86         printk("netlink_kernel_create error !\n");
 87         return -1; 
 88     }   
 89     printk("test_netlink_init\n");
 90     
 91     return 0;
 92 }
 93 
 94 void test_netlink_exit(void)
 95 {
 96     if (nlsk){
 97         netlink_kernel_release(nlsk); /* release ..*/
 98         nlsk = NULL;
 99     }   
100     printk("test_netlink_exit!\n");
101 }
102 
103 module_init(test_netlink_init);
104 module_exit(test_netlink_exit);

Makeflie:

 1 #
 2 #Desgin of Netlink
 3 #
 4 
 5 MODULE_NAME :=netlink_test
 6 obj-m :=$(MODULE_NAME).o
 7 
 8 KERNELDIR ?= /lib/modules/$(shell uname -r)/build
 9 PWD := $(shell pwd)
10 
11 all:
12     $(MAKE) -C $(KERNELDIR) M=$(PWD)
13 
14 clean:
15     $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

運行結果:

首先將編譯出來的Netlink內核模塊插入到系統當中(insmod netlink_test.ko)能夠看到以下:

1 [root@localhost nt_2nd]# insmod netlink_test.ko 
2 [root@localhost nt_2nd]# dmesg
3 [25024.276345] test_netlink_init

接着運行應用程序:./a.out

1 [root@localhost nt_2nd]# ./a.out 
2 send kernel:hello netlink!!
3 from kernel:hello users!!!
4 [root@localhost nt_2nd]# dmesg 
5 [25024.276345] test_netlink_init
6 [25117.548350] kernel recv from user: hello netlink!!
7 [root@localhost nt_2nd]# 
相關文章
相關標籤/搜索