netlink socket是一種用於用戶態進程和內核態進程之間的通訊機制。它經過爲內核模塊提供一組特殊的API,併爲用戶程序提供了一組標準的socket接口的方式,實現了全雙工的通信鏈接。segmentfault
特色:架構
netlink僅支持32種協議類型,這在實際應用中可能並不足夠,所以產生了generic netlink(如下簡稱爲genl),
generic netlink支持1023個子協議號,彌補了netlink協議類型較少的缺陷。異步
Netlink子系統:全部genl通訊的基礎,Netlink子系統中收到的全部Generic類型的netlink數據都被送到genl總線上;從內核發出的數據也經由genl總線送至netlink子系統,再打包送至用戶空間 socket
Generic Netlink控制器:做爲內核的一部分,負責動態地分配genl通道(即genl family id),並管理genl任務,genl控制器是一個特殊的genl內核用戶,它負責監聽genl bus上的通訊通道函數
genl通訊創建在一系列的通訊通道的基礎上,每一個genl family對應多個通道,這些通道由genl控制器動態分配ui
genl family
Generic Netlink是基於客戶端-服務端模型的通訊機制,服務端註冊family(family是對genl服務的各項定義的集合),控制器和客戶端都經過已註冊的信息與服務端通訊。spa
//genl_family主要字段 struct genl_family { unsigned int id; //family id unsigned int hdrsize; //用戶自定議頭部長度 char name[GENL_NAMSIZ]; //family名,要求不一樣的family使用不一樣的名字 unsigned int version; //版本 unsigned int maxattr; //最大attr類型數,使用netlink標準的attr來傳輸數據 genl_ops *ops; // 操做集合 };
genl_ops
定義了netlink family相關的操做.net
// genl_ops主要字段 struct genl_ops { u8 cmd; //命令名,用於識別genl_ops unsigned int flags; //設置屬性 struct nla_policy *policy; //定義了attr規則,genl在觸發事件處理程序以前,會用其進行attr校驗 int (*doit)(struct sk_buff *skb, struct genl_info *info); int (*dumpit)(struct sk_buff *skb, struct netlink_callback *cb); };
dumpit與doit的區別是:dumpit的第一個參數skb不會攜帶從客戶端發來的數據。相反地,開發者應該在skb中填入須要傳給客戶端的數據,skb中攜帶的數據會被自動送到客戶端。只要dumpit的返回值大於0,dumpit函數就會再次被調用,並被要求在skb中填入數據。當服務端沒有數據要傳給客戶端時,dumpit要返回0。若是函數中出錯,要求返回一個負值。指針
nal_policy
定義了attr規則code
struct nla_policy { u16 type; //attr中的數據類型 u16 len; //若是在type字段配置的是字符串有關的值,要把len設置爲字符串的最大長度 };
genl_info
內核在接收到用戶的genetlink消息後,會對消息解析並封裝成genl_info結構
struct genl_info { u32 snd_seq; //發送序號 u32 snd_pid; //發送客戶端的PID struct nlmsghdr * nlhdr; //netlink header的指針 struct genlmsghdr * genlhdr; //genl頭部的指針(即family頭部) void * userhdr; //用戶自定義頭部指針 struct nlattr ** attrs; //若是定義了genl_ops->policy,保存被policy過濾之後的結果 };
這裏以OVS中packet的處理爲例:
1. 定義family
//定義packet family static struct genl_family dp_packet_genl_family __ro_after_init = { .hdrsize = sizeof(struct ovs_header), .name = OVS_PACKET_FAMILY, .version = OVS_PACKET_VERSION, .maxattr = OVS_PACKET_ATTR_MAX, .netnsok = true, .parallel_ops = true, .ops = dp_packet_genl_ops, //操做集合 .n_ops = ARRAY_SIZE(dp_packet_genl_ops), .module = THIS_MODULE, };
2. 定義operation
// 定義packet family 的操做 --- packet類型的操做只支持OVS_PACKET_CMD_EXECUTE static struct genl_ops dp_packet_genl_ops[] = { { .cmd = OVS_PACKET_CMD_EXECUTE, .flags = GENL_UNS_ADMIN_PERM, .policy = packet_policy, .doit = ovs_packet_cmd_execute //接受數據包時,調用ovs_packet_cmd_execute進行處理 } }; // 定義packet family 的過濾規則 static const struct nla_policy packet_policy[OVS_PACKET_ATTR_MAX + 1] = { [OVS_PACKET_ATTR_PACKET] = { .len = ETH_HLEN }, [OVS_PACKET_ATTR_KEY] = { .type = NLA_NESTED }, [OVS_PACKET_ATTR_ACTIONS] = { .type = NLA_NESTED }, [OVS_PACKET_ATTR_PROBE] = { .type = NLA_FLAG }, [OVS_PACKET_ATTR_MRU] = { .type = NLA_U16 }, };
3. 註冊family
genl_register_family(&dp_packet_genl_family);
struct sockaddr_nl saddr; int sock; sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); //建立一個netlink類型的socket if (sock < 0) { return -1; } memset(&saddr, 0, sizeof(saddr)); saddr.nl_family = AF_NETLINK; saddr.nl_pid = getpid(); //獲取family id if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) { //綁定 printf("bind fail!\n"); close(*p_sock); return -1; }
接受數據:內核端一旦收到generic netlink數據,會觸發doit函數運行,經過回調函數進行處理
發送數據:將數據打包好以後,可經過單播(genlmsg_unicast)或多播()的形式進行發送
接受數據:調用recv函數便可完成從內核來的數據的接收
發送數據:調用sendto來發送數據
做者: yearsj
轉載請註明出處: https://segmentfault.com/a/11...