Generic Netlink

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_第1张图片

 

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_第2张图片 

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消息。

  

  

 

你可能感兴趣的:(Generic Netlink)