当前位置: 首页 > news >正文

Linux之netlink(2)libnl使用介绍(1)

Linux之netlink(2)Libnl3使用介绍(1)

Author:Onceday Date:2025年4月26日

漫漫长路,才刚刚开始…

全系列文章可查看专栏: Linux内核知识_Once-Day的博客-CSDN博客

本文翻译自libnl3官方文档:Netlink Library (libnl)

参考文档:

  • thom311/libnl: Netlink Library Suite
  • libnl - Netlink Protocol Library Suite
  • Routing Family Netlink Library (libnl-route)
  • Netlink Library (libnl)
  • Documentation Overview - libnl Suite

文章目录

      • Linux之netlink(2)Libnl3使用介绍(1)
        • 1. libnl介绍
        • 2. Netlink协议介绍
          • 2.1 Netlink地址
          • 2.2 消息格式
          • 2.3 消息类型
          • 2.4 序列号
        • 3. Netlink套接字
          • 3.1 Socket实例
          • 3.2 序列号
          • 3.3 多播组
          • 3.4 回调函数配置
          • 3.5 套接字属性

1. libnl介绍

libnl3是Linux平台上一个功能丰富的网络编程库。它为用户空间程序提供了一组全面的API,用于与Linux内核的网络组件进行交互。libnl3让开发者能够方便地配置和管理各种网络功能,如链路、接口、路由、地址、邻居、策略路由、流量控制、网络状态监控等。

libnl 套件是一组库的集合,它为基于 Netlink 协议的 Linux 内核接口提供了应用程序编程接口(API)。

Netlink 是一种主要在内核与用户空间进程之间使用的进程间通信(IPC)机制。它被设计成是 ioctl(输入输出控制)更灵活的替代方案,主要用于提供与网络相关的内核配置和监控接口。

在这里插入图片描述

这些接口被划分到几个小型库中,这样就不会强制应用程序链接到一个庞大臃肿的单一库上。

  • libnl,核心库,实现了使用 Netlink 协议所需的基本功能,比如套接字处理、消息构建与解析,以及数据的发送和接收。这个库保持小巧且简约。该套件中的其他库都依赖于这个库。
  • libnl-route,提供了 NETLINK_ROUTE 系列配置接口的 API,这些接口涉及网络接口、路由、地址、邻居节点以及流量控制等方面。
  • libnl-genl,提供了通用 Netlink 协议(Netlink 协议的扩展版本)的 API。
  • libnl-nf,提供了基于 Netlink 的 netfilter(网络过滤器)配置和监控接口(连接跟踪、日志记录、队列)的 API。

主要的头文件是 <netlink/netlink.h>。根据程序所使用的子系统和组件的不同,可能还需要在源文件中包含其他的头文件。

#include <netlink/netlink.h>#include <netlink/cache.h>#include <netlink/route/link.h>

如果该库在编译时启用了调试语句,那么当环境变量 NLDBG 的值设置为大于 0 时,它将会把调试信息打印到标准错误输出(stderr)中。

NLDBG=2 ./myprogram

下面是各个NLDBG值对应的调试级别:

调试级别描述
0调试功能已禁用(默认设置)
1警告、重要事件及通知信息
2或多或少较为重要的调试消息
3会产生大量调试消息的重复性事件相关信息
4甚至更不太重要的消息

查看与其他套接字交换的 Netlink 消息流通常是很有用的。设置环境变量 NLCB=debug 将启用调试消息处理程序,该程序进而会以人类可读的格式将交换的 Netlink 消息打印到标准错误输出(stderr)中。

$ NLCB=debug ./myprogram-- Debug: Sent Message:--------------------------   BEGIN NETLINK MESSAGE ---------------------------[HEADER] 16 octets.nlmsg_len = 20.nlmsg_type = 18 <route/link::get>.nlmsg_flags = 773 <REQUEST,ACK,ROOT,MATCH>.nlmsg_seq = 1301410712.nlmsg_pid = 20014[PAYLOAD] 16 octets
.....
2. Netlink协议介绍
2.1 Netlink地址

Netlink 协议是一种基于套接字的进程间通信(IPC)机制,用于用户空间进程与内核之间,或者用户空间进程彼此之间的通信。Netlink 协议基于 BSD 套接字,并使用 AF_NETLINK 地址族。每一种 Netlink 协议都有其自身的协议编号(例如,NETLINK_ROUTE、NETLINK_NETFILTER 等)。它的编址模式基于一个 32 位的端口号,以前称为进程标识符(PID),这个端口号唯一标识每个通信对等端。

