Netlink基于网络的消息机制,能够让用户和内核空间进行通信,12.3节提到的ifconfig是使用ioctl方法和内核通信的,而ip命令则是使用netlink和内核通信的。该机制初衷是为网络服务的,但是现在起应用范围已经大大扩展。

14.1 netlink支持的通信

用户空代码使用实例,发送消息时内核使用同一套代码,也就是说调用这套消息机制代码除了可以发送netlink消息还可以发送其它消息,但是这些消息又各有不同,并且netlink本身也分为好多种,内核在处理这些不同时,使用了两个结构体解决这个问题。

 


1. struct msghdr {  
2. void * msg_name; /* Socket name */
3. int msg_namelen; /* Length of name */
4. struct iovec * msg_iov; /* Data blocks */
5. /* Number of blocks */
6. void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
7. /* Length of cmsg list */
8. int msg_flags;
9. };

该结构体用于描述不同的消息,msg_iov存放的是消息内容,针对netlink消息有nlmsghdr头来描述。


1. struct nlmsghdr {  
2. /* Length of message including header */
3. /* Message content */
4. /* Additional flags */
5. /* Sequence number */
6. /* Sending process port ID */
7. };

下面的代码片段展示了netlink的基本用法。

 


1. 12 #define SEND_TEST_DATA "Hello Word"  
2. 13
3. struct event_msg{
4. int event;
5. int sub_event;
6. int len;
7. char data[0];
8. 19 };
9. /* DEMO SUB EVENT */
10. 21 #define LOOP_UNICAST 1
11. 22 #define LOOP_BROADCAST 2
12. 23
13. 24 #define PRIVATE_EVENT_GROUP 2
14. 25 #define NETLINK_TEST 17
15. /* maximum payload size*/
16. 27 #define TEST_CNT 100000
17. 28
18. struct hello _info {
19. int idx; //idx
20. int irq_type;
21. long timestamp; //jiffies
22. 33 };
23. 34
24. 35
25. 36
26. int main(int argc, char* argv[])
27. 38 {
28. int i;
29. struct sockaddr_nl src_addr, dest_addr;
30. struct nlmsghdr *nlh = NULL;
31. struct iovec iov;
32. int sock_fd;
33. struct msghdr message,recv_msg;
34. struct event_msg *msg;
35. struct alarm_info *alarm_info;
36.
37. //创建netlink套接字,第三个参数是netlink协议类型,所有的类型见下文。
38. 48 sock_fd = socket(PF_NETLINK, SOCK_RAW,NETLINK_TEST);
39. sizeof(message));
40. sizeof(src_addr));
41. 51 src_addr.nl_family = AF_NETLINK;
42. 52 src_addr.nl_pid = getpid();
43. 53 src_addr.nl_groups = PRIVATE_EVENT_GROUP ;
44. /*************************************************************************************
45. *******struct sockaddr_nl {
46. ******* __kernel_sa_family_t nl_family; /* AF_NETLINK */
47. ******* unsigned short nl_pad; /* zero */
48. ******* __u32 nl_pid; /* port ID */
49. ******* __u32 nl_groups; /* multicast groups mask */
50. *******};
51. *************************************************************************************/
52.
53. struct sockaddr*)&src_addr, sizeof(src_addr)); //将netlink套接字和netlink地址绑定。
54. sizeof(dest_addr));
55. 56
56. 57 dest_addr.nl_family = AF_NETLINK;
57. /* For Linux Kernel */
58. 59 dest_addr.nl_groups = PRIVATE_EVENT_GROUP;
59. 60
60. 61 nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
61. /* 参看图14.1;*/
62. 63 nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
63. /* self pid */
64. 65 nlh->nlmsg_flags = 0;
65. 66
66. for (i = 0;i < TEST_CNT;i++){
67. /* Fill in the netlink message payload */
68.
69. //将netlink信息和msg关联起来。
70. 70 msg = NLMSG_DATA(nlh); //消息头的首部存放netlink的头,见图14.1。
71. 71 msg->event = 0;
72. 72 msg->sub_event = (i%2) + 1;
73. sizeof(SEND_TEST_DATA);//Hello Word字符串在payload里了,见图14.1。
74. 74 strcpy(msg->data, SEND_TEST_DATA);
75. 75
76. //printf("test %d time; %s\n",i,(msg->sub_event == LOOP_UNICAST) ? "UNICAST" : "BROADCAST");
77. void *)nlh; //这边管理netlink头,也是关联msg,在70行,netlink和msg的关系就确定了。
78. 78 iov.iov_len = nlh->nlmsg_len;
79. void *)&dest_addr;
80. sizeof(dest_addr);
81. 81 message.msg_iov = &iov;
82. 82 message.msg_iovlen = 1;
83. 83
84. //发送消息给内核
85. /* Read message from kernel */
86. 86
87. 87 memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
88. //从内核接收消息
89. 89 msg = NLMSG_DATA(nlh);
90. struct hello_info *)&msg->data;
91. if ( msg->event == 3)
92. 92 {
93. "recv event %dsub_event %d alarm_info.\n",msg->event, msg->sub_event);
94. 96 }
95. 97 }
96. 98 close(sock_fd);
97. return 0;
98. 100 }

