天天看點

linux網絡協定棧核心分析 1. Linux 網絡路徑

文章轉載自:http://blog.csdn.net/xaiojiang/article/details/51584533

1. Linux 網絡路徑

linux網絡協定棧核心分析 1. Linux 網絡路徑

1.1 發送端

1.1.1 應用層

(1) Socket

應用層的各種網絡應用程式基本上都是通過 Linux Socket 程式設計接口來和核心空間的網絡協定棧通信的。Linux Socket 是從 BSD Socket 發展而來的,它是 Linux 作業系統的重要組成部分之一,它是網絡應用程式的基礎。從層次上來說,它位于應用層,是作業系統為應用程式員提供的 API,通過它,應用程式可以通路傳輸層協定。

  • socket 位于傳輸層協定之上,屏蔽了不同網絡協定之間的差異
  • socket 是網絡程式設計的入口,它提供了大量的系統調用,構成了網絡程式的主體
  • 在Linux系統中,socket 屬于檔案系統的一部分,網絡通信可以被看作是對檔案的讀取,使得我們對網絡的控制和對檔案的控制一樣友善。
linux網絡協定棧核心分析 1. Linux 網絡路徑
linux網絡協定棧核心分析 1. Linux 網絡路徑

 UDP socket 處理過程 (來源)

linux網絡協定棧核心分析 1. Linux 網絡路徑

TCP Socket 處理過程(來源)

(2) 應用層處理流程

  1. 網絡應用調用Socket API socket (int family, int type, int protocol) 建立一個 socket,該調用最終會調用 Linux system call socket() ,并最終調用 Linux Kernel 的 sock_create() 方法。該方法傳回被建立好了的那個 socket 的 file descriptor。對于每一個 userspace 網絡應用建立的 socket,在核心中都有一個對應的 struct socket和 struct sock。其中,struct sock 有三個隊列(queue),分别是 rx , tx 和 err,在 sock 結構被初始化的時候,這些緩沖隊列也被初始化完成;在收據收發過程中,每個 queue 中儲存要發送或者接受的每個 packet 對應的 Linux 網絡棧 sk_buffer 資料結構的執行個體 skb。
  2. 對于 TCP socket 來說,應用調用 connect()API ,使得用戶端和伺服器端通過該 socket 建立一個虛拟連接配接。在此過程中,TCP 協定棧通過三次握手會建立 TCP 連接配接。預設地,該 API 會等到 TCP 握手完成連接配接建立後才傳回。在建立連接配接的過程中的一個重要步驟是,确定雙方使用的 Maxium Segemet Size (MSS)。因為 UDP 是面向無連接配接的協定,是以它是不需要該步驟的。
  3. 應用調用 Linux Socket 的 send 或者 write API 來發出一個 message 給接收端
  4. sock_sendmsg 被調用,它使用 socket descriptor 擷取 sock struct,建立 message header 和 socket control message
  5. _sock_sendmsg 被調用,根據 socket 的協定類型,調用相應協定的發送函數。
    1. 對于 TCP ,調用 tcp_sendmsg 函數。
    2. 對于 UDP 來說,userspace 應用可以調用 send()/sendto()/sendmsg() 三個 system call 中的任意一個來發送 UDP message,它們最終都會調用核心中的 udp_sendmsg() 函數。
linux網絡協定棧核心分析 1. Linux 網絡路徑

1.1.2 傳輸層

傳輸層的最終目的是向它的使用者提供高效的、可靠的和成本有效的資料傳輸服務,主要功能包括 (1)構造 TCP segment (2)計算 checksum (3)發送回複(ACK)包 (4)滑動視窗(sliding windown)等保證可靠性的操作。TCP 協定棧的大緻處理過程如下圖所示:

linux網絡協定棧核心分析 1. Linux 網絡路徑

TCP 棧簡要過程:

  1. tcp_sendmsg 函數會首先檢查已經建立的 TCP connection 的狀态,然後擷取該連接配接的 MSS,開始 segement 發送流程。
  2. 構造 TCP 段的 playload:它在核心空間中建立該 packet 的 sk_buffer 資料結構的執行個體 skb,從 userspace buffer 中拷貝 packet 的資料到 skb 的 buffer。
  3. 構造 TCP header。
  4. 計算 TCP 校驗和(checksum)和 順序号 (sequence number)。
    1. TCP 校驗和是一個端到端的校驗和,由發送端計算,然後由接收端驗證。其目的是為了發現TCP首部和資料在發送端到接收端之間發生的任何改動。如果接收方檢測到校驗和有差錯,則TCP段會被直接丢棄。TCP校驗和覆寫 TCP 首部和 TCP 資料。
    2. TCP的校驗和是必需的
  5. 發到 IP 層處理:調用 IP handler 句柄 ip_queue_xmit,将 skb 傳入 IP 處理流程。

UDP 棧簡要過程:

  1. UDP 将 message 封裝成 UDP 資料報
  2. 調用 ip_append_data() 方法将 packet 送到 IP 層進行處理。

1.1.3 IP 網絡層 – 添加header 和 checksum,路由處理,IP fragmentation

網絡層的任務就是選擇合适的網間路由和交換結點, 確定資料及時傳送。網絡層将資料鍊路層提供的幀組成資料包,包中封裝有網絡層標頭,其中含有邏輯位址資訊- -源站點和目的站點位址的網絡位址。其主要任務包括 (1)路由處理,即選擇下一跳 (2)添加 IP header(3)計算 IP header checksum,用于檢測 IP 封包頭部在傳播過程中是否出錯 (4)可能的話,進行 IP 分片(5)處理完畢,擷取下一跳的 MAC 位址,設定鍊路層封包頭,然後轉傳入連結路層處理。

IP 頭:

linux網絡協定棧核心分析 1. Linux 網絡路徑

IP 棧基本處理過程如下圖所示:

linux網絡協定棧核心分析 1. Linux 網絡路徑
  1. 首先,ip_queue_xmit(skb)會檢查skb->dst路由資訊。如果沒有,比如套接字的第一個包,就使用ip_route_output()選擇一個路由。
  2. 接着,填充IP包的各個字段,比如版本、標頭長度、TOS等。
  3. 中間的一些分片等,可參閱相關文檔。基本思想是,當封包的長度大于mtu,gso的長度不為0就會調用 ip_fragment 進行分片,否則就會調用ip_finish_output2把資料發送出去。ip_fragment 函數中,會檢查 IP_DF 标志位,如果待分片IP資料包禁止分片,則調用 icmp_send()向發送方發送一個原因為需要分片而設定了不分片标志的目的不可達ICMP封包,并丢棄封包,即設定IP狀态為分片失敗,釋放skb,傳回消息過長錯誤碼。
  4. 接下來就用 ip_finish_ouput2 設定鍊路層封包頭了。如果,鍊路層報頭緩存有(即hh不為空),那就拷貝到skb裡。如果沒,那麼就調用neigh_resolve_output,使用 ARP 擷取。

1.1.4 資料鍊路層 

   功能上,在實體層提供比特流服務的基礎上,建立相鄰結點之間的資料鍊路,通過差錯控制提供資料幀(Frame)在信道上無差錯的傳輸,并進行各電路上的動作系列。資料鍊路層在不可靠的實體媒體上提供可靠的傳輸。該層的作用包括:實體位址尋址、資料的成幀、流量控制、資料的檢錯、重發等。在這一層,資料的機關稱為幀(frame)。資料鍊路層協定的代表包括:SDLC、HDLC、PPP、STP、幀中繼等。

實作上,Linux 提供了一個 Network device 的抽象層,其實作在 linux/net/core/dev.c。具體的實體網絡裝置在裝置驅動中(driver.c)需要實作其中的虛函數。Network Device 抽象層調用具體網絡裝置的函數。

linux網絡協定棧核心分析 1. Linux 網絡路徑

1.1.5 實體層 – 實體層封裝和發送

linux網絡協定棧核心分析 1. Linux 網絡路徑
  1. 實體層在收到發送請求之後,通過 DMA 将該主存中的資料拷貝至内部RAM(buffer)之中。在資料拷貝中,同時加入符合以太網協定的相關header,IFG、前導符和CRC。對于以太網網絡,實體層發送采用CSMA/CD,即在發送過程中偵聽鍊路沖突。
  2. 一旦網卡完成封包發送,将産生中斷通知CPU,然後驅動層中的中斷處理程式就可以删除儲存的 skb 了。

1.1.6 簡單總結

linux網絡協定棧核心分析 1. Linux 網絡路徑

 (來源)

1.2 接收端

1.2.1 實體層和資料鍊路層

linux網絡協定棧核心分析 1. Linux 網絡路徑
linux網絡協定棧核心分析 1. Linux 網絡路徑

簡要過程:

  1. 一個 package 到達機器的實體網絡擴充卡,當它接收到資料幀時,就會觸發一個中斷,并将通過 DMA 傳送到位于 linux kernel 記憶體中的 rx_ring。
  2. 網卡發出中斷,通知 CPU 有個 package 需要它處理。中斷處理程式主要進行以下一些操作,包括配置設定 skb_buff 資料結構,并将接收到的資料幀從網絡擴充卡I/O端口拷貝到skb_buff 緩沖區中;從資料幀中提取出一些資訊,并設定 skb_buff 相應的參數,這些參數将被上層的網絡協定使用,例如skb->protocol;
  3. 終端處理程式經過簡單處理後,發出一個軟中斷(NET_RX_SOFTIRQ),通知核心接收到新的資料幀。
  4. 核心 2.5 中引入一組新的 API 來處理接收的資料幀,即 NAPI。是以,驅動有兩種方式通知核心:(1) 通過以前的函數netif_rx;(2)通過NAPI機制。該中斷處理程式調用 Network device的 netif_rx_schedule 函數,進入軟中斷處理流程,再調用 net_rx_action 函數。
  5. 該函數關閉中斷,擷取每個 Network device 的 rx_ring 中的所有 package,最終 pacakage 從 rx_ring 中被删除,進入 netif _receive_skb 處理流程。
  6. netif_receive_skb 是鍊路層接收資料報的最後一站。它根據注冊在全局數組 ptype_all 和 ptype_base 裡的網絡層資料報類型,把資料報遞交給不同的網絡層協定的接收函數(INET域中主要是ip_rcv和arp_rcv)。該函數主要就是調用第三層協定的接收函數處理該skb包,進入第三層網絡層處理。

1.2.2 網絡層

linux網絡協定棧核心分析 1. Linux 網絡路徑
linux網絡協定棧核心分析 1. Linux 網絡路徑
  1. IP 層的入口函數在 ip_rcv 函數。該函數首先會做包括 package checksum 在内的各種檢查,如果需要的話會做 IP defragment(将多個分片合并),然後 packet 調用已經注冊的 Pre-routing netfilter hook ,完成後最終到達 ip_rcv_finish 函數。
  2. ip_rcv_finish 函數會調用 ip_router_input 函數,進入路由處理環節。它首先會調用 ip_route_input 來更新路由,然後查找 route,決定該 package 将會被發到本機還是會被轉發還是丢棄:
    1. 如果是發到本機的話,調用 ip_local_deliver 函數,可能會做 de-fragment(合并多個 IP packet),然後調用 ip_local_deliver 函數。該函數根據 package 的下一個處理層的 protocal number,調用下一層接口,包括 tcp_v4_rcv (TCP), udp_rcv (UDP),icmp_rcv (ICMP),igmp_rcv(IGMP)。對于 TCP 來說,函數 tcp_v4_rcv 函數會被調用,進而處理流程進入 TCP 棧。
    2. 如果需要轉發 (forward),則進入轉發流程。該流程需要處理 TTL,再調用 dst_input 函數。該函數會 (1)處理 Netfilter Hook (2)執行 IP fragmentation (3)調用 dev_queue_xmit,進傳入連結路層處理流程。
linux網絡協定棧核心分析 1. Linux 網絡路徑
linux網絡協定棧核心分析 1. Linux 網絡路徑

