從TCPIP協議談Linux核心引數最佳化(值得收藏)

從TCP/IP協議談Linux核心引數最佳化(值得收藏)

在硬體資源有限的情況下,最大的壓榨伺服器效能,提高伺服器的併發處理能力,是很多技術人員思考的問題,除了最佳化Nginx/PHP-FPM/Mysql/Redis這類服務軟體配置外,還可以透過修改Linux的核心相關TCP引數,來最大的提高伺服器效能。

在Linux核心引數最佳化之前,我們需要先搞懂TCP/IP協議,這是我們實施最佳化的理論依據。

TCP/IP協議

TCP/IP協議是十分複雜的協議,完全掌握不是一件容易的事情,但作為基本知識,我們必須知道TCP/IP協的

三次握手

四次揮手

的邏輯過程。

三次握手

所謂三次握手是指建立一個 TCP 連線時需要客戶端和伺服器端總共傳送三個包以確認連線的建立。在socket程式設計中,這一過程由客戶端執行connect來觸發。

三次握手流程圖:

從TCP/IP協議談Linux核心引數最佳化(值得收藏)

三次握手流程

第一次握手

:客戶端將標誌位SYN置為1,隨機產生一個值seq=J,並將該資料包傳送給伺服器端,客戶端進入

SYN_SENT

狀態,等待伺服器端確認。

第二次握手

:伺服器端收到資料包後由標誌位

SYN=1

知道客戶端請求建立連線,伺服器端將標誌位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,並將該資料包傳送給客戶端以確認連線請求,伺服器端進入

SYN_RCVD

狀態。

第三次握手

:客戶端收到確認後,檢查ack是否為J+1,ACK是否為1,如果正確則將標誌位ACK置為1,ack=K+1,並將該資料包傳送給伺服器端,伺服器端檢查ack是否為K+1,ACK是否為1,如果正確則連線建立成功,客戶端和伺服器端進入

ESTABLISHED

狀態,完成三次握手,隨後客戶端與伺服器端之間可以開始傳輸資料了。

四次揮手

四次揮手即終止TCP連線,就是指斷開一個TCP連線時,需要客戶端和服務端總共傳送4個包以確認連線的斷開。在socket程式設計中,這一過程由客戶端或服務端任一方執行close來觸發。

由於TCP連線是全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成資料傳送任務後,傳送一個FIN來終止這一方向的連線,收到一個FIN只是意味著這一方向上沒有資料流動了,即不會再收到資料了,但是在這個TCP連線上仍然能夠傳送資料,直到這一方向也傳送了FIN。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉。

四次揮手的流程圖:

從TCP/IP協議談Linux核心引數最佳化(值得收藏)

四次揮手流程

中斷連線端可以是客戶端,也可以是伺服器端。

第一次揮手

:客戶端傳送一個FIN=M,用來關閉客戶端到伺服器端的資料傳送,客戶端進入

FIN_WAIT_1

狀態。意思是說”我客戶端沒有資料要發給你了”,但是如果你伺服器端還有資料沒有傳送完成,則不必急著關閉連線,可以繼續傳送資料。

第二次揮手

:伺服器端收到FIN後,先發送ack=M+1,告訴客戶端,你的請求我收到了,但是我還沒準備好,請繼續你等我的訊息。這個時候客戶端就進入

FIN_WAIT_2

狀態,繼續等待伺服器端的FIN報文。

第三次揮手

:當伺服器端確定資料已傳送完成,則向客戶端傳送FIN=N報文,告訴客戶端,好了,我這邊資料發完了,準備好關閉連線了。伺服器端進入

LAST_ACK

狀態。

第四次揮手:客戶端收到FIN=N報文後,就知道可以關閉連線了,但是他還是不相信網路,怕伺服器端不知道要關閉,所以傳送ack=N+1後進入

TIME_WAIT

狀態,如果Server端沒有收到ACK則可以重傳。伺服器端收到ACK後,就知道可以斷開連線了。客戶端

等待了2MSL後依然沒有收到回覆,則證明伺服器端已正常關閉

,那好,我客戶端也可以關閉連線了。最終完成了四次握手。

序列號與確認應答

大家都知道TCP/IP協議是以一種

高可靠

的通訊協議,透過

序列號

確認應答

來保障通訊高可靠,有如下幾個關鍵點:

當傳送端的資料到達接收主機時,接收端主機會返回一個已收到訊息的通知。這個訊息叫做確認應答(ACK)。當傳送端將資料發出之後會等待對端的確認應答。如果有確認應答,說明資料已經成功到達對端。

反之,則資料丟失的可能性很大

在一定時間內沒有等待到確認應答,傳送端就可以認為資料已經丟失,並進行

重發

。由此,即使產生了丟包,仍然能夠保證資料能夠到達對端,實現可靠傳輸。

未收到確認應答並不意味著資料一定丟失。也有可能是資料對方已經收到,只是返回的確認應答在途中丟失。這種情況也會導致傳送端誤以為資料沒有到達目的地而重發資料。

此外,也有可能因為一些其他原因導致確認應答延遲到達,在源主機重發資料以後才到達的情況也屢見不鮮。此時,源主機只要按照機制重發資料即可。

對於目標主機來說,反覆收到相同的資料是不可取的。為了對上層應用提供可靠的傳輸,目標主機必須放棄重複的資料包。為此我們引入了序列號。

序列號是按照順序給傳送資料的每一個位元組(8位位元組)都標上號碼的編號。接收端查詢接收資料 TCP 首部中的序列號和資料的長度,將自己下一步應該接收的序列號作為確認應答返送回去。透過序列號和確認應答號,TCP 能夠識別是否已經接收資料,又能夠判斷是否需要接收,從而實現可靠傳輸。

重發超時是指在重發資料之前,等待確認應答到來的那個特定時間間隔。

如果超過這個時間仍未收到確認應答,傳送端將進行資料重發。最理想的是,找到一個最小時間,它能保證“確認應答一定能在這個時間內返回”。

TCP 要求不論處在何種網路環境下都要提供高效能通訊,並且無論網路擁堵情況發生何種變化,都必須保持這一特性。為此,它在每次發包時都會計算往返時間及其偏差。將這個往返時間和偏差時間相加,重發超時的時間就是比這個總和要稍大一點的值。

資料被重發之後若還是收不到確認應答,則進行再次傳送。此時,等待確認應答的時間將會以2倍、4倍的指數函式延長。

此外,

資料也不會被無限、反覆地重發。達到一定重發次數之後,如果仍沒有任何確認應答返回,就會判斷為網路或對端主機發生了異常,強制關閉連線。並且通知應用通訊異常強行終止。

TCP/IP協議缺陷

瞭解了TCP/IP協議之後,我們就會發現幾個問題:

在三次握手中,如果客戶端發起第一次握手後就中斷或者不響應伺服器發回的

ACK=1

資料包,那伺服器就會不斷的重試傳送資料包,直到超時。 沒錯,這就是

SYN FLOOD攻擊原理

在四次揮手中,主動關閉連線的客戶端處在TIME_WAIT狀態後,會一直持續2MSL時間長度,MSL就是maximum segment lifetime(最大分節生命期),這是一個IP資料包能在網際網路上生存的最長時間,超過這個時間將在網路中消失(TIME_WAIT狀態一般維持在1-4分鐘)。透過2MSL時間長度來確保舊的連線狀態不會對新連線產生影響。處於TIME_WAIT狀態的連線佔用的資源不會被核心釋放,所以作為伺服器,在可能的情 況下,儘量不要主動斷開連線,以減少TIME_WAIT狀態造成的資源浪費。如果我們的伺服器是負載均衡伺服器,上游伺服器長時間沒有影響,負載均衡伺服器將主動關閉連結,高併發場景下將導致

TIME_WAIT狀態的累積

在四次揮手中,如果客戶端在收到FIN 報文後,應用沒有返回 ACK,服務端同樣會不斷嘗試傳送FIN報文,這樣服務端就會出現

CLOSE_WAIT

狀態的累積。

SYN Flood攻擊

Syn Flood攻擊是當前網路上最為常見的DDoS攻擊,也是最為經典的拒絕服務攻擊,它利用了TCP協議實現上的一個缺陷,透過向網路服務所在埠傳送大量的偽造源地址的攻擊報文,就可能造成目標伺服器中的半開連線佇列被佔滿,從而阻止其他合法使用者進行訪問。

Syn Flood攻擊原理

攻擊者首先偽造地址對伺服器發起SYN請求(我可以建立連線嗎?),伺服器就會迴應一個ACK+SYN(可以+請確認)。而真實的IP會認為,我沒有傳送請求,不作迴應。伺服器沒有收到迴應,會重試3-5次並且等待一個

SYN Time

(一般30秒-2分鐘)後,丟棄這個連線。

如果攻擊者大量傳送這種偽造源地址的SYN請求,伺服器端將會消耗非常多的資源來處理這種半連線,儲存遍歷會消耗非常多的CPU時間和記憶體,何況還要不斷對這個列表中的IP進行SYN+ACK的重試。TCP是可靠協議,這時就會重傳報文,預設重試次數為5次,重試的間隔時間從1s開始每次都番倍,分別為1s + 2s + 4s + 8s +16s = 31s,第5次發出後還要等32s才知道第5次也超時了,所以一共是31 + 32 = 63s。

