TCP的TIME_WAIT快速回收与重用

声明一点:

Linux中是无法修改tcp的TIME_WAIT值的,除非重新编译,起码我是没有找到怎么改。值得注意的是,net.ipv4.tcp_fin_timeout这个参数是FIN_WAIT_2的值,而不是TIME_WAIT的值。我不知道为何很多人都会把它当成是TIME_WAIT的值,想了一下,我觉得是两点:
1.TIME_WAIT过于耀眼,以至于所有出现timeout,加上里面有个tcp的配置,都会想当然往TIME_WAIT上联系;
2.FIN_WAIT_2过于默默无闻,以至于很少有人知道它也是一种状态。

所以,我想大家在学习的时候,不能想当然。

TIME_WAIT的作用

TCP初见于互联网早期,当时的网络很不稳定,大量的丢包,为了冗余,大量包复制多径传输,网速慢--正因为如此,TCP才会更有意义。为了保证TCP的严格语义,就要避免上述冗余机制以及网速慢导致的问题,这些问题集中体现在连接关闭时的四次挥手上。
        由于TCP是全双工的,因此关闭连接必须在两个方向上分别进行。首先发起关闭的一方为主动关闭方,另一方为被动关闭方。很多人都会在这里晕掉,实际上四次挥手比三次握手还简单。四次挥手简单地分为三个过程:
过程一.主动关闭方发送FIN,被动关闭方收到后发送该FIN的ACK;
过程二.被动关闭方发送FIN,主动关闭方收到后发送该FIN的ACK;
过程三.被动关闭方收到ACK。

以上三步下来,圆圈就闭合了!也就是说,在过程三后,被动关闭方就可以100%确认连接已经关闭,因此它便可以直接进入CLOSE状态了,然而主动关闭的一方,它无法确定最后的那个发给被动关闭方的ACK是否已经被收到,据TCP协议规范,不对ACK进行ACK,因此它不可能再收到被动关闭方的任何数据了,因此在这里就陷入了僵局,TCP连接的主动关闭方如何来保证圆圈的闭合?这里,协议外的东西起作用了,和STP(Spanning tree)依靠各类超时值来收敛一样,IP也有一个超时值,即MSL,这类超时值超级重要,因为它们给出了一个物理意义上的不可逾越的界限,它们是自洽协议的唯一外部输入。MSL表明这是IP报文在地球上存活的最长时间,如果在火星上,Linux的代码必须要重新定义MSL的值并且要重新编译。
       于是问题就解决了,主动关闭一方等待MSL时间再释放连接,这个状态就是TIME_WAIT。对于被动关闭的一方,发出FIN之后就处在了LAST_ACK状态了,既然已经发出FIN了,缺的无非也就是个ACK,连接本身其实已经关闭了,因此被动关闭的一方就没有TIME_WAIT状态。
       实际上,两倍MSL才能说明一个报文的彻底丢失,因为还要记入其ACK返回时的MSL。

TIME_WAIT的问题

这个就不多说了,由于TIME_WAIT的存在,短连接时关闭的socket会长时间占据大量的tuple空间。

TIME_WAIT的快速回收

Linux实现了一个TIME_WAIT状态快速回收的机制,即无需等待两倍的MSL这么久的时间,而是等待一个Retrans时间即释放,也就是等待一个重传时间(一般超级短,以至于你都来不及能在netstat -ant中看到TIME_WAIT状态)随即释放。释放了之后,一个连接的tuple元素信息就都没有了,而此时,新建立的TCP却面临着危险,什么危险呢?即:
1.可能被之前迟到的FIN包给终止的危险;
2.被之前连接劫持的危险;
...

于是需要有一定的手段避免这些危险。什么手段呢?虽然曾经连接的tuple信息没有了,但是在IP层还可以保存一个peer信息,注意这个信息不单单是用于TCP这个四层协议的,路由逻辑也会使用它,其字段包括但不限于:
对端IP地址
peer最后一次被TCP触摸到的时间戳

...
在快速释放掉TIME_WAIT连接之后,peer依然保留着。丢失的仅仅是端口信息。不过有了peer的IP地址信息以及TCP最后一次触摸它的时间戳就足够了,TCP规范给出一个优化,即一个新的连接除了同时触犯了以下几点,其它的均可以快速接入,即使它本应该处在TIME_WAIT状态(但是被即快速回收了):
1.来自同一台机器的TCP连接携带时间戳;
2.之前同一台peer机器(仅仅识别IP地址,因为连接被快速释放了,没了端口信息)的某个TCP数据在MSL秒之内到过本机;
3.新连接的时间戳小于peer机器上次TCP到来时的时间戳,且差值大于重放窗口戳。

