首頁 Linux教程如何學習 Linux 內核網絡協議棧

如何學習 Linux 內核網絡協議棧

運維派隸屬馬哥教育旗下專業運維社區,是國內成立最早的IT運維技術社區,歡迎關注公眾號:yunweipai
領取學習更多免費Linux云計算、Python、Docker、K8s教程關注公眾號:馬哥linux運維

下面將介紹一些內核網絡協議棧中常常涉及到的概念。

sk_buff

內核顯然需要一個數據結構來表示報文,這個結構就是 sk_buff ( socket buffer 的簡稱),它等同于在<TCP/IP詳解 卷2>中描述的 BSD 內核中的 mbuf。

sk_buff 結構自身并不存儲報文內容,它通過多個指針指向真正的報文內存空間:

如何學習 Linux 內核網絡協議棧插圖

sk_buff 是一個貫穿整個協議棧層次的結構,在各層間傳遞時,內核只需要調整 sk_buff 中的指針位置就行。

net_device

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

如下圖所示,每個網卡都有兩端,一端是協議棧(IP、TCP、UDP),另一端則有所區別,對物理網卡來說,這一端是網卡生產廠商提供的設備驅動程序,而對虛擬網卡來說差別就大了,正是由于虛擬網卡的存在,內核才能支持各種隧道封裝、容器通信等功能。

如何學習 Linux 內核網絡協議棧插圖1

socket & sock

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

如下圖所示,這兩個結構實際上是一一對應的。

注意到,這兩個結構上都有一個叫 ops 的指針, 但它們的類型不同。socket 的 ops 是一個指向 struct proto_ops 的指針,sock 的 ops 是一個指向 struct proto 的指針, 它們在結構被創建時確定。

回憶網絡編程中 socket() 函數的原型:

#include <sys/socket.h>

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,
    },
}
.......

L3->L4

我們知道網絡協議棧是分層的,但實際上,具體到實現,內核協議棧的分層只是邏輯上的,本質還是函數調用。發送流程(上層調用下層)通常是直接調用(因為沒有不確定性,比如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,
    .....
};
.......

而在IP層,查詢過路由后,如果該報文是需要上送本機的,則會根據報文的 L4 協議,送給不同的 L4 處理:

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

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);
}
.......

Netfilter

Netfilter 是報文在內核協議棧必然會通過的路徑,我們從下面這張圖就可以看到,Netfilter 在內核的 5 個地方設置了 HOOK 點,用戶可以通過配置 iptables 規則,在 HOOK 點對報文進行過濾、修改等操作。

在內核代碼中,我們時??梢?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;
}
.......

st_entry

內核需要確定收到的報文是應該本地上送(local deliver)還是轉發(forward),對本機發送(local out)的報文需要確定是從哪個網卡發送出去,這都是內核通過查詢 fib (forward information base, 轉發信息表) 確定。fib 可以理解為一個數據庫,數據來源是用戶配置或者內核自動生成的路由。

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://www.sohu.com/a/611251461_121124373

本文鏈接:http://www.thecarconnectin.com/43105.html

網友評論comments

發表回復

您的電子郵箱地址不會被公開。

暫無評論

Copyright ? 2012-2022 YUNWEIPAI.COM - 運維派 京ICP備16064699號-6
掃二維碼
掃二維碼
返回頂部
国产曰批视频免费观看完|久久久一本精品99久久精品66直播|色天使色偷偷AV一区二区三区|国产色秀视频在线播放|亚洲欧洲免费三级网站