TCP 半連接隊列和全連接隊列滿了會發(fā)生什么原押?又該如何應(yīng)對胁镐?

原文地址:TCP 半連接隊列和全連接隊列滿了會發(fā)生什么偎血?

一個端口上面的tcp連接創(chuàng)建诸衔,基本都只用一個線程處理。如果大并發(fā)連接請求過來颇玷,處理不了笨农,那么會放入“待處理隊列”。為什么不用多線程呢帖渠?因為創(chuàng)建連接基本都是內(nèi)存操作谒亦,速度很快。

原文地址:Netty系列文章之Netty線程模型

當然如果你想對某個端口開啟多個線程監(jiān)聽空郊,那么可以實現(xiàn)多個Reactor模型份招。單個Reactor包含:一個selector監(jiān)聽器,多個事件處理器Acceptor狞甚,Reader锁摔,Writer等。

主從多線程模型 (最流行)

image.png
  • 存在多個Reactor哼审,每個Reactor都有自己的selector選擇器谐腰,線程和dispatch
  • 主線程中的mainReactor通過自己的selector只監(jiān)控連接建立事件,收到事件后通過Accpetor接收涩盾,將新的連接分配給某個subReactor十气,同時該連接注冊到該subReactor的selector上,達到多個subReactor平均分配sockect的目的春霍。
  • 子線程中的subReactor將mainReactor分配的連接加入連接隊列中通過自己的selector進行監(jiān)聽砸西,并創(chuàng)建一個Handler用于處理后續(xù)事件:
    Handler完成read->業(yè)務(wù)處理->send的完整業(yè)務(wù)流程


    image.png

舉個例子:有1萬個連接通過mainReactor被accpect后創(chuàng)建各自的socket連接,相應(yīng)的sockect被分別注冊到4個subReactor的selector址儒。那么每個selector只需要監(jiān)聽2500個sockect就可以了芹枷,提高了監(jiān)聽效率。我感覺倒不是提高了監(jiān)聽效率离福,因為用了epoll是內(nèi)核通知用戶線程數(shù)據(jù)準備好了杖狼。一次監(jiān)聽2500和一次監(jiān)聽10000監(jiān)聽效率感覺差不多,真正影響效率的是響應(yīng)活躍連接的速度妖爷。比如:如果1w個連接同時大概有100個活躍連接蝶涩,那么一次監(jiān)聽到100個活躍連接理朋,最后一個連接的響應(yīng)是最慢的,需要前面99個處理完绿聘,才能響應(yīng)它嗽上。但是如果是均分到4個selector,那么每個selector平均有25個活躍連接熄攘,最后一個連接的響應(yīng)只要等前24個處理完就可以響應(yīng)了兽愤。

參考原文:談?wù)凬etty的線程模型

image.png

每一個Loop都是一個完整體,包含:Queue,Thread,Selector挪圾。

  1. 創(chuàng)建的socket通過輪訓(xùn)方式的負載均衡分配到不同的NioEventLoop
  2. NioEventLoop在處理讀寫事件時浅萧,為了避免某一個連接處理時間太長導(dǎo)致其他連接無法響應(yīng)。有避免饑餓的處理機制哲思。在NioEventLoop.run方法里面可以看得到洼畅,根據(jù)ioRatio來調(diào)整的,ioRatio默認是50

前言

網(wǎng)上許多博客針對增大 TCP 半連接隊列和全連接隊列的方式如下:

  • 增大 TCP 半連接隊列方式是增大 tcp_max_syn_backlog棚赔;
  • 增大 TCP 全連接隊列方式是增大 listen() 函數(shù)中的 backlog帝簇;

這里先跟大家說下,上面的方式都是不準確的靠益。

“你怎么知道不準確丧肴?”

很簡單呀,因為我做了實驗和看了 TCP 協(xié)議棧的內(nèi)核源碼胧后,發(fā)現(xiàn)要增大這兩個隊列長度芋浮,不是簡簡單單增大某一個參數(shù)就可以的。

接下來绩卤,就會以實戰(zhàn) + 源碼分析途样,帶大家解密 TCP 半連接隊列和全連接隊列。

“源碼分析濒憋,那不是勸退嗎何暇?我們搞 Java 的看不懂呀”

放心,本文的源碼分析不會涉及很深的知識凛驮,因為都被我刪減了裆站,你只需要會條件判斷語句 if、左移右移操作符黔夭、加減法等基本語法宏胯,就可以看懂。

另外本姥,不僅有源碼分析肩袍,還會介紹 Linux 排查半連接隊列和全連接隊列的命令。

“哦婚惫?似乎很有看頭氛赐,那我姑且看一下吧魂爪!”

行,沒有被勸退的小伙伴艰管,值得鼓勵滓侍,下面這圖是本文的提綱:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對牲芋?

本文提綱


正文

什么是 TCP 半連接隊列和全連接隊列撩笆?

在 TCP 三次握手的時候,Linux 內(nèi)核會維護兩個隊列缸浦,分別是:

  • 半連接隊列夕冲,也稱 SYN 隊列;
  • 全連接隊列餐济,也稱 accepet 隊列耘擂;

服務(wù)端收到客戶端發(fā)起的 SYN 請求后,內(nèi)核會把該連接存儲到半連接隊列絮姆,并向客戶端響應(yīng) SYN+ACK,接著客戶端會返回 ACK秩霍,服務(wù)端收到第三次握手的 ACK 后篙悯,內(nèi)核會把連接從半連接隊列移除,然后創(chuàng)建新的完全的連接铃绒,并將其添加到 accept 隊列鸽照,等待進程調(diào)用 accept 函數(shù)****時****把連接取出來。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么颠悬?又該如何應(yīng)對矮燎?

半連接隊列與全連接隊列

不管是半連接隊列還是全連接隊列,都有最大長度限制赔癌,超過限制時诞外,內(nèi)核會直接丟棄,或返回 RST 包灾票。


實戰(zhàn) - TCP 全連接隊列溢出

如何知道應(yīng)用程序的 TCP 全連接隊列大邢恳辍?

在服務(wù)端可以使用 ss 命令刊苍,來查看 TCP 全連接隊列的情況:

但需要注意的是 ss 命令獲取的 Recv-Q/Send-Q 在「LISTEN 狀態(tài)」和「非 LISTEN 狀態(tài)」所表達的含義是不同的既们。從下面的內(nèi)核代碼可以看出區(qū)別:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對正什?

在「LISTEN 狀態(tài)」時啥纸,Recv-Q/Send-Q 表示的含義如下:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對婴氮?
  • Recv-Q:當前全連接隊列的大小斯棒,也就是當前已完成三次握手并等待服務(wù)端accept() 的 TCP 連接個數(shù)馒索;
  • Send-Q:當前全連接最大隊列長度,上面的輸出結(jié)果說明監(jiān)聽 8088 端口的 TCP 服務(wù)進程名船,最大全連接長度為 128绰上;

在「非 LISTEN 狀態(tài)」時,Recv-Q/Send-Q 表示的含義如下:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么渠驼?又該如何應(yīng)對蜈块?
  • Recv-Q:已收到但未被應(yīng)用進程讀取的字節(jié)數(shù);
  • Send-Q:已發(fā)送但未收到確認的字節(jié)數(shù)迷扇;

如何模擬 TCP 全連接隊列溢出的場景百揭?

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對蜓席?

測試環(huán)境

實驗環(huán)境:

  • 客戶端和服務(wù)端都是 CentOs 6.5 器一,Linux 內(nèi)核版本 2.6.32
  • 服務(wù)端 IP 192.168.3.200,客戶端 IP 192.168.3.100
  • 服務(wù)端是 Nginx 服務(wù)厨内,端口為 8088