看样子只有以上的3点的同时满足才能拒绝掉一个新连接,要比TIME_WAIT机制设置的障碍导致的连接拒绝几率小很多,但是要看到,上述的快速释放机制没有端口信息!这就把几率扩大了65535倍。然而,如果对于单独的机器而言,这不算什么,因此单台机器的时间戳不可能倒流的,出现上述的3点均满足时,一定是老的重复数据包又回来了。
        但是,一旦涉及到NAT设备,就悲催了,因为NAT设备将数据包的源IP地址都改成了一个地址(或者少量的IP地址),但是却基本上不修改TCP包的时间戳,这就带来了问题。 假设PC1和PC2均启用了TCP时间戳,它们经过NAT设备N1往服务器S1的22端口连接:
PC1:192.168.100.1
PC2:192.168.100.2
N1外网口(即NAT后的地址):172.16.100.1
S2:172.16.100.2
所有涉事机器的配置:
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
TCP的时间戳是根据本机的jiffers,uptime计算的,现在我能保证PC2的时间戳肯定远小于PC1。现在在PC1上先做一个telnet:
telnet 172.16.100.2 22
连接成功,S1上抓包,得到时间戳timestamps:TS val 698583769
为了让S1主动关闭进而快速回收TIME_WAIT,在S1上执行:
kill $(ps -ef|grep [s]sh|grep acce|awk -F ' ' '{print $2}');
目的是把仅仅光完成三次握手的连接终止掉而不触动已经连接的ssh。此时马上在PC2上telnet:
telnet 172.16.100.2 22
不通!在S1上抓包,得到时间戳timestamps:TS val 27727766。明显小于PC1的!由于有NAT设备,S1看来是同一台机器发出的,且出现了时间戳倒流,连接拒绝!此时在S1上查看计数值:
cat /proc/net/netstat
发现了PAWSPassive对应的值增加了1,每次PC2重发SYN,该计数值均会增加1,直到一个MSL时间过后,才能连接成功。如果反过来就没有问题,即先在PC2上telnet,然后S1主动关闭,然后紧接着PC1上telnet依然可以成功,这是因为时间戳是递增的,不满足上述的第三点。

       仅仅两台机器就出现了这个问题,试问如果大量的源端机器在服务器的入口处遇到了NAT设备会怎样?即一台三层NAT设备部署在高负载网站的入口处...没有谁能保证时间戳小的机器一定先发起连接,各个机器频繁连接断开后依然按照时间戳从小到大的顺序连接!!
       TIME_WAIT快速回收在Linux上通过net.ipv4.tcp_tw_recycle启用,由于其根据时间戳来判定,所以必须开启TCP时间戳才有效。 建议:如果前端部署了三/四层NAT设备,尽量关闭快速回收,以免发生NAT背后真实机器由于时间戳混乱导致的SYN拒绝问题。

TIME_WAIT重用

如果说TIME_WAIT(输入法切换太烦人了,后面简称TW)回收只是一种特定系统的优化实现的话,那么TW重用则有相关的规范,即:如果能保证以下任意一点,一个TW状态的四元组(即一个socket连接)可以重新被新到来的SYN连接使用:
1.初始序列号比TW老连接的末序列号大
2.如果使能了时间戳,那么新到来的连接的时间戳比老连接的时间戳大
Linux上完美实现了上述的特性,可以通过下面的实验来证实:
S1上的服务程序:侦听端口1234,accept新连接,发送一段数据后调用close主动关闭连接。
S1上的额外配置:通过iptables禁止RESET包进入第四层,因为它会将TW状态终结。
PC1上客户端程序:绑定192.168.100.1,2000端口,连接S1,获取数据后调用close关闭连接。
PC2上客户端程序:使用IP_TRANSPARENT选项同样绑定192.168.100.1地址和2000端口,其它和PC1的程序相同。
启动服务端S1:172.16.100.2,不断侦听端口1234;
启动PC1上的C1:192.168.100.1,端口2000,连接S1的1234端口;
此时在S1上抓包,获取正常的三次握手/数据传输/四次挥手数据包。此时在S1上netstat -ant可以看到一个TW状态的连接。
启动PC2上的C2:192.168.100.1,端口2000,连接S1的1234端口;
此时在S1上抓包,SYN序列号seq 3934898078大于PC1发起连接时的最后一个序列号[F.], seq 2513913083, ack 3712390788,S1正常回复SYNACK:Flags [S.], seq 3712456325, ack 3934898079, ...对于这种TW重用的情况,S1的SYNACK的初始序列号是通过TW状态老连接的最后一个ack,即3712390788,加上常量65535+2算出来的!
      以上的实验是在关闭时间戳的情况下完成的,实际上开启时间戳的话,重用的可能性更高一些,毕竟是否能重用一个TW连接是通过以上的条件之一来判断的!

从外部干掉TIME_WAIT

