linux nl80211与用户空间采用Generic Netlink机制通信,Generic Netlink在netlink删扩展而来,而netlink是基于socekt通信

注:本文中代码运行环境为:Android M Linux version 3.18.20

1 netlnik介绍

netlink用于用户控件与内核空间的通信的一组API,是在socket的机制上实现的。
socket是做网络连接的API,用户空间的进程将数据传递到内核,然后内核在将数据传递到网卡驱动。
netlink是在原有socket的基础上扩展而来,使用netlink通信时,用户空间传递到内核中相应的模块中,同时相应的模块也可以传到数据到用户空间的进程中。
具体socket是如何实现用户空间和内核通信就不清楚了。


我了解的用户空间和内核模块有3方式通信,read/write方法和ioctl方法
相比与其他用户空间与内核的通信机制,netlink有以下优点:
1 全双工
由于socket的全双工特性,一条netlink连接可以同时用于用户空间和内核空间的互传消息

2 内核空间向用户空间的多播传输
netlnik机制中支持用户空间的socket bind到单个端口(port id)或者某个组(group),若是bind到端口,则采用的是一对一单播通信,
若采用的是bind到某个group,采用的是一(内核)对多(用户)的通信,所有加入该group的用户空间socekt都会接收到内核发来的消息
注:如何实现用户空间对内核的一对多通信,目前还没有看到实例

2 netlnik使用简介

代码位置:
kernel/include/uapi/linux/netlink.h
kernel/include/net/netlink.h
kernel/net/netlink/af_netlink.c

参考文章http://bbs.chinaunix.net/thread-2029813-1-1.html,文章中代码比较老了,在最新内核中已经不适用了


2.1 socekt创建

用户空间和内核空间通过相同的 protocol/unit 创建通信socket:
可以自己定义新的protocol,也可以现有的(头文件查看),只要保证用户空间和内核空间一致即可
user:
int  socket(int protofamily, int type, int protocol)
kernel:
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

PF_NETLINK与AF_NETLINK的值一样,只不过一个用于protocal 用于socket创建,address用于sockaddr_nl的family字段。
/* Protocol families, same as address families. */
#define PF_NETLINK AF_NETLINK




3 单播

3.1 用户空间

此处使用的还是比较底层的socket API,后来看libnl代码时,发现netlnik有提供更为便捷的API
socekt创建
sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
struct sockaddr_nl src_addr;
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid();
bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
addr的nl_pid字段需要定义当前socket上*唯一*的一个port id,一般采用当前进程的pid,如果是同一进程的线程的不同线程,通常是pid和tid共同生成。
当两个socket bind同一个port id时,只能有一个socekt接收到,好像网络上的某些攻击就是采用的此方式。

发送

sendmsg(sock_fd, &msg, 0);


接收
recvmsg(sock_fd, &msg, 0);

3.2内核空间

socekt创建:
struct netlink_kernel_cfg cfg = {
.input = test_rcv,
}
cfg中定义了接收函数
nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);


发送
user_pid需要和用户空间bind的port id一致
nlmsg_unicast(nl_sk, skb_send, user_pid)

port id可以从接收到数据skb->data中的nlmsghdr头中获取到,

因此单播模式一般来说需要用户空间向内核发送消息后,内核才可以向用户空间发送


3.3 代码示例

user:

#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  /* 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[]) 
{
	int ret;

	printf("NETLINK_TEST:%d\n", NETLINK_TEST);
	sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
	if (!sock_fd) {
		printf("get error socekt creat %d:%s\n",errno, strerror(errno));
		return -errno;
	}
		
	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_pid = 4567; //port id
	printf("self pid:%d\n", src_addr.nl_pid);
	//src_addr.nl_groups = 0;  /* not in mcast groups */

	ret = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
	if (ret < 0) {
		printf("bind sock error:%d errno %d:%s\n", ret, errno, strerror(errno));
		close(sock_fd);
		return -1;
	}
        
	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_pid = 4567; //same with src_addr.nl_pid, kernel use it to get src port
	nlh->nlmsg_flags = 0;
	/* Fill in the netlink message payload */
	strcpy(NLMSG_DATA(nlh), argv[1]);
	printf("nlmsg_len:%d, nlh_pid:%d, msg:%s\n", nlh->nlmsg_len, nlh->nlmsg_pid, (char*)NLMSG_DATA(nlh));
	
	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;

	sendmsg(sock_fd, &msg, 0);

	/* Read message from kernel */
	memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
	printf("rev nlh_pid:%d\n", nlh->nlmsg_pid);
    recvmsg(sock_fd, &msg, 0);
    printf(" Received message pid:%d, payload: %s\n", nlh->nlmsg_pid, NLMSG_DATA(nlh));

	memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
	recvmsg(sock_fd, &msg, 0);
	printf(" Received message pid:%d, payload: %s\n", nlh->nlmsg_pid, NLMSG_DATA(nlh));
    close(sock_fd);
	return 0;
}

