文章目录
系统中有已经有很多具体的Netlink协议了,不过它们都是针对某个子系统的,功能已经比较固定,如果我们需要扩展新的Netlink协议,那么就需要对内核进行侵入式修改(最起码要扩展MAX_LINKS),为此,Netlink框架还提供了一个通用的Netlink协议: Generic Netlink。通过它,内核模块可以在无需扩充协议的基础上使用Netlink机制。
这篇笔记分析了内核Generic Netlink核心部分的实现,官方参考资料见这里。
文件 | 说明 |
---|---|
net/netlink/genetlink.c | Generic Netlink协议框架实现文件 |
include/net/genetlink.h | 仅内核态可见的Generic Netlink协议头文件 |
include/linux/genetlink.h | 用户态也可见的Generic Netlink协议头文件 |
数据结构
genl_family
/**
* struct genl_family - generic netlink family
* @id: protocol family idenfitier
* @hdrsize: length of user specific header in bytes
* @name: name of family
* @version: protocol version
* @maxattr: maximum number of attributes supported
* @attrbuf: buffer to store parsed attributes
* @ops_list: list of all assigned operations
* @family_list: family list
* @mcast_groups: multicast groups list
*/
struct genl_family
{
unsigned int id;
unsigned int hdrsize;
char name[GENL_NAMSIZ]; // 每个family也拥有一个全局唯一的名字
unsigned int version;
unsigned int maxattr;
struct nlattr ** attrbuf; /* private */
struct list_head ops_list; /* private */ // 该family的命令操作集链表
struct list_head family_list; /* private */ // 将family对象保存在全局哈希表中
struct list_head mcast_groups; /* private */
};
- id
每个family拥有一个全局唯一的ID,该ID可以由faimily的实现指定(比较难保证唯一性),也可由内核自动分配(推荐做法),可用范围为(GENL_MIN_ID(0x10), GENL_MAX_ID(1023)];
- hdrsize
每个Generic Netlink消息首部除了标准的genlmsghdr外,family还可以追加自己的首部,只需要通过该字段指定自己追加的首部大小即可;
- maxattr/attrbuf
maxattr字段记录了该faimily支持的最大属性个数,如果指定了属性个数,Generic Netlink框架会负责分配attrbuf,该区域用于消息接收过程中的属性解析,具体见genl_rcv_msg()。
genl_ops
genl_ops定义一个family支持的命令。如genl_family的定义,每个family可以支持多个命令,这些命令被组织到genl_family.ops_list中。
/**
* struct genl_ops - generic netlink operations
* @cmd: command identifier
* @flags: flags
* @policy: attribute validation policy
* @doit: standard command callback
* @dumpit: callback for dumpers
* @done: completion callback for dumps
* @ops_list: operations list
*/
struct genl_ops
{
u8 cmd; // 命令ID,在family内部唯一标识命令
unsigned int flags;
const struct nla_policy *policy; // 属性策略,nlmsg_parse()会根据该策略对属性格式做基本的校验
int (*doit)(struct sk_buff *skb, struct genl_info *info);
int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb);
int (*done)(struct netlink_callback *cb);
struct list_head ops_list; // 将ops对象放入family->ops_list链表中
};
- flags
取值来自标准的Netlink消息中的NLM_F_XXX,不过Generic Netlink只用了其中部分标记,这些标记会影响消息的接收流程。
flags | 值 | 含义 |
---|---|---|
GENL_ADMIN_PERM | 0x1 | 表示该消息的发送需要管理员权限 |
GENL_CMD_CAP_DO | 0x2 | 表示该操作集指定了doit()回调 |
GENL_CMD_CAP_DUMP | 0x4 | 表示该操作集指定了dumpit()回调 |
GENL_CMD_CAP_HASPOL | 0x8 | 表示该操作集指定了属性策略 |
多播组: genl_multicast_group
/**
* struct genl_multicast_group - generic netlink multicast group
* @name: name of the multicast group, names are per-family
* @id: multicast group ID, assigned by the core, to use with
* genlmsg_multicast().
* @list: list entry for linking
* @family: pointer to family, need not be set before registering
*/
struct genl_multicast_group
{
struct genl_family *family; /* private */
struct list_head list; /* private */
char name[GENL_NAMSIZ]; // 多播组名字
u32 id; // 多播组ID,在整个Generic Netlink层面唯一
};
/*
* Bitmap of multicast groups that are currently in use.
*
* To avoid an allocation at boot of just one unsigned long,
* declare it global instead.
* Bit 0 is marked as already used since group 0 is invalid.
*/
static unsigned long mc_group_start = 0x1;
static unsigned long *mc_groups = &mc_group_start;
// 按位表示所有多播组需要的unsigned long整数个数
static unsigned long mc_groups_longs = 1;
Generic Netlink框架
+---------------------+ +---------------------+
| (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" |
+---------------------+ +---------------------+
需要解释几个核心关键点:
- Generic Netlink是基于Netlink框架的一种协议;
- 基于Generic Netlink的模块在Generic Netlink来看是一个个的family,对应到代码就是一个个的genl_family对象,特殊的,Generic Netlink框架实现了一个特殊的控制family,它用来辅助管理所有的普通family。
Generic Netlink消息
Generic 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 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
消息头部
如上,除了通用的Netlink消息首部外,Generic Netlink消息有固定首部genlmsghdr,其格式如下:
struct nlmsghdr {
...
__u16 nlmsg_type; // Generic Netlink消息时,该字段必须设置为Generic Netlink的famlyId
...
}
struct genlmsghdr {
__u8 cmd; // family内部唯一标识命令的ID
__u8 version;
__u16 reserved;
};
此外,如果具体的family在实现时需要有自己的标准首部,那么可以通过genl_family.hdrsize指定其大小,这样框架在需要时就可以做出正确的处理了。
消息API
为了方便编程,Generic Netlink框架在Netlink协议消息API的基础上,实现了一组包装接口,核心API介绍如下。
消息的封装
使用genlmsg_new()分配一个Generic Netlink消息的skb。
/**
* genlmsg_new - Allocate a new generic netlink message
* @payload: size of the message payload
* @flags: the type of memory to allocate.
*/
static inline struct sk_buff *genlmsg_new(size_t payload, gfp_t flags);
然后使用genlmsg_put()填充消息首部,包括Netlink消息首部和Generic Netlink消息首部。
/**
* genlmsg_put - Add generic netlink header to netlink message
* @skb: socket buffer holding the message
* @pid: netlink pid the message is addressed to(发送方pid)
* @seq: sequence number (usually the one of the sender)
* @family: generic netlink family
* @flags netlink message flags
* @cmd: generic netlink command
*
* Returns pointer to user specific header
*/
static inline void *genlmsg_put(struct sk_buff *skb, u32 pid, u32 seq,
struct genl_family *family, int flags, u8 cmd);
之后便可以使用Netlink消息属性相关的API填充消息的payload,在填充完毕后可以使用genlmsg_end()结束消息的封装。
/**
* genlmsg_end - Finalize a generic netlink message
* @skb: socket buffer the message is stored in
* @hdr: user specific header,来自genlmsg_put()的返回值
*/
static inline int genlmsg_end(struct sk_buff *skb, void *hdr);
消息的解析
可以使用genlmsg_data()获得消息得payload指针(如果family有自己的首部,返回的是指向该首部的指针)。
/**
* gennlmsg_data - head of message payload
* @gnlh: genetlink messsage header
*/
static inline void *genlmsg_data(const struct genlmsghdr *gnlh)
{
return ((unsigned char *) gnlh + GENL_HDRLEN);
}
可以使用genlmsg_len()获取到消息payload得长度。
/**
* genlmsg_len - length of message payload
* @gnlh: genetlink message header
*/
static inline int genlmsg_len(const struct genlmsghdr *gnlh)
可以使用下面两个api计算出长度为payload的Generic Netlink消息得长度。
/**
* genlmsg_msg_size - length of genetlink message not including padding
* @payload: length of message payload
*/
static inline int genlmsg_msg_size(int payload);
/**
* genlmsg_total_size - length of genetlink message including padding
* @payload: length of message payload
*/
static inline int genlmsg_total_size(int payload);
消息的发送
内核态可以使用如下接口进行单播和组播消息发送。
/**
* genlmsg_unicast - unicast a netlink message
* @skb: netlink message as socket buffer
* @pid: netlink pid of the destination socket
*/
static inline int genlmsg_unicast(struct sk_buff *skb, u32 pid);
/**
* genlmsg_multicast - multicast a netlink message
* @skb: netlink message as socket buffer
* @pid: own netlink pid to avoid sending to yourself
* @group: multicast group id
* @flags: allocation flags
*/
static inline int genlmsg_multicast(struct sk_buff *skb, u32 pid,
unsigned int group, gfp_t flags);
协议初始化
在Generic Netlink初始化时,向Netlink注册了NETLINK_GENERIC协议类型。
static int __init genl_init(void)
{
int i, err;
// 初始化全局family对象哈希表
for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
INIT_LIST_HEAD(&family_ht[i]);
// 注册控制family
err = genl_register_family(&genl_ctrl);
if (err < 0)
goto errout;
// 为控制family注册命令操作集
err = genl_register_ops(&genl_ctrl, &genl_ctrl_ops);
if (err < 0)
goto errout_register;
// 设置非root用户也可以接收GeNetlink组播消息
netlink_set_nonroot(NETLINK_GENERIC, NL_NONROOT_RECV);
// 向Netlink注册了NETLINK_GENERIC协议类型,对应的数据接收函数为genl_rcv()
genl_sock = netlink_kernel_create(&init_net, NETLINK_GENERIC, 0,
genl_rcv, &genl_mutex, THIS_MODULE);
if (genl_sock == NULL)
panic("GENL: Cannot initialize generic netlink\n");
err = genl_register_mc_group(&genl_ctrl, ¬ify_grp);
if (err < 0)
goto errout_register;
return 0;
errout_register:
genl_unregister_family(&genl_ctrl);
errout:
panic("GENL: Cannot register controller: %d\n", err);
}
subsys_initcall(genl_init);
family的管理
Generic Netlink将内部更具体的协议称为family,每个family对应一个genl_family对象,所有的family对象被保存在全局哈希表中。
#define GENL_FAM_TAB_SIZE 16
#define GENL_FAM_TAB_MASK (GENL_FAM_TAB_SIZE - 1)
static struct list_head family_ht[GENL_FAM_TAB_SIZE];
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */
family的注册&去注册
#define GENL_MIN_ID NLMSG_MIN_TYPE
#define GENL_MAX_ID 1023
int genl_register_family(struct genl_family *family)
{
int err = -EINVAL;
// 检查指定的family ID是否在有效范围内
if (family->id && family->id < GENL_MIN_ID)
goto errout;
if (family->id > GENL_MAX_ID)
goto errout;
// 初始化操作链表和多播链表
INIT_LIST_HEAD(&family->ops_list);
INIT_LIST_HEAD(&family->mcast_groups);
genl_lock();
// 确保family的名字全局唯一
if (genl_family_find_byname(family->name)) {
err = -EEXIST;
goto errout_locked;
}
// 确保family的ID全局唯一
if (genl_family_find_byid(family->id)) {
err = -EEXIST;
goto errout_locked;
}
// 当指定的id为GENL_ID_GENERATE时,内核会自动分配一个(很实用的功能)
if (family->id == GENL_ID_GENERATE) {
u16 newid = genl_generate_id();
if (!newid) {
err = -ENOMEM;
goto errout_locked;
}
family->id = newid;
}
// 如果指定了支持的属性个数,则分配属性buffer,方便后面接收到消息后的属性解析
if (family->maxattr) {
family->attrbuf = kmalloc((family->maxattr+1) * sizeof(struct nlattr *), GFP_KERNEL);
if (family->attrbuf == NULL) {
err = -ENOMEM;
goto errout_locked;
}
} else
family->attrbuf = NULL;
// 将新的family保存到全局哈希表中
list_add_tail(&family->family_list, genl_family_chain(family->id));
genl_unlock();
// 发布一个CTRL_CMD_NEWFAMILY组播事件
genl_ctrl_event(CTRL_CMD_NEWFAMILY, family);
return 0;
errout_locked:
genl_unlock();
errout:
return err;
}
对应的去注册函数为genl_unregister_family(),这里不再展开。
family操作集的注册&去注册
每个family能够支持多个命令,每个命令对应一个genl_ops对象,family将其支持的命令组织到family->ops_list链表中。对应的注册函数为genl_register_ops(),去注册函数为genl_unregister_ops(),这里不再展开去注册函数。
int genl_register_ops(struct genl_family *family, struct genl_ops *ops)
{
int err = -EINVAL;
// 命令至少指定dumpit()和doit()回调中的一个
if (ops->dumpit == NULL && ops->doit == NULL)
goto errout;
// 命令ID在family内要保持唯一
if (genl_get_cmd(ops->cmd, family)) {
err = -EEXIST;
goto errout;
}
// 设置回调函数标记
if (ops->dumpit)
ops->flags |= GENL_CMD_CAP_DUMP;
if (ops->doit)
ops->flags |= GENL_CMD_CAP_DO;
if (ops->policy)
ops->flags |= GENL_CMD_CAP_HASPOL;
// 将ops对象放入family->ops_list链表中
genl_lock();
list_add_tail(&ops->ops_list, &family->ops_list);
genl_unlock();
// 向外发送CTRL_CMD_NEWOPS组播事件
genl_ctrl_event(CTRL_CMD_NEWOPS, ops);
err = 0;
errout:
return err;
}
控制family
Generic Netlink框架的核心是其控制family,其family对象实例化如下:
#define GENL_ID_CTRL NLMSG_MIN_TYPE // 0x10,控制family的familyID是固定的
static struct genl_family genl_ctrl = {
.id = GENL_ID_CTRL,
.name = "nlctrl",
.version = 0x2,
.maxattr = CTRL_ATTR_MAX,
};
其命令操作集如下。可见控制family只支持接收一个CTRL_CMD_GETFAMILY命令(其它命令是向外发送的),用户态程序可以通过该命令查询已注册family的信息。
static struct genl_ops genl_ctrl_ops = {
.cmd = CTRL_CMD_GETFAMILY,
.doit = ctrl_getfamily,
.dumpit = ctrl_dumpfamily,
.policy = ctrl_policy,
};
family查询命令: ctrl_getfamily()
static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info)
{
struct sk_buff *msg;
struct genl_family *res = NULL;
int err = -EINVAL;
// 通过family ID查询
if (info->attrs[CTRL_ATTR_FAMILY_ID]) {
u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]);
res = genl_family_find_byid(id);
}
// 通过family名字查询,显然名字的优先级更高
if (info->attrs[CTRL_ATTR_FAMILY_NAME]) {
char *name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]);
res = genl_family_find_byname(name);
}
// 没有找到
if (res == NULL) {
err = -ENOENT;
goto errout;
}
// 填充该family的信息到skb
msg = ctrl_build_family_msg(res, info->snd_pid, info->snd_seq, CTRL_CMD_NEWFAMILY);
if (IS_ERR(msg)) {
err = PTR_ERR(msg);
goto errout;
}
// 将封装好的skb返回给用户态
err = genlmsg_reply(msg, info);
errout:
return err;
}
可见,内核支持通过名字和ID来查询family的信息,不过大多数的使用场景是通过名字查询。ctrl_build_family_msg()函数不再展开,从中可以看到通过该命令可以查询到一个family的所有信息,包括名字、ID、版本、支持的命令列表等等。
此外,控制family还支持一个组播,通过该组播,控制family向外广播family的变化以及family组播的变化情况。
消息接收: genl_rcv()
如协议初始化部分分析,当Netlink框架收到发送往Generic Netlink的消息时,会调用Generic Netlink提供的回调函数: genl_rcv()。
static void genl_rcv(struct sk_buff *skb)
{
genl_lock();
netlink_rcv_skb(skb, &genl_rcv_msg);
genl_unlock();
}
持锁后,调用Netlink框架提供的netlink_rcv_skb()函数对Netlink消息进行接收,该函数的核心思想就是循环处理skb中的Netlink消息,对于合法的消息,将其交给回调函数处理。
int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *, struct nlmsghdr *))
{
struct nlmsghdr *nlh;
int err;
// 循环解析Netlink消息
while (skb->len >= nlmsg_total_size(0)) { // 消息长度满足一个Netlink消息头部
int msglen;
nlh = nlmsg_hdr(skb); // Netlink消息首部
err = 0;
// 检查skb中是否够一个完整的Netlink消息
if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
return 0;
// 发往内核的Netlink消息必须设置REQUEST标记
if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
goto ack;
// 跳过控制类消息:NLMSG_NOOP等属于控制消息
if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
goto ack;
// 将消息传给回调函数处理
err = cb(skb, nlh);
if (err == -EINTR)
goto skip;
ack:
// 可见,对于错误消息,内核总是会ACK的
if (nlh->nlmsg_flags & NLM_F_ACK || err)
netlink_ack(skb, nlh, err);
skip:
// skb指针向前偏移指定长度,继续处理下一个Netlink消息
msglen = NLMSG_ALIGN(nlh->nlmsg_len);
if (msglen > skb->len)
msglen = skb->len;
skb_pull(skb, msglen);
}
return 0;
}
genl_rcv_msg()
该函数用于接收一个Generic Netlink消息,消息数据已经在参数nlh中了。
static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
struct genl_ops *ops;
struct genl_family *family;
struct genl_info info;
struct genlmsghdr *hdr = nlmsg_data(nlh);
int hdrlen, err;
// 根据family ID找到注册的family对象
family = genl_family_find_byid(nlh->nlmsg_type);
if (family == NULL)
return -ENOENT;
// 头部长度检查:消息的长度要至少包含完整的Generic Netlink消息首部
hdrlen = GENL_HDRLEN + family->hdrsize;
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
return -EINVAL;
// 从family中查找该命令对应的genl_ops对象
ops = genl_get_cmd(hdr->cmd, family);
if (ops == NULL)
return -EOPNOTSUPP;
// 如果该操作有权限限制,则进行权限检查
if ((ops->flags & GENL_ADMIN_PERM) && security_netlink_recv(skb, CAP_NET_ADMIN))
return -EPERM;
// 消息如果指定了NLM_F_DUMP标记,则调用的是ops中的dumpit()和done()回调
if (nlh->nlmsg_flags & NLM_F_DUMP) {
if (ops->dumpit == NULL)
return -EOPNOTSUPP;
genl_unlock();
err = netlink_dump_start(genl_sock, skb, nlh, ops->dumpit, ops->done);
genl_lock();
return err;
}
// 命令必须提供doit()回调
if (ops->doit == NULL)
return -EOPNOTSUPP;
// 如果family提供了属性buf,那么用该buffer空间存储解析后的消息属性
if (family->attrbuf) {
err = nlmsg_parse(nlh, hdrlen, family->attrbuf, family->maxattr, ops->policy);
if (err < 0)
return err;
}
// 将信息组织到info中,然后调用命令操作集的doit()回调
info.snd_seq = nlh->nlmsg_seq;
info.snd_pid = NETLINK_CB(skb).pid;
info.nlhdr = nlh;
info.genlhdr = nlmsg_data(nlh);
info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
info.attrs = family->attrbuf;
return ops->doit(skb, &info);
}
Generic Netlink组播
Generic Netlink在Netlink组播的基础上,也可支持具体family实现自己的组播。
多播组的注册&去注册
具体family要支持多播组,那么需要首先向Generic Netlink框架注册,对应的函数为genl_register_mc_group(),类似的,去注册接口为genl_unregister_mc_group(),这里不再展开。
/**
* genl_register_mc_group - register a multicast group
*
* Registers the specified multicast group and notifies userspace
* about the new group.
*
* Returns 0 on success or a negative error code.
*
* @family: The generic netlink family the group shall be registered for.
* @grp: The group to register, must have a name.
*/
int genl_register_mc_group(struct genl_family *family,
struct genl_multicast_group *grp)
{
int id;
unsigned long *new_groups;
int err;
BUG_ON(grp->name[0] == '\0');
genl_lock();
/* special-case our own group */
if (grp == ¬ify_grp) // 控制family的多播组ID是固定的
id = GENL_ID_CTRL;
else
// 分配一个组ID
id = find_first_zero_bit(mc_groups,
mc_groups_longs * BITS_PER_LONG);
// 多播组掩码已经不够用了,重新分配
if (id >= mc_groups_longs * BITS_PER_LONG) {
size_t nlen = (mc_groups_longs + 1) * sizeof(unsigned long);
if (mc_groups == &mc_group_start) {
new_groups = kzalloc(nlen, GFP_KERNEL);
if (!new_groups) {
err = -ENOMEM;
goto out;
}
mc_groups = new_groups;
*mc_groups = mc_group_start;
} else {
new_groups = krealloc(mc_groups, nlen, GFP_KERNEL);
if (!new_groups) {
err = -ENOMEM;
goto out;
}
mc_groups = new_groups;
mc_groups[mc_groups_longs] = 0;
}
mc_groups_longs++;
}
// 让Netlink框架变更协议对象nl_table中的组播字段
err = netlink_change_ngroups(genl_sock, mc_groups_longs * BITS_PER_LONG);
if (err)
goto out;
// 设置多播组对象中的各字段
grp->id = id;
set_bit(id, mc_groups);
list_add_tail(&grp->list, &family->mcast_groups);
grp->family = family;
// 控制family对外广播NEWMCAST_GRP事件
genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, grp);
out:
genl_unlock();
return err;
}