Android wpa_supplicant源码分析---内核通信Netlink简介
本篇文章侧重剖析wpa_supplicant与内核的通信机制linux nl80211与用户空间采用Generic Netlink机制通信,Generic Netlink在netlink删扩展而来,而netlink是基于socekt通信
·
linux nl80211与用户空间采用Generic Netlink机制通信,Generic Netlink在netlink删扩展而来,而netlink是基于socekt通信
注:本文中代码运行环境为:Android M Linux version 3.18.201 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 运行结果,后续补充
从运行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 运行结果,后续补充
更多推荐
已为社区贡献2条内容
所有评论(0)