kernel:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <net/net_namespace.h>
#include <linux/netlink.h>
//#include <errno.h>

//#define NETLINK_TEST 40
struct sock *nl_sk = NULL;

char *msg_send = "hello from kernel";


static void test_rcv(struct sk_buff *skb)
{
	struct nlmsghdr *nlh;
	void* msg = NULL;
	struct sk_buff *skb_send;
	int msg_size;
	int user_pid;

	//down_read(&cb_lock);
	printk("test_rcv:skb->len:%u\n", skb->len);
	//想要使用netlink_rcv_skb方式,需要设置好flag和type
	//netlink_rcv_skb(skb, &test_rcv_msg);
	
	nlh = nlmsg_hdr(skb);
	user_pid = nlh->nlmsg_pid;
	printk("nlh->nlmsg_len %u, nlh->nlmsg_flags %u, nlh->nlmsg_pid %u\n", nlh->nlmsg_len, nlh->nlmsg_flags, nlh->nlmsg_pid);
	msg = NLMSG_DATA(nlh);
	printk("rcv msg: %s\n", (char*)msg);

	if (strstr((char*)msg, "no send") != NULL)
		return;
	
	printk("send msg:%s\n", msg_send);
	msg_size = strlen(msg_send);
	skb_send = nlmsg_new(msg_size, 0);
	nlh = nlmsg_put(skb_send, 0, 0, NLMSG_DONE, msg_size, 0);
	strncpy(nlmsg_data(nlh), msg_send, msg_size);
	
	NETLINK_CB(skb_send).portid = 3333;
	NETLINK_CB(skb_send).dst_group = 0;
	if (nlmsg_unicast(nl_sk, skb_send, user_pid) < 0)
		printk("error send to user\n");
	 
	
	//up_read(&cb_lock);
}


int __init test_netlink_init(void) 
{
	struct netlink_kernel_cfg cfg = {
	.input		= test_rcv,
	//.flags		= NL_CFG_F_NONROOT_RECV,
	//.groups		= 1,
	};

	printk("nl_sk NETLINK_TEST:%d\n", NETLINK_TEST);
	nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
	
	if (nl_sk == NULL){
		printk("nl_sk creat fail, error!\n");
		return 0;
	}
	printk("nl_sk creat ok!\n");
	return 0;
}

module_init(test_netlink_init);

