struct sk_buff

sk_buff 是表示資料包的主要網路結構。

基本sk_buff幾何結構

struct sk_buff 本身是一個元資料結構,不包含任何資料包資料。 所有資料都儲存在關聯的緩衝區中。

sk_buff.head 指向主“頭”緩衝區。 頭緩衝區分為兩部分

  • 資料緩衝區,包含標頭,有時還包含有效負載; 這是 skb 中由常用輔助函式(例如 skb_put()skb_pull())操作的部分;

  • 共享資訊 (struct skb_shared_info),其中包含指向 (page, offset, length) 格式的只讀資料的指標陣列。

可選地,skb_shared_info.frag_list 可能會指向另一個 skb。

基本圖可能如下所示

                                ---------------
                               | sk_buff       |
                                ---------------
   ,---------------------------  + head
  /          ,-----------------  + data
 /          /      ,-----------  + tail
|          |      |            , + end
|          |      |           |
v          v      v           v
 -----------------------------------------------
| headroom | data |  tailroom | skb_shared_info |
 -----------------------------------------------
                               + [page frag]
                               + [page frag]
                               + [page frag]
                               + [page frag]       ---------
                               + frag_list    --> | sk_buff |
                                                   ---------

共享skbs和skb克隆

sk_buff.users 是一個簡單的引用計數,允許多個實體保持 struct sk_buff 處於活動狀態。 sk_buff.users != 1 的 skb 被稱為共享 skb(參見 skb_shared())。

skb_clone() 允許快速複製 skb。 沒有複製任何資料緩衝區,但呼叫者會獲得一個新的元資料結構 (struct sk_buff)。 &skb_shared_info.refcount 指示指向同一資料包資料的 skb 數量(即,克隆)。

dataref和無標頭的skbs

傳輸層傳送出它們持有的有效負載 skb 的克隆,以進行重傳。 為了允許堆疊的較低層新增它們的標頭,我們將 skb_shared_info.dataref 分成兩半。 較低的 16 位計數總的引用次數。 較高的 16 位指示有多少引用是僅有效負載的。 skb_header_cloned() 檢查是否允許 skb 新增/寫入標頭。

skb 的建立者(例如,TCP)將其 skb 標記為 sk_buff.nohdr (透過 __skb_header_release())。 從標記的 skb 建立的任何克隆都將使用可用的預留空間填充 sk_buff.hdr_len。 如果只存在一個克隆,則它能夠隨意修改預留空間。 傳輸層中的呼叫順序是

<alloc skb>
skb_reserve()
__skb_header_release()
skb_clone()
// send the clone down the stack

這不是一個非常通用的結構,它取決於傳輸層是否做正確的事情。 實際上,通常只有一個僅有效負載的 skb。 擁有具有不同 hdr_len 長度的多個僅有效負載的 skb 是不可能的。 僅有效負載的 skb 絕不應該離開它們的擁有者。

校驗和資訊

堆疊和網路驅動程式之間的校驗和解除安裝的介面如下...

裝置接收的資料包的校驗和

校驗和驗證的指示在 sk_buff.ip_summed 中設定。 可能的值是

  • CHECKSUM_NONE

    裝置沒有校驗此資料包的校驗和,例如,由於缺乏能力。 資料包包含完整(但未驗證)的校驗和,但 skb->csum 中沒有。 因此,在這種情況下,skb->csum 是未定義的。

  • CHECKSUM_UNNECESSARY

    您正在處理的硬體不計算完整的校驗和(如 CHECKSUM_COMPLETE 中),但它會解析標頭並驗證特定協議的校驗和。 對於此類資料包,如果它們的校驗和沒問題,則它將設定 CHECKSUM_UNNECESSARY。 在這種情況下,sk_buff.csum 仍然是未定義的。 驅動程式或裝置絕不能修改資料包中的校驗和欄位,即使校驗和已驗證。

    CHECKSUM_UNNECESSARY 適用於以下協議

    • TCP:IPv6 和 IPv4。

    • UDP:IPv4 和 IPv6。 裝置可以將 CHECKSUM_UNNECESSARY 應用於 IPv4 或 IPv6 的零 UDP 校驗和,在這種情況下,網路堆疊可能會執行進一步的驗證。

    • GRE:僅當標頭中存在校驗和時。

    • SCTP:指示 SCTP 標頭中的 CRC 已驗證。

    • FCOE:指示 FC 幀中的 CRC 已驗證。

    sk_buff.csum_level 指示在資料包中找到的連續校驗和的數量(減 1),這些校驗和已驗證為 CHECKSUM_UNNECESSARY。 例如,如果裝置接收到 IPv6->UDP->GRE->IPv4->TCP 資料包,並且裝置能夠驗證 UDP(可能為零)、GRE(設定了校驗和標誌)和 TCP 的校驗和,則 sk_buff.csum_level 將設定為 2。 如果裝置只能驗證 UDP 校驗和,而不能驗證 GRE,可能是因為它不支援 GRE 校驗和或因為 GRE 校驗和錯誤,則 skb->csum_level 將設定為 0(在這種情況下不考慮 TCP 校驗和)。

  • CHECKSUM_COMPLETE

    這是最通用的方法。 裝置提供 netif_rx() 看到的 _整個_ 資料包的校驗和,並填寫 sk_buff.csum。 這意味著硬體不需要解析 L3/L4 標頭來實現此目的。

    注意事項

    • 即使裝置僅支援某些協議,但能夠生成 skb->csum,它也必須使用 CHECKSUM_COMPLETE,而不是 CHECKSUM_UNNECESSARY。

    • CHECKSUM_COMPLETE 不適用於 SCTP 和 FCoE 協議。

  • CHECKSUM_PARTIAL

    設定校驗和以解除安裝到裝置,如 CHECKSUM_PARTIAL 的輸出描述中所述。 這可能發生在直接從另一個 Linux OS 接收的資料包上,例如,同一主機上的虛擬化 Linux 核心,或者它可能在 GRO 或遠端校驗和解除安裝的輸入路徑中設定。 為了校驗和驗證的目的,skb->csum_start + skb->csum_offset 引用的校驗和以及資料包中任何前面的校驗和都被認為是已驗證的。 資料包中位於要解除安裝的校驗和之後的任何校驗和都不被認為是已驗證的。

