banner
jzman

jzman

Coding、思考、自觉。
github

Wiresharkの分析によるTCPプロトコルの検証

任何技能的沿程都需要刻意練習,此外就是十年如一日的堅持。

最近特意書梳理了一下 TCP 相關的知識,並通過抓包進行了驗證,並分析了從 TCP 建立連接到端來連接的全過程,相信以前和我一樣上課沒懂的看完這篇文章應該差不多懂了。

TCP 提供的是一種面向連接的、可靠的字節流服務,也就是說兩個 TCP 的應用在交換數據之前必須建立一個 TCP 連接,且在一個 TCP 連接中僅有兩方進行通信,TCP 和 UDP 都使用相同的網絡層。

使用 TCP 發送數據時,數據會被分割成 TCP 認為最適合發送的數據塊,這一點與 UDP 不同,UDP 產生的數據報的長度不變,這個數據塊稱之為報文段 (segment),每個報文段的初始化序列號 ISN (Initial Sequence Number) 都是根據一定算法隨機生成的,當然這個序列號也是該報文段第一個數據字節的數據編號。本文將從以下幾個方面介紹 TCP 協議:

  1. TCP 協議的數據格式
  2. TCP 如何建立連接
  3. TCP 如何斷開連接
  4. TCP 狀態變遷圖
  5. Wireshark 分析驗證
  6. 為什麼 SYN 和 FIN 會占一個序列號

TCP 的數據格式#

TCP 數據被封裝在 IP 數據報中,如下圖所示:

image

  • 源端口 (Source Port):數據發送方的端口;
  • 目的端口 (Destination Port):數據接收方的端口;
  • 序列號 (Sequenece number):16 位占 4 個字節,用來標識從 TCP 發端到 TCP 收端發送的數據字節流,其值是該報文段第一個數據字節的數據編號,這個序列號號是 32 位的無符號數,序列號到達 2^32 -1 則又從 0 開始;
  • 確認號 (Acknowledgment number):16 位占 4 個字節,指的是期待接收的數據字節的數據編號,也就是上次報文段最後一個數據字節編號加 1 的值;
  • SYN:標誌位,同步序號用來發起一個 TCP 連接,置 SYN = 1;
  • ACK:標誌位,確認序號有效,置 ACK = 1;
  • RST:標誌位,重建連接,置 RST = 1;
  • FIN:標誌位,發端完成發送任務,希望斷開連接,置 FIN = 1;
  • URG:標誌位,緊急指針有效,置 URG = 1;
  • PSH:標誌位,接收方應該儘快把這個數據交給應用層,置 PSH = 1;

TCP 如何建立連接#

在 TCP 報文段中包含源端口號和目的端口號,用於尋找發送端和接收端的應用進程,再加上 IP 首部的源 IP 地址和目標 IP 地址唯一確定一個 TCP 連接,這就保證了客戶端與服務端之間通信的可能,也是建立 TCP 連接的基礎。

此外,這裡要明確每個報文段的初始序列號 ISN (Initial Sequence Number) 都是根據一定算法隨機生成的,各不相同,此外為保證建立連接標誌位 SYN 佔一個序列號,會在後文進一步分析。

TCP 通過三次握手建立連接過程如下:

  1. 客戶端請求連接時發送一個報文段 seq = x,置標誌位 SYN = 1, 向服務端發起一個 TCP 連接,服務端根據 SYN = 1 知道客戶端在請求建立連接;
  2. 服務端收到後會向客戶端請求確認,隨後發送一個報文段 seq = y, 置標誌位 ACK = 1SYN = 1,將確認序號 ack 設置為客戶端的序列號加 1 ,即 ack = x + 1
  3. 客戶端收到服務端後確定 ack 是否是客戶端上一次發的報文段的序列號加 1,即滿足 ack = x + 1,正確則向服務端發送一個報文段 seq = x + 1,置標誌位 ack = y + 1,服務端收到後該客戶端和服務端的 TCP 連接就建立了,可以互相通信了。

TCP 通過三次握手建立連接圖示如下:

image

肯定都知道 TCP 是通過三次握手建立連接,那麼更好的理解這個建立連接的過程呢?

實際上 TCP 連接的建立上是兩個主機 “互喊” 對方要進行通信的過程,無論是客戶端還是服務端過程都是一樣的,都是發送 [SYN] 包請求建立連接,然後等待對應主機發送 ACK 應答這次請求,整個過程兩次請求連接、兩次應答對方請求,誰都正確應答則成功建立鏈接,其中第二次握手的時候可以拆分為兩個過程:

  1. 服務端發送 ACK 報文段應答客戶端的請求;
  2. 服務端發送 SYN 報文段向客戶端請求建立連接。

顯然這兩個過程目標都是客戶端,所以合在一起了,這樣兩個主機通過 “互喊” 就建立 TCP 連接了,兩個主機分別應答的時候的確認號就是對應主機之前發送報文段的序列號加 1,即 ack = seq + 1

TCP 如何斷開連接#

在介紹如何斷開連接之前,先得了解 TCP 的半關閉狀態。

TCP 提供了在結束它的發送後還能接收另一端的能力,這就是 TCP 的半關閉狀態,舉個例子就是:客戶端完成了數據傳送任務,發送一個標誌位 FIN = 1 的報文段給服務端,此時客戶端沒有了數據發送能力,但是還有接收服務端數據的能力,直到服務端應答一個標誌位 FIN = 1 的報文段給客戶端,至此 TCP 斷開連接。此外為保證斷開連接標誌位 FIN 佔一個序列號,會在後文進一步分析。

正是因為 TCP 的半關閉狀態,才是的 TCP 斷開連接需要四次握手,實際上 TCP 斷開連接的過程也是兩個主機 “互喊” 結束的過程,只有兩主機都對對方斷開連接的請求應答,整個 TCP 連接才徹底斷開。

在上文中我們知道 TCP 建立連接第二次握手過程可以分為兩個階段,最終實現上合在一起了,同樣在 TCP 斷開連接過程中,這個過程不能合併發送的原因就是 TCP 的半關閉狀態,這種半關閉狀態有其應用的可能性,故在 TCP 斷開連接的過程中是完成四次揮手才能徹底斷開 TCP 的連接。

TCP 通過四次揮手斷開連接的過程如下:

  1. 客戶端完成發送任務後,向服務端發送一個報文段 seq = m,置標誌位 FIN = 1 ,確認序列號 ack 設置為服務端發送的上一個報文段的序列號加 1,告訴服務端要斷開連接;
  2. 服務端收到客戶端要斷開連接的報文段之後,向客戶端應答一個報文段 seq = n,置標誌位 ACK = 1,將確認序號 ack 設置為客戶端發送的上一個報文段的序列號加 1 ,即 ack = m + 1,客戶端正確收到該報文段就單向斷開了與服務段的連接,進入半關閉狀態,也就是只能接收服務端的數據,不能向服務端發送數據了;
  3. 服務端完成發送任務後,向客戶端發送一個報文段 seq = n + 1,置標誌位 FIN = 1,確認序號 ack 設置為客戶端發送的上一個報文段的序列號加 1,即 ack = m + 1,告訴客戶端要斷開連接;
  4. 客戶端收到服務端要斷開連接的報文段之後,向服務端應答一個報文段 seq = m + 1,置標誌位 ACK = 1,將確認序號 ack 設置為服務端發送的上一個報文段的序列號加 1 ,即 ack = n + 1,服務端正確收到該報文段就斷開了與客戶端的連接,此時客戶端和服務端就徹底斷開了連接。

下面看一下 TCP 斷開連接的圖示:

image

Wireshark 分析驗證#

打開 Wireshark 抓包可以對以上內容進行驗證,如果只是驗證下 TCP 連接和斷開的這個過程,只需選定對應網卡,開始抓包即可,然後打開瀏覽器訪問幾個頁面,正常情況下就會抓到對應的網絡包,之後可以在顯示過濾器中輸入 tcp 過濾 TCP 協議,隨便選擇一個,右鍵選擇追蹤流、TCP 流查看該 TCP 連接的相關信息,如下:

image

具體就不分析了,這個 TCP 連接沒有發送過數據,也正好便於分析 TCP 建立連接和斷開連接的過程。

TCP 狀態變遷圖#

TCP 在連接一直到斷開的過程中共有 11 種狀態,附上一張 TCP 的狀態變遷圖,如下:

image

為什麼 SYN 和 FIN 會占一個序列號#

在前面的分析中 SYN 和 FIN 都各佔一個序列號,對應到序列號的定義上則是該報文段攜帶 1 字節的數據,那麼下一個報文段的序列號則是上一個報文段的序列號加 1。

以 TCP 通過三次握手建立連接為例,正常情況下,當客戶端發送一個報文段 SYN = 1,seq = x 請求建立連接,服務端收到客戶端發送的報文段之後要應答客戶端的請求,即服務端發送一個報文段 ACK = 1,ack = x + 1,其中 ACK= 1 表示收到了收到了客戶端的連接請求,確認序號 ack = x + 1 表示已經收到序列號為 x 的報文段了,期待收到的下一個報文段的序列號是 x + 1,顯然服務端應答的客戶端請求連接的序號為 x 的報文段。

如果 SYN 不佔一個序列號,當服務端收到客戶端請求建立連接的報文段時,服務端應答的一個報文段 AXK = 1,ack = x,根據確認序列號 ack 的定義 ack = x 表示已經收到已經收到了序列號為 x - 1 的報文段,那麼就無法確認客戶端請求連接的報文段了,進而 TCP 不能正常完成三次握手,也就無法建立 TCP 連接了。

當然 FIN 也是同樣的道理,所以在 TCP 協議中 SYN 和 FIN 都是各佔一個序列號,如有錯誤還望大家指正。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。