TIME_WAIT状态时则一个阑尾!Linux系统上,除了使能recycle tw,在Linux系统上你无法更简单地缩短TW状态的时间,但是80%的问题却都是由TW状态引发,在Windows系统上,你需要在注册表添加一个隐式的项,稍微拼写错误都会引发沉默的失败!TW确实让人生气,因此我一直都希望干掉TW状态的连接,特别是干掉服务端TW状态的连接!我们可以通过TCP的RESET来干死TW连接。这个怎么说呢?
       根据TCP规范,收到任何的发送到未侦听端口或者序列号乱掉(窗口外)的数据,都要回执以RESET,这就是可以利用的!一个连接等待在TW,它自身无能为力,但是可以从外部间接杀掉它!具体来讲就是利用了IP_TRANSPARENT这个socket选项,它可以bind不属于本地的地址,因此可以从任意机器绑定TW连接的peer地址以及端口,然后发起一个连接,TW连接收到后由于序列号乱序会直接发送一个ACK,该ACK会回到TW连接的peer处,由于99%的可能该peer已经释放了连接(对端由于不能收到FIN-ACK的ACK,进而不放心ACK是否已经到达对端,等待MSL以便所有的老数据均已经丢失),因此peer由于没有该连接会回复RESET,TW连接收到RESET后会释放连接,进而为后续的连接腾出地方!

Linux实现Tips

Linux照顾到了一种特殊情况,即杀死进程的情况,在系统kill进程的时候,会直接调用连接的close函数单方面关闭一个方向的连接,然后并不会等待对端关闭另一个方向的连接进程即退出。现在的问题是,TCP规范和UNIX进程的文件描述符规范直接冲突!进程关闭了,套接字就要关闭,但是TCP是全双工的,你不能保证对端也在同一个时刻同意并且实施关闭动作,既然连接不能关闭,作为文件描述符,进程就不会关闭得彻底!所以,Linux使用了一种“子状态”的机制,即在进程退出的时候,单方面发送FIN,然后不等后续的关闭序列即将连接拷贝到一个占用资源更少的TW套接字,状态直接转入TIMW_WAIT,此时记录一个子状态FIN_WAIT_2,接下来的套接字就和原来的属于进程描述符的连接没有关系了。等到新的连接到来的时候,直接匹配到这个主状态为TW,子状态为FIN_WAIT_2的TW连接上,它负责处理FIN,FIN ACK等数据。

TIME_WAIT快速回收与重用

通过以上描述,我们看到TW状态的连接既可以被快速回收又可以被重用,但是二者的副作用是不同的。对于快速回收,由于丢失了TW连接的端口信息,全部映射到了IP地址信息,所以整个IP地址,也就是整机均被列入了考察对象,这本身并没有什么问题,因为快速回收只考虑时间戳信息,只要其保持单调递增即可,一般的机器时间是不会倒流的,但是遇到NAT合并就不行了,NAT设备为所有的内部设备代理一个IP地址即主机标识,然而却不触动其时间戳,而各个机器的时间戳并不满足任何规律...
        TW重用解决了整机范围拒绝接入的问题,但是却面临资源消耗的问题。它这个做法的依据之一仍然为,一般一个单独的主机是不可能在MSL内用同一个端口连接同一个服务的,除非它做了bind。因此等待一些遗留的数据丢失或者到达是有盼头的。有一点我有异议,我个人感觉,如果处在默默地TW等待中,有默默地非递增SYN或者递增时间戳SYN到来,千万别发ACK,只要默默丢弃即可,因为你发了ACK,对方在已经终止了连接的情况下,就会发RESET,进而终止掉本段连接。

TIME_WAIT的80/20悲剧

80%的问题都由20%的TW引发,甚至在各种的TCP实现中,大量的代码在处理TW!我个人觉得这有点过了!引入TW状态是为了确认老数据到来或者消失,且等待时延那么久,这已经是很多年以前的事了,那时我可能刚出生,家里可能还没有装电话...那时的网络条件,引入这些机制是确实需要的,但是随着网络技术的发展,TW已经慢慢成了鸡肋。即便新的TCP连接被老的FIN终止又怎样,即使新的连接被老的劫持又能怎样,即便不考虑这些,MSL未免也太长了些吧,话说当年DDN年代,这个值就已经很久了...不要试图保持TCP的安全了,即使面对中间人又能怎样?我们不是可以用SSL吗?TCP作为一种底层的传输协议,一定要简单,可是现在呢?虽然其内核保持着原汁原味,但是其细节使多少求知若渴的人踱步门外啊,不得不说,TCP的细节太复杂了,即使是再好的作家,也无法写出一本让人彻底明白的关于TCP细节的书。
        看看规范,各种公式,各种不可插拔的算法,各种魔术字,即使作者本人估计都很难说清楚内中细节。不得不说,TCP有点过度设计了,作为当年的设计精品,在当今越发往上层移动的年代,不合适了。如今越来越多的协议或者开元软件使用简单的UDP做扩展,在实现按序到达,确认,否认,时间戳,可靠连接等机制中实现自己需要的而不是所有,从TLS到OpenVPN,无一没有把UDP当成下一代的天骄。我很讨厌TCP,很讨厌这种乱七八糟的东西。你可能会反驳我,但我觉得你被洗脑了,你要知道,如果让你设计一个可靠的有连接协议,你可能做的真的比TCP更好。

 

