近來(lái)線上陸續(xù)出現(xiàn)了一些connect失敗的問(wèn)題,經(jīng)過(guò)分析試驗(yàn),最終確認(rèn)和proc參數(shù)tcp_tw_recycle/tcp_timestamps相關(guān);
1. 現(xiàn)象
第一個(gè)現(xiàn)象:模塊A通過(guò)NAT網(wǎng)關(guān)訪問(wèn)服務(wù)S成功,而模塊B通過(guò)NAT網(wǎng)關(guān)訪問(wèn)服務(wù)S經(jīng)常性出現(xiàn)connect失敗,抓包發(fā)現(xiàn):服務(wù)S端已經(jīng)收到了syn包,但沒(méi)有回復(fù)synack;另外,模塊A關(guān)閉了tcp timestamp,而模塊B開啟了tcp timestamp;
第二個(gè)現(xiàn)象:不同主機(jī)上的模塊C(開啟timestamp),通過(guò)NAT網(wǎng)關(guān)(1個(gè)出口ip)訪問(wèn)同一服務(wù)S,主機(jī)C1 connect成功,而主機(jī)C2 connect失敗;
2. 分析
根據(jù)現(xiàn)象上述問(wèn)題明顯和tcp timestmap有關(guān);查看linux 2.6.32內(nèi)核源碼,發(fā)現(xiàn)tcp_tw_recycle/tcp_timestamps都開啟的條件下,60s內(nèi)同一源ip主機(jī)的socket connect請(qǐng)求中的timestamp必須是遞增的。
源碼函數(shù):tcp_v4_conn_request(),該函數(shù)是tcp層三次握手syn包的處理函數(shù)(服務(wù)端);
源碼片段:
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:本機(jī)系統(tǒng)開啟tcp_tw_recycle選項(xiàng)
TCP_PAWS_MSL:60s,該條件判斷表示該源ip的上次tcp通訊發(fā)生在60s內(nèi)
TCP_PAWS_WINDOW:1,該條件判斷表示該源ip的上次tcp通訊的timestamp 大于 本次tcp
分析:主機(jī)client1和 client2通過(guò)NAT網(wǎng)關(guān)(1個(gè)ip地址)訪問(wèn)serverN,由于timestamp時(shí)間為系統(tǒng)啟動(dòng)到當(dāng)前的時(shí)間,因此,client1和 client2的timestamp不相同;根據(jù)上述syn包處理源碼,在tcp_tw_recycle和tcp_timestamps同時(shí)開啟的條件 下,timestamp大的主機(jī)訪問(wèn)serverN成功,而timestmap小的主機(jī)訪問(wèn)失敗;
參數(shù):/proc/sys/net/ipv4/tcp_timestamps - 控制timestamp選項(xiàng)開啟/關(guān)閉
/proc/sys/net/ipv4/tcp_tw_recycle - 減少timewait socket釋放的超時(shí)時(shí)間
3. 解決方法
echo 0 > /proc/sys/net/ipv4/tcp_tw_recycle;
tcp_tw_recycle默認(rèn)是關(guān)閉的,有不少服務(wù)器,為了提高性能,開啟了該選項(xiàng);
為了解決上述問(wèn)題,個(gè)人建議關(guān)閉tcp_tw_recycle選項(xiàng),而不是timestamp;因?yàn)?在tcp timestamp關(guān)閉的條件下,開啟tcp_tw_recycle是不起作用的;而tcp timestamp可以獨(dú)立開啟并起作用。
源碼函數(shù): 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)
timeo = TCP_TIMEWAIT_LEN;
}