由問(wèn)題驅(qū)動(dòng),看一下TCP連接的本質(zhì)吧。

1. TCP如何保證消息的有序和不丟包?
Sequence Number就是SYN——包的序號(hào),用來(lái)解決網(wǎng)絡(luò)包亂序(reordering)問(wèn)題。
Acknowledgement Number就是ACK——用于確認(rèn)收到,用來(lái)解決不丟包的問(wèn)題。
2. 三次握手的目的?
為了建立可靠的數(shù)據(jù)傳輸,TCP通信雙方相互告知初始化序列號(hào)(ISN),并確定對(duì)方已經(jīng)收到ISN的(使用ACK機(jī)制)。
3. 三次握手的過(guò)程?
- 客戶端發(fā)送一個(gè)
SYN段,并指明客戶端的初始序列號(hào),即ISN(c). - 服務(wù)端發(fā)送自己的
SYN段作為應(yīng)答,同樣指明自己的ISN(s)。為了確認(rèn)客戶端的SYN,將ISN(c)+1作為ACK數(shù)值。這樣,每發(fā)送一個(gè)SYN,序列號(hào)就會(huì)加1. 如果有丟失的情況,則會(huì)重傳。 - 為了確認(rèn)服務(wù)器端的
SYN,客戶端將ISN(s)+1作為返回的ACK數(shù)值。

4. 為什么不能用兩次握手進(jìn)行連接?
三次握手的目的就是為了建立可靠的連接,TCP通信雙方都必須維護(hù)一個(gè)序列號(hào),以標(biāo)識(shí)發(fā)送的包哪些是被對(duì)方收到的。三次握手的過(guò)程中通信雙方要相互告知初始化序列號(hào),并確定對(duì)方已經(jīng)收到。
如果只是兩次握手,至多只有連接發(fā)起方的初始化序列號(hào)(ISN)能夠被確認(rèn),另一方的序列化得不到確認(rèn)。

5. 為什么建立連接是三次握手,而關(guān)閉連接卻是四次揮手呢?

