- 问题现象:
在网关映射一个端口,到公司内部一台服务器(基于Linux的)的服务端口,发现此服务用Windows(10,中文家庭版)连接很顺畅,而手机、Linux客户端连接非常困难,十有八九连不上。
- 分析过程
在Windows上用Wireshark抓成功连接的数据包,并在手机端用tcpdump(https://www.androidtcpdump.com/android-tcpdump/downloads)抓失败连接的数据包,经详细对比发现:手机端发出的连接请求数据包的TCP层包头中,比Windows多了tcp_timestamp这个option
Window发出的连接请求数据包
Android客户端发出的请求,多了Timstamps Option
这种带有tcp_timestamps的连接请求,收不到服务器的响应(同时在服务器端抓数据包,证明服务器根本没发出连接响应数据包,而不是在路由中途丢失)
用tcpreplay工具包修改这个失败的连接请求(用WireEdit也可以),去掉timestamp option(工具会自动计算包头校验和),并重发这个数据包,可以看到服务器端就有连接响应包发出。
- 原因剖析及解决方案
尝试:在linux客户端,禁用tcp_timestamps:
echo 0 > /proc/sys/net/ipv4/tcp_timestamps
再试连接,结果很顺畅。
禁用服务端tcp_timestamps,也有同样效果。
最后查资料,原因简单来说是:NAT映射的对外服务,对于服务器来说,所有的client都来自一个IP地址,而服务端又启用了 tcp_timestamps和 tcp_tw_recycle选项;同时,Linux的协议栈实现有这么个逻辑:在以上设置的条件下,60秒内,从同一IP的连接请求,timestamp必须是递增的。如果两个客户端都带有timestamp,由于系统时间一般很难精确同步,则系统时间偏大的那个可以连接进来,小的就无法连入。而没有时间戳的请求,服务端无从判定,就可以连接进来了。
知道原因,问题也就好解决了:
修改服务器的 /etc/sysctl.conf
增加或修改一行:
net.ipv4.tcp_tw_recycle = 0
然后重启服务器,或者执行 sysctl -p 就可以了。
技术细节参考:
http://www.cnxct.com/coping-with-the-tcp-time_wait-state-on-busy-linux-servers-in-chinese-and-dont-enable-tcp_tw_recycle/
http://blog.sina.com.cn/s/blog_781b0c850100znjd.html