Linux: Netlink 简介

2024-04-28 03:12
文章标签 linux 简介 netlink

本文主要是介绍Linux: Netlink 简介,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 1. 前言
  • 2. Netlink 范例
  • 3. Netlink 简析
    • 3.1 Netlink 协议簇注册
    • 3.2 创建 用户空间 Netlink 套接字
    • 3.3 用户空间 Netlink 套接字 的 绑定
    • 3.4 向 内核空间 Netlink 套接字 发消息
    • 3.5 从 内核空间 Netlink 套接字 读消息
      • 3.5.1 内核空间 Netlink 套接字 向 用户空间 Netlink 套接字 发送消息
      • 3.5.2 用户空间 Netlink 套接字 读取 内核空间 Netlink 套接字 发送的消息
  • 4. 参考资料

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		66static 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 内容如下:

ifneq ($(KERNELRELEASE),)obj-m	:= netlink_kern_test.oelseKERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesendifclean: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 200struct 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_TESTretval = 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. 参考资料

[1] Introduction to Netlink

这篇关于Linux: Netlink 简介的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/942200

相关文章

Linux使用fdisk进行磁盘的相关操作

《Linux使用fdisk进行磁盘的相关操作》fdisk命令是Linux中用于管理磁盘分区的强大文本实用程序,这篇文章主要为大家详细介绍了如何使用fdisk进行磁盘的相关操作,需要的可以了解下... 目录简介基本语法示例用法列出所有分区查看指定磁盘的区分管理指定的磁盘进入交互式模式创建一个新的分区删除一个存

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

Linux使用dd命令来复制和转换数据的操作方法

《Linux使用dd命令来复制和转换数据的操作方法》Linux中的dd命令是一个功能强大的数据复制和转换实用程序,它以较低级别运行,通常用于创建可启动的USB驱动器、克隆磁盘和生成随机数据等任务,本文... 目录简介功能和能力语法常用选项示例用法基础用法创建可启动www.chinasem.cn的 USB 驱动

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面

LinuxMint怎么安装? Linux Mint22下载安装图文教程

《LinuxMint怎么安装?LinuxMint22下载安装图文教程》LinuxMint22发布以后,有很多新功能,很多朋友想要下载并安装,该怎么操作呢?下面我们就来看看详细安装指南... linux Mint 是一款基于 Ubuntu 的流行发行版,凭借其现代、精致、易于使用的特性,深受小伙伴们所喜爱。对

什么是 Linux Mint? 适合初学者体验的桌面操作系统

《什么是LinuxMint?适合初学者体验的桌面操作系统》今天带你全面了解LinuxMint,包括它的历史、功能、版本以及独特亮点,话不多说,马上开始吧... linux Mint 是一款基于 Ubuntu 和 Debian 的知名发行版,它的用户体验非常友好,深受广大 Linux 爱好者和日常用户的青睐,

Linux(Centos7)安装Mysql/Redis/MinIO方式

《Linux(Centos7)安装Mysql/Redis/MinIO方式》文章总结:介绍了如何安装MySQL和Redis,以及如何配置它们为开机自启,还详细讲解了如何安装MinIO,包括配置Syste... 目录安装mysql安装Redis安装MinIO总结安装Mysql安装Redis搜索Red

Linux中Curl参数详解实践应用

《Linux中Curl参数详解实践应用》在现代网络开发和运维工作中,curl命令是一个不可或缺的工具,它是一个利用URL语法在命令行下工作的文件传输工具,支持多种协议,如HTTP、HTTPS、FTP等... 目录引言一、基础请求参数1. -X 或 --request2. -d 或 --data3. -H 或