1.2.3 傳輸層 (TCP/UDP)

  1. 傳輸層 TCP 處理入口在 tcp_v4_rcv 函數(位于 linux/net/ipv4/tcp ipv4.c 檔案中),它會做 TCP header 檢查等處理。
  2. 調用 _tcp_v4_lookup,查找該 package 的 open socket。如果找不到,該 package 會被丢棄。接下來檢查 socket 和 connection 的狀态。
  3. 如果socket 和 connection 一切正常,調用 tcp_prequeue 使 package 從核心進入 user space,放進 socket 的 receive queue。然後 socket 會被喚醒,調用 system call,并最終調用 tcp_recvmsg 函數去從 socket recieve queue 中擷取 segment。

1.2.4 接收端 – 應用層

  1. 每當使用者應用調用  read 或者 recvfrom 時,該調用會被映射為/net/socket.c 中的 sys_recv 系統調用,并被轉化為 sys_recvfrom 調用,然後調用 sock_recgmsg 函數。
  2. 對于 INET 類型的 socket,/net/ipv4/af inet.c 中的 inet_recvmsg 方法會被調用,它會調用相關協定的資料接收方法。
  3. 對 TCP 來說,調用 tcp_recvmsg。該函數從 socket buffer 中拷貝資料到 user buffer。
  4. 對 UDP 來說,從 user space 中可以調用三個 system call recv()/recvfrom()/recvmsg() 中的任意一個來接收 UDP package,這些系統調用最終都會調用核心中的 udp_recvmsg 方法。

1.2.5 封包接收過程簡單總結

linux網絡協定棧核心分析 1. Linux 網絡路徑

 2. Linux sk_buff struct 資料結構和隊列(Queue)

2.1 sk_buff

