龙空技术网

内核态与用户态通信方式——Nelink

中科CASAioT 731

前言:

目前我们对“netlink_route”都比较关切,姐妹们都想要剖析一些“netlink_route”的相关知识。那么小编也在网络上汇集了一些有关“netlink_route””的相关知识,希望姐妹们能喜欢,姐妹们快快来学习一下吧!

内核态与用户态通信方式

Linux下内核空间与用户空间进行通信的方式主要有system call、sysctl、procfs、模块参数、debugfs、relayfs、sysfs和netlink等。

Why NetlinK full-duplex

模块参数、sysfs、procfs、debugfs和relayfs都是基于文件系统,用于内核向用户发送消息;sysctl和system call是用户向内核发送消息。它们都是单工通信方式。netlink是一种特殊的通信方式,用于在内核空间和用户空间传递消息,是一种双工通信方式。使用地址协议簇AF_NETLINK,使用头文件include/linux/netlink.h。

simple to add

为新特性添加system call、sysctl或者procfs是一件复杂的工作,它们会污染kernel,破坏系统的稳定性,这是非常危险的。Netlink的添加,对内核的影响仅在于向netlink.h中添加一个固定的协议类型,然后内核模块和应用层的通信使用一套标准的API。 Netlink Socket APIs 用户空间 创建

int socket(int doamin, int type, int protocal);

domain填AF_NETLINK,type为SOCK_RAW或者SOCK_DGRAM。protocal可选NETLINK_ROUTE、NETLINK_FIREWALL、NETLINK_ARPD、NETLINK_ROUTE6和NETLINK_IP6_FW,或者传入自定义协议类型。

绑定

int bind(int fd, (struct sockaddr *)&nladdr, sizeof(nladdr));

netlink地址结构如下

struct sockaddr_nl{ sa_family_t nl_family; //AF_NETLINK unsigned short nl_pad; //0 __u32 nl_pid; //进程pid __u32 nl_groups; //多播组掩码}

如果进程仅仅使用一个netlink,nl_pid可以传入该进程的pid,

my_pid = getpid();

如果一个进程的多个线程需要不同的netlink,使用如下方法获得nl_pid:

pthread_self << 16 | gitpid();

发送

int sendmsg(fd, &msg, 0);

struct nlmsghdr msg为netlink发送消息结构,

struct msghdr{ void *msg_name; //socket name int msg_namelen; //len of name struct iovec *msg_iov; //data block __kernel_size_t msg_iovlen; //num of block void *msg_control; //per protocol magic __kernel_size_t msg_controllen; //len of cmsg list unsigned msg_flags; }

其中需要填充的是msg_iov和msg_oivlen。

msg与其他结构体的关系如图所示

nlmsghdr结构体为消息头信息

struct nlmsghdr{ __u32 nlmsg_len; //len of msg __u16 nlmsg_type; //msg type __u16 nlmsg_flags; //additional flags __u32 nlmsg_seq; //sequence num __u32 nlmsg_pid; //send process pid}

nlmsg_len是整个消息的长度,包含头;

nlmsg_type对内核不透明;

nlmsg_flag被用于对消息的附加控制;

nlmsg_seq和nlmsg_pid被用于跟踪消息,对内核不透明。

要将地址结构体传入消息头部:

struct msghdr msg;msg.msg_name = (void *)&nladdr;msg.msg_namelen = sizeof(nladdr);

发送地址同样使用sockaddr_nl结构体,如果目的是内核,nl_pid和nl_groups都为0;如果消息为单播,目的为另一个进程,nl_pid是目的进程pid,nl_groups为0;如果消息为多播,目的是一个或多个多播组,nl_groups域中需填写所有目的多播组的掩码和。

结构体iovce结构如下

struct iovec iov;iov.iov_base = (void *)nlh;iov.iov_len = nlh->nlmsg_len;msg.msg_iov = &iov;msg.msg_iovlen = 1;

接收

接收时需要创建足够的buf,

struct sockadddr_nl nladdr;struct msghdr msg;struct iovec iov; iov.iov_base = (void *)nlh;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(fd, &msg, 0);

内核空间

内核空间的API由net/core/af_netlink.c支持。

可以在netlink.h中添加自定义协议类型:

#define NETLINK_TEST 17

创建

struct sock *netlink_kernel_create(int unit,  void (*input)(struct sock *sk, int len));

unit是netlink.h中定义的协议类型,*input是一个回调函数指针,接收到消息会触发这个函数。

发送

发送消息使用struct sk_buff *skb。

用以下方式设置本地地址:

NETLINK_CB(skb).groups = local_groups;NETLINK_CB(skb).pid = 0;

因为是在内核,pid = 0

用以下方式设置目的地址:

NETLINK_CB(skb).dst_groups = dst_groups;NETLINK_CB(skb).dst_pid = dst_pid;

发送单播函数为

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

*sk为netlink_kernel_create()返回的结构体指针;

skb->data指向消息;

pid为应用程序的pid;

block表明如果当接收缓冲非法时数据分块还是返回错误值。

发送多播函数为

void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);

group是所有需要接收多播消息的组掩码和;

allocation是内核内存分配标志,中断上下文中使用GFP_ATOMIC,其他情况使用GFP_KERNEL。

销毁

sock_release(nl_sk->socket);

example user-space 初始化

#define MAX_NLMSG_PAYLOAD_LEN 128#define NETLINK_GROUP_MASK(group) (1 << group) int usr_netlink_init(int netlink_type, int *fd, int group){ struct nlmsghdr *nlh = NULL; struct sockaddr_nl addr;  memset((void *)&addr, 0x0, sizeof(addr));  if ((*fd = socket(PF_NETLINK, SOCK_RAW, netlink_type)) < 0) { /* error process*/ return -1; }  addr.nl_family = AF_NETLINK; addr.nl_pid = getpid(); addr.nl_groups = NETLINK_GROUP_MASK(group);  if (bind(*fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { /* error process */ close(*fd); *fd = -1; return -2; }  /* send pid to kernel */ if (us_netlink_send(*fd, 0, NULL, 0) < 0) { /* error process */ close(*fd); *fd = -1; return -3; }  return 0;}

发送

int usr_netlink_send(int fd, uint16 msg_type, char *data, int data_len){ struct nlmsghdr *nlh; struct msghdr msg; struct iovec iov; int msg_len;  if (data && (msg_len > MAX_NLMSG_PAYLOAD_LEN)) { return -1; }  memset((void*)&iov, 0x0, sizeof(iov)); memset((void*)&msg, 0x0, sizeof(msg));  msg_len = NLMSG_SPACE(data_len);  nlh = (struct nlmsghdr *)malloc(msg_len); if (NULL = nlh) { return -2; }  /* fill nlmsghdr */ memset(nlh, 0x0, msg_len); nlh->nlmsg_len = msg_len; nlh->nlmsg_pid = getpid(); nlh->nlmsg_type = msg_type; nlh->nlmsg_flags = 0;  if (data) { memcpy(NLMSG_DATA(nlh), data, msg_len); }  /* fill iovec */ iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_iov = &iov; msg.msg_iovlen = 1;  if (sendmsg(fd, &msg, 0) < 0) { /* error process */ free(nlh); return -3; }  free(nlh); return 0;}

接收

int usr_netlink_recv(int fd, char *buf, int len){ struct iovec iov = {buf, len}; struct sockaddr_nl nl_src_addr; struct msghdr msg; int recv_len;  memset(&msg, 0x0, sizeof(struct msghdr)); memset(&nl_src_addr, 0x0, sizeof(struct sockaddr_nl));  msg.msg_name = (void *)&nl_src_addr; msg.msg_namelen = sizeof(nl_src_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1;  recv_len = recvmsg(fd, &msg, 0); if (recv_len < 0) { /* recv error */ } else if (recv_len == 0) { /* recv EOF */ }  return recv_len;}

kernel 初始化

static struct sock *sg_knl_xxx_sk = NULL;static int sg_knl_xxx_pid = -1; void knl_netlink_init(){  struct net init_net;  sg_knl_xxx_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, knl_neklink_recv, NULL, THIS_MODULE);}

接收

void knl_netlink_recv(struct sock *skb){ struct nlmsghdr *nlh = nlmsg_hdr(skb); sg_knl_xxx_pid = nlh->nlmsg_pid;}

发送

int group_mask(int group){ return (1 << group);} void knl_netlink_send(int msg_type, char *data, int data_len, int group){ struct sk_buff *skb = NULL; struct nlmsghdr *nlh = NULL; unsigned int msg_len = NLMSG_SPACE(data_len);  if(data && (data_len > MAX_PAYLOAD_LEN)) { /* error process */ return; }   skb = alloc_skb(msg_len, GFP_KERNEL); if (!skb) { /* erro process */ return; }  memset((void *)skb, 0x0, msg_len); nlh = nlmsg_put(skb, 0, 0, msg_type, msg_len, 0);  if(data) { memcpy(NLMSG_DATA(nlh), data, data_len); }   NETLINK_CB(skb).pid = 0; /*from kernel */ NETLINK_CB(skb).dst_group = group_mask(group);   /* if unicast */ netlink_unicast(sg_knl_xxx_sk, skb, sg_knl_xxx_pid, MSG_DONTWAIT); /* if mulicast */ //nlmsg_multicast(sg_knl_xxx_sk, skb, 0, group_mask(group), 0);}

标签: #netlink_route #netlink_unicast