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

本文转载自:http://blog.csdn.net/shichaog/article/details/44682613

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

14.1 netlink支持的通信

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

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

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

[cpp] view plain copy
 
  1. struct nlmsghdr {  
  2.     __u32       nlmsg_len;  /* Length of message including header */  
  3.     __u16       nlmsg_type; /* Message content */  
  4.     __u16       nlmsg_flags;    /* Additional flags */  
  5.     __u32       nlmsg_seq;  /* Sequence number */  
  6.     __u32       nlmsg_pid;  /* Sending process port ID */  
  7. };  

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

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

Netlink消息类型

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

图14.1 netlink消息格式

14.2 netlink用户空间API

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

 

[cpp] view plain copy
 
  1. static const struct net_proto_family netlink_family_ops = {  
  2.     .family = PF_NETLINK,  
  3.     .create = netlink_create,  
  4.     .owner  = THIS_MODULE,  /* 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。

 

[cpp] view plain copy
 
  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的封装。

[cpp] view plain copy
 
  1. af_netlink.c  
  2. 54 static inline struct sock *  
  3.  55 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)  
  4.  56 {  
  5.  57     return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);  
  6.  58 }  

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

[cpp] view plain copy
 
  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是:

[cpp] view plain copy
 
  1. netlink_unicast  
  2. netlink_broadcast  

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

[cpp] view plain copy
 
  1. if (pid) {  
  2.     /* unicast */  
  3.     NETLINK_CB(skb).portid = pid;  
  4.     ret = netlink_unicast(nlsock, skb, pid, MSG_DONTWAIT);//单播发送法  
  5. }else{  
  6.     /* broadcast */  
  7.     NETLINK_CB(skb).dst_group = group;  
  8.     ret = netlink_broadcast(nlsock, skb, 0, group, 0);//广播发送法  
posted @ 2017-08-01 10:45  请给我倒杯茶  阅读(808)  评论(0编辑  收藏  举报