這里先介紹下 wrk 工具祈秕,它是一款簡單的 HTTP 壓測工具,它能夠在單機多核 CPU 的條件下雏胃,使用系統(tǒng)自帶的高性能 I/O 機制请毛,通過多線程和事件模式,對目標機器產(chǎn)生大量的負載瞭亮。

本次模擬實驗就使用 wrk 工具來壓力測試服務(wù)端方仿,發(fā)起大量的請求,一起看看服務(wù)端 TCP 全連接隊列滿了會發(fā)生什么统翩?有什么觀察指標仙蚜?

客戶端執(zhí)行 wrk 命令對服務(wù)端發(fā)起壓力測試,并發(fā) 3 萬個連接:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么厂汗?又該如何應(yīng)對委粉?

在服務(wù)端可以使用 ss 命令,來查看當前 TCP 全連接隊列的情況:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么面徽?又該如何應(yīng)對艳丛?

其間共執(zhí)行了兩次 ss 命令,從上面的輸出結(jié)果趟紊,可以發(fā)現(xiàn)當前 TCP 全連接隊列上升到了 129 大小氮双,超過了最大 TCP 全連接隊列。

當超過了 TCP 最大全連接隊列霎匈,服務(wù)端則會丟掉后續(xù)進來的 TCP 連接戴差,丟掉的 TCP 連接的個數(shù)會被統(tǒng)計起來,我們可以使用 netstat -s 命令來查看:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么铛嘱?又該如何應(yīng)對暖释?

上面看到的 41150 times 袭厂,表示全連接隊列溢出的次數(shù),注意這個是累計值球匕∥苹牵可以隔幾秒鐘執(zhí)行下,如果這個數(shù)字一直在增加的話肯定全連接隊列偶爾滿了亮曹。

從上面的模擬結(jié)果橄杨,可以得知,當服務(wù)端并發(fā)處理大量請求時照卦,如果 TCP 全連接隊列過小式矫,就容易溢出。發(fā)生 TCP 全連接隊溢出的時候役耕,后續(xù)的請求就會被丟棄采转,這樣就會出現(xiàn)服務(wù)端請求數(shù)量上不去的現(xiàn)象。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么瞬痘?又該如何應(yīng)對故慈?

全連接隊列溢出

全連接隊列滿了,就只會丟棄連接嗎图云?

實際上惯悠,丟棄連接只是 Linux 的默認行為,我們還可以選擇向客戶端發(fā)送 RST 復(fù)位報文竣况,告訴客戶端連接已經(jīng)建立失敗。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么筒严?又該如何應(yīng)對丹泉?

tcp_abort_on_overflow 共有兩個值分別是 0 和 1,其分別表示:

  • 0 :表示如果全連接隊列滿了鸭蛙,那么 server 扔掉 client 發(fā)過來的 ack 摹恨;
  • 1 :表示如果全連接隊列滿了,那么 server 發(fā)送一個 reset 包給 client娶视,表示廢掉這個握手過程和這個連接晒哄;

如果要想知道客戶端連接不上服務(wù)端,是不是服務(wù)端 TCP 全連接隊列滿的原因肪获,那么可以把 tcp_abort_on_overflow 設(shè)置為 1寝凌,這時如果在客戶端異常中可以看到很多connection reset by peer 的錯誤,那么就可以證明是由于服務(wù)端 TCP 全連接隊列溢出的問題孝赫。

通常情況下较木,應(yīng)當把 tcp_abort_on_overflow 設(shè)置為 0,因為這樣更有利于應(yīng)對突發(fā)流量青柄。

舉個例子伐债,當 TCP 全連接隊列滿導(dǎo)致服務(wù)器丟掉了 ACK预侯,與此同時,客戶端的連接狀態(tài)卻是 ESTABLISHED峰锁,進程就在建立好的連接上發(fā)送請求萎馅。只要服務(wù)器沒有為請求回復(fù) ACK,請求就會被多次重發(fā)虹蒋。如果服務(wù)器上的進程只是短暫的繁忙造成 accept 隊列滿糜芳,那么當 TCP 全連接隊列有空位時,再次接收到的請求報文由于含有 ACK千诬,仍然會觸發(fā)服務(wù)器端成功建立連接耍目。

