netlink基础结构
結構圖
netlink特點
netlink socket創建流程
代碼流程
關鍵函數
關鍵變量
相關函數
netlink bind綁定流程
代碼流程
相關函數/宏定義
connect調用
代碼流程
sendto & sendmsg
關鍵函數
wireshark 抓取netlink報文
netlink_recvmsg
關鍵函數
其他資料
/proc中netlink信息
netlink庫libnl
netlink選項
netlink protocoltoc
結構圖
netlink特點
異步收發
kernel可以主動發送
支持多播
netlink socket創建流程
代碼流程
netlink socket可以在內核創建也可以在用戶空間創建,內核通過netlink_kernel_create來創建,用戶空間通過socket系統調用創建,這里重點關注socket系統調用實現
int socket(int domain, int type, int protocol);
SYSCALL_DEFINE3->__sys_socket->sock_create->__sock_create->netlink_create->__netlink_create
關鍵函數
__sock_create:分配socket內存。如果net_families中沒有注冊對應的family沒有注冊,則嘗試加載對應的模塊request_module("net-pf-%d", family)。接著通過pf->create(net, sock, protocol, kern)來實際調用inet_create進一步創建socket。
inet_create:從inetsw中查找type相符合的協議,如果找不到則會嘗試按照如下代碼加載協議模塊。對于raw場景,最終將inet_sockraw_ops和raw_prot初始化到socket和sock中。
request_module("net-pf-%d-proto-%d-type-%d", PF_INET, protocol, sock->type);
request_module("net-pf-%d-proto-%d", PF_INET, protocol);
inet->inet_num = protocol;
inet->inet_sport = htons(inet->inet_num)
err = sk->sk_prot->hash(sk); //raw_hash_sk 其中會添加/proc/net/sockstat統計
err = sk->sk_prot->init(sk); //raw_sk_init
PF_INET類型的socket創建時會走入inet_create,netlink socket的創建不會進入inet_create分支,而是進入netlink_create。此處順便用于說明PF_INET類型的socket的創建流程。
netlink_create與__netlink_create
主要功能是校驗參數,初始化nlk。簡化代碼如下:
if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
return -ESOCKTNOSUPPORT;
if (protocol < 0 || protocol >= MAX_LINKS)
return -EPROTONOSUPPORT;
#ifdef CONFIG_MODULES
if (!nl_table[protocol].registered) {
request_module("net-pf-%d-proto-%d", PF_NETLINK, protocol);
}
#endif
if (nl_table[protocol].registered && try_module_get(nl_table[protocol].module))
module = nl_table[protocol].module;
else
err = -EPROTONOSUPPORT;
cb_mutex = nl_table[protocol].cb_mutex;
bind = nl_table[protocol].bind;
unbind = nl_table[protocol].unbind;
err = __netlink_create(net, sock, cb_mutex, protocol, kern);
nlk = nlk_sk(sock->sk);
nlk->module = module;
nlk->netlink_bind = bind;
nlk->netlink_unbind = unbind;
__netlink_create簡化邏輯如下:
sock->ops = &netlink_ops;
sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto, kern); // 設置sk->sk_net = net
if (!sk)
return -ENOMEM;
sock_init_data(sock, sk); //設置sock->sk = sk;
nlk = nlk_sk(sk);
if (cb_mutex) {
nlk->cb_mutex = cb_mutex;
} else {
nlk->cb_mutex = &nlk->cb_def_mutex;
mutex_init(nlk->cb_mutex);
lockdep_set_class_and_name(nlk->cb_mutex,
nlk_cb_mutex_keys + protocol,
nlk_cb_mutex_key_strings[protocol]);
}
init_waitqueue_head(&nlk->wait);
sk->sk_destruct = netlink_sock_destruct;
sk->sk_protocol = protocol;
關鍵變量
net_families
通過sock_register函數注冊相關的協議,例如在netlink_proto_init函數中注冊netlink_family_ops,其他如inet_family_ops、inet6_family_ops、unix_family_ops也在inet_init類似注冊,例如:
static const struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
創建socket的時候,在__sock_create中會按照如下代碼進行分流
pf = rcu_dereference(net_families[family]);
//省略.....
err = pf->create(net, sock, protocol, kern);
inetsw_array和inetsw
inetsw_array為數組,inetsw為鏈表。inet_init函數中將 inetsw_array中的協議注冊到inetsw中。依據INET_PROTOSW_PERMANENT標識將協議分為兩類,PERMANENT協議在鏈表的前半部,新插入的協議在PERMANENT協議和非PERMANENT協議之間。在inet_create函數中將依據套接字type進行分流。
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot,
.ops = &inet_stream_ops,
.flags = INET_PROTOSW_PERMANENT |
INET_PROTOSW_ICSK,
},
/* 省略。。。。 */
{
.type = SOCK_RAW,
.protocol = IPPROTO_IP, /* wild card */
.prot = &raw_prot,
.ops = &inet_sockraw_ops,
.flags = INET_PROTOSW_REUSE,
}
};
nl_table
是一個以netlink類型為索引的數組(即以protocol為索引),數組中每個元素為struct netlink_table。主要通過__netlink_kernel_create來進行初始化。創建的內核netlink sock主要保存在net結構中(也有保存在其他結構中的),具體參考上面的結構圖。
相關函數
inet_init:把相關協議注冊到proto_list中,可以通過/proc/net/protocols來顯示;inet_family_ops注冊到net_families,用于創建套接字時依據family分流;將tcp_protocol等注冊到inet_protos,用于接收數據包的時候,ip層向上分流接收的數據包;將 inetsw_array中的協議注冊到inetsw中,依據套接字的type分流。
netlink_kernel_create
創建kernel netlink套接字,初始化netlink類型對應的nl_table。
sk = sock->sk;
if (!cfg || cfg->groups < 32)
groups = 32;
else
groups = cfg->groups;
listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL);
sk->sk_data_ready = netlink_data_ready;
if (cfg && cfg->input)
nlk_sk(sk)->netlink_rcv = cfg->input;
if (netlink_insert(sk, 0))
goto out_sock_release;
nlk = nlk_sk(sk);
nlk->flags |= NETLINK_F_KERNEL_SOCKET;
nl_table[unit].groups = groups;
rcu_assign_pointer(nl_table[unit].listeners, listeners);
nl_table[unit].cb_mutex = cb_mutex;
nl_table[unit].module = module;
if (cfg) {
nl_table[unit].bind = cfg->bind;
nl_table[unit].unbind = cfg->unbind;
nl_table[unit].flags = cfg->flags;
if (cfg->compare)
nl_table[unit].compare = cfg->compare;
}
nl_table[unit].registered = 1;
netlink bind綁定流程
代碼流程
通過bind系統調用只能綁定到32個多播group,通過NETLINK_ADD_MEMBERSHIP套接字選項則可以綁定到更多的group。
SYSCALL_DEFINE3->__sys_bind->netlink_bind
通過netlink_realloc_groups分配內存,按照nl_table[sk->sk_protocol].groups初始化nlk->ngroups和nlk->groups。
通過nlk->netlink_bind來按照bit逐個綁定對應的多播group。
nladdr->nl_pid ? netlink_insert(sk, nladdr->nl_pid) : netlink_autobind(sock)綁定portid。
通過netlink_update_subscriptions來更新nl_table[sk->sk_protocol].mc_list。
通過netlink_update_listeners來更新tbl->listeners->masks。
相關函數/宏定義
NLGRPSZ和NLGRPLONGS:參與入參x,當1<=x<=64的時候, NLGRPSZ為8,NLGRPLONGS為1,當65<=x<=128的時候, NLGRPSZ為16,NLGRPLONGS為2。
netlink_realloc_groups:對nl_table[sk->sk_protocol].groups按照64bit對齊向上取整,分配對應的bit數,分配的bit數保存到nlk->groups,地址保存到nlk->ngroups。
netlink_insert:將nlk_sk(sk)->node插入到nl_table[sk->sk_protocol].hash中,其中hash key為(sock_net(sk), nlk_sk(sk)->portid)組合成的參數netlink_compare_arg。
netlink_autobind:首先嘗試以進程id(PIDTYPE_TGID)來綁定sk,如果沖突失敗的話,則在[S32_MIN, -4097]之間通過一些隨機算法來綁定sk。
雖然是按照負數來查找portid,但是
nlk_sk(sk)->portid實際是按照u32保存的。
netlink_update_subscriptions:依據當前sk是否訂閱多播,將當前sk添加到nl_table[sk->sk_protocol].mc_list,或者從其中刪除。
if (nlk->subscriptions && !subscriptions)
__sk_del_bind_node(sk);
else if (!nlk->subscriptions && subscriptions)
sk_add_bind_node(sk, &nl_table[sk->sk_protocol].mc_list);
nlk->subscriptions = subscriptions;
netlink_update_listeners:遍tbl->listeners歷nl_table[sk->sk_protocol].mc_list,將監聽的多播group進行二進制或操作,保存到tbl->listeners->masks中。
struct netlink_table *tbl = &nl_table[sk->sk_protocol];
unsigned long mask;
unsigned int i;
struct listeners *listeners;
listeners = nl_deref_protected(tbl->listeners);
if (!listeners)
return;
for (i = 0; i < NLGRPLONGS(tbl->groups); i++) {
mask = 0;
sk_for_each_bound(sk, &tbl->mc_list) {
if (i < NLGRPLONGS(nlk_sk(sk)->ngroups))
mask |= nlk_sk(sk)->groups[i];
}
listeners->masks[i] = mask;
}
connect調用
代碼流程
__sys_connect->netlink_connect
netlink_connect主要邏輯如下:
傳入AF_UNSPEC可以撤銷之前connect的操作。
如果之前沒有bind,則調用netlink_autobind來自動綁定。
初始化nlk->dst_portid和nlk->dst_group
if (addr->sa_family == AF_UNSPEC) {
sk->sk_state = NETLINK_UNCONNECTED;
nlk->dst_portid = 0;
nlk->dst_group = 0;
return 0;
}
if (!nlk->bound)
err = netlink_autobind(sock);
if (err == 0) {
sk->sk_state = NETLINK_CONNECTED;
nlk->dst_portid = nladdr->nl_pid;
nlk->dst_group = ffs(nladdr->nl_groups); //ffs返回二進制最后一位的位數,比如二進制1101 0000,ffs結果為5
}
sendto & sendmsg
__sys_sendto->sock_sendmsg->netlink_sendmsg
struct sock *sk = sock->sk;
struct netlink_sock *nlk = nlk_sk(sk);
DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name);
struct scm_cookie scm;
u32 netlink_skb_flags = 0;
err = scm_send(sock, msg, &scm, true);
if (msg->msg_namelen) {
dst_portid = addr->nl_pid;
dst_group = ffs(addr->nl_groups);
netlink_skb_flags |= NETLINK_SKB_DST;
} else {
dst_portid = nlk->dst_portid;
dst_group = nlk->dst_group;
}
if (!nlk->bound) {
err = netlink_autobind(sock);
} else {
/* Ensure nlk is hashed and visible. */
smp_rmb();
}
skb = netlink_alloc_large_skb(len, dst_group);
NETLINK_CB(skb).portid = nlk->portid;
NETLINK_CB(skb).dst_group = dst_group;
NETLINK_CB(skb).creds = scm.creds;
NETLINK_CB(skb).flags = netlink_skb_flags;
err = -EFAULT;
if (memcpy_from_msg(skb_put(skb, len), msg, len)) {
kfree_skb(skb);
goto out;
}
if (dst_group) {
refcount_inc(&skb->users);
netlink_broadcast(sk, skb, dst_portid, dst_group, GFP_KERNEL);
}
err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT);
通過scm_send獲取cred
如果通過msg->msg_name傳遞了目的端地址,則使用該目的地址,否則使用connect設定的目的地址(沒有調用connect的話,默認dst_portid和dst_group應該是0)。
如果之前沒有綁定過,則通過netlink_autobind來自動綁定。
分配skb,并copy要發送的數據到skb中。
如果指定了多播,則調用netlink_broadcast來廣播。
通過netlink_unicast來發送單播消息。
廣播:netlink_broadcast->netlink_broadcast_filtered->do_one_broadcast->netlink_broadcast_deliver->__netlink_sendskb
單播:netlink_unicast
通過netlink_getsockbyportid來查找目的sk。該函數依據發送消息的目的portid和net,從nl_table[protocol].hash中查找目的sk,如果目的sk的狀態是NETLINK_CONNECTED,則目的sk的nlk->dst_portid需要與發送端的nlk_sk(ssk)->portid相等才行。
如果查找到的目的sk為內核創建的sk,則調用netlink_unicast_kernel發送報文到內核,netlink_unicast_kernel中實際調用nlk->netlink_rcv鉤子函數來處理,另外還會通過netlink_deliver_tap_kernel函數將發往內核netlink的報文發送到nlmon虛擬設備上,以支持wireshark抓包。
調用netlink_attachskb來進行超時等待,如果發送端是kernel netlink,對端擁塞的時候,直接給對端標記錯誤。如果發送端是用戶netlink,對端擁塞且設置了套接字的超時參數,則把當前進程添加到nlk->wait等待隊列中。
最后調用netlink_sendskb來把報文發送給對端的netlink,實際與廣播發送的路徑一致,都是調用__netlink_sendskb。
關鍵函數
scm_send:傳遞fd或者creds,其中在netlink場景下僅能傳遞creds,不能傳遞fd。在unix socket下可以傳遞套接字。
__netlink_sendskb: 將skb添加到sk->sk_receive_queue隊列尾部,并調用sk->sk_data_ready喚醒等待隊列sk->sk_wq中的進程。內核創建的netlink只接收單播,不接收廣播。
do_one_broadcast: 還會有一些異常處理,擁塞控制等。比如當前(&sk->sk_rmem_alloc) > (sk->sk_rcvbuf >> 1),則處于擁塞狀態,會通過yield()讓出運行權。
netlink_deliver_tap:由__netlink_sendskb和netlink_deliver_tap_kernel調用,用于wireshark通過nlmon模塊抓取netlink報文。
nlk->wait和sk->sk_wq區別:如果發送的時候對端擁塞,發送端阻塞在nlk->wait,如果接收的時候緩存中沒有數據,接收方阻塞在sk->sk_wq
wireshark 抓取netlink報文
modprobe nlmon; ip link add type nlmon; ip link set nlmon0 up
參考:https://gitlab.com/wireshark/wireshark/-/wikis/Protocols/netlink
netlink_recvmsg
skb = skb_recv_datagram(sk, flags, noblock, &err);
if (skb == NULL)
goto out;
// 省略:拷貝skb數據到入參msg中
if (nlk->flags & NETLINK_F_RECV_PKTINFO)
// 拷貝NETLINK_CB(skb).dst_group到cmsg
netlink_cmsg_recv_pktinfo(msg, skb);
if (nlk->flags & NETLINK_F_LISTEN_ALL_NSID)
// 拷貝NETLINK_CB(skb).nsid到cmsg
netlink_cmsg_listen_all_nsid(sk, msg, skb);
if (nlk->cb_running &&
atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
ret = netlink_dump(sk);
if (ret) {
sk->sk_err = -ret;
sk->sk_error_report(sk);
}
}
scm_recv(sock, msg, &scm, flags);
out:
netlink_rcv_wake(sk);
關鍵函數
skb_recv_datagram: 嘗試從sk->sk_receive_queue中獲取skb數據,如果當前接收隊列中沒有數據,則可以跟進超時設置,把當前進程阻塞在sk->sk_wq中。
其他資料
/proc中netlink信息
/proc/net/netlink對應netlink_seq_show,實際遍歷nl_table[iter->link].hash
netlink庫libnl
地址:http://www.infradead.org/~tgr/libnl/
git:https://github.com/tgraf/libnl
其中libnl-utils提供了netlink的細分功能
netlink選項
#define NETLINK_ADD_MEMBERSHIP 1
#define NETLINK_DROP_MEMBERSHIP 2
#define NETLINK_PKTINFO 3
#define NETLINK_BROADCAST_ERROR 4
#define NETLINK_NO_ENOBUFS 5
#ifndef __KERNEL__
#define NETLINK_RX_RING 6
#define NETLINK_TX_RING 7
#endif
#define NETLINK_LISTEN_ALL_NSID 8
#define NETLINK_LIST_MEMBERSHIPS 9
#define NETLINK_CAP_ACK 10
#define NETLINK_EXT_ACK 11
#define NETLINK_GET_STRICT_CHK 12
netlink protocol
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
#define NETLINK_SMC 22 /* SMC monitoring */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32
附件列表
總結
以上是生活随笔為你收集整理的netlink基础结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Jmeter之压力测试总结
- 下一篇: 浦发新出的VISA超凡白金信用卡,境外刷