1. 程式人生 > >NAT網路下TCP連線建立時可能SYN包被伺服器忽略-tcp_tw_recycle

NAT網路下TCP連線建立時可能SYN包被伺服器忽略-tcp_tw_recycle

原帖:http://chenzhenianqing.cn/articles/1150.html

相關資料:

http://noops.me/?p=269

http://linuxfun.me/?p=1564

http://www.365dw.cn/396.html

感覺還是關閉tcp_timestamps靠譜

最近一個長連線服務經常被反饋連線失敗,剛開始懷疑是網路問題,也就沒有細查。今天仔細抓包分析了一下,原來碰到了在開啟tcp_tw_recycle和tcp_timestamps的機器上,當多個客戶端使用同一個外網IP( NAT)時可能出現連線建立不成功的坑,具體表現為客戶端傳送了SYN 包給伺服器,伺服器也收到了,但就是不回覆SYN+ACK 給客戶端,從而導致客戶端重傳SYNC,直至一分鐘左右才能成功。

從客戶端這邊抓包的結果是這樣的:大量SYN重傳:

QQ截圖20140529095320

期初懷疑是網路不穩定導致的,因為公司大量機器連線到伺服器,所以抓包難度比較大,也就不好在伺服器抓包分析。最近問題不斷出現,也就開始懷疑一定什麼地方出了問題了。

首先用電腦建立個無線wifi, 然後使用2個手機,一個Android,一個IPone都連線到這個wifi下面,在電腦上抓包, IPhone先連線上伺服器的X埠,然後再用Android去連線,發現android總是連線不上,出現SYN重傳的情況。

所以斷定應該是2著的SYN包有不一樣的地方,如下圖可以看出,2個地方不一樣,WS 也就是window scaling, 視窗擴大選項,這個應該沒關係,剩下的一個就是TSVal,也就是timestamp value, 客戶端的時間戳,本身這個時間戳2個端不一樣是沒問題的,但悲劇的是他們公用一個外網IP,使用NAT網路連線的,所以他們到伺服器的時候,伺服器首先接受到一個IPhone的比較大的TSVal, 記錄起來,然後碰到Android的TSVal的時候發現這個值特別小,所以認為這是一個錯誤的資料包,於是忽略了!

簡單囉嗦一下伺服器這麼做的原因, 由於可能之前系統初始化的時候沒注意,設定了 /proc/sys/net/ipv4/tcp_tw_recycle (預設關閉的) 和 /proc/sys/net/ipv4/tcp_timestamps (預設開啟的), 前者用來快速回收處於TIME_WAIT的連線,後者用來支援RTT的來回時間計算。

關於TIME_WAIT狀態不多介紹,注意這個狀態只在主動關閉的一方才會出現,但是很多不明真相的同學總會喜歡不管如何,都設定這個引數,其實像web伺服器這樣的,開啟了keepalive的伺服器,是不需要去care這個狀態的,因為一般都是客戶端主動關閉連線,所以是客戶端的責任區處理TIME_WAIT。

簡單來說,tcp_tw_recycle  機制允許協議不需要真的等待2個最大段生存時間MSL 那麼長,就可以關閉一個連線了,只需要等待2個數據包來回時間,這個相對很短,所以TIME_WAIT狀態的連線就可以及時回收了,免得佔用系統資源。但2*MSL改為了2*RTT, 那麼問題很明顯,可能出現數據包錯亂,比如被動關閉一方的FIN遲遲沒有到來,伺服器這邊會回收這個連線,然後之後的新連線可能就會複用了這個埠port資訊,然後突然之間客戶端的老的FIN到達了伺服器,然後伺服器以為這個FIN包對應的Port正好是剛剛建立的新連線的一個FIN包,於是伺服器就把新連線給幹掉了·····

也就是說,本身TCP協議規定了,主動關閉一方必須等待2*MSL 的時間,可能長達幾分鐘,但你現在改為2*RTT,明顯短太多了,會出問題的。所以怎麼辦呢?

由於我們在/proc/sys/net/ipv4/tcp_tw_recycle設定後,會清除掉TCP的四元組資訊,釋放記憶體,所以沒法進行埠判斷了,但是IP還是可以的,所以退而求其次:比較這個IP的最新更新的時間戳,如果碰到一個很老的時間戳TSVal的資料包過來,那麼伺服器認為這應該是之前很老的資料包的重傳,只能忽略它。因此實踐中協議採取的策略是:60秒內同一個IP建立2個連線的話,後面一個SYN連線的時間戳必須大於之前的SYN裡面的TSVal,否則伺服器會認為這是一個老連線的資料包,忽略它

從而導致客戶端明明發送了SYN,服務端卻偏偏就是不回覆SYN-ACK。

遇到這個坑的人不少,比如:

類似的不少人碰到,就不多重複了。解決方法一般就是關閉/proc/sys/net/ipv4/tcp_tw_recycle 就OK了,不用這麼激進的去回收TIME_WAIT。

解決辦法:
1. 修改proc:    echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle; 建議別去設定這個,因為重啟後又沒了,沒必要給自己挖坑。

2. 或者更正規的的,修改/etc/sysctl.conf,然後sysctl -p, 指令為:

echo “net.ipv4.tcp_tw_recycle = 0″ >> /etc/sysctl.conf  ; sysctl -p ;

官方文件上明確這麼說了:

tcp_tw_recycle – BOOLEAN
Enable fast recycling TIME-WAIT sockets. Default value is 0.
It should not be changed without advice/request of technical
experts.

再次重複一下:TIME_WAIT 只在主動關閉的一端出現,所以在前端WEB機器上設定這些選項是沒用的····。