點擊左上方藍色“一口Linux”,選擇“設為星標”

協議棧的細節
?
下面將介紹一些內核網絡協議棧中常常涉及到的概念。
sk_buff
sk_buff 結構自身并不存儲報文內容,它通過多個指針指向真正的報文內存空間:

內核使用 net_device 表示網卡。網卡可以分為物理網卡和虛擬網卡。物理網卡是指真正能把報文發出本機的網卡,包括真實物理機的網卡以及VM虛擬機的網卡,而像 tun/tap,vxlan、veth pair 這樣的則屬于虛擬網卡的范疇。

用戶空間通過 socket()、bind()、listen()、accept() 等庫函數進行網絡編程。而這里提到的 socket 和 sock 是內核中的兩個數據結構,其中 socket 向上面向用戶,而 sock 向下面向協議棧。

注意到,這兩個結構上都有一個叫 ops 的指針, 但它們的類型不同。socket 的 ops 是一個指向 struct proto_ops 的指針,sock 的 ops 是一個指向 struct proto 的指針, 它們在結構被創建時確定。
回憶網絡編程中 socket() 函數的原型:
#include?
sockfd?=?socket(int?socket_family,?int?socket_type,?int?protocol);實際上, socket->ops 和 sock->ops 由前兩個參數 socket_family 和 socket_type 共同確定。
如果 socket_family 是最常用的 PF_INET 協議簇, 則 socket->ops 和 sock->ops 的取值就記錄在 INET 協議開關表中:
static?struct?inet_protosw?inetsw_array[]?=
{
????{
????????.type?=?????SOCK_STREAM,
????????.protocol?=?IPPROTO_TCP,
????????.prot?=?????&tcp_prot,?????????????????//?對應?sock->ops
????????.ops?=??????&inet_stream_ops,??????????//?對應?socket->ops
????????.flags?=????INET_PROTOSW_PERMANENT?|?INET_PROTOSW_ICSK,
????},
????{
????????.type?=?????SOCK_DGRAM,
????????.protocol?=?IPPROTO_UDP,
????????.prot?=?????&udp_prot,?????????????????//?對應?sock->ops
????????.ops?=??????&inet_dgram_ops,???????????//?對應?socket->ops
????????.flags?=????INET_PROTOSW_PERMANENT,
????},
}
.......我們知道網絡協議棧是分層的,但實際上,具體到實現,內核協議棧的分層只是邏輯上的,本質還是函數調用。發送流程(上層調用下層)通常是直接調用(因為沒有不確定性,比如TCP知道下面一定IP),但接收過程不一樣了,比如報文在 IP 層時,它上面可能是 TCP,也可能是 UDP,或者是 ICMP 等等,所以接收過程使用的是注冊-回調機制。
還是以 INET 協議簇為例,注冊接口是:
int?inet_add_protocol(const?struct?net_protocol?*prot,?unsigned?char?protocol);在內核網絡子系統初始化時,L4 層協議(如下面的 TCP 和 UDP)會被注冊:
static?struct?net_protocol?tcp_protocol?=?{
????......
????.handler?=?tcp_v4_rcv,
????......
};
static?struct?net_protocol?udp_protocol?=?{
????.....
????.handler?=?udp_rcv,
????.....
};
.......static?int?ip_local_deliver_finish(struct?net?*net,?struct?sock?*sk,?struct?sk_buff?*skb)
{
????......
????ipprot?=?rcu_dereference(inet_protos[protocol]);
????......
????ret?=?ipprot->handler(skb);?????
????......
}
.......L2->L3 如出一轍。只不過注冊接口變成了:
void?dev_add_pack(struct?packet_type?*pt)誰會注冊呢?顯然至少 IP 會:
static?struct?packet_type?ip_packet_type?=?{
????.type?=?cpu_to_be16(ETH_P_IP),
????.func?=?ip_rcv,
}
.......而在報文接收過程中,設備驅動程序會將報文的 L3 類型設置到 skb->protocol,然后在內核 netif_receive_skb 收包時,會根據這個 protocol 調用不同的回調函數:
__netif_receive_skb(struct?sk_buff?*skb)
{
????......
????type?=?skb->protocol;
????......
????ret?=?pt_prev->func(skb,?skb->dev,?pt_prev,?orig_dev);
}
.......
在內核代碼中,我們時常可見 NF_HOOK 這樣的調用。我的建議是,如果你暫時不考慮 Netfilter,那么就直接跳過, 跟蹤 okfn 就行。
static?inline?int?NF_HOOK(uint8_t?pf,?unsigned?int?hook,?struct?net?*net,?struct?sock?*sk,?
????struct?sk_buff?*skb,?struct?net_device?*in,?struct?net_device?*out,
????int?(*okfn)(struct?net?*,?struct?sock?*,?struct?sk_buff?*))
{
????int?ret?=?nf_hook(pf,?hook,?net,?sk,?skb,?in,?out,?okfn);
????if?(ret?==?1)
????????ret?=?okfn(net,?sk,?skb);
????return?ret;
}
.......dst_entry
fib 查詢的輸入是報文 sk_buff,輸出是 dst_entry. dst_entry 會被設置到 skb 上:
static?inline?void?skb_dst_set(struct?sk_buff?*skb,?struct?dst_entry?*dst)
{
????skb->_skb_refdst?=?(unsigned?long)dst;
}而 dst_entry 中最重要的是一個 input 指針和 output 指針:
struct?dst_entry?
{
????......
????int?(*input)(struct?sk_buff?*);
????int?(*output)(struct?net?*net,?struct?sock?*sk,?struct?sk_buff?*skb);
????......
}對于需要本機上送的報文:
rth->dst.input?=?ip_local_deliver;對需要轉發的報文:
rth->dst.input?=?ip_forward;對本機發送的報文:
rth->dst.output?=?ip_output;原文:https://segmentfault.com/a/1190000021227338
版權歸原作者所有,如有侵權,請聯系刪除。
end
一口Linux?
關注,回復【1024】海量Linux資料贈送
精彩文章合集
文章推薦