系统相关
首页 > 系统相关> > Linux 通用Netlink Howto文档翻译

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:

发送消息

发送通用的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架构,它由五种不同类型的组件组成:

  1. Netlink子系统,它作为所有通用Netlink通信的底层传输层。
  2. 通用的Netlink总线,它是在内核中实现的,但是用户可以通过套接字API使用它,在内核中通过普通的Netlink和通用的Netlink API使用它。
  3. 通过通用Netlink总线彼此通信的通用Netlink用户;用户可以同时存在于内核空间和用户空间中。
  4. 通用Netlink控制器,它是内核的一部分,负责动态分配通用Netlink通信通道和其他管理任务。通用Netlink控制器被实现为标准的通用Netlink用户,但是,它监听一个特殊的、预先分配的通用Netlink通道。
  5. 内核套接字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结构字段的使用方式如下:

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结构字段的使用方式如下:

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;
    };

字段的填充方式如下:

The nla_policy Structure

通用Netlink属性策略由nla_policy结构定义,如下所示:

    struct nla_policy
    {
       u16             type;
       u16             len;
    };

各字段的使用方式如下:

建议

通用网络链接机制是一种非常灵活的通信机制,因此有许多不同的方式可以使用它。以下建议是基于Linux内核中的约定,应该尽可能遵循。虽然不是所有现有的内核代码都遵循这里概述的建议,但所有新代码都应该将这些建议视为需求。

属性和消息有效负载

在定义新的通用Netlink消息格式时,必须尽可能使用Netlink属性。精心设计的Netlink属性机制允许未来的消息扩展,同时保持向后兼容性。使用Netlink属性还有其他好处,包括开发人员熟悉的属性和基本的输入检查。
大多数常见的数据结构可以用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