Netlink消息类型


1. Include/uapi/linux/netlink.h  
2. /* Routing/device hook */
3. /* Unused number */
4. /* Reserved for user mode socket protocols */
5. /* Unused number, formerly ip_queue */
6. /* socket monitoring */
7. /* netfilter/iptables ULOG */
8. /* ip security*/
9. /* SELinux event notifications */
10. /* Open-iSCSI */
11. /* auditing */
12. 18 #define NETLINK_FIB_LOOKUP 10
13. 19 #define NETLINK_CONNECTOR 11
14. /* netfilter subsystem */
15. 21 #define NETLINK_IP6_FW 13
16. /* DECnet routing messages */
17. /* Kernel messages to userspace */
18. 24 #define NETLINK_GENERIC 16
19. /* leave room for NETLINK_DM (DM Events) */
20. /* SCSI Transports */
21. 27 #define NETLINK_ECRYPTFS 19
22. 28 #define NETLINK_RDMA 20
23. /* Crypto layer */
24. 30
25. 31 #define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
26. 32
27. 33 #define MAX_LINKS 32
  1.      

第十四章 netlink机制--基于Linux3.10【转】_#define

图14.1 netlink消息格式

14.2 netlink用户空间API

在第六章提到套接字创建的系统调用时,提到实际的套接字创建是由具体的协议族的套接字创建函数来完成的,其调用形如err = pf->create(net, sock, protocol, kern);在af_netlink.c文件中netlink协议族的创建netlink套接字的注册的结构体如下。

 


1. static const struct net_proto_family netlink_family_ops = {  
2. .family = PF_NETLINK,
3. .create = netlink_create,
4. /* for consistency 8) */
5. };

 

其创建netlink套接字的过程和inet套接字是一样的。和inet协议很相似,netlink实现相关的主要代码在af_netlink.c(inet也有一个af_inet.c)文件。

14.3 netlink内核空间API 

14.2节的内容是针对用户空间的,这本小节则是针对内核而言的。Netlink内核创建API位于include/​​Linux​​/netlink.h。在14.1节的netlink应用程序调用14.2节的netlink套接字创建API创建netlink套接字,并发送了一个netlink消息,在内核侧有对应的netlink套接字接收应用程序发送的消息。内核侧创建netlink消息方法和应用程序调用的接口并不一样。接收应用程序发送的消息的内核侧驱动程序netlink创建的netlink代码片段可以看出。312~319行可以看到netlink套接字创建接口的参数随着内核版本的升级发生了一些变化。本文基于3.10内核,所以创建的API是318行代码中的netlink_kernel_create。

 


1. 304 static int event_notify_init(void)  
2. 305 {
3. 306 struct sock *nlsock = NULL;
4. 307 int ret;
5. 308 struct netlink_kernel_cfg cfg = {
6. 309 .input = event_notify_receive_skb,
7. 310 };
8. 311
9. 312 #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24))
10. 313 nlsock = netlink_kernel_create(EVENT_NOTIFY, 0, event_notify_receive_skb, THIS_MODULE);
11. 314 #elif (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0))
12. 315 nlsock = netlink_kernel_create(&init_net, EVENT_NOTIFY, 0,
13. 316 event_notify_receive_skb, NULL, THIS_MODULE);
14. 317 #else
15. 318 nlsock = netlink_kernel_create(&init_net, EVENT_NOTIFY, &cfg);
16. 319 #endif
17. 320
18. 321 if (nlsock) {
19. 322 en_nlsock = nlsock;
20. 323 ret = init_events(events_group);
21. 324 if (ret) {
22. 325 printk(KERN_ERR "some events init fail\n");
23. 326 }
24. 327 return 0;
25. 328 }else{
26. 329 printk(KERN_ERR "create netlink %d error\n",EVENT_NOTIFY);
27. 330 return -1;
28. 331 }
29. 332 }

 