Netlink 地址(端口)由一个 32 位整数组成。端口 0(零)是为内核保留的,它指的是每个 Netlink 协议族在内核端的套接字。其他端口号通常指的是用户空间所拥有的套接字,不过这并非强制规定。

起初,常见的做法是使用进程标识符(PID)作为本地端口号。但随着支持多线程的 Netlink 应用程序以及需要多个套接字的应用程序的出现,这种做法变得不太实用了。因此,libnl 库会基于进程标识符生成唯一的端口号,并为其添加一个偏移量,从而允许使用多个套接字。出于向后兼容的原因,初始套接字的端口号仍然会等于进程标识符。

在这里插入图片描述

上图展示了三个应用程序以及内核端所暴露的两个内核端套接字。它呈现了常见的 Netlink 使用场景:

  • 用户空间到内核。
  • 用户空间到用户空间。
  • 监听内核的多播通知。

(1)用户空间到内核:Netlink 最常见的使用形式是用户空间应用程序向内核发送请求,并处理回复,回复内容要么是错误消息,要么是成功通知。

在这里插入图片描述

(2)用户空间到用户空间:Netlink 也可以用作一种进程间通信机制,以便用户空间应用程序之间直接进行通信。通信并不局限于两个对等端,任意数量的对等端都可以相互通信,并且多播功能允许通过一条消息发送给多个对等端。

为了让各个套接字能够相互可见,必须为相同的 Netlink 协议族创建这两个套接字。

在这里插入图片描述

(3)用户空间监听内核通知:这种形式的 Netlink 通信通常出现在用户空间守护进程中,这些守护进程需要针对某些内核事件采取行动。此类守护进程通常会维护一个订阅了某个多播组的 Netlink 套接字,内核使用该多播组向感兴趣的用户空间各方通知特定事件。

在这里插入图片描述

由于在任何时候更换用户空间组件时都无需内核察觉,并且具有灵活性,所以相较于直接编址,多播的使用更为可取。

2.2 消息格式

Netlink 协议通常基于消息,由 Netlink 消息头部(struct nlmsghdr)以及附加到它上面的有效载荷组成。有效载荷可以由任意数据构成,但通常包含一个固定大小的特定于协议的头部,其后跟着一系列属性。

在这里插入图片描述

Netlink 消息头部(struct nlmsghdr结构体):

  • 总长度(32 位):消息的总长度,以字节为单位,包括 Netlink 消息头部。

  • 消息类型(16 位):消息类型指定了该消息所携带的有效载荷的类型。Netlink 协议定义了几种标准的消息类型。每个协议族可能会定义额外的消息类型。

  • 消息标志(16 位):消息标志可用于修改消息类型的行为。

  • 序列号(32 位):序列号是可选的,可用于引用先前的消息,例如,一条错误消息可以引用导致该错误的原始请求。

  • 端口号(32 位):端口号指定了该消息应发送到的对等端。如果未指定端口号,该消息将被发送到同一协议族中第一个匹配的内核端套接字。

2.3 消息类型

Netlink 协议区分请求、通知和回复。请求是设置了NLM_F_REQUEST标志的消息,其目的是向接收方请求执行某个操作。请求通常由用户空间进程发送到内核。虽然并非严格强制要求,但每个发送的请求都应该携带一个递增的序列号。

根据请求的性质,接收方可能会用另一条 Netlink 消息来回复该请求。回复的序列号必须与它所关联的请求的序列号相匹配。

通知属于非正规性质的消息,不需要回复,因此序列号通常设置为 0。

在这里插入图片描述

消息的类型主要通过消息头部中设置的 16 位消息类型来识别。定义了以下标准消息类型:

  • NLMSG_NOOP,无操作,该消息必须被丢弃。

  • NLMSG_ERROR,错误消息或确认消息(ACK)。

  • NLMSG_DONE,多部分消息序列的结束。

  • NLMSG_OVERRUN,溢出通知(错误)。

每个 Netlink 协议都可以自由定义自己的消息类型。请注意,小于 NLMSG_MIN_TYPE(0x10)的消息类型值是保留的,不能使用。

