從TCP三次握手提及--淺析TCP協議中的疑難雜症(1)

版權聲明:本文由黃日成原創文章,轉載請註明出處: 
文章原文連接:https://www.qcloud.com/community/article/73linux

來源:騰雲閣 https://www.qcloud.com/community緩存

 

說到TCP協議,相信你們都比較熟悉了,對於TCP協議總能說個一二三來,可是TCP協議又是一個很是複雜的協議,其中有很多細節點讓人頭疼點。本文就是來講說這些頭疼點的,淺談一些TCP的疑難雜症。那麼從哪提及呢?固然是從三次握手和四次揮手提及啦,可能你們都知道TCP是三次交互完成鏈接的創建,四次交互來斷開一個鏈接,那爲何是三次握手和四次揮手呢?反過來不行嗎?服務器

1. 疑症(1)TCP的三次握手、四次揮手

下面兩圖你們再熟悉不過了,TCP的三次握手和四次揮手見下面左邊的」TCP創建鏈接」、」TCP數據傳送」、」TCP斷開鏈接」時序圖和右邊的」TCP協議狀態機」

TCP三次握手、四次揮手時序圖

TCP協議狀態機cookie

要弄清TCP創建鏈接須要幾回交互才行,咱們須要弄清創建鏈接進行初始化的目標是什麼。TCP進行握手初始化一個鏈接的目標是:分配資源、初始化序列號(通知peer對端個人初始序列號是多少),知道初始化鏈接的目標,那麼要達成這個目標的過程就簡單了,握手過程能夠簡化爲下面的四次交互:tcp

1)client端首先發送一個SYN包告訴Server端個人初始序列號是X。
2)Server端收到SYN包後回覆給client一個ACK確認包,告訴client說我收到了。
3)接着Server端也須要告訴client端本身的初始序列號,因而Server也發送一個SYN包告訴client個人初始序列號是Y。
4)Client收到後,回覆Server一個ACK確認包說我知道了。spa

整個過程4次交互便可完成初始化,可是,細心的同窗會發現兩個問題:
[1]. Server發送SYN包是做爲發起鏈接的SYN包,仍是做爲響應發起者的SYN包呢?怎麼區分?比較容易引發混淆
[2].Server的ACK確認包和接下來的SYN包能夠合成一個SYN ACK包一塊兒發送的,不必分別單獨發送,這樣省了一次交互同時也解決了問題[1]. 這樣TCP創建一個鏈接,三次握手在進行最少次交互的狀況下完成了Peer兩端的資源分配和初始化序列號的交換。server

大部分狀況下創建鏈接須要三次握手,也不必定都是三次,有可能出現四次握手來創建鏈接的。以下圖,當Peer兩端同時發起SYN來創建鏈接的時候,就出現了四次握手來創建鏈接(對於有些TCP/IP的實現,可能不支持這種同時打開的狀況)。
隊列

在三次握手過程當中,細心的同窗可能會有如下疑問:
(2). 初始化序列號X、Y是能夠是寫死固定的嗎,爲何不能呢?
(3). 假如Client發送一個SYN包給Server後就掛了或是無論了,這個時候這個鏈接處於什麼狀態呢?會超時嗎?爲何呢?ip

TCP進行斷開鏈接的目標是:回收資源、終止數據傳輸。因爲TCP是全雙工的,須要Peer兩端分別各自拆除本身通向Peer對端的方向的通訊信道。這樣須要四次揮手來分別拆除通訊信道,就比較清晰明瞭了。資源

1)Client發送一個FIN包來告訴Server我已經沒數據須要發給Server了。
2)Server收到後回覆一個ACK確認包說我知道了。
3)而後server在本身也沒數據發送給client後,Server也發送一個FIN包給Client告訴Client我也已經沒數據發給client了。
4)Client收到後,就會回覆一個ACK確認包說我知道了。
到此,四次揮手,這個TCP鏈接就能夠徹底拆除了。在四次揮手的過程當中,細心的同窗可能會有如下疑問:

(4). Client和Server同時發起斷開鏈接的FIN包會怎麼樣呢,TCP狀態是怎麼轉移的?
(5). 左側圖中的四次揮手過程當中,Server端的ACK確認包能不能和接下來的FIN包合併成一個包呢,這樣四次揮手就變成三次揮手了。
(6). 四次揮手過程當中,首先斷開鏈接的一端,在回覆最後一個ACK後,爲何要進行TIME_WAIT呢(超時設置是 2*MSL,RFC793定義了MSL爲2分鐘,Linux設置成了30s),在TIME_WAIT的時候又不能釋放資源,白白讓資源佔用那麼長時間,能不能省了TIME_WAIT呢,爲何?

2. 疑症(2),TCP鏈接的初始化序列號可否固定

