Linux 通用Netlink Howto文档翻译
作者:互联网
原地址:https://wiki.linuxfoundation.org/networking/generic_netlink_howto
通用Netlink HOWTO
本文简要介绍了通用Netlink,一些简单的例子说明了如何使用它,并就如何充分利用通用Netlink通信接口提出了一些建议。虽然本文不要求读者详细了解Netlink是什么以及它是如何工作的,但这里假设读者具有一些基本的Netlink知识。和往常一样,内核源代码是您在这里最好的朋友。
虽然本文从用户空间的角度简要讨论了Generic Netlink,但主要关注的是内核的Generic Netlink API。建议对使用Generic Netlink感兴趣的应用程序开发人员使用libnl库。
通用Netlink示例
本节讨论Linux内核中的通用Netlink子系统,并提供一个简单的示例,说明内核内用户如何使用通用Netlink API。在编写任何代码之前,不要忘记检查第4节“建议”,因为它可以为您和审阅您代码的人节省大量时间!
第一部分解释了如何注册通用Netlink系列,这是希望充当服务器、侦听通用Netlink总线的通用Netlink用户所需要的。第二部分解释了如何在内核中发送和接收通用的Netlink消息。最后,第三部分简要介绍了在用户空间中使用Generic Netlink。
注册一个族
注册一个通用Netlink族是一个简单的四个步骤:定义族、定义操作、注册族、注册操作。为了帮助演示这些步骤,下面是一个简单的示例,并详细解释。
第一步是定义族本身,这是通过创建genl_family结构的实例来完成的。在我们的简单示例中,我们将创建一个新的名为“DOC_EXMPL”的通用Netlink系列。
/* 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,
};
您可以在上面看到,我们定义了一个新族,该族识别一个属性DOC_EXMPL_A_MSG,这是一个以NULL结束的字符串。GENL_ID_GENERATE宏/常量实际上只是值0x0,它表示我们希望通用Netlink控制器在我们注册家族时分配通道号。
第二步是定义家族的操作,我们通过创建至少一个genl_ops结构实例来完成。在本例中,我们只定义一个操作,但每个族最多可以定义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,
};
在这里,我们定义了一个操作DOC_EXMPL_C_ECHO,它使用我们在上面定义的Netlink属性策略。一旦注册,这个特定的操作将在通过通用Netlink总线将DOC_EXMPL_C_ECHO消息发送到DOC_EXMPL家族时调用doc_expl_echo()函数。
第三步是使用Generic Netlink操作注册DOC_EXMPL族。我们通过一个函数调用来做到这一点:
int rc;
rc = genl_register_family(&doc_exmpl_gnl_family);
if (rc != 0)
goto failure;
这个调用向Generic Netlink机制注册新的族名,并请求一个存储在genl_family结构中的新通道号,替换GENL_ID_GENERATE值。当内核为每个已注册的Netlink族分配资源时,记住注销通用Netlink族是很重要的。
第四步,也是最后一步,是为族登记操作。这又是一个简单的函数调用:
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结构中的一个元素(元素.ops),以及命令的数量(元素.n_ops)。
这个调用注册与DOC_EXMPL家族关联的DOC_EXMPL_C_ECHO操作。这个过程现在已经完成了。其他通用Netlink用户现在可以发出DOC_EXMPL_C_ECHO命令,它们将根据需要进行处理。
内核通信
内核提供了两组接口,用于发送、接收和处理通用Netlink消息。大多数API由通用的Netlink接口组成,然而,也有少数特定于通用Netlink的接口。以下两个“include”文件定义了内核的Netlink和Generic Netlink API:
- include/net/netlink.h
- include/net/genetlink.h
发送消息
发送通用的Netlink消息有三个步骤:为消息缓冲区分配内存,创建消息,发送消息。为了帮助演示这些步骤,下面是一个使用DOC_EXMPL家族的简单示例。
第一步是分配一个Netlink消息缓冲区;最简单的方法是使用nlsmsg_new()函数。
struct sk_buff *skb;
skb = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
if (skb == NULL)
goto failure;
当您不知道分配消息缓冲区的大小时,NLMSG_GOODSIZE宏/常量是一个很好的值。不要忘记genlmsg_new()函数会自动为Netlink和Generic Netlink消息头添加空格。
第二步是实际创建消息有效负载。这显然是每个服务特有的,但下面展示了一个简单的示例。
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);
genlmsg_put()函数创建所需的Netlink和Generic Netlink消息头,用给定的值填充它们;有关参数的描述,请参阅Generic 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;
接收消息
通常,内核模块充当通用Netlink服务器,这意味着接收消息的行为是由通用Netlink总线自动处理的。一旦总线接收到消息并确定了正确的路由,该消息就直接传递给家族特定的操作回调进行处理。如果内核充当Generic Netlink客户端,则可以使用标准内核套接字接口通过Generic Netlink套接字接收服务器响应消息。
用户空间通信
虽然可以使用标准套接字API发送和接收通用Netlink消息,但建议用户空间应用程序使用libnl库[1]。libnl库将应用程序与许多低级的Netlink任务隔离开来,并使用与上面所示的内核API非常相似的API。
架构概述
图6说明了基本的通用Netlink架构,它由五种不同类型的组件组成:
- Netlink子系统,它作为所有通用Netlink通信的底层传输层。
- 通用的Netlink总线,它是在内核中实现的,但是用户可以通过套接字API使用它,在内核中通过普通的Netlink和通用的Netlink API使用它。
- 通过通用Netlink总线彼此通信的通用Netlink用户;用户可以同时存在于内核空间和用户空间中。
- 通用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" |
+---------------------+ +---------------------+
在查看图6时,需要注意的是,任何通用Netlink用户都可以通过总线使用相同的API与任何其他用户通信,而不管用户位于内核/用户空间边界的何处。
一般的网络链路通信本质上是一系列不同的通信信道,它们在一个单一的网络链路家族中多路复用。通信通道由通道号唯一标识,这些通道号由通用Netlink控制器动态分配。控制器是一个特殊的Generic Netlink用户,它侦听始终存在的固定通信通道0x10。通过通用Netlink总线提供服务的内核或用户空间用户通过向通用Netlink控制器注册他们的服务来建立新的通信通道。想要使用服务的用户向控制器查询服务是否存在并确定正确的通道号。
实现细节
本节将更深入地解释通用Netlink消息格式和数据结构。
消息格式
通用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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
图#7只是让您大致了解Generic Netlink消息是如何格式化和“在网络上”发送的。在实践中,Netlink和Generic Netlink API应该将大多数用户与消息格式和Netlink消息头的细节隔离开来。
数据结构
本节主要讨论在内核中定义的通用Netlink数据结构。对于使用libnl库[1]的用户空间应用程序,也存在类似的API。
The genl_family Structure
一般的Netlink族由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;
};
genl_family结构字段的使用方式如下:
- unsigned int id
这是动态分配的信道号。值0x0表示通道号应该由控制器分配,0x10值保留给控制器使用。用户在注册新族时应该始终使用GENL_ID_GENERATE宏/常量(值0x0)。 - unsigned int hdrsize
如果族使用了族特定的头文件,则其大小存储在这里。如果没有家族特定的头文件,这个值应该为零。 - char name[GENL_NAMSIZ]
这个字符串对于这个家族来说应该是唯一的,因为它是控制器在被请求时用来查找通道号的键。 - unsigned int version
家族特定版本号。 - unsigned int maxattr
通用的Netlink使用标准的Netlink属性;此值保存为通用Netlink系列定义的最大属性数量。 - struct nlattr **attrbuf
这是一个私有字段,不应该被修改。 - struct list_head ops_list
这是一个私有字段,不应该被修改。 - struct list_head family_list
这是一个私有字段,不应该被修改。
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;
};
genl_ops结构字段的使用方式如下:
- u8 cmd
该值在对应的Generic Netlink系列中是唯一的,用于引用操作。 - unsigned int flags
该字段用于指定操作的任何特殊属性。可以使用以下标志(多个标志可以一起或):- GENL_ADMIN_PERM
该操作需要CAP_NET_ADMIN权限
- GENL_ADMIN_PERM
- struct nla_policy policy
该字段定义了操作请求消息的Netlink属性策略。如果指定了该策略,通用Netlink机制将使用该策略在调用操作处理程序之前验证操作请求消息中的所有属性。
属性策略被定义为一个nla_policy结构数组,这些结构按属性编号索引。nla_policy结构的定义如图#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()处理程序返回大于零的值,就会再次调用它,并使用新分配的消息缓冲区填充它。当处理程序没有更多的数据要发送时,它应该返回0;错误条件通过返回负值来表示。如果有必要,可以在netlink_callback参数中保留state,该参数被传递给dumpit()处理程序;netlink_callback参数值将在单个请求的处理程序调用之间保留。 - struct list_head ops_list
这是一个私有字段,不应该被修改。
The genl_info Structure
一般的Netlink消息信息通过genl_info结构传递,如下所示:
struct genl_info
{
u32 snd_seq;
u32 snd_pid;
struct nlmsghdr * nlhdr;
struct genlmsghdr * genlhdr;
void * userhdr;
struct nlattr ** attrs;
};
字段的填充方式如下:
- u32 snd_seq
这是请求的Netlink序列号。 - u32 snd_pid
这是发出请求的客户端的Netlink PID;需要注意的是,Netlink PID与标准内核PID不同。 - struct nlmsghdr *nlhdr
它被设置为指向请求的Netlink消息头。 - struct genlmsghdr *genlhdr
它被设置为指向请求的Generic Netlink消息头。 - void *userhdr
如果通用Netlink系列使用了系列特定的头文件,该指针将被设置为指向系列特定头文件的开始。 - struct nlattr **attrs
从请求中解析的Netlink属性;如果Generic Netlink系列定义指定了Netlink属性策略,那么这些属性将已经经过验证。
doit()处理程序应该执行任何必要的处理,并在成功时返回0,在失败时返回负值。负返回值将导致发送NLMSG_ERROR消息,而零返回值只会在接收到设置了NLM_F_ACK标志的请求时发送NLMSG_ERROR消息。
The nla_policy Structure
通用Netlink属性策略由nla_policy结构定义,如下所示:
struct nla_policy
{
u16 type;
u16 len;
};
各字段的使用方式如下:
- u16 type
这指定了属性的类型;目前定义了以下类型供一般使用:- NLA_UNSPEC
未定义类型 - NLA_U8
8位无符号整数 - NLA_U16
16位无符号整数 - NLA_U32
32位无符号整数 - NLA_U64
64位无符号整数 - NLA_FLAG
布尔标志 - NLA_MSECS
msecs中的64位时间值 - NLA_STRING
可变长字符串 - NLA_NUL_STRING
可变长的NULL结尾字符串 - NLA_NESTED
属性流
- NLA_UNSPEC
- u16 len
当属性类型是字符串类型之一时,该字段应该设置为字符串的最大长度,不包括终端NULL字节。如果属性类型为unknown或NLA_UNSPEC,则应该将该字段设置为属性有效负载的确切长度。
除非属性类型是上面的固定长度类型之一,否则值为0表示不应执行属性验证。
建议
通用网络链接机制是一种非常灵活的通信机制,因此有许多不同的方式可以使用它。以下建议是基于Linux内核中的约定,应该尽可能遵循。虽然不是所有现有的内核代码都遵循这里概述的建议,但所有新代码都应该将这些建议视为需求。
属性和消息有效负载
在定义新的通用Netlink消息格式时,必须尽可能使用Netlink属性。精心设计的Netlink属性机制允许未来的消息扩展,同时保持向后兼容性。使用Netlink属性还有其他好处,包括开发人员熟悉的属性和基本的输入检查。
大多数常见的数据结构可以用Netlink属性表示:
- 标量值
大多数标量值已经具有定义良好的属性类型;详见第4节。 - 结构
可以使用嵌套属性表示结构,其中结构字段在容器属性的有效负载中表示为属性。 - 数组
可以通过使用单个嵌套属性作为容器来表示数组,每个容器中有几个相同的属性类型表示数组中的一个点。
尽可能多地使用独特的属性也很重要。这有助于最大限度地利用Netlink属性机制,并为将来更改消息格式提供方便。
操作粒度
虽然很容易为通用Netlink系列注册单个操作,并在单个操作上多个子命令,但出于安全原因,强烈不建议这样做。将多个行为组合到一个操作中,使得使用现有的Linux内核安全机制来限制操作变得困难。
确认和错误报告
通用Netlink服务通常需要向客户端返回ACK或错误代码。没有必要实现显式确认消息,因为Netlink已经提供了灵活的确认和错误报告消息类型NLMSG_ERROR。当发生错误时,将向客户端返回一个NLMSG_ERROR消息,其中包含由Generic Netlink操作处理程序返回的错误代码。当没有错误发生时,客户端也可以通过在请求上设置NLM_F_ACK标志来请求NLMSG_ERROR消息。
References
http://people.suug.ch/~tgr/libnl
标签:Netlink,通用,struct,DOC,Howto,EXMPL,genl,Linux 来源: https://blog.csdn.net/shao0099876/article/details/121750625