TCP三次握手和四次揮手深入實踐

TCP連接狀態

圖1是TCP三次握手、數據傳輸、四次揮手三個階段的狀態轉移圖,狀態說明如下:

  • LISTEN:偵聽來自客戶端的TCP端口的連接請求
  • SYN-SENT:再發送連接請求后等待匹配的連接請求(如果有大量這樣的狀態包,檢查是否中招了)
  • SYN-RCVD:再收到和發送一個連接請求后等待對方對連接請求的確認(如有大量此狀態,估計被flood攻擊了)
  • ESTABLISHED:代表一個打開的連接
  • FIN-WAIT-1:等待遠程TCP連接中斷請求,或先前的連接中斷請求的確認
  • FIN-WAIT-2:從遠程TCP等待連接中斷請求
  • CLOSE-WAIT:等待從本地用戶發來的連接中斷請求
  • LAST-ACK:等待原來的發向遠程TCP的連接中斷請求的確認(不是什么好東西,此項出現,檢查是否被攻擊)
  • TIME-WAIT:等待足夠的時間以確保遠程TCP接收到連接中斷請求的確認
  • CLOSED:沒有任何連接狀態,連接結束

本文通過實踐例子模擬每個階段的狀態變化。

圖1 TCP連接狀態轉移圖

本文用tcpdump抓包分析TCP連接的交互過程,其中tcpdump Flags含義如下:

  • S=SYN 發起連接標志
  • P=PUSH 傳送數據標志
  • F=FIN 關閉連接標志
  • R=RESET 異常關閉連接,鏈接重置
  • . 表示沒有任何標志,表示返回ack

為什么要三次握手?

  • 如果只有一次握手,Client不能確定與Server的單向連接,更加不能確定Server與Client的單向連接;
  • 如果只有兩次握手,Client確定與Server的單向連接,但是Server不能確定與Client的單向連接;
  • 只有三次握手,Client與Server才能相互確認雙向連接,實現雙工數據傳輸。
圖2 TCP三次握手

為什么要四次揮手?

“三次握手”的第二次握手發送SYN+ACK回應第一次握手的SYN,但是“四次揮手”的第二次揮手只能發送ACK回應第一次揮手的FIN,因為此時Server可能還有數據傳輸給Client,所以Server傳輸數據完成后才能發起第三次揮手發送FIN給Client,等待Client的第四次揮手ACK。

圖3 TCP四次揮手

下面通過例子模擬TCP連接過程的狀態轉移。

情況1:Client啟動服務,Server不啟動服務

Server(127.0.0.1:2017)未啟動服務,如圖4抓包所示,Client(127.0.0.1:32906)發送SYN(localhost.32906 > localhost.2017: Flags [S])啟動TCP連接,Server返回localhost.2017 > localhost.32906: Flags [R.],R=RESET表示異常關閉連接,連接重置。

圖4 Client啟動服務,Server不啟動服務(tcpdump)
情況2:Server啟動服務,Client不啟動服務

如圖5所示,Server(127.0.0.1:2017)監聽2017端口,TCP連接狀態是LISTEN,等待Client發起TCP連接,如圖6所示,tcpdump抓包沒有連接數據。

圖5 Server啟動服務,Client不啟動服務(netstat))
圖6 Server啟動服務,Client不啟動服務(tcpdump)
情況3:Client連接Server

如圖7所示,Server(127.0.0.1:2017)啟動2017端口監聽連接,Client(127.0.0.1:55702)啟動55702端口訪問2017端口的Server,由圖7、圖1和圖2比對,兩端的雙向連接已經建立,TCP狀態轉移到ESTABLISHED。正常情況下,SYN-SENT和SYN-RCVD轉移非常快,netstat難以捕獲,捕獲場景請查看下面異常情況1和異常情況2。
如圖8抓包所示,三行數據對應圖2的三次握手,說明雙向連接已經建立。

圖7 Client連接Server(netstat)
圖8 Client連接Server(tcpdump)
情況4:Client傳輸數據

從圖1和圖9看,數據傳輸過程中Server(127.0.0.1:2017)和Client(127.0.0.1:55866)的TCP連接狀態均為ESTABLISHED。

圖9 Client數據傳輸(netstat)

從圖10紅色方框看,Client(localhost:55866)向Server(localhost:2017)發送數據(localhost.55866 > localhost.2017: Flags [P.]),Server返回ack(localhost.2017 > localhost.55866: Flags [.])確認。

圖10 Client數據傳輸抓包(tcpdump)
情況5:Client斷開連接,Server保持連接

Client(localhost.41416)主動斷掉TCP連接,進入ESTABLISHED -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT狀態,分別順序對應圖11紅色方框的三行記錄,對比圖3,第一行表示第一次揮手,第二行表示第二/三次揮手,第三行表示第四次揮手。

圖11 Client斷開連接,Server保持服務(tcpdump)

對比12和圖3,Client(127.0.0.1:41416)主動斷掉TCP連接,進入ESTABLISHED -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT狀態,等待2MSL,如果2MSL內Server(127.0.0.1:2017)沒有發送FIN,意味著Server已經接收到Client的FIN ACK,1分鐘(/proc/sys/net/ipv4/tcp_fin_timeout:60)超時后Client TCP狀態轉移為CLOSED,符合TCP四次揮手邏輯。

圖12 Client斷開連接,Server保持服務(netstat)
情況6:Server斷開連接,Client保持連接

Server(localhost:2017)主動斷掉TCP連接,根據圖13倒數第二行,對比圖3(Server/Client反著對比),Server發送FIN后TCP狀態由ESTABLISHED轉移到FIN-WAIT-1,根據圖13倒數第一行,Server接收到Client(localhost:40277)發送的FIN ACK,由FIN-WAIT-1轉移到FIN-WAIT-2狀態,如圖14所示,超時后轉移到CLOSED。
如圖14所示,Client(127.0.0.1:40277)回應Server FIN ACK,但是Client未斷開連接,TCP狀態由ESTABLISHED轉移為CLOSE_WAIT,不會由CLOSE_WAIT轉移為LAST-ACK。
如圖15所示,Client(localhost:40277)關閉連接,發送FIN,Client由CLOSE_WAIT -> LAST-ACK -> CLOSED。Server(localhost:2017)已經關閉,故回復localhost.2017 > localhost.40277: Flags [R],R=RESET表示異常關閉連接,連接重置。

圖13 Server斷開連接,Client保持連接(tcpdump)
圖14 Server斷開連接,Client保持連接(netstat)
圖15 Client關閉連接(tcpdump)

正常情況下,SYN-SENT、SYN-RCVD、LAST-ACK這些狀態轉移非常快,netstat很難查看到,如果netstat出現這些狀態,有可能受到攻擊,下面將模擬這些場景。

異常情況1:模擬出現SYN-SENT

從圖2看到,正常情況,三次握手Client的TCP狀態很快轉移到ESTABLISHED,不會長時間停留在SYN_SENT。但是如果Server不返回SYN ACK,Client通過netstat可以查看到SYN_SENT。
1.設置防火墻iptables丟棄發送給Server(127.0.0.1:2017)的數據包,這樣Server不會響應Client發送的SYN而返回SYN ACK。如圖16,把紅色打叉的傳輸過程掐斷,防火墻丟棄Client發送給Server的SYN。

iptables -I INPUT -s 127.0.0.1 -p tcp --dport 2017 -j DROP
圖16 設置防火墻丟棄Client發送的SYN

2.Client(localhost:35996)發送SYN給Server(localhost:2017),如圖17所示,從localhost.35996 > localhost.2017: Flags [S]行記錄看,Client TCP連接狀態轉移到SYN_SENT,防火墻丟棄發送給Server的SYN,Server端也就不會發送SYN ACK給Client,結合圖16和圖18,Client TCP連接狀態不會由SYN_SENT轉移到ESTABLISHED,Server TCP連接狀態不會由LISTEN轉移到SYN_RCVD。
如圖17紅色方框所示,Client端以2的倍數遞增的間隔重試6次,重試時間間隔:1s、2s、4s、8s、16s、32s。如圖18、19所示,Client TCP連接狀態保持在SYN_SENT,超時后轉移到CLOSED,等待時長對應圖17的重試時間。

圖17 模擬出現SYN-SENT(tcpdump)
圖18 Client端TCP連接狀態SYN_SENT等待時長1
圖19 Client端TCP連接狀態SYN_SENT等待時長2

3.客戶端握手超時報錯信息:Connection timed out

Exception in thread "main" java.net.ConnectException: Connection timed out (Connection timed out)
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at java.net.Socket.connect(Socket.java:538)
    at java.net.Socket.<init>(Socket.java:434)
    at java.net.Socket.<init>(Socket.java:211)
    at io.socket.Client1.main(Client1.java:17)
異常情況2:模擬出現SYN-RCVD

從圖2看到,正常情況,Server的TCP連接狀態很快轉移到ESTABLISHED,不會長時間停留在SYN_RCVD。但是如果Client不返回SYN ACK,Server通過netstat可以查看到SYN_RCVD。
1.設置防火墻丟棄Server(127.0.0.1:2017)發送出去的數據包,這樣Client不會響應Server發送的SYN而返回SYN ACK。如圖20,把紅色打叉的傳輸過程掐斷,防火墻丟棄Server發送的SYN&ACK。

iptables -I INPUT -s 127.0.0.1 -p tcp --sport 2017 -j DROP
圖20 設置防火墻丟棄Server發送的SYN&ACK

2.Client(localhost:36436)發送SYN給Server(localhost:2017),Server發送SYN ACK給Client,但是被防火墻丟棄Server發送Client端的SYN&ACK,Client也就不會發送SYN ACK給Server,結合圖20和圖22,Client TCP連接狀態轉移為SYN_SENT,但是不會轉移到ESTABLISHED,Server TCP連接狀態由LISTEN轉移到SYN_RCVD,但是不會轉移到ESTABLISHED。
如圖21紅色方框所示,Client收不到Server 的SYN ACK,從localhost.36436 > localhost.2017: Flags [S.]的行記錄看,Client以2的倍數遞增的間隔重試6次,重試時間間隔:1s、2s、4s、8s、16s、32s。
Server收不到Client發送的SYN ACK,從localhost.2017 > localhost.36436: Flags [S.]的行記錄看,Server端以2的倍數遞增的間隔重試5次,重試時間間隔:1s、2s、4s、8s、16s。
如圖22所示,Client TCP連接狀態保持在SYN_SENT,超時后轉移到CLOSED,等待時長對應圖21的重試時間。Server TCP連接狀態保持在SYN_RCVD,超時后轉移到CLOSED,等待時長對應圖21的重試時間。