Netlink内核侧的创建函数实际是对__netlink_kernel_create的封装。

 


1. af_netlink.c  
2. 54 static inline struct sock *
3. struct net *net, int unit, struct netlink_kernel_cfg *cfg)
4. 56 {
5. return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
6. 58 }

其调用的函数位于af_netlink.c,*net指向所在的网络命名空间,unit是netlink协议类型,module是模块所有者信息,cfg存放的是netlink内核配置参数,配置参数中的input成员用于处理接收到的消息,对于上面的驱动程序只初始化了cfg的input函数指针。该回调函数在应用程序调用sendmsg()发送消息时被调用


1. 2229 struct sock *  
2. 2230 __netlink_kernel_create(struct net *net, int unit, struct module *module,
3. 2231 struct netlink_kernel_cfg *cfg)
4. 2232 {
5. 2233 struct socket *sock;
6. 2234 struct sock *sk;
7. 2235 struct netlink_sock *nlk;
8. 2236 struct listeners *listeners = NULL;
9. 2237 struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
10. 2238 unsigned int groups;
11. 2239
12. 2240 BUG_ON(!nl_table);
13. 2241
14. 2242 if (unit < 0 || unit >= MAX_LINKS)
15. 2243 return NULL;
16. //为sock申请套接字存储空间,并将套接字类型设置为SOCK_DGRAM。
17. 2245 if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))
18. 2246 return NULL;
19. //按netlink机制需要初始化套接字相应的成员。注意是在初始网络命名空间中完成的。
20. 2254 if (__netlink_create(&init_net, sock, cb_mutex, unit) < 0)
21. 2255 goto out_sock_release_nosk;
22. //更新套接字命名空间
23. 2257 sk = sock->sk;
24. 2258 sk_change_net(sk, net);
25. 2259
26. 2260 if (!cfg || cfg->groups < 32)
27. 2261 groups = 32;
28. 2262 else
29. 2263 groups = cfg->groups;
30. 2264
31. 2265 listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
32. 2266 if (!listeners)
33. 2267 goto out_sock_release;
34. 2268
35. 2269 sk->sk_data_ready = netlink_data_ready;
36. 2270 if (cfg && cfg->input)
37. 2271 nlk_sk(sk)->netlink_rcv = cfg->input; //设置netlink消息接收处理函数。
38. 2272
39. 2273 if (netlink_insert(sk, net, 0))
40. 2274 goto out_sock_release;
41. 2275
42. 2276 nlk = nlk_sk(sk);
43. 2277 nlk->flags |= NETLINK_KERNEL_SOCKET;
44. 2278
45. 2279 netlink_table_grab();//将进程放到nl_table_wait等待链表上,并调度其它进程。
46. 2280 if (!nl_table[unit].registered) {
47. 2281 nl_table[unit].groups = groups;
48. 2282 rcu_assign_pointer(nl_table[unit].listeners, listeners);
49. 2283 nl_table[unit].cb_mutex = cb_mutex;
50. 2284 nl_table[unit].module = module;
51. 2285 if (cfg) {
52. 2286 nl_table[unit].bind = cfg->bind;
53. 2287 nl_table[unit].flags = cfg->flags;
54. 2288 }
55. 2289 nl_table[unit].registered = 1;
56. 2290 } else {
57. 2291 kfree(listeners);
58. 2292 nl_table[unit].registered++;
59. 2293 }
60. 2294 netlink_table_ungrab();
61. 2295 return sk;
62. 2296
63. 2297 out_sock_release:
64. 2298 kfree(listeners);
65. 2299 netlink_kernel_release(sk);
66. 2300 return NULL;
67. 2301
68. 2302 out_sock_release_nosk:
69. 2303 sock_release(sock); //内核关闭netlink套接字API。
70. 2304 return NULL;
71. 2305 }

Netlink内核发送消息的内核空间API是:

1. netlink_unicast  
2. netlink_broadcast

发送消息的代码片段如下:

1. if (pid) {  
2. /* unicast */
3. NETLINK_CB(skb).portid = pid;
4. //单播发送法
5. }else{
6. /* broadcast */
7. NETLINK_CB(skb).dst_group = group;
8. //广播发送法