實戰 TCP 三次握手,三次握手到底做了些什麼?

開篇


我有一個小弟,做 golang 開發的,有一次我讓他調研一下 ulimit -c 達到上限以後 fd 的行爲,小弟一臉懵逼,問 fd 是什麼。所以說,高級語言封裝的太好,的確是方便了開發人員,但也帶來了對基礎原理的疏遠。

我將本文題目取爲《實戰 TCP 三次握手》,就是要以一種看得見摸得着的方式,讓大家真切感受一下 TCP 建立的過程中,三次握手到底做了些什麼。

關於 TCP 三次握手的理論知識,往上一搜一大片,本文就跳過理論,直接上手。Let’s go。

準備知識


抓一個 TCP 三次握手的包


開啓三個窗口,窗口 1 執行命令:

sudo tcpdump -i lo -nn -s0 -vvv port 7899 -A -X

這個命令用來抓包,抓的是 7899 端口的包。-A 和 -X 是爲了顯示詳細的包內容,方便分析。如果不習慣用 tcpdump 直接分析,也可以使用 wireshark,更加直觀一些。

窗口 2 執行命令:

nc -l 7899

該命令監聽 7899 端口,相當於啓動了一個監聽 7899 端口的 server。當然你要是有興趣的話,可以用代碼寫一個 server。

窗口 3 執行命令:

telnet 127.0.0.1 7899

這個命令是向 127.0.0.1:7899 建立連接,相當於 client 執行 connect 函數。

這個命令一執行,就會連接到 7899 端口上 ,在第一個窗口上立即就會抓到連續的三個包,如下圖所示:

如上步驟,演示了 TCP 建立連接的過程,tcpdump 抓到的三個包,正好就是三次握手。

很多資料講解三次握手時,都會有一幅類似於這樣的圖:

我們對應抓到的三個包來看。

第一個包:

第二個包:

第三個包:

這個步驟,和上圖大致是能一一對應上的。

除了這些簡而易見的信息,還有一些 可能一時半會兒看不懂的東西,比如:

Flags [S], cksum 0xfe30 (incorrect -> 0x2d48), seq 3051156309, win 65495, options [mss 65495,sackOK,TS val 2052530964 ecr 0,nop,wscale 7], length 0

要了解這些東西,需要先了解 TCP 協議棧。

瞭解一下 TCP 協議棧


首先,我們應該知道,一個完整的以太網幀,包含了 ethdr + iphdr + tcpdhr + data + etend

其中,以太網頭佔 14 字節,IP 頭佔 20 字節,TCP 頭佔 20 字節,以太網尾佔 4 字節,應用數據大小不定,但不會超過一個 MTU。

因爲我們只研究三次握手,所以關於以太網幀,瞭解這些就夠了。

我們再來看看具體的 TCP 協議棧:

TCP 協議棧包含:

16 位目的端口號,佔 2 字節

32 位序號(seq),佔 4 字節

32 位確認序號(ack),佔 4 字節

4 位首部長度,佔 0.5 字節

6 位保留位,佔 0.75 字節

6 位標誌位,佔 0.75 字節, 以上:4 位首部長度 + 6 位保留長度 + 6 位標誌位,合計 16 位,計 2 字節

    標誌位包括:

     URG

     ACK

     PSH

     RST

     SYN

     FIN

16 位窗口大小(window size),佔 2 字節

16 位校驗和(checksum),佔 2 字節

16 位緊急指針,佔 2 字節

庖丁解牛,深度剖析 TCP 協議棧的三次握手


有了以上這些知識,我們再來解析上面的協議棧。

第一個包

127.0.0.1.48448 > 127.0.0.1.7899: Flags [S], cksum 0xfe30 (incorrect -> 0x2d48), seq 3051156309, win 65495, options [mss 65495,sackOK,TS val 2052530964 ecr 0,nop,wscale 7], length 0
0x0000:  4510 003c dbf7 4000 4006 60b2 7f00 0001  E..<..@.@.`.....
        0x0010:  7f00 0001 bd40 1edb b5dc f355 0000 0000  .....@.....U....
        0x0020:  a002 ffd7 fe30 0000 0204 ffd7 0402 080a  .....0..........
        0x0030:  7a57 2314 0000 0000 0103 0307            zW#.........

十六進制報文中,前 20 個字節是 IP 協議頭,後 20 個字節雖然也屬於 TCP 協議 ,但是是可選項 option,並非標準的 TCP 協議一定有的內容,所以我們真正關心的內容 , 是下面高亮的部分:

即:

bd40 1edb b5dc f355 0000 0000 a002 ffd7 fe30 0000

接下來,我們逐個字節解析:

16 位源端口, 即 bd40, 轉換成 10 進製爲 48448

16 位目的端口,即 1edb,轉換成 10 進製爲 7899

32 位序號:b5dc f355, 即 3051156309

32 位確認號:0000 0000,即 0

後面三個域由於不是 完整的字節,放在一塊解析:

      標誌位要解釋一下 :

    標誌位哪一位設置爲 1,就代表當前屬於什麼包

    由上面對應關係,可知當前是一個 SYN 包。

16 位窗口大小:ffd7, 即 65495

16 位校驗和,即:fe30

16 位緊急指針,即 0000

由以上內容,我們提取一些關鍵信息:

第一次握手:

第二個包

127.0.0.1.7899 > 127.0.0.1.48448: Flags [S.], cksum 0xfe30 (incorrect -> 0xd4f6), seq 2031501770, ack 3051156310, win 65483, options [mss 65495,sackOK,TS val 2052530964 ecr 2052530964,nop,wscale 7], length 0
        0x0000:  4500 003c 0000 4000 4006 3cba 7f00 0001  E..<..@.@.<.....
        0x0010:  7f00 0001 1edb bd40 7916 41ca b5dc f356  .......@y.A....V
        0x0020:  a012 ffcb fe30 0000 0204 ffd7 0402 080a  .....0..........
        0x0030:  7a57 2314 7a57 2314 0103 0307            zW#.zW#.....

由上面的知識,我們知道 TCP 報文主要是下面這段:

1edb bd40 7916 41ca b5dc f356 a012 ffcb fe30 0000

通過同樣的方法,可以解析出:

第二次握手:

第三個包

127.0.0.1.48448 > 127.0.0.1.7899: Flags [.], cksum 0xfe28 (incorrect -> 0xfbb2), seq 1, ack 1, win 512, options [nop,nop,TS val 2052530964 ecr 2052530964], length 0
        0x0000:  4510 0034 dbf8 4000 4006 60b9 7f00 0001  E..4..@.@.`.....
        0x0010:  7f00 0001 bd40 1edb b5dc f356 7916 41cb  .....@.....Vy.A.
        0x0020:  8010 0200 fe28 0000 0101 080a 7a57 2314  .....(......zW#.
        0x0030:  7a57 2314                                zW#.

TCP 協議部分:

bd40 1edb b5dc f356 7916 41cb 8010 0200 fe28 0000

可以解析出 :

第三次握手:

歸納:

以上內容,如果用比較直觀的方式總結一下 ,大約如下圖:

爲什麼需要三次握手


又回到老生常談的話題:爲什麼需要三次握手?少一次行不行?只握手一次成不成?

在聊這個話題之前,我們引入一下著名科幻小說《三體》中葉文傑教主和三體文明建立聯繫的過程。

首先,葉教主向三體文明發送了一條消息,緊接着,三體人回覆了一條消息,內容是 “不要回答,不要回答,不要回答!” 然後葉教主回覆了這條消息 ,導致地球成功被三體人定位。

不得不說,大劉是懂 TCP 協議的。至少他懂三次握手的重要性。

第一次發消息,你說三體人收到沒有?肯定是收到了的。但這個連接可靠不?明顯不可靠。對於三體人來說,他怎麼知道這個消息是誰發的?發消息的文明是否還活着?對於地球來說,更是如此,他怎麼知道 這條消息對方肯定收到了?又沒有人收?

第二次發消息,三體人差不多要把 ACK 標誌寫在臉上了,就是明明白白告訴你,我這是一個 ACK 消息,你只要不應答這個 ACK,我們這個連接就建立不成,三體小說就全劇終。這就相當於三體人告訴葉教主:我活着,並且能收到你的消息,但是我還不知道你是誰,你能不能收到我這條消息。

所以第三條消息,狡猾的大劉當然不會讓三體就此 game over,就是老葉告訴三體人,我也能收到你的消息,從此以後,咱們是 “同志” 了。

類比三次握手,和這個步驟非常相似,缺少其中任意一環,這個連接都是不可靠的,因爲你不知道對方能不能收到我的消息。所以三次握手,並不是表示連接 “可達” 的,而是表示連接 “可靠” 的。這之間是有區別的,可達很簡單,UDP 也能可達,一次握手也是可達的,但是並不可靠。因爲無法知道這條消息對方能不能正確接收到。只有這樣反覆確認後,才能表示這個連接是可靠的連接。

有槓精肯定表示不服,說理雖然是這麼個理,但是會不會有巧合啊。比如某個服務既是客戶端又是服務端,我在給你發第一次握手的時候,你也恰好在給我發第一次握手,讓我誤以爲你給我的消息是第二次握手的回包,從而建立了一個不可靠的連接?

而槓精之所以是槓精,就是因爲木有腦子。你考慮的問題,咱們祖師爺肯定都考慮到了。

我們在前面分析三次握手的過程的時候,爲什麼要強調 ack = 上一次的 seq+1?就是代表我不僅收到了你的,我還在你的 seq 上加 1,代表我收到的確實是你的消息,這就相當於給這條消息打上了獨一無二的標誌,別人想魚目混珠都不可能。

最後,咱們說說,三體人和葉文傑建立的是 TCP 連接嗎?咳咳,明顯不是。本文只是舉例類比。要知道,葉文傑第一個包可是 broadcast,誰都能收到的。

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