Netlink主要是基于linux 内核接口提供的网络接口操作。Netlink是一种内核与用户空间通信的IPC机制,主要是网络相关操作提供相应的接口。本文主要介绍如何使用Generic Netlink接口,Generic Netlink是netlink的一个扩展版本。

 

本文主要参考https://wiki.linuxfoundation.org/networking/generic_netlink_howto,描述了Generic Netlink, Generic Netlink是linux 一种特殊的linux socket. 可以用于应用与linux 内核进行通信,应用与应用通信,内核与内核通信。本文会给出简单的例子来介绍怎么使用generic netlink,并不需要读者深入理解generic netlink。 Generic Netlink与其他模块交互在Linux架构中的位置,如下图。

 

 

Generic Netlink

 

3.1 Generic Netlink 用例

本部分描述Generic Netlink 内核子模块的API,并给出一个内核用户如何使用Generic netlink API的例子. 该用例分为两个部分, 第一部分介绍作为一个server, 怎样注册一个Generic Netlink family, 并且在Generic netlink bus上监听消息。第二部分介绍如何在内核中发送和接收消息。第三部分给出一个在用户空间如何使用Generic Netlink的简单介绍。

3.1.1 注册一个Family

注册一个Generic Netlink Family 只需要四步,定义family, 定义操作,注册这个family, 注册操作。下面是一个详细的例子解释这四步。

Step1: 定义一个family, 并且定义相关的属性。

 

Enum

{

TEST_A_UPSPEC,

TEST_A_NOTIF,

TEST_A_MSG,

__TEST_A_MAX

};

#define TEST_A_MAX (__TEST_A_MAX – 1)

static struct nla_policy test_genl_policy[IWF_A_MAX + 1] =      

{

[TEST_A_NOTIF] = {.type = NLA_U32},

[TEST_A_MSG] = {.type = NLA_BINARY},

};

struct genl_family test_genl_family =        // step1 define a family

{

    .id = GEN_ID_GENERATE, // GEN_ID_GENERATE是一个宏定义,值是0x0, 表明需要Generic Netlink Controller对注册的family分配一个channel ID.

    .hdrsize = 0,

    .name = "TEST",

    .version = 1,

    .maxattr = TEST_A_MAX,

};

  

 

Step2: 定义对于family的相关操作。至少创建一个gen_ops结构体。对于每个family, 最多可以创建255个唯一的operations.

enum

{

    TEST_CMD_NOOP,

    TEST_CMD_NOTIFY,

    TEST_CMD_MSG,

    __TEST_CMD_MAX,

};

static int test_cmd_noop(struct sk_buff *skb, struct genl_info *info);

static int test_cmd_notify(struct sk_buff *skb, struct genl_info *info);

static int test_cmd_msg(struct sk_buff *skb, struct genl_info *info);

#define TEST_CMD_MAX (__TEST_CMD_MAX - 1)

 

static struct genl_ops test_ops[] =

{

    {

        .cmd = TEST_CMD_NOOP,

        .flags = 0,

        .doit = test_cmd_noop,

        .policy = test_genl_policy,

        .dumpit = NULL,

    },

    {

        .cmd = TEST_CMD_NOTIFY,

        .flags = 0,

        .doit = test_cmd_nofity,

        .policy = test_genl_policy,

        .dumpit = NULL,

    },

    {

        .cmd = TEST_CMD_MSG,

        .flags = 0,

        .doit = test_cmd_msg,

        .policy = test_genl_policy,

        .dumpit = NULL,

    },

}

  

上面我们定义了三个操作,TEST_CMD_NOOP, TEST_CMD_NOTIFY, TEST_CMD_MSG, 都用了netlink属性policy. 当注册之后,当收到相应的消息之后,操作就会调用相应的注册函数。

Step 3: 注册family.

int rc;

 rc = genl_register_family(&test_genl_family); // 注册test family到netlink 并且申请一个channel ID.

 if (rc != 0)

     goto failure;

  

Step 4: 注册family的操作函数.

int rc;

 rc = genl_register_ops(&test_genl_family, & test_ops);

 if (rc != 0)

     goto failure;

  

Step 5: 注销family的操作函数.

非常重要的一点是需要记住,当不在需要使用的时候,需要unregister这个family,因为内核在注册family的时候申请了相应的资源,否则会造成内核资源泄露。

genl_unregister_family(&test_genl_family);

 

3.2 内核通信

内核代码对于发送、接收和处理Generic Netlink消息提供了两类接口,API接口中主要包含一般的netlink接口,只有一小部分是专门针对Generic Netlink的。要使用这些API需要包含以下两个头文件

include/net/netlink.h

include/net/genetlink.h

  

发送消息

发送一个Generic Netlink消息需要三步:为消息申请内存,创建消息,发送消息。下面是一个具体的例子:

Step1: 申请一个Netlink 消息的buffer, 最简单的方法就是调用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的消息头。

Step 2: 创建一个消息体。消息体的内容是根据不同的消息。

int rc;

 void *msg_head;

 /* create the message headers */

 msg_head = genlmsg_put(skb, pid, seq, type, 0, flags, TEST_CMD_MSG, 1); // 根据参数填写netlink和generic netlink的消息头

 if (msg_head == NULL) {

     rc = -ENOMEM;

     goto failure;

 }

 /* add a TEST_A_MSG  attribute */

 rc = nla_put_string(skb, TEST_A_MSG,, "Generic Netlink Rocks"); //在netlink消息之后添加字符串

 if (rc != 0)

     goto failure;

 /* finalize the message */

 genlmsg_end(skb, msg_head); //更新netlink消息,该函数必须在发送消息之前调用

  

