Manjusaka

Manjusaka

談談 sk_buff 中一個冷門字段:nohdr

今天遇到一個很有趣的問題,“nohdr 字段到底有什麼用”,在這裡寫個水文簡單記錄一下

正文#

前情提要#

首先來說,不管介紹再冷門的字段,既然涉及到 SKBUFF ,那麼就得先來對 sk_buff 做個簡單的介紹

簡而言之,sk_buff 是 Linux 網路子系統的核心資料結構,從鏈路層到我們最終對資料包的操作,背後都離不開 sk_buff

sk_buff 要完全講解基本就相當於把 Linux 網路系統完全講解了,所以講完是不可能講完的,這輩子都不可能的!

簡單聊幾個關鍵,可能會幫助大家理解我們本文提到的冷門字段 nohdr 的關鍵字段吧

首先來講,最重要的三個字段:datamacnh ,分別代表著當前 sk_buff 的資料區的起始地址,L2 header 的起始地址,L3 Header 的起始地址。用一個圖方便大家理解

sk_buff 三劍客

看了圖的同學可能會有點明白了,實際上在內核裡,也是一層一層的通過指針偏移,不斷的添加新的 header 來處理網路請求。和我們直覺相符。可能有同學會問,我既然知道 L3 Header 的起始地址,IP 之類的 L3 協議的 header 長度是固定的。我是不是可以算出 L4 的偏移,然後手動處理。

Bingo,內核裡有 tcphdr 的資料結構(對應 IP 是 iphdr ),你根據偏移,手動 cast 就可以手動處理。不過詳細做法以後再聊

接著兩個比較重要的字段,是 lendata_len ,這兩個字段都是標識資料長度,但是簡要來說,len 代表著當前 sk_buff 所有資料的長度(即包含當前協議的 header 和 payload),data_len 代表當前有效資料長度(即當前協議 payload 長度)

OK,前情提要到此結束

關於 nohdr#

花開兩朵,各表一支。聊了 sk_buff 一些預備知識,我們來聊一下 nohdr 這個字段。說實話這個字段真的很冷門

首先官方對此有對應描述

The 'nohdr' field is used in the support of TCP Segmentation Offload ('TSO' for short). Most devices supporting this feature need to make some minor modifications to the TCP and IP headers of an outgoing packet to get it in the right form for the hardware to process. We do not want these modifications to be seen by packet sniffers and the like. So we use this 'nohdr' field and a special bit in the data area reference count to keep track of whether the device needs to replace the data area before making the packet header modifications.

嗯,這段屬實有點拗口。首先 TSO 大家肯定有所了解。利用網卡來對大資料包進行分段(具體 Linux 下 GSO/TSO 的實現可以改天鴿一篇文章來聊),那麼在這種情況下,網卡可能會需要對 header 部分進行一點小的修改來完成分片的操作。

但是有些時候,我們對於 L4 這一層的包,我並不需要關心其被修改的 Header ,只需要關心其 payload,那麼怎麼搞。這個時候就是 nohdr 發揮作用了。

在這裡, nohdr 生效還需要配合另外一個字段,datarefdataref 是一個計數字段,其具體的含義是指當前 data 字段所指向的資料區,被多少個 sk_buff 所引用。在這裡有兩種情況

  1. 在 nohdr 為 0 的情況下,dataref 值為資料區的引用計數

  2. 在 nohdr 為 1 的情況下,高 16 位,是資料區中 payload 資料區的引用計數,低 16 位是資料區的引用計數

對此官方有這樣的描述

/* We divide dataref into two halves. The higher 16 bits hold references * to the payload part of skb->data. The lower 16 bits hold references to * the entire skb->data. It is up to the users of the skb to agree on * where the payload starts.

* * All users must obey the rule that the skb->data reference count must be * greater than or equal to the payload reference count.

* * Holding a reference to the payload part means that the user does not * care about modifications to the header part of skb->data.

*/ 
#define SKB_DATAREF_SHIFT 16 #define SKB_DATAREF_MASK ((1 << SKB_DATAREF_SHIFT) - 1)

實際上這裡也不太難理解為什麼這麼設計。首先來說,我們在內核裡去獲取資料包的時候,有些時候不需要去關心具體的 header,只需要關心具體的 payload。 而我們對於 payload 的引用計數,也需要單獨的處理來保證其正確性。這樣確保我們的資料還沒處理完的時候。資料片不會被內核提前釋放。當然這裡需要大家在處理這塊的時候需要保證資料區的引用計數要大於 payload 的引用計數(感覺這裡像約定大於配置的做法?(當然這裡不遵守約定的後果就是你內核 dump 了 2333

在最後,我們的內核也通過 dataref 來在合適的時機釋放資料區的記憶體空間,釋放條件是滿足以下其一即可

  1. !skb->cloned: skb 沒有 被 clone
  2. !atomic_sub_return (skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1, &skb_shinfo (skb)->dataref) 即在 nohdr 為 1 的時候通過 dataref-(1 << SKB_DATAREF_SHIFT) + 1) 判斷是否需要釋放資料區。而 nohdr 為 0 的時候通過 dataref-1 來決定是否需要釋放資料區

總結#

水文差不多就這樣。。nohdr 真的是個很冷門的字段。嗯,因為這篇水文的一些 reference 是在地鐵上查的。。我就懶得列在文章裡了。。差不多這樣。。寫題去了。。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。