理解 Linux 網絡棧(1):Linux 網絡協議棧簡單總結

引自:https://www.cnblogs.com/sammyliu/p/5225623.html

本系列文章總結 Linux 網絡棧,包括:

(1)Linux 網絡協議棧總結html

(2)非虛擬化Linux環境中的網絡分段卸載技術 GSO/TSO/UFO/LRO/GROlinux

(3)QEMU/KVM + VxLAN 環境下的 Segmentation Offloading 技術(發送端) git

(4)QEMU/KVM + VxLAN 環境下的 Segmentation Offloading 技術(接收端)程序員

 

1. Linux 網絡路徑

1.1 發送端

1.1.1 應用層

(1) Socketgithub

   應用層的各類網絡應用程序基本上都是經過 Linux Socket 編程接口來和內核空間的網絡協議棧通訊的。Linux Socket 是從 BSD Socket 發展而來的,它是 Linux 操做系統的重要組成部分之一,它是網絡應用程序的基礎。從層次上來講,它位於應用層,是操做系統爲應用程序員提供的 API,經過它,應用程序能夠訪問傳輸層協議。編程

  • socket 位於傳輸層協議之上,屏蔽了不一樣網絡協議之間的差別
  • socket 是網絡編程的入口,它提供了大量的系統調用,構成了網絡程序的主體
  • 在Linux系統中,socket 屬於文件系統的一部分,網絡通訊能夠被看做是對文件的讀取,使得咱們對網絡的控制和對文件的控制同樣方便。

   

 

                         UDP socket 處理過程 (來源)                                                                                       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() 函數。

 

1.1.2 傳輸層

    傳輸層的最終目的是向它的用戶提供高效的、可靠的和成本有效的數據傳輸服務,主要功能包括 (1)構造 TCP segment (2)計算 checksum (3)發送回覆(ACK)包 (4)滑動窗口(sliding windown)等保證可靠性的操做。TCP 協議棧的大體處理過程以下圖所示: 服務器

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 頭:

  

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

  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 抽象層調用具體網絡設備的函數。

1.1.5 物理層 - 物理層封裝和發送

  1. 物理層在收到發送請求以後,經過 DMA 將該主存中的數據拷貝至內部RAM(buffer)之中。在數據拷貝中,同時加入符合以太網協議的相關header,IFG、前導符和CRC。對於以太網網絡,物理層發送採用CSMA/CD,即在發送過程當中偵聽鏈路衝突。
  2. 一旦網卡完成報文發送,將產生中斷通知CPU,而後驅動層中的中斷處理程序就能夠刪除保存的 skb 了。

1.1.6 簡單總結

 (來源

1.2 接收端

1.2.1 物理層和數據鏈路層

     

簡要過程:

  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 網絡層

   

  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,進入鏈路層處理流程。

  

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 報文接收過程簡單總結

 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。

它的主要結構成員:

複製代碼
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;
複製代碼

2.1.2 skb 的主要操做

(1)分配 skb = alloc_skb(len, GFP_KERNEL) 

       

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

       

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

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

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

2.2.1 隊列

  在 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 內核的版本之間也有差別,文中的內容還須要進一步加工,錯誤在所不免。

 


 

網絡數據包的封包與拆包

過程以下:

將數據從一臺計算機經過必定的路徑發送到另外一臺計算機。應用層數據經過協議棧發到網絡上時,每層協議都要加上一個數據首部(header),稱爲封裝(Encapsulation),以下圖所示:

不一樣的協議層對數據包有不一樣的稱謂,在傳輸層叫作段(segment),在網絡層叫作數據包(packet),在鏈路層叫作幀(frame)。數據封裝成幀後發到傳輸介質上,到達目的主機後每層協議再剝掉相應的首部,最後將應用層數據交給應用程序處理。
上圖對應兩臺計算機在同一網段中的狀況,若是兩臺計算機在不一樣的網段中,那麼數據從一臺計算機到另外一臺計算機傳輸過程當中要通過一個或多個路由器,以下圖所示:

其實在鏈路層之下還有物理層,指的是電信號的傳遞方式,好比如今以太網通用的網線(雙絞線)、早期以太網採用的的同軸電纜(如今主要用於有線電視)、光纖等都屬於物理層的概念。物理層的能力決定了最大傳輸速率、傳輸距離、抗干擾性等。集線器(Hub)是工做在物理層的網絡設備,用於雙絞線的鏈接和信號中繼(將已衰減的信號再次放大使之傳得更遠)。

鏈路層有以太網、令牌環網等標準,鏈路層負責網卡設備的驅動、幀同步(就是說從網線上檢測到什麼信號算做新幀的開始)、衝突檢測(若是檢測到衝突就自動重發)、數據差錯校驗等工做。交換機是工做在鏈路層的網絡設備,能夠在不一樣的鏈路層網絡之間轉發數據幀(好比十兆以太網和百兆以太網之間、以太網和令牌環網之間),因爲不一樣鏈路層的幀格式不一樣,交換機要將進來的數據包拆掉鏈路層首部從新封裝以後再轉發。

網絡層的IP協議是構成Internet的基礎。Internet上的主機經過IP地址來標識,Internet上有大量路由器負責根據IP地址選擇合適的路徑轉發數據包,數據包從Internet上的源主機到目的主機每每要通過十多個路由器。路由器是工做在第三層的網絡設備,同時兼有交換機的功能,能夠在不一樣的鏈路層接口之間轉發數據包,所以路由器須要將進來的數據包拆掉網絡層和鏈路層兩層首部並從新封裝。IP協議不保證傳輸的可靠性,數據包在傳輸過程當中可能丟失,可靠性能夠在上層協議或應用程序中提供支持。

 

網絡層負責點到點(point-to-point)的傳輸(這裏的「點」指主機或路由器),而傳輸層負責端到端(end-to-end)的傳輸(這裏的「端」指源主機和目的主機)。傳輸層可選擇TCP或UDP協議。TCP是一種面向鏈接的、可靠的協議,有點像打電話,雙方拿起電話互通身份以後就創建了鏈接,而後說話就好了,這邊說的話那邊保證聽獲得,而且是按說話的順序聽到的,說完話掛機斷開鏈接。也就是說TCP傳輸的雙方須要首先創建鏈接,以後由TCP協議保證數據收發的可靠性,丟失的數據包自動重發,上層應用程序收到的老是可靠的數據流,通信以後關閉鏈接。UDP協議不面向鏈接,也不保證可靠性,有點像寄信,寫好信放到郵筒裏,既不能保證信件在郵遞過程當中不會丟失,也不能保證信件是按順序寄到目的地的。使用UDP協議的應用程序須要本身完成丟包重發、消息排序等工做。

拆包的協議

目的主機收到數據包後,如何通過各層協議棧最後到達應用程序呢?整個過程以下圖所示:

以太網驅動程序首先根據以太網首部中的「上層協議」字段肯定該數據幀的有效載荷(payload,指除去協議首部以外實際傳輸的數據)是IP、ARP仍是RARP協議的數據報,而後交給相應的協議處理。假如是IP數據報,IP協議再根據IP首部中的「上層協議」字段肯定該數據報的有效載荷是TCP、UDP、ICMP仍是IGMP,而後交給相應的協議處理。假如是TCP段或UDP段,TCP或UDP協議再根據TCP首部或UDP首部的「端口號」字段肯定應該將應用層數據交給哪一個用戶進程。IP地址是標識網絡中不一樣主機的地址,而端口號就是同一臺主機上標識不一樣進程的地址,IP地址和端口號合起來標識網絡中惟一的進程。


注意,雖然IP、ARP和RARP數據報都須要以太網驅動程序來封裝成幀,可是從功能上劃分,ARP和RARP屬於鏈路層,IP屬於網絡層。雖然ICMP、IGMP、TCP、UDP的數據都須要IP協議來封裝成數據報,可是從功能上劃分,ICMP、IGMP與IP同屬於網絡層,TCP和UDP屬於傳輸層。

以太網鏈路層數據幀格式:

 

IP數據包頭:

 

IP數據報格式以下:

 

 

參考連接:

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

linux內核學習筆記------ip報文的分片

相關文章
相關標籤/搜索