(本章節摘選自 http://amsekharkernel.blogspot.com/2014/08/what-is-skb-in-linux-kernel-what-are.html)

2.1.1 sk_buff 是什麼

當網絡包被核心處理時,底層協定的資料被傳送更高層,當資料傳送時過程反過來。由不同協定産生的資料(包括頭和負載)不斷往下層傳遞直到它們最終被發送。因為這些操作的速度對于網絡層的表現至關重要,核心使用一個特定的結構叫 sk_buff, 其定義檔案在 skbuffer.h。Socket buffer被用來在網絡實作層交換資料而不用拷貝來或去資料包 –這顯著獲得速度收益。

  • sk_buff 是 Linux 網絡的一個核心資料結構,其定義檔案在 skbuffer.h。
  • socket kernel buffer (skb) 是 Linux 核心網絡棧(L2 到 L4)處理網絡包(packets)所使用的 buffer,它的類型是 sk_buffer。簡單來說,一個 skb 表示 Linux 網絡棧中的一個 packet;TCP 分段和 IP 分組生産的多個 skb 被一個 skb list 形式來儲存。
  • struct sock 有三個 skb 隊列(sk_buffer queue),分别是 rx , tx 和 err。
linux網絡協定棧核心分析 1. Linux 網絡路徑

它的主要結構成員:

linux網絡協定棧核心分析 1. Linux 網絡路徑
struct sk_buff {
    /* These two members must be first. */ # packet 可以存在于 list 或者 queue 中,這兩個成員用于連結清單處理
    struct sk_buff        *next;
    struct sk_buff        *prev;
    struct sk_buff_head    *list; #該 packet 所在的 list
 ...
    struct sock        *sk;      #跟該 skb 相關聯的 socket
    struct timeval        stamp; # packet 發送或者接收的時間,主要用于 packet sniffers
    struct net_device    *dev;  #這三個成員跟蹤該 packet 相關的 devices,比如接收它的裝置等
    struct net_device    *input_dev;
    struct net_device    *real_dev;

    union {                  #指向各協定層 header 結構
        struct tcphdr    *th;
        struct udphdr    *uh;
        struct icmphdr    *icmph;
        struct igmphdr    *igmph;
        struct iphdr    *ipiph;
        struct ipv6hdr    *ipv6h;
        unsigned char    *raw;
    } h;

    union {
        struct iphdr    *iph;
        struct ipv6hdr    *ipv6h;
        struct arphdr    *arph;
        unsigned char    *raw;
    } nh;

    union {
        unsigned char    *raw;
    } mac;

    struct  dst_entry    *dst; #指向該 packet 的路由目的結構,告訴我們它會被如何路由到目的地
    char            cb[40];    # SKB control block,用于各協定層儲存私有資訊,比如 TCP 的順序号和幀的重發狀态
    unsigned int        len, #packet 的長度
                data_len,
                mac_len,       # MAC header 長度
                csum;          # packet 的 checksum,用于計算儲存在 protocol header 中的校驗和。發送時,當 checksum offloading 時,不設定;接收時,可以由device計算

    unsigned char        local_df, #用于 IPV4 在已經做了分片的情況下的再分片,比如 IPSEC 情況下。
                cloned:1, #在 skb 被 cloned 時設定,此時,skb 各成員是自己的,但是資料是shared的
                nohdr:1,  #用于支援 TSO
                pkt_type, #packet 類型
                ip_summed; # 網卡能支援的校驗和計算的類型,NONE 表示不支援,HW 表示支援,

    __u32            priority; #用于 QoS
    unsigned short        protocol, # 接收 packet 的協定
                security;      
linux網絡協定棧核心分析 1. Linux 網絡路徑

2.1.2 skb 的主要操作

(1)配置設定 skb = alloc_skb(len, GFP_KERNEL)

linux網絡協定棧核心分析 1. Linux 網絡路徑

(2)添加 payload (skb_put(skb, user_data_len))

linux網絡協定棧核心分析 1. Linux 網絡路徑

(3)使用 skb->push 添加 protocol header,或者 skb->pull 删除 header

linux網絡協定棧核心分析 1. Linux 網絡路徑

  2.2 Linux 網絡棧使用的驅動隊列 (driver queue)

(本章節摘選自 Queueing in the Linux Network Stack by Dan Siemon)

2.2.1 隊列

linux網絡協定棧核心分析 1. Linux 網絡路徑

在 IP 棧和 NIC 驅動之間,存在一個 driver queue (驅動隊列)。典型地,它被實作為 FIFO ring buffer,簡單地可以認為它是固定大小的。這個隊列不包含 packet data,相反,它隻是儲存 socket kernel buffer (skb)的指針,而 skb 的使用如上節所述是貫穿核心網絡棧處理過程的始終的。

該隊列的輸入時 IP 棧處理完畢的 packets。這些packets 要麼是本機的應用産生的,要麼是進入本機又要被路由出去的。被 IP 棧加入隊列的 packets 會被網絡裝置驅動(hardware driver)取出并且通過一個資料通道(data bus)發到 NIC 硬體裝置并傳輸出去。

在不使用 TSO/GSO 的情況下,IP 棧發到該隊列的 packets 的長度必須小于 MTU。

2.2.2 skb 大小 – 預設最大大小為 NIC MTU

絕大多數的網卡都有一個固定的最大傳輸單元(maximum transmission unit, MTU)屬性,它是該網絡裝置能夠傳輸的最大幀(frame)的大小。對以太網來說,預設值為 1500 bytes,但是有些以太網絡可以支援巨幀(jumbo frame),最大能到 9000 bytes。在 IP 網絡棧内,MTU 表示能發給 NIC 的最大 packet 的大小。比如,如果一個應用向一個 TCP socket 寫入了 2000 bytes 資料,那麼 IP 棧需要建立兩個 IP packets 來保持每個 packet 的大小等于或者小于 1500 bytes。可見,對于大資料傳輸,相對較小的 MTU 會導緻産生大量的小網絡包(small packets)并被傳入 driver queue。這成為 IP 分片 (IP fragmentation)。

下圖表示 payload 為 1500 bytes 的 IP 包,在 MTU 為 1000 和 600 時候的分片情況:

linux網絡協定棧核心分析 1. Linux 網絡路徑

備注:

  • 以上資料是從網絡上擷取的各種資料整理而來
  • 這一塊本身就比較複雜,而且不同的 linux 核心的版本之間也有差異,文中的内容還需要進一步加工,錯誤在所難免。

參考連結:

Linux網絡協定棧(一)——Socket入門

Linux網絡協定棧(四)——鍊路層(1)

What is SKB in Linux kernel? What are SKB operations? Memory Representation of SKB? How to send packet out using skb operations?

Queueing in the Linux Network Stack

Transmission Control Protocol

TCP/IP協定棧中的資料收發

http://www.haifux.org/lectures/217/netLec5.pdf

繼續閱讀