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