void __exit test_netlink_exit(void)
{
	if(nl_sk == NULL) {
		printk("nl_sk = null!\n");
		return;
	}
	netlink_kernel_release(nl_sk);
}
module_exit(test_netlink_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cuijiyue");


3.4 运行结果,后续补充

从运行log中可以看到只需要user空间在bind是设置的pid和kernel发送单播时传入的pid一致,就可以正常通信,skb_snd.portid中的值可以设置任意值




4 多播

4.1 用户空间

在bind时,pid指定为0,group id按如下方式指定
src_addr.nl_groups = 1 << (group_id-1);
接收函数和单播时相同
recvmsg(sock_fd, &msg, 0);


4.2 内核空间

创建socekt代码不变
发送数据时需要设置和用户空间相同的group_id
netlink_broadcast(nl_sk, skb_send, 0, group_id, 0);


4.3 代码实例:

为方便测试,内核中循环发送多条消息

kernel:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <net/net_namespace.h>
#include <linux/netlink.h>
#include <linux/kthread.h>
//#include <errno.h>

//#define NETLINK_TEST 40
struct sock *nl_sk = NULL;

char *msg_send = "hello from kernel";
int group_test = 5;


static void test_rcv(struct sk_buff *skb)
{
	struct nlmsghdr *nlh;
	void* msg = NULL;
	struct sk_buff *skb_send;
	int msg_size;
	int user_pid;

	//down_read(&cb_lock);
	printk("test_rcv:skb->len:%u\n", skb->len);
	//想要使用netlink_rcv_skb方式,需要设置好flag和type
	//netlink_rcv_skb(skb, &test_rcv_msg);
	
	nlh = nlmsg_hdr(skb);
	user_pid = nlh->nlmsg_pid;
	printk("nlh->nlmsg_len %u, nlh->nlmsg_flags %u, nlh->nlmsg_pid %u\n", nlh->nlmsg_len, nlh->nlmsg_flags, nlh->nlmsg_pid);
	msg = NLMSG_DATA(nlh);
	printk("rcv msg: %s\n", (char*)msg);

	if (strstr((char*)msg, "no send") != NULL)
		return;
	
	printk("send msg:%s\n", msg_send);
	msg_size = strlen(msg_send);
	skb_send = nlmsg_new(msg_size, 0);
	nlh = nlmsg_put(skb_send, 0, 0, NLMSG_DONE, msg_size, 0);
	strncpy(nlmsg_data(nlh), msg_send, msg_size);
	
	NETLINK_CB(skb_send).portid = 3333;
	NETLINK_CB(skb_send).dst_group = 0;
	if (nlmsg_unicast(nl_sk, skb_send, user_pid) < 0)
		printk("error send to user\n");
	 
	
	//up_read(&cb_lock);
}

static int send_multi(void *data)
{
	struct nlmsghdr *nlh;
	struct sk_buff *skb_send;
	int msg_size;
	int ret;
	int i = 0;
	char msg[100] = {0};
	for(i = 0; i<10; i++){
	ssleep(3);
	sprintf(msg, "%s_%d", msg_send, i);
	printk("nl_sk send msg:%s\n", msg);

	msg_size = strlen(msg);
	skb_send = nlmsg_new(NLMSG_ALIGN(msg_size+1), 0);
	nlh = nlmsg_put(skb_send, 0, 1, NLMSG_DONE, msg_size + 1, 0);
	strcpy(nlmsg_data(nlh), msg);
	
	NETLINK_CB(skb_send).portid = 0;
	NETLINK_CB(skb_send).dst_group = group_test;
	ret = netlink_broadcast(nl_sk, skb_send, 0, group_test, 0);

	if (ret < 0)
			printk("nl_sk error multi send to user, ret:%d\n", ret);
	}
	return 0;
}

int __init test_netlink_init(void) 
{
	struct netlink_kernel_cfg cfg = {
	.input		= test_rcv,
	//.flags		= NL_CFG_F_NONROOT_RECV,
	//.groups		= 1,
	};

	printk("nl_sk NETLINK_TEST:%d\n", NETLINK_TEST);
	nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
	
	if (nl_sk == NULL){
		printk("nl_sk creat fail, error!\n");
		return 0;
	}
	printk("nl_sk creat ok! sleep 10s then send msg\n");
	kthread_run(send_multi, NULL, "kernel_send");
	//send_multi(msg_send);
	printk("nl_sk init over\n");
	return 0;
}

module_init(test_netlink_init);

void __exit test_netlink_exit(void)
{
	if(nl_sk == NULL) {
		printk("nl_sk = null!\n");
		return;
	}
	netlink_kernel_release(nl_sk);
}
module_exit(test_netlink_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cuijiyue");

user:

#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 <netlink/genl/genl.h>
#include <netlink/genl/family.h>
#include <netlink/genl/ctrl.h> 
#include <errno.h>


#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 group_test = 5;

// example nl_connect()
int main(int argc, char* argv[]) 
{
	int ret;
	int i;

	printf("NETLINK_TEST:%d\n", NETLINK_TEST);
	sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
	if (!sock_fd) {
		printf("get error socekt creat %d:%s\n",errno, strerror(errno));
		return -errno;
	}
		
	memset(&msg, 0, sizeof(msg));
	memset(&src_addr, 0, sizeof(src_addr));
	src_addr.nl_family = PF_NETLINK;
	src_addr.nl_pid = 0;  /* multi not use port id */
	//this way not work, use
	src_addr.nl_groups = 1 << (group_test-1);
	
	//bind should after kernel netlink creat
	ret = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
	if (ret < 0) {
		printf("bind sock error:%d errno %d:%s\n", ret, errno, strerror(errno));
		close(sock_fd);
		return -1;
	}

	//ret = setsockopt(sock_fd, 270, NETLINK_ADD_MEMBERSHIP, &group_test, sizeof(group_test));
	//ret = nl_socket_add_membership(sock_fd, group_test);
	if (ret < 0) {
		printf("set sock error:%d errno %d:%s\n", ret, errno, strerror(errno));
		close(sock_fd);
		return -1;
	}
      
	memset(&dest_addr, 0, sizeof(dest_addr));

	nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
	nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);

	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;


	/* Read message from kernel */
	printf("waiting rev\n");
	for (i = 0; i<11; i++){
        	recvmsg(sock_fd, &msg, 0);
        	printf(" Received message pid:%d, payload: %s\n", nlh->nlmsg_pid, NLMSG_DATA(nlh));
	}
	close(sock_fd);
	return 0;
}

4.4 运行结果,后续补充






Logo

更多推荐