【转载】Linux netlink socket使用总结
2018-02-22 14:35
323 查看
http://blog.csdn.net/sprintwind/article/details/44204499
一、netlink机制的引入
Linux提供了多种机制来完成内核空间与用户空间之间的数据交换,分别有内核启动参数、模块参数、sysfs、sysctl、系统调用、procfs、seq_file、debugfs、relayfs。其中,模块参数、sysfs、sysctl、procfs、seq_file、debugfs、relayfs是基于文件系统的通信机制,用于内核空间向用户空间输出信息;sysctl、系统调用是由用户空间发起的通信机制。以上均为单工通信机制,在内核空间与用户空间的双向数据交换上略显不足。netlink是基于socket的通信机制,由于socket本身的双工性、突发性、不阻塞性等特点,能够很好地满足内核空间与用户空间小量数据的及时交互,因此在Linux 2.6内核开始被广泛使用,例如内核态的netfilter与用户态的iptables的数据交换就是通过netlink机制完成的。
二、netlink机制的优点
netlink相对于其他的通信机制具有以下优点:1. 使用netlink通过自定义一种新的协议并加入协议族即可通过socket API使用netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
2. netlink使用socket缓冲队列,是一种异步通信机制,而ioctl是同步通信,如果传输数据量较大会影响系统性能。
3. netlink支持多播,属于一个netlink组的模块和进程都能获得该多播消息。
4. netlink允许内核发起会话,而ioctl和系统调用只能由用户空间发起。
三、netlink机制的使用
3.1 用户态使用netlink
用户态使用标准的socket API如socket,bind,sendmsg,recvmsg和close等接口就能很容易地使用netlink socket。3.1.1 创建netlink socket
使用如下参数创建一个netlink socket:socket(AF_NETLINK/PF_NETLINK, int type, int protocol);1
其中type可以是SOCK_RAW或SOCK_DGRAM,protocol指定netlink协议类型,目前已经支持的协议类型在linux/netlink.h中定义:
#define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unused number */ #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ #define NETLINK_FIREWALL 3 /* Firewalling hook */ #define NETLINK_INET_DIAG 4 /* INET socket monitoring */ #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ #define NETLINK_XFRM 6 /* ipsec */ #define NETLINK_SELINUX 7 /* SELinux event notifications */ #define NETLINK_ISCSI 8 /* Open-iSCSI */ #define NETLINK_AUDIT 9 /* auditing */ #define NETLINK_FIB_LOOKUP 10 #define NETLINK_CONNECTOR 11 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ #define NETLINK_IP6_FW 13 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 /* leave room for NETLINK_DM (DM Events) */ #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 201
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
3.1.2 将netlink socket与进程进行绑定
函数bind用于把一个打开的netlink socket与进程进行绑定,需要进行绑定的netlink socket地址结构如下:struct sockaddr_nl { sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ __u32 nl_groups; /* multicast groups mask */ };1
2
3
4
5
6
7
其中nl_family固定为AF_NETLINK或PF_NETLINK,nl_pad为填充字段,设置为0,nl_pid设置为当前进程的PID,nl_groups用于指定多播组,每一个bit对应一个多播组,如果设置为0,表示不加入任何多播组。
填充完netlink socket地址后,就可以调用bind接口进行绑定了,调用方法如下:
bind(sock_fd, (struct sockaddr*)&nl_addr, sizeof(&nl_addr));1
3.1.3 发送netlink消息
为了能够把netlink消息发送给内核或者别的用户进程,需要使用另外一个结构体struct sockaddr_nl作为目的地址。如果消息是发往内核的话,nl_pid和nl_groups都应该设置为0,;如果消息是发往另一个进程,nl_pid应该设置为接受者进程的PID,nl_groups用于设置需要发往的多播组。填充好了目的地址后,就可以将netlink地址应用到结构体struct msghdr中,供函数sendmsg来调用:
struct msghdr msg; msg.msg_name = (void *)&nladdr; msg.msg_namelen = sizeof(nladdr);1
2
3
由于linux内核的netlink部分总是认为每个netlink消息体中已经包含了下面的消息头,所以每个应用程序在发送netlink消息之前需要提供这个头信息:
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 port ID */ };1
2
3
4
5
6
7
nlmsg_len需要填充为包含netlink消息头的整个netlink消息的长度,nlmsg_type和nlmsg_flags根据不同应用的需要进行填充,nlmsg_seq是用于跟踪消息的序号,nlmsg_pid设置为当前进程的PID。
填充完消息头后,在消息头后面就可以填充消息体的内容了,填充完消息体,使用struct iovec结构体,使iov_base指向包含netlink消息的缓冲区,即可调用sendmsg接口发送netlink消息。struct iovec结构定义如下:
struct iovec { void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */ __kernel_size_t iov_len; /* Must be size_t (1003.1g) */ };1
2
3
4
5
发送netlink消息的代码如下:
struct iovec iov; iov.iov_base = (void *)msg_buffer; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(sock_fd, &msg, 0);1
2
3
4
5
6
接收netlink消息
接收程序需要申请足够大的空间来存储netlink消息头和消息的payload部分。用如下的方式填充结构体struct msghdr,然后调用recvmsg接口来接收netlink消息:char msg_buffer[MSX_NL_MSG_LEN]; struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; iov.iov_base = (void *)msg_buffer; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(sock_fd, &msg, 0);1
2
3
4
5
6
7
8
9
10
11
12
13
14
当消息正确接收后,msg_buffer里保存包含netlink消息头的整个netlink消息,nladdr包含接收到的消息体的目的地址信息。
3.1.5 关闭netlink socket
使用完前面创建的netlink socket后,就可以使用close接口关闭netlink socket,释放socket资源。关闭netlink socket的代码与关闭其他socket一致,代码如下:close(sock_fd);1
3.2 内核态使用netlink socket
内核空间的netlink API接口是由内核中的netlink核心代码支持的,在net/core/af_netlink.c中实现。内核模块通过这些API访问netlink socket并与用户空间的程序进行通信。3.2.1 添加自定义协议类型
内核已经支持的协议类型在linux/netlink.h中定义,如果要使用自定义的协议类型,需要在此文件中新增一个协议类型,然后就可以在linux内核模块中使用这个协议类型了。3.2.2 在内核创建netlink socket
在内核空间,通过如下接口创建netlink socket:struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module);1
2
3
4
5
6
参数net为网络设备名称,一般传入&init_net即可;unit为协议类型,传入自定义的协议类型;groups指定netlink消息有多少个组,一般情况下传入0即可;input是用于netlink socket在收到消息时调用的处理消息的函数指针;cb_mutex是用于内核处理netlink socket消息时使用的互斥锁,一般情况下传入NULL即可;module指定创建的netlink socket所属的内核模块,一般情况下传入THIS_MODULE。
在内核创建了netlink socket后,当用户程序发送一个netlink消息到内核时,函数input都会被调用。下面是一个实现了消息处理函数input的例子:
void input (struct sock *sk, int len) { struct sk_buff *skb; struct nlmsghdr *nlh = NULL; u8 *payload = NULL; while ((skb = skb_dequeue(&sk->receive_queue))!= NULL) { /* process netlink message pointed by skb->data */ nlh = (struct nlmsghdr *)skb->data; payload = NLMSG_DATA(nlh); /* process netlink message with header pointed by * nlh and payload pointed by payload */ } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
回调函数input是在用户进程调用sendmsg系统调用时被调用的。因此该函数中的处理时间不能太长,否则会导致其他系统调用被阻塞。比较好的做法是在该函数中创建一个新的内核线程来处理netlink socket消息。
3.2.3 在内核中发送netlink消息
在内核空间发送netlink消息时有两个接口可以使用,netlink_unicast接口用来发送一个单播消息,其定义如下:int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)1
2
ssk是调用netlink_kernel_create接口所创建的socket控制块,skb中的data指向需要发送的netlink消息体,pid为要发往的用户进程的PID,nonblock指示当发送缓冲区不可用时尝试发送的超时时间。
netlink消息的源地址和目的地址可以通过如下代码进行设置:
NETLINK_CB(skb).groups = local_groups; NETLINK_CB(skb).pid = 0; /* from kernel */ NETLINK_CB(skb).dst_groups = dst_groups; NETLINK_CB(skb).dst_pid = dst_pid;1
2
3
4
使用netlink_broadcast接口可以发送一个多播消息,其定义如下:
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)1
2
其中group是接收消息的各个组的比特位进行与运算的结果。allocation是内核申请内存的类型,通常情况下在中断上下文中使用GFP_ATOMIC,否则使用GFP_KERNEL。
在内核中关闭netlink socket
假设netlink_kernel_create函数返回的netlink socket为struct sock *nl_sk,我们可以通过如下API来关闭这个netlink socket:sock_release(nl_sk->socket);1
sock_release(nl_sk->socket);1
四、netlink socket使用实例
如下代码是一个使用netlink机制的简单实例:netlink_test_user.c:
#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> #define NETLINK_TEST 21 #define MAX_PAYLOAD 1024 /* maximum payload size*/ struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; int sock_fd; struct msghdr msg; int main(int argc, char* argv[]) { sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); memset(&msg, 0, sizeof(msg)); memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); /* self pid */ src_addr.nl_groups = 0; /* not in mcast groups */ bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; /* For Linux Kernel */ dest_addr.nl_groups = 0; /* unicast */ nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); /* Fill the netlink message header */ nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); /* self pid */ nlh->nlmsg_flags = 0; /* Fill in the netlink message payload */ strcpy(NLMSG_DATA(nlh), "This is a netlink test !"); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; printf(" Sending message. ...\n"); sendmsg(sock_fd, &msg, 0); /* Read message from kernel */ memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); printf(" Waiting message. ...\n"); recvmsg(sock_fd, &msg, 0); printf(" Received message payload: %s\n",NLMSG_DATA(nlh)); /* Close Netlink Socket */ close(sock_fd);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
netlink_test_kernel.c:
#include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> #include <linux/sched.h> #include <net/sock.h> #include <net/netlink.h> #define NETLINK_TEST 21 struct sock *nl_sk = NULL; EXPORT_SYMBOL_GPL(nl_sk); void nl_data_ready (struct sk_buff *__skb) { struct sk_buff *skb; struct nlmsghdr *nlh; u32 pid; int rc; int len = NLMSG_SPACE(1200); char str[100]; printk("net_link: data is ready to read.\n"); skb = skb_get(__skb); if (skb->len >= NLMSG_SPACE(0)) { nlh = nlmsg_hdr(skb); printk("net_link: recv %s.\n", (char *)NLMSG_DATA(nlh)); memcpy(str,NLMSG_DATA(nlh), sizeof(str)); pid = nlh->nlmsg_pid; /*pid of sending process */ printk("net_link: pid is %d\n", pid); kfree_skb(skb); skb = alloc_skb(len, GFP_ATOMIC); if (!skb){ printk(KERN_ERR "net_link: allocate failed.\n"); return; } nlh = nlmsg_put(skb,0,0,0,1200,0); NETLINK_CB(skb).pid = 0; /* from kernel */ memcpy(NLMSG_DATA(nlh), str, sizeof(str)); printk("net_link: going to send.\n"); rc = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT); if (rc < 0) { printk(KERN_ERR "net_link: can not unicast skb (%d)\n", rc); } printk("net_link: send is ok.\n"); } return; } static int test_netlink(void) { nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE); if (!nl_sk) { printk(KERN_ERR "net_link: Cannot create netlink socket.\n"); return -EIO; } printk("net_link: create socket ok.\n"); return 0; } int init_module() { test_netlink(); return 0; } void cleanup_module( ) { if (nl_sk != NULL){ sock_release(nl_sk->sk_socket); } printk("net_link: remove ok.\n"); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("sprintwind");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Makefile:
MODULE_NAME := netlink_test_kernel obj-m :=$(MODULE_NAME).o KERNEL_VER := $(shell uname -r) KERNEL_DIR := /lib/modules/$(KERNEL_VER)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNEL_DIR) M=$(PWD) gcc -o netlink_test_user netlink_test_user.c clean: rm -fr *.ko *.o *.cmd netlink_test_user $(MODULE_NAME).mod.c1
2
3
4
5
6
7
8
9
10
11
12
使用方法:将这3个文件放在同一个目录,修改Makefile的属性为可执行,执行make命令进行编译,将编译出来的内核模块使用insmod命令插入内核,然后执行./netlink_test_user查看运行结果。内核模块的打印可以通过dmesg命令查看。
相关文章推荐
- Linux netlink socket使用总结
- Linux下的packet socket使用总结
- linux socket使用经验总结
- [转载] Linux下查看内存使用情况方法总结
- [Android][Linux][Netlink]内核态和用户态使用netlink方法总结
- Linux Raw Socket使用总结
- Linux下的packet socket使用总结
- Linux C语言编程-Linux网络通信--Linux上使用套接字(socket)来发送信息---知识点总结+实例
- linux中命令 which/whereis/locate/find 的使用总结(总结+转载)
- 在linux下使用用Valgrind查找内存泄漏和无效内存访问(转载)
- 转载:extern使用方法总结!
- 使用linux过程中遇到的问题总结
- 使用socket的Linux上的C语言helloworld
- 转载:Windows Socket API 使用经验
- 使用socket的Linux上的C语言文件传输顺序服务器和客户端示例程序
- 使用socket的Linux上的C语言文件传输顺序服务器和客户端示例程序
- 使用socket的Linux上的C语言helloworld多线程服务器和客户端测试程序
- 为何以及如何使用Netlink Socket ( Why and How to Use Netlink Socket)
- 关于在neoshine linux下使用电视卡遇到的问题和总结
- (转载)使用kgdb调试linux内核及内核模块