|
一、介绍
netlink是一种用于用户空间进程与内核间通信的方法,也可以用于用户进程之间的通信(IPC)。
netlink和ioctl比较相似,都能从用户空间向内核空间通信,但netlink是一种异步通信机制,而ioctl是同步通信机制。且ioctl不能从内核向用户空间发送消息。
二、实现原理
1、用户态和内核交换过程
用户态 | 内核 | socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) | netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) 初始化内核netlink 服务端,注册回调函数 | bind | cfg.input 消息接收后处理回调函数 | sendto/recvfrom | netlink_unicast 广播回复消息 | 用户态和内核的交换如下图所示:

2、关键函数和数据结构
nlmsghdr netlink msg报文头
struct nlmsghdr {
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process port ID */
};netlink_kernel_create 创建netlink内核通信服务端,定义如下,uinit就是魔术字表示使用哪种协议,下面使用的是自定义的魔术字 NETLINK_TEST,通常设置网络参数使用的是NETLINK_ROUTE 魔术字
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
{
return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);
}netlink_kernel_cfg 结构体注册接受到消息后处理回调函数,定义如下:
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb); //消息接收后触发回调函数
struct mutex *cb_mutex;
int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group);
bool (*compare)(struct net *net, struct sock *sk);
};netlink_unicast netlink消息在内核中都是通过广播的方式发送,根据目的端口号找到发送的客户端
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 portid, int nonblock)三、实例分析
自定义的 NETLINK_TEST 通信协议
用户态测试程序netlink如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#define NETLINK_TEST 30
#define MSG_LEN 125
#define MAX_PLOAD 125
typedef struct _user_msg_info
{
struct nlmsghdr hdr;
char msg[MSG_LEN];
} user_msg_info;
int main(int argc, char **argv)
{
int skfd;
int ret;
user_msg_info u_info;
socklen_t len;
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl saddr, daddr; //saddr 表示源端口地址,daddr表示目的端口地址
char *umsg = &#34;hello netlink!!&#34;;
/* 创建NETLINK socket */
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
if(skfd == -1)
{
perror(&#34;create socket error\n&#34;);
return -1;
}
memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK; //AF_NETLINK
saddr.nl_pid = 100; //端口号(port ID)
saddr.nl_groups = 0;
if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
{
perror(&#34;bind() error\n&#34;);
close(skfd);
return -1;
}
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0; // to kernel
daddr.nl_groups = 0;
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
memset(nlh, 0, sizeof(struct nlmsghdr));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = saddr.nl_pid; //self port
memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg)); // 拷贝发送的数据到报文头指向内存后面
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
if(!ret)
{
perror(&#34;sendto error\n&#34;);
close(skfd);
exit(-1);
}
printf(&#34;send kernel:%s\n&#34;, umsg);
memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
if(!ret)
{
perror(&#34;recv form kernel error\n&#34;);
close(skfd);
exit(-1);
}
printf(&#34;from kernel:%s\n&#34;, u_info.msg);
close(skfd);
free((void *)nlh);
return 0;
}内核模块程序netlink_test.c 代码如下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
#define NETLINK_TEST 30
#define MSG_LEN 125
#define USER_PORT 100
MODULE_LICENSE(&#34;GPL&#34;);
MODULE_AUTHOR(&#34;xx&#34;);
MODULE_DESCRIPTION(&#34;netlink example&#34;);
struct sock *nlsk = NULL;
extern struct net init_net;
int send_usrmsg(char *pbuf, uint16_t len)
{
struct sk_buff *nl_skb;
struct nlmsghdr *nlh;
int ret;
/* 创建sk_buff 空间 */
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if(!nl_skb)
{
printk(&#34;netlink alloc failure\n&#34;);
return -1;
}
/* 设置netlink消息头部 */
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0); // 填充nlmsg报文头,并最终将报文填充到sk_buff发送出去
if(nlh == NULL)
{
printk(&#34;nlmsg_put failaure \n&#34;);
nlmsg_free(nl_skb);
return -1;
}
/* 拷贝数据发送 */
memcpy(nlmsg_data(nlh), pbuf, len);
ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT); // 指定端口号,端口号是唯一识别目的地址的标识,广播非阻塞式发送
return ret;
}
static void netlink_rcv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh = NULL;
char *umsg = NULL;
char *kmsg = &#34;hello users!!!&#34;;
if(skb->len >= nlmsg_total_size(0))
{
nlh = nlmsg_hdr(skb); // 取出报文头
umsg = NLMSG_DATA(nlh); // 取出报文内容
if(umsg)
{
printk(&#34;kernel recv from user: %s\n&#34;, umsg);
send_usrmsg(kmsg, strlen(kmsg));
}
}
}
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg, /* set recv callback */
};
int test_netlink_init(void)
{
/* create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if(nlsk == NULL)
{
printk(&#34;netlink_kernel_create error !\n&#34;);
return -1;
}
printk(&#34;test_netlink_init\n&#34;);
return 0;
}
void test_netlink_exit(void)
{
if (nlsk){
netlink_kernel_release(nlsk); /* release ..*/
nlsk = NULL;
}
printk(&#34;test_netlink_exit!\n&#34;);
}
module_init(test_netlink_init);
module_exit(test_netlink_exit);Makefile构建脚本:
#
#Desgin of Netlink
#
MODULE_NAME :=netlink_test
obj-m :=$(MODULE_NAME).o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean测试代码运行结果:
 |
|