原文連結:https://blog.csdn.net/weixin_34309435/article/details/91550273
一,背景:
今天下午發現線上的一台機器從辦公網登入不上且所有tcp端口都telnet不通,但是通過同機房的其它機器卻可以正常通路到出問題的機器。于是就立即在這台出問題的server端抓包分析,發現問題如下:
server端收到了本地pc發的SYN包,但是沒有回syn+ack包,是以确認是server端系統問題。tcpdump抓包如下:

二,排查
1,發現系統沒有任何負載
2,網卡也沒有丢包
3,iptables政策也都沒問題
4,系統的SYN_RECV連接配接很少,也沒超限
5,系統的檔案描述符等資源也都沒問題
6,messages和dmesg中沒有任何提示或者錯誤資訊
7,通過netstat指令檢視系統上協定統計資訊,發現很多請求由于時間戳的問題被rejected
-
# netstat -s |grep reject
-
2181 passive connections rejected because of time stamp
-
34 packets rejects in established connections because of timestamp
三,通過google來協助
發現有同樣的人遇見這個問題:
是通過調整sysctl -w net.ipv4.tcp_timestamps=0或者sysctl -w net.ipv4.tcp_tw_recycle=0來解決這個問題,于是我就順藤摸瓜繼續查。
而在查詢這兩個參數的過程中,發現問題原因如下:
發現是 Linux tcp_tw_recycle/tcp_timestamps設定導緻的問題。 因為在linux kernel源碼中發現tcp_tw_recycle/tcp_timestamps都開啟的條件下,60s内同一源ip主機的socket connect請求中的timestamp必須是遞增的。經過測試,我這邊centos6系統(kernel 2.6.32)和centos7系統(kernel 3.10.0)都有這問題。
-
源碼函數:kernel 2.6.32 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
總結:
我這邊和其它同僚通過公司出口(NAT網關隻有1個ip位址)通路問題server,由于timestamp時間為系統啟動到目前的時間,故我和其它同僚的timestamp肯定不相同;根據上述SYN包處理源碼,在tcp_tw_recycle和tcp_timestamps同時開啟的條件下,timestamp大的主機通路serverN成功,而timestmap小的主機通路失敗。并且,我在辦公網找了兩台機器可100%重制這個問題。
解決:
# echo "0" > /proc/sys/net/ipv4/tcp_tw_recycle
四,擴充
1,net.ipv4.tcp_timestamps
tcp_timestamps的本質是記錄資料包的發送時間。基本的步驟如下:
- 發送方在發送資料時,将一個timestamp(表示發送時間)放在包裡面
- 接收方在收到資料包後,在對應的ACK包中将收到的timestamp傳回給發送方(echo back)
- 發送發收到ACK包後,用目前時刻now - ACK包中的timestamp就能得到準确的RTT
當然實際運用中要考慮到RTT的波動,是以有了後續的(Round-Trip Time Measurement)RTTM機制。
TCP Timestamps Option (TSopt)具體設計如下
-
Kind: 8 // 标記唯一的選項類型,比如window scale是3
-
Length: 10 bytes // 标記Timestamps選項的位元組數
-
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
| Kind=8 | Length=10 | TS Value (TSval) | TS ECho Reply (TSecr) |
-
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-
1 1 4 4
timestamps一個雙向的選項,當一方不開啟時,兩方都将停用timestamps。比如client端發送的SYN包中帶有timestamp選項,但server端并沒有開啟該選項。則回複的SYN-ACK将不帶timestamp選項,同時client後續回複的ACK也不會帶有timestamp選項。當然,如果client發送的SYN包中就不帶timestamp,雙向都将停用timestamp。
tcp資料包中timestamps的value是系統開機時間到現在時間的(毫秒級)時間戳。
參數:
0:停用
1:啟用(系統預設值)
2,net.ipv4.tcp_tw_recycle
TCP規範中規定的處于TIME_WAIT的TCP連接配接必須等待2MSL時間。但在linux中,如果開啟了tcp_tw_recycle,TIME_WAIT的TCP連接配接就不會等待2MSL時間(而是rto或者60s),進而達到快速重用(回收)處于TIME_WAIT狀态的tcp連接配接的目的。這就可能導緻連接配接收到之前連接配接的資料。為此,linux在打開tcp_tw_recycle的情況下,會記錄下TIME_WAIT連接配接的對端(peer)資訊,包括IP位址、時間戳等。這樣,當核心收到同一個IP的SYN包時,就會去比較時間戳,檢查SYN包的時間戳是否滞後,如果滞後,就将其丢掉(認為是舊連接配接的資料)。這在絕大部分情況下是沒有問題的,但是對于我們實際的client-server的服務,通路我們服務的使用者一般都位于NAT之後,如果NAT之後有多個使用者通路同一個服務,就有可能因為時間戳滞後的連接配接被丢掉。
參數:
0:停用(系統預設值)
1:啟用
參考:
https://serverfault.com/questions/235965/why-would-a-server-not-send-a-syn-ack-packet-in-response-to-a-syn-packet
http://hustcat.github.io/tcp_tw_recycle-and-tcp_timestamp/
轉載于:https://blog.51cto.com/leejia/1954628