在了解 tcp 的基本機制後本文繼續介紹 linux 核心提供的鏈結佇列、tw_reuse、so_reuseport、syn_cookies 等機制以優化生產環境中遇到的效能問題。
linux 核心會維護兩個佇列:
佇列滿後伺服器會丟棄溢位的連線會導致的情況:
全連線佇列溢位時伺服器根據 net.ipv4.tcp_abort_on_overflow 引數決定如何處理:
上述引數配置可以通過sysctl -w
命令進行修改,例如:sysctl -w net.core.somaxconn=32768
。機器重啟後使用sysctl -w
進行的修改會丟失,若需要持久化配置可以在 /etc/sysctl.conf 檔案中增加一行net.core.somaxconn= 4000
, 然後執行sysctl -p
使修改生效。
連線佇列溢位會導致無法與伺服器建立新連線或者客戶端出現大量 connection reset by peer 錯誤。
使用netstat -s | grep overflowed
可以檢查是否出現全連線佇列溢位的情況:
# netstat -s | grep overflowed
11451 times the listen queue of a socket overflowed
上面的輸出表示某個 listen 狀態的 socket 全連線佇列溢位了 11451 次。這個數字是個累計值,可以多執行幾次來判斷溢位次數是否在上公升。
使用netstat -s | grep syns | grep dropped
可以檢查是否出現半連線佇列溢位的情況:
# netstat -s | grep syns | grep dropped
32404 syns to listen sockets dropped
上面的輸出表示有 32404 次 syn 被丟棄,這個數字同樣是累計值。
我們之前提到 time wait 狀態會持續 60s, 過多 time_wait 狀態的連線會占用非常有限的 tcp 埠導致無法建立新的連線。
net.ipv4.tcp_max_tw_buckets 引數控制系統中 time_wait 狀態連線的最大數量。預設值是 nr_file*2,並且會根據系統的記憶體容量被調整。
檢測 time_wait 是否過多
time_wait 狀態的連線過多會在 dmesg 核心日誌中報錯:kernel: tcp: kernel: tcp: time wait bucket table overflow
.
使用 netstat 命令可以檢視各狀態連線數:
#netstat -n | awk '/^tcp/ end '
close_wait 36
established 35
time_wait 173
awk 命令不好記可以直接 wc 數行數:
# netstat -n | grep 'time_wait' | wc -l
172
在正式介紹 tw_reuse 和 tw_recycle 之前我們先來介紹它們依賴的 tcp_timestamps 機制。
tcp 最早在 rfc1323 中引入了 timestamp 選項, timestamp 有兩個目的:一是更精確地估算報文往返時間(round-trip-time, rtt) 二是防止陳舊的報文干擾正常的連線。
在引入 timestamps 機制之前,tcp 協議棧通過傳送資料報和收到 ack 的時間差來計算 rtt,在出現丟包時這一計算方式會出現問題。比如第一次傳送的時間為 t1, 重傳包的時間是 t2, 傳送方在 t3 收到了 ack, 由於不知道這個 ack 包是確認第乙個資料報還是確認重傳包我們也無法確定 rtt 是 t3 - t2 還是 t3 - t1。
在設定 net.ipv4.tcp_timestamps=1 之後, 傳送方在傳送資料時將傳送時間 timestamp 放在包裡面, 接收方在收到資料報後返回的ack包中將收到的timestamp返回給傳送方(echo back),這樣傳送方就可以利用收到 ack 包的時間和 ack 包中的echo back timestamp 確定準確的 rtt。
tcp 序列號採用 32 位無符號整數儲存,seq 在達到最大值後會從 0 開始再次遞增,這種迴圈被稱為序列號迴繞。由於迴繞現象存在ack和重傳機制無法通過序列號唯一確定資料報,從而導致錯誤。
上圖中由於迴繞出現了兩個 seq=a 的包,接收方把上一次迴圈的 seq a 當做了當前的 seq a 丟棄了正常的資料報導致資料錯誤。
開啟 net.ipv4.tcp_tw_reuse 後客戶端在呼叫 connect() 函式時,核心會隨機找乙個 time_wait 狀態超過 1 秒的連線給新的連線復用,所以該選項只適用於連線發起方。
開啟 tw_reuse 之後,tcp 協議棧通過 paws 機制來丟棄屬於舊連線的資料報。因此必須開啟 net.ipv4.tcp_timestamps 之後 tw_reuse 才會生效。
net.ipv4.tw_recycle 同樣利用 timestamp 來丟棄上乙個連線的資料報從而不需要在 time_wait 狀態等待太長時間即可關閉連線。
在開啟 tw_recycle 後會自動啟動 per-host paws 機制, 即對「對端 ip 做 paws 檢查」,而非對「ip + 埠」四元組做 paws 檢查。在開啟 nat 了網路中, 客戶端 a 和 b 通過同乙個 nat 閘道器與伺服器建立連線。 在伺服器看來他們的 ip 位址相同,若 b 的 timestamp 比 客戶端 a 的 小,那麼由於服務端的 per-host 的 paws 機制的作用,服務端就會丟棄客戶端主機 b 發來的 syn 包。
由於 ipv4 位址緊張目前大多數裝置均通過 nat 接入網路(比如你的電腦和路由器), 所以在生產環境開啟 tw_recycle極度危險。在 linux 4.12 版本後,直接取消了 tw_recycle 引數。
在呼叫 bind 後可以使用 setsockopt 函式為 socket 設定 so_reuseport 或 so_reuseaddr 選項。
因為服務程序關閉時伺服器主動關閉了連線,程序關閉後有一些 socket 處於 time_wait 狀態,導致服務端重啟後無法 bind 並 listen 原埠。
服務端在 bind 時設定 so_reuseaddr 則可以忽略 time_wait 狀態的連線,重啟後直接 bind 成功。so_reuseaddr 的作用僅限於讓伺服器重啟後立即 bind 成功, 對效能無改善。
so_reuseport 允許多個程序同時監聽同乙個ip:port。so_reuseport 允許多程序監聽同乙個埠避免只有乙個 listen 程序成為系統的效能瓶頸,隨著 cpu 核數的增加系統吞吐量會線性增加。
主程序建立 socket、bind、 listen 之後,fork 出多個子程序,每個程序都在同乙個 socket 上呼叫 accept 等待新連線進入:
這一模型利用了多核cpu的優勢但仍有兩個缺點:
單一 listener工作程序會成為瓶頸, 隨著核數的擴充套件,效能並沒有隨著提公升
很難做到cpu之間的負載均衡
在 linux 3.9 引入 so_reuseport 之後允許多個進(線)程 listen 同乙個埠,因此我們可以先 fork 多個程序然後在每個子程序中進行建立 socket、bind、listen、accept。 核心會負責在多個 cpu 之間進行負載均衡, 也解決了單一 listener 稱為系統瓶頸的問題。
我們在前面提到當服務端收到來自客戶端的 syn 報文之後會向客戶端回覆 syn + ack 並將連線放入半連線佇列中。若攻擊者大量傳送 syn 報文服務端的半連線佇列很快就會佔滿,導致伺服器無法繼續接收連線從而無法正常提供服務。這種攻擊方式稱為 syn 洪氾(syn flood)攻擊, 是一種典型的拒絕服務攻擊方式。
syn cookies 的原理是服務端在握手過程中返回 syn+ack 後不分配資源儲存半連線資料,而是根據 syn 中的資料生成乙個 cookie 值作為自己的起始序列號。在收到客戶端返回的 ack 後通過其中的序列號判斷 ack 的合法性。由於建立連線的時候不需要儲存半連線,從而可以有效規避 syn flood 攻擊。
tcp連線建立時,雙方的起始報文序號是可以任意的, syn cookies 利用這一特性構造初始序列號:
設t為乙個緩慢增長的時間戳(典型實現是每64s遞增一次)
設m為客戶端傳送的syn報文中的mss選項值
設s是連線的元組資訊(源ip,目的ip,源埠,目的埠)和t經過密碼**算後的hash值
則初始序列號n為:
高 5 位為t mod 32
接下來3位為m的編碼值
低 24 位為s
客戶端收到服務端的 syn+ack 後會向伺服器返回 ack, 且報文中ack = n + 1。接下來,伺服器需要對 ack - 1 進行檢查判斷 t 是否超時以及 s 是否被篡改。若報文有效,則從中取出 mss 值建立連線。
syn cookies 同樣存在一些缺點:
linux 的 net.ipv4.tcp_syncookies 配置項可以開啟 syn cookies 功能:
Python 網路程式設計2 TCP
tcp協議全稱 傳輸控制協議,顧名思義,就是要對資料的傳輸進行一定的控制 tcp協議的特點 1 相對於傳輸層的udp協議,tcp協議的特點是面向連線的 可靠的傳輸和位元組流。2 使用tcp協議通訊的雙方必須首先建立連線,然後才能開始資料的讀寫。雙方都必須為該連線分配必要的核心資源,以管理連線的狀態和...
LINUX核心引數,針對TCP協議優化
程序可以同時開啟的最大控制代碼數,限制最大併發連線數 fs.file max 999999 允許time wait狀態的socket重新用於新的tcp連線 net.ipv4.tcp tw reuse 1 當keepalive啟用時,tcp傳送keepalive訊息的頻度,預設是2個小時。設定小些,可...
UNP筆記2 TCP套介面函式
4.1 tcp連線和啟動 tcp連線 4.2 socket函式 分配最小的未用socket控制代碼 include int socket int family,int type,int protocol family 說明 af inet ipv4 af inet6 ipv6 af local un...