所以,tcp_abort_on_overflow 設(shè)為 0 可以提高連接建立的成功率徐绑,只有你非承巴裕肯定 TCP 全連接隊列會長期溢出時,才能設(shè)置為 1 以盡快通知客戶端毅访。

如何增大 TCP 全連接隊列呢?

是的盘榨,當發(fā)現(xiàn) TCP 全連接隊列發(fā)生溢出的時候喻粹,我們就需要增大該隊列的大小,以便可以應(yīng)對客戶端大量的請求草巡。

TCP 全連接隊列足最大值取決于 somaxconn 和 backlog 之間的最小值守呜,也就是 min(somaxconn, backlog)。從下面的 Linux 內(nèi)核代碼可以得知:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么山憨?又該如何應(yīng)對查乒?
  • somaxconn 是 Linux 內(nèi)核的參數(shù),默認值是 128郁竟,可以通過/proc/sys/net/core/somaxconn 來設(shè)置其值玛迄;
  • backlog 是 listen(int sockfd, int backlog) 函數(shù)中的 backlog 大小,Nginx 默認值是 511棚亩,可以通過修改配置文件設(shè)置其長度蓖议;

前面模擬測試中,我的測試環(huán)境:

  • somaxconn 是默認值 128讥蟆;
  • Nginx 的 backlog 是默認值 511

所以測試環(huán)境的 TCP 全連接隊列最大值為 min(128, 511)勒虾,也就是 128,可以執(zhí)行ss 命令查看:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么攻询?又該如何應(yīng)對从撼?

現(xiàn)在我們重新壓測,把 TCP 全連接隊列搞大,把 somaxconn 設(shè)置成 5000:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么低零?又該如何應(yīng)對婆翔?

接著把 Nginx 的 backlog 也同樣設(shè)置成 5000:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對掏婶?

最后要重啟 Nginx 服務(wù)啃奴,因為只有重新調(diào)用 listen() 函數(shù), TCP 全連接隊列才會重新初始化雄妥。

重啟完后 Nginx 服務(wù)后最蕾,服務(wù)端執(zhí)行 ss 命令,查看 TCP 全連接隊列大欣涎帷:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么瘟则?又該如何應(yīng)對?

從執(zhí)行結(jié)果枝秤,可以發(fā)現(xiàn) TCP 全連接最大值為 5000醋拧。

增大 TCP 全連接隊列后,繼續(xù)壓測

客戶端同樣以 3 萬個連接并發(fā)發(fā)送請求給服務(wù)端:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么淀弹?又該如何應(yīng)對丹壕?

服務(wù)端執(zhí)行 ss 命令,查看 TCP 全連接隊列使用情況:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么薇溃?又該如何應(yīng)對菌赖?

從上面的執(zhí)行結(jié)果,可以發(fā)現(xiàn)全連接隊列使用增長的很快沐序,但是一直都沒有超過最大值琉用,所以就不會溢出,那么 netstat -s 就不會有 TCP 全連接隊列溢出個數(shù)的顯示:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么策幼?又該如何應(yīng)對辕羽?

說明 TCP 全連接隊列最大值從 128 增大到 5000 后,服務(wù)端抗住了 3 萬連接并發(fā)請求垄惧,也沒有發(fā)生全連接隊列溢出的現(xiàn)象了。

如果持續(xù)不斷地有連接因為 TCP 全連接隊列溢出被丟棄绰寞,就應(yīng)該調(diào)大 backlog 以及 somaxconn 參數(shù)到逊。


實戰(zhàn) - TCP 半連接隊列溢出

如何查看 TCP 半連接隊列長度?

