當(dāng)客戶端想和服務(wù)端建立 TCP 連接的時(shí)候,首先第一個(gè)發(fā)的就是 SYN 報(bào)文,然后進(jìn)入到 SYN_SENT 狀態(tài)。
在這之后,如果客戶端遲遲收不到服務(wù)端的 SYN-ACK 報(bào)文(第二次握手),就會(huì)觸發(fā)超時(shí)重傳機(jī)制。
不同版本的操作系統(tǒng)可能超時(shí)時(shí)間不同,有的 1 秒的,也有 3 秒的,這個(gè)超時(shí)時(shí)間是寫死在內(nèi)核里的,如果想要更改則需要重新編譯內(nèi)核,比較麻煩。
當(dāng)客戶端在 1 秒后沒收到服務(wù)端的 SYN-ACK 報(bào)文后,客戶端就會(huì)重發(fā) SYN 報(bào)文,那到底重發(fā)幾次呢?
在 Linux 里,客戶端的 SYN 報(bào)文最大重傳次數(shù)由 tcp_syn_retries 內(nèi)核參數(shù)控制,這個(gè)參數(shù)是可以自定義的,默認(rèn)值一般是 5。
5
通常,第一次超時(shí)重傳是在 1 秒后,第二次超時(shí)重傳是在 2 秒,第三次超時(shí)重傳是在 4 秒后,第四次超時(shí)重傳是在 8 秒后,第五次是在超時(shí)重傳 16 秒后。沒錯(cuò),每次超時(shí)的時(shí)間是上一次的 2 倍。
當(dāng)?shù)谖宕纬瑫r(shí)重傳后,會(huì)繼續(xù)等待 32 秒,如果服務(wù)端仍然沒有回應(yīng) ACK,客戶端就不再發(fā)送 SYN 包,然后斷開 TCP 連接。
所以,總耗時(shí)是 1+2+4+8+16+32=63 秒,大約 1 分鐘左右。
場景復(fù)現(xiàn)
在服務(wù)端先 ban 掉客戶端的 IP iptables -I INPUT -s 客戶端 IP -j DROP,然后客戶端通過 curl 指令去訪問服務(wù)端:
可以自己設(shè)置 tcp_syn_retries:
第二次握手丟失了,會(huì)發(fā)生什么?
當(dāng)服務(wù)端收到客戶端的第一次握手后,就會(huì)回 SYN-ACK 報(bào)文給客戶端,這個(gè)就是第二次握手,此時(shí)服務(wù)端會(huì)進(jìn)入 SYN_RCVD 狀態(tài)。
第二次握手的 SYN-ACK 報(bào)文其實(shí)有兩個(gè)目的 :
- 第二次握手里的 ACK, 是對(duì)第一次握手的確認(rèn)報(bào)文;
- 第二次握手里的 SYN,是服務(wù)端發(fā)起建立 TCP 連接的報(bào)文;
所以,如果第二次握手丟了,就會(huì)發(fā)送比較有意思的事情,具體會(huì)怎么樣呢?
因?yàn)榈诙挝帐謭?bào)文里是包含對(duì)客戶端的第一次握手的 ACK 確認(rèn)報(bào)文,所以,如果客戶端遲遲沒有收到第二次握手,那么客戶端就覺得可能自己的 SYN 報(bào)文(第一次握手)丟失了,于是客戶端就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN 報(bào)文。
然后,因?yàn)榈诙挝帐种邪?wù)端的 SYN 報(bào)文,所以當(dāng)客戶端收到后,需要給服務(wù)端發(fā)送 ACK 確認(rèn)報(bào)文(第三次握手),服務(wù)端才會(huì)認(rèn)為該 SYN 報(bào)文被客戶端收到了。
那么,如果第二次握手丟失了,服務(wù)端就收不到第三次握手,于是服務(wù)端這邊會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN-ACK 報(bào)文。
在 Linux 下,SYN-ACK 報(bào)文的最大重傳次數(shù)由 tcp_synack_retries 內(nèi)核參數(shù)決定,默認(rèn)值是 5。
因此,當(dāng)?shù)诙挝帐謥G失了,客戶端和服務(wù)端都會(huì)重傳:
- 客戶端會(huì)重傳 SYN 報(bào)文,也就是第一次握手,最大重傳次數(shù)由 tcp_syn_retries 內(nèi)核參數(shù)決定;
- 服務(wù)端會(huì)重傳 SYN-AKC 報(bào)文,也就是第二次握手,最大重傳次數(shù)由 tcp_synack_retries 內(nèi)核參數(shù)決定。
場景復(fù)現(xiàn)
這時(shí)候解除服務(wù)端 ban 掉的 客戶端的IP iptables -D INPUT -s 客戶端 IP -j DROP,轉(zhuǎn)而讓客戶端 ban 掉服務(wù)端的 IP,這樣的話,客戶端發(fā)送的 SYN 包能被服務(wù)端收到,但是服務(wù)端返回的 ACK 包會(huì)在網(wǎng)絡(luò)層就被嘎掉,無法到達(dá)傳輸層交給 TCP 解析,因此就人為的造成了第二次握手失敗的場景;
客戶端設(shè)置了防火墻,屏蔽了服務(wù)端的網(wǎng)絡(luò)包,為什么 tcpdump 還能抓到服務(wù)端的網(wǎng)絡(luò)包?
添加 iptables 限制后, tcpdump 是否能抓到包 ,這要看添加的 iptables 限制條件:
- 如果添加的是 INPUT 規(guī)則,則可以抓得到包;
- 如果添加的是 OUTPUT 規(guī)則,則抓不到包;
網(wǎng)絡(luò)包進(jìn)入主機(jī)后的順序如下:
第三次握手丟失了,會(huì)發(fā)生什么?
客戶端收到服務(wù)端的 SYN-ACK 報(bào)文后,就會(huì)給服務(wù)端回一個(gè) ACK 報(bào)文,也就是第三次握手,此時(shí)客戶端狀態(tài)進(jìn)入到 ESTABLISH 狀態(tài)。
因?yàn)檫@個(gè)第三次握手的 ACK 是對(duì)第二次握手的 SYN 的確認(rèn)報(bào)文,所以當(dāng)?shù)谌挝帐謥G失了,如果服務(wù)端那一方遲遲收不到這個(gè)確認(rèn)報(bào)文,就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 SYN-ACK 報(bào)文,直到收到第三次握手,或者達(dá)到最大重傳次數(shù)。
注意,ACK 報(bào)文是不會(huì)有重傳的,當(dāng) ACK 丟失了,就由對(duì)方重傳對(duì)應(yīng)的報(bào)文。
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 `echo socket is listening!`
+0 < S 0:0(0) win 4000
+0 > S. 0:0(0) ack 1 <...>
+0 `sleep 1000000`
揮手失敗
第一次揮手丟失了,會(huì)發(fā)生什么?
當(dāng)客戶端(主動(dòng)關(guān)閉方)調(diào)用 close 函數(shù)后,就會(huì)向服務(wù)端發(fā)送 FIN 報(bào)文,試圖與服務(wù)端斷開連接,此時(shí)客戶端的連接進(jìn)入到 FIN_WAIT_1 狀態(tài)。
正常情況下,如果能及時(shí)收到服務(wù)端(被動(dòng)關(guān)閉方)的 ACK,則會(huì)很快變?yōu)?FIN_WAIT2 狀態(tài)。
如果第一次揮手丟失了,那么客戶端遲遲收不到被動(dòng)方的 ACK 的話,也就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 FIN 報(bào)文,重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制。
當(dāng)客戶端重傳 FIN 報(bào)文的次數(shù)超過 tcp_orphan_retries 后,就不再發(fā)送 FIN 報(bào)文,直接進(jìn)入到 CLOSE 狀態(tài)。
第二次揮手丟失了,會(huì)發(fā)生什么?
當(dāng)服務(wù)端收到客戶端的第一次揮手后,就會(huì)先回一個(gè) ACK 確認(rèn)報(bào)文,此時(shí)服務(wù)端的連接進(jìn)入到 CLOSE_WAIT 狀態(tài)。
在前面我們也提了,ACK 報(bào)文是不會(huì)重傳的,所以如果服務(wù)端的第二次揮手丟失了,客戶端就會(huì)觸發(fā)超時(shí)重傳機(jī)制,重傳 FIN 報(bào)文,直到收到服務(wù)端的第二次揮手,或者達(dá)到最大的重傳次數(shù)。
這里提一下,當(dāng)客戶端收到第二次揮手,也就是收到服務(wù)端發(fā)送的 ACK 報(bào)文后,客戶端就會(huì)處于 FIN_WAIT2 狀態(tài),在這個(gè)狀態(tài)需要等服務(wù)端發(fā)送第三次揮手,也就是服務(wù)端的 FIN 報(bào)文。
對(duì)于 close 函數(shù)關(guān)閉的連接,由于無法再發(fā)送和接收數(shù)據(jù),所以 FIN_WAIT2 狀態(tài)不可以持續(xù)太久,而 tcp_fin_timeout 控制了這個(gè)狀態(tài)下連接的持續(xù)時(shí)長,默認(rèn)值是 60 秒。
這意味著對(duì)于調(diào)用 close 關(guān)閉的連接,如果在 60 秒后還沒有收到 FIN 報(bào)文,客戶端(主動(dòng)關(guān)閉方)的連接就會(huì)直接關(guān)閉。
第三次揮手丟失了,會(huì)發(fā)生什么?
當(dāng)服務(wù)端(被動(dòng)關(guān)閉方)收到客戶端(主動(dòng)關(guān)閉方)的 FIN 報(bào)文后,內(nèi)核會(huì)自動(dòng)回復(fù) ACK,同時(shí)連接處于 CLOSE_WAIT 狀態(tài),顧名思義,它表示等待應(yīng)用進(jìn)程調(diào)用 close 函數(shù)關(guān)閉連接。
此時(shí),內(nèi)核是沒有權(quán)利替代進(jìn)程關(guān)閉連接,必須由進(jìn)程主動(dòng)調(diào)用 close 函數(shù)來觸發(fā)服務(wù)端發(fā)送 FIN 報(bào)文。
服務(wù)端處于 CLOSE_WAIT 狀態(tài)時(shí),調(diào)用了 close 函數(shù),內(nèi)核就會(huì)發(fā)出 FIN 報(bào)文,同時(shí)連接進(jìn)入 LAST_ACK 狀態(tài),等待客戶端返回 ACK 來確認(rèn)連接關(guān)閉。
如果遲遲收不到這個(gè) ACK,服務(wù)端就會(huì)重發(fā) FIN 報(bào)文,重發(fā)次數(shù)仍然由 tcp_orphan_retries 參數(shù)控制,這與客戶端重發(fā) FIN 報(bào)文的重傳次數(shù)控制方式是一樣的。
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0 `echo socket is listening!`
+0 < S 0:0(0) win 4000
+0 > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 1000
+0 accept(3, ..., ...) = 4
+0 `echo connection established!`
+0 < F. 1:1(0) ack 1 win 1000
+0 > . 1:1(0) ack 2
+0.1 close(4)=0
+0 > F. 1:1(0) ack 2 <...>
+0 `sleep 1000000`
第四次揮手丟失了,會(huì)發(fā)生什么?
當(dāng)客戶端收到服務(wù)端的第三次揮手的 FIN 報(bào)文后,就會(huì)回 ACK 報(bào)文,也就是第四次揮手,此時(shí)客戶端連接進(jìn)入 TIME_WAIT 狀態(tài)。
在 Linux 系統(tǒng),TIME_WAIT 狀態(tài)會(huì)持續(xù) 60 秒后才會(huì)進(jìn)入關(guān)閉狀態(tài)。
然后,服務(wù)端(被動(dòng)關(guān)閉方)沒有收到 ACK 報(bào)文前,還是處于 LAST_ACK 狀態(tài)。
如果第四次揮手的 ACK 報(bào)文沒有到達(dá)服務(wù)端,服務(wù)端就會(huì)重發(fā) FIN 報(bào)文,重發(fā)次數(shù)仍然由前面介紹過的 tcp_orphan_retries 參數(shù)控制。
為什么是三次握手?
現(xiàn)在耳熟能詳?shù)?TCP 連接就是三次握手,四次揮手,那么你有想過 為什么是三次握手,而不是兩次或者四次呢?
相信比較平?;卮鸬氖牵骸耙?yàn)槿挝帐植拍鼙WC雙方具有接收和發(fā)送的能力”。這樣的回答是沒問題的,但是這回答是片面的,并沒有說出主要的原因。
在前面我們知道了什么是 TCP 連接:用于保證可靠性和流量控制維護(hù)的某些狀態(tài)信息,這些信息的組合,包括 Socket、序列號(hào)和窗口大小稱為連接。
所以,重要的是為什么三次握手才可以初始化 Socket、序列號(hào)和窗口大小并建立 TCP 連接。
接下來以三個(gè)方面分析三次握手的原因:
- 三次握手才可以阻止重復(fù)歷史連接的初始化(主要原因);
- 三次握手才可以同步雙方的初始序列號(hào);
- 三次握手才可以避免資源浪費(fèi);
原因一:避免歷史連接
RFC 793 指出的 TCP 連接使用三次握手的首要原因:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
簡單來說,三次握手的首要原因是為了防止舊的重復(fù)連接初始化造成混亂。
網(wǎng)絡(luò)環(huán)境是錯(cuò)綜復(fù)雜的,往往并不是如我們期望的一樣,先發(fā)送的數(shù)據(jù)包,就先到達(dá)目標(biāo)主機(jī);可能會(huì)由于網(wǎng)絡(luò)擁堵等亂七八糟的原因,會(huì)使得舊的數(shù)據(jù)包,先到達(dá)目標(biāo)主機(jī)。那么這種情況下 TCP 三次握手是如何避免的呢?
客戶端連續(xù)發(fā)送多次 SYN 建立連接的報(bào)文,在網(wǎng)絡(luò)擁堵情況下:
- 一個(gè)「舊 SYN 報(bào)文」比「最新的 SYN 」 報(bào)文早到達(dá)了服務(wù)端;
- 那么此時(shí)服務(wù)端就會(huì)回一個(gè) SYN + ACK 報(bào)文給客戶端;
- 客戶端收到后可以根據(jù)自身的上下文,判斷這是一個(gè)歷史連接(序列號(hào)過期或超時(shí)),那么客戶端就會(huì)發(fā)送 RST 報(bào)文給服務(wù)端,表示中止這一次連接。
如果是兩次握手連接,就不能判斷當(dāng)前連接是否是歷史連接,三次握手則可以在客戶端(發(fā)送方)準(zhǔn)備發(fā)送第三次報(bào)文時(shí),客戶端因有足夠的上下文來判斷當(dāng)前連接是否是歷史連接:
- 如果是歷史連接(序列號(hào)過期或超時(shí)),則第三次握手發(fā)送的報(bào)文是 RST 報(bào)文,以此中止歷史連接;
- 如果不是歷史連接,則第三次發(fā)送的報(bào)文是 ACK 報(bào)文,通信雙方就會(huì)成功建立連接;
所以,TCP 使用三次握手建立連接的最主要原因是防止歷史連接初始化了連接。
原因二:同步雙方初始序列號(hào)
TCP 協(xié)議的通信雙方, 都必須維護(hù)一個(gè)「序列號(hào)」, 序列號(hào)是可靠傳輸?shù)囊粋€(gè)關(guān)鍵因素,它的作用:
- 接收方可以去除重復(fù)的數(shù)據(jù);
- 接收方可以根據(jù)數(shù)據(jù)包的序列號(hào)按序接收;
- 可以標(biāo)識(shí)發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對(duì)方收到的;
可見,序列號(hào)在 TCP 連接中占據(jù)著非常重要的作用,所以當(dāng)客戶端發(fā)送攜帶「初始序列號(hào)」的 SYN 報(bào)文的時(shí)候,需要服務(wù)端回一個(gè) ACK 應(yīng)答報(bào)文,表示客戶端的 SYN 報(bào)文已被服務(wù)端成功接收,那當(dāng)服務(wù)端發(fā)送「初始序列號(hào)」給客戶端的時(shí)候,依然也要得到客戶端的應(yīng)答回應(yīng),這樣一來一回,才能確保雙方的初始序列號(hào)能被可靠的同步。
四次握手與三次握手:
四次握手其實(shí)也能夠可靠的同步雙方的初始化序號(hào),但由于第二步和第三步可以優(yōu)化成一步,所以就成了「三次握手」。
而兩次握手只保證了一方的初始序列號(hào)能被對(duì)方成功接收,沒辦法保證雙方的初始序列號(hào)都能被確認(rèn)接收。
原因三:避免資源浪費(fèi)
如果只有「兩次握手」,當(dāng)客戶端的 SYN 請(qǐng)求連接在網(wǎng)絡(luò)中阻塞,客戶端沒有接收到 ACK 報(bào)文,就會(huì)重新發(fā)送 SYN ,由于沒有第三次握手,服務(wù)器不清楚客戶端是否收到了自己發(fā)送的建立連接的 ACK 確認(rèn)信號(hào),所以每收到一個(gè) SYN 就只能先主動(dòng)建立一個(gè)連接,這會(huì)造成什么情況呢?
如果客戶端的 SYN 阻塞了,重復(fù)發(fā)送多次 SYN 報(bào)文,那么服務(wù)器在收到請(qǐng)求后就會(huì)建立多個(gè)冗余的無效鏈接,造成不必要的資源浪費(fèi)。
兩次握手會(huì)造成資源浪費(fèi):
即兩次握手會(huì)造成消息滯留情況下,服務(wù)器重復(fù)接受無用的連接請(qǐng)求 SYN 報(bào)文,而造成重復(fù)分配資源。
小結(jié)
TCP 建立連接時(shí),通過三次握手能:
- 防止歷史連接的建立,能減少雙方不必要的資源開銷;
- 能幫助雙方同步初始化序列號(hào),序列號(hào)能夠保證數(shù)據(jù)包不重復(fù)、不丟棄和按序傳輸;
不使用「兩次握手」和「四次握手」的原因:
- 「兩次握手」:無法防止歷史連接的建立,會(huì)造成雙方資源的浪費(fèi),也無法可靠的同步雙方序列號(hào);
- 「四次握手」:三次握手就已經(jīng)理論上最少可靠連接建立,所以不需要使用更多的通信次數(shù);
如何避免 SYN 攻擊?
什么是 SYN 攻擊?
在了解如何避免 SYN 攻擊前,我們得先了解什么是 SYN 攻擊;
我們都知道 TCP 連接建立是需要三次握手,假設(shè)攻擊者短時(shí)間偽造不同 IP 地址的 SYN 報(bào)文,服務(wù)端每接收到一個(gè) SYN 報(bào)文,就進(jìn)入SYN_RCVD 狀態(tài),但服務(wù)端發(fā)送出去的 SYN + ACK 報(bào)文,無法得到未知 IP 主機(jī)的 ACK 應(yīng)答,久而久之就會(huì)占滿服務(wù)端的 SYN 接收隊(duì)列(未連接隊(duì)列),使得服務(wù)器不能為正常用戶服務(wù)。
也就是上面曾講述的 第三次握手失敗;
常用的工具有 LOIC,Hping3 等,演示攻擊如下:
因?yàn)樘摂M機(jī)的配置低,所以很容易就實(shí)現(xiàn) CPU 爆滿的狀況了,這時(shí)候服務(wù)器就很難響應(yīng)其他服務(wù)請(qǐng)求,更嚴(yán)重的可能會(huì)直接宕機(jī);
避免 SYN 攻擊方式一
第一種解決方式是通過修改 Linux 內(nèi)核參數(shù),控制隊(duì)列大小和當(dāng)隊(duì)列滿時(shí)應(yīng)做什么處理。
當(dāng)網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時(shí),會(huì)有一個(gè)隊(duì)列保存這些數(shù)據(jù)包。
- 控制該隊(duì)列的最大值:net.core.netdev_max_backlog;
- SYN_RCVD 狀態(tài)連接的最大個(gè)數(shù):net.ipv4.tcp_max_syn_backlog;
- 超出處理能時(shí),對(duì)新的 SYN 直接回報(bào) RST,丟棄連接:net.ipv4.tcp_abort_on_overflow;
避免 SYN 攻擊方式二
我們先來看下 Linux 內(nèi)核的 SYN (未完成連接建立)隊(duì)列與 Accpet (已完成連接建立)隊(duì)列是如何工作的:
1、正常流程;
- 當(dāng)服務(wù)端接收到客戶端的 SYN 報(bào)文時(shí),會(huì)將其加入到內(nèi)核的「 SYN 隊(duì)列」;
- 接著發(fā)送 SYN + ACK 給客戶端,等待客戶端回應(yīng) ACK 報(bào)文;
- 服務(wù)端接收到 ACK 報(bào)文后,從「 SYN 隊(duì)列」移除放入到「 Accept 隊(duì)列」;
- 應(yīng)用通過調(diào)用 accpet() socket 接口,從「 Accept 隊(duì)列」取出連接。
2、應(yīng)用程序過慢;
如果應(yīng)用程序過慢時(shí),就會(huì)導(dǎo)致「 Accept 隊(duì)列」被占滿。
3、受到 SYN 攻擊;
如果不斷受到 SYN 攻擊,就會(huì)導(dǎo)致「 SYN 隊(duì)列」被占滿。
tcp_syncookies 的方式可以應(yīng)對(duì) SYN 攻擊的方法:net.ipv4.tcp_syncookies = 1
- 當(dāng) 「 SYN 隊(duì)列」?jié)M之后,后續(xù)服務(wù)器收到 SYN 包,不進(jìn)入「 SYN 隊(duì)列」;
- 計(jì)算出一個(gè) cookie 值,再以 SYN + ACK 中的「序列號(hào)」返回客戶端;
- 服務(wù)端接收到客戶端的應(yīng)答報(bào)文時(shí),服務(wù)器會(huì)檢查這個(gè) ACK 包的合法性,如果合法,直接放入到「 Accept 隊(duì)列」;
- 最后應(yīng)用通過調(diào)用 accpet() socket 接口,從「 Accept 隊(duì)列」取出的連接。
MTU 與 MSS 那些事兒
最大報(bào)文段長度 MSS 與 最大傳輸單元 MTU 均是協(xié)議用來定義最大長度的。不同的是,MTU 應(yīng)用于 OSI 模型的第二層數(shù)據(jù)鏈接層,并無具體針對(duì)的協(xié)議,限制了數(shù)據(jù)鏈接層上可以傳輸?shù)臄?shù)據(jù)包的大小,也因此限制了上層(網(wǎng)絡(luò)層)的數(shù)據(jù)包大??;MSS 針對(duì)的是 OSI 模型里的第四層傳輸層的 TCP 協(xié)議,因?yàn)?MSS 應(yīng)用的協(xié)議在數(shù)據(jù)鏈接層的上層,所以 MSS 會(huì)受到 MTU 的限制。
- MTU:Maximum Transmission Unit,最大傳輸單元,由硬件規(guī)定,如以太網(wǎng)的 MTU 為1500字節(jié)。
- MSS:Maximum Segment Size,最大分段大小,是 TCP 數(shù)據(jù)包每次傳輸?shù)淖畲髷?shù)據(jù)分段大小,一般由發(fā)送端向?qū)Χ?TCP 通知,對(duì)端在每個(gè)分節(jié)中能發(fā)送的最大 TCP 數(shù)據(jù)。MSS 值為 MTU 值減去IPv4 Header(20 Byte)和 TCP header(20 Byte)得到。
- 分片:Fragmentation,若 IP 數(shù)據(jù)報(bào)大小超過相應(yīng)鏈路的 MTU 的時(shí)候,IPV4 和 IPV6 都執(zhí)行分片操作,各片段到達(dá)目的地前通常不會(huì)被重組 (re-assembling)。IPV4 主機(jī)對(duì)其產(chǎn)生的數(shù)據(jù)報(bào)執(zhí)行分片,IPV4 路由器對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)也執(zhí)行分片,然而 IPV6 只在數(shù)據(jù)產(chǎn)生的主機(jī)執(zhí)行分片,IPV6 路由器對(duì)其轉(zhuǎn)發(fā)的數(shù)據(jù)不執(zhí)行分片。
那么問題來了,為什么由 Wireshark 抓到的數(shù)據(jù)包的MSS = 1460,但卻在Len = 1448的時(shí)候就進(jìn)行分包了呢?如下圖所示:
既然在握手階段就協(xié)商了 MSS = 1460,那為什么 TCP 的最大數(shù)據(jù)段長度卻只有 1448 bytes 呢?
原來在實(shí)際場景中,TCP 包頭中會(huì)帶有12字節(jié)的選項(xiàng),時(shí)間戳(Timestamps),這樣,單個(gè) TCP 包實(shí)際傳輸?shù)淖畲罅烤涂s減為1448字節(jié)了,如下所示:
那 MTU = MSS + IP 頭長度 + TCP 頭長度,一般 IP 頭長度和 TCP 頭長度都為20,即MTU = MSS + 20 +20 = 1500,但是呢,從下圖中不難發(fā)現(xiàn),一個(gè) TCP 包的總長度 Length 卻大于 MTU,這是為什么呢?
這是根據(jù)以太網(wǎng)幀結(jié)構(gòu)所決定的,至于如何判斷是否為以太網(wǎng),可以根據(jù)物理層中的[Protocols in frame: eth:ethertype:ip:tcp]判定,這表示幀內(nèi)封裝的協(xié)議層次結(jié)構(gòu);
在不選擇填充 802.1Q 標(biāo)簽,負(fù)載拉滿(即為 MTU 值)的前提下,以太網(wǎng)的最大幀大小應(yīng)該是 前導(dǎo)碼+幀開始符+MAC目標(biāo)地址+MAC源地址+以太類型+負(fù)載+冗余校驗(yàn) = 7+1+6+6+2+1500+4 = 1526 字節(jié),那為什么 Wireshark 抓來的數(shù)據(jù)包的最大幀卻只有1514字節(jié)呢?
原來是因?yàn)楫?dāng)數(shù)據(jù)幀到達(dá)網(wǎng)卡時(shí),在物理層上網(wǎng)卡要先去掉前導(dǎo)碼和幀開始定界符,然后對(duì)幀進(jìn)行 CRC 校驗(yàn):如果幀校驗(yàn)和錯(cuò)誤,就丟棄此幀;如果幀校驗(yàn)和正確,就判斷該幀的 MAC 目的地址是否符合自己的接收條件,如果符合,就將此幀交付 設(shè)備驅(qū)動(dòng)程序 做進(jìn)一步處理。這時(shí) Wireshark 才能抓到數(shù)據(jù),因此,Wireshark 抓到的是去掉前導(dǎo)碼、幀開始分界符、CRC校驗(yàn)之外的數(shù)據(jù),其最大值是 6+6+2+1500=1514 字節(jié);
TIME_WAIT 的巧妙設(shè)計(jì)
為什么 TIME_WAIT 等待的時(shí)間是 2MSL?
MSL (Maximum Segment Lifetime),報(bào)文最大生存時(shí)間,它是任何報(bào)文在網(wǎng)絡(luò)上存在的最長時(shí)間,超過這個(gè)時(shí)間報(bào)文將被丟棄。因?yàn)?TCP 報(bào)文基于是 IP 協(xié)議的,而 IP 頭中有一個(gè) TTL 字段,是 IP 數(shù)據(jù)報(bào)可以經(jīng)過的最大路由數(shù),每經(jīng)過一個(gè)處理他的路由器此值就減 1,當(dāng)此值為 0 則數(shù)據(jù)報(bào)將被丟棄,同時(shí)發(fā)送 ICMP 報(bào)文通知源主機(jī)。
MSL 與 TTL 的區(qū)別: MSL 的單位是時(shí)間,而 TTL 是經(jīng)過路由跳數(shù)。所以 MSL 應(yīng)該要大于等于 TTL 消耗為 0 的時(shí)間,以確保報(bào)文已被自然消亡。
TIME_WAIT 等待 2 倍的 MSL,比較合理的解釋是: 網(wǎng)絡(luò)中可能存在來自發(fā)送方的數(shù)據(jù)包,當(dāng)這些發(fā)送方的數(shù)據(jù)包被接收方處理后又會(huì)向?qū)Ψ桨l(fā)送響應(yīng),所以一來一回需要等待 2 倍的時(shí)間。
比如被動(dòng)關(guān)閉方?jīng)]有收到斷開連接的最后的 ACK 報(bào)文,就會(huì)觸發(fā)超時(shí)重發(fā) FIN 報(bào)文,另一方接收到 FIN 后,會(huì)重發(fā) ACK 給被動(dòng)關(guān)閉方, 一來一去正好 2 個(gè) MSL。
2MSL 的時(shí)間是從客戶端接收到 FIN 后發(fā)送 ACK 開始計(jì)時(shí)的。如果在 TIME-WAIT 時(shí)間內(nèi),因?yàn)榭蛻舳说?ACK 沒有傳輸?shù)椒?wù)端,客戶端又接收到了服務(wù)端重發(fā)的 FIN 報(bào)文,那么 2MSL 時(shí)間將重新計(jì)時(shí)。
在 Linux 系統(tǒng)里 2MSL 默認(rèn)是 60 秒,那么一個(gè) MSL 也就是 30 秒。Linux 系統(tǒng)停留在 TIME_WAIT 的時(shí)間為固定的 60 秒。
其定義在 Linux 內(nèi)核代碼里的名稱為 TCP_TIMEWAIT_LEN:
如果要修改 TIME_WAIT 的時(shí)間長度,只能修改 Linux 內(nèi)核代碼里 TCP_TIMEWAIT_LEN 的值,并重新編譯 Linux 內(nèi)核。
為什么需要 TIME_WAIT 狀態(tài)?
主動(dòng)發(fā)起關(guān)閉連接的一方,才會(huì)有 TIME-WAIT 狀態(tài)。
需要 TIME-WAIT 狀態(tài),主要是兩個(gè)原因:
- 防止具有相同「四元組」的「舊」數(shù)據(jù)包被收到;
- 保證「被動(dòng)關(guān)閉連接」的一方能被正確的關(guān)閉,即保證最后的 ACK 能讓被動(dòng)關(guān)閉方接收,從而幫助其正常關(guān)閉;
原因一:防止舊連接的數(shù)據(jù)包
假設(shè)TIME-WAIT沒有等待時(shí)間或時(shí)間過短,被延遲的數(shù)據(jù)包抵達(dá)后會(huì)發(fā)生什么呢?
- 如上圖黃色框框服務(wù)端在關(guān)閉連接之前發(fā)送的 SEQ = 301 報(bào)文,被網(wǎng)絡(luò)延遲了。
- 這時(shí)有相同端口的 TCP 連接被復(fù)用后,被延遲的 SEQ = 301 抵達(dá)了客戶端,那么客戶端是有可能正常接收這個(gè)過期的報(bào)文,這就會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂等嚴(yán)重的問題。
所以,TCP 就設(shè)計(jì)出了這么一個(gè)機(jī)制,經(jīng)過 2MSL 這個(gè)時(shí)間,足以讓兩個(gè)方向上的數(shù)據(jù)包都被丟棄,使得原來連接的數(shù)據(jù)包在網(wǎng)絡(luò)中都自然消失,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的。
原因二:保證連接正確關(guān)閉
在 RFC 793 指出 TIME-WAIT 另一個(gè)重要的作用是:
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是說,TIME-WAIT 作用是等待足夠的時(shí)間以確保最后的 ACK 能讓被動(dòng)關(guān)閉方接收,從而幫助其正常關(guān)閉。
假設(shè) TIME-WAIT 沒有等待時(shí)間或時(shí)間過短,斷開連接會(huì)造成什么問題呢?
- 如上圖紅色框框客戶端四次揮手的最后一個(gè) ACK 報(bào)文如果在網(wǎng)絡(luò)中被丟失了,此時(shí)如果客戶端 TIME-WAIT 過短或沒有,則就直接進(jìn)入了 CLOSED 狀態(tài)了,那么服務(wù)端則會(huì)一直處在 LASE_ACK 狀態(tài)。
- 當(dāng)客戶端發(fā)起建立連接的 SYN 請(qǐng)求報(bào)文后,服務(wù)端會(huì)發(fā)送 RST 報(bào)文給客戶端,連接建立的過程就會(huì)被終止。
如果 TIME-WAIT 等待足夠長的情況就會(huì)遇到兩種情況:
- 服務(wù)端正常收到四次揮手的最后一個(gè) ACK 報(bào)文,則服務(wù)端正常關(guān)閉連接。
- 服務(wù)端沒有收到四次揮手的最后一個(gè) ACK 報(bào)文時(shí),則會(huì)重發(fā) FIN 關(guān)閉連接報(bào)文并等待新的 ACK 報(bào)文。
所以客戶端在 TIME-WAIT 狀態(tài)等待 2MSL 時(shí)間后,就可以保證雙方的連接都可以正常的關(guān)閉。
TIME_WAIT 過長有什么危害?
如果服務(wù)器有處于 TIME-WAIT 狀態(tài)的 TCP,則說明是由服務(wù)器方主動(dòng)發(fā)起的斷開請(qǐng)求。
過多的 TIME-WAIT 狀態(tài)主要的危害有兩種:
- 第一是內(nèi)存資源占用;
- 第二是對(duì)端口資源的占用,一個(gè) TCP 連接至少消耗一個(gè)本地端口;
第二個(gè)危害是會(huì)造成嚴(yán)重的后果的,要知道,端口資源也是有限的,一般可以開啟的端口為 32768~61000,也可以通過參數(shù)設(shè)置指定 net.ipv4.ip_local_port_range,如果發(fā)起連接一方的 TIME_WAIT 狀態(tài)過多,占滿了所有端口資源,則會(huì)導(dǎo)致無法創(chuàng)建新連接。
客戶端受端口資源限制:
- 客戶端 TIME_WAIT 過多,就會(huì)導(dǎo)致端口資源被占用,因?yàn)槎丝诰?5536個(gè),被占滿就會(huì)導(dǎo)致無法創(chuàng)建新的連接。
服務(wù)端受系統(tǒng)資源限制:
- 由于一個(gè)四元組表示 TCP 連接,理論上服務(wù)端可以建立很多連接,服務(wù)端確實(shí)只監(jiān)聽一個(gè)端口 但是會(huì)把連接扔給處理線程,所以理論上監(jiān)聽的端口可以繼續(xù)監(jiān)聽。但是線程池處理不了那么多一直不斷的連接了。所以當(dāng)服務(wù)端出現(xiàn)大量 TIME_WAIT 時(shí),系統(tǒng)資源被占滿時(shí),會(huì)導(dǎo)致處理不過來新的連接。
如何優(yōu)化 TIME_WAIT?
這里給出優(yōu)化 TIME-WAIT 的幾個(gè)方式,都是有利有弊:
方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
如下的 Linux 內(nèi)核參數(shù)開啟后,則可以復(fù)用處于 TIME_WAIT 的 socket 為新的連接所用。
有一點(diǎn)需要注意的是,tcp_tw_reuse 功能只能用于客戶端(連接發(fā)起方),因?yàn)殚_啟了該功能,在調(diào)用 connect() 函數(shù)時(shí),內(nèi)核會(huì)隨機(jī)找一個(gè) time_wait 狀態(tài)超過 1 秒的連接給新的連接復(fù)用。
net.ipv4.tcp_tw_reuse = 1
使用這個(gè)選項(xiàng),還有一個(gè)前提,需要打開對(duì) TCP 時(shí)間戳的支持,即 net.ipv4.tcp_timestamps=1(默認(rèn)即為 1),這個(gè)時(shí)間戳的字段是在 TCP 頭部的「選項(xiàng)」里,用于記錄 TCP 發(fā)送方的當(dāng)前時(shí)間戳和從對(duì)端接收到的最新時(shí)間戳。
由于引入了時(shí)間戳,我們在前面提到的 2MSL 問題就不復(fù)存在了,因?yàn)橹貜?fù)的數(shù)據(jù)包會(huì)因?yàn)闀r(shí)間戳過期被自然丟棄。
方式二:net.ipv4.tcp_max_tw_buckets
這個(gè)值默認(rèn)為 18000,當(dāng)系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個(gè)值時(shí),系統(tǒng)就會(huì)將后面的 TIME_WAIT 連接狀態(tài)重置。
這個(gè)方法過于暴力,而且治標(biāo)不治本,帶來的問題遠(yuǎn)比解決的問題多,不推薦使用。
方式三:程序中使用 SO_LINGER
我們可以通過設(shè)置 socket 選項(xiàng),來設(shè)置調(diào)用 close 關(guān)閉連接行為。
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));
如果 l_onoff 為非 0, 且 l_linger 值為 0,那么調(diào)用 close 后,會(huì)立該發(fā)送一個(gè) RST 標(biāo)志給對(duì)端,該 TCP 連接將跳過四次揮手,也就跳過了 TIME_WAIT 狀態(tài),直接關(guān)閉。
但這為跨越 TIME_WAIT 狀態(tài)提供了一個(gè)可能,不過是一個(gè)非常危險(xiǎn)的行為,不值得提倡。
如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦?
TCP 有一個(gè)機(jī)制是保活機(jī)制。這個(gè)機(jī)制的原理是這樣的:
定義一個(gè)時(shí)間段,在這個(gè)時(shí)間段內(nèi),如果沒有任何連接相關(guān)的活動(dòng),TCP 保活機(jī)制會(huì)開始作用,每隔一個(gè)時(shí)間間隔,發(fā)送一個(gè)探測報(bào)文,該探測報(bào)文包含的數(shù)據(jù)非常少,如果連續(xù)幾個(gè)探測報(bào)文都沒有得到響應(yīng),則認(rèn)為當(dāng)前的 TCP 連接已經(jīng)死亡,系統(tǒng)內(nèi)核將錯(cuò)誤信息通知給上層應(yīng)用程序。
在 Linux 內(nèi)核可以有對(duì)應(yīng)的參數(shù)可以設(shè)置?;顣r(shí)間、?;钐綔y的次數(shù)、?;钐綔y的時(shí)間間隔,以下都為默認(rèn)值:
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
# 表示?;顣r(shí)間是 7200 秒(2小時(shí)),也就 2 小時(shí)內(nèi)如果沒有任何連接相關(guān)的活動(dòng),則會(huì)啟動(dòng)?;顧C(jī)制
tcp_keepalive_time=7200
# 表示每次檢測間隔 75 秒
tcp_keepalive_intvl=75
# 表示檢測 9 次無響應(yīng),認(rèn)為對(duì)方是不可達(dá)的,從而中斷本次的連接。
tcp_keepalive_probes=9:
也就是說在 Linux 系統(tǒng)中,最少需要經(jīng)過 2 小時(shí) 11 分 15 秒才可以發(fā)現(xiàn)一個(gè)「死亡」連接。
這個(gè)時(shí)間是有點(diǎn)長的,我們也可以根據(jù)實(shí)際的需求,對(duì)以上的?;钕嚓P(guān)的參數(shù)進(jìn)行設(shè)置。
如果開啟了 TCP ?;睿枰紤]以下幾種情況:
第一種,對(duì)端程序是正常工作的。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端, 對(duì)端會(huì)正常響應(yīng),這樣 TCP 保活時(shí)間會(huì)被重置,等待下一個(gè) TCP ?;顣r(shí)間的到來。
第二種,對(duì)端程序崩潰并重啟。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端后,對(duì)端是可以響應(yīng)的,但由于沒有該連接的有效信息,會(huì)產(chǎn)生一個(gè) RST 報(bào)文,這樣很快就會(huì)發(fā)現(xiàn) TCP 連接已經(jīng)被重置。
第三種,是對(duì)端程序崩潰,或?qū)Χ擞捎谄渌驅(qū)е聢?bào)文不可達(dá)。當(dāng) TCP ?;畹奶綔y報(bào)文發(fā)送給對(duì)端后,石沉大海,沒有響應(yīng),連續(xù)幾次,達(dá)到?;钐綔y次數(shù)后,TCP 會(huì)報(bào)告該 TCP 連接已經(jīng)死亡。
初始序列號(hào) ISN為什么不同?
主要原因是為了防止歷史報(bào)文被下一個(gè)相同四元組的連接接收。
如果一個(gè)已經(jīng)失效的連接被重用了,但是該舊連接的歷史報(bào)文還殘留在網(wǎng)絡(luò)中,如果序列號(hào)相同,那么就無法分辨出該報(bào)文是不是歷史報(bào)文,如果歷史報(bào)文被新的連接接收了,則會(huì)產(chǎn)生數(shù)據(jù)錯(cuò)亂。所以,每次建立連接前重新初始化一個(gè)序列號(hào)主要是為了通信雙方能夠根據(jù)序號(hào)將不屬于本連接的報(bào)文段丟棄。
另一方面是為了安全性,防止黑客偽造的相同序列號(hào)的 TCP 報(bào)文被對(duì)方接收。
初始序列號(hào) ISN 是如何隨機(jī)產(chǎn)生的?
起始 ISN 是基于時(shí)鐘的,每 4 毫秒 + 1,轉(zhuǎn)一圈要 4.55 個(gè)小時(shí)。
RFC1948 中提出了一個(gè)較好的初始化序列號(hào) ISN 隨機(jī)生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
M 是一個(gè)計(jì)時(shí)器,這個(gè)計(jì)時(shí)器每隔 4 毫秒加 1。
F 是一個(gè) Hash 算法,根據(jù)源 IP、目的 IP、源端口、目的端口生成一個(gè)隨機(jī)數(shù)值。要保證 Hash 算法不能被外部輕易推算得出,用 MD5 算法是一個(gè)比較好的選擇。
TIME_WAIT 狀態(tài)不是會(huì)持續(xù) 2 MSL 時(shí)長,歷史報(bào)文不是早就在網(wǎng)絡(luò)中消失了嗎?
是的,如果能正常四次揮手,由于 TIME_WAIT 狀態(tài)會(huì)持續(xù) 2 MSL 時(shí)長,歷史報(bào)文會(huì)在下一個(gè)連接之前就會(huì)自然消失。
但是來了,我們并不能保證每次連接都能通過四次揮手來正常關(guān)閉連接。
假設(shè)每次建立連接,客戶端和服務(wù)端的初始化序列號(hào)都是從 0 開始:
過程如下:
- 客戶端和服務(wù)端建立一個(gè) TCP 連接,在客戶端發(fā)送數(shù)據(jù)包被網(wǎng)絡(luò)阻塞了,而此時(shí)服務(wù)端的進(jìn)程重啟了,于是就會(huì)發(fā)送 RST 報(bào)文來斷開連接。
- 緊接著,客戶端又與服務(wù)端建立了與上一個(gè)連接相同四元組的連接;
- 在新連接建立完成后,上一個(gè)連接中被網(wǎng)絡(luò)阻塞的數(shù)據(jù)包正好抵達(dá)了服務(wù)端,剛好該數(shù)據(jù)包的序列號(hào)正好是在服務(wù)端的接收窗口內(nèi),所以該數(shù)據(jù)包會(huì)被服務(wù)端正常接收,就會(huì)造成數(shù)據(jù)錯(cuò)亂。
可以看到,如果每次建立連接,客戶端和服務(wù)端的初始化序列號(hào)都是一樣的話,很容易出現(xiàn)歷史報(bào)文被下一個(gè)相同四元組的連接接收的問題。
客戶端和服務(wù)端的初始化序列號(hào)不一樣不是也會(huì)發(fā)生這樣的事情嗎?
是的,即使客戶端和服務(wù)端的初始化序列號(hào)不一樣,也會(huì)存在收到歷史報(bào)文的可能。
但是我們要清楚一點(diǎn),歷史報(bào)文能否被對(duì)方接收,還要看該歷史報(bào)文的序列號(hào)是否正好在對(duì)方接收窗口內(nèi),如果不在就會(huì)丟棄,如果在才會(huì)接收。
如果每次建立連接客戶端和服務(wù)端的初始化序列號(hào)都「不一樣」,就有大概率因?yàn)闅v史報(bào)文的序列號(hào)「不在」對(duì)方接收窗口,從而很大程度上避免了歷史報(bào)文,比如下圖:
相反,如果每次建立連接客戶端和服務(wù)端的初始化序列號(hào)都「一樣」,就有大概率遇到歷史報(bào)文的序列號(hào)剛「好在」對(duì)方的接收窗口內(nèi),從而導(dǎo)致歷史報(bào)文被新連接成功接收。
所以,每次初始化序列號(hào)不一樣能夠很大程度上避免歷史報(bào)文被下一個(gè)相同四元組的連接接收,注意是很大程度上,并不是完全避免了。
客戶端和服務(wù)端的初始化序列號(hào)不一樣不是也會(huì)發(fā)生這樣的事情嗎?
是的,但是也不是完全避免了。
為了能更好的理解這個(gè)原因,我們先來了解序列號(hào)(SEQ)和初始序列號(hào)(ISN)。
- 序列號(hào),是 TCP 一個(gè)頭部字段,標(biāo)識(shí)了 TCP 發(fā)送端到 TCP 接收端的數(shù)據(jù)流的一個(gè)字節(jié),因?yàn)?TCP 是面向字節(jié)流的可靠協(xié)議,為了保證消息的順序性和可靠性,TCP 為每個(gè)傳輸方向上的每個(gè)字節(jié)都賦予了一個(gè)編號(hào),以便于傳輸成功后確認(rèn)、丟失后重傳以及在接收端保證不會(huì)亂序。序列號(hào)是一個(gè) 32 位的無符號(hào)數(shù),因此在到達(dá) 4G 之后再循環(huán)回到 0。
- 初始序列號(hào),在 TCP 建立連接的時(shí)候,客戶端和服務(wù)端都會(huì)各自生成一個(gè)初始序列號(hào),它是基于時(shí)鐘生成的一個(gè)隨機(jī)數(shù),來保證每個(gè)連接都擁有不同的初始序列號(hào)。初始化序列號(hào)可被視為一個(gè) 32 位的計(jì)數(shù)器,該計(jì)數(shù)器的數(shù)值每 4 微秒加 1,循環(huán)一次需要 4.55 小時(shí)。
通過前面我們知道,序列號(hào)和初始化序列號(hào)并不是無限遞增的,會(huì)發(fā)生回繞為初始值的情況,這意味著無法根據(jù)序列號(hào)來判斷新老數(shù)據(jù)。
不要以為序列號(hào)的上限值是 4GB,就以為很大,很難發(fā)生回繞。在一個(gè)速度足夠快的網(wǎng)絡(luò)中傳輸大量數(shù)據(jù)時(shí),序列號(hào)的回繞時(shí)間就會(huì)變短。如果序列號(hào)回繞的時(shí)間極短,我們就會(huì)再次面臨之前延遲的報(bào)文抵達(dá)后序列號(hào)依然有效的問題。
為了解決這個(gè)問題,就需要有 TCP 時(shí)間戳。tcp_timestamps 參數(shù)是默認(rèn)開啟的,開啟了 tcp_timestamps 參數(shù),TCP 頭部就會(huì)使用時(shí)間戳選項(xiàng),它有兩個(gè)好處,一個(gè)是便于精確計(jì)算 RTT ,另一個(gè)是能防止序列號(hào)回繞(PAWS)。
試看下面的示例,假設(shè) TCP 的發(fā)送窗口是 1 GB,并且使用了時(shí)間戳選項(xiàng),發(fā)送方會(huì)為每個(gè) TCP 報(bào)文分配時(shí)間戳數(shù)值,我們假設(shè)每個(gè)報(bào)文時(shí)間加 1,然后使用這個(gè)連接傳輸一個(gè) 6GB 大小的數(shù)據(jù)流。
32 位的序列號(hào)在時(shí)刻 D 和 E 之間回繞。假設(shè)在時(shí)刻B有一個(gè)報(bào)文丟失并被重傳,又假設(shè)這個(gè)報(bào)文段在網(wǎng)絡(luò)上繞了遠(yuǎn)路并在時(shí)刻 F 重新出現(xiàn)。如果 TCP 無法識(shí)別這個(gè)繞回的報(bào)文,那么數(shù)據(jù)完整性就會(huì)遭到破壞。
使用時(shí)間戳選項(xiàng)能夠有效的防止上述問題,如果丟失的報(bào)文會(huì)在時(shí)刻 F 重新出現(xiàn),由于它的時(shí)間戳為 2,小于最近的有效時(shí)間戳(5 或 6),因此防回繞序列號(hào)算法(PAWS)會(huì)將其丟棄。
防回繞序列號(hào)算法要求連接雙方維護(hù)最近一次收到的數(shù)據(jù)包的時(shí)間戳(Recent TSval),每收到一個(gè)新數(shù)據(jù)包都會(huì)讀取數(shù)據(jù)包中的時(shí)間戳值跟 Recent TSval 值做比較,如果發(fā)現(xiàn)收到的數(shù)據(jù)包中時(shí)間戳不是遞增的,則表示該數(shù)據(jù)包是過期的,就會(huì)直接丟棄這個(gè)數(shù)據(jù)包。
客戶端和服務(wù)端的初始化序列號(hào)都是隨機(jī)生成,能很大程度上避免歷史報(bào)文被下一個(gè)相同四元組的連接接收,然后又引入時(shí)間戳的機(jī)制,從而完全避免了歷史報(bào)文被接收的問題。
你知道 TCP 的最大連接數(shù)嗎?
有一個(gè) IP 的服務(wù)器監(jiān)聽了一個(gè)端口,它的 TCP 的最大連接數(shù)是多少?
服務(wù)器通常固定在某個(gè)本地端口上監(jiān)聽,等待客戶端的連接請(qǐng)求。因此,客戶端 IP 和 端口是可變的,其理論值計(jì)算公式如下:
對(duì) IPv4,客戶端的 IP 數(shù)最多為 2的32次方,客戶端的端口數(shù)最多為 2的16次方,也就是服務(wù)端單機(jī)最大 TCP 連接數(shù),約為 2的48次方。
當(dāng)然,服務(wù)端最大并發(fā) TCP 連接數(shù)遠(yuǎn)不能達(dá)到理論上限:
- 首先主要是文件描述符限制,Socket 是文件,所以首先要通過 ulimit 配置文件描述符的數(shù)目;
- 另一個(gè)是內(nèi)存限制,每個(gè) TCP 連接都要占用一定內(nèi)存,操作系統(tǒng)的內(nèi)存是有限的。
-
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7103瀏覽量
125050 -
TCP
+關(guān)注
關(guān)注
8文章
1398瀏覽量
80468 -
ACK
+關(guān)注
關(guān)注
0文章
28瀏覽量
11346 -
服務(wù)端
+關(guān)注
關(guān)注
0文章
68瀏覽量
7199
發(fā)布評(píng)論請(qǐng)先 登錄
評(píng)論