通常的做法是使用自定义的消息类型来实现远程过程调用(RPC)模式。假设正在实现的 Netlink 协议的目标是允许对特定网络设备进行配置,因此希望提供对各种配置选项的读写访问权限。实现这一目标的典型 “Netlink 方式” 是定义两种消息类型:MSG_SETCFG(设置配置消息)和 MSG_GETCFG(获取配置消息):

#define MSG_SETCFG      0x11
#define MSG_GETCFG      0x12

发送一条MSG_GETCFG请求消息通常会触发一条消息类型为MSG_SETCFG的回复,回复中包含当前的配置信息。用面向对象的术语来说,这可以描述为 “内核在用户空间中设置配置的本地副本”。

在这里插入图片描述

可以通过发送一条 MSG_SETCFG 消息来更改配置,对该消息的回复要么是一条确认消息(ACK),要么是一条错误消息。

在这里插入图片描述

作为可选操作,内核可以发送配置更改的通知,使用户空间能够监听这些更改,而无需频繁轮询。通知通常会重用现有的消息类型,并且依赖于应用程序使用单独的套接字来区分请求和通知,但你也可以指定一个单独的消息类型。

在这里插入图片描述

尽管从理论上讲,一条 Netlink 消息的大小最大可达 4GiB,但套接字缓冲区很可能不够大,无法容纳如此大小的消息。因此,通常的做法是将消息大小限制为一页的大小(PAGE_SIZE),并使用多部分机制将大块数据分割成几条消息。一条多部分消息(Multipart Messages)设置了标志NLM_F_MULTI,接收方需要持续接收和解析消息,直到接收到特殊的消息类型NLMSG_DONE为止。

与分片的 IP 数据包不同,多部分消息无需重新组装,尽管如果协议希望以这种方式工作,重新组装也是完全可行的。多部分消息常常用于发送对象列表或对象树,每条多部分消息仅仅携带多个对象,从而允许独立解析每条消息。

在这里插入图片描述

错误消息可以作为对请求的响应而发送。错误消息必须使用标准消息类型 NLMSG_ERROR。其有效载荷由一个错误代码和原始请求的 Netlink 消息头部组成。

在这里插入图片描述

错误消息应将序列号设置为导致该错误的请求的序列号。

在这里插入图片描述

确认消息(ACK),发送方可以通过在请求中设置 NLM_F_ACK 标志,来请求接收方为每条已处理的请求发回一条确认消息。这通常用于让发送方在请求被接收方处理之前同步后续的处理操作。

在这里插入图片描述

确认消息也使用消息类型NLMSG_ERROR和有效载荷格式,但错误代码设置为 0。

消息标志,定义了以下标准标志:

#define NLM_F_REQUEST           1
#define NLM_F_MULTI             2
#define NLM_F_ACK               4
#define NLM_F_ECHO              8
  • NLM_F_REQUEST - 消息是一个请求。
  • NLM_F_MULTI - 多部分消息。
  • NLM_F_ACK - 请求确认消息。
  • NLM_F_ECHO - 请求回显该请求。

标志 NLM_F_ECHO 与 NLM_F_ACK 标志类似。它可以与 NLM_F_REQUEST 标志结合使用,使得作为请求结果而发送的通知,无论发送方是否已订阅相应的多播组,都会被发送给发送方。

还定义了一些仅适用于 GET 请求的额外通用消息标志:

#define NLM_F_ROOT      0x100
#define NLM_F_MATCH     0x200
#define NLM_F_ATOMIC    0x400
#define NLM_F_DUMP      (NLM_F_ROOT|NLM_F_MATCH)
  • NLM_F_ROOT - 基于树的根节点返回数据。
  • NLM_F_MATCH - 返回所有匹配的条目。
  • NLM_F_ATOMIC - 已过时,曾经用于请求一个原子操作。
  • NLM_F_DUMP - 返回所有对象的列表(NLM_F_ROOT|NLM_F_MATCH)。

这些标志的使用完全是可选的,许多 Netlink 协议仅使用 NLM_F_DUMP 标志,该标志通常请求接收方以多部分消息序列的形式,发送与消息类型相关的所有对象的列表。

还有另一组与 NEW 或 SET 请求相关的标志。这些标志与 GET 请求的标志相互排斥:

#define NLM_F_REPLACE   0x100
#define NLM_F_EXCL      0x200
#define NLM_F_CREATE    0x400
#define NLM_F_APPEND    0x800
  • NLM_F_REPLACE - 如果对象已存在,则替换该现有对象。
  • NLM_F_EXCL - 如果对象已经存在,则不更新该对象。
  • NLM_F_CREATE - 如果对象尚不存在,则创建该对象。
  • NLM_F_APPEND - 在列表末尾添加对象。

这些标志的行为在不同的 Netlink 协议之间可能会略有不同。

2.4 序列号

Netlink 允许使用序列号来帮助将回复与请求相关联。需要注意的是,与 TCP 等协议不同,Netlink 对序列号没有严格的强制要求。序列号的唯一目的是帮助发送方将回复与相应的请求关联起来。序列号是在每个套接字的基础上进行管理的。

3. Netlink套接字
3.1 Socket实例

为了使用 Netlink 协议,需要一个 Netlink 套接字。每个套接字都定义了一个独立的消息发送和接收上下文。一个应用程序可以使用多个套接字,例如,一个套接字用于发送请求并接收回复,另一个套接字订阅多播组以接收通知。

Netlink 套接字以及所有相关属性(包括实际的文件描述符)都由struct nl_sock结构体表示。

#include <netlink/socket.h>struct nl_sock *nl_socket_alloc(void)
void nl_socket_free(struct nl_sock *sk)

应用程序必须为其希望使用的每个 Netlink 套接字分配一个struct nl_sock结构体的实例。

3.2 序列号

该库会自动为应用程序处理序列号。在套接字结构中存储有一个序列号计数器,当需要发送预期会产生回复(如错误消息或任何其他需要与原始消息相关联的消息类型)的消息时,会自动使用并递增该计数器。

或者,也可以通过函数nl_socket_use_seq()直接使用该计数器。它将返回计数器的当前值,然后将其递增 1。

#include <netlink/socket.h>unsigned int nl_socket_use_seq(struct nl_sock *sk);

不过,大多数应用程序并不希望自己处理序列号。当使用nl_send_auto()函数时,序列号会自动填入,并且在收到回复时会再次进行匹配。

如果所实现的 Netlink 协议不使用请求 / 回复模型,例如当套接字用于接收通知消息时,这种(自动处理序列号的)行为可以并且必须被禁用。

#include <netlink/socket.h>void nl_socket_disable_seq_check(struct nl_sock *sk);
3.3 多播组

每个套接字可以订阅其所连接的 Netlink 协议的任意数量的多播组。然后,该套接字将接收发送到任何一个多播组的每条消息的副本。多播组通常用于实现事件通知。

在内核版本 2.6.14 之前,组订阅是使用位掩码来执行的,这将每个协议族的组数量限制为 32 个。即使不建议在新代码中使用,仍然可以通过函数nl_join_groups()访问这个过时的接口。

#include <netlink/socket.h>void nl_join_groups(struct nl_sock *sk, int bitmask);

从内核版本 2.6.14 开始引入了一种新方法,该方法支持订阅几乎无限数量的多播组。

#include <netlink/socket.h>int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);
int nl_socket_drop_memberships(struct nl_sock *sk, int group, ...);
3.4 回调函数配置

每个套接字都被分配一个回调配置,该配置控制套接字的行为。例如,这对于每个套接字拥有一个单独的消息接收函数是必要的。不过,在套接字之间共享回调配置也是完全可行的。

可以使用以下函数来访问和设置套接字的回调配置:

#include <netlink/socket.h>struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);
void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);

此外,还存在一种快捷方式,可以直接修改分配给套接字的回调配置:

#include <netlink/socket.h>int nl_socket_modify_cb(struct nl_sock *sk, enum nl_cb_type type, enum nl_cb_kind kind,nl_recvmsg_msg_cb_t func, void *arg);
3.5 套接字属性

本地端口号唯一标识该套接字,并用于对其进行寻址。在分配套接字时,会自动生成一个唯一的本地端口号。它将由进程 ID(22 位)和一个随机数(10 位)组成,因此每个进程最多允许有 1024 个套接字。

#include <netlink/socket.h>uint32_t nl_socket_get_local_port(const struct nl_sock *sk);
void nl_socket_set_local_port(struct nl_sock *sk, uint32_t port);

注意:可以覆盖本地端口号,但你必须确保所提供的值是唯一的,并且任何其他应用程序中的其他套接字都没有使用相同的值。