很遺憾滤钱,TCP 半連接隊列長度的長度觉壶,沒有像全連接隊列那樣可以用 ss 命令查看。

但是我們可以抓住 TCP 半連接的特點件缸,就是服務(wù)端處于 SYN_RECV 狀態(tài)的 TCP 連接铜靶,就是在 TCP 半連接隊列。

于是他炊,我們可以使用如下命令計算當前 TCP 半連接隊列長度:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么争剿?又該如何應(yīng)對已艰?

如何模擬 TCP 半連接隊列溢出場景?

模擬 TCP 半連接溢出場景不難蚕苇,實際上就是對服務(wù)端一直發(fā)送 TCP SYN 包哩掺,但是不回第三次握手 ACK,這樣就會使得服務(wù)端有大量的處于 SYN_RECV 狀態(tài)的 TCP 連接涩笤。

這其實也就是所謂的 SYN 洪泛嚼吞、SYN 攻擊、DDos 攻擊蹬碧。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么舱禽?又該如何應(yīng)對?

測試環(huán)境

實驗環(huán)境:

  • 客戶端和服務(wù)端都是 CentOs 6.5 恩沽,Linux 內(nèi)核版本 2.6.32
  • 服務(wù)端 IP 192.168.3.200誊稚,客戶端 IP 192.168.3.100
  • 服務(wù)端是 Nginx 服務(wù),端口為 8088

注意:本次模擬實驗是沒有開啟 tcp_syncookies飒筑,關(guān)于 tcp_syncookies 的作用片吊,后續(xù)會說明。

本次實驗使用 hping3 工具模擬 SYN 攻擊:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么协屡?又該如何應(yīng)對俏脊?

當服務(wù)端受到 SYN 攻擊后,連接服務(wù)端 ssh 就會斷開了肤晓,無法再連上爷贫。只能在服務(wù)端主機上執(zhí)行查看當前 TCP 半連接隊列大小:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么补憾?又該如何應(yīng)對漫萄?

同時,還可以通過 netstat -s 觀察半連接隊列溢出的情況:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么盈匾?又該如何應(yīng)對腾务?

上面輸出的數(shù)值是累計值,表示共有多少個 TCP 連接因為半連接隊列溢出而被丟棄削饵。隔幾秒執(zhí)行幾次稿黄,如果有上升的趨勢锣枝,說明當前存在半連接隊列溢出的現(xiàn)象约急。

大部分人都說 tcp_max_syn_backlog 是指定半連接隊列的大小咒彤,是真的嗎?

很遺憾劈伴,半連接隊列的大小并不單單只跟 tcp_max_syn_backlog 有關(guān)系密末。

上面模擬 SYN 攻擊場景時,服務(wù)端的 tcp_max_syn_backlog 的默認值如下:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對严里?

但是在測試的時候發(fā)現(xiàn)新啼,服務(wù)端最多只有 256 個半連接隊列,而不是 512田炭,所以半連接隊列的最大長度不一定由 tcp_max_syn_backlog 值決定的师抄。

接下來,走進 Linux 內(nèi)核的源碼教硫,來分析 TCP 半連接隊列的最大值是如何決定的叨吮。

TCP 第一次握手(收到 SYN 包)的 Linux 內(nèi)核代碼如下,其中縮減了大量的代碼瞬矩,只需要重點關(guān)注 TCP 半連接隊列溢出的處理邏輯:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么茶鉴?又該如何應(yīng)對?

從源碼中景用,我可以得出共有三個條件因隊列長度的關(guān)系而被丟棄的:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么涵叮?又該如何應(yīng)對?
  1. 如果半連接隊列滿了伞插,并且沒有開啟 tcp_syncookies割粮,則會丟棄;
  2. 若全連接隊列滿了媚污,且沒有重傳 SYN+ACK 包的連接請求多于 1 個舀瓢,則會丟棄;
  3. 如果沒有開啟 tcp_syncookies耗美,并且 max_syn_backlog 減去 當前半連接隊列長度小于 (max_syn_backlog >> 2)京髓,則會丟棄;

