文章目录
本文档简要介绍了Generic Netlink,提供了一些有关如何使用它的简单示例,以及一些有关如何充分利用Generic Netlink通信接口的建议。 尽管本文档不需要读者对Netlink是什么以及它如何工作有详细的了解,但是假定读者具有一些Netlink基本知识。 和往常一样,内核源代码是您最好的朋友。
尽管本文档从用户空间的角度简要讨论了Generic Netlink,但其主要重点是内核的Generic Netlink API。 建议对Generic Netlink感兴趣的应用程序开发人员使用 libnl 库。
1. Generic Netlink By Example
本节介绍Linux内核中的Generic Netlink子系统,并提供一个简单的示例,说明内核用户如何使用Generic Netlink API。 在编写任何代码之前,请不要忘记阅读第4节“建议”,因为这样做可以节省您和审查代码的人员的大量时间!
第一部分说明了如何注册通用Netlink系列,这对于希望充当服务器并通过通用Netlink总线侦听的通用Netlink用户是必需的。 第二部分介绍了如何在内核中发送和接收通用Netlink消息。 最后,第三部分简要介绍了在用户空间中使用通用Netlink。
1.1 Registering A Family
注册通用Netlink系列是一个简单的四个步骤:define the family,define the operations,register the family,register the operations。 为了帮助说明这些步骤,下面是一个简单的示例,并进行了详细说明。
第一步是 define the family 本身,我们通过创建genl_family结构的实例来做到这一点。 在我们的简单示例中,我们将创建一个名为 “ DOC_EXMPL” 的 new Generic Netlink family 。
/* attributes */
enum {
DOC_EXMPL_A_UNSPEC,
DOC_EXMPL_A_MSG,
__DOC_EXMPL_A_MAX,
};
#define DOC_EXMPL_A_MAX (__DOC_EXMPL_A_MAX - 1)
/* attribute policy */
static struct nla_policy doc_exmpl_genl_policy[DOC_EXMPL_A_MAX + 1] = {
[DOC_EXMPL_A_MSG] = { .type = NLA_NUL_STRING },
};
/* family definition */
static struct genl_family doc_exmpl_gnl_family = {
.id = GENL_ID_GENERATE,
.hdrsize = 0,
.name = "DOC_EXMPL",
.version = 1,
.maxattr = DOC_EXMPL_A_MAX,
};
Figure 1: The DOC_EXMPL family, attributes and policy
您可以在上面看到我们定义了一个 new family ,并且该 family 识别单个属性 DOC_EXMPL_A_MSG ,它是一个以NULL结尾的字符串。 GENL_ID_GENERATE宏/常量实际上只是值0x0,它表示在注册族时我们希望通用Netlink控制器分配通道号。
第二步是 define the operations for the family ,我们通过创建至少一个 genl_ops 结构实例来完成。 在此示例中,我们仅定义一个操作,但是每个 family 最多可以定义255个唯一操作。
/* handler */
static int doc_exmpl_echo(struct sk_buff *skb, struct genl_info *info)
{
/* message handling code goes here; return 0 on success, negative
* values on failure */
}
/* commands */
enum {
DOC_EXMPL_C_UNSPEC,
DOC_EXMPL_C_ECHO,
__DOC_EXMPL_C_MAX,
};
#define DOC_EXMPL_C_MAX (__DOC_EXMPL_C_MAX - 1)
/* operation definition */
static struct genl_ops doc_exmpl_gnl_ops_echo = {
.cmd = DOC_EXMPL_C_ECHO,
.flags = 0,
.policy = doc_exmpl_genl_policy,
.doit = doc_exmpl_echo,
.dumpit = NULL,
};
Figure 2: The DOC_EXMPL_C_ECHO operation
在这里,我们定义了一个单独的操作DOC_EXMPL_C_ECHO,该操作使用了上面定义的 Netlink attribute policy 。 一旦注册,只要通过通用Netlink总线将 DOC_EXMPL_C_ECHO 消息发送到 DOC_EXMPL 系列,此特定操作就会调用 doc_exmpl_echo() 函数。
第三步是注册带有 Generic Netlink operation 的 DOC_EXMPL family 。我们通过一个函数调用来做到这一点:
int rc;
rc = genl_register_family(&doc_exmpl_gnl_family);
if (rc != 0)
goto failure;
调用该函数后,将注册一个带有 Generic Netlink mechanism 的 new family name,并且分配一个 new channel number,将它存储在 genl_family struct 中,来替代 GENL_ID_GENERATE 的值。很重要的一点是,当使用完成时要记住 unregister Generic Netlink families,因为内核会为每个 registered family 分配资源。
第四步是最后一步,为 family 注册 operations。这是一个简单的函数调用:
int rc;
rc = genl_register_ops(&doc_exmpl_gnl_family, &doc_exmpl_gnl_ops_echo);
if (rc != 0)
goto failure;
注意:此功能在linux 3.12以后不存在。 在linux 4.10之前,请使用genl_register_family_with_ops() 。 在4.10及更高版本上,将对genl_ops结构的引用作为元素包含在genl_family结构(element .ops)中,以及命令的数量(element .n_ops)。
此调用注册了 DOC_EXMPL_C_ECHO 操作,并与 DOC_EXMPL family 进行了关联。该过程现已完成。现在,其他 Generic Netlink 用户可以发出 DOC_EXMPL_C_ECHO 命令,并将根据需要对其进行处理。
1.2 Kernel Communication
内核提供了两组用于发送,接收和处理通用Netlink消息的接口。 API的大部分由通用Netlink接口组成,但是,只有少数特定于Generic Netlink的接口。 以下两个“ include”文件为内核定义了Netlink和Generic Netlink API:
- include/net/netlink.h
- include/net/genetlink.h
1.2.1 Sending Messages
发送通用Netlink消息是一个三步过程:为消息缓冲区分配内存,创建消息,发送消息。 为了帮助演示这些步骤,下面是使用DOC_EXMPL系列的简单示例。
第一步是分配一个Netlink消息缓冲区。 最简单的方法是使用 nlsmsg_new() 函数。
struct sk_buff *skb;
skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
goto failure;
Figure 3: Allocating a Generic Netlink message buffer
当您在分配时不知道消息缓冲区的大小时,可以使用NLMSG_GOODSIZE宏/常量作为一个好值。 不要忘记 genlmsg_new() 函数会自动为 Netlink and Generic Netlink message headers 添加空间。
第二步是实际创建消息有效负载。 这显然是每种服务都非常特定的,但是下面显示了一个简单的示例。
int rc;
void *msg_head;
/* create the message headers */
msg_head = genlmsg_put(skb, pid, seq, type, 0, flags, DOC_EXMPL_C_ECHO, 1);
if (msg_head == NULL) {
rc = -ENOMEM;
goto failure;
}
/* add a DOC_EXMPL_A_MSG attribute */
rc = nla_put_string(skb, DOC_EXMPL_A_MSG, "Generic Netlink Rocks");
if (rc != 0)
goto failure;
/* finalize the message */
genlmsg_end(skb, msg_head);
Figure 4: Creating a Generic Netlink message payload
genlmsg_put()函数创建所需的Netlink和Generic Netlink消息头,并使用给定的值填充它们。 有关参数的说明,请参见通用Netlink头文件。 nla_put_string()函数是一个标准的Netlink属性函数,它在Netlink消息的末尾添加了一个字符串属性。 有关参数的说明,请参见Netlink头文件。 消息有效负载完成后,genlmsg_end() 函数将更新Netlink消息头。 发送消息之前,应先调用此函数。
第三步也是最后一步是发送Generic Netlink消息,该消息可以通过单个函数调用完成。 以下示例是用于单播发送的,但是存在用于执行通用Netlink消息的多播发送的接口。
int rc;
rc = genlmsg_unicast(skb, pid);
if (rc != 0)
goto failure;
Figure 5: Sending Generic Netlink messages
1.2.2 Receiving Messages
通常,内核模块充当Generic Netlink服务器,这意味着接收消息的行为由Generic Netlink总线自动处理。 总线接收到消息并确定正确的路由后,该消息将直接传递到特定于系列的操作回调以进行处理。 如果内核充当Generic Netlink客户端,则可以使用标准内核套接字接口通过通用Netlink套接字接收服务器响应消息。
1.3 Userspace Communication
虽然可以使用标准套接字API发送和接收通用Netlink消息,但建议用户空间应用程序使用 libnl 库。 libnl库将应用程序与许多低级Netlink任务隔离开来,并使用与上述内核API非常相似的API。
2. Architectural Overview
Figure #6 展示了基本的通用Netlink体系结构,该体系结构由五种不同类型的组件组成:
- Netlink子系统用作所有通用Netlink通信的基础传输层。
- Generic Netlink bus 在内核内部实现,但可通过套接字API在用户空间使用,而在内核内部可通过常规 Netlink 和 Generic Netlink API 使用。
- 通过 Generic Netlink bus 进行通信的所有 Generic Netlink users 可以存在于 kernel 和 user space 中。
- 通用Netlink控制器是内核的一部分,负责动态分配通用Netlink通信通道和其他管理任务。 通用Netlink控制器是作为标准通用Netlink用户实现的,但是,它侦听一个特殊的,预先分配的通用Netlink通道。
- 内核套接字API。 通用Netlink套接字是使用PF_NETLINK域和NETLINK_GENERIC协议值创建的。
+---------------------+ +---------------------+
| (3) application "A" | | (3) application "B" |
+------+--------------+ +--------------+------+
| |
\ /
\ /
| |
+-------+--------------------------------+-------+
| : : | user-space
=====+ : (5) kernel socket API : +================
| : : | kernel-space
+--------+-------------------------------+-------+
| |
+-----+-------------------------------+----+
| (1) Netlink subsystem |
+---------------------+--------------------+
|
+---------------------+--------------------+
| (2) Generic Netlink bus |
+--+--------------------------+-------+----+
| | |
+-------+---------+ | |
| (4) controller | / \
+-----------------+ / \
| |
+------------------+--+ +--+------------------+
| (3) kernel user "X" | | (3) kernel user "Y" |
+---------------------+ +---------------------+
Figure 6: Generic Netlink Architecture
查看图6时,请务必注意,任何通用Netlink用户都可以使用相同的API通过总线与任何其他用户进行通信,无论该用户相对于内核/用户空间边界位于何处。
通用Netlink通信本质上是一系列不同的通信通道,它们在单个Netlink系列上复用。 通信通道由通用Netlink控制器动态分配的通道号唯一标识。 控制器是一个特殊的通用Netlink用户,它在固定的通信信道上监听,该信道始终存在,编号为0x10。 通过通用Netlink总线提供服务的内核或用户空间用户通过向通用Netlink控制器注册其服务来建立新的通信通道。 想要使用服务的用户查询控制器,以查看该服务是否存在并确定正确的通道号。
3. Implementation Details
This section provides a more in-depth explanation of the Generic Netlink message formats and data structures.
3.1 Message Format
通用Netlink使用标准Netlink子系统作为传输层,这意味着通用Netlink消息的基础是标准Netlink消息格式-唯一的区别是包含了通用Netlink消息头。 消息的格式定义如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Netlink message header (nlmsghdr) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Generic Netlink message header (genlmsghdr) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Optional user specific message header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Optional Generic Netlink message payload |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 7: Generic Netlink message format
包含图#7只是为了让您大致了解通用Netlink消息是如何格式化和 “在线(on the wire)” 发送的。 实际上,Netlink和通用Netlink API应该使大多数用户与消息格式和Netlink消息头的详细信息隔离。
3.2 Data Structures
本节重点介绍内核中定义的通用Netlink数据结构。 使用libnl库[1]为用户空间应用程序提供了类似的API。
3.2.1 The genl_family Structure
Generic Netlink families 由 genl_family 结构定义,如下所示:
struct genl_family
{
unsigned int id;
unsigned int hdrsize;
char name[GENL_NAMSIZ];
unsigned int version;
unsigned int maxattr;
struct nlattr ** attrbuf;
struct list_head ops_list;
struct list_head family_list;
};
Figure 8: The genl_family structure
genl_family结构字段以以下方式使用:
-
unsigned int id
这是动态分配的通道号。 值0x0表示应该由控制器分配通道号,并且0x10值保留供控制器使用。 注册新系列时,用户应始终使用GENL_ID_GENERATE宏/常量(值0x0)。 -
unsigned int hdrsize
如果 family 使用 family 特定的标头,则其大小存储在此处。 如果没有特定于 family 的标头,则该值应为零。 -
char name[GENL_NAMSIZ]
该字符串对于 family 来说应该是唯一的,因为它是控制器在请求时用于查找通道号的密钥。 -
unsigned int version
Family specific version number. -
unsigned int maxattr
Generic Netlink 使用标准Netlink属性。 此值保存为 Generic Netlink family 定义的最大属性数。 -
struct nlattr **attrbuf
This is a private field and should not be modified. -
struct list_head ops_list
This is a private field and should not be modified. -
struct list_head family_list
This is a private field and should not be modified.
3.2.2 The genl_ops Structure
通用Netlink操作由genl_ops结构定义,如下所示:
struct genl_ops
{
u8 cmd;
unsigned int flags;
struct nla_policy *policy;
int (*doit)(struct sk_buff *skb, struct genl_info *info);
int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
struct list_head ops_list;
};
Figure 9: The genl_ops structure
genl_ops结构字段按以下方式使用:
-
u8 cmd
该值在相应的 Generic Netlink family 中是唯一的,用于引用操作。 -
unsigned int flags
该字段用于指定操作的任何特殊属性。 可以使用以下标志(可以将多个标志进行“或”运算):
GENL_ADMIN_PERM
该操作需要CAP_NET_ADMIN权限 -
struct nla_policy policy
This field defines the Netlink attribute policy for the operation request message. If specified, the Generic Netlink mechanism uses this policy to verify all of the attributes in the operation request message before calling the operation handler.
The attribute policy is defined as an array of nla_policy structures indexed by the attribute number. The nla_policy structure is defined as shown in figure #11. -
int (*doit)(struct skbuff *skb, struct genl_info *info)
此回调的用法类似于标准Netlink doit()回调,主要区别在于参数的更改。
doit()处理程序接收两个参数:第一个是触发该处理程序的消息缓冲区,第二个是Generic Netlink genl_info结构,其定义如图10所示。 -
int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb)
此回调的用法类似于标准Netlink dumpit()回调。 当接收到带有NLM_F_DUMP标志设置的通用Netlink消息时,将调用dumpit()回调。
dumpit()处理程序和doit()处理程序之间的主要区别在于,dumpit()处理程序不为响应分配消息缓冲区。 预分配的sk_buff作为第一个参数传递到dumpit()处理函数。 dumpit()处理程序应使用适当的响应消息填充消息缓冲区,并返回sk_buff的大小,即sk_buff→len,消息缓冲区将自动发送到发起请求的Generic Netlink客户端。 只要dumpit()处理函数返回的值大于零,就会使用新分配的消息缓冲区再次调用它来填充。 当处理程序没有更多数据要发送时,它应该返回零; 错误条件通过返回负值来指示。 如有必要,可以在传递给dumpit()处理程序的netlink_callback参数中保留状态。 netlink_callback参数值将在单个请求的处理程序调用之间保留。 -
struct list_head ops_list
这是一个私有字段,不应修改。
3.2.3 The genl_info Structure
genl_info 结构传递 Generic Netlink message information,如下所示:
struct genl_info
{
u32 snd_seq;
u32 snd_pid;
struct nlmsghdr * nlhdr;
struct genlmsghdr * genlhdr;
void * userhdr;
struct nlattr ** attrs;
};
Figure 10: The genl_info structure
-
u32 snd_seq
This is the Netlink sequence number of the request. -
u32 snd_pid
这是发出请求的客户端的Netlink PID; 重要的是要注意,Netlink PID与标准内核PID不同。 -
struct nlmsghdr *nlhdr
This is set to point to the Netlink message header of the request. -
struct genlmsghdr *genlhdr
This is set to point to the Generic Netlink message header of the request. -
void *userhdr
如果 Generic Netlink family 使用特定于 family 的 header,则此指针将设置为指向特定于 family 的 header 。 -
struct nlattr **attrs
从请求中解析的 Netlink attributes; 如果 Generic Netlink family 定义指定了 Netlink attribute policy ,则该 attributes 已经过验证。
doit()处理函数应执行必要的任何处理,并在成功时返回零,在失败时返回负值,负返回值将导致发送 NLMSG_ERROR 消息。如果是接收一个带有 NLM_F_ACK 标志的请求,此时返回 0 将导致发送一个 NLMSG_ERROR 消息
3.2.4 The nla_policy Structure
Generic Netlink attribute policy is defined by the nla_policy structure, which is shown below:
struct nla_policy
{
u16 type;
u16 len;
};
Figure 11: The nla_policy structure
-
u16 type
这指定属性的类型; 目前,以下类型已定义为通用:-
NLA_UNSPEC
Undefined type ,未定义类型。 -
NLA_U8
An 8-bit unsigned integer -
NLA_U16
A 16-bit unsigned integer -
NLA_U32
A 32-bit unsigned integer -
NLA_U64
A 64-bit unsigned integer -
NLA_FLAG
A simple boolean flag -
NLA_MSECS
A 64-bit time value in msecs -
NLA_STRING
A variable length string -
NLA_NUL_STRING
A variable length NULL terminated string -
NLA_NESTED
A stream of attributes
-
-
u16 len
当属性类型是字符串类型之一时,则此字段应设置为字符串的最大长度,不包括终端NULL字节。 如果属性类型未知或为NLA_UNSPEC,则应将此字段设置为属性有效负载的确切长度。
除非属性类型是上述固定长度类型之一,否则值为零表示不应该对属性进行验证。
4. Recommendations
Generic Netlink mechanism 是一种非常灵活的通信机制,因此可以使用许多不同的方式。 以下建议基于Linux内核中的约定,并应尽可能遵循。 虽然并非所有现有内核代码都遵循此处概述的建议,但是所有新代码都应将这些建议视为要求。
4.1 Attributes And Message Payloads
在定义新的通用Netlink消息格式时,必须尽可能使用Netlink属性。 Netlink属性机制经过精心设计,可以在将来扩展消息的同时保留向后兼容性。 使用Netlink属性还有其他好处,包括开发人员的熟悉程度和基本的输入检查。
最常见的数据结构可以用Netlink属性表示:
- 标量值; 大多数标量值已经具有定义明确的属性类型。 有关详细信息,请参见第4节。
- 结构; 可以使用嵌套属性表示结构,而将结构字段表示为容器属性的有效负载中的属性。
- 数组; 可以通过使用单个嵌套属性作为容器来表示数组,其中多个内部具有相同的属性类型,每个属性代表数组中的一个点。
尽可能使用唯一属性也很重要。 这有助于充分利用Netlink属性机制,并在将来轻松更改消息格式。
4.2 Operation Granularity
虽然可能很容易为 Generic Netlink family 注册单个操作并在单个操作上复用多个子命令,但出于安全原因,强烈建议不要这样做。 将多种行为组合到一个操作中,很难使用现有的Linux内核安全性机制来限制操作。
4.3 Acknowledgment and Error Reporting
常规Netlink服务通常需要向客户端返回ACK或错误代码。 Netlink已经提供了一种灵活的确认和错误报告消息类型,称为NLMSG_ERROR,因此不必实现显式的确认消息。 发生错误时,将使用通用Netlink操作处理程序返回的错误代码将NLMSG_ERROR消息返回给客户端。 通过在请求上设置NLM_F_ACK标志,客户端也可以在没有错误发生时请求NLMSG_ERROR消息。
参考
- linux-5.4.37/Documentation/networking/generic_netlink.txt
- generic_netlink_howto
- Netlink Protocol Library Suite (libnl)
- Linux kernel下的内核socket编程