step 3: 发送消息,可以采用单播,也可以采用组播的方式发送Generic Netlink消息

int rc;

 rc = genlmsg_unicast(skb, pid);

 if (rc != 0)

     goto failure;

  

接收消息

内核通常用来作为Generic Netlink的服务器端,消息的接收通常是由Generic Netlink 总线自动完成的。一旦总线接收到消息,找到正确的路由,消息就被传送到相应的family的操作函数进行处理。如果内核作为Generic Netlink的客户端,服务器的响应可以通过标准的内核socket接口来进行接收。

3.3 Generic Netlink 详解

下面部分主要解释Generic Netlink的消息格式和数据结构

3.3.1 消息格式

Generic Netlink采用标准Netlink子系统作为传输层,因此Generic Netlink的消息格式是Netlink的消息格式. 消息格式定义如下

 Generic Netlink 

3.3.2 数据结构

下面主要是针对内核中Generic Netlink的数据结构进行解释,这些API跟应用程序采用libnl库的函数有些类似。

genl_family结构体

Generic Netlink family的结构体定义如下

struct genl_family

 {

       unsigned int            id;    // 动态申请的channel ID. 传入参数为0x0表明需要controller来分配channel ID, 0x10预留使用。用户对于注册一个新的                       family, 应该采用 GENL_ID_GENERATE宏

       unsigned int            hdrsize; //如果family是采用一个特定的family header, hdrsize表示这个header的大小,如果没有特定的family header, 此值为0

       char                    name[GENL_NAMSIZ]; // family name应该是唯一的,因为controller采用family name来查找channel ID.

       unsigned int            version; // family 特定的版本号

       unsigned int            maxattr; // maxattr保持属性的最大值

       struct nlattr **        attrbuf; // 私有变量,不能修改

       struct list_head        ops_list; // 私有变量,不能修改

       struct list_head        family_list; // 私有变量,不能修改

 };

gen_ops 结构

struct genl_ops

 {

       u8                      cmd;  // 在定义的family中应该唯一,用来参考operation

       unsigned int            flags; //operation操作的特殊属性设置,属性可以用或操作。例如当用户需要 CAP_NET_ADMIN特权时,需要设置GENL_ADMIN_PERM

       struct nla_policy       *policy; // 对于请求的消息采用的策略。如果定义,则在调用相应的操作前,采用策略验证请求消息的所有属性

       int                     (*doit)(struct sk_buff *skb, // 回调函数,一个参数是消息的buffer, 第二参数是Generic Netlink的genl_info结构体

                                       struct genl_info *info);

       int                     (*dumpit)(struct sk_buff *skb, // 该回调函数当 NLM_F_DUMP被设置之后才调用

                                         struct netlink_callback *cb);

       struct list_head        ops_list; //私有变量,不可以修改

 };

 

 

The genl_info Structure

    struct genl_info

    {

       u32                     snd_seq; // Netlink消息序列号

       u32                     snd_pid; // Netlink客户端的进程ID

       struct nlmsghdr *       nlhdr; // 指向Netlink消息头的指针

       struct genlmsghdr *     genlhdr; //指向Generic Netlink消息头的指针

       void *                  userhdr; //如果Generic Netlink familiy定义了一个特定的family header, 那么该指针指向此header.

       struct nlattr **        attrs; // 如果定义了特定的属性,并且该属性被policy验证过,此指针指向该属性

    };

 

nla_policy 结构

struct nla_policy

    {

       u16             type;

       u16             len;

    };

  

Nla policy定义的type

type字段表示attr中的数据类型

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的值表示当type为string的时候,len的值为string的最大长度,不包含结束符\null. 当type为unknown或者NLA_UNSPEC时,len的值应该为属性的payload的值。如果len值为0, 表明该属性不需要被验证。

3.4一些建议

Generic Netlink机制是一种非常灵活的通信方式,可以被用在各种各样的方式下。下面的一些建议是来自Linux 内核的表明,在使用Generic Netlink的时候应该遵循。虽然内核中的现有代码并不是完全遵循,但是新的代码应该考虑将下面的建议作为需求。

属性和消息体

当定义新的Generic Nelink消息的时候,必须利用netlink的属性。为了将来的扩展和前向兼容,Netlink的属性机制是被详细考虑和设计的。Netlink的属性机制还有其他的好处,包含基本的输入检查和开发人员的熟悉程度。

大多数的数据结构可以同netlink属性来表示:

  • 向量值 大多数向量值被很好的定义了
  • 结构体
  • 数组

非常重要的一点是尽可能的采用唯一的属性,这样有助于对netlink属性进行将来的改动。

操作的颗粒度

只注册一个操作对应于Generic Netlink family, 然后采用多个子命令是比较简单的方法,但是为了安全原因,强烈不推荐这样做。将不同行为组合为一个操作的方法限制了操作利用现有Linux内核的安全机制。

确认与错误上报

对于Generic Netlink 服务,返回确认和错误给客户端是非常必要的。Netlink机制提供了灵活的确认和错误上报机制,因此客户端不需要实现确切的确认消息。当错误发生时,NLMSG_ERROR消息被返回给客户端。客户端同样可以在没有错误发生的情况下,通过在请求中设置NLM_F_ACK来请求NLMSG_ERROR消息。

  

  

 

相关文章:

  • 2021-07-25
  • 2022-02-24
  • 2022-03-03
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-05-29
  • 2022-12-23
  • 2021-11-05
  • 2021-04-28
  • 2021-06-15
  • 2021-09-19
相关资源
相似解决方案