關(guān)于 tcp_syncookies 的設(shè)置商架,后面在詳細說明堰怨,可以先給大家說一下,開啟 tcp_syncookies 是緩解 SYN 攻擊其中一個手段蛇摸。

接下來备图,我們繼續(xù)跟一下檢測半連接隊列是否滿的函數(shù)
inet_csk_reqsk_queue_is_full 和 檢測全連接隊列是否滿的函數(shù) sk_acceptq_is_full :

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對赶袄?

從上面源碼诬烹,可以得知:

  • 連接隊列的最大值是 sk_max_ack_backlog 變量,sk_max_ack_backlog 實際上是在 listen() 源碼里指定的弃鸦,也就是 min(somaxconn, backlog)
  • 連接隊列的最大值是 max_qlen_log 變量幢痘,max_qlen_log 是在哪指定的呢唬格?現(xiàn)在暫時還不知道,我們繼續(xù)跟進;

我們繼續(xù)跟進代碼购岗,看一下是哪里初始化了半連接隊列的最大值 max_qlen_log:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么汰聋?又該如何應(yīng)對?

從上面的代碼中喊积,我們可以算出 max_qlen_log 是 8烹困,于是代入到 檢測半連接隊列是否滿的函數(shù) reqsk_queue_is_full :

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對乾吻?

也就是 qlen >> 8 什么時候為 1 就代表半連接隊列滿了髓梅。這計算這不難,很明顯是當 qlen 為 256 時绎签,256 >> 8 = 1枯饿。

至此,總算知道為什么上面模擬測試 SYN 攻擊的時候诡必,服務(wù)端處于 SYN_RECV 連接最大只有 256 個奢方。

可見,半連接隊列最大值不是單單由 max_syn_backlog 決定爸舒,還跟 somaxconn 和 backlog 有關(guān)系蟋字。

在 Linux 2.6.32 內(nèi)核版本,它們之間的關(guān)系扭勉,總體可以概況為:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么鹊奖?又該如何應(yīng)對?

1. 當 max_syn_backlog > min(somaxconn, backlog) 時剖效, 半連接隊列最大值 max_qlen_log = min(somaxconn, backlog) * 2;

2. 當 max_syn_backlog < min(somaxconn, backlog) 時嫉入, 半連接隊列最大值 max_qlen_log = max_syn_backlog * 2;

半連接隊列最大值 max_qlen_log 就表示服務(wù)端處于 SYN_REVC 狀態(tài)的最大個數(shù)嗎?

依然很遺憾璧尸,并不是咒林。

max_qlen_log 是理論半連接隊列最大值,并不一定代表服務(wù)端處于 SYN_REVC 狀態(tài)的最大個數(shù)爷光。

在前面我們在分析 TCP 第一次握手(收到 SYN 包)時會被丟棄的三種條件:

  1. 如果半連接隊列滿了垫竞,并且沒有開啟 tcp_syncookies,則會丟棄蛀序;
  2. 若全連接隊列滿了欢瞪,且沒有重傳 SYN+ACK 包的連接請求多于 1 個,則會丟棄徐裸;
  3. 如果沒有開啟 tcp_syncookies遣鼓,并且 max_syn_backlog 減去 當前半連接隊列長度小于 (max_syn_backlog >> 2),則會丟棄重贺;

假設(shè)條件 1 當前半連接隊列的長度 「沒有超過」理論的半連接隊列最大值 max_qlen_log骑祟,那么如果條件 3 成立回懦,則依然會丟棄 SYN 包,也就會使得服務(wù)端處于 SYN_REVC 狀態(tài)的最大個數(shù)不會是理論值 max_qlen_log次企。

似乎很難理解怯晕,我們繼續(xù)接著做實驗,實驗見真知缸棵。

