我又一次被震驚了。。。
時(shí)間:2021-09-17 15:44:35
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
[導(dǎo)讀]??大家好,我是小林。昨晚有位讀者問(wèn)了我這么個(gè)問(wèn)題:大概意思是,一個(gè)已經(jīng)建立的TCP連接,客戶端中途宕機(jī)了,而服務(wù)端此時(shí)也沒(méi)有數(shù)據(jù)要發(fā)送,一直處于establish狀態(tài),客戶端恢復(fù)后,向服務(wù)端建立連接,此時(shí)服務(wù)端會(huì)怎么處理?看過(guò)我的圖解網(wǎng)絡(luò)的讀者都知道,TCP連接是由「四元組」唯...
??大家好,我是小林。昨晚有位讀者問(wèn)了我這么個(gè)問(wèn)題:大概意思是,一個(gè)已經(jīng)建立的 TCP 連接,客戶端中途宕機(jī)了,而服務(wù)端此時(shí)也沒(méi)有數(shù)據(jù)要發(fā)送,一直處于 establish 狀態(tài),客戶端恢復(fù)后,向服務(wù)端建立連接,此時(shí)服務(wù)端會(huì)怎么處理?看過(guò)我的圖解網(wǎng)絡(luò)的讀者都知道,TCP 連接是由「四元組」唯一確認(rèn)的。然后這個(gè)場(chǎng)景中,客戶端的IP、服務(wù)端IP、目的端口并沒(méi)有變化,所以這個(gè)問(wèn)題關(guān)鍵要看客戶端發(fā)送的 SYN 報(bào)文中的源端口是否和上一次連接的源端口相同。1. 客戶端的 SYN 報(bào)文里的端口號(hào)與歷史連接不相同如果客戶端恢復(fù)后發(fā)送的 SYN 報(bào)文中的源端口號(hào)跟上一次連接的源端口號(hào)不一樣,此時(shí)服務(wù)端會(huì)認(rèn)為是新的連接要建立,于是就會(huì)通過(guò)三次握手來(lái)建立新的連接。那舊連接里處于 establish 狀態(tài)的服務(wù)端最后會(huì)怎么樣呢?
??->?tcp_v4_do_rcv
????->?tcp_rcv_established
??????->?tcp_validate_incoming
????????->?tcp_send_ack
我們只關(guān)注 tcp_validate_incoming 函數(shù)是怎么處理 SYN 報(bào)文的,精簡(jiǎn)后的代碼如下:從上面的代碼實(shí)現(xiàn)可以看到,處于 establish 狀態(tài)的服務(wù)端,在收到報(bào)文后,首先會(huì)判斷序列號(hào)是否在窗口內(nèi),如果不在,則看看 RST 標(biāo)記有沒(méi)有被設(shè)置,如果有就會(huì)丟掉。然后如果沒(méi)有 RST 標(biāo)志,就會(huì)判斷是否有 SYN 標(biāo)記,如果有 SYN 標(biāo)記就會(huì)跳轉(zhuǎn)到 syn_challenge 標(biāo)簽,然后執(zhí)行 tcp_send_challenge_ack 函數(shù)。tcp_send_challenge_ack 函數(shù)里就會(huì)調(diào)用 tcp_send_ack 函數(shù)來(lái)回復(fù)一個(gè)攜帶了正確序列號(hào)和確認(rèn)號(hào)的 ACK 報(bào)文。
killcx 工具的工作原理,如下圖。它偽造客戶端發(fā)送 SYN 報(bào)文,服務(wù)端收到后就會(huì)回復(fù)一個(gè)攜帶了正確「序列號(hào)和確認(rèn)號(hào)」的 ACK 報(bào)文(Challenge ACK),然后就可以利用這個(gè) ACK 報(bào)文里面的信息,偽造兩個(gè) RST 報(bào)文: ??
- 如果服務(wù)端發(fā)送了數(shù)據(jù)包給客戶端,由于客戶端的連接已經(jīng)被關(guān)閉了,此時(shí)客戶的內(nèi)核就會(huì)回 RST 報(bào)文,服務(wù)端收到后就會(huì)釋放連接。
- 如果服務(wù)端一直沒(méi)有發(fā)送數(shù)據(jù)包給客戶端,在超過(guò)一段時(shí)間后, TCP 保活機(jī)制就會(huì)啟動(dòng),檢測(cè)到客戶端沒(méi)有存活后,接著服務(wù)端就會(huì)釋放掉該連接。
- 丟掉 SYN 報(bào)文?
- 回復(fù) RST 報(bào)文?
- 回復(fù) ACK 報(bào)文?
RFC 文檔解釋
rfc793 文檔里的第 34 頁(yè)里,有說(shuō)到這個(gè)例子。原文的解釋我也貼出來(lái)給大家看看。我就不瞎翻譯了,意思和我在前面用中文說(shuō)的解釋差不多。
- When the SYN arrives at line 3, TCP B, being in a synchronized state,
and the incoming segment outside the window, responds with an
acknowledgment indicating what sequence it next expects to hear (ACK
100).- TCP A sees that this segment does not acknowledge anything it
sent and, being unsynchronized, sends a reset (RST) because it has
detected a half-open connection.- TCP B aborts at line 5. ?
- TCP A willcontinue to try to establish the connection;
源碼分析
處于 establish 狀態(tài)的服務(wù)端如果收到了客戶端的 SYN 報(bào)文時(shí),內(nèi)核會(huì)調(diào)用這些函數(shù):tcp_v4_rcv??->?tcp_v4_do_rcv
????->?tcp_rcv_established
??????->?tcp_validate_incoming
????????->?tcp_send_ack
我們只關(guān)注 tcp_validate_incoming 函數(shù)是怎么處理 SYN 報(bào)文的,精簡(jiǎn)后的代碼如下:從上面的代碼實(shí)現(xiàn)可以看到,處于 establish 狀態(tài)的服務(wù)端,在收到報(bào)文后,首先會(huì)判斷序列號(hào)是否在窗口內(nèi),如果不在,則看看 RST 標(biāo)記有沒(méi)有被設(shè)置,如果有就會(huì)丟掉。然后如果沒(méi)有 RST 標(biāo)志,就會(huì)判斷是否有 SYN 標(biāo)記,如果有 SYN 標(biāo)記就會(huì)跳轉(zhuǎn)到 syn_challenge 標(biāo)簽,然后執(zhí)行 tcp_send_challenge_ack 函數(shù)。tcp_send_challenge_ack 函數(shù)里就會(huì)調(diào)用 tcp_send_ack 函數(shù)來(lái)回復(fù)一個(gè)攜帶了正確序列號(hào)和確認(rèn)號(hào)的 ACK 報(bào)文。
如何關(guān)閉一個(gè) TCP 連接?
我這里問(wèn)題大家這么一個(gè)問(wèn)題,如何一個(gè) TCP 連接?可能大家第一反應(yīng)是「殺掉進(jìn)程」不就行了嗎?是的,這個(gè)是最粗暴的方式,殺掉客戶端進(jìn)程和服務(wù)端進(jìn)程影響的范圍會(huì)有所不同:- 在客戶端殺掉進(jìn)程的話,就會(huì)發(fā)送 FIN 報(bào)文,來(lái)斷開這個(gè)客戶端進(jìn)程與服務(wù)端建立的所有 TCP 連接,這種方式影響范圍只有這個(gè)客戶端進(jìn)程所建立的連接,而其他客戶端或進(jìn)程不會(huì)受影響。
- 而在服務(wù)端殺掉進(jìn)程影響就大了,此時(shí)所有的 TCP 連接都會(huì)被關(guān)閉,服務(wù)端無(wú)法繼續(xù)提供訪問(wèn)服務(wù)。
killcx 工具的工作原理,如下圖。它偽造客戶端發(fā)送 SYN 報(bào)文,服務(wù)端收到后就會(huì)回復(fù)一個(gè)攜帶了正確「序列號(hào)和確認(rèn)號(hào)」的 ACK 報(bào)文(Challenge ACK),然后就可以利用這個(gè) ACK 報(bào)文里面的信息,偽造兩個(gè) RST 報(bào)文:
- 用 Challenge ACK 里的確認(rèn)號(hào)偽造 RST 報(bào)文發(fā)送給服務(wù)端,服務(wù)端收到 RST 報(bào)文后就會(huì)釋放連接。
- 用 Challenge ACK 里的序列號(hào)偽造 RST 報(bào)文發(fā)送給客戶端,客戶端收到 RST 也會(huì)釋放連接。