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

相关文章

Kali Linux安装实现教程(亲测有效)

《KaliLinux安装实现教程(亲测有效)》:本文主要介绍KaliLinux安装实现教程(亲测有效),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、下载二、安装总结一、下载1、点http://www.chinasem.cn击链接 Get Kali | Kal

linux服务之NIS账户管理服务方式

《linux服务之NIS账户管理服务方式》:本文主要介绍linux服务之NIS账户管理服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、所需要的软件二、服务器配置1、安装 NIS 服务2、设定 NIS 的域名 (NIS domain name)3、修改主

Linux实现简易版Shell的代码详解

《Linux实现简易版Shell的代码详解》本篇文章,我们将一起踏上一段有趣的旅程,仿照CentOS–Bash的工作流程,实现一个功能虽然简单,但足以让你深刻理解Shell工作原理的迷你Sh... 目录一、程序流程分析二、代码实现1. 打印命令行提示符2. 获取用户输入的命令行3. 命令行解析4. 执行命令

ubuntu16.04如何部署dify? 在Linux上安装部署Dify的技巧

《ubuntu16.04如何部署dify?在Linux上安装部署Dify的技巧》随着云计算和容器技术的快速发展,Docker已经成为现代软件开发和部署的重要工具之一,Dify作为一款优秀的云原生应用... Dify 是一个基于 docker 的工作流管理工具,旨在简化机器学习和数据科学领域的多步骤工作流。它

rust 中的 EBNF简介举例

《rust中的EBNF简介举例》:本文主要介绍rust中的EBNF简介举例,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 什么是 EBNF?2. 核心概念3. EBNF 语法符号详解4. 如何阅读 EBNF 规则5. 示例示例 1:简单的电子邮件地址

Linux高并发场景下的网络参数调优实战指南

《Linux高并发场景下的网络参数调优实战指南》在高并发网络服务场景中,Linux内核的默认网络参数往往无法满足需求,导致性能瓶颈、连接超时甚至服务崩溃,本文基于真实案例分析,从参数解读、问题诊断到优... 目录一、问题背景:当并发连接遇上性能瓶颈1.1 案例环境1.2 初始参数分析二、深度诊断:连接状态与

Linux系统调试之ltrace工具使用与调试过程

《Linux系统调试之ltrace工具使用与调试过程》:本文主要介绍Linux系统调试之ltrace工具使用与调试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、ltrace 定义与作用二、ltrace 工作原理1. 劫持进程的 PLT/GOT 表2. 重定

Python 异步编程 asyncio简介及基本用法

《Python异步编程asyncio简介及基本用法》asyncio是Python的一个库,用于编写并发代码,使用协程、任务和Futures来处理I/O密集型和高延迟操作,本文给大家介绍Python... 目录1、asyncio是什么IO密集型任务特征2、怎么用1、基本用法2、关键字 async1、async

Linux区分SSD和机械硬盘的方法总结

《Linux区分SSD和机械硬盘的方法总结》在Linux系统管理中,了解存储设备的类型和特性是至关重要的,不同的存储介质(如固态硬盘SSD和机械硬盘HDD)在性能、可靠性和适用场景上有着显著差异,本文... 目录一、lsblk 命令简介基本用法二、识别磁盘类型的关键参数:ROTA查询 ROTA 参数ROTA

嵌入式Linux之使用设备树驱动GPIO的实现方式

《嵌入式Linux之使用设备树驱动GPIO的实现方式》:本文主要介绍嵌入式Linux之使用设备树驱动GPIO的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录一、设备树配置1.1 添加 pinctrl 节点1.2 添加 LED 设备节点二、编写驱动程序2.1