服務(wù)端環(huán)境如下:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么舟茶?又該如何應(yīng)對?

配置完后堵第,服務(wù)端要重啟 Nginx吧凉,因為全連接隊列最大和半連接隊列最大值是在 listen() 函數(shù)初始化。

根據(jù)前面的源碼分析型诚,我們可以計算出半連接隊列 max_qlen_log 的最大值為 256:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么客燕?又該如何應(yīng)對?

客戶端執(zhí)行 hping3 發(fā)起 SYN 攻擊:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么狰贯?又該如何應(yīng)對也搓?

服務(wù)端執(zhí)行如下命令,查看處于 SYN_RECV 狀態(tài)的最大個數(shù):

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么涵紊?又該如何應(yīng)對傍妒?

可以發(fā)現(xiàn),服務(wù)端處于 SYN_RECV 狀態(tài)的最大個數(shù)并不是 max_qlen_log 變量的值摸柄。

這就是前面所說的原因:如果當前半連接隊列的長度 「沒有超過」理論半連接隊列最大值 max_qlen_log颤练,那么如果條件 3 成立,則依然會丟棄 SYN 包驱负,也就會使得服務(wù)端處于 SYN_REVC 狀態(tài)的最大個數(shù)不會是理論值 max_qlen_log嗦玖。

我們來分析一波條件 3 :

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對跃脊?

從上面的分析宇挫,可以得知如果觸發(fā)「當前半連接隊列長度 > 192」條件,TCP 第一次握手的 SYN 包是會被丟棄的酪术。

在前面我們測試的結(jié)果器瘪,服務(wù)端處于 SYN_RECV 狀態(tài)的最大個數(shù)是 193,正好是觸發(fā)了條件 3绘雁,所以處于 SYN_RECV 狀態(tài)的個數(shù)還沒到「理論半連接隊列最大值 256」橡疼,就已經(jīng)把 SYN 包丟棄了。

所以庐舟,服務(wù)端處于 SYN_RECV 狀態(tài)的最大個數(shù)分為如下兩種情況:

1. 如果「當前半連接隊列」沒超過「理論半連接隊列最大值」欣除,但是超過 max_syn_backlog - (max_syn_backlog >> 2),那么處于 SYN_RECV 狀態(tài)的最大個數(shù)就是 max_syn_backlog - (max_syn_backlog >> 2)挪略;

2. 如果「當前半連接隊列」超過「理論半連接隊列最大值」耻涛,那么處于 SYN_RECV 狀態(tài)的最大個數(shù)就是「理論半連接隊列最大值」废酷;

每個 Linux 內(nèi)核版本「理論」半連接最大值計算方式會不同。

在上面我們是針對 Linux 2.6.32 版本分析的「理論」半連接最大值的算法抹缕,可能每個版本有些不同。

比如在 Linux 5.0.0 的時候墨辛,「理論」半連接最大值就是全連接隊列最大值卓研,但依然還是有隊列溢出的三個條件:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對睹簇?

如果 SYN 半連接隊列已滿奏赘,只能丟棄連接嗎?

并不是這樣太惠,開啟 syncookies 功能就可以在不使用 SYN 半連接隊列的情況下成功建立連接磨淌,在前面我們源碼分析也可以看到這點,當開啟了 syncookies 功能就不會丟棄連接凿渊。

syncookies 是這么做的:服務(wù)器根據(jù)當前狀態(tài)計算出一個值梁只,放在己方發(fā)出的 SYN+ACK 報文中發(fā)出,當客戶端返回 ACK 報文時埃脏,取出該值驗證搪锣,如果合法,就認為連接建立成功彩掐,如下圖所示构舟。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對堵幽?

開啟 syncookies 功能

syncookies 參數(shù)主要有以下三個值:

  • 0 值狗超,表示關(guān)閉該功能;
  • 1 值朴下,表示僅當 SYN 半連接隊列放不下時努咐,再啟用它;
  • 2 值桐猬,表示無條件開啟功能麦撵;

