netlink是一种用于用户空间进程与内核间通信的方法,也可以用于用户进程之间的通信(IPC)。
netlink和ioctl比较相似,都能从用户空间向内核空间通信,但netlink是一种异步通信机制,而ioctl是同步通信机制。且ioctl不能从内核向用户空间发送消息。

下面我们结合实例来进一步了解netlink。我们的例子是用户态发送一串数据给内核态,内核接收到后,返回"I am from kernel!"给用户态程序。

内核态(以内核版本3.13为例)

先来了解下内核中netlink相关的数据结构和函数。

使用netlink_kernel_create()函数创建内核套接字服务,函数类型:
  1. static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
函数返回创建的struct sock对象指针。struct sock (定义在include\linux\net.h) 结构是内核网络系统中最常见的结构之一。它的使用基本贯穿L2,L3,L4层,而且是各层之间的一个联系,无论发送还是接收的数据包都要被存到sock结构中的缓冲队列中。
函数参数net是网络命名空间,如果没自定义网络命名空间的话,就使用内核默认的init_net。
unit是netlink类型,内核已经定义了21个(在include/uapi/linux/netlink.h中),如果要增加我们自己的netlink类型,就必须定义新的值,但要注意,新的值必须小于MAX_LINKS(32)
cfg是netlink的配置信息。该结构体定义如下:
  1. struct netlink_kernel_cfg {
  2.         unsigned int groups;
  3.         unsigned int flags;
  4.         void (*input)(struct sk_buff *skb);
  5.         struct mutex *cb_mutex;
  6.         int (*bind)(struct net *net, int group);
  7.         void (*unbind)(struct net *net, int group);
  8.         bool (*compare)(struct net *net, struct sock *sk);
  9. };
其中最重要的是input域,该函数用于处理用户空间发送到内核方向的数据。内核接收到数据包后,会传给input函数。

来看代码:
  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. #include <linux/timer.h>
  4. #include <linux/time.h>
  5. #include <linux/types.h>
  6. #include <net/sock.h>
  7. #include <net/netlink.h>
  8. #include <linux/string.h>

  9. #define NETLINK_TEST 25
  10. #define MAX_MSGSIZE 1024

  11. struct sock *nl_sk = NULL;

  12. void send_msg(char *message, int pid)
  13. {
  14.         struct sk_buff *skb;
  15.         struct nlmsghdr *nlh;
  16.         int len = NLMSG_SPACE(MAX_MSGSIZE);

  17.         if (!message || !nl_sk) {
  18.                 return;
  19.         }
  20.         printk(KERN_INFO "pid:%d\n", pid);
  21.         skb = alloc_skb(len, GFP_KERNEL);
  22.         if (!skb) {
  23.                 printk(KERN_ERR "send_msg:alloc_skb error\n");
  24.                 return;
  25.         }
  26.         nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
  27.         NETLINK_CB(skb).portid = 0;
  28.         NETLINK_CB(skb).dst_group = 0;
  29.         strcpy(NLMSG_DATA(nlh), message);
  30.         printk(KERN_INFO "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
  31.         netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
  32. }

  33. void recv_nlmsg(struct sk_buff *skb)
  34. {
  35.         int pid;
  36.         struct nlmsghdr *nlh = nlmsg_hdr(skb);

  37.         if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
  38.                 return;

  39.         printk(KERN_INFO "Message received:%s\n", (char*)NLMSG_DATA(nlh));
  40.         pid = nlh->nlmsg_pid;
  41.         send_msg("I am from kernel!", pid);
  42. }

  43. struct netlink_kernel_cfg nl_kernel_cfg = {
  44.         .groups = 0,
  45.         .flags = 0,
  46.         .input = recv_nlmsg,
  47.         .cb_mutex = NULL,
  48.         .bind = NULL,
  49.         .compare = NULL,
  50. };

  51. int netlink_init(void)
  52. {
  53.         nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nl_kernel_cfg);
  54.         if (!nl_sk) {
  55.                 printk(KERN_ERR "my_net_link: create netlink socket error.\n");
  56.                 return 1;
  57.         }
  58.         printk(KERN_INFO "netlink_init: create netlink socket ok.\n");
  59.         return 0;
  60. }

  61. static void netlink_exit(void)
  62. {
  63.         if (nl_sk != NULL)
  64.                 sock_release(nl_sk->sk_socket);

  65.         printk(KERN_INFO "my_net_link: self module exited\n");
  66. }

  67. module_init(netlink_init);
  68. module_exit(netlink_exit);
  69. MODULE_AUTHOR("vv1133");
  70. MODULE_LICENSE("GPL");

程序中,加载模块时,调用netlink_kernel_create()创建套接字。卸载模块时,调用sock_release()释放套接字。
netlink数据包处理函数是recv_nlmsg(),首先通过nlmsg_hdr()函数获取到netlink报头,netlink的消息报头由include/uapi/linux/netlink.h中的nlmsghdr定义:
  1. struct nlmsghdr
  2. {
  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 PID */
  8. };

我们通过NLMSG_DATA获取netlink的payload并打印出来。注意,netlink的payload一般使用TLV格式,即“类型-长度-值”,但这里我们只传递一个固定的字符串,简单起见就不用这个格式了。然后,通过alloc_skb()函数创建一个新的skb,并用nlmsg_put()函数填入netlink结构,填充要发送的字符串。最后调用netlink_unicast()发送数据。

用户态

对于用户态的程序,netlink通信与一般的socket通信类似。只是要用sendmsg()和recvmsg()代替send()/write()和recv()/read()
具体步骤如下:

1. 创建套接字
用户空间使用系统调用socket()创建netlink套接字,需要将域名设置成AF_NETLINK,类型必须是SOCK_RAW或SOCK_DGRAM,协议设置成我们的自定义netlink协议NETLINK_TEST。

2. 将本地套接字与源地址绑定
由于在netlink报头里要设置端口号,我们必须使用bind()函数将指定的端口与socket绑定。

3. 初始化msghdr
a. 设置目的地址信息
  1. dest_addr.nl_family = AF_NETLINK;
  2. dest_addr.nl_pid = 0;
  3. dest_addr.nl_groups = 0;
b. 填充netlink报头,即nlmsghdr结构体
  1. nlh = nlmsg_hdr(skb);
  2. nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  3. nlh->nlmsg_pid = getpid();
  4. nlh->nlmsg_flags = 0;
c. 将缓冲区向量iovec与消息进行绑定,指向消息头。
  1. iov.iov_base = (void *)nlh;
  2. iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
d. 填充msghdr结构体
  1. struct msghdr msg;
  2. msg.msg_name = (void *)&dest_addr;
  3. msg.msg_namelen = sizeof(dest_addr);
  4. msg.msg_iov = &iov;
  5. msg.msg_iovlen = 1;

4. 调用sendmsg向内核发送消息,recvmsg从内核获取消息。

来看代码:
  1. #include <sys/stat.h>
  2. #include <unistd.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <sys/socket.h>
  6. #include <sys/types.h>
  7. #include <string.h>
  8. #include <asm/types.h>
  9. #include <linux/netlink.h>
  10. #include <linux/socket.h>
  11. #include <errno.h>

  12. #define MAX_PAYLOAD 1024
  13. #define NETLINK_TEST 25

  14. int main(int argc, char* argv[])
  15. {
  16.         int state;
  17.         struct sockaddr_nl src_addr, dest_addr;
  18.         struct nlmsghdr *nlh = NULL;
  19.         struct iovec iov;
  20.         struct msghdr msg;
  21.         int sock_fd, retval;
  22.         int state_smg = 0;

  23.         // Create a socket
  24.         sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
  25.         if(sock_fd == -1){
  26.                 printf("error getting socket: %s", strerror(errno));
  27.                 return -1;
  28.         }

  29.         // To prepare binding
  30.         memset(&src_addr, 0, sizeof(src_addr));
  31.         src_addr.nl_family = AF_NETLINK;
  32.         src_addr.nl_pid = 100; //设置源端端口号
  33.         src_addr.nl_groups = 0;

  34.         //Bind
  35.         retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
  36.         if (retval < 0) {
  37.                 printf("bind failed: %s", strerror(errno));
  38.                 close(sock_fd);
  39.                 return -1;
  40.         }

  41.         // To orepare create mssage
  42.         nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
  43.         if (!nlh) {
  44.                 printf("malloc nlmsghdr error!\n");
  45.                 close(sock_fd);
  46.                 return -1;
  47.         }

  48.         memset(&dest_addr,0,sizeof(dest_addr));
  49.         dest_addr.nl_family = AF_NETLINK;
  50.         dest_addr.nl_pid = 0; //设置目的端口号
  51.         dest_addr.nl_groups = 0;
  52.         nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
  53.         nlh->nlmsg_pid = 100; //设置源端口
  54.         nlh->nlmsg_flags = 0;
  55.         strcpy(NLMSG_DATA(nlh), "Hello from client!"); //设置消息体
  56.         iov.iov_base = (void *)nlh;
  57.         iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);

  58.         //Create mssage
  59.         memset(&msg, 0, sizeof(msg));
  60.         msg.msg_name = (void *)&dest_addr;
  61.         msg.msg_namelen = sizeof(dest_addr);
  62.         msg.msg_iov = &iov;
  63.         msg.msg_iovlen = 1;

  64.         //send message
  65.         printf("state_smg\n");
  66.         state_smg = sendmsg(sock_fd,&msg,0);
  67.         if (state_smg == -1) {
  68.                 printf("get error sendmsg = %s\n",strerror(errno));
  69.         }
  70.         memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));

  71.         //receive message
  72.         printf("waiting received!\n");
  73.         state = recvmsg(sock_fd, &msg, 0);
  74.         if (state < 0) {
  75.                 printf("state);
  76.         }
  77.         printf("Received message: %s\n", (char*)NLMSG_DATA(nlh));

  78.         close(sock_fd);
  79.         return 0;
  80. }

运行程序:

加载模块
  1. # sudo insmod netl.ko

运行客户端程序
  1. # ./client
  2. state_smg
  3. waiting
  4. Received message: I am from

查看内核打印
  1. # dmesg
  2. [ 545.278040] my_net_link: self module exited
  3. [ 679.799672] PPP MPPE Compression module registered
  4. [ 4368.567435] netlink_init: create netlink socket ok.
  5. [ 4379.239327] Message received:Hello from
  6. [ 4379.239331] pid:100
  7. [ 4379.239333] my_net_link:send message 'I am from kernel!'.

卸载模块
  1. # sudo rmmod netl

文中的代码可以在这里下载。

12-23 09:01