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:沒有任何連接狀態,連接結束
本文通過實踐例子模擬每個階段的狀態變化。
本文用tcpdump抓包分析TCP連接的交互過程,其中tcpdump Flags含義如下:
- S=SYN 發起連接標志
- P=PUSH 傳送數據標志
- F=FIN 關閉連接標志
- R=RESET 異常關閉連接,鏈接重置
- . 表示沒有任何標志,表示返回ack
為什么要三次握手?
- 如果只有一次握手,Client不能確定與Server的單向連接,更加不能確定Server與Client的單向連接;
- 如果只有兩次握手,Client確定與Server的單向連接,但是Server不能確定與Client的單向連接;
- 只有三次握手,Client與Server才能相互確認雙向連接,實現雙工數據傳輸。
為什么要四次揮手?
“三次握手”的第二次握手發送SYN+ACK回應第一次握手的SYN,但是“四次揮手”的第二次揮手只能發送ACK回應第一次揮手的FIN,因為此時Server可能還有數據傳輸給Client,所以Server傳輸數據完成后才能發起第三次揮手發送FIN給Client,等待Client的第四次揮手ACK。
下面通過例子模擬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表示異常關閉連接,連接重置。
情況2:Server啟動服務,Client不啟動服務
如圖5所示,Server(127.0.0.1:2017)監聽2017端口,TCP連接狀態是LISTEN,等待Client發起TCP連接,如圖6所示,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的三次握手,說明雙向連接已經建立。
情況4:Client傳輸數據
從圖1和圖9看,數據傳輸過程中Server(127.0.0.1:2017)和Client(127.0.0.1:55866)的TCP連接狀態均為ESTABLISHED。
從圖10紅色方框看,Client(localhost:55866)向Server(localhost:2017)發送數據(localhost.55866 > localhost.2017: Flags [P.]
),Server返回ack(localhost.2017 > localhost.55866: Flags [.]
)確認。
情況5:Client斷開連接,Server保持連接
Client(localhost.41416)主動斷掉TCP連接,進入ESTABLISHED -> FIN-WAIT-1 -> FIN-WAIT-2 -> TIME-WAIT狀態,分別順序對應圖11紅色方框的三行記錄,對比圖3,第一行表示第一次揮手,第二行表示第二/三次揮手,第三行表示第四次揮手。
對比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四次揮手邏輯。
情況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表示異常關閉連接,連接重置。
正常情況下,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
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的重試時間。
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
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的重試時間。
異常情況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
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的重試時間。
異常情況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
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的重試時長。