非GSO的傳輸校驗和

堆疊在資料包的 sk_buff.ip_summed 中請求校驗和解除安裝。 值是

  • CHECKSUM_PARTIAL

    驅動程式需要校驗 hard_start_xmit() 從 sk_buff.csum_start 到結尾看到的資料包的校驗和,並在偏移量 sk_buff.csum_start + sk_buff.csum_offset 處記錄/寫入校驗和。 驅動程式可能會驗證 csum_start 和 csum_offset 值是否是給定資料包的長度和偏移量的有效值,但它不應嘗試驗證校驗和是否引用合法的傳輸層校驗和 - 驗證 csum_start 和 csum_offset 是否正確設定是堆疊的職責。

    當堆疊請求資料包的校驗和解除安裝時,驅動程式必須確保正確設定校驗和。 驅動程式可以將校驗和計算解除安裝到裝置,或者呼叫 skb_checksum_help(如果裝置不支援特定校驗和的解除安裝)。

    NETIF_F_IP_CSUMNETIF_F_IPV6_CSUM 正被棄用,取而代之的是 NETIF_F_HW_CSUM。 新裝置應使用 NETIF_F_HW_CSUM 來指示校驗和解除安裝能力。 可以呼叫 skb_csum_hwoffload_help() 以基於網路裝置校驗和能力解析 CHECKSUM_PARTIAL:如果資料包不匹配它們,則呼叫 skb_checksum_help() 或 skb_crc32c_help()(取決於 sk_buff.csum_not_inet 的值,請參見 非IP校驗和 (CRC) 解除安裝)來解析校驗和。

  • CHECKSUM_NONE

    skb 已被協議校驗和,或者不需要校驗和。

  • CHECKSUM_UNNECESSARY

    這與輸出上的校驗和解除安裝的 CHECKSUM_NONE 含義相同。

  • CHECKSUM_COMPLETE

    未用於校驗和輸出。 如果驅動程式觀察到 skbuff 中設定了此值的資料包,則應將資料包視為設定了 CHECKSUM_NONE

非IP校驗和(CRC)解除安裝

NETIF_F_SCTP_CRC

此功能指示裝置能夠解除安裝資料包中的 SCTP CRC。 為了執行此解除安裝,堆疊將相應地設定 csum_start 和 csum_offset,將 ip_summed 設定為 CHECKSUM_PARTIAL,並將 csum_not_inet 設定為 1,以在 skbuff 中指示 CHECKSUM_PARTIAL 引用 CRC32c。 支援 IP 校驗和解除安裝和 SCTP CRC32c 解除安裝的驅動程式必須透過測試 sk_buff.csum_not_inet 的值來驗證為資料包配置了哪個解除安裝; 提供 skb_crc32c_csum_help() 來解析 csum_not_inet 設定為 1 的 skb 上的 CHECKSUM_PARTIAL

NETIF_F_FCOE_CRC

此功能指示裝置能夠解除安裝資料包中的 FCOE CRC。 為了執行此解除安裝,堆疊會將 ip_summed 設定為 CHECKSUM_PARTIAL,並相應地設定 csum_start 和 csum_offset。 請注意,skbuff 中沒有指示 CHECKSUM_PARTIAL 引用 FCOE 校驗和,因此支援 IP 校驗和解除安裝和 FCOE CRC 解除安裝的驅動程式必須透過檢查資料包標頭來驗證為資料包配置了哪個解除安裝。

帶有GSO的輸出校驗和

在 GSO 資料包的情況下(skb_is_gso() 為真),校驗和解除安裝由 gso_type 中的 SKB_GSO_* 標誌暗示。 最明顯的是,如果 gso_type 是 SKB_GSO_TCPV4SKB_GSO_TCPV6,則意味著作為 GSO 操作一部分的 TCP 校驗和解除安裝。 如果正在使用 GSO 解除安裝校驗和,則 ip_summed 是 CHECKSUM_PARTIAL,並且 csum_start 和 csum_offset 都設定為引用要解除安裝的最外層校驗和(使用 UDP 封裝可以解除安裝兩個校驗和)。