wpa_supplicant与内核nl80211通信之Netlink
2016-06-06 18:25
260 查看
linux nl80211与用户空间采用Generic Netlink机制通信,Generic Netlink在netlink删扩展而来,而netlink是基于socekt通信
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都会接收到内核发来的消息
注:如何实现用户空间对内核的一对多通信,目前还没有看到实例
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,文章中代码比较老了,在最新内核中已经不适用了
可以自己定义新的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
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);
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头中获取到,
因此单播模式一般来说需要用户空间向内核发送消息后,内核才可以向用户空间发送
kernel:
src_addr.nl_groups = 1 << (group_id-1);
接收函数和单播时相同
recvmsg(sock_fd, &msg, 0);
发送数据时需要设置和用户空间相同的group_id
netlink_broadcast(nl_sk, skb_send, 0, group_id, 0);
kernel:
user:
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有提供更为便捷的APIsocekt创建
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 运行结果,后续补充
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 运行结果,后续补充
相关文章推荐
- CodeForces 300A Array
- JedisRedirectionException:Too many Cluster redirections
- ctags简易用法
- wifi协议-802
- RandomAccess接口
- 第二阶段冲刺第七天,6月6日。
- 第二阶段冲刺6(6月5号)
- 在PPT中插入网页技巧
- Android蓝牙与全站仪蓝牙仪器数据交互
- 安卓让自己的应用处在栈顶
- Android_Service(基础篇)
- 改版一位同学的刮刮卡效果源码
- 模仿password输入框
- document.getElementById("xx").style.xxx中的所有属性
- [整体二分 树状数组套线段树] BZOJ 2674 Attack
- bitmap的几个用法
- 『Python学习』MySQL学习
- 第二阶段冲刺第六天,6月5日。
- 获取当前应用信息
- 通过 SysVinit、Systemd 和 Upstart 管理系统自启动进程和服务