Netlink 是一種IPC(Inter Process Commumicate)機制,它是一種用於內核與用戶空間通信的機制,同時它也以用於進程間通信(Netlink 更多用於內核通信,進程之間通信更多使用Unix域套接字)。在一般情況下,用戶態和內核態通信會使用三種傳統的通信方式:
1 ioctl; 2 sysfs屬性文件; 3 proc fs屬性文件;
但這3種通信方式都是同步通信方式,由用戶態主動發起向內核態的通信,內核無法主動發起通信。而Netlink是一種異步全雙工的通信方式,它支持由內核態主動發起通信,內核為Netlink通信提供了一組特殊的API接口,用戶態則基於socket API,內核發送的數據會保存在接收進程socket 的接收緩存中,由接收進程處理。Netlink 有以下優點:
1 雙向全雙工異步傳輸,支持由內核主動發起傳輸通信,而不需要用戶空間出發(例如使用ioctl這類的單工方式)。如此用戶空間在等待內核某種觸發條件滿足時就無需不斷輪詢,而異步接收內核消息即可。 2 支持組播傳輸,即內核態可以將消息發送給多個接收進程,這樣就不用每個進程單獨來查詢了。
目前已經有許多內核模塊使用netlink 機制,其中驅動模型中使用的uevent 就是基於netlink 實現。
netlink的架構框圖如下:

netlink source code path如下:
linux-4.9.73\net\netlink;
linux-4.9.73\net\wireless;
linux-4.9.73\net\core;
linux-4.9.73\net\socket.c
include/uapi/linux/netlink.h;
linux-4.9.73\include\net
目前 netlink 協議族支持32種協議類型,它們定義在 include/uapi/linux/netlink.h 中:
1 #define NETLINK_ROUTE 0 /* 用於設置和查詢路由表等網絡核心模塊*/ 2 #define NETLINK_UNUSED 1 /* Unused number */ 3 #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ 4 #define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */ 5 #define NETLINK_SOCK_DIAG 4 /* socket monitoring */ 6 #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ 7 #define NETLINK_XFRM 6 /* ipsec */ 8 #define NETLINK_SELINUX 7 /* SELinux event notifications */ 9 #define NETLINK_ISCSI 8 /* Open-iSCSI */ 10 #define NETLINK_AUDIT 9 /* auditing */ 11 #define NETLINK_FIB_LOOKUP 10 12 #define NETLINK_CONNECTOR 11 13 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ 14 #define NETLINK_IP6_FW 13 15 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ 16 #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace 用於uevent消息通信*/ 17 #define NETLINK_GENERIC 16 //generic netlink 18 /* leave room for NETLINK_DM (DM Events) */ 19 #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ 20 #define NETLINK_ECRYPTFS 19 21 #define NETLINK_RDMA 20 22 #define NETLINK_CRYPTO 21 /* Crypto layer */
對於在實際應用中,可能會有一些定制化的需求,以上這幾種專用的協議類型無法滿足,這時可以在不超過最大32種類型的基礎之上自行添加。但是一般情況下這樣做有些不妥,於是內核開發者就設計了一種通用netlink 協議類型(Generic Netlink)NETLINK_GENERIC,它就是一個Netlink復用器,便於用戶自行擴展子協議類型。
下面來分析Netlink的具體創建和通信流程。
1 內核netlink初始化
在系統啟動階段調用af_netlink.c的netlink_proto_init()函數完成內核Netlink的初始化。
code位於:linux-4.9.73\net\netlink\Af_netlink.c
1 static int __init netlink_proto_init(void) 2 { 3 int i; 4 int err = proto_register(&netlink_proto, 0);//向內核注冊netlink協議 5 6 if (err != 0) 7 goto out; 8 9 BUILD_BUG_ON(sizeof(struct netlink_skb_parms) > FIELD_SIZEOF(struct sk_buff, cb)); 10 11 nl_table = kcalloc(MAX_LINKS, sizeof(*nl_table), GFP_KERNEL);//創建並初始化了nl_table表數組 --------- 詳解1 12 if (!nl_table) 13 goto panic; 14 15 for (i = 0; i < MAX_LINKS; i++) { 16 if (rhashtable_init(&nl_table[i].hash, 17 &netlink_rhashtable_params) < 0) { 18 while (--i > 0) 19 rhashtable_destroy(&nl_table[i].hash); 20 kfree(nl_table); 21 goto panic; 22 } 23 } 24 25 INIT_LIST_HEAD(&netlink_tap_all); 26 27 netlink_add_usersock_entry();//初始化應用層使用的NETLINK_USERSOCK協議類型的netlink(用於應用層進程間通信) 28 29 sock_register(&netlink_family_ops);-------------- 詳解2 30 register_pernet_subsys(&netlink_net_ops);------------------ 詳解3 31 /* The netlink device handler may be needed early. */ 32 rtnetlink_init();----------- 詳解4 33 out: 34 return err; 35 panic: 36 panic("netlink_init: Cannot allocate nl_table\n"); 37 } 38 39 core_initcall(netlink_proto_init);//內核初始化時調用
詳解1:創建並初始化了nl_table表數組,這個表是整個netlink實現的最關鍵的一步,每種協議類型占數組中的一項,后續內核中創建的不同種協議類型的netlink都將保存在這個表中,由該表統一維護,其定義如下:
1 struct netlink_table { 2 struct rhashtable hash;//hash(哈希表)用來索引同種協議類型的不同netlink套接字實例 3 struct hlist_head mc_list;//為多播使用的sock散列表 4 struct listeners __rcu *listeners;//監聽者掩碼 5 unsigned int flags; 6 unsigned int groups;//協議支持的最大多播組數量 7 struct mutex *cb_mutex; 8 struct module *module; 9 /*定義了一些函數指針,它們會在內核首次創建netlink時被賦值,后續應用層創建和綁定socket時調用到*/ 10 int (*bind)(struct net *net, int group); 11 void (*unbind)(struct net *net, int group); 12 bool (*compare)(struct net *net, struct sock *sock); 13 int registered; 14 };
詳解2:調用sock_register向內核注冊協議處理函數,即將netlink的socket創建處理函數注冊到內核中,以后應用層創建netlink類型的socket時將會調用該協議處理函數。netlink_family_ops的定義如下:
1 static const struct net_proto_family netlink_family_ops = { 2 .family = PF_NETLINK, 3 .create = netlink_create, 4 .owner = THIS_MODULE, /* for consistency 8) */ 5 };
這樣應用層創建PF_NETLINK(AF_NETLINK)類型的socket()系統調用時將由netlink_create()函數負責處理。
詳解3:調用register_pernet_subsys向內核所有的網絡命名空間注冊”子系統“的初始化和去初始化函數,這里的"子系統”並非指的是netlink子系統,而是一種通用的處理方式,在網絡命名空間創建和注銷時會調用這里注冊的初始化和去初始化函數,當然對於已經存在的網絡命名空間,在注冊的過程中也會調用其初始化函數。netlink_net_ops定義如下:
1 static struct pernet_operations __net_initdata netlink_net_ops = { 2 .init = netlink_net_init, 3 .exit = netlink_net_exit, 4 };
其中netlink_net_init()會在文件系統中位每個網絡命名空間創建一個proc入口,而netlink_net_exit()不用時銷毀。
詳解4:調用rtnetlink_init()創建NETLINK_ROUTE協議類型的netlink,該種類型的netlink才是當初內核設計netlink的初衷,它用來傳遞網絡路由子系統、鄰居子系統、接口設置、防火牆等消息。至此整個netlink子系統初始化完成。
2 創建內核Netlink套接字
內核創建套接字從函數rtnetlink_init開始分析,定義位於:linux-4.9.73\net\core\rtnetlink.c
1 void __init rtnetlink_init(void) 2 { 3 if (register_pernet_subsys(&rtnetlink_net_ops)) 4 panic("rtnetlink_init: cannot initialize rtnetlink\n");//將rtnetlink的init函數和exit函數注冊到內核的每個網絡命名空間中 ------- 詳解1 5 6 register_netdevice_notifier(&rtnetlink_dev_notifier); 7 8 rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, 9 rtnl_dump_ifinfo, rtnl_calcit); 10 rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL); 11 rtnl_register(PF_UNSPEC, RTM_NEWLINK, rtnl_newlink, NULL, NULL); 12 rtnl_register(PF_UNSPEC, RTM_DELLINK, rtnl_dellink, NULL, NULL); 13 14 rtnl_register(PF_UNSPEC, RTM_GETADDR, NULL, rtnl_dump_all, NULL); 15 rtnl_register(PF_UNSPEC, RTM_GETROUTE, NULL, rtnl_dump_all, NULL); 16 17 rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, rtnl_fdb_add, NULL, NULL); 18 rtnl_register(PF_BRIDGE, RTM_DELNEIGH, rtnl_fdb_del, NULL, NULL); 19 rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, rtnl_fdb_dump, NULL); 20 21 rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL); 22 rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL); 23 rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL); 24 25 rtnl_register(PF_UNSPEC, RTM_GETSTATS, rtnl_stats_get, rtnl_stats_dump, 26 NULL); 27 }
詳解1:將rtnetlink的init函數和exit函數注冊到內核的每個網絡命名空間中,對於已經存在的網絡命名空間會調用其中個的init函數,即函數rtnetlink_net_init()。rtnetlink_net_ops定義如下;
1 static struct pernet_operations rtnetlink_net_ops = { 2 .init = rtnetlink_net_init, 3 .exit = rtnetlink_net_exit, 4 };
2.1 分析函數rtnetlink_net_init
1 static int __net_init rtnetlink_net_init(struct net *net) 2 { 3 struct sock *sk; 4 struct netlink_kernel_cfg cfg = {// ------- 詳解1 5 .groups = RTNLGRP_MAX, 6 .input = rtnetlink_rcv, 7 .cb_mutex = &rtnl_mutex, 8 .flags = NL_CFG_F_NONROOT_RECV, 9 }; 10 11 sk = netlink_kernel_create(net, NETLINK_ROUTE, &cfg);// ------- 詳解2 12 if (!sk) 13 return -ENOMEM; 14 net->rtnl = sk; 15 return 0; 16 }
詳解1:首先這里定義了一個 netlink_kernel_cfg結構體實例,該結構體定義位於include\linux\netlink.h如下:
1 /* optional Netlink kernel configuration parameters */ 2 struct netlink_kernel_cfg { 3 unsigned int groups; 4 unsigned int flags; 5 void (*input)(struct sk_buff *skb); 6 struct mutex *cb_mutex; 7 int (*bind)(struct net *net, int group); 8 void (*unbind)(struct net *net, int group); 9 bool (*compare)(struct net *net, struct sock *sk); 10 };
該結構包含了內核 netlink的可選參數。
groups:用於指定最大的多播組;
flags:可以為NL_CFG_F_NONROOT_RECV或NL_CFG_F_NONROOT_SEND,這兩個符號前者用來限定非超級用戶是否可以綁定到多播組,后者用來限定非超級用戶是否可以發送組播;
input指針:用於指定回調函數,該回調函數用於接收和處理來自用戶空間的消息(若無需接收來自用戶空間的消息可不指定);
最后的三個函數指針實現sock的綁定和解綁定等操作,會添加到nl_table對應的項中去。
rtnetlink_net_init函數中設置groups為RTNLGRP_MAX后指定消息接收處理函數為rtnetlink_rcv,並設置flag為NL_CFG_F_NONROOT_RECV,這表明非超級用戶可以綁定到多播組,但是沒有設置NL_CFG_F_NONROOT_SEND,這表明非超級用戶將不能發送組播消息。
詳解2:調用netlink_kernel_create()向當前的網絡命名空間創建NETLINK_ROUTE類型的套接字,並指定定義的那個配置結構cfg。netlink_kernel_create()函數定義如下:
1 static inline struct sock * 2 netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg) 3 { 4 return __netlink_kernel_create(net, unit, THIS_MODULE, cfg);//調用__netlink_kernel_create() 5 }
2.2 函數__netlink_kernel_create:
1 struct sock * 2 __netlink_kernel_create(struct net *net, int unit, struct module *module, 3 struct netlink_kernel_cfg *cfg) 4 { 5 struct socket *sock; 6 struct sock *sk; 7 struct netlink_sock *nlk;------- 詳解7 8 struct listeners *listeners = NULL; 9 struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL; 10 unsigned int groups; 11 12 BUG_ON(!nl_table); 13 14 if (unit < 0 || unit >= MAX_LINKS)//簡單的參數判斷 15 return NULL; 16 17 if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock))//創建了一個以PF_NETLINK為地址族的 SOCK_DGRAM類型的 socket 套接字,其協議類型就是作為參數傳入的NETLINK_ROUTE 18 return NULL; 19 20 if (__netlink_create(net, sock, cb_mutex, unit, 1) < 0)//向內核初始化netlink套接字 --------- 詳解1 21 goto out_sock_release_nosk; 22 23 sk = sock->sk; 24 25 if (!cfg || cfg->groups < 32)---------------- 詳解2 26 groups = 32; 27 else 28 groups = cfg->groups; 29 30 listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);//分配listeners內存空間,這里邊保存了監聽者(監聽套接字)的信息 31 if (!listeners) 32 goto out_sock_release; 33 34 sk->sk_data_ready = netlink_data_ready; 35 if (cfg && cfg->input) 36 nlk_sk(sk)->netlink_rcv = cfg->input;------- 詳解3 37 38 if (netlink_insert(sk, 0)) ------------ 詳解4 39 goto out_sock_release; 40 41 nlk = nlk_sk(sk); 42 nlk->flags |= NETLINK_F_KERNEL_SOCKET;//設置標識NETLINK_KERNEL_SOCKET表明這個netlink套接字是一個內核套接字 43 44 netlink_table_grab(); -------- 詳解5 45 if (!nl_table[unit].registered) { 46 nl_table[unit].groups = groups; 47 rcu_assign_pointer(nl_table[unit].listeners, listeners); 48 nl_table[unit].cb_mutex = cb_mutex; 49 nl_table[unit].module = module; 50 if (cfg) { 51 nl_table[unit].bind = cfg->bind; 52 nl_table[unit].unbind = cfg->unbind; 53 nl_table[unit].flags = cfg->flags; 54 if (cfg->compare) 55 nl_table[unit].compare = cfg->compare; 56 } 57 nl_table[unit].registered = 1; 58 } else { 59 kfree(listeners); 60 nl_table[unit].registered++; 61 } 62 netlink_table_ungrab(); 63 return sk; ------- 詳解6 64 65 out_sock_release: 66 kfree(listeners); 67 netlink_kernel_release(sk); 68 return NULL; 69 70 out_sock_release_nosk: 71 sock_release(sock); 72 return NULL; 73 }
詳解1:調用 __netlink_create()函數向內核初始化netlink套接字 ,其實在用戶態創建netlink套接字也是間接調用到該函數。
函數__netlink_create:
1 static int __netlink_create(struct net *net, struct socket *sock, 2 struct mutex *cb_mutex, int protocol, 3 int kern) 4 { 5 struct sock *sk; 6 struct netlink_sock *nlk; 7 8 sock->ops = &netlink_ops;//將sock的操作函數集指針設置為netlink_ops 9 10 sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern);//分配sock結構並進行初始化 11 if (!sk) 12 return -ENOMEM; 13 14 sock_init_data(sock, sk);//初始化發送接收消息隊列 15 16 nlk = nlk_sk(sk);//初始化數據緩存 17 if (cb_mutex) { 18 nlk->cb_mutex = cb_mutex; 19 } else { 20 nlk->cb_mutex = &nlk->cb_def_mutex; 21 mutex_init(nlk->cb_mutex);//初始化互斥鎖 22 lockdep_set_class_and_name(nlk->cb_mutex, 23 nlk_cb_mutex_keys + protocol, 24 nlk_cb_mutex_key_strings[protocol]); 25 } 26 init_waitqueue_head(&nlk->wait);//初始化等待隊列 27 28 sk->sk_destruct = netlink_sock_destruct;//設置sk_destruct回調函數 29 sk->sk_protocol = protocol;//設置和協議類型 30 return 0; 31 }
netlink_ops賦值如下:
1 static const struct proto_ops netlink_ops = { 2 .family = PF_NETLINK, 3 .owner = THIS_MODULE, 4 .release = netlink_release, 5 .bind = netlink_bind, 6 .connect = netlink_connect, 7 .socketpair = sock_no_socketpair, 8 .accept = sock_no_accept, 9 .getname = netlink_getname, 10 .poll = datagram_poll, 11 .ioctl = netlink_ioctl, 12 .listen = sock_no_listen, 13 .shutdown = sock_no_shutdown, 14 .setsockopt = netlink_setsockopt, 15 .getsockopt = netlink_getsockopt, 16 .sendmsg = netlink_sendmsg, 17 .recvmsg = netlink_recvmsg, 18 .mmap = sock_no_mmap, 19 .sendpage = sock_no_sendpage, 20 };
詳解2:接下來校驗groups,默認最小支持32個組播地址(因為后文會看到用戶層在綁定地址時最多綁定32個組播地址),但內核也有可能支持大於32個組播地址的情況(Genetlink就屬於這種情況)
詳解3:初始化函數指針,這里將前文中定義的rtnetlink_rcv注冊到了nlk_sk(sk)->netlink_rcv中,這樣就設置完了內核態的消息處理函數
詳解4:然后調用netlink_insert()函數將本次創建的這個套接字添加到nl_table中去(其核心是調用__netlink_insert()),注冊的套接字是通過nl_table中的哈希表來管理的。
詳解5:接下來繼續初始化nl_table表中對應傳入 NETLINK_ROUTE協議類型的數組項,首先會判斷是否已經先有同樣協議類型的已經注冊過了,如果有就不再初始化該表項了,直接釋放剛才申請的listeners內存空間然后遞增注冊個數並返回。這里假定是首次注冊NETLINK_ROUTE協議類型的套接字,這里依次初始化了nl_table表項中的groups、listeners、cb_mutex、module、bind、unbind、flags和compare字段。通過前文中cfg的實例分析,這里的初始化的值分別如下:
1 nl_table[NETLINK_ROUTE].groups = RTNLGRP_MAX; 2 nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex; 3 nl_table[ NETLINK_ROUTE ].module = THIS_MODULE; 4 nl_table[NETLINK_ROUTE].bind = NULL; 5 nl_table[NETLINK_ROUTE].unbind = NULL; 6 nl_table[NETLINK_ROUTE].compare = NULL; 7 nl_table[NETLINK_ROUTE].flags= NL_CFG_F_NONROOT_RECV;
這些寫值在后面的通信流程中就會使用到。
詳解6:在函數的最后返回成功創建的netlink套接字中的sock指針,它會在最先前的rtnetlink_net_init()函數中被保存到net->rtnl中去,注意只有NETLINK_ROUTE協議類型的套接字才會執行這個步驟,因為網絡命名空間中專門為其預留了一個sock指針。
至此,內核netlink套接字創建完成。
詳解7:netlink套接字結構:netlink_sock
1 struct netlink_sock { 2 /* struct sock has to be the first member of netlink_sock */ 3 struct sock sk; 4 u32 portid;//表示本套接字自己綁定的id號,對於內核來說它就是0 5 u32 dst_portid;//表示目的id號 6 u32 dst_group; 7 u32 flags; 8 u32 subscriptions; 9 u32 ngroups;//表示協議支持多播組數量 10 unsigned long *groups;//保存組位掩碼 11 ... 12 void (*netlink_rcv)(struct sk_buff *skb);//保存接收到用戶態數據后的處理函數 13 int (*netlink_bind)(struct net *net, int group);//用於協議子協議自身特有的綁定 14 void (*netlink_unbind)(struct net *net, int group);//用於協議子協議自身特有的解綁 15 ... 16 }
3 應用層netlink套接字創建
應用層通過標准的sock API即可使用Netlink完成通信功能,如socket()、sendto()、recv()、sendmsg()和recvmsg()等,這些都是系統調用,內核中對應的定義為sys_xxx。
3.1 創建Netlink套接字
應用層調用socket()函數系統調用創建套接字,socket系統調用的第一個參數可以是AF_NETLINK或PF_NETLINK,在Linux系統中它倆實際為同一種宏;第二個參數可以是SOCK_RAW或SOCK_DGRAM,原始套接字或無連接的數據報套接字)。最后一個參為netlink.h中定義的協議類型,用戶可以按需求自行創建上述不同種類的套接字。
例如調用 socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 即創建了一個NETLINK_ROUTE類型的Netlink套接字。接着來具體分析查看內核是如何為用戶層創建這個套接字。
定義位於:linux-4.9.73\net\socket.c
函數socket首先做了一些參數檢查之后就調用sock_create()函數創建套接字,在創建完成后向內核申請描述符並返回該描述符
1 SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 2 { 3 int retval; 4 struct socket *sock; 5 int flags; 6 7 /* Check the SOCK_* constants for consistency. */ 8 BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); 9 BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); 10 BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); 11 BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); 12 13 flags = type & ~SOCK_TYPE_MASK; 14 if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) 15 return -EINVAL; 16 type &= SOCK_TYPE_MASK; 17 18 if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) 19 flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; 20 21 retval = sock_create(family, type, protocol, &sock); 22 if (retval < 0) 23 goto out; 24 25 retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); 26 if (retval < 0) 27 goto out_release; 28 29 out: 30 /* It may be already another descriptor 8) Not kernel problem. */ 31 return retval; 32 33 out_release: 34 sock_release(sock); 35 return retval; 36 }
函數sock_create,定義位於:?
1 int sock_create(int family, int type, int protocol, struct socket **res) 2 { 3 return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0); 4 }
函數__sock_create,定義位於:?
1 int __sock_create(struct net *net, int family, int type, int protocol, 2 struct socket **res, int kern) 3 { 4 int err; 5 struct socket *sock; 6 const struct net_proto_family *pf; 7 //參數判斷 8 /* 9 * Check protocol is in range 10 */ 11 if (family < 0 || family >= NPROTO) 12 return -EAFNOSUPPORT; 13 if (type < 0 || type >= SOCK_MAX) 14 return -EINVAL; 15 16 /* Compatibility. 17 This uglymoron is moved from INET layer to here to avoid 18 deadlock in module load. 19 */ 20 if (family == PF_INET && type == SOCK_PACKET) { 21 static int warned; 22 if (!warned) { 23 warned = 1; 24 pr_info("%s uses obsolete (PF_INET,SOCK_PACKET)\n", 25 current->comm); 26 } 27 family = PF_PACKET; 28 } 29 err = security_socket_create(family, type, protocol, kern);---------- 詳解1 30 if (err) 31 return err; 32 33 /* 34 * Allocate the socket and allow the family to set things up. if 35 * the protocol is 0, the family is instructed to select an appropriate 36 * default. 37 */ 38 sock = sock_alloc();//分配socket實例,它會為其創建和初始化索引節點 39 if (!sock) { 40 net_warn_ratelimited("socket: no more sockets\n"); 41 return -ENFILE; /* Not exactly a match, but its the 42 closest posix thing */ 43 } 44 45 sock->type = type;//將sock->type賦值為傳入的SOCK_RAW 46 #ifdef CONFIG_MODULES 47 /* Attempt to load a protocol module if the find failed. 48 * 49 * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user 50 * requested real, full-featured networking support upon configuration. 51 * Otherwise module support will break! 52 */ 53 if (rcu_access_pointer(net_families[family]) == NULL)-------- 詳解2 54 request_module("net-pf-%d", family); 55 #endif 56 57 rcu_read_lock(); 58 pf = rcu_dereference(net_families[family]); 59 err = -EAFNOSUPPORT; 60 if (!pf) 61 goto out_release; 62 /* 63 * We will call the ->create function, that possibly is in a loadable 64 * module, so we have to bump that loadable module refcnt first. 65 */ 66 if (!try_module_get(pf->owner))//獲取模塊的引用計數 67 goto out_release; 68 69 /* Now protected by module ref count */ 70 rcu_read_unlock(); 71 72 err = pf->create(net, sock, protocol, kern);--------- 詳解3 73 if (err < 0) 74 goto out_module_put; 75 76 /* 77 * Now to bump the refcnt of the [loadable] module that owns this 78 * socket at sock_release time we decrement its refcnt. 79 */ 80 if (!try_module_get(sock->ops->owner)) 81 goto out_module_busy; 82 83 /* 84 * Now that we're done with the ->create function, the [loadable] 85 * module can have its refcnt decremented 86 */ 87 module_put(pf->owner); 88 err = security_socket_post_create(sock, family, type, protocol, kern); 89 if (err) 90 goto out_sock_release; 91 *res = sock; 92 93 return 0; 94 }
詳解1:對創建socket執行安全性檢查,security_socket_create這個函數在內核沒有啟用CONFIG_SECURITY_NETWORK配置時是一個空函數直接返回0,這里可先不考慮。
詳解2:在啟用內核模塊的情況下,這里會到內核net_families數組中查找該family(AF_NETLINK)是否已經注冊,如果沒有注冊就會嘗試加載網絡子系統模塊。其實在內核的netlink初始化函數中已經調用sock_register()完成注冊了(見上文第一節詳解2)。接下來從net_families數組中獲取已經注冊的struct net_proto_family結構實例,這里就是上面描述過的netlink_family_ops。
詳解3:調用netlink協議的creat()鈎子函數執行進一步的創建和初始化操作(即netlink_family_ops中定義的netlink_create()了,見上文第一節詳解2),完成之后就釋放鎖同時釋放當前模塊的引用計數並返回創建成功的socket。
函數netlink_create,定義位於:linux-4.9.73\net\netlink\af_netlink.c
1 static int netlink_create(struct net *net, struct socket *sock, int protocol, 2 int kern) 3 { 4 struct module *module = NULL; 5 struct mutex *cb_mutex; 6 struct netlink_sock *nlk; 7 int (*bind)(struct net *net, int group); 8 void (*unbind)(struct net *net, int group); 9 int err = 0; 10 11 sock->state = SS_UNCONNECTED;//將socket的狀態標記為未連接 12 13 if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)//判斷套接字的類型是否是SOCK_RAW或SOCK_DGRAM類型的,若不是就不能繼續創建 14 return -ESOCKTNOSUPPORT; 15 16 if (protocol < 0 || protocol >= MAX_LINKS)-------- 詳解1 17 return -EPROTONOSUPPORT; 18 19 netlink_lock_table(); 20 #ifdef CONFIG_MODULES 21 if (!nl_table[protocol].registered) { 22 netlink_unlock_table(); 23 request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol); 24 netlink_lock_table(); 25 } 26 #endif 27 if (nl_table[protocol].registered && 28 try_module_get(nl_table[protocol].module)) 29 module = nl_table[protocol].module; 30 else 31 err = -EPROTONOSUPPORT; 32 cb_mutex = nl_table[protocol].cb_mutex; 33 bind = nl_table[protocol].bind; 34 unbind = nl_table[protocol].unbind; 35 netlink_unlock_table(); 36 37 if (err < 0) 38 goto out; 39 40 err = __netlink_create(net, sock, cb_mutex, protocol, kern);------- 詳解2 41 if (err < 0) 42 goto out_module; 43 44 local_bh_disable(); 45 sock_prot_inuse_add(net, &netlink_proto, 1);//添加協議的引用計數 46 local_bh_enable(); 47 //賦值 48 nlk = nlk_sk(sock->sk); 49 nlk->module = module; 50 nlk->netlink_bind = bind; 51 nlk->netlink_unbind = unbind; 52 out: 53 return err; 54 55 out_module: 56 module_put(module); 57 goto out; 58 }
詳解1:接着判斷該協議類型的netlink是否已經注冊了,由於前文中內核在初始化netlink子系統時已經初始化了NETLINK_ROUTE內核套接字並向nl_table注冊,所以這里的幾個賦值結果如下::
1 cb_mutex = nl_table[NETLINK_ROUTE].cb_mutex = &rtnl_mutex; 2 module = nl_table[NETLINK_ROUTE].module = THIS_MODULE; 3 bind = nl_table[NETLINK_ROUTE].bind = NULL; 4 unbind = nl_table[NETLINK_ROUTE].unbind = NULL;
詳解2:調用__netlink_create()完成核心的創建初始化,詳解見上文2.1節詳解2
3.2 綁定套接字系統調用
在創建完成套接字后需要調用bind()函數進行綁定,將該套接字綁定到一個特定的地址或者加入一個多播組中,以后內核或其他應用層套接字向該地址單播或向該多播組發送組播消息時即可通過recv()或recvmsg()函數接收消息了。綁定地址時需要使用到sockaddr_nl地址結構,如果使用使用單播則需要將地址本地地址信息填入nl_pid變量並設置nl_groups為0,如果使用多播則將nl_pid設置為0並填充nl_groups為多播地址,如下可將當前進程的PID號作為單播地址進行綁定:
bind(fd, (struct sockaddr *) &local, sizeof(local));
其中bind()的第一個參數:為剛創建的Netlink套接字描述符;
第二個參數:需要綁定的套接字地址;
最后一個參數:地址的長度。這個綁定操作同創建TCP套接字類似,需要制定綁定的端口。
套接字的綁定是有系統調用bind()實現:
1 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen) 2 { 3 struct socket *sock; 4 struct sockaddr_storage address; 5 int err, fput_needed; 6 7 sock = sockfd_lookup_light(fd, &err, &fput_needed);//根據用戶傳入的fd文件描述符向內核查找對應的socket結構 8 if (sock) { 9 err = move_addr_to_kernel(umyaddr, addrlen, &address);//將用戶空間傳入的地址struct sockaddr拷貝到內核中(會使用到copy_from_user()) 10 if (err >= 0) { 11 err = security_socket_bind(sock, 12 (struct sockaddr *)&address, 13 addrlen);//空函數,跳過安全檢查 14 if (!err) 15 err = sock->ops->bind(sock, 16 (struct sockaddr *) 17 &address, addrlen);//------ 詳解1 18 } 19 fput_light(sock->file, fput_needed); 20 } 21 return err; 22 }
詳解1:然后調用sock->ops->bind()。在創建套接字時調用的__netlink_create()函數中已經將sock->ops賦值為netlink_ops了,如上2.2節詳解1,節netlink_bind函數。
3.3 函數netlink_bind
1 static int netlink_bind(struct socket *sock, struct sockaddr *addr, 2 int addr_len) 3 { 4 struct sock *sk = sock->sk; 5 struct net *net = sock_net(sk); 6 struct netlink_sock *nlk = nlk_sk(sk); 7 struct sockaddr_nl *nladdr = (struct sockaddr_nl *)addr;//將用戶傳入的地址類型強制轉換成了sockaddr_nl類型的地址結構 8 int err; 9 long unsigned int groups = nladdr->nl_groups; 10 bool bound; 11 12 if (addr_len < sizeof(struct sockaddr_nl)) 13 return -EINVAL; 14 15 if (nladdr->nl_family != AF_NETLINK) 16 return -EINVAL; 17 18 /* Only superuser is allowed to listen multicasts */ 19 if (groups) {//如果用戶設定了需要綁定的多播地址 -------- 詳解1 20 if (!netlink_allowed(sock, NL_CFG_F_NONROOT_RECV)) 21 return -EPERM; 22 err = netlink_realloc_groups(sk);//-------- 詳解2 23 if (err) 24 return err; 25 } 26 27 bound = nlk->bound; 28 if (bound) {//如果已綁定 29 /* Ensure nlk->portid is up-to-date. */ 30 smp_rmb(); 31 32 if (nladdr->nl_pid != nlk->portid)//檢查新需要綁定的id號是否等於已經綁定的id號 33 return -EINVAL;//若不相等則返回失敗 34 } 35 36 if (nlk->netlink_bind && groups) { ----- 詳解3 37 int group; 38 39 for (group = 0; group < nlk->ngroups; group++) { 40 if (!test_bit(group, &groups)) 41 continue; 42 err = nlk->netlink_bind(net, group + 1); 43 if (!err) 44 continue; 45 netlink_undo_bind(group, groups, sk); 46 return err; 47 } 48 } 49 50 /* No need for barriers here as we return to user-space without 51 * using any of the bound attributes. 52 */ 53 if (!bound) {-------- 詳解4 54 err = nladdr->nl_pid ? 55 netlink_insert(sk, nladdr->nl_pid) : 56 netlink_autobind(sock);------ 詳解5 57 if (err) { 58 netlink_undo_bind(nlk->ngroups, groups, sk); 59 return err; 60 } 61 } 62 63 if (!groups && (nlk->groups == NULL || !(u32)nlk->groups[0]))--------- 詳解6 64 return 0; 65 66 netlink_table_grab(); 67 netlink_update_subscriptions(sk, nlk->subscriptions + 68 hweight32(groups) - 69 hweight32(nlk->groups[0])); 70 nlk->groups[0] = (nlk->groups[0] & ~0xffffffffUL) | groups; 71 netlink_update_listeners(sk); 72 netlink_table_ungrab(); 73 74 return 0; 75 }
詳解1:如果用戶設定了需要綁定的多播地址,這里會去檢擦nl_table中注冊的套接字是否已經設置了NL_CFG_F_NONROOT_RECV標識,如果沒有設置將拒絕用戶綁定到組播組,顯然在前文中已經看到了NETLINK_ROUTE類型的套接字是設置了這個標識的,所以這里會調用netlink_realloc_groups分配組播空間。
詳解2:函數netlink_realloc_groups:
這里會比較驗證一下當前套接字中指定的組播地址上限是否大於NETLINK_ROUTE套接字支持的最大地址(這里為RTNLGRP_MAX),由於這個套接字是前面剛剛創建的,所以nlk->ngroups = 0。
然后為其分配內存空間,分配的空間大小為NLGRPSZ(groups)(這是一個取整對齊的宏),分配完成后將新分配的空間清零,首地址保存在nlk->groups中,最后更新nlk->ngroups變量。
1 static int netlink_realloc_groups(struct sock *sk) 2 { 3 struct netlink_sock *nlk = nlk_sk(sk); 4 unsigned int groups; 5 unsigned long *new_groups; 6 int err = 0; 7 8 netlink_table_grab(); 9 10 groups = nl_table[sk->sk_protocol].groups; 11 if (!nl_table[sk->sk_protocol].registered) { 12 err = -ENOENT; 13 goto out_unlock; 14 } 15 16 if (nlk->ngroups >= groups) 17 goto out_unlock; 18 19 new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); 20 if (new_groups == NULL) { 21 err = -ENOMEM; 22 goto out_unlock; 23 } 24 memset((char *)new_groups + NLGRPSZ(nlk->ngroups), 0, 25 NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); 26 27 nlk->groups = new_groups; 28 nlk->ngroups = groups; 29 out_unlock: 30 netlink_table_ungrab(); 31 return err; 32 }
詳解3:如果netlink套接字子協議存在特有的bind函數且用戶指定了需要綁定的組播地址,則調用之為其綁定到特定的組播組中去。現由於NETLINK_ROUTE套接字並不存在nlk->netlink_bind()函數實現,所以這里並不會調用。
詳解4:如果本套接字並沒有被綁定過(目前就是這種情況),這里會根據用戶是否指定了單播的綁定地址來調用不同的函數。首先假定用戶空間指定了單播的綁定地址,這里會調用netlink_insert()函數將這個套接字插入到nl_table[NETLINK_ROUTE]數組項的哈希表中去,同時設置nlk_sk(sk)->bound = nlk_sk(sk)->portid = nladdr->nl_pid。我們再假定用戶空間沒有設置單播的綁定地址,這里會調用netlink_autobind()動態的綁定一個地址。
詳解5:函數netlink_autobind()動態的綁定一個地址:
1 static int netlink_autobind(struct socket *sock) 2 { 3 struct sock *sk = sock->sk; 4 struct net *net = sock_net(sk); 5 struct netlink_table *table = &nl_table[sk->sk_protocol]; 6 s32 portid = task_tgid_vnr(current); 7 int err; 8 s32 rover = -4096; 9 bool ok; 10 11 retry: 12 /*先嘗試選用當前的進程ID作為端口地址,如果當前進程ID已經綁定過其他的相同protocol套接字則會選用一個負數作為ID號(查找直到存在可用的)*/ 13 cond_resched(); 14 rcu_read_lock(); 15 ok = !__netlink_lookup(table, portid, net); 16 rcu_read_unlock(); 17 if (!ok) { 18 /* Bind collision, search negative portid values. */ 19 if (rover == -4096) 20 /* rover will be in range [S32_MIN, -4097] */ 21 rover = S32_MIN + prandom_u32_max(-4096 - S32_MIN); 22 else if (rover >= -4096) 23 rover = -4097; 24 portid = rover--; 25 goto retry; 26 } 27 28 err = netlink_insert(sk, portid); 29 if (err == -EADDRINUSE) 30 goto retry; 31 32 /* If 2 threads race to autobind, that is fine. */ 33 if (err == -EBUSY) 34 err = 0; 35 36 return err; 37 }
詳解6:如果沒有指定組播地址且沒有分配組播的內存,綁定工作到這里就已經結束了,可以直接返回了。現假定用戶指定了需要綁定的組播地址,這里首先調用netlink_update_subscriptions綁定sk->sk_bind_node到nl_table[sk->sk_protocol].mc_list中,同時將加入的組播組數目記錄到nlk->subscriptions中,並將組播地址保存到nlk->groups[0]中,最后更新netlink監聽位掩碼。至此綁定操作結束。