一段假的syn報文,會佔用TCP準備佇列63s之久,而半連線佇列預設為1024,在沒有任何防護的情況下,每秒傳送20個偽造syn包,就足夠撐爆半連線佇列,從而使真正的連線無法建立,無法響應正常請求。 最後的結果是伺服器無暇理睬正常的連線請求—拒絕服務。

核心TCP引數最佳化

編輯檔案

/etc/sysctl.conf

,加入以下內容:

net。ipv4。tcp_fin_timeout = 2net。ipv4。tcp_tw_reuse = 1net。ipv4。tcp_tw_recycle = 1net。ipv4。tcp_syncookies = 1net。ipv4。tcp_keepalive_time = 600net。ipv4。ip_local_port_range = 4000 65000net。ipv4。tcp_max_syn_backlog = 16384net。ipv4。tcp_max_tw_buckets = 36000net。ipv4。route。gc_timeout = 100net。ipv4。tcp_syn_retries = 1net。ipv4。tcp_synack_retries = 1net。core。somaxconn = 16384net。core。netdev_max_backlog = 16384net。ipv4。tcp_max_orphans = 16384

然後執行 sysctl -p 讓引數生效。

作用說明:

net。ipv4。tcp_fin_timeout 表示套接字由本端要求關閉,這個引數決定了它保持在FIN-WAIT-2狀態的時間,預設值是60秒。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_fin_timeout 60

net。ipv4。tcp_tw_reuse 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設值為0,表示關閉。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_tw_reuse 0

net。ipv4。tcp_tw_recycle 表示開啟TCP連線中TIME-WAIT sockets的快速回收。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_tw_recycle,預設為0,表示關閉。 提示:reuse和recycle這兩個引數是為防止生產環境下Web、Squid等業務伺服器time_wait網路狀態數量過多設定的。

net。ipv4。tcp_syncookies 表示開啟SYN Cookies功能。當出現SYN等待佇列溢位時,啟用Cookies來處理,可防範少量SYN攻擊,這個引數也可以不新增。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_syncookies,預設值為1

net。ipv4。tcp_keepalive_time 表示當keepalive啟用時,TCP傳送keepalive訊息的頻度。預設是2小時,建議改為10分鐘。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_keepalive_time,預設為7200秒。

net。ipv4。ip_local_port_range 該選項用來設定允許系統開啟的埠範圍,即用於向外連線的埠範圍。 該引數對應系統路徑為:/proc/sys/net/ipv4/ip_local_port_range 32768 61000

net。ipv4。tcp_max_syn_backlog 表示SYN佇列的長度,預設為1024,建議加大佇列的長度為8192或更多,這樣可以容納更多等待連線的網路連線數。 該引數為伺服器端用於記錄那些尚未收到客戶端確認資訊的連線請求最大值。 該引數物件系統路徑為:/proc/sys/net/ipv4/tcp_max_syn_backlog

net。ipv4。tcp_max_tw_buckets 表示系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數值,TIME_WAIT套接字將立刻被清除並列印警告資訊。 預設為180000,對於Apache、Nginx等伺服器來說可以將其調低一點,如改為5000~30000,不通業務的伺服器也可以給大一點,比如LVS、Squid。 此項引數可以控制TIME_WAIT套接字的最大數量,避免Squid伺服器被大量的TIME_WAIT套接字拖死。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_max_tw_buckets

net。ipv4。tcp_synack_retries 引數的值決定了核心放棄連線之前傳送SYN+ACK包的數量。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_synack_retries,預設值為5

net。ipv4。tcp_syn_retries 表示在核心放棄建立連線之前傳送SYN包的數量。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_syn_retries 5

net。ipv4。tcp_max_orphans 用於設定系統中最多有多少個TCP套接字不被關聯到任何一個使用者檔案控制代碼上。 如果超過這個數值,孤立連線將被立即被複位並打印出警告資訊。 這個限制只有為了防止簡單的DoS攻擊。不能過分依靠這個限制甚至認為減少這個值,更多的情況是增加這個值。 該引數對應系統路徑為:/proc/sys/net/ipv4/tcp_max_orphans 65536

net。core。somaxconn 該選項預設值是128,這個引數用於調節系統同時發起的TCP連線數,在高併發的請求中,預設的值可能會導致連結超時或重傳,因此,需要結合併發請求數來調節此值。 該引數對應系統路徑為:/proc/sys/net/core/somaxconn 128

net。core。netdev_max_backlog 表示當每個網路介面接收資料包的速率比核心處理這些包的速率快時,允許傳送到佇列的資料包最大數。 該引數對應系統路徑為:/proc/sys/net/core/netdev_max_backlog,預設值為1000