1. 程式人生 > 其它 >記錄一次tcp_timestamps 線上問題的排查

記錄一次tcp_timestamps 線上問題的排查

我們的情況和這個朋友遇到的有點類似:
https://blog.csdn.net/majianting/article/details/96476375
如我的域名是:yuming.api.com
如公網ip是:192.168.2.202
我線上的介面是:http://yuming.api.com/?s=init
業務架構:nginx+PHP+redis+mysql
域名有騰訊雲clb轉發進來內部rs 機器。

一、場景復現

研發偶爾會反饋說介面連不上的問題,報504。但是nginx沒有記錄到日誌,PHP也沒有。一般報504 的話是後端介面返回超時,這種情況比較多的是出現在上傳大檔案的時候。只要改下nginx或者PHP的限制超時引數即可。但是我們這個問題不是這個原因引起,因為研發反饋的時候,請求沒到nginx。並且我們復現的時候也很難。因為這是偶現的,而且專案還沒上線就出現了。

二、排查過程

1、檢視nginx日誌,PHP日誌,系統message日誌都沒有看到和這個504異常的資訊。即說明請求異常請求沒有到達我們的nginx,更別說PHP了。
2、檢視zabbix監控,關於記憶體、CPU、磁碟io和網路流量監控,發現異常時間附近會出現相對大一點的流量,但是不至於說返回504。其他業務機也是這樣,算是正常情況。
3、懷疑是域名轉發的問題,騰訊雲開了一個負載均衡的ip,即clb,將域名繫結到負載ip上再轉發進去真實ip,但還是有問題。看clb日誌看到有到達clb,但是沒有到達我們RS的nginx。
4、本來想通過zabbix的web監控這個介面,看下異常報錯是否有規律可循。因為人工很難復現。通過zabbix新增web監控來監控這個介面和這個業務的後臺,發現這個介面一直是504的狀態,但是後臺是200,說明zabbix訪問這臺機器異常了,即可以完美復現了研發說的偶現的504 情況,這也給了問題排查提供了關鍵的切入點。通過zabbix機器使用curl直接訪問這個介面返回504。但是如果繫結host去訪問的話就沒問題,說明機器直達也是沒問題的。但是去掉host就不行。說明通過域名轉發進去的是有問題的,但是如果不經過域名那層nat 轉發進來就沒問題。問題也可能就出現在nat轉發這裡。
5、通過tcpdump在介面機抓包,抓zabbix機器的訪問的包,按照按照tcp三次握手原理和http請求再對比其他正常的請求,發現zabbix機器到達了介面機,但是介面機沒有返回,導致三次握手沒有完成,更別說要做資料交換。三次握手沒有成功,說明要麼是資料包被介面機丟棄了,要麼是tcp連線被介面機直接斷開了。

三、解決方式

調整下面三個核心引數:
net.ipv4.tcp_tw_reuse = 0
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_timestamps = 0

四、相關核心引數解析:

