1. 程式人生 > >linux地址重用 net.ipv4.tcp_tw_reuse

linux地址重用 net.ipv4.tcp_tw_reuse

http://blog.csdn.net/caianye/article/details/38540867

http://www.cnblogs.com/lulu/p/4149312.html

臨近年關,人會變得浮躁,期間寫的程式碼可謂亂七八糟。不過出來混始終是要還的,這不最近就發現一個PHP指令碼時常連不上伺服器。

遇到這類問題,我習慣於先用strace命令跟蹤了一下看看:

shell> strace php /path/to/file EADDRNOTAVAIL (Cannot assign requested address)

從字面結果看似乎是網路資源相關問題。這裡順便介紹一點小技巧:在除錯的時候一般是從後往前看strace命令的結果,這樣更容易找到有價值的資訊。

檢視一下當前的網路連線情況,結果發現TIME_WAIT數非常大:

shell> netstat -nt | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"t",state[key]}' TIME_WAIT 28233

重複了幾次測試,結果每次出問題的時候,TIME_WAIT都等於28233,這真是一個魔法數字!實際原因很簡單,它取決於一個核心引數net.ipv4.ip_local_port_range:

shell> sysctl -a | grep port net.ipv4.ip_local_port_range = 32768 61000

因為埠範圍是一個閉區間,所以實際可用的埠數量是:

shell> echo $((61000-32768+1)) 28233

問題分析到這裡基本就清晰了,解決方向也明確了,內容所限,這裡就不說如何優化程式程式碼了,只是從系統方面來闡述如何解決問題,無非就是以下兩個方面:

首先是增加本地可用埠數量。這點可以用以下命令來實現:

shell> echo "net.ipv4.ip_local_port_range = 10240 61000" >> /etc/sysctl.conf shell> sysctl -p

其次是減少TIME_WAIT連線狀態。網路上已經有不少相關的介紹,大多是建議:

shell> sysctl net.ipv4.tcp_tw_reuse=1 shell> sysctl net.ipv4.tcp_tw_recycle=1

注:通過sysctl命令修改核心引數,重啟後會還原,要想持久化可以參考前面的方法。

這兩個選項在降低TIME_WAIT數量方面可以說是立竿見影,不過如果你覺得問題已經完美搞定那就錯了,實際上這樣可能會引入一個更復雜的網路故障。

關於核心引數的詳細介紹,可以參考官方文件。我們這裡簡要說明一下tcp_tw_recycle引數。它用來快速回收TIME_WAIT連線,不過如果在NAT環境下會引發問題。

An additional mechanism could be added to the TCP, a per-hostcache of the last timestamp received from any connection.This value could then be used in the PAWS mechanism to rejectold duplicate segments from earlier incarnations of theconnection, if the timestamp clock can be guaranteed to haveticked at least once since the old connection was open. Thiswould require that the TIME-WAIT delay plus the RTT togethermust be at least one tick of the sender’s timestamp clock.Such an extension is not part of the proposal of this RFC.

大概意思是說TCP有一種行為,可以快取每個連線最新的時間戳,後續請求中如果時間戳小於快取的時間戳,即視為無效,相應的資料包會被丟棄。

Linux是否啟用這種行為取決於tcp_timestamps和tcp_tw_recycle,因為tcp_timestamps預設就是開啟的,所以當tcp_tw_recycle被開啟後,實際上這種行為就被激活了。

現在很多公司都用LVS做負載均衡,通常是前面一臺LVS,後面多臺後端伺服器,這其實就是NAT,當請求到達LVS後,它修改地址資料後便轉發給後端伺服器,但不會修改時間戳資料,對於後端伺服器來說,請求的源地址就是LVS的地址,加上埠會複用,所以從後端伺服器的角度看,原本不同客戶端的請求經過LVS的轉發,就可能會被認為是同一個連線,加之不同客戶端的時間可能不一致,所以就會出現時間戳錯亂的現象,於是後面的資料包就被丟棄了,具體的表現通常是是客戶端明明發送的SYN,但服務端就是不響應ACK,還可以通過下面命令來確認資料包不斷被丟棄的現象:

shell> netstat -s | grep timestamp ... packets rejects in established connections because of timestamp