可以为套接字分配一个对等端口,这将导致通过该套接字发送的所有单播消息都被发送到该对等方。如果未指定对等方,则消息将发送到内核,内核将尝试自动将该套接字绑定到同一 Netlink 协议族的内核端套接字。通常的做法是不将套接字绑定到对等端口,因为每个 Netlink 协议族通常只存在一个内核端套接字。

#include <netlink/socket.h>uint32_t nl_socket_get_peer_port(const struct nl_sock *sk);
void nl_socket_set_peer_port(struct nl_sock *sk, uint32_t port);

Netlink 使用 BSD 套接字接口,因此每个套接字背后都有一个文件描述符,你可以直接使用它。

#include <netlink/socket.h>int nl_socket_get_fd(const struct nl_sock *sk);

如果一个套接字仅用于接收通知,通常最好将该套接字设置为非阻塞模式,并定期轮询以获取新的通知。

#include <netlink/socket.h>int nl_socket_set_nonblocking(const struct nl_sock *sk);

套接字缓冲区用于在发送方和接收方之间对 Netlink 消息进行排队。这些缓冲区的大小指定了你能够写入 Netlink 套接字的最大大小,也就是说,它将间接定义最大消息大小。默认大小是 32KiB。

#include <netlink/socket.h>int nl_socket_set_buffer_size(struct nl_sock *sk, int rx, int tx);

以下函数允许在套接字上启用 / 禁用自动确认模式。自动确认模式默认是启用的。

#include <netlink/socket.h>void nl_socket_enable_auto_ack(struct nl_sock *sk);
void nl_socket_disable_auto_ack(struct nl_sock *sk);

启用/禁用消息窥探(Message Peeking)。如果启用,消息窥探会使nl_recv函数尝试使用 MSG_PEEK 来检索接收到的下一条消息的大小,并分配一个该大小的缓冲区。消息窥探默认是启用的,但可以使用以下函数将其禁用:

#include <netlink/socket.h>void nl_socket_enable_msg_peek(struct nl_sock *sk);
void nl_socket_disable_msg_peek(struct nl_sock *sk);

启用 / 禁用接收数据包信息。如果启用,从内核接收到的每条 Netlink 消息都将在控制消息中包含一个额外的struct nl_pktinfo结构体。可以使用以下函数来启用 / 禁用接收数据包信息。

#include <netlink/socket.h>int nl_socket_recv_pktinfo(struct nl_sock *sk, int state);

注意,Netlink Pktinfo的处理功能还没有实现。

相关文章:

  • Redis 数据类型全览:特性、场景与操作实例
  • 【Hive入门】Hive动态分区与静态分区:使用场景与性能对比完全指南
  • 游戏引擎学习第245天:wglChoosePixelFormatARB
  • 写入cache时数据格式错误产生的ERRO导致整个测试框架无法运行
  • PID程序实现
  • php一些命名规范 和 css命名规范
  • AIGC在自动化测试领域的创新应用:智能生成测试用例与缺陷预测
  • SpringCloud原理和机制
  • 产销协同的作用是什么?又如何对各部门发挥作用?
  • A. Ideal Generator
  • 【数据融合】基于拓展卡尔曼滤波实现雷达与红外的异步融合附matlab代码
  • 部署大模型需要多少GPU显存?以DeepSeek R1部署为例
  • 直接映射例题及解析
  • [笔记] MCPO搭建教程
  • 【Kafka】Windows环境下生产与消费流程详解(附流程图)
  • VO包装类和实体类分别是什么?区别是什么?
  • Jmeter如何取JDBC request响应参数作为下一个接口的值?
  • ORACLE数据库备份入门:第四部分:2-备份场景举例
  • SpringCloud组件——OpenFeign
  • MySQL 中 SQL 语句的详细执行过程
  • 人民日报:应对外贸行业风险挑战,稳企业就是稳就业
  • 第二十届中国电影华表奖揭晓!完整获奖名单来了
  • 上海明天起进入“升温通道”,五一假期冲刺33℃
  • 伊朗国防部发言人:发生爆炸的港口无进出口军用物资
  • 专访|伊朗学者:美伊核谈不只是改革派立场,但伊朗不信任美国
  • 从世界工厂走向全球创新中心,上海车展为何成为全球汽车行业风向标?