四次揮手過(guò)程:
主機(jī)A發(fā)送
FIN后,進(jìn)入終止等待狀態(tài),服務(wù)器B收到主機(jī)A連接的釋放報(bào)文,就立即給主機(jī)A發(fā)送ACK。然后服務(wù)器B就進(jìn)入了close-wait狀態(tài)。并且服務(wù)器B再次發(fā)送
FIN通知主機(jī)A關(guān)閉連接,服務(wù)器B進(jìn)入最后確定狀態(tài)。主機(jī)A收到服務(wù)器B
FIN請(qǐng)求后,會(huì)發(fā)送一個(gè)ACK告訴服務(wù)器B收到,于是客戶端和服務(wù)器都關(guān)閉了。
FIN —— 該報(bào)文段的發(fā)送方已經(jīng)結(jié)束向?qū)Ψ桨l(fā)送數(shù)據(jù)。
這是因?yàn)榉?wù)端在LISTEN狀態(tài)下,收到建立連接請(qǐng)求的SYN報(bào)文后,把ACK和SYN放在一個(gè)報(bào)文里發(fā)送給客戶端。而關(guān)閉連接時(shí),當(dāng)收到對(duì)方的FIN報(bào)文時(shí),僅僅表示對(duì)方不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù),己方是否現(xiàn)在關(guān)閉發(fā)送數(shù)據(jù)通道,需要上層應(yīng)用來(lái)決定,因此,己方ACK和FIN一般都會(huì)分開(kāi)發(fā)送。
一句話總結(jié):是否關(guān)閉通道,是上層應(yīng)用決定的的,TCP無(wú)權(quán)將FIN和ACK一同發(fā)送。
6. 三次握手建立連接時(shí)SYN超時(shí)?
server端接到了clien發(fā)的SYN后回了SYN-ACK后client掉線了,server端沒(méi)有收到client回來(lái)的ACK,那么,這個(gè)連接處于一個(gè)中間狀態(tài),即沒(méi)成功,也沒(méi)失敗。于是,server端如果在一定時(shí)間內(nèi)沒(méi)有收到的TCP會(huì)重發(fā)SYN-ACK。在Linux下,默認(rèn)重試次數(shù)為5次,重試的間隔時(shí)間從1s開(kāi)始每次都翻售,5次的重試時(shí)間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第5次發(fā)出后還要等32s都知道第5次也超時(shí)了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才會(huì)把斷開(kāi)這個(gè)連接。
一句話總結(jié):服務(wù)器未收到客戶端的確定ACK,便會(huì)一直重試。
7. 關(guān)于SYN攻擊?
我們說(shuō)過(guò),在建立連接時(shí),server未收到client端的ACK通知,便開(kāi)始了長(zhǎng)達(dá)63s的重試機(jī)制。
7.1 什么叫做SYN Flood:
一些惡意的人就為此制造了SYN Flood攻擊——給服務(wù)器發(fā)了一個(gè)SYN后,就下線了,于是服務(wù)器需要默認(rèn)等63s才會(huì)斷開(kāi)連接,這樣,攻擊者就可以把服務(wù)器的syn連接的隊(duì)列耗盡,讓正常的連接請(qǐng)求不能處理。
7.2 妥協(xié)版的TCP協(xié)議:synccookies:
于是,Linux下給了一個(gè)叫tcp_syncookies的參數(shù)來(lái)應(yīng)對(duì)這個(gè)事——當(dāng)SYN隊(duì)列滿了后,TCP會(huì)通過(guò)源地址端口、目標(biāo)地址端口和時(shí)間戳打造出一個(gè)特別的Sequence Number發(fā)回去(又叫cookie),如果是攻擊者則不會(huì)有響應(yīng),如果是正常連接,則會(huì)把這個(gè) SYN Cookie發(fā)回來(lái),然后服務(wù)端可以通過(guò)cookie建連接(即使你不在SYN隊(duì)列中)。請(qǐng)注意,請(qǐng)先千萬(wàn)別用tcp_syncookies來(lái)處理正常的大負(fù)載的連接的情況。因?yàn)椋?code>synccookies是妥協(xié)版的TCP協(xié)議,并不嚴(yán)謹(jǐn)。
7.3 對(duì)于正常的請(qǐng)求,你應(yīng)該調(diào)整三個(gè)TCP參數(shù)可供你選擇:
第一個(gè)是:tcp_synack_retries 可以用他來(lái)減少重試次數(shù);
第二個(gè)是:tcp_max_syn_backlog,可以增大SYN連接數(shù);
第三個(gè)是:tcp_abort_on_overflow 處理不過(guò)來(lái)干脆就直接拒絕連接了。
8. 如何設(shè)置ISN的值?
三次握手的一個(gè)重要功能是客戶端和服務(wù)端交換ISN(Initial Sequence Number), 以便讓對(duì)方知道接下來(lái)接收數(shù)據(jù)的時(shí)候如何按序列號(hào)組裝數(shù)據(jù)。
如果ISN是固定的,攻擊者很容易猜出后續(xù)的確認(rèn)號(hào)。
ISN = M + F(localhost, localport, remotehost, remoteport)
M是一個(gè)計(jì)時(shí)器,每隔4微秒加1。 F是一個(gè)Hash算法,根據(jù)源IP、目的IP、源端口、目的端口生成一個(gè)隨機(jī)數(shù)值。要保證hash算法不能被外部輕易推算得出。
一句話總結(jié):ISN不能是固定不變的,一般是計(jì)時(shí)器(每4微妙+1)+隨機(jī)hash值設(shè)置的,防止被攻擊。
9、序列號(hào)回繞
因?yàn)镮SN是隨機(jī)的,所以序列號(hào)容易就會(huì)超過(guò)2^31-1. 而tcp對(duì)于丟包和亂序等問(wèn)題的判斷都是依賴于序列號(hào)大小比較的。此時(shí)就出現(xiàn)了所謂的tcp序列號(hào)回繞(sequence wraparound)問(wèn)題。怎么解決?
內(nèi)核代碼:
/** The next routines deal with comparing 32 bit unsigned ints
* and worry about wraparound (automatic with unsigned arithmetic).*/
static inline int before(__u32 seq1, __u32 seq2){
return (__s32)(seq1-seq2) < 0;}
序列號(hào)發(fā)生回繞后,序列號(hào)變小,相減之后,把結(jié)果變成有符號(hào)數(shù)了,因此結(jié)果成了負(fù)數(shù)。
假設(shè)seq1=255, seq2=1(發(fā)生了回繞)。
seq1 = 1111 1111 seq2 = 0000 0001
我們希望比較結(jié)果是
seq1 - seq2=
1111 1111
-0000 0001
————————————
1111 1110
由于我們將結(jié)果轉(zhuǎn)化成了有符號(hào)數(shù),由于最高位是1,因此結(jié)果是一個(gè)負(fù)數(shù),負(fù)數(shù)的絕對(duì)值為
0000 0001 + 1 = 0000 0010 = 2
因此seq1 - seq2 < 0
文章參考: