共計 5081 個字符,預計需要花費 13 分鐘才能閱讀完成。
本篇內容介紹了“Linux Tcp 內核協議棧 Packet Drill 基本原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓丸趣 TV 小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Linux TCP 內核協議棧是一個非常復雜的實現, 不但沉淀了過去 20 多年的設計與實現,同時還在不停的更新。相關的 RFC 與優化工作一直還在進行中。如何研究和學習 Linux TCP 內核協議棧這樣一塊硬骨頭就成了一大難題。
當然最重要也是最基本的還是要閱讀相關的 RFC 和內核中的代碼實現。這個是最最基本的要求。想要馴服 TCP 內核協議棧這樣的 monster 僅僅瀏覽和靜態分析代碼是完全不夠的。因為整個實現中充斥著各種邊界條件和異常的處理(這里有部分原因是因為 TCP 協議本身設計造成的),尤其是 TCP 是有狀態的協議, 很多邊界條件的觸發需要一系列的報文來構成,同時還需要滿足時延等其它條件。
幸運的是 Google 在 2013 年替大家解決了這個難題。Google 在 2013 年發布了 TCP 內核協議棧 測試工具 Packet Drill。這個工具是名副其實,大大的簡化了學習和測試 TCP 內核協議棧的難度。基本可以隨心所欲的觸摸 TCP 內核協議棧的每個細節。Google 的這件工具真是造福了人類。
使用 Packet Drill,用戶可以隨心所欲的構造報文序列,可以指定所有的報文格式 (類似 tcpdump 語法) 然后通過 TUN 接口和目標系統的 TCP 內核協議棧來通信,并對接收到的來自目標系統 TCP 內核協議棧 的報文進行校驗,來確定是否通過測試。再進一步結合 wireshark+Packet Drill 用戶可以獲得最直觀而且具體的體驗。每個報文的每個細節都在掌控之中,溜得飛起,人生瞬間到達了巔峰。
Packet Drill 基本原理
TUN 網絡設備
TUN 是 Linux 下的虛擬網絡設備,可以直通到網絡層。使得應用程序可以直接收發 IP 報文。
Packet Drill 腳本解析 / 執行引擎
首先 Packet Drill 腳本必須要被解析和分解為 通過傳統 socket 接口收發報文的部分和通過 TUN 接口收發報文的部分
在傳統 socket 接口執行對應的動作。
在 TUN 接口執行對應的動作,并對收到的數據進行比對。
在本文中 socket 接口主要扮演的是 server side 的角色。TUN 接口扮演的是 client 的角色。因而我們可以通過 TUN 接口完全掌控我們將要發送出去的 IP 報文,并受到 TCP 協議棧的反饋。并和預設數據進行比對。
Packet Drill 語法簡介
相對時間順序
Packet Drill 每一個事件 (發送 / 接收 / 發起系統調用) 都有相對前后事件的時間便宜。一般使用 +number 來表達。例如 +0 就是在之前的事件結束之后立即發起。+.1 表示為在之前時間結束 0.1 秒之后發起。以此類推
系統調用
Packet Drill 中集成了系統調用,可以通過腳本來完成例如 socket,bind,read,write,getsocketoption 等等系統調用。熟悉 socket 編程的同學很容易理解并使用。
報文的發送與接受
通過內核棧側。可以通過調用系統調用 read/write 來完成報文的發送與接受。但是因為 tcp 是有狀態的協議棧,所以內核棧本身也會根據協議棧所處狀態發送報文(例如 ACK/SACK).
TUN 設備側. Packet Drill 使用 表示發送報文,使用 表示接收報文。
報文的格式描述
報文格式的表達比較類似 tcpdump。例如 S 0:0(0) win 1000 表示 syn 包 win 大小為 1000,同時 tcp 的選項 mss (max segment size)為 1000.
下面我們通過 2 個例子來進一步學習
Handshake and Teardown
我們通過 packet drill 的腳本 復習一下這個經典的流程。
首選來回顧一下 TCP 協議標準的 handshake 和 treardown 流程
接下來我們結合 packet drill 的腳本來重現 整個過程
// 創建 server 側 socket, server 側 socket 將通過內核協議棧來通信 // 注意這里使用的是傳統的系統調用 0 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3 // 設置對應的 socket options // 注意這里使用的是傳統的系統調用 +0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 //bind socket // 注意這里使用的是傳統的系統調用 +0 bind(3, ..., ...) = 0 //listen on the socket // 注意這里使用的是傳統的系統調用 +0 listen(3, 1) = 0 // client 側(TUN)發送 syn 握手的第一個報文 // 注意這里的語法 syn seq 都是相對的,從 0 開始。 +0 S 0:0(0) win 1000 mss 1000 // client 側(TUN)期望收到的報文格式 syn+ack 且 ack.no=ISN(c)+1 // 參考標準流程圖 最后的 ... 表示任何 tcp option 都可以 // 這里是握手的第二步 +0 S. 0:0(0) ack 1 ... // client 側(TUN)發送 ack 報文 seq = ISN(c)+1, ack = ISN(c) +1 // 這里是握手的第三步 +.1 . 1:1(0) ack 1 win 1000 // 握手成功,server 側 socket 返回 established socket // 這時通過 accept 系統調用拿到這個 stream 的 socket +0 accept(3, ..., ...) = 4 //server 側向 stream 寫入 10 bytes // 通過系統調用來完成寫操作 +0 write(4, ..., 10)=10 //client 側期望收到 receive 10 bytes +0 P. 1:11(10) ack 1 //client 側應答 ack 表示接收到 10 bytes +.0 . 1:1(0) ack 11 win 1000 // client 關閉連接 發送 fin 包 +0 F. 1:1(0) ack 11 win 4000 // client 側期望接收到 server 端的對于 fin 的 ack 報文 // 這里由內核協議棧發回。ack = server seq +1, seq = server ack // 參考標準流程圖 +.005 . 11:11(0) ack 2 // server 關閉連接 通過系統調用完成 +0 close(4) = 0 // client 期望接收到的 fin 包格式 +0 F. 11:11(0) ack 2 // client 發送 server 端 fin 包的應答 ack 包 +0 . 2:2(0) ack 12 win 4000
至此,我們純手動的完成了全部的發起和關閉連接的過程。然后我們用 wireshark 來驗證一下
通過結合 packetdrill 與 wireshark 使得每一步都在我們的掌控之中,
SACK
我們將使用 packet drill 來探索一些更為復雜的案例。例如內核協議棧對于 SACK 中各種排列組合的響應。
SACK 是 TCP 協議中優化重傳機制的一個重要選項(該選項一般都在報頭的 options 部分)。
最原始的情況下如果發送方對于 每一個報文接受到 ACK 之后再發送下一個報文,效率將是極為低下的。引入滑動窗口之后允許發送方一次發送多個報文 但是如果中間某個報文丟失 (沒有收到其對應的 ACK) 那么從那個報文開始,其后所有發送過的報文都要被重新發送一次。造成了極大的浪費。
SACK 是一種優化措施,用來避免不必要的重發,告知發送方那些報文已經收到,不用再重發。tcp 的選項中允許帶有最多 3 個 SACK 的 options。也就是三個已經收到了得報文區間信息。說了這么多,還是有一些抽象,我們來看一個具體的示例。
示例說明
在下面的這個例子中,我們需要發送報文的順序是 1,3,5,6,8,4,7,2 也就是測試一下內核 tcp 協議棧的 SACK 邏輯是否如同 RFC 中所描述的一樣。
// 初始化部分建立服務器端 socket, 不再贅述 +0 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 // Client 端發送 握手報文以及接受服務器響應,不再贅述。這里注意激活了 SACK +.1 S 0:0(0) win 50000 mss 1000, sackOK,nop,nop,nop,wscale 7 +0 S. 0:0(0) ack 1 win 32000 mss 1000,nop,nop,sackOK +0 . 1:1(0) ack 1 win 50000 // Server 端就緒 +.1 accept(3, ..., ...) = 4 // 發送報文 1 +0 . 1:1001(1000) ack 1 win 50000 // 發送報文 3, 報文 2 被調整到最后發送 +0 . 2001:3001(1000) ack 1 win 50000 // 發送報文 5 報文 4 被調整亂序 +0 . 4001:5001(1000) ack 1 win 50000 // 發送報文 6 +0 . 5001:6001(1000) ack 1 win 50000 // 發送報文 8 報文 7 被調整亂序 +0 P. 7001:8001(1000) ack 1 win 50000 // 發送報文 4 +0 . 3001:4001(1000) ack 1 win 50000 // 發送報文 7 +0 . 6001:7001(1000) ack 1 win 50000 // 接收到第一個報文的 ACK +0 . 1:1(0) ack 1001 // 接收到 SACK, 報告收到了亂序的報文 3,但是沒報文 2。 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 2001:3001 // 接收到 SACK, 報告收到了亂序的報文 3,報文 5,但是沒報文 2。沒報文 4 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 4001:5001 2001:3001 // 接收到 SACK, 報告收到了亂序的報文 3,報文 5,但是沒報文 2。沒報文 4 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 4001:6001 2001:3001 // 接收到 SACK, 報告收到了亂序的報文 3,報文 5,6, 報文 8,但是沒報文 2。沒報文 4,沒報文 7 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 7001:8001 4001:6001 2001:3001 // 接收到 SACK, 報告收到了亂序的報文 3,4,5,6, 報文 8,但是沒報文 2。沒報文 7 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 2001:6001 7001:8001 // 接收到 SACK, 報告收到了亂序的報文 3,4,5,6,7,8,但是沒報文 2 +0 . 1:1(0) ack 1001 win 31000 nop,nop,sack 2001:8001 // 發送報文 2 至此所有報文完結 +0 . 1001:2001(1000) ack 1 win 50000 +0 . 1:1(0) ack 8001`
隨后我們再來用 wireshark 驗證一下。
果然完全匹配。
Packet Drill 其實還有非常復雜而且更精巧的玩法,可以充分測試各種邊界條件。以后有機會再和大家進一步分享
“Linux Tcp 內核協議棧 Packet Drill 基本原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注丸趣 TV 網站,丸趣 TV 小編將為大家輸出更多高質量的實用文章!