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)

圖22 TCP連接狀態SYN_SENT/SYN_RCVD等待時長

異常情況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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章