案例描述:我们的合作客户(国内知名电子支付企业)反应有四台机器调用我们的接口服务,但是奇怪的是四台中有两台是通的,有两台是不通的,不通的机器也是偶尔通偶尔不通,这个问题一直断断续续困扰了他们很久,刚开始我们认为是他们系统那里参数配置不对,就没有给予太多关注,毕竟我们还有好多合作客户,却没有问题;这个问题直到有一天,他们实在扛不住,实在找不出原因了,要求我们技术人员现场去帮他们排查,才开始了一场无解破解案。


  那天接手了这个问题后,开始一对一让对方网络技术人员配合联合排查,分别从正常机器,不正常机器,出口网络设备,我方接收机器同时抓包测试;从抓包现象来分析的确是有重传现象,并且三台机器中偶尔一台通,另外两台就通不了;那天我们手工画了双方的 网络拓扑图,从中了解到一点 他们出去的时候做了S-NAT,并且在问题一开始他们试出了一个解决方法:再加一个外网ip 出来,四台机器不共用一个外网ip 出来,而是两个!这样有两台机器正常了。还有两台依然不正常。有点怪异。



  中间的各种怀疑也不描述了,直到重看抓包,的确发现问题,对方在三次握手(这里就不做详细解释)的时候,发出syn包的时候,我方没有syn+ack 确认,对方一直tcp 重传,并且可以确认的是对方的syn 包的确到达我们了,是我们这边没有回复syn+ack!我们开始把注意力转向我方的网络情况。


三次握手过程截图:

wKiom1d7bBKSMzsWAAMidj9NyJE446.png-wh_50

对方出口截图:

wKioL1d7WQWRRVPAAADCstB0xmg362.png-wh_50

我们的入口截图:

wKiom1d7WXHg7MlXAACYJzcgC64771.png-wh_50


从源端口26414看,的确是同一连接请求从对方出口已经发出,到了我们迟迟没有syn+ack 回复,于是就有了不停的重传请求,连接超时。


刚开始怀疑nginx问题,怀疑keepd 问题,怀疑centos 问题,甚至怀疑底层虚拟化kvm/esxi 问题,做过的无尽测试排查这里就不描述了。但是从单方面一直没有突破,这个时候给了点希望,我方有台esxi ubuntu 的机器确实很正常的,对方三台机器都可以联通,于是就开始对比我们这边两台机器的内核配置参数 sysctl -a > sysctl.txt 导出对比。


wKiom1d7WsPDKbyWAAEbXzmecGw440.png-wh_50



通过对比主要怀疑了以下参数(具体作用大家可网上查阅):

tcp_sack 

tcp_fack

tcp_syncookies

tcp_tw_recycle

tcp_retries1

tcp_timestamps


发现tcp_tw_recycle 这个参数二者是不一样的,于是改成一样。对方测试,恢复正常。再关掉,对方测试,重新异常。基本就锁定了这个参数。


tcp_tw_recycle默认是关闭的,有不少服务器,为了提高性能,开启了该选项,在一些高并发的 WebServer上,为了端口能够快速回收,打开了net.ipv4.tcp_tw_recycle。为什么我这里打开了快速回收,对方的服务器就连接异常了呢,仔细对比抓包发现,问题漏出了端倪。


wKiom1d7ZBfSMgonAAK3eXk7kes778.png-wh_50




    对的,仔细看图只要是异常的,没有回复的syn 包,时间戳都是不对的,默认情况应该是递增的,才会连接正常。再结合对方网络是共用一个Ip做了S-NAT,问题原因找到了。


wKiom1d7c77TdIJRAAGcGrmIrFY795.png-wh_50



我方服务器打开了 tcp_tw_reccycle 了,就会检查时间戳,很不幸对方发来的包的时间戳是乱跳的(截图时间字段,准确来讲对方发来的时间戳是滞后的,这样的包我方肯定不会回复),所以服务器就把带了“倒退”的时间戳的包当作是“recycle的tw连接的重传数据,不是新的请求”,于是丢掉不回包,造成大量丢包。



##########################################################################################

疑问:为什么对方发来的时间戳是乱跳的?

  通过对比对方的配置sysctl -a 发现,对端开启了tcp_timestamps,而且在我们tcp_tw_recycle被打开了话,会假设对端开启了tcp_timestamps,然后会去比较时间戳,如果时间戳变大了,就可以重用。但是,如果对端是一个NAT网络的话(如:一个公司只用一个IP出公网)或是对端的IP被另一台重用了,这个事就复杂了。建链接的SYN可能就被直接丢掉了(你可能会看到connection time out的错误)(如果你想观摩一下Linux的内核代码,请参看源码 tcp_timewait_state_process)。



看到这里问题似乎已经解决了,但是不是,这不是最优的解决方法。因为从目前来看,我们得出两种解决方法:

1,我方关闭tcp_tw_recycle ,不开启回收功能,也就不会检查时间戳,这种包肯定会被响应了。

2.对方关闭tcp_timestamps, 这样发来的报文就不带时间戳。但是这两种方法各有长短优劣。






##########################################################################################

  1. 关闭tcp_tw_recycle ,好处:不能保证所有客户都关闭了tcp_timestamps情况下,可以避免类似问题发生。短处:若负载量高的情况下,web_server 的time_wait 状态量会直线飙升。官方建议是关闭该选项:


wKioL1d7cp3Dx5zRAAGsEp3AlZw503.png-wh_50



##########################################################################################

2.对方关闭tcp_timestamps.好处:问题迎刃而解 短处:查阅rfc 文档,这个tcp 可选字段会造成一些影响。


wKiom1d7bbKToRE3AAA0ois9Qcg299.png-wh_50


wKioL1d7bgzwTwf6AAAZB07oEHU772.png-wh_50




##########################################################################################

最后显示一个没有timestamp可选字段的报文,实地测试通过,也是正常可用:

wKiom1d7chKgDBa0AAITB4drV_o159.png-wh_50



有timestamp可选字段的报文:

wKiom1d7ckrgO4e8AAJZ18f_YlQ223.png-wh_50




  至此,一场由tcp_timestamps 引发的无解追击案结束了。从这里得出任何事情一定要实事求是,仔细抓包分析,仔细查阅文档,用数据说话。不要整天猜测而不是去实地考察研究,这样只会浪费更多的时间,当然,文章还有很多纰漏之处,毕竟牵扯的技术点太多,望包涵指正。