記錄 - 4.30
什么是SYN攻擊? 如何避免SYN 攻擊?
攻擊者短時間偽造不同ip地址的SYN報文,但不會對服務端的SYN+ACK 報文回復,久而久之,服務端的半連接隊列就會被占滿,使得服務端收到之后的SYN報文就會丟棄。
避免 SYN 攻擊方式,可以有以下四種方法:
調大 netdev_max_backlog;(當網(wǎng)卡接收數(shù)據(jù)包的速度大于內核處理的速度時,會有一個隊列保存這些數(shù)據(jù)包)
增大 TCP 半連接隊列;(增大 net.ipv4.tcp_max_syn_backlog、 listen() 函數(shù)中的 backlog、 net.core.somaxconn)
開啟 tcp_syncookies; (0 :關閉該功能;1:僅當SYN半連接隊列放不下時,再啟用;2:無條件開啟。)
減少 SYN+ACK 重傳次數(shù)(最大重傳次數(shù)由 tcp_synack_retries內核參數(shù)決定(默認值是 5 次))
TCP連接斷開- 四次揮手

第一次握手丟失了,會發(fā)生什么?
觸發(fā)客戶端的重傳,重發(fā)FIN報文,重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制。超過重傳次數(shù)后,在等待一段時間(時間為上一次超時時間的 2 倍),如果還是沒能收到第二次揮手,那么直接進入到 close 狀態(tài)。
第二次握手丟失了,會發(fā)生什么?
ACK 報文是不會重傳的。因此如果第二次揮手丟失,會觸發(fā)客戶端重發(fā)FIN報文。
對于 close 函數(shù)關閉的連接,由于無法再發(fā)送和接收數(shù)據(jù),所以FIN_WAIT2 狀態(tài)不可以持續(xù)太久,而 tcp_fin_timeout 控制了這個狀態(tài)下連接的持續(xù)時長,默認值是 60 秒。即如調用close 關閉的連接,如果在60秒后還沒有收到FIN報文,客戶端(主動關閉方)的連接就會直接關閉。
但是如果主動關閉方使用 shutdown 函數(shù)關閉連接,指定了只關閉發(fā)送方向,而接收方向并沒有關閉,那么意味著主動關閉方還是可以接收數(shù)據(jù)的。
此時,如果主動關閉方一直沒收到第三次揮手,那么主動關閉方的連接將會一直處于 FIN_WAIT2 狀態(tài)(tcp_fin_timeout 無法控制 shutdown 關閉的連接)。
第三次揮手丟失了,會發(fā)生什么?
因為沒收到ACK,服務端重發(fā)FIN報文,重發(fā)次數(shù)由 tcp_orphan_retries 參數(shù)控制。
為什么TIME_WAIT 等待的時間2MSL?
MSL 是 Maximum Segment Lifetime,報文最大生存時間。
網(wǎng)絡中可能存在來自發(fā)送方的數(shù)據(jù)包,當這些發(fā)送方的數(shù)據(jù)包被接收方處理后又會向對方發(fā)送響應,所以一來一回需要等待 2 倍的時間。 2MSL時長 這其實是相當于至少允許報文丟失一次。
如果被動關閉方?jīng)]有收到斷開連接的最后的 ACK 報文,就會觸發(fā)超時重發(fā) FIN 報文,另一方接收到 FIN 后,會重發(fā) ACK 給被動關閉方, 一來一去正好 2 個 MSL。如果客戶端再接收到服務端重傳的FIN報文,那么2MSL時間將重新計時。
為什么需要TIME_WAIT 狀態(tài)?
主動關閉連接的一方,才會有TIME_WAIT 狀態(tài)
主要是兩個原因:
防止歷史連接中的數(shù)據(jù),被后面相同四元組的連接錯誤的接收;
2MSL這個時間足以讓兩個方向上的數(shù)據(jù)包都被丟棄,使得原來連接的數(shù)據(jù)包在網(wǎng)絡中都自然消失,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的。
保證「被動關閉連接」的一方,能被正確的關閉;
TIME_WAIT 過多有什么危害?
占用系統(tǒng)資源,比如文件描述符、內存資源、CPU資源、線程資源等。
占用端口資源,可以通過 net.ipv4.ip_local_port_range參數(shù)指定范圍。
如果客戶端(主動發(fā)起關閉連接方)的 TIME_WAIT 狀態(tài)過多,容易占滿端口;
如果服務端(主動發(fā)起關閉連接方)的 TIME_WAIT 狀態(tài)過多,并不會導致端口資源受限,因為服務端只監(jiān)聽一個端口,而且由于一個四元組唯一確定一個 TCP 連接,因此理論上服務端可以建立很多連接,但是 TCP 連接過多,會占用系統(tǒng)資源,比如文件描述符、內存資源、CPU 資源、線程資源等。
如何優(yōu)化 TIME_WAIT?
優(yōu)化 TIME-WAIT 的幾個方式,都是有利有弊:
打開 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 選項;(復用連接,只對連接發(fā)起方有用)
net.ipv4.tcp_max_tw_buckets;(當系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個值時,系統(tǒng)就會將后面的 TIME_WAIT 連接狀態(tài)重置,這個方法比較暴力)
程序中使用 SO_LINGER ,應用強制使用 RST 關閉。
服務器大量出現(xiàn)TIME_WAIT 狀態(tài)的原因有哪些?
主動方關閉才有TIME_WAIT 狀態(tài)
HTTP 沒有使用長連接,是否關閉了HTTP Keep-Alive
無論是客戶端還是服務端禁用了Keep-Alive,都會由服務端來關閉連接的。
客戶端禁用:本來服務端開啟的作用就是要復用連接,那不再重用連接的時機自然是由服務端控制,因此是由服務端來關閉的。
服務端禁用:在服務端主動關閉時,調用一次close()就可以釋放連接,剩下的工作由內核TCP 棧進行處理,整個過程只有一次 syscall ;但若客戶端關閉,則在寫完最后一個 response 之后需要把這個 socket 放入 readable 隊列,調用 select / epoll 去等待事件;然后調用一次 read() 才能知道連接已經(jīng)被關閉,這其中是兩次 syscall,多一次用戶態(tài)程序被激活執(zhí)行,而且 socket 保持時間也會更長。
HTTP 長連接超時,如客戶端完成一個HTTP請求后,就不再發(fā)起新的請求,此時這個TCP 連接就會一直占用資源??梢酝ㄟ^ngix提供的參數(shù)進行控制。
HTTP 長連接的請求數(shù)量達到上限。
服務器出現(xiàn)大量CLOSE_WAIT 狀態(tài)的原因有哪些?
CLOSE_WAIT 狀態(tài)是「被動關閉方」才會有的狀態(tài),而且如果「被動關閉方」沒有調用 close 函數(shù)關閉連接,那么就無法發(fā)出 FIN 報文,從而無法使得 CLOSE_WAIT 狀態(tài)的連接轉變?yōu)?LAST_ACK 狀態(tài)。
所以,當服務端出現(xiàn)大量 CLOSE_WAIT 狀態(tài)的連接的時候,說明服務端的程序沒有調用 close 函數(shù)關閉連接。
如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)了故障怎么辦?
TCP ?;顧C制,可以設置?;顣r間、探測次數(shù)和時間間隔。但檢測的時間較長。
如果對端正常工作,收到TCP ?;畹奶綔y報文后,保活時間會被重置
如果對端宕機并重啟。收到報文可以響應,但沒有該連接的有效信息,會發(fā)送RST報文
在應用層實現(xiàn)心跳檢測。
如果已經(jīng)建立了連接,但服務端的進程崩潰會發(fā)生什么?
TCP 的連接信息是由內核維護的,所以當服務端的進程崩潰后,內核需要回收該進程的所有 TCP 連接資源,于是內核會發(fā)送第一次揮手 FIN 報文,后續(xù)的揮手過程也都是在內核完成,并不需要進程的參與,所以即使服務端的進程退出了,還是能與客戶端完成 TCP 四次揮手的過程。
關于accept
Linux內核中會維護兩個隊列:
半連接隊列(SYN 隊列):接收到一個 SYN 建立連接請求,處于 SYN_RCVD 狀態(tài);
全連接隊列(Accpet 隊列):已完成 TCP 三次握手過程,處于 ESTABLISHED 狀態(tài);
客戶端 connect 成功返回是在第二次握手,服務端 accept 成功返回是在三次握手成功之后。

