目录
1、新建一个套接字,并绑定上 netlink address
2、向 GENL_ID_CRTL 询问想通信的命令族的 id
一、generic netlink 消息结构
消息以流的形式在程序之间进行传递,一个流中可能包含多个消息。
对于每个消息消息来说,为了便于维护和方便使用,还需要一些有关记录消息的信息。一个 netlink message 结构如下:
其中 nlmsghdr 结构里记录了该条 netlink message 的如下信息:
struct nlmsghdr {
__u32 nlmsg_len; //该 netlink message 的长度(包括 nlmsghdr 部分本身)
__u16 nlmsg_type; //该 netlink message 的种类
__u16 nlmsg_flags; //一个附加的标记
__u32 nlmsg_seq; //该 netlink message 在消息流中的序列号
__u32 nlmsg_pid; //发送该 netlink message 的进程的进程号 pid
};
为了使数据对齐,在 payload 两端可能会塞入了一些填充字段。netlink message 的 payload 结构如下:
这个结构称作 family。进程间通信的目的通常是希望信息接收方能够执行某些动作,而很多命令都是围绕同一个事件的(如对某个表的增、删、改、查)。因此为了便于管理,netlink 通信协议将这些相关的一套命令和其对应的操作组织起来,归于一个family管辖,每个family都有个独有的名字,该名字需公示出来,以便其他程序与该 family 通信,当 family 在内核完成注册后,内核会给其分配一个独有的 id。如果用户需要结构化的信息,可以在 netlink message 的 payload 中添加可自选的信息头 user specific header。剩下的 attributes 结构如下:
每个 attribute 即是一个不可再分的最基本信息单元,结构如下:
其中 nlattr 是管理 payload 的信息头,记录了 payload 的以下信息:
struct nlattr {
__u16 nla_len;//attribute 长度,包括 nlattr 本身
__u16 nla_type;//attribute 的类型
};
payload 部分即是想要传递的信息了,如传递一句话 “ hello world ”。为了对齐,payload 两端可能会塞入 pad。
netlink message 协议使用的信息结构即是以上这些,但对着使用越来越多,内核可以用来分配给 family 的 id 资源开始紧张了。因此 generic netlink 协议在 netlink 协议上又做了一些拓展,即在 netlink message 的 payload 上又做了一层封装:
该结构即为 generic netlink message。该信息头 genlmsghdr 中记录如下内容。
struct genlmsghdr {
__u8 cmd; //命令号
__u8 version; //命令版本号
__u16 reserved; //保留字段
};
故最终 generic netlink message 的结构如下:
二、接口搭建过程
kernel端程序的准备
1、定义想要传送的消息种类
/* generic netlink message attributes */
enum {
MY_ATTR_UNSPEC,
MY_ATTR_MSG,
__MY_ATTR_MAX,
};
#define MY_ATTR_MAX (__MY_ATTR_MAX - 1)
你可能有多种消息需要传递,使用枚举类型来管理消息种类是个不错的办法。这里实际上只定义了一个消息种类,即 MY_ATTR_MSG。 根据枚举类型的特点,在枚举体最后定义的 __MY_ATTR_MAX 实际上表示了该属性集的大小,而 MY_ATTR_MAX 则表示了该属性集中最后一类属性(即 MY_MTTR_MSG)的编号 1。
(在 linux 内核中大量的枚举体的首个元素名均是以 UNSPEC 结尾,在这里 MY_ATTR_UNSPEC 作为该属性集的“一般属性”,可能只是用来对应一些“空操作”,而并不特指哪一消息类型。——作者猜测)
2、定义一组命令
/* commands 定义命令类型,用户空间以此来表明需要执行的命令 */
enum {
MY_CMD_UNSPEC,
MY_CMD_1, //命令
__MY_CMD_MAX, //表示命令集的大小,并不是命令
};
#define MY_CMD_MAX (__MY_CMD_MAX - 1)
与消息属性的定义类似,这里同样使用了枚举类型来定义命令集,但其实这里只定义了一个命令,即 MY_CMD_1。根据枚举类型的特点,在枚举体最后定义的 __MY_CMD_MAX 实际上表示了该命令集的大小,而 MY_CMD_MAX 则表示了该命令集中最后一个命令(即 MY_CMD_1)的编号 1。
3、为每个命令定义一个响应函数
generic netlink协议要求命令相应函数的类型是:
static int my_callback_function(struct sk_buff *skb, struct genl_info *info);
4、将命令与相应函数关联起来
static struct genl_ops my_cmd_ops = {
.cmd = MY_CMD_1, //命令
.flags = 0,
.policy = my_cmd_policy,
.doit = my_callback_function, //响应函数
.dumpit = NULL,
};
通过定义一个 genl_ops, 我们可以将一个命令和其对应的响应函数关联起来。成员 policy 指明了该响应函数对其能够处理的信息的要求。具体如下:
static struct nla_policy my_cmd_policy[MY_ATTRIBUTE_MAX + 1] = {
[MY_ATTRIBUTE_MSG] = { .type = NLA_NUL_STRING },
};
这里使用了比较难懂的语法,先来看一下 struct nla_policy 的定义:
struct nla_policy { u16 type; u16 len; };
my_cmd_policy 是一个 nal_policy 结构体数组,数组长度为2(即 MY_ATTRIBUTE_MAX+1) ,即前文定义的 generic netlink message 属性集的长度(my_cmd_policy[0] 对应 MY_ATTRIBUTE_UNSPEC;my_cmd_policy[1] 对应 MY_ATTRIBUTE_MSG)。在大括号中将 my_cmd_policy[1] 的 type 属性赋值为 NLA_NUL_STRING,该宏的意思是除去NULL外的最大字符串长度。
5、创建一个命令族
/* family definition */
static struct genl_family my_cmd_family = {
.id = GENL_ID_GENERATE, //