原文链接:https://www.cnblogs.com/pangblog/p/3400297.html

 

最近同事遇到一个问题,使用python开发的工具在执行的时候无法和MySQL建立连接,其最直接的现象就是满篇的TIME_WAIT,最后通过调整tcp_timestamps参数问题得以解决,再次记录一下这次解决的经验总结。

ps:不过先汗颜一个,对基础的tcp知识太不敏感了,需要回炉重新学习啊。

一、看下TIME_WAIT产生的原因

大家都知道建立连接是著名的三次握手机制。

那么如何关闭连接呢? 其实也是著名的四次握手机制。

TIME_WAIT就产生在四次握手的的主动关闭方,而server端会进入close状态。

那么TIME_WAIT什么时候会消失呢?LINUX会在2MSL时间内消失。(MSL是最大分段生存期,默认为2分钟)

二、无法建立连接的原因

由于同事的脚本的用途是进行采集数据后的写入操作,为了加快速度故使用python的map功能并发写入数据库,当在脚本执行的服务器上报错后,发现满篇都是TIME_WAIT的现象。

经过我们内部讨论,初步认为是由于TIME_WAT没有快速回收导致Linux的可用端口被沾满导致无发建立连接,和MySQL的设置没有关系。

查看目前Linux可用端口范围:

[me]sysctl -a|grep ip_local_port_range net.ipv4.ip_local_port_range = 3276861000查看当前占用的端口直接用netstat就可以了:

netstat -nat|grep -i time_wait|wc -l三、如何解决?

对于TCP链接的优化大家都知道的三个参数:tcp_tw_reuse、tcp_tw_recycle、tcp_timestamp

1、我们先看看这3个参数的定义和作用。

tcp_tw_reuse:用来使用time-wait状态的sockets重用

Allow to reuse TIME-WAIT sockets for new connections when it issafe from protocol viewpoint. Default value is 0.It should not be changed without advice/request of technical experts.tcp_tw_recycle:用来加速对time-wait

Enable fast recycling TIME-WAIT sockets. Default value is 0.It should not be changed without advice/request of technical experts.tcp_timestamp:在TCP的包头添加时间戳

Enable timestamps as defined in RFC13232、调整记录

我们先查看服务器的状态,发现reuse和recycle的参数都是1,但是time_wait并没有被很快的回收,也没有被快速的重用。

[root ~]# cat /proc/sys/net/ipv4/tcp_tw_reuse 1[root ~]# cat /proc/sys/net/ipv4/tcp_tw_recycle 1后来发现是由于tcp_timestamp这个参数设置为0的原因

[root ~] cat /proc/sys/net/ipv4/tcp_timestamps 0将这个参数置为1之后,time_wati的状态立刻下降了很多,脚本也可以正常执行了。看来tcp_timestamp非常关键。

3、探寻原因

但是,究竟是tcp_tw_reuse起作用了? 还是tcp_tw_recycle起作用了?我们还是不知道,只能从现象得知问题解决了。

我们将tcp_tw_recycle的值设定为0之后,再次测试,再次出现了大量的time_wait现象。

为了证明tcp_tw_recycle真正起作用了,我们再次将tcp_tw_reuse参数设置为1,这时候也再次出现了大量的time_wait的现象。

看来,最终我们的结论是同时开启tcp_tw_recycle,tcp_tw_reuse和tcp_timestamp才能真正做到快速回收和服用time-wait状态的socket。

从stackoverflow上看到的一个答案比较靠谱。点这里

但是同时也发现,网上有很多信息显示,如果同时开启tcp_tw_recycle和tcp_timestamp会出现问题。主要是由于TCP的一种行为

RFC 1323  TCP Extensions for High Performance  Jacobson, Braden, & BormanAn 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.三、结论

1、开启tcp_timestamp是开启tcp_tw_recycle,tcp_tw_reuse和tcp_timestamp的前提条件。

2、但是在nat模式下,不用将tcp_tw_recycle和tcp_timestamp同时开启,这会造成tcp超时引发故障。
————————————————

原文链接:https://blog.csdn.net/wych1981/java/article/details/45892035

posted @ 2020-04-25 10:19  罐头1992  阅读(893)  评论(0)    收藏  举报