http://blog.chinaunix.net/uid-20273473-id-3137039.html
1. Generic Netlink 的数据结构
首先, Generic Netlink是按照Family管理的,内核的其它模块或子系统可以向它注册自己的Family. 所有的Family会存放在一张Hash链表上。
注册Family:
如果Family ID是GENL_ID_GENERATE(被定义为0),则表示由系统自动分配ID,否则表示指定固定ID。 指定的ID必须在大于0x10并小于等于1023. 0x10被预留给CTRL_FAMILY.
自动分配ID的方法是从0x10开始找到第一个未被分配的ID。 可见Family ID是全局唯一的。
Family在Hash表上的位置由ID决定, ID & 0x0F, 也就是ID的低四Bit决定
- #define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */
-
- #define GENL_ID_GENERATE 0
- #define GENL_ID_CTRL NLMSG_MIN_TYPE
-
- #define GENL_MIN_ID NLMSG_MIN_TYPE
- #define GENL_MAX_ID 1023
自动分配ID的方法是从0x10开始找到第一个未被分配的ID。 可见Family ID是全局唯一的。
Family在Hash表上的位置由ID决定, ID & 0x0F, 也就是ID的低四Bit决定
Family name最好也是全局唯一的, 在genl_family_find_byname(char *name)函数中可见它是找到第一个匹配的name就返回的。
Family的maxattr:根据maxattr的值给struct nlattr ** attrbuf; 分配相应大小的内存。nlattr的作用后面会讲到。
每个Family都会有一个operations的列表,可以调用genl_register_ops()函数把各个ops添加到famliy 的ops_list;链表中。每个ops都有一个cmd和相应的Handler.
每个Family通常也都会有一个mc_group的列表。
每个mc_group都隶属于一个Famliy, 同一个Family内的mc_group不能有相同的name,
Name是由用户指定的,但是ID是由系统分配的,在genl_register_mc_group()函数里面会分配ID
分配ID的方法是:在起始地址为mc_groups的内存地方设置一个bitmap, bit0初始化为1, 然后寻找第一个为零的Bit,该Bit的位置即为mc_group的ID, 并把该Bit置为1
这个Bitmap初始化时只有32Bit空间,随着Group数目的增多会动态增大。
可见:mc_group的ID是全局唯一的。
每个mc_group都隶属于一个Famliy, 同一个Family内的mc_group不能有相同的name,
Name是由用户指定的,但是ID是由系统分配的,在genl_register_mc_group()函数里面会分配ID
分配ID的方法是:在起始地址为mc_groups的内存地方设置一个bitmap, bit0初始化为1, 然后寻找第一个为零的Bit,该Bit的位置即为mc_group的ID, 并把该Bit置为1
这个Bitmap初始化时只有32Bit空间,随着Group数目的增多会动态增大。
可见:mc_group的ID是全局唯一的。
2. The Control Family
ctrl_family是一个特殊的Family, 它是由Generic Netlink自己注册和实现,并用来查询Family列表、管理各个Family的添加、删除等事件的。这里以ctrl_family为例看一下注册famly、注册ops、注册mcgroup的方法。
- static struct genl_family genl_ctrl = {
- .id = GENL_ID_CTRL,
- .name = "nlctrl",
- .version = 0x2,
- .maxattr = CTRL_ATTR_MAX,
- .netnsok = true,
- };
ID是0x10, 名字是nlctrl
它只有一个ops:
- static struct genl_ops genl_ctrl_ops = {
- .cmd = CTRL_CMD_GETFAMILY,
- .doit = ctrl_getfamily,
- .dumpit = ctrl_dumpfamily,
- .policy = ctrl_policy,
- };
只有一个mc_group, 名字为notify:
- static struct genl_multicast_group notify_grp = {
- .name = "notify",
- };
这个group的ID被预留为0x10,而不是按照通用的规则分配的。
所谓policy, 是用来验证接收到的数据是否合适。 接收到数据由一个属性header(struct nlattr)和真正的数据构成, 对一种属性的数据, 定义一个policy, 如下代码表示如果数据CTRL_ATTR_FAMILY_ID属性的,那么其类型必须是U16.
- static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = {
- [CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },
- [CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING,
- .len = GENL_NAMSIZ - 1 },
- };
3. 用户空间程序查询Family列表
分析用户空间程序向ctrl_family发送CTRL_CMD_GETFAMILY命令后,内核的执行流程和向用户程序反送结果的流程。 libmnl-1.0.2里面的genl-family-get.c文件:
分析用户空间程序向ctrl_family发送CTRL_CMD_GETFAMILY命令后,内核的执行流程和向用户程序反送结果的流程。 libmnl-1.0.2里面的genl-family-get.c文件:
- nlh = mnl_nlmsg_put_header(buf);
- nlh->nlmsg_type = GENL_ID_CTRL;
- nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
- nlh->nlmsg_seq = seq = time(NULL);
-
- genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
- genl->cmd = CTRL_CMD_GETFAMILY;
- genl->version = 1;
可见做法是先构造nlmsghdr再构造genlmsghdr。
Nlmsghdr的nlmsg_type赋值为要接收消息的Family ID,
Genlmsghdr的cmd 赋值为要发送的CMD
Genlmsghdr的cmd 赋值为要发送的CMD
- mnl_attr_put_u32(nlh, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL);
- if (argc >= 2)
- mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, argv[1]);
- else
- nlh->nlmsg_flags |= NLM_F_DUMP;
所谓attr, 意思就是标记要传送的数据是什么数据。
这里填充了两种属性的数据:CTRL_ATTR_FAMILY_ID和CTRL_ATTR_FAMILY_NAME
每种数据都是一个header紧跟真正的数据。如下图:
这里填充了两种属性的数据:CTRL_ATTR_FAMILY_ID和CTRL_ATTR_FAMILY_NAME
每种数据都是一个header紧跟真正的数据。如下图:
发送数据:
- if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
- perror("mnl_socket_send");
- exit(EXIT_FAILURE);
- }
内核接收到消息后会交由genl_rcv_msg 函数处理, 该函数找到相应cmd对应的doit函数并执行之。对于CTRL_CMD_GETFAMILY来说,就是ctrl_dumpfamily函数。这个函数构造好回复信息后调用genlmsg_reply发送回复到用户程序。
用户空间程序怎样监听某一个mc_group: 用setsockopt
需要知道mc_group的ID才行。
- mnl_socket_setsockopt(nl, NETLINK_ADD_MEMBERSHIP, &grp, sizeof(grp));