netlink和ioctl比较相似,都能从用户空间向内核空间通信,但netlink是一种异步通信机制,而ioctl是同步通信机制。且ioctl不能从内核向用户空间发送消息。
下面我们结合实例来进一步了解netlink。我们的例子是用户态发送一串数据给内核态,内核接收到后,返回"I am from kernel!"给用户态程序。
内核态(以内核版本3.13为例)
先来了解下内核中netlink相关的数据结构和函数。使用netlink_kernel_create()函数创建内核套接字服务,函数类型:
- static inline struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);
函数参数net是网络命名空间,如果没自定义网络命名空间的话,就使用内核默认的init_net。
unit是netlink类型,内核已经定义了21个(在include/uapi/linux/netlink.h中),如果要增加我们自己的netlink类型,就必须定义新的值,但要注意,新的值必须小于MAX_LINKS(32)
cfg是netlink的配置信息。该结构体定义如下:
- struct netlink_kernel_cfg {
- unsigned int groups;
- unsigned int flags;
- void (*input)(struct sk_buff *skb);
- struct mutex *cb_mutex;
- int (*bind)(struct net *net, int group);
- void (*unbind)(struct net *net, int group);
- bool (*compare)(struct net *net, struct sock *sk);
- };
来看代码:
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/timer.h>
- #include <linux/time.h>
- #include <linux/types.h>
- #include <net/sock.h>
- #include <net/netlink.h>
- #include <linux/string.h>
- #define NETLINK_TEST 25
- #define MAX_MSGSIZE 1024
- struct sock *nl_sk = NULL;
- void send_msg(char *message, int pid)
- {
- struct sk_buff *skb;
- struct nlmsghdr *nlh;
- int len = NLMSG_SPACE(MAX_MSGSIZE);
- if (!message || !nl_sk) {
- return;
- }
- printk(KERN_INFO "pid:%d\n", pid);
- skb = alloc_skb(len, GFP_KERNEL);
- if (!skb) {
- printk(KERN_ERR "send_msg:alloc_skb error\n");
- return;
- }
- nlh = nlmsg_put(skb, 0, 0, 0, MAX_MSGSIZE, 0);
- NETLINK_CB(skb).portid = 0;
- NETLINK_CB(skb).dst_group = 0;
- strcpy(NLMSG_DATA(nlh), message);
- printk(KERN_INFO "my_net_link:send message '%s'.\n",(char *)NLMSG_DATA(nlh));
- netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
- }
- void recv_nlmsg(struct sk_buff *skb)
- {
- int pid;
- struct nlmsghdr *nlh = nlmsg_hdr(skb);
- if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
- return;
- printk(KERN_INFO "Message received:%s\n", (char*)NLMSG_DATA(nlh));
- pid = nlh->nlmsg_pid;
- send_msg("I am from kernel!", pid);
- }
- struct netlink_kernel_cfg nl_kernel_cfg = {
- .groups = 0,
- .flags = 0,
- .input = recv_nlmsg,
- .cb_mutex = NULL,
- .bind = NULL,
- .compare = NULL,
- };
- int netlink_init(void)
- {
- nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &nl_kernel_cfg);
- if (!nl_sk) {
- printk(KERN_ERR "my_net_link: create netlink socket error.\n");
- return 1;
- }
- printk(KERN_INFO "netlink_init: create netlink socket ok.\n");
- return 0;
- }
- static void netlink_exit(void)
- {
- if (nl_sk != NULL)
- sock_release(nl_sk->sk_socket);
- printk(KERN_INFO "my_net_link: self module exited\n");
- }
- module_init(netlink_init);
- module_exit(netlink_exit);
- MODULE_AUTHOR("vv1133");
- MODULE_LICENSE("GPL");
程序中,加载模块时,调用netlink_kernel_create()创建套接字。卸载模块时,调用sock_release()释放套接字。
netlink数据包处理函数是recv_nlmsg(),首先通过nlmsg_hdr()函数获取到netlink报头,netlink的消息报头由include/uapi/linux/netlink.h中的nlmsghdr定义:
- struct nlmsghdr
- {
- __u32 nlmsg_len; /* Length of message including header */
- __u16 nlmsg_type; /* Message content */
- __u16 nlmsg_flags; /* Additional flags */
- __u32 nlmsg_seq; /* Sequence number */
- __u32 nlmsg_pid; /* Sending process PID */
- };
我们通过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. 设置目的地址信息
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0;
- dest_addr.nl_groups = 0;
- nlh = nlmsg_hdr(skb);
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = getpid();
- nlh->nlmsg_flags = 0;
- iov.iov_base = (void *)nlh;
- iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
- struct msghdr msg;
- msg.msg_name = (void *)&dest_addr;
- msg.msg_namelen = sizeof(dest_addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
4. 调用sendmsg向内核发送消息,recvmsg从内核获取消息。
来看代码:
- #include <sys/stat.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <string.h>
- #include <asm/types.h>
- #include <linux/netlink.h>
- #include <linux/socket.h>
- #include <errno.h>
- #define MAX_PAYLOAD 1024
- #define NETLINK_TEST 25
- int main(int argc, char* argv[])
- {
- int state;
- struct sockaddr_nl src_addr, dest_addr;
- struct nlmsghdr *nlh = NULL;
- struct iovec iov;
- struct msghdr msg;
- int sock_fd, retval;
- int state_smg = 0;
- // Create a socket
- sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
- if(sock_fd == -1){
- printf("error getting socket: %s", strerror(errno));
- return -1;
- }
- // To prepare binding
- memset(&src_addr, 0, sizeof(src_addr));
- src_addr.nl_family = AF_NETLINK;
- src_addr.nl_pid = 100; //设置源端端口号
- src_addr.nl_groups = 0;
- //Bind
- retval = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
- if (retval < 0) {
- printf("bind failed: %s", strerror(errno));
- close(sock_fd);
- return -1;
- }
- // To orepare create mssage
- nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
- if (!nlh) {
- printf("malloc nlmsghdr error!\n");
- close(sock_fd);
- return -1;
- }
- memset(&dest_addr,0,sizeof(dest_addr));
- dest_addr.nl_family = AF_NETLINK;
- dest_addr.nl_pid = 0; //设置目的端口号
- dest_addr.nl_groups = 0;
- nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
- nlh->nlmsg_pid = 100; //设置源端口
- nlh->nlmsg_flags = 0;
- strcpy(NLMSG_DATA(nlh), "Hello from client!"); //设置消息体
- iov.iov_base = (void *)nlh;
- iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);
- //Create mssage
- memset(&msg, 0, sizeof(msg));
- msg.msg_name = (void *)&dest_addr;
- msg.msg_namelen = sizeof(dest_addr);
- msg.msg_iov = &iov;
- msg.msg_iovlen = 1;
- //send message
- printf("state_smg\n");
- state_smg = sendmsg(sock_fd,&msg,0);
- if (state_smg == -1) {
- printf("get error sendmsg = %s\n",strerror(errno));
- }
- memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
- //receive message
- printf("waiting received!\n");
- state = recvmsg(sock_fd, &msg, 0);
- if (state < 0) {
- printf("state);
- }
- printf("Received message: %s\n", (char*)NLMSG_DATA(nlh));
- close(sock_fd);
- return 0;
- }
运行程序:
加载模块
- # sudo insmod netl.ko
运行客户端程序
- # ./client
- state_smg
- waiting
- Received message: I am from
查看内核打印
- # dmesg
- [ 545.278040] my_net_link: self module exited
- [ 679.799672] PPP MPPE Compression module registered
- [ 4368.567435] netlink_init: create netlink socket ok.
- [ 4379.239327] Message received:Hello from
- [ 4379.239331] pid:100
- [ 4379.239333] my_net_link:send message 'I am from kernel!'.
卸载模块
- # sudo rmmod netl
文中的代码可以在这里下载。