若是初始化序列號(縮寫爲ISN:Inital Sequence Number)能夠固定,咱們來看看會出現什麼問題。假設ISN固定是1,Client和Server創建好一條TCP鏈接後,Client連續給Server發了10個包,這10個包不知怎麼被鏈路上的路由器緩存了(路由器會毫無先兆地緩存或者丟棄任何的數據包),這個時候碰巧Client掛掉了,而後Client用一樣的端口號從新連上Server,Client又連續給Server發了幾個包,假設這個時候Client的序列號變成了5。接着,以前被路由器緩存的10個數據包所有被路由到Server端了,Server給Client回覆確認號10,這個時候,Client整個都很差了,這是什麼狀況?個人序列號纔到5,你怎麼給個人確認號是10了,整個都亂了。
RFC793中,建議ISN和一個假的時鐘綁在一塊兒,這個時鐘會在每4微秒對ISN作加一操做,直到超過2^32,又從0開始,這須要4小時纔會產生ISN的迴繞問題,這幾乎能夠保證每一個新鏈接的ISN不會和舊的鏈接的ISN產生衝突。這種遞增方式的ISN,很容易讓攻擊者猜想到TCP鏈接的ISN,如今的實現大可能是在一個基準值的基礎上進行隨機的。

3. 疑症(3),初始化鏈接的SYN超時問題

Client發送SYN包給Server後掛了,Server回給Client的SYN-ACK一直沒收到Client的ACK確認,這個時候這個鏈接既沒創建起來,也不能算失敗。這就須要一個超時時間讓Server將這個鏈接斷開,不然這個鏈接就會一直佔用Server的SYN鏈接隊列中的一個位置,大量這樣的鏈接就會將Server的SYN鏈接隊列耗盡,讓正常的鏈接沒法獲得處理。目前,Linux下默認會進行5次重發SYN-ACK包,重試的間隔時間從1s開始,下次的重試間隔時間是前一次的雙倍,5次的重試時間間隔爲1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s都知道第5次也超時了,因此,總共須要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP纔會把斷開這個鏈接。因爲,SYN超時須要63秒,那麼就給攻擊者一個攻擊服務器的機會,攻擊者在短期內發送大量的SYN包給Server(俗稱 SYN flood 攻擊),用於耗盡Server的SYN隊列。對於應對SYN 過多的問題,linux提供了幾個TCP參數:tcp_syncookies、tcp_synack_retries、tcp_max_syn_backlog、tcp_abort_on_overflow 來調整應對。

4. 疑症(4) ,TCP的Peer兩端同時斷開鏈接

由上面的」TCP協議狀態機 「圖能夠看出,TCP的Peer端在收到對端的FIN包前發出了FIN包,那麼該Peer的狀態就變成了FIN_WAIT1,Peer在FIN_WAIT1狀態下收到對端Peer對本身FIN包的ACK包的話,那麼Peer狀態就變成FIN_WAIT2,Peer在FIN_WAIT2下收到對端Peer的FIN包,在確認已經收到了對端Peer所有的Data數據包後,就響應一個ACK給對端Peer,而後本身進入TIME_WAIT狀態;可是若是Peer在FIN_WAIT1狀態下首先收到對端Peer的FIN包的話,那麼該Peer在確認已經收到了對端Peer所有的Data數據包後,就響應一個ACK給對端Peer,而後本身進入CLOSEING狀態,Peer在CLOSEING狀態下收到本身的FIN包的ACK包的話,那麼就進入TIME WAIT 狀態。因而,TCP的Peer兩端同時發起FIN包進行斷開鏈接,那麼兩端Peer可能出現徹底同樣的狀態轉移 FIN_WAIT1——>CLOSEING——->TIME_WAIT,也就會Client和Server最後同時進入TIME_WAIT狀態。同時關閉鏈接的狀態轉移以下圖所示:

5. 疑症(5)四次揮手能不能變成三次揮手呢??

答案是可能的。TCP是全雙工通訊,Cliet在本身已經不會在有新的數據要發送給Server後,能夠發送FIN信號告知Server,這邊已經終止Client到對端Server那邊的數據傳輸。可是,這個時候對端Server能夠繼續往Client這邊發送數據包。因而,兩端數據傳輸的終止在時序上是獨立而且可能會相隔比較長的時間,這個時候就必須最少須要2+2 = 4 次揮手來徹底終止這個鏈接。可是,若是Server在收到Client的FIN包後,在也沒數據須要發送給Client了,那麼對Client的ACK包和Server本身的FIN包就能夠合併成爲一個包發送過去,這樣四次揮手就能夠變成三次了(彷佛linux協議棧就是這樣實現的)

6. 疑症(6) TCP的頭號疼症TIME_WAIT狀態