那么在應(yīng)對 SYN 攻擊時,只需要設(shè)置為 1 即可:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么溃肪?又該如何應(yīng)對免胃?

如何防御 SYN 攻擊?

這里給出幾種防御 SYN 攻擊的方法:

  • 增大半連接隊列惫撰;
  • 開啟 tcp_syncookies 功能
  • 減少 SYN+ACK 重傳次數(shù)

方式一:增大半連接隊列

在前面源碼和實驗中羔沙,得知要想增大半連接隊列,我們得知不能只單純增大 tcp_max_syn_backlog 的值厨钻,還需一同增大 somaxconn 和 backlog扼雏,也就是增大全連接隊列坚嗜。否則,只單純增大 tcp_max_syn_backlog 是無效的诗充。

增大 tcp_max_syn_backlog 和 somaxconn 的方法是修改 Linux 內(nèi)核參數(shù):

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么苍蔬?又該如何應(yīng)對?

增大 backlog 的方式蝴蜓,每個 Web 服務(wù)都不同碟绑,比如 Nginx 增大 backlog 的方法如下:

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么?又該如何應(yīng)對茎匠?

最后格仲,改變了如上這些參數(shù)后,要重啟 Nginx 服務(wù)诵冒,因為半連接隊列和全連接隊列都是在 listen() 初始化的凯肋。

方式二:開啟 tcp_syncookies 功能

開啟 tcp_syncookies 功能的方式也很簡單,修改 Linux 內(nèi)核參數(shù):

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么汽馋?又該如何應(yīng)對侮东?

方式三:減少 SYN+ACK 重傳次數(shù)

當服務(wù)端受到 SYN 攻擊時,就會有大量處于 SYN_REVC 狀態(tài)的 TCP 連接惭蟋,處于這個狀態(tài)的 TCP 會重傳 SYN+ACK 苗桂,當重傳超過次數(shù)達到上限后,就會斷開連接告组。

那么針對 SYN 攻擊的場景煤伟,我們可以減少 SYN+ACK 的重傳次數(shù),以加快處于 SYN_REVC 狀態(tài)的 TCP 連接斷開木缝。

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么便锨?又該如何應(yīng)對?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末我碟,一起剝皮案震驚了整個濱河市放案,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌矫俺,老刑警劉巖吱殉,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異厘托,居然都是意外死亡友雳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門铅匹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來押赊,“玉大人,你說我怎么就攤上這事包斑×鹘福” “怎么了涕俗?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長神帅。 經(jīng)常有香客問我再姑,道長,這世上最難降的妖魔是什么找御? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任询刹,我火速辦了婚禮,結(jié)果婚禮上萎坷,老公的妹妹穿的比我還像新娘。我一直安慰自己沐兰,他們只是感情好哆档,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著住闯,像睡著了一般瓜浸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上比原,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天插佛,我揣著相機與錄音,去河邊找鬼量窘。 笑死雇寇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蚌铜。 我是一名探鬼主播锨侯,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼冬殃!你這毒婦竟也來了囚痴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤审葬,失蹤者是張志新(化名)和其女友劉穎深滚,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涣觉,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡痴荐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了旨枯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹬昌。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖攀隔,靈堂內(nèi)的尸體忽然破棺而出皂贩,到底是詐尸還是另有隱情栖榨,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布婴栽,位于F島的核電站,受9級特大地震影響辈末,放射性物質(zhì)發(fā)生泄漏愚争。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一挤聘、第九天 我趴在偏房一處隱蔽的房頂上張望轰枝。 院中可真熱鬧,春花似錦组去、人聲如沸鞍陨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诚撵。三九已至,卻和暖如春键闺,著一層夾襖步出監(jiān)牢的瞬間寿烟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工辛燥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留筛武,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓购桑,卻偏偏與公主長得像畅铭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勃蜘,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359