TCP 傳輸時 sk_buff 的 clone 和 unclone

周一有位朋友咨詢個問題,問題本身不重要,但牽扯出的細節卻是非常有趣。

Linux 內核協議棧的 skb 設計非常高效和精巧,多個 skb 可以指向同一塊 data,這就是 clone,當 data 不止一個 skb 指示時,任何一個 skb 要修改 data 時,必須 unclone,其內部實現就是一個簡單的 cow(copy on write),這就是 unclone,原理如下:
在這里插入圖片描述

clone 和 unclone 的含義簡述如下:

  • clone,復制一個新 skb,指向相同 data,dataref 遞增;
  • unclone,復制一份新 data,原 dataref 遞減,skb 指向新 data;

值得注意的是 skb 的 clone 和 share 的區別:

  • clone,針對 skb 的 data;
  • share,針對 skb 結構體本身;

因此,kfree_skb 就非常清晰了:

  • 先遞減 share 計數,自己是最后一個才繼續釋放 skb 的其它內容;
  • 再遞減 data 的 dataref,自己是最后一個才徹底釋放 data 本身;

至于 skb_copy,pskb_copy,自然就不必說,理解 skb_clone,skb_unclone,kfree_skb 就夠了。
下面是 TCP 傳輸和重傳過程的 clone,unclone 序列:
在這里插入圖片描述

TCP 傳輸和重傳時,紅黑樹(解釋理由)上的本體 skb 結構體始終未變,變的是 clone skb 結構體和 data:

  • 每次傳輸或重傳時,均會 clone 一份本體 skb 實際傳輸,clone 過程本體 skb 的 data 不變,dataref 遞增;
  • 每次重傳時,如果 dataref 大于 1,本體 skb 會 unclone,復制一份 data ,原 data 的 dataref 遞減,回到 1;

unclone 后留下的原 data 并未游離,因為還有上下文引用它們,可能驅動尚未發送完畢,等發完了一般會有中斷通知,那就留給 clone 它的上下文去 kfree 了。

以下是上面知識在處理朋友問題時的一個應用,該應用過程反過來也能加深上面知識的理解和內化。

問題是這樣的。理論上 TCP 重傳時要從重傳隊列 head 開始,可抓包卻發現先重傳了最后 fin,再次重傳時才重傳隊列更前面的 data。

初步猜測是 tlp,過年期間寫過一個關于 tlp 的,詳見 探秘 TCP TLP:從背景到實現。為幫朋友定位問題,我寫了下面的 pdrill 復現腳本,并打開 tlp:

   // 開啟 tlp//sysctl -q net.ipv4.tcp_early_retrans=4 net.ipv4.tcp_recovery=10 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0+0 bind(3, ..., ...) = 0+0 listen(3, 1) = 0+0 < S 0:0(0) win 32792 <mss 1460,sackOK, nop, nop, nop,wscale 7>+0 > S. 0:0(0) ack 1 <mss 1460,nop, nop, sackOK, nop, wscale 7>+0 < . 1:1(0) ack 1 win 257+0 accept(3, ..., ...) = 4+0 write(4, ..., 1360) = 1360+0 close(4) = 0+10 < . 1:1(0) ack 2821 win 257

通篇沒收到任何 ack, 1360 字節數據和 close 的 fin 均要被重傳,會先重傳最后的 fin,但理論上后續重傳 1360 字節時會將 fin 合并帶上,幾乎可以完全復現場景:
在這里插入圖片描述

然而抓包卻是這樣:
在這里插入圖片描述

并沒有合并捎帶 fin。

不合并就不合并了,不是什么大事,本身 tcp collapse 觸發條件就不止一個。但作為 pdrill 本地測試,最基本的場景,立場應該是想讓它 collapse 它就得 collapse,一定是哪里出了問題才導致 1360 字節的報文沒有 collapse fin。

tcp 重傳 collapse 的條件是沒有其它上下文引用該 skb 的 data,即 shinfo->dataref == 1,以下是詳細觀測 skb shinfo->dataref 的腳本:

bpftrace -e '
//kprobe:__tcp_transmit_skb  // 尚未 clone,dataref = 1
//kprobe:__ip_queue_xmit     // 已經 clone,dataref = 2
//kprobe:__dev_queue_xmit
//kprobe:dev_hard_start_xmit
//kprobe:dev_queue_xmit_nit // PACKET 套接字等抓包程序會再次 clone,dataref = 3,處理完成后 dataref = 2
//kprobe:網卡 xmit 回調      // 傳輸完畢由驅動負載 kfree_skb,dataref = 1
{$skb = (struct sk_buff *)arg0;$dev = (struct net_device *)($skb->dev);if (strncmp($dev->name, "tun0", 4) == 0) {$shinfo = (struct skb_shared_info *)($skb->head + $skb->end);$dataref = $shinfo->dataref.counter;printf("tun0 skb=%p dataref=%d\n", $skb, $dataref & 0xffff);}
}
'

結果是到了 tun_net_xmit 時,dataref = 3,但在 dev_hard_start_xmit 時 dataref 還是 2,這中間只有 PACKET 套接字,類似抓包路徑了。而抓包需要 copy data,肯定是需要 clone skb 的。還果然是這個原因:

root@vbox:~# strace -e trace=socket packetdrill ./tlp-with-fin.pkt
...
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6624, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=6626, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) = 6

也可在 packetdrill 運行期間通過 procfs 確認:

root@vbox:~# cat /proc/net/packet
sk               RefCnt Type Proto  Iface R Rmem   User   Inode
00000000dafae456 3      2    0003   0     1 0      0      42872

但即使減少了 PACKET 套接字的 1 個 ref,還有 1 個 ref 直到 tun_net_xmit 也沒釋放,原因在于 packetdrill 并未 read tun0fd。

為了避免這種本地測試工具不處理 read 造成的額外混亂,我覺得 tun 驅動應該增加一個 “低效率” 模式,即 skb 進入 tun 驅動的 tun_net_xmit 回調時直接轉交掉,多一次復制,少一份混亂:

static netdev_tx_t tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{struct tun_struct *tun = netdev_priv(dev);enum skb_drop_reason drop_reason;int txq = skb->queue_mapping;struct netdev_queue *queue;struct tun_file *tfile;int len = skb->len;struct sk_buff *nskb = skb;rcu_read_lock();if (tun-> & 低效) {skb = skb_copy(nskb, GFP_ATOMIC);kfree_skb(nskb);// 這里順帶幫 PACKET 套接字解除一個 ref,實際中不需要atomic_dec(&skb_shinfo(nskb)->dataref);}tfile = rcu_dereference(tun->tfiles[txq]);

再次運行,結果就符合預期了:
在這里插入圖片描述

如果 skb 發到真實網卡,當網卡中斷通知發送完成,該 skb 即可得到釋放,但本地環回的 skb 生命周期可是要長得多,比如 loopback_xmit,直接用 tx 路徑的 skb 調用 netif_rx 了。

不出本機協議棧的數據包環回處理最好在 ndo_start_xmit 中都轉交 copy 一下 skb,畢竟本地處理不要求性能(要求高性能的抓包也不用 skb),這樣可避免循環依賴而傷害到協議棧的固有行為,比如我用 pdrill 幾乎無法驗證 collapse 機制。

多年以前我曾因為 tun 網卡沒有調用 sk_mem_uncharge 導致了通過 tun 網卡的 socket 異常行為,但忘記題目了,文章找不到了。

不管這世界多么無知和操蛋,只要給我一本歷史書,給我一個 TCP 疑難問題,或帶我到一座可以攀登的山腳下,我就馬上元氣爆滿!

浙江溫州皮鞋濕,下雨進水不會胖。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88738.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88738.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88738.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【51單片機】51單片機學習筆記-課程簡介

00. 目錄 文章目錄00. 目錄01. 學習哪種類型的單片機02. 學習單片機方法03. 學習單片機硬件設備04. 學習單片機軟件設備05. 學完單片機能做什么06. 附錄01. 學習哪種類型的單片機 單片機的型號那么多&#xff0c;該如何選擇一款合適的進行學習呢&#xff1f;這里給讀者首推的當…

【Docker基礎】Docker端口映射(-p參數)深度解析與實踐指南

目錄 前言 1 Docker網絡基礎 1.1 Docker網絡模型概述 1.2 容器網絡隔離性 2 端口映射基礎 2.1 端口映射概念 2.2 為什么需要端口映射 3 -p參數詳解 3.1 基本語法 3.2 四種映射格式 3.2.1 完整格式 3.2.2 省略宿主機IP 3.2.3 隨機宿主機端口 3.2.4 指定協議類型 …

2、鴻蒙Harmony Next開發:ArkTS語言

目錄 什么是ArkTS&#xff1f; ArkTS的發展趨勢 ArkTS的定位及約束 ArkTS的對UI的拓展 1、UI描述 2、狀態管理&#xff1a; ArkTS語法基礎 基本知識&#xff1a;聲明 基本知識&#xff1a;類型 基本知識&#xff1a;空安全 基本知識&#xff1a;類型安全與類型推斷 …

【Elasticsearch】function_score

如果你希望在 Elasticsearch 查詢中降低某些特定 `id` 的文檔評分,可以通過 `function_score` 查詢結合 `script_score` 函數來實現。`script_score` 允許你使用自定義腳本對文檔的評分進行調整。 以下是一個示例,展示如何降低某些特定 `id` 的文檔評分: 示例場景 假設我們…

vscode打開stm32CubeIDE的項目的注釋問題

文章目錄 目的是為消除紅色底線打開命令面板&#xff1a;CtrlShiftP 搜索并打開&#xff1a;C/C: Edit Configurations (JSON) 修改并添加。&#xff08;注意里面的版本號&#xff09; {"configurations": [{"name": "Win32","includePath&…

ESP32使用freertos更新lvgl控件內容

LVGL不是線程安全&#xff0c;所有 lv_xxx方法只能在GUI主線程調用。 freertos都是線程池&#xff0c;子線程&#xff0c;不能直接更新lvgl&#xff0c;不然看門狗被觸發&#xff0c;死機。 推薦方法案例&#xff1a; 假如搜索wifi列表得到參數是wifi_options&#xff0c;需要通…

OBOO鷗柏丨滿天星(MTSTAR)多媒體信息發布系統技術解析

初次啟動歡迎您使用鷗柏(OBOO)滿天星(MTSTAR)多媒體信息發布系統&#xff0c;在使用本系統的獨立服務器模式前&#xff0c;我們需要完成設備的一些必須設置教程技術說明。其總體流程分為兩步&#xff1a;錄入本地服務器IP地址->連接網絡您獲取到的OBOO鷗柏滿天星(MTSTAR)液晶…

數據結構:棧、隊列、鏈表

目錄 棧 ?隊列 鏈表 棧 棧數據結構特點&#xff1a;先入棧的數據后出&#xff0c;此數據結構常用的方法有&#xff1a;入棧push、出棧pop、查看棧頂元素peek等&#xff0c;下方示例以數組實現棧結構。 package com.ginko.datastructure; import lombok.extern.slf4j.Slf4j…

Python-難點-uinttest

1 需求要求&#xff1a;unittest.TestCase放在列表中&#xff0c;列表存儲的是腳本文件名import使用動態加載方式&#xff1a;importlib.import_module()unittest.TestLoader使用loadTestsFromModule()2 接口3 示例4 參考資料

開源 python 應用 開發(五)python opencv之目標檢測

最近有個項目需要做視覺自動化處理的工具&#xff0c;最后選用的軟件為python&#xff0c;剛好這個機會進行系統學習。短時間學習&#xff0c;需要快速開發&#xff0c;所以記錄要點步驟&#xff0c;防止忘記。 鏈接&#xff1a; 開源 python 應用 開發&#xff08;一&#xf…

ABP VNext + OpenTelemetry + Jaeger:分布式追蹤與調用鏈可視化

ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680; &#x1f4da; 目錄ABP VNext OpenTelemetry Jaeger&#xff1a;分布式追蹤與調用鏈可視化 &#x1f680;背景與動機 &#x1f31f;環境與依賴 &#x1f4e6;必裝 NuGet 包系統架構概覽…

C語言中整數編碼方式(原碼、反碼、補碼)

在 C 語言中&#xff0c;原碼、反碼、補碼的運算規則與其編碼特性密切相關&#xff0c;核心差異體現在符號位是否參與運算、進位如何處理以及減法是否能轉化為加法等方面。以下是三者的運算規則及特點分析&#xff08;以 8 位整數為例&#xff0c;符號位為最高位&#xff09;&a…

js二維數組如何變為一維數組

在 JavaScript 中&#xff0c;將二維數組轉換為一維數組&#xff08;扁平化&#xff09;有多種方法&#xff0c;可根據數組結構復雜度、性能需求和兼容性選擇。以下是最常用的實現方式&#xff1a; 1. 使用 flat() 方法&#xff08;ES2019&#xff09; MDN釋義&#xff1a;flat…

Claude code在Windows上的配置流程

前言 昨天在服務器上配置好了 Claude code&#xff0c;發現其編碼性能和效率都非常不錯。 然而&#xff0c;嘗試用它修改帶 UI 界面的客戶端程序時頗為不便&#xff0c;因為服務器沒有圖形化界面&#xff0c;無法直接將應用界面直接顯示到開發機上&#xff0c;調試起來頗為不…

手把手教你用YOLOv10打造智能垃圾檢測系統

無需編程基礎&#xff01;手把手教你用YOLOv10打造智能垃圾檢測系統 垃圾分類不再難&#xff0c;AI助手秒識別 你是否曾站在分類垃圾桶前猶豫不決&#xff1f;塑料瓶是可回收還是其他垃圾&#xff1f;外賣餐盒到底該丟哪里&#xff1f;隨著垃圾分類政策推廣&#xff0c;這樣的困…

batchnorm類

1. 偽代碼&#xff1a;2. python代碼&#xff1a;3. 測試&#xff1a;4. 加深理解&#xff1a;以 為例&#xff0c;x3&#xff0c;可見輸出的batchnorm后y0.2627.查看模型記錄的均值及方差&#xff0c;計算y0.286799&#xff0c;理解是大致這樣的計算過程。&#xff08;為什么數…

SpringBoot項目保證接口冪等的五種方法!

1. 冪等概述 1.1 深入理解冪等性 在計算機領域中&#xff0c;冪等&#xff08;Idempotence&#xff09;是指任意一個操作的多次執行總是能獲得相同的結果&#xff0c;不會對系統狀態產生額外影響。在Java后端開發中&#xff0c;冪等性的實現通常通過確保方法或服務調用的結果…

SQL新手入門詳細教程和應用實例

SQL(Structured Query Language)是用于管理和操作關系型數據庫的標準語言。它允許你創建、查詢、更新和刪除數據。本教程將從基礎概念開始,逐步引導你上手SQL,并提供詳細的應用實例。教程基于標準SQL語法,實際使用時需根據數據庫系統(如MySQL、SQLite或PostgreSQL)調整。…

DVWA-LOW級-SQL手工注入漏洞測試(MySQL數據庫)+sqlmap自動化注入-小白必看(超詳細)

首次使用DVWA的靶場&#xff0c;咋們先從最低級別的LOW開始&#xff0c;因為之前玩過一下墨者學院&#xff0c;對sql注入有一點認識和理解&#xff0c;所以先從sql的盲注開始&#xff1b; 1、測試注入點是否存在sql注入的漏洞&#xff1b; &#xff08;1&#xff09;首先我們…

JAVA線程池詳解+學習筆記

1.線程池基礎概念線程池是一種資源復用技術&#xff0c;通過預先創建并管理一組線程&#xff0c;減少頻繁創建和銷毀線程的開銷。核心思想與數據庫連接池、字符串常量池類似&#xff0c;旨在提升系統性能。核心參數解析ThreadPoolExecutor構造函數包含7個關鍵參數&#xff1a;c…