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镜像文件制作方式

《Linux镜像文件制作方式》本文介绍了Linux镜像文件制作的过程,包括确定磁盘空间布局、制作空白镜像文件、分区与格式化、复制引导分区和其他分区... 目录1.确定磁盘空间布局2.制作空白镜像文件3.分区与格式化1) 分区2) 格式化4.复制引导分区5.复制其它分区1) 挂载2) 复制bootfs分区3)

Linux服务器数据盘移除并重新挂载的全过程

《Linux服务器数据盘移除并重新挂载的全过程》:本文主要介绍在Linux服务器上移除并重新挂载数据盘的整个过程,分为三大步:卸载文件系统、分离磁盘和重新挂载,每一步都有详细的步骤和注意事项,确保... 目录引言第一步:卸载文件系统第二步:分离磁盘第三步:重新挂载引言在 linux 服务器上移除并重新挂p

Linux下屏幕亮度的调节方式

《Linux下屏幕亮度的调节方式》文章介绍了Linux下屏幕亮度调节的几种方法,包括图形界面、手动调节(使用ACPI内核模块)和外接显示屏调节,以及自动调节软件(CaliseRedshift和Reds... 目录1 概述2 手动调节http://www.chinasem.cn2.1 手动屏幕调节2.2 外接显

Linux(centos7)虚拟机没有IP问题及解决方案

《Linux(centos7)虚拟机没有IP问题及解决方案》文章介绍了在CentOS7中配置虚拟机网络并使用Xshell连接虚拟机的步骤,首先,检查并配置网卡ens33的ONBOOT属性为yes,然后... 目录输入查看ZFhrxIP命令:ip addr查看,没有虚拟机IP修改ens33配置文件重启网络Xh

linux实现对.jar文件的配置文件进行修改

《linux实现对.jar文件的配置文件进行修改》文章讲述了如何使用Linux系统修改.jar文件的配置文件,包括进入文件夹、编辑文件、保存并退出编辑器,以及重新启动项目... 目录linux对.jar文件的配置文件进行修改第一步第二步 第三步第四步总结linux对.jar文件的配置文件进行修改第一步进

linux ssh如何实现增加访问端口

《linuxssh如何实现增加访问端口》Linux中SSH默认使用22端口,为了增强安全性或满足特定需求,可以通过修改SSH配置来增加或更改SSH访问端口,具体步骤包括修改SSH配置文件、增加或修改... 目录1. 修改 SSH 配置文件2. 增加或修改端口3. 保存并退出编辑器4. 更新防火墙规则使用uf

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res