沒有accpet,能建立TCP連接嗎?
可以的。 accept 系統(tǒng)調用并不參與TCP 三次握手過程。他只是負責從全連接隊列取出一個已經(jīng)建立連接的 socket,用戶層通過 accept 系統(tǒng)調用拿到了已經(jīng)建立連接的 socket,就可以對該 socket 進行讀寫操作了。
客戶端調用close,連接斷開的流程是什么?
客戶端調用 close,表明客戶端沒有數(shù)據(jù)需要發(fā)送了,則此時會向服務端發(fā)送 FIN 報文,進入 FIN_WAIT_1 狀態(tài);
服務端接收到了 FIN 報文,TCP 協(xié)議棧會為 FIN 包插入一個文件結束符 EOF 到接收緩沖區(qū)中,應用程序可以通過 read 調用來感知這個 FIN 包。這個 EOF 會被放在已排隊等候的其他已接收的數(shù)據(jù)之后,表示在該連接上再無額外數(shù)據(jù)到達。同時發(fā)送ACK,服務端進入 CLOSE_WAIT 狀態(tài);
接著,當處理完數(shù)據(jù)后,自然就會讀到 EOF,于是也調用 close 關閉它的套接字,這會使得服務端發(fā)出一個 FIN 包,之后處于 LAST_ACK 狀態(tài);
客戶端接收到服務端的 FIN 包,并發(fā)送 ACK 給服務端,此時客戶端進入 TIME_WAIT 狀態(tài);
服務端收到 ACK 確認包后,就進入了最后的 CLOSE 狀態(tài);
客戶端經(jīng)過 2MSL 時間之后,也進入 CLOSE 狀態(tài)
沒有l(wèi)isten ,能建立TCP 連接嗎?
答案:可以的。
客戶端是可以自己連自己的形成連接(TCP自連接),也可以兩個客戶端同時向對方發(fā)出請求建立連接(TCP同時打開),這兩個情況都有個共同點,就是沒有服務端參與,也就是沒有 listen,就能 TCP 建立連接。