Linux 網絡收包流程

哈嘍大家好,我是鹹魚

我們在跟別人網上聊天的時候,有沒有想過你發送的信息是怎麼傳到對方的電腦上的

又或者我們在上網衝浪的時候,有沒有想過 HTML 頁面是怎麼顯示在我們的電腦屏幕上的

無論是我們跟別人聊天還是上網衝浪,其實都依靠於計算機網絡這項技術

計算機網絡是指將多臺計算機通過通信設備和傳輸介質連接在一起,使得它們之間能夠相互通信、資源共享和協同工作

而計算機之間是通過數據包來實現信息傳輸和信息交換的,數據包是計算機網絡中傳輸數據的基本單位

今天鹹魚將以 Linux 爲例來給大家介紹一下 Linux 是如何實現網絡接收數據包的

網絡協議棧 & 網絡子系統

在正文開始之前,我們先來了解一下 Linux 中的網絡協議模型和網絡子系統

在 Linux 中,Linux 網絡協議棧分成了五層

其中:

網絡子系統是 Linux 內核中的一部分,由多個模塊和驅動程序組成,它負責管理和控制系統的網絡功能以實現網絡通信

通過 Linux 網絡子系統(網絡架構)來實現上述網絡協議模型

其中

收包流程

當 Linux 接收一個數據包的時候,這個包是怎麼經過 Linux 的內核從而被應用程序拿到的呢?

首先數據包到達網卡之後,網卡會校驗接收到的數據包中的目的 MAC 地址是不是自己的 MAC 地址,如果不是的話通常就會丟棄掉

這種只接受發送給自己的數據包(其餘的扔掉)的工作模式稱爲非混雜模式(Non-Promiscuous Mode)

混雜模式(Promiscuous Mode)則是網卡會接收通過網絡傳輸的所有數據包,而不僅僅是發送給它自己的數據包

非混雜模式是網卡默認的工作模式,可以儘可能的保護網絡安全和減少網絡負載

網卡在校驗完 MAC 地址之後還會校驗數據幀(Data Frame)中校驗字段 FCS 來一次確保接收到的數據包是正確的

當網卡接收到數據包時,它將數據包的內容存儲在硬件緩衝區中,然後通過 DMA 將接收到的數據從硬件緩衝區傳輸到系統內存中的指定位置,這個位置通常是一個環形緩衝區( ring buffer)

DMA(直接內存訪問,Direct Memory Access) DMA 是一種數據傳輸技術,允許外設(如網卡、硬盤控制器、顯卡等)直接訪問計算機內存,而無需經過 CPU 通過 DMA 可以大大提高數據傳輸的效率,減輕 CPU 的負擔

當網卡將數據包 DMA 到用於接收的環形緩衝區(rx_ring)之後,就會觸發一個硬中斷來告訴 CPU 數據包收到了

什麼時候會觸發一個硬中斷,可以通過下面的參數來進行配置:

上面的參數配置可以通過下面的命令來查看

# 以 CentOS 7 爲例
ethtool -c <網卡名稱>

當 ring buffer 滿了之後,新來的數據包將給丟棄

ifconfig 查看網卡的時候,可以裏面有個 overruns,表示因爲環形隊列滿而被丟棄的包

CPU 收到硬中斷之後就會停止手中的活,保存上下文,然後去調用網卡驅動註冊的硬中斷處理函數

爲數據包分配 skb_buff ,並將接收到的數據拷貝到 skb_buff 緩衝區中

當一個數據包經過了網卡引起中斷之後,每一個包都會在內存中分配一塊區域,稱爲 sk_buff (套接字緩存,socket buffer)

sk_buff  是 Linux 網絡的一個核心數據結構

網卡的硬中斷處理函數處理完之後驅動先 disable 硬中斷,然後 enable 軟中斷

ps:待 ring buffer 中的所有數據包被處理完成後,enable 網卡的硬中斷,這樣下次網卡再收到數據的時候就會通知 CPU

內核負責軟中斷進程 ksoftirqd 發現有軟中斷請求到來,進行下面的一些操作

# 查看軟中斷進程
[root@localhost ~]# ps -ef | grep ksoftirqd

調用 net_rx_action 函數

它會通過 poll 函數去 rx_ring 中拿數據幀,獲取的時候順便把 rx_ring 上的數據給刪除

static void net_rx_action(struct softirq_action *h)
{
   struct softnet_data *sd = &__get_cpu_var(softnet_data);
   unsigned long time_limit = jiffies + 2;
   int budget = netdev_budget;
   void *have;

   local_irq_disable();

   while (!list_empty(&sd->poll_list)) {
      ......
       n = list_first_entry(&sd->poll_list, struct napi_struct, poll_list);

       work = 0;
       if (test_bit(NAPI_STATE_SCHED, &n->state)) {
           work = n->poll(n, weight);
           trace_napi_poll(n);
      }

       budget -= work;
  }
}

除此之外,poll 函數會把 ring buffer 中的數據包轉換成內核網絡模塊能夠識別的 skb 格式(即 socket kernel buffer

socket kernel buffer (skb) 是 Linux 內核網絡棧處理網絡包(packets)所使用的 buffer,它的類型是 sk_buffer

3、最後進入 netif _receive_skb 處理流程,它是數據鏈路層接收數據幀的最後一關

根據註冊在全局數組 ptype_allptype_base 裏的網絡層數據幀類型去調用第三層協議的接收函數處理

例如對於 ip 包來講,就會進入到 ip_rcv;如果是 arp 包的話,會進入到 arp_rcv

IP 層的入口函數在 ip_rcv 函數,調用 ip_rcv 函數進入三層協議棧

首先會對數據包進行各種檢查(檢查 IP Header),然後調用  netfilter  中的鉤子函數:NF_INET_PRE_ROUTING

netfilter:是 Linux 內核中進行數據包過濾,連接跟蹤(Connect Track),網絡地址轉換(NAT)等功能的主要實現框架

該框架在網絡協議棧處理數據包的關鍵流程中定義了一系列鉤子點(Hook 點),並在這些鉤子點中註冊一系列函數對數據包進行處理

這些註冊在鉤子點的函數即爲設置在網絡協議棧內的數據包通行策略,也就意味着,這些函數可以決定內核是接受還是丟棄某個數據包

NF_INET_PRE_ROUTING 會根據預設的規則對數據包進行判斷並根據判斷結果做相關的處理(修改或者丟棄數據包)

處理完成後,數據包交由 ip_rcv_finish 處理,該函數根據路由判決結果,決定數據包是交由本機上層應用處理,還是需要進行轉發

如果是交由本機處理,則會交由 ip_local_deliver 本地上交流程;如果需要轉發,則交由 ip_forward 函數走轉發流程

傳輸層 TCP 處理入口在  tcp_v4_rcv 函數,首先檢查數據包的 TCP 頭部等信息,確保數據包的完整性和正確性

然後去查找該數據包對應的已經打開的 socket ,如果找不到匹配的 socket,表示該數據包不屬於任何一個已建立的連接,因此該數據包會被丟棄

如果找到了匹配的 socket,TCP 會進一步檢查該 socket 和連接的狀態,如果狀態正常,TCP 會將數據包從內核傳輸到用戶空間,放入 socket 的接收緩衝區(socket receive buffer)

當數據包到達操作系統內核的傳輸層時,應用程序可以從套接字的接收緩衝區(socket receive buffer)中讀取數據包

一般有兩種方式讀取數據,一種是 recvfrom 函數阻塞在那裏等着數據來,這種情況下當 socket 收到通知後,recvfrom 就會被喚醒,然後讀取接收隊列的數據

另一種是通過 epoll 或者 select 監聽相應的 socket,當收到通知後,再調用 recvfrom 函數去讀取接收隊列的數據

總結

網絡模塊可以說是 Linux 內核中最複雜的模塊了

看起來一個簡簡單單的收包過程就涉及到許多內核組件之間的交互,如網卡驅動、協議棧,內核 ksoftirqd 線程等

鹹魚原本打算把收包和發包的流程都寫上的,但是光是寫收包流程就就要了我半條命了 QAQ

等下次有機會把發包的流程也寫一下

總結一下 Linux 網絡收包流程:


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6c0ZZ3ZZZ_ocIqH2iey1lw