1 概述
Generic Netlink 是内核专门为了扩展netlink协议簇而设计的“通用netlink协议簇”。由于netlink协议最多支持32个协议簇,目前Linux4.1的内核中已经使用其中21个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在include/linux/netlink.h中添加簇定义,但有时不方便,为此Linux设计了这种通用Netlink协议簇,用户可在此之上定义更多类型的子协议。Generic Netlink使用NETLINK_GENERIC类型协议簇,同样基于netlink子系统。具体框架如下: Genetlink消息基于这个消息结构类型并定制化为如下结构:
其中family头对于Genetlink来说就是Generic消息头genlmsghdr,接下来是可选的用户特定消息头,最后才是可选的有效载荷,即一个个消息属性实例。Genetlink消息是命令驱动式的,即每一条消息的genlmsghdr中都指明了当前消息的cmd消息命令,这些消息cmd命令由用户自行定义。内核在接收到用户的genl消息后,首先会对命令cmd做判断,找到对应的消息处理结构(可能会执行attr有效性检查),然后才会去调用消息处理回调函数从消息载荷区中读取并处理其所需要的的attr属性载荷。
2 主要数据
2.1 struct genlmsghdr
Generic Netlink消息头结构。Generic Netlink消息头比较简单,仅包含了两个字段。其中cmd表示消息命令,对于用户自己定义的每个子协议类型都需要定义特定的消息命令集,这里该字段表示当前消息的消息命令;version字段表示版本控制,可以在在不破坏向后兼容性的情况下修改消息的格式,可以不使用该字段;最后的reserved字段保留。
1 struct genlmsghdr { 2 __u8 cmd; 3 __u8 version; 4 __u16 reserved; 5 };
2.2 struct genl_family
Generic Netlink Family结构:struct genl_family,内核中完成注册。Generic Netlink按照family进行管理,用户需注册自己定义的genl_family结构,同时内核使用一个哈希表family_ht对已经注册的genl family进行管理。
1 struct genl_family { 2 unsigned int id;//genl family的ID号,一般由内核进行分配,取值范围为GENL_MIN_ID~GENL_MAX_ID(16~1023),其中GENL_ID_CTRL为控制器的family ID,不可另行分配,该familyID全局唯一并且在family_ht中的位置也由该值确定 3 unsigned int hdrsize;//用户私有报头的长度,即可选的user msg header长度,若没有则为0 4 char name[GENL_NAMSIZ];//genl family的名称,必须是独一无二且用户层已知的(用户通过它来向控制查找family id) 5 unsigned int version;//版本号 6 unsigned int maxattr;//消息属性attr最大的类型数(即该genl family所支持的最大attr属性类型的种类个数) 7 bool netnsok;//指示当前簇是否能够处理网络命名空间 8 bool parallel_ops; 9 int (*pre_doit)(const struct genl_ops *ops, 10 struct sk_buff *skb, 11 struct genl_info *info);//调用genl_ops结构中处理消息函数doit()前调用的钩子函数,一般用于执行一些前置的当前簇通用化处理,例如对临界区加锁等 12 void (*post_doit)(const struct genl_ops *ops, 13 struct sk_buff *skb, 14 struct genl_info *info);//调用genl_ops结构中处理消息函数doit()后调用的钩子函数,一般执行pre_doit函数相反的操作 15 /*在绑定/解绑定socket到一个特定的genl netlink组播组中调用,目前内核中没有相关使用*/ 16 int (*mcast_bind)(struct net *net, int group); 17 void (*mcast_unbind)(struct net *net, int group); 18 struct nlattr ** attrbuf; /* 保存拷贝的attr属性缓存*/ 19 const struct genl_ops * ops; /* 保存genl family命令处理结构即命令的个数*/ 20 const struct genl_multicast_group *mcgrps; /* 保存当前簇使用的组播组及组播地址的个数*/ 21 unsigned int n_ops; /* 保存genl family命令处理结构即命令的个数*/ 22 unsigned int n_mcgrps; /* 保存当前簇使用的组播组及组播地址的个数 */ 23 unsigned int mcgrp_offset; /* private */ 24 struct list_head family_list; /* 链表结构,用于将当前当前簇链入全局family_ht散列表中*/ 25 struct module *module; 26 }
2.3 struct genl_ops
Generic Netlink Family命令处理结构,内核中完成注册。该结构用于注册genl family的用户命令cmd处理函数。对于只向应用层发送消息的簇可以不用实现和注册该结构。
1 struct genl_ops { 2 const struct nla_policy *policy;//属性attr有效性策略,若该字段不为空,在genl执行消息处理函数前会对消息中的attr属性进行校验,否则则不做校验 3 int (*doit)(struct sk_buff *skb, 4 struct genl_info *info);//标准命令回调函数,在当前族中收到数据时触发调用,函数的第一个入参skb中保存了用户下发的消息内容 5 int (*start)(struct netlink_callback *cb); 6 int (*dumpit)(struct sk_buff *skb, 7 struct netlink_callback *cb); --------- 详解1 8 int (*done)(struct netlink_callback *cb);//转储结束后执行的回调函数 9 u8 cmd;//簇命令类型,由用户自行根据需要定义 10 u8 internal_flags;//簇私有标识,用于进行一些分支处理,可以不使用 11 u8 flags;//操作标识,有以下四种类型,在genetlink.h中定义 ---------- 详解2 12 };
详解1:dumpit:转储回调函数,当genl_ops的flag标志被添加了NLM_F_DUMP以后会调用该回调函数,这里的第一个入参skb中不再有用户下发消息内容,而是要求函数能够在传入的skb中填入消息载荷并返回填入数据长度。
详解2:操作标识,有以下四种类型,在genetlink.h中定义,如下:
1 #define GENL_ADMIN_PERM 0x01 // 当设置该标识时表示本命令操作需要具有CAP_NET_ADMIN权限 2 #define GENL_CMD_CAP_DO 0x02 // 当genl_ops结构中实现了doit()回调函数则设置该标识 3 #define GENL_CMD_CAP_DUMP 0x04 // 当genl_ops结构中实现了dumpit()回调函数则设置该标识 4 #define GENL_CMD_CAP_HASPOL 0x08 //当genl_ops结构中定义了属性有效性策略(nla_policy)则设置该标识
2.4 struct genl_info
Generic Netlink Family内核接收消息结构。内核在接收到用户的genetlink消息后,会对消息解析并封装成genl_info结构,便于命令回校函数进行处理,其中各字段含义如下:
1 struct genl_info { 2 u32 snd_seq;//消息的发送序号,不强制使用 3 u32 snd_portid;//消息发送端socket所绑定的ID 4 struct nlmsghdr * nlhdr;//netlink消息头 5 struct genlmsghdr * genlhdr;//generic netlink消息头 6 void * userhdr;//用户私有报头 7 struct nlattr ** attrs;//netlink属性,包含了消息的实际载荷 8 possible_net_t _net;//network namespace 9 void * user_ptr[2];//应用指针 10 };
3 Generic Netlink初始化
Generic Netlink只是中特殊类型的Netlink,本质上还是依赖于netlink的内核机制,相关的函数在genetlink.c中,由genl_init()启动初始化流程。
3.1 函数genl_init
其定义如下:
1 static int __init genl_init(void) 2 { 3 int i, err; 4 5 for (i = 0; i < GENL_FAM_TAB_SIZE; i++) 6 INIT_LIST_HEAD(&family_ht[i]);//初始化用于保存和维护Generic netlink family的散列表family_ht数组 7 8 err = genl_register_family_with_ops_groups(&genl_ctrl, genl_ctrl_ops, ----------------- 详解1 函数详解见3.2节 9 genl_ctrl_groups);//向内核Generic netlink子系统注册控制器簇类型的Genetlink Family 10 if (err < 0) 11 goto problem; 12 13 err = register_pernet_subsys(&genl_pernet_ops);//为当前系统中的网络命名空间创建Generic Netlink套接字 --------------- 详解2见3.3节 14 if (err) 15 goto problem; 16 17 return 0; 18 19 problem: 20 panic("GENL: Cannot register controller: %d\n", err); 21 }
详解1:genl_ctrl : 调用genl_register_family_with_ops_groups向内核Generic netlink子系统注册控制器簇类型的Genetlink Family。genl_ctrl的定义如下:
1 /************************************************************************** 2 * Controller 3 **************************************************************************/ 4 5 static struct genl_family genl_ctrl = { 6 .id = GENL_ID_CTRL,//分配区间的最小值 7 .name = "nlctrl", 8 .version = 0x2, 9 .maxattr = CTRL_ATTR_MAX,//maxattr定义为支持的attr属性最大个数 10 .netnsok = true,//为true表示支持net命名空间 11 };
maxattr定义为支持的attr属性最大个数CTRL_ATTR_MAX,定义如下,这里为genetlink控制器定义了以CTRL_ATTR_UNSPEC为开头到最后的__CTRL_ATTR_MAX中的一共7个attr属性类型。
1 enum { 2 CTRL_ATTR_UNSPEC, 3 CTRL_ATTR_FAMILY_ID, 4 CTRL_ATTR_FAMILY_NAME, 5 CTRL_ATTR_VERSION, 6 CTRL_ATTR_HDRSIZE, 7 CTRL_ATTR_MAXATTR, 8 CTRL_ATTR_OPS, 9 CTRL_ATTR_MCAST_GROUPS, 10 __CTRL_ATTR_MAX, 11 }; 12 13 #define CTRL_ATTR_MAX(__CTRL_ATTR_MAX - 1)
详解1 genl_ctrl_ops:
这里为控制器类型的genetlink family只定义了一种cmd类型的内核操作接口,即CTRL_CMD_GETFAMILY,它用于应用空间从内核中获取指定family名称的ID号。因为该ID号在内核注册family时由内核进行分配,应用空间一般只知道需要通信的family name,但是要发起通信就必须知道该ID号,所以内核设计了控制器类型的family并定义了CTRL_CMD_GETFAMILY命令的处理接口用于应用程序查找ID号。
然后指明doit和dumpit回调函数为ctrl_getfamily和ctrl_dumpfamily。
1 static const struct genl_ops genl_ctrl_ops[] = { 2 { 3 .cmd = CTRL_CMD_GETFAMILY, 4 .doit = ctrl_getfamily, 5 .dumpit = ctrl_dumpfamily, 6 .policy = ctrl_policy, 7 }, 8 };
最后指定attr有效性策略为ctrl_policy:
1 static const struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] = { 2 [CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },//限定类型为16位无符号数 3 [CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING, 4 .len = GENL_NAMSIZ - 1 },//限定为空结尾的字符串类型并限定了长度 5 };
详解1genl_ctrl_groups注册的组播组
1 static struct genl_multicast_group genl_ctrl_groups[] = { 2 { .name = "notify", },//添加了name为”notify“的组播组 3 };
3.2 genl_register_family_with_ops_groups函数
1 #define genl_register_family_with_ops_groups(family, ops, grps) \ 2 _genl_register_family_with_ops_grps((family), \ 3 (ops), ARRAY_SIZE(ops), \ 4 (grps), ARRAY_SIZE(grps)) 5 6 static inline int 7 _genl_register_family_with_ops_grps(struct genl_family *family, 8 const struct genl_ops *ops, size_t n_ops, 9 const struct genl_multicast_group *mcgrps, 10 size_t n_mcgrps) 11 { //初始化了family的ops等字段 12 family->module = THIS_MODULE; 13 family->ops = ops; 14 family->n_ops = n_ops; 15 family->mcgrps = mcgrps; 16 family->n_mcgrps = n_mcgrps; 17 return __genl_register_family(family); 18 }
函数__genl_register_family
1 int __genl_register_family(struct genl_family *family) 2 { 3 int err = -EINVAL, i; 4 //对入参的ID号进行判断,一般来说,为了保证ID号的全局唯一性,程序中一般都设置为GENL_ID_GENERATE,由内核统一分配(当然这里注册控制器family除外了)。 5 if (family->id && family->id < GENL_MIN_ID) 6 goto errout; 7 8 if (family->id > GENL_MAX_ID) 9 goto errout; 10 11 err = genl_validate_ops(family);//----------- 详解1 12 if (err) 13 return err; 14 15 genl_lock_all(); 16 17 if (genl_family_find_byname(family->name)) { 18 err = -EEXIST; 19 goto errout_locked; 20 } 21 22 if (family->id == GENL_ID_GENERATE) {//判断传入的ID号是否为GENL_ID_GENERATE,若是则由内核分配一个空闲的ID号,否则得确保程序指定的ID号没有被使用过 23 u16 newid = genl_generate_id(); 24 25 if (!newid) { 26 err = -ENOMEM; 27 goto errout_locked; 28 } 29 30 family->id = newid; 31 } else if (genl_family_find_byid(family->id)) { 32 err = -EEXIST; 33 goto errout_locked; 34 } 35 36 if (family->maxattr && !family->parallel_ops) {// ------------- 详解2 37 family->attrbuf = kmalloc((family->maxattr+1) * 38 sizeof(struct nlattr *), GFP_KERNEL); 39 if (family->attrbuf == NULL) { 40 err = -ENOMEM; 41 goto errout_locked; 42 } 43 } else 44 family->attrbuf = NULL; 45 46 err = genl_validate_assign_mc_groups(family);//---------------- 详解3 47 if (err) 48 goto errout_free; 49 50 list_add_tail(&family->family_list, genl_family_chain(family->id));//将family注册到链表中 51 genl_unlock_all(); 52 53 /* send all events */ 54 genl_ctrl_event(CTRL_CMD_NEWFAMILY, family, NULL, 0);//---------- 详解4 55 for (i = 0; i < family->n_mcgrps; i++) 56 genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, family, 57 &family->mcgrps[i], family->mcgrp_offset + i); 58 59 return 0; 60 61 errout_free: 62 kfree(family->attrbuf); 63 errout_locked: 64 genl_unlock_all(); 65 errout: 66 return err; 67 }
详解1:调用genl_validate_ops对ops函数集做校验,对于每一个注册的genl_ops结构,其中doit和dumpit回调函数必须至少实现一个,然后其针对的cmd命令不可以出现重复,否则返回错误,注册失败。然后上锁开始启动链表操作,首先需要确保的是family name的全局唯一性,因此这里会查找是否有同名的簇已经注册了,若有就不能再注册了。
详解2:接着根据注册的最大attr参数maxattr,这里对于genl_ctrl来说一共分配了CTRL_ATTR_MAX+1个指针内存空间,以后用于缓存attr属性( 注意仅仅是保存属性的地址而非内容)。
详解3:调用genl_validate_assign_mc_groups()函数判断新增组播地址空间,该函数一共做了3件事:(1)判断注册family的group组播名的有效性;(2)为该family分配组播地址比特位并将bit偏移保存到family->mcgrp_offset变量中(由于generic netlink中不同类型的family簇共用NETLINK_GENERIC协议类型的group组播地址空间,因此内核特别维护了几个全局变量mc_groups_longs、mc_groups和mc_group_start用以维护组播地址的比特位,另外对于几种特殊的family是已经分配了的。无需再行分配,例如这里的crtl控制器);(3)更新全局nl_table对应的NETLINK_GENERIC协议类型netlink的groups标识。
详解4:调用genl_ctrl_event()函数向内核的控制器family发送CTRL_CMD_NEWFAMILY和CTRL_CMD_NEWMCAST_GRP命令消息,当然这里本身就是在创建ctrl控制器family,所以该函数不会做任何的事情。
3.3 函数register_pernet_subsys
为当前系统中的网络命名空间创建Generic Netlink套接字。
1 int register_pernet_subsys(struct pernet_operations *ops) 2 { 3 int error; 4 mutex_lock(&net_mutex); 5 error = register_pernet_operations(first_device, ops); 6 mutex_unlock(&net_mutex); 7 return error; 8 }
genl_pernet_ops的定义:
1 static struct pernet_operations genl_pernet_ops = { 2 .init = genl_pernet_init, 3 .exit = genl_pernet_exit, 4 }; 5 6 static int __net_init genl_pernet_init(struct net *net) 7 { 8 struct netlink_kernel_cfg cfg = { 9 .input = genl_rcv,//指定了消息处理函数为genl_rcv() 10 .flags = NL_CFG_F_NONROOT_RECV, 11 .bind = genl_bind,//套接字绑定函数 12 .unbind = genl_unbind,//解绑定函数 13 }; 14 15 /* we'll bump the group number right afterwards */ 16 net->genl_sock = netlink_kernel_create(net, NETLINK_GENERIC, &cfg);//完成内核套接字的注册 详解1 17 18 if (!net->genl_sock && net_eq(net, &init_net)) 19 panic("GENL: Cannot initialize generic netlink\n"); 20 21 if (!net->genl_sock) 22 return -ENOMEM; 23 24 return 0; 25 }
详解1:netlink_kernel_create()函数完成内核套接字的注册(netlink_kernel_create函数在前文中已分析过),并将生成的套接字赋值到网络命名空间net的genl_sock中,以后就可以通过net->genl_sock来找到genetlink内核套接字了。
4 generic netlink初始化过程总结
参考博文:https://blog.csdn.net/luckyapple1028/article/details/51172455