您的位置:首页 > 其它

wpa_supplicant与内核nl80211通信之Netlink

2016-06-06 18:25 260 查看
linux nl80211与用户空间采用Generic Netlink机制通信,Generic Netlink在netlink删扩展而来,而netlink是基于socekt通信

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 运行结果,后续补充

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 运行结果,后续补充

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: