面試官:為什麼 TCP 三次握手期間,客戶端和服務端的初始化序列號要求不一樣?

小林coding發表於2022-01-10

大家好,我是小林。

為什麼 TCP 三次握手期間,客戶端和服務端的初始化序列號要求不一樣的呢?

接下來,我一步一步給大家講明白,我覺得應該有不少人會有類似的問題,所以今天在肝一篇!

正文

為什麼 TCP 三次握手期間,為什麼客戶端和服務端的初始化序列號要求不一樣的呢?

主要原因是為了防止歷史報文被下一個相同四元組的連線接收。

TCP 四次揮手中的 TIME_WAIT 狀態不是會持續 2 MSL 時長,歷史報文不是早就在網路中消失了嗎?

是的,如果能正常四次揮手,由於 TIME_WAIT 狀態會持續 2 MSL 時長,歷史報文會在下一個連線之前就會自然消失。

但是來了,我們並不能保證每次連線都能通過四次揮手來正常關閉連線。

假設每次建立連線,客戶端和服務端的初始化序列號都是從 0 開始:

圖片圖片

過程如下:

  • 客戶端和服務端建立一個 TCP 連線,在客戶端傳送資料包被網路阻塞了,而此時服務端的程式重啟了,於是就會傳送 RST 報文來斷開連線。
  • 緊接著,客戶端又與服務端建立了與上一個連線相同四元組的連線;
  • 在新連線建立完成後,上一個連線中被網路阻塞的資料包正好抵達了服務端,剛好該資料包的序列號正好是在服務端的接收視窗內,所以該資料包會被服務端正常接收,就會造成資料錯亂。

可以看到,如果每次建立連線,客戶端和服務端的初始化序列號都是一樣的話,很容易出現歷史報文被下一個相同四元組的連線接收的問題。

客戶端和服務端的初始化序列號不一樣不是也會發生這樣的事情嗎?

是的,即使客戶端和服務端的初始化序列號不一樣,也會存在收到歷史報文的可能。

但是我們要清楚一點,歷史報文能否被對方接收,還要看該歷史報文的序列號是否正好在對方接收視窗內,如果不在就會丟棄,如果在才會接收。

如果每次建立連線客戶端和服務端的初始化序列號都「不一樣」,就有大概率因為歷史報文的序列號「不在」對方接收視窗,從而很大程度上避免了歷史報文,比如下圖:

圖片圖片

相反,如果每次建立連線客戶端和服務端的初始化序列號都「一樣」,就有大概率遇到歷史報文的序列號剛「好在」對方的接收視窗內,從而導致歷史報文被新連線成功接收。

所以,每次初始化序列號不一樣能夠很大程度上避免歷史報文被下一個相同四元組的連線接收,注意是很大程度上,並不是完全避免了。

那客戶端和服務端的初始化序列號都是隨機的,那還是有可能隨機成一樣的呀?

RFC793 提到初始化序列號 ISN 隨機生成演算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

  • M是一個計時器,這個計時器每隔4毫秒加1。
  • F 是一個 Hash 演算法,根據源IP、目的IP、源埠、目的埠生成一個隨機數值,要保證 hash 演算法不能被外部輕易推算得出。

可以看到,隨機數是會基於時鐘計時器遞增的,基本不可能會隨機成一樣的初始化序列號。

懂了,客戶端和服務端初始化序列號都是隨機生成的話,就能避免連線接收歷史報文了。

是的,但是也不是完全避免了。

為了能更好的理解這個原因,我們先來了解序列號(SEQ)和初始序列號(ISN)。

  • 序列號,是 TCP 一個頭部欄位,標識了 TCP 傳送端到 TCP 接收端的資料流的一個位元組,因為 TCP 是面向位元組流的可靠協議,為了保證訊息的順序性和可靠性,TCP 為每個傳輸方向上的每個位元組都賦予了一個編號,以便於傳輸成功後確認、丟失後重傳以及在接收端保證不會亂序。序列號是一個 32 位的無符號數,因此在到達 4G 之後再迴圈回到 0
  • 初始序列號,在 TCP 建立連線的時候,客戶端和服務端都會各自生成一個初始序列號,它是基於時鐘生成的一個隨機數,來保證每個連線都擁有不同的初始序列號。初始化序列號可被視為一個 32 位的計數器,該計數器的數值每 4 微秒加 1,迴圈一次需要 4.55 小時

給大家抓了一個包,下圖中的 Seq 就是序列號,其中紅色框住的分別是客戶端和服務端各自生成的初始序列號。

圖片圖片

圖片

通過前面我們知道,序列號和初始化序列號並不是無限遞增的,會發生迴繞為初始值的情況,這意味著無法根據序列號來判斷新老資料

不要以為序列號的上限值是 4GB,就以為很大,很難發生迴繞。在一個速度足夠快的網路中傳輸大量資料時,序列號的迴繞時間就會變短。如果序列號迴繞的時間極短,我們就會再次面臨之前延遲的報文抵達後序列號依然有效的問題。

為了解決這個問題,就需要有 TCP 時間戳。tcp_timestamps 引數是預設開啟的,開啟了 tcp_timestamps 引數,TCP 頭部就會使用時間戳選項,它有兩個好處,一個是便於精確計算 RTT ,另一個是能防止序列號迴繞(PAWS)

試看下面的示例,假設 TCP 的傳送視窗是 1 GB,並且使用了時間戳選項,傳送方會為每個 TCP 報文分配時間戳數值,我們假設每個報文時間加 1,然後使用這個連線傳輸一個 6GB 大小的資料流。

圖片圖片

32 位的序列號在時刻 D 和 E 之間迴繞。假設在時刻B有一個報文丟失並被重傳,又假設這個報文段在網路上繞了遠路並在時刻 F 重新出現。如果 TCP 無法識別這個繞回的報文,那麼資料完整性就會遭到破壞。

使用時間戳選項能夠有效的防止上述問題,如果丟失的報文會在時刻 F 重新出現,由於它的時間戳為 2,小於最近的有效時間戳(5 或 6),因此防迴繞序列號演算法(PAWS)會將其丟棄。

防迴繞序列號演算法要求連線雙方維護最近一次收到的資料包的時間戳(Recent TSval),每收到一個新資料包都會讀取資料包中的時間戳值跟 Recent TSval 值做比較,如果發現收到的資料包中時間戳不是遞增的,則表示該資料包是過期的,就會直接丟棄這個資料包

懂了,客戶端和服務端的初始化序列號都是隨機生成,能很大程度上避免歷史報文被下一個相同四元組的連線接收,然後又引入時間戳的機制,從而完全避免了歷史報文被接收的問題。

嗯嗯,沒錯。

相關文章