文章目录
- [1. 前言](#1. 前言)
- [2. Netlink 范例](#2. Netlink 范例)
- [3. Netlink 简析](#3. Netlink 简析)
-
- [3.1 Netlink 协议簇注册](#3.1 Netlink 协议簇注册)
- [3.2 创建 用户空间 Netlink 套接字](#3.2 创建 用户空间 Netlink 套接字)
- [3.3 用户空间 Netlink 套接字 的 绑定](#3.3 用户空间 Netlink 套接字 的 绑定)
- [3.4 向 内核空间 Netlink 套接字 发消息](#3.4 向 内核空间 Netlink 套接字 发消息)
- [3.5 从 内核空间 Netlink 套接字 读消息](#3.5 从 内核空间 Netlink 套接字 读消息)
-
- [3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息](#3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息)
- [3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息](#3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息)
- [4. 参考资料](#4. 参考资料)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. Netlink 范例
Netlink
通信包括 内核空间
和 用户空间
两部分,具体来讲,是两个分别位于 内核空间
和 用户空间
的 AF_NETLINK 协议簇套接字
。来看一个具体的例子,先看 内核空间
部分 netlink_kern_test.c
:
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()
向数据发送套接字回送数据。
接下来构建一个 Makefile
来编译这个内核模块,并安装到系统。Makefile
内容如下:
c
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
bash
$ make
$ sudo insmod netlink_kern_test.ko
再看 Netlink 示例的用户控件部分 netlink_user_test.c
:
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
:
bash
$ make netlink_user_test
$ ./netlink_user_test
本文示例是 内核空间
和 用户空间
Netlink 套接字通信,事实上,内核空间的 Netlink 相互之间也可以通信,而且 Netlink 不仅支持单播通信,还支持广播通信,本文对此不做展开。
3. Netlink 简析
本文就示例中 内核空间
和 用户空间
Netlink 套接字通信做简单分析。
3.1 Netlink 协议簇注册
c
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 套接字
c
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 套接字 的 绑定
c
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 套接字 发消息
c
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 套接字 读消息, 这分为两步:
bash
1. 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
2. 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息
3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
c
#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 套接字 发送的消息
c
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);
}
...