要說明TIME_WAIT的問題,須要解答如下幾個問題

  • Peer兩端,哪一端會進入TIME_WAIT呢?爲何?
    相信你們都知道,TCP主動關閉鏈接的那一方會最後進入TIME_WAIT。那麼怎麼界定主動關閉方呢?是否主動關閉是由FIN包的前後決定的,就是在本身沒收到對端Peer的FIN包以前本身發出了FIN包,那麼本身就是主動關閉鏈接的那一方。對於疑症(4) 中描述的狀況,那麼Peer兩邊都是主動關閉的一方,兩邊都會進入TIME_WAIT。爲何是主動關閉的一方進行TIME_WAIT呢,被動關閉的進入TIME_WAIT能夠不呢?咱們來看看TCP四次揮手能夠簡單分爲下面三個過程

    過程一.主動關閉方發送FIN;
    過程二.被動關閉方收到主動關閉方的FIN後發送該FIN的ACK,被動關閉方發送FIN;
    過程三.主動關閉方收到被動關閉方的FIN後發送該FIN的ACK,被動關閉方等待本身FIN的ACK

    問題就在過程三中,據TCP協議規範,不對ACK進行ACK,若是主動關閉方不進入TIME_WAIT,那麼主動關閉方在發送完ACK就走了的話,若是最後發送的ACK在路由過程當中丟掉了,最後沒能到被動關閉方,這個時候被動關閉方沒收到本身FIN的ACK就不能關閉鏈接,接着被動關閉方會超時重發FIN包,可是這個時候已經沒有對端會給該FIN回ACK,被動關閉方就沒法正常關閉鏈接了,因此主動關閉方須要進入TIME_WAIT以便可以重發丟掉的被動關閉方FIN的ACK。

  • TIME_WAIT狀態是用來解決或避免什麼問題呢?
    TIME_WAIT主要是用來解決如下幾個問題:

    1)上面解釋爲何主動關閉方須要進入TIME_WAIT狀態中提到的: 主動關閉方須要進入TIME_WAIT以便可以重發丟掉的被動關閉方FIN包的ACK。若是主動關閉方不進入TIME_WAIT,那麼在主動關閉方對被動關閉方FIN包的ACK丟失了的時候,被動關閉方因爲沒收到本身FIN的ACK,會進行重傳FIN包,這個FIN包到主動關閉方後,因爲這個鏈接已經不存在於主動關閉方了,這個時候主動關閉方沒法識別這個FIN包,協議棧會認爲對方瘋了,都還沒創建鏈接你給我來個FIN包?,因而回復一個RST包給被動關閉方,被動關閉方就會收到一個錯誤(咱們見的比較多的:connect reset by peer,這裏順便說下 Broken pipe,在收到RST包的時候,還往這個鏈接寫數據,就會收到 Broken pipe錯誤了),本來應該正常關閉的鏈接,給我來個錯誤,很難讓人接受。
    2)防止已經斷開的鏈接1中在鏈路中殘留的FIN包終止掉新的鏈接2(重用了鏈接1的全部的5元素(源IP,目的IP,TCP,源端口,目的端口)),這個機率比較低,由於涉及到一個匹配問題,遲到的FIN分段的序列號必須落在鏈接2的一方的指望序列號範圍以內,雖然機率低,可是確實可能發生,由於初始序列號都是隨機產生的,而且這個序列號是32位的,會迴繞。
    3)防止鏈路上已經關閉的鏈接的殘餘數據包(a lost duplicate packet or a wandering duplicate packet) 干擾正常的數據包,形成數據流的不正常。這個問題和2)相似。

  • TIME_WAIT會帶來哪些問題呢?
    TIME_WAIT帶來的問題注意是源於:一個鏈接進入TIME_WAIT狀態後須要等待2*MSL(通常是1到4分鐘)那麼長的時間才能斷開鏈接釋放鏈接佔用的資源,會形成如下問題

    1) 做爲服務器,短期內關閉了大量的Client鏈接,就會形成服務器上出現大量的TIME_WAIT鏈接,佔據大量的tuple,嚴重消耗着服務器的資源。
    2) 做爲客戶端,短期內大量的短鏈接,會大量消耗的Client機器的端口,畢竟端口只有65535個,端口被耗盡了,後續就沒法在發起新的鏈接了。
    (因爲上面兩個問題,做爲客戶端須要連本機的一個服務的時候,首選UNIX域套接字而不是TCP)

    TIME_WAIT很使人頭疼,不少問題是由TIME_WAIT形成的,可是TIME_WAIT又不是多餘的不能簡單將TIME_WAIT去掉,那麼怎麼來解決或緩解TIME_WAIT問題呢?能夠進行TIME_WAIT的快速回收和重用來緩解TIME_WAIT的問題。有沒一些清掉TIME_WAIT的技巧呢?

文章來自公衆號:小時光茶社(Tech Teahouse)

相關文章
相關標籤/搜索