如果伺服器身處NAT環境,安全起見,通常要禁止tcp_tw_recycle,至於TIME_WAIT連線過多的問題,可以通過啟用tcp_tw_reuse來緩解。

進一步思考,既然必須同時啟用tcp_timestamps和tcp_tw_recycle才會觸發這種現象,那隻要禁止tcp_timestamps,同時啟用tcp_tw_recycle,就可以既避免NAT丟包問題,又降低TIME_WAIT連線數量。如果伺服器並不依賴於RFC1323,那麼這種方法應該也是可行的,不過最好多做測試,以防有其他的副作用。

shell> sysctl net.ipv4.tcp_timestamps=0 shell> sysctl net.ipv4.tcp_tw_recycle=1

總體來說,這次網路故障本身並沒什麼高深之處,本不想羅羅嗦嗦寫這麼多,不過拔出蘿蔔帶出泥,在過程中牽扯的方方面面還是值得大家品味的,於是便有了這篇文字。

近來線上陸續出現了一些connect失敗的問題,經過分析試驗,最終確認和proc引數tcp_tw_recycle/tcp_timestamps相關;
1. 現象
第一個現象:模組A通過NAT閘道器訪問服務S成功,而模組B通過NAT閘道器訪問服務S經常性出現connect失敗,抓包發現:服務S端已經收到了syn包,但沒有回覆synack;另外,模組A關閉了tcp timestamp,而模組B開啟了tcp timestamp;
第二個現象:不同主機上的模組C(開啟timestamp),通過NAT閘道器(1個出口ip)訪問同一服務S,主機C1 connect成功,而主機C2 connect失敗;

2. 分析
根據現象上述問題明顯和tcp timestmap有關;檢視linux 2.6.32核心原始碼,發現tcp_tw_recycle/tcp_timestamps都開啟的條件下,60s內同一源ip主機的socket connect請求中的timestamp必須是遞增的。
原始碼函式:tcp_v4_conn_request(),該函式是tcp層三次握手syn包的處理函式(服務端);
原始碼片段
if (tmp_opt.saw_tstamp &&
tcp_death_row.sysctl_tw_recycle &&
(dst = inet_csk_route_req(sk, req)) != NULL &&
(peer = rt_get_peer((struct rtable *)dst)) != NULL &&
peer->v4daddr == saddr) {
if (get_seconds() < peer->tcp_ts_stamp + TCP_PAWS_MSL &&
(s32)(peer->tcp_ts - req->ts_recent) >
TCP_PAWS_WINDOW) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}
tmp_opt.saw_tstamp:該socket支援tcp_timestamp
sysctl_tw_recycle:本機系統開啟tcp_tw_recycle選項
TCP_PAWS_MSL:60s,該條件判斷表示該源ip的上次tcp通訊發生在60s內
TCP_PAWS_WINDOW:1,該條件判斷表示該源ip的上次tcp通訊的timestamp 大於本次tcp

分析:主機client1和client2通過NAT閘道器(1個ip地址)訪問serverN,由於timestamp時間為系統啟動到當前的時間,因此,client1和client2的timestamp不相同;根據上述syn包處理原始碼,在tcp_tw_recycle和tcp_timestamps同時開啟的條件下,timestamp大的主機訪問serverN成功,而timestmap小的主機訪問失敗;

引數:/proc/sys/net/ipv4/tcp_timestamps - 控制timestamp選項開啟/關閉
/proc/sys/net/ipv4/tcp_tw_recycle - 減少timewait socket釋放的超時時間

3. 解決方法
echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle;
tcp_tw_recycle預設是關閉的,有不少伺服器,為了提高效能,開啟了該選項;
為了解決上述問題,個人建議關閉tcp_tw_recycle選項,而不是timestamp;因為 在tcp timestamp關閉的條件下,開啟tcp_tw_recycle是不起作用的;而tcp timestamp可以獨立開啟並起作用。
原始碼函式: tcp_time_wait()
原始碼片段:
if (tcp_death_row.sysctl_tw_recycle && tp->rx_opt.ts_recent_stamp)
recycle_ok = icsk->icsk_af_ops->remember_stamp(sk);
......

if (timeo < rto)
timeo = rto;

if (recycle_ok) {
tw->tw_timeout = rto;
} else {
tw->tw_timeout = TCP_TIMEWAIT_LEN;
if (state == TCP_TIME_WAIT)