Linux: Netlink 简介

发布于:2024-05-01 ⋅ 阅读:(33) ⋅ 点赞:(0)

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. Netlink 范例

Netlink 通信包括 内核空间用户空间 两部分,具体来讲,是两个分别位于 内核空间用户空间AF_NETLINK 协议簇套接字。来看一个具体的例子,先看 内核空间 部分 netlink_kern_test.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>

extern struct net init_net;

#define NETLINK_TEST	30
#define MSG_LEN		100
#define USER_PORT	66

static struct sock *test_nlsk;

int send_usrmsg(char *buf, uint16_t len)
{
	struct sk_buff *nl_skb;
	struct nlmsghdr *nlh;

	nl_skb = nlmsg_new(len, GFP_ATOMIC);
	if (!nl_skb) {
		pr_err("netlink alloc failure\n");
		return -1;
	}

	nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
	if (nlh == NULL) {
		pr_err("nlmsg_put failure\n");
		nlmsg_free(nl_skb);
		return -1;
	}

	memcpy(nlmsg_data(nlh), buf, len);

	return netlink_unicast(test_nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);
}

static void netlink_rcv_msg(struct sk_buff *skb)
{   
	struct nlmsghdr *nlh;
	char *umsg = NULL;
	char *kmsg = "Hello user process!";
	
	nlh = nlmsg_hdr(skb);
	umsg = NLMSG_DATA(nlh);
	if (umsg) {
		printk("kernel recv msg from user: %s\n", umsg);
		printk("port id: %d\n", NETLINK_CB(skb).portid);
		send_usrmsg(kmsg, strlen(kmsg));
	}
}

static int __init test_netlink_init(void)
{
	struct netlink_kernel_cfg cfg = {
		.input = netlink_rcv_msg,
	};
	
	 test_nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);

	return test_nlsk ? 0 : -1;
}

static void __exit test_netlink_exit(void)
{
	netlink_kernel_release(test_nlsk);
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);

MODULE_LICENSE("GPL");

内核 Netlink 示例模块的逻辑很简单,调用接口 netlink_kernel_create() 创建一个 Netlink 内核空间套接字,且其协议类型为自定的 NETLINK_TEST,其数据接收接口为 netlink_rcv_msg() 。当其它 Netlink 套接字向它发送数据时,内核调用 netlink_rcv_msg() 处理数据接收,此时可通过调用 netlink_unicast() 向数据发送 Netlink 套接字回送数据。
接下来构建一个 Makefile 来编译这个内核模块,并安装到系统。Makefile 内容如下:

ifneq ($(KERNELRELEASE),)

obj-m	:= netlink_kern_test.o

else

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

endif

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions .cache.mk modules.order Module.symvers
$ make
$ sudo insmod netlink_kern_test.ko

再看 Netlink 示例的用户空间部分 netlink_user_test.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>

#define NETLINK_TEST 30
#define USER_PORT 66

#define MSG_LEN 100
#define MAX_PLOAD 200

struct netlink_user_msg {
	struct nlmsghdr hdr;
	char msg[MSG_LEN];
};

int main(int argc,char **argv)
{
	int sockfd;
	struct sockaddr_nl saddr, daddr;
	struct nlmsghdr *nlh;
	struct netlink_user_msg u_info;
	char *msg = "Hello kernel, I am a user process!";
	socklen_t len;

	// 创建 Netlink 用户空间套接字
	sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
	if (sockfd < 0) {
		perror("socket");
		return -1;
	}

	// 数据 源 Netlink 套接字 地址
	memset(&saddr, 0, sizeof(saddr));
	saddr.nl_family = AF_NETLINK;
	saddr.nl_pad = 0;
	saddr.nl_pid = USER_PORT; // 端口号
	saddr.nl_groups = 0;
	if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
		perror("socket");
		return -1;
	}

	// Netlink 消息数据
	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;
	memcpy(NLMSG_DATA(nlh), msg, strlen(msg));

	// 数据 目的 Netlink 套接字 地址
	memset(&daddr, 0, sizeof(daddr));
	daddr.nl_family = AF_NETLINK;
	daddr.nl_pid = 0; /* 0 表示数据发往 内核空间 Netlink 套接字 */
	daddr.nl_groups = 0;
	sendto(sockfd, nlh, nlh->nlmsg_len, 0, 
			(struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
	printf("send kernel: %s", msg);

	// 从内核 Netlink 套接字接收数据
	memset(&daddr, 0, sizeof(daddr));
	memset(&u_info, 0, sizeof(u_info));
	len = sizeof(struct sockaddr_nl);
	recvfrom(sockfd, &u_info, sizeof(user_msg_info), 0, 
					(struct sockaddr *)&daddr, &len);
	printf("\n");
	printf("from kernel(%u): %s\n", daddr.nl_pid, u_info.msg);

	close(sockfd);

	return 0;
}

编译执行用户空间测试程序 netlink_user_test

$ make netlink_user_test
$ ./netlink_user_test

本文示例是 内核空间用户空间 Netlink 套接字通信,事实上,内核空间的 Netlink 相互之间也可以通信,而且 Netlink 不仅支持单播通信,还支持广播通信,本文对此不做展开。

3. Netlink 简析

本文就示例中 内核空间用户空间 Netlink 套接字通信做简单分析。

3.1 Netlink 协议簇注册

static struct proto netlink_proto = {
	.name	  = "NETLINK",
	.owner	  = THIS_MODULE,
	.obj_size = sizeof(struct netlink_sock),
};

static const struct net_proto_family netlink_family_ops = {
	.family = PF_NETLINK,
	.create = netlink_create, /* 创建 用户空间 netlink 套接字 */
	.owner	= THIS_MODULE,	/* for consistency 8) */
};

static int __init netlink_proto_init(void)
{
	int err = proto_register(&netlink_proto, 0);

	...
	nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);

	...
	// Netlink 协议簇 PF_NETLINK 注册
	sock_register(&netlink_family_ops);
	...
}

core_initcall(netlink_proto_init);

3.2 创建 用户空间 Netlink 套接字

sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); /* net/socket.c */
	retval = sock_create(family, type, protocol, &sock);
		__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
			sock = sock_alloc();
			...
			sock->type = type; // SOCK_RAW
			...
			pf = rcu_dereference(net_families[family]);
			...
			err = pf->create(net, sock, protocol, kern);
				netlink_create() /* net/netlink/af_netlink.c */
					...
					err = __netlink_create(net, sock, cb_mutex, protocol, kern);
						...
						sock->ops = &netlink_ops;
						sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);
						...
						sk->sk_destruct = netlink_sock_destruct;
						sk->sk_protocol = protocol; // NETLINK_TEST
	retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));

3.3 用户空间 Netlink 套接字 的 绑定

struct sockaddr_nl saddr;

memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pad = 0;
saddr.nl_pid = USER_PORT;
saddr.nl_groups = 0;
bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr))
	struct socket *sock;

	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	...
	/* 拷贝用户参数到内核空间: umyaddr ==> address */
	err = move_addr_to_kernel(umyaddr, addrlen, &address);
	...
	err = sock->ops->bind(sock, (struct sockaddr *)&address, addrlen);
		netlink_bind() /* net/netlink/af_netlink.c */
			bool bound;
			...
			bound = nlk->bound;
			...
			if (!bound) { /* 还没有对 netlink 套接字进行过绑定 */
				...
				netlink_insert(sk, nladdr->nl_pid)
					...
					nlk_sk(sk)->portid = portid; /* 设定 netlink 套接字绑定的端口号 */
					...
					/*
					 * 将 @sk 插入到 netlink 协议类型 @sk->sk_protocol
					 * 对应表项 nl_table[sk->sk_protocol] 的 sock 列表。
					 * @sk->sk_protocol => {NETLINK_ROUTE, ...}
					 */
					err = __netlink_insert(table, sk);
					...
					/*
					 * 标记 netlink 套接字 @sk 已经进行了 地址 和 端口 的绑定, 
					 * 即已经调用过了 bind().
					 */
					smp_wmb();
					nlk_sk(sk)->bound = portid;
			}

3.4 向 内核空间 Netlink 套接字 发消息

struct sockaddr_nl daddr;
struct nlmsghdr *nlh;

nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD)); // 分配 头部 + 内容 空间 并对齐到 4 字节

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; // 指定 源端口号 USER_PORT
memcpy(NLMSG_DATA(nlh), msg, strlen(msg)); // 设置消息内容

// 设置消息发往的 目标 内核空间 netlink 套接字 地址
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0; // 发往内核 Netlink 套接字
daddr.nl_groups = 0;

// 往 目标内核 netlink 套接字 发消息
sendto(sockfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
	struct socket *sock;
	...
	struct msghdr msg;
	...
	
	err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter);
	...
	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	...
	msg.msg_name = NULL;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_namelen = 0;
	err = move_addr_to_kernel(addr, addr_len, &address);
	msg.msg_name = (struct sockaddr *)&address;
	msg.msg_namelen = addr_len;
	...
	msg.msg_flags = flags;
	err = sock_sendmsg(sock, &msg);
		sock_sendmsg_nosec(sock, msg);
			int ret = sock->ops->sendmsg(sock, msg, msg_data_left(msg));
				netlink_sendmsg() /* net/netlink/af_netlink.c */
					...
					if (msg->msg_namelen) {
						...
						/*
						 * @dst_portid: 数据发往的 目标 netlink 套接字端口号
						 * @dst_group : 数据发往的 目标 netlink 套接字 组
						 */
						dst_portid = addr->nl_pid;
						dst_group = ffs(addr->nl_groups);
						...
						netlink_skb_flags |= NETLINK_SKB_DST;
					} else {
						...
					}
					...
					skb = netlink_alloc_large_skb(len, dst_group); /* 分配 skb, 准备用来接收来自 @sock 的数据 */
					...
					NETLINK_CB(skb).portid	= nlk->portid; /* 设定 skb 的数据 源端口号 */
					NETLINK_CB(skb).dst_group = dst_group; /* 设定 skb 的数据 目标组 */
					...
					NETLINK_CB(skb).flags	= netlink_skb_flags; // |= NETLINK_SKB_DST
					...
					if (memcpy_from_msg(skb_put(skb, len), msg, len)) { /* 将要发送的数据 @msg 拷贝到 @skb */
						...
					}
					...
					err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT); /* netlink 单播 */
						...
					retry:	
						/* 
						 * 目标 sock (@sk) 是 内核 sock, 如情形:
						 *                             data
						 * 用户空间 netlink 套接字 @ssk ----> 内核空间 netlink 套接字 @sk
						 */
						sk = netlink_getsockbyportid(ssk, portid);
						...
						if (netlink_is_kernel(sk))
							netlink_unicast_kernel()
								nlk->netlink_rcv(skb)
									recv_msg_from_user_land()

3.5 从 内核空间 Netlink 套接字 读消息

内核空间 Netlink 套接字 读消息, 分为两步:

1. 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
2. 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息

3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息

#define USER_PORT 66 // 用户空间 netlink 套接字 端口号

struct sk_buff *nl_skb;
struct nlmsghdr *nlh;

// 构建 skb: 容纳 消息头部 + @len 长度的数据
nl_skb = nlmsg_new(len, GFP_ATOMIC);

// 设置源消息头部: 协议类型、端口号、数据长度等
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
	__nlmsg_put(skb, portid, seq, type, payload, flags); // @portid == 0, 指代内核 netlink 套接字
		struct nlmsghdr *nlh;
		int size = nlmsg_msg_size(len);

		nlh = skb_put(skb, NLMSG_ALIGN(size));
		nlh->nlmsg_type = type;
		nlh->nlmsg_len = size;
		nlh->nlmsg_flags = flags;
		nlh->nlmsg_pid = portid;
		nlh->nlmsg_seq = seq;

// 填充要发送的数据内容
memcpy(nlmsg_data(nlh), buf, len);

netlink_unicast(test_nlsk, nl_skb, USER_PORT, MSG_DONTWAIT); // 将数据发送 用户空间 netlink 套接字
	...
retry:	
	/* 
	 * 用 
	 * {sock_net(@ssk), @ssk->sk_protocol, @portid}
	 * 寻找目标 netlink sock, 数据传输方向: 
	 * @ssk (源sock) -> @sk (目标sock)
	 */
	sk = netlink_getsockbyportid(ssk, portid);
	...
	/* 
	 * 目标 sock (@sk) 是 用户空间 sock, 如情形:
	 *                             data
	 * 内核空间 netlink 套接字 @ssk ----> 用户空间 netlink 套接字 @sk
	 */
	return netlink_sendskb(sk, skb);
		int len = __netlink_sendskb(sk, skb);
			...
			/* 将数据添加到 目标 @sk 的 接收 skb 队列 */
			skb_queue_tail(&sk->sk_receive_queue, skb);
			/* 唤醒 可能的、因等待读取数据 而睡眠的进程 */
			sk->sk_data_ready(sk);
				sock_def_readable()

3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息

struct netlink_user_msg {
    struct nlmsghdr hdr;
    char msg[MSG_LEN];
};

struct sockaddr_nl daddr;
struct netlink_user_msg u_info;

memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
recvfrom(sockfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
	...
	err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
	...
	sock = sockfd_lookup_light(fd, &err, &fput_needed);

	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	/* Save some cycles and don't copy the address if not needed */
	msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
	/* We assume all kernel code knows the size of sockaddr_storage */
	msg.msg_namelen = 0;
	msg.msg_iocb = NULL;
	msg.msg_flags = 0;
	...
	err = sock_recvmsg(sock, &msg, flags);
		sock_recvmsg_nosec(sock, msg, flags);
			sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags);
				netlink_recvmsg() /* net/netlink/af_netlink.c */
					...
					/*
					 * 从 @sk 的接收队列 @sk->sk_receive_queue 取就绪的数据 skb.
					 * 阻塞模式下, 可因没有数据而陷入睡眠等待.
					 */
					skb = skb_recv_datagram(sk, flags, noblock, &err);
					...
					data_skb = skb; /* 返回就绪的 skb */
					...
					/* 这里告诉我们, netlink 协议数据位于 传输层(L4) 之上, 即位于 应用层(L5) */
					skb_reset_transport_header(data_skb);
					err = skb_copy_datagram_msg(data_skb, 0, msg, copied); /* 从就绪的 skb 读取数据 */
					
					if (msg->msg_name) {
						DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);
						addr->nl_family = AF_NETLINK;
						addr->nl_pad    = 0;
						addr->nl_pid	= NETLINK_CB(skb).portid;
						addr->nl_groups	= netlink_group_mask(NETLINK_CB(skb).dst_group);
						msg->msg_namelen = sizeof(*addr);
					}
					...

4. Netlink 的经典应用

在 Linux 中,Netlink 的典型应用有 路由表管理uevent

4.1 Netlink 路由表管理

/* net/core/rtnetlink.c */

static int __net_init rtnetlink_net_init(struct net *net)
{
        struct sock *sk;
        struct netlink_kernel_cfg cfg = {
                .groups         = RTNLGRP_MAX,
                .input          = rtnetlink_rcv,
                .cb_mutex       = &rtnl_mutex,
                .flags          = NL_CFG_F_NONROOT_RECV,
                .bind           = rtnetlink_bind,
        };

	sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);
        if (!sk)
                return -ENOMEM;
        net->rtnl = sk;
        return 0;
}

static struct pernet_operations rtnetlink_net_ops = {
	.init = rtnetlink_net_init,
        .exit = rtnetlink_net_exit,
};

void __init rtnetlink_init(void)
{
	...
	if (register_pernet_subsys(&rtnetlink_net_ops))
                panic("rtnetlink_init: cannot initialize rtnetlink\n");
	...
}

Netlink 路由表管理的细节复杂且庞大,本文不再做进一步展开。另外,用户空间的 ip 程序路由表功能部分,就是通过 Netlink 套接字实现的。

4.2 Netlink uevent

/* lib/kobject_uevent.c */

/* 向用户空间发送 uevent 设备事件 */
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
         char *envp_ext[])
{
	...

#if defined(CONFIG_NET)
	/* send netlink message */
	list_for_each_entry(ue_sk, &uevent_sock_list, list) {
		struct sock *uevent_sock = ue_sk->sk;
		struct sk_buff *skb;
		size_t len;

		...
		/* allocate message with the maximum possible size */
		len = strlen(action_string) + strlen(devpath) + 2;
		skb = alloc_skb(len + env->buflen, GFP_KERNEL);
		if (skb) {
			...
			NETLINK_CB(skb).dst_group = 1;
			retval = netlink_broadcast_filtered(uevent_sock, skb,
					0, 1, GFP_KERNEL,
					kobj_bcast_filter,
					kobj);
			...
		}
	}
}

#if defined(CONFIG_NET)
static int uevent_net_init(struct net *net)
{
	struct uevent_sock *ue_sk;
	struct netlink_kernel_cfg cfg = {
		.groups = 1,
		.flags = NL_CFG_F_NONROOT_RECV,
	};
 
 	ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
 	...

	/* 创建 uevent netlink 套接字,用户空间可以通过它监听设备 uevent 事件 */
	ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);
	...
	mutex_lock(&uevent_sock_mutex);
	list_add_tail(&ue_sk->list, &uevent_sock_list);
	mutex_unlock(&uevent_sock_mutex);
	return 0;
}

static struct pernet_operations uevent_net_ops = {
	.init = uevent_net_init,
	.exit = uevent_net_exit,
};

static int __init kobject_uevent_init(void)
{
	return register_pernet_subsys(&uevent_net_ops);
}

postcore_initcall(kobject_uevent_init);
#endif

5. 参考资料

[1] Introduction to Netlink


网站公告

今日签到

点亮在社区的每一天
去签到