彻底弄懂TIME_WAIT 及 tcp_tw_reuse选项

彻底弄懂TIME_WAIT 及 tcp_tw_reuse选项

约等于这篇文章的摘选和翻译
https://vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux

彻底弄懂TIME_WAIT 及 tcp_tw_reuse选项_第1张图片

TIME_WAIT是tcp释放连接的四次挥手后的主动关闭连接方的状态

TIME_WAIT存在的两个原因

人尽皆知的是,防止上一个TCP连接的延迟的数据包(发起关闭,但关闭没完成),被接收后,影响到新的TCP连接。(唯一连接确认方式为四元组:源IP地址、目的IP地址、源端口、目的端口),包的序列号也有一定作用,会减少问题发生的几率,但无法完全避免。尤其是较大接收windows size的快速(回收)连接。

第一个理由和连接“化身”和报文迷走有关系,为了让旧连接的重复分节在网络中自然消失。
下一次新的连接中就肯定不会出现旧连接的报文段了。

  • 我们知道,在网络中,经常会发生报文经过一段时间才能到达目的地的情况,产生的原因是多种多样的,如路由器重启,链路突然出现故障等。
  • 如果迷走报文到达时,发现 TCP 连接四元组(源 IP,源端口,目的 IP,目的端口)所代表的连接不复存在,那么很简单,这个报文自然丢弃。
    • 我们考虑这样一个场景,在原连接中断后,又重新创建了一个原连接的“化身”,说是化身其实是因为这个连接和原先的连接四元组完全相同,如果迷失报文经过一段时间也到达,那么这个报文会被误认为是连接“化身”的一个 TCP 分节,这样就会对 TCP 通信产生影响。

另外一个作用是,当最后一个ACK丢失时,远程连接进入LAST-ACK状态,它可以确保远程已经关闭当前TCP连接。如果没有TIME-WAIT状态,当远程仍认为这个连接是有效的(没有收到ACK或RST),则会继续与其通讯,导致这个连接会被重新打开。当远程收到一个SYN 时,会回复一个RST包,因为这SEQ不对,那么新的连接将无法建立成功,报错终止。

  • 如果远程因为最后一个ACK包丢失,导致停留在LAST-ACK状态,将影响新建立具有相同四元组的TCP连接。

所以,TCP 就设计出了这么一个机制,经过 2MSL 这个时间,足以让两个方向上的分组都被丢弃,使得原来连接的分组在网络中都自然消失,再出现的分组一定都是新化身所产生的。

2MSL 的时间是从主机 1 接收到 FIN 后发送 ACK 开始计时的;如果在 TIME_WAIT 时间内,因为主机 1 的 ACK 没有传输到主机 2,主机 1 又接收到了主机 2 重发的 FIN 报文,那么 2MSL 时间将重新计时。道理很简单,因为 2MSL 的时间,目的是为了让旧连接的所有报文都能自然消亡,现在主机 1 重新发送了 ACK 报文,自然需要重新计时,以便防止这个 ACK 报文对新可能的连接化身造成干扰。

TIME_WAIT 的危害

  • 对端口资源的占用,一个 TCP 连接至少消耗一个本地端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过net.ipv4.ip_local_port_range指定,如果 TIME_WAIT 状态过多,会导致无法创建新连接。这个也是我们在一开始讲到的那个例子。

net.ipv4.tcp_tw_reuse —— 针对客户端(发起连接的一方)优化TIME_WAIT

机器作为客户端时起作用,开启后time_wait在一秒内回收
对于服务端,打开tw_reuse无效

TIME-WAIT状态是为了防止不相关的延迟请求包被接受。 但在某些特定条件下,很有可能出现,新建立的TCP连接请求包,被老连接(同样的四元组,暂时还是TIME-WAIT状态,回收中)的连接误处理。

RFC 1323 实现了TCP拓展规范,以保证网络繁忙状态下的高可用。除此之外,另外,它定义了一个新的TCP选项–两个四字节的timestamp fields时间戳字段,第一个是TCP发送方的当前时钟时间戳,而第二个是从远程主机接收到的最新时间戳。

启用net.ipv4.tcp_tw_reuse后,如果新的时间戳,比以前存储的时间戳更大,那么linux将会从TIME-WAIT状态的存活连接中,选取一个,重新分配给新的连接出去的TCP连接。

连出的TIME-WAIT状态连接,仅仅1秒后就可以被重用了。

它如何安全?

  • TIME-WAIT状态的第一个目的是避免在不相关的连接中接受重复的段。由于使用了时间戳,此类重复段将带有过时的时间戳,因此会被丢弃。
彻底弄懂TIME_WAIT 及 tcp_tw_reuse选项_第2张图片
  • 第二个目的是确保远端 不会因为最后一个ACK丢失而处于LAST-ACK状态。
    远端将重传FIN段,直到(3种可能):
    • 它放弃(并断开连接)
    • 它收到正在等待的ACK(并断开连接)
    • 它收到一个RST(并断开连接) —— 新连接会回复
    • 它收到 连接请求(同四元组) —— 特殊情况

如果按时接收到FIN段,则本端套接字仍将处于该TIME-WAIT状态,并会发送预期的ACK段。

net.ipv4.tcp_tw_reuse开启后 特殊情况的分析

  • 特殊情况
    之前处于time_wait的机器 变为SYN_SENT状态,向之前的 断开连接的机器 重新 发送连接请求(四元组相同)

一旦新连接(左边的机器)替换了TIME-WAIT状态,新连接的SYN 段将被忽略(感谢时间戳)并且不会由RST应答,而只会通过FIN段的重传 。然后FIN段将用RST应答 (因为本地连接处于该SYN-SENT状态),这将允许转换出该LAST-ACK状态。 最初的SYN 段最终将被重新发送(一秒后),因为没有应答,并且连接将在没有明显错误的情况下建立,除了轻微的延迟:

如果远端因为最后一个ACK丢失而一直处于LAST-ACK状态,当本端转换到SYN-SENT状态时,远端连接将被重置。

总结

通用的解决方案是通过使用更多的服务器端口来增加可能的四胞胎的数量。这将使您不会用尽可能的TIME-WAIT 状态的连接。

服务器端,启用net.ipv4.tcp_tw_reuse对传入连接无用。

客户端,启用net.ipv4.tcp_tw_reuse是另一种几乎安全的解决方案。启用net.ipv4.tcp_tw_recycle除了net.ipv4.tcp_tw_reuse大部分是无用的。

此外,在设计协议时,不要让客户端先关闭。客户端将不必处理TIME-WAIT将责任推给更适合处理此问题的服务器的状态。

你可能感兴趣的:(linux笔记,tcp/ip,网络)