net.ipv4.tcp_tw_reuse = 0    表示是否開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0 關閉。1 為開啟。
net.ipv4.tcp_tw_recycle = 0  表示是否開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0 關閉。1 為開啟。
上面兩個引數必須是timestamps 開啟的情況下才會生效!
net.ipv4.tcp_timestamps = 0     是否開啟是時間戳 0 關閉,1開啟(預設開啟)
net.ipv4.tcp_fin_timeout = 60  表示如果套接字由本端要求關閉,這個引數決定了它保持在FIN-WAIT-2狀態的時間(可改為30,一般來說FIN-WAIT-2的連線也極少

五、描述

搜尋該引數相關的資料,發現同時啟用tcp_tw_recycle和tcp_timestamps後有可能在NAT環境下導致客戶端始連線失敗,抓包表現為:客戶端一直髮送SYN報文,但服務端不響應。
也就是說伺服器同時開啟tcp_timestamps 和tcp_tw_recycle:可以快取每個連線最新的時間戳,後續請求中如果時間戳小於快取的時間戳,即視為無效,相應的資料包會被丟棄。意思就是同一個源IP來連線同一個目的埠的資料包時間戳必須是遞增的
比如我們用lvs做負載均衡,lvs會將使用者請求轉發到rs機器,實際上中間就是nat過去的。也就是說lvs訪問伺服器是同一個ip,而且是nat過去的,可能會被認為是同一個連線,加之不同客戶端的時間可能不一致,所以就會出現時間戳錯亂的現象,於是後面的資料包就被丟棄了,那麼上面的問題就很可能會出現。
也就是說:伺服器在建立三次握手的時候會校驗同一個源ip 傳送過來的ack資料包的時間戳,如果時間戳比系統記錄的時間戳小的話,才會丟棄這小時間戳的連結,直接不會SYN+ACK包了。

六、案例說明:

LVS地址:222.222.222.222
web伺服器地址:111.111.111.111
客戶端C1地址:100.100.100.101
客戶端C2地址:100.100.100.102
假如出現這種情況
13:23:02這個時間點
C1發出的TCP資料包源IP和源埠為100.100.100.101:6332,目標地址和埠是 222.222.222.222:80
13:23:05這個時間點
C2發出的TCP資料包源IP和源埠為100.100.100.102:52223,目標地址和埠是 222.222.222.222:80
經過LVS的full nat。
假如在13:23:06時刻LVS收到C2的資料包
C2的資料包被轉換為
222.222.222.222:52223 ---->111.111.111.111:80
假如在13:23:07時刻LVS收到C1的資料包
C1的資料包被轉換為
222.222.222.222:6332 ---->111.111.111.111:80
假如web伺服器開啟了tcp的tcp_timestamps和tcp_tw_recycle這兩個引數。web伺服器根據資料包的時間戳確認。
C1的資料包由於時間戳小於目前系統登記的此源IP連線的時間戳,被認為是重傳資料,C1的資料包就被丟棄了。

上面引數可能會引起下面幾個問題:

1、機器ping得通,埠Telnet通,但是使用curl訪問頁面的時候不通。
2、抓包的時候發現客戶端的包到了伺服器,但是伺服器沒有返回,也就是說三次握手的時候客戶端發了SYN給伺服器,但是伺服器沒有返回SYN+ack包給客戶端,這樣建立三次握手失敗。

七、對核心引數tcp_syncookies 理解:

瞭解SYN Flood 攻擊
TCP連線建立時,客戶端通過傳送SYN報文發起向處於監聽狀態的伺服器發起連線,伺服器為該連線分配一定的資源,併發送SYN+ACK報文。對伺服器來說,此時該連線的狀態稱為半連線(Half-Open),而當其之後收到客戶端回覆的ACK報文後,連線才算建立完成。在這個過程中,如果伺服器一直沒有收到ACK報文(比如在鏈路中丟失了),伺服器會在超時後重傳SYN+ACK。

三次握手圖示

如果經過多次超時重傳後,還沒有收到, 那麼伺服器會回收資源並關閉半連線,彷彿之前最初的SYN報文從來沒到過一樣!如下圖,伺服器不斷的重發SYN+ACK報文,但是沒有客戶端接收,這樣就浪費了伺服器資源了。

這看上一切正常,但是如果有壞人故意大量不斷髮送偽造的SYN報文,那麼伺服器就會分配大量註定無用的資源,並且從backlog的意義 中可知,伺服器能儲存的半連線的數量是有限的!所以當伺服器受到大量攻擊報文時,它就不能再接收正常的連線了。換句話說,它的服務不再可用了!這就是SYN Flood攻擊的原理,它是一種典型的DDoS攻擊。
它的原理是,在TCP伺服器接收到TCP SYN包並返回TCP SYN + ACK包時,不分配一個專門的資料區,而是根據這個SYN包計算出一個cookie值。這個cookie作為將要返回的SYN ACK包的初始序列號。當客戶端返回一個ACK包時,根據包頭資訊計算cookie,與返回的確認序列號(初始序列號 + 1)進行對比,如果相同,則是一個正常連線,然後,分配資源,建立連線。
Linux中的/proc/sys/net/ipv4/tcp_syncookies是核心中的SYN Cookies開關,0表示關閉SYN Cookies;1表示在新連線壓力比較大時啟用SYN Cookies,2表示始終使用SYN Cookies。