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架构中的位置,如下图。
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的消息格式. 消息格式定义如下
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消息。