圖21 模擬出現SYN-RCVD(tcpdump)
異常情況3:模擬出現FIN_WAIT1

從圖3看到,正常情況,四次握手Client斷開連接后,TCP連接狀態很快轉移到FIN_WAIT2,不會長時間停留在FIN_WAIT1。但是如果Server不返回FIN ACK,Client通過netstat可以查看到FIN_WAIT1。
1.Client在斷開連接之前,設置防火墻丟棄Client發送給Server(127.0.0.1:2017)的數據包,這樣Server不會響應Client發送的FIN而返回FIN ACK。如圖23,把紅色打叉的傳輸過程掐斷,防火墻丟棄Client發送的FIN。

 iptables -I INPUT -s 127.0.0.1 -p tcp --dport 2017 -j DROP
圖23 設置防火墻丟棄Client發送的FIN

2.設置防火墻丟棄Client(127.0.0.1:40604)發送出去的包,Client主動斷開連接,此時Server保持連接,如圖24所示, 從localhost.40604 -> localhost.2017 Flags [F.]的行記錄,Client發送FIN給Server,但是被防火墻丟棄,結合圖23和圖25,Client的TCP狀態轉移過程:ESTABLISHED -> FIN_WAIT1,Client收不到Server發送的FIN ACK, TCP狀態不能由FIN_WAIT1轉移到FIN_WAIT2;Server TCP狀態為ESTABLISHED,Server接收不到Client的FIN而沒有發送FIN ACK, TCP狀態不能由ESTABLISHED轉移到CLOSE_WAIT。
如圖24紅色方框所示,Client收不到Server發送的FIN ACK,從localhost.40604 -> localhost.2017 Flags [F.]行記錄看,Client重試6次,重試時間間隔:1s、1s、2s、3s、6s、14s、26s。
如圖25所示,Client TCP連接狀態保持在FIN_WAIT1,超時后轉移到CLOSED,等待時長對應圖24的重試時間。

圖24 模擬出現FIN_WAIT1(tcpdump)
圖25 TCP連接狀態FIN_WAIT1等待時長
異常情況4:模擬出現LAST-ACK

從圖3看,Server主動斷開連接,實際上此時Server變成客戶端,Client和Server的TCP狀態反著查看TCP四次揮手,Client發送FIN&ACK后CLOSE-WAIT -> LAST-ACK,接收到Server的FIN ACK后LAST-ACK -> CLOSED。
1.Server(127.0.0.1:2017)主動斷開連接后,Client(127.0.0.1:40541)主動斷開連接前,設置防火墻丟棄Client發送出去的包,這樣Server不能接收到Client發送的FIN,Client(127.0.0.1:40541)TCP狀態由CLOSE-WAIT過渡到LAST-ACK,超時由LAST-ACK轉移為CLOSED。如圖26,把紅色打叉的傳輸過程掐斷,防火墻丟棄Client FIN。

iptables -I INPUT -s 127.0.0.1 -p tcp --sport 40541 -j DROP
圖26 設置防火墻丟棄Client FIN

2.Server(127.0.0.1:2017)主動斷開連接,此時Client(127.0.0.1:40541)保持連接,如圖27所示, localhost.2017 > localhost.40541 Flags [F.],Server發送FIN給Client,結合圖26和圖28,Server的TCP狀態轉移過程:ESTABLISHED -> FIN_WAIT1 -> FIN_WAIT2,Client的TCP狀態由ESTABLISHED轉移為CLOSE_WAIT。
如圖27紅色方框所示,因為Client(localhost:40541)發給Server的FIN被防火墻丟棄,所以Client收不到Server發送的FIN ACK,從localhost.40541 > localhost.2017 Flags [F.]行記錄看,Client重試7次,重試時間間隔:1s、1s、1s、4s、6s、13s、26s。
設置防火墻丟棄Client(127.0.0.1:40541)發送出去的包,緊接著Client主動斷開連接,結合圖26和圖28,Client的TCP狀態由CLOSE_WAIT轉移為LAST_ACK,因為Server(127.0.0.1:2017)接收不到Client發送的FIN(已被防火墻丟棄),也就不能給Client發送FIN ACK,LAST_ACK不能直接轉移為CLOSED,超時后LAST_ACK -> CLOSED,超時時長對應圖27的重試時長。

圖27 模擬出現LAST-ACK(tcpdump)
圖28 TCP連接狀態LAST-ACK等待時長(netstat)

參考文檔

tcp 三次握手和四次斷連深入分析:連接狀態和socket API的關系

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容