大家好,欢迎来到IT知识分享网。
前言
Netlink用于在内核进程和用户空间进程之间传输信息。它有两套API,一套API在用户空间使用,一套API在内核空间使用。它旨在成为比 ioctl
更灵活的后继者,主要提供与网络相关的内核配置(kernel configuration)和接口监控(monitoring interfaces)。
libnl是一组基于 netlink
协议的API库的集合。它是 netlink
更高层次的封装。
本文介绍netlink API的最简单使用,为之后使用libnl打下基础。
本文完整示例代码见仓库:https://github.com/da1234cao/demo-2/tree/laboratory/37-ntlink-1
netlink hello world
本节参考自:Linux Netlink 详解 、netlink(7) — Linux manual page 、Introduction to Netlink
netlink 用户层接口说明
Netlink通信通过套接字进行,需要先打开套接字。socket
的 domain
为 AF_NETLINK
。type
为 SOCK_DGRAM
或者 SOCK_DGRAM
,netlink 协议不区分这两者。protocol
是 Netlink
可用的协议,目前已经使用了二十多个,而最多允许定义32个。后面的示例中,我们将使用自定义的协议类型。系统已有的协议类型涉及到具体的网络内容,本文不涉及。
// socket (int domain, int type, int protocol) fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
既然是通信,那至少有接收方和发送方。Netlink的接收方/发送方通常是用户进程/内核。socket网络编程中,通信双方使用四元组(源IP:源port-目的IP:目的port)来在网络中进行唯一标识。Netlink只能用于本机通信,所以不需要IP标识。端口方面,Netlink的端口并不是真的占用一个网络端口号,它只是一个唯一标识。内核的端口号总是0,用户进程的端口号可以为进程ID,如果是在多线程中,可以使用线程ID。
所以我们接下来是给套接字绑定端口号。socket网络编程中,我们通常填充一个 sockaddr_in
结构,然后调用bind()函数。Netlink
也差不多,不过填充的是 sockaddr_nl
结构。
struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ // 这里填充我们的端口号 __u32 nl_groups; /* multicast groups mask */ // 不使用广播时,这里设置为0 };
之后,用户空间程序调用 socket的 recv/send 等函数,即可与内核互相发送内容。
send(fd, &request, sizeof(request)); n = recv(fd, &buffer, RSP_BUFFER_SIZE);
发送的内容一定得遵循某种格式,这样双方才能互相解析。Netlink
使用TLV(type, length, value)格式,即一个消息头,消息头后面时负载,消息头中记录整个消息得长度。Netlink
协议的标头格式如下。
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 */ };
示例代码
上面的接口说明是比较无聊的。我们实际来跑一个示例。
示例代码
演示代码来自:Linux Netlink 详解
演示代码的功能:
- 用户空间的进程,向内核空间,发送 ”Hello kernel“。
- 内核空间的进程,向该用户进程,回复 ”Hello userspace“ 。
首先是用户空间的代码。
#include <linux/netlink.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <unistd.h> #define NETLINK_TEST 30 #define MAX_PAYLOAD 1024 /* maximum payload size*/ #define MAX_NL_BUFSIZ NLMSG_SPACE(MAX_PAYLOAD) // int PORTID = getpid(); int PORTID = 1; int create_nl_socket(uint32_t pid, uint32_t groups) {
int fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST); if (fd == -1) {
return -1; } struct sockaddr_nl addr; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = pid; addr.nl_groups = groups; if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
close(fd); return -1; } return fd; } ssize_t nl_recv(int fd) {
char nl_tmp_buffer[MAX_NL_BUFSIZ]; struct nlmsghdr *nlh; ssize_t ret; // 设置 Netlink 消息缓冲区 nlh = (struct nlmsghdr *)&nl_tmp_buffer; memset(nlh, 0, MAX_NL_BUFSIZ); ret = recvfrom(fd, nlh, MAX_NL_BUFSIZ, 0, NULL, NULL); if (ret < 0) {
return ret; } printf("==== LEN(%d) TYPE(%d) FLAGS(%d) SEQ(%d) PID(%d)\n\n", nlh->nlmsg_len, nlh->nlmsg_type, nlh->nlmsg_flags, nlh->nlmsg_seq, nlh->nlmsg_pid); printf("Received data: %s\n", NLMSG_DATA(nlh)); return ret; } int nl_sendto(int fd, void *buffer, size_t size, uint32_t pid, uint32_t groups) {
char nl_tmp_buffer[MAX_NL_BUFSIZ]; struct nlmsghdr *nlh; if (NLMSG_SPACE(size) > MAX_NL_BUFSIZ) {
return -1; } struct sockaddr_nl addr; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = pid; /* Send messages to the linux kernel. */ addr.nl_groups = groups; /* unicast */ // 设置 Netlink 消息缓冲区 nlh = (struct nlmsghdr *)&nl_tmp_buffer; memset(nlh, 0, MAX_NL_BUFSIZ); nlh->nlmsg_len = NLMSG_LENGTH(size); nlh->nlmsg_pid = PORTID; memcpy(NLMSG_DATA(nlh), buffer, size); return sendto(fd, nlh, NLMSG_LENGTH(size), 0, (struct sockaddr *)&addr, sizeof(addr)); } int main(void) {
char data[] = "Hello kernel"; int sockfd = create_nl_socket(PORTID, 0); if (sockfd == -1) {
return 1; } int ret; ret = nl_sendto(sockfd, data, sizeof(data), 0, 0); if (ret < 0) {
printf("Fail to send\n"); return 1; } printf("Sent %d bytes\n", ret); ret = nl_recv(sockfd); if (ret < 0) {
printf("Fail to receive\n"); } printf("Received %d bytes\n", ret); // while (1) {
// nl_recv(sockfd); // nl_sendto(sockfd, data, sizeof(data), 0, 0); // } return 0; }
然后是内核空间的代码。
#include <linux/module.h> #include <linux/netlink.h> #include <linux/skbuff.h> #include <net/sock.h> #define NETLINK_TEST 30 static struct sock *nl_sk = NULL; /* * Send the data of `data`, whose length is `size`, to the socket whose port is * `pid` through the unicast. * * @param data: the data which will be sent. * @param size: the size of `data`. * @param pid: the port of the socket to which will be sent. * @return: if successfully, return 0; or, return -1. */ int test_unicast(void *data, size_t size, __u32 pid) {
struct sk_buff *skb_out; skb_out = nlmsg_new(size, GFP_ATOMIC); if (!skb_out) {
printk(KERN_ERR "Failed to allocate a new sk_buff\n"); return -1; } // struct nlmsghdr* nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int // type, int len, int flags); struct nlmsghdr *nlh; nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, size, 0); memcpy(nlmsg_data(nlh), data, size); // 设置 SKB 的控制块(CB) // 控制块是 struct sk_buff // 结构特有的,用于每个协议层的控制信息(如:IP层、TCP层) 对于 Netlink // 来说,其控制信息是如下结构体: struct netlink_skb_parms {
// struct scm_credscreds; // Skb credentials // __u32portid; // 发送此SKB的Socket的Port号 // __u32dst_group; // 目的多播组,即接收此消息的多播组 // __u32flags; // struct sock*sk; // }; // 对于此结构体,一般只需要设置 portid 和 dst_group 字段。 // 但对于不同的Linux版本,其结构体会所有变化:早期版本 portid 字段名为 pid。 // NETLINK_CB(skb_out).pid = pid; NETLINK_CB(skb_out).portid = pid; NETLINK_CB(skb_out).dst_group = 0; /* not in mcast group */ // 单播/多播 if (nlmsg_unicast(nl_sk, skb_out, pid) < 0) {
printk(KERN_INFO "Error while sending a msg to userspace\n"); return -1; } return 0; } EXPORT_SYMBOL(test_unicast); static void nl_recv_msg(struct sk_buff *skb) {
struct nlmsghdr *nlh = (struct nlmsghdr *)skb->data; char *data = "Hello userspace"; printk(KERN_INFO "==== LEN(%d) TYPE(%d) FLAGS(%d) SEQ(%d) PORTID(%d)\n", nlh->nlmsg_len, nlh->nlmsg_type, nlh->nlmsg_flags, nlh->nlmsg_seq, nlh->nlmsg_pid); printk("Received %d bytes: %s\n", nlmsg_len(nlh), (char *)nlmsg_data(nlh)); test_unicast(data, strlen(data), nlh->nlmsg_pid); } static int __init test_init(void) {
printk("Loading the netlink module\n"); // This is for 3.8 kernels and above. struct netlink_kernel_cfg cfg = {
.input = nl_recv_msg, }; nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg); if (!nl_sk) {
printk(KERN_ALERT "Error creating socket.\n"); return -10; } return 0; } static void __exit test_exit(void) {
printk(KERN_INFO "Unloading the netlink module\n"); netlink_kernel_release(nl_sk); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE("GPL");
安装依赖
# 当前内核版本 uname -a Linux bogon 4.18.0-553.8.1.el8_10.x86_64 # https://rockylinux.pkgs.org/8/rockylinux-devel-x86_64/kernel-headers-4.18.0-553.el8_10.x86_64.rpm.html dnf config-manager --set-enable devel dnf install kernel-headers-$(uname -r) kernel-devel-$(uname -r) # 如果安装的kernel-devel和当前正在运行的内核不匹配 # https://forums.rockylinux.org/t/installing-kernel-devel-does-not-match-the-running-kernel/12619/4 dnf upgrade kernel
使用cmake构建内核模块
我没有使用MakeFile,而是使用cmake来构建内核模块,所有有点麻烦。具体构建方式见仓库。
- 首先我们得知道MakeFile是如何构建内核模块的。可见:Linux内核模块编写之1: Hello World及Makefile
- 但是我想用cmake构建,这样显得比较厉害。这里有可用的示例:cmake : specify linux kernel module output build directory、cmake-kernel-module
- 上面的cmake-kernel-module,还是有的不好使。当有多个源文件时,不够优雅。使用 configure_file 来配置Kbuild是个好主意。可见:Using CMake for a Linux kernel module (a template project)
构建脚本这里就不粘贴了。具体见仓库。
运行
# 安装上面编译生成的内核模块 insmod netlink_demo.ko # 运行用户空间进程,可以看到下面输出 ./netlink_user Sent 29 bytes ==== LEN(31) TYPE(3) FLAGS(0) SEQ(0) PID(0) Received data: Hello userspace Received 32 bytes # 查看内核输出 dmesg [ 9169.] netlink_demo: loading out-of-tree module taints kernel. [ 9169.] netlink_demo: module verification failed: signature and/or required key missing - tainting kernel [ 9169.] Loading the netlink module [ 9189.] ==== LEN(29) TYPE(0) FLAGS(0) SEQ(0) PORTID(1) [ 9189.] Received 13 bytes: Hello kernel
最后
目前,我不需要写netlink的内核模块,只需要关注netlink在用户空间的使用即可。
通常,我应该也不会直接调用netlink API , 而是调用libnl API,它更高层一些。
唉,又得去看libnl的文档。可怜的程序员。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/124634.html