一、什麼是Netlink通信機制
Netlink套接字是用以實作使用者程序與核心程序通信的一種特殊的程序間通信(IPC) ,也是網絡應用程式與核心通信的最常用的接口。
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 首先發起會話;
二、Netlink常用資料結構及函數
使用者态應用使用标準的 socket API有(sendto()),recvfrom(); sendmsg(), recvmsg())
下面簡單介紹幾種NETLINK使用者态通信的常用資料結構
1、使用者态資料結構
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 結構:
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種通用的消息類型,它們分别是:
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 */
三、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
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]#