实战案例|多样的端口重用现象

科技   科技   2024-09-26 18:06   上海  





引言


在当今复杂多变的网络环境中,TCP 连接的管理已成为影响系统性能和可靠性的关键因素。其中,TCP TIME_WAIT 状态下的端口重用问题尤为棘手,它不仅涉及 TCP 协议的底层机制,还与实际网络环境的复杂性密切相关。本文将通过理论分析和实践验证来探讨这一问题。





偶发的交易失败


在一个大型企业的互联网业务系统中,部分客户端在访问系统时偶尔会遇到交易失败。这种失败看似随机发生,却足以影响用户体验和业务运营。为了找出问题的根源,我们开展了一系列深入的网络分析工作。



我们首先收集了大量的网络报文,并在两个关键点进行了数据包捕获:

  1. 捕获点 A:靠近客户端

  1. 捕获点 B:在网络路径上,该点右侧连接广域网

通过对这些数据包的仔细分析,我们逐渐揭示了问题的现象:


  • 在捕获点 A,我们观察到客户端发出 SYN 报文尝试建立 TCP 连接,但服务端并未回应预期的SYN-ACK。相反,服务端直接返回了 RST 报文,导致 TCP 握手失败。



  • 更有趣的是,在捕获点 B 的观察中,我们发现客户端在短时间内,具体为 9.4 秒,试图重用相同的端口发起新的连接。然而,服务端以 RST 报文拒绝了这些请求。








初步诊断



这些观察结果指向了一个现象:TCP 端口重用被拒绝。



我们形成了初步推断:
  1. 1、网络中存在 NAT(网络地址转换)设备:A、B 两点之间的网络设备做了 SNAT,而 B 到服务端之间的网络设备,如防火墙和负载均衡器,也可能会进行 NAT 转换。
  2. 2、在我们可观察到的 A、B 两点之间的网络设备上,由于 NAT 池资源不足,导致在短时间内重用了仍处于 TIME_WAIT 状态的端口。
  3. 3、“服务端”因某种原因拒绝了这些重用端口的连接请求。但这里的“服务端”到底是最右端服务器,还是它和捕获点 B 之间的某个网络设备,我们缺乏直接观测点,还不能下结论。


这个判断为我们指明了进一步调查的方向。然而,要全面理解这个问题,我们需要先深入探讨 TCP TIME_WAIT 状态的本质及其在网络通信中的作用。






TCP TIME_WAIT 状态



TIME_WAIT 状态是 TCP 连接生命周期中一个常被忽视却至关重要的阶段。它由主动关闭连接的一方进入,通常持续 2MSL(Maximum Segment Lifetime)的时间。在大多数 Linux 系统中,MSL 时间默认设置为 30 秒,因此 TIME_WAIT 状态将持续 60 秒。


TIME_WAIT 解决的问题


TIME_WAIT 状态在 TCP 协议中扮演着重要角色:

  • 防止旧连接的数据包干扰新连接:在网络环境中,数据包的传输并非总是即时的。有时,属于已关闭连接的数据包可能会延迟到达。如果没有 TIME_WAIT 状态,这些延迟的数据包可能会被误认为是新建立的连接(使用相同的四元组)的一部分,从而导致数据混乱。

  • 确保被动关闭方能够正常关闭连接:在四次挥手的最后阶段,如果主动关闭方(进入 TIME_WAIT 状态的一方)发送的最后一个 ACK 丢失,被动关闭方会重新发送 FIN。TIME_WAIT 状态确保主动关闭方在这段时间内仍能响应,重新发送 ACK,从而保证连接的完整关闭。


TIME_WAIT 的形成过程


为了更好地理解 TIME_WAIT 状态,让我们回顾一下 TCP 连接关闭的完整过程,特别是当服务端主动关闭连接时:
  1. 第一次挥手:服务端发送 FIN 报文,表示不再发送数据,但仍可接收数据。此时服务端进入FIN_WAIT_1 状态。
  1. 第二次挥手:客户端收到 FIN 后,发送 ACK 作为响应,并进入 CLOSE_WAIT 状态。服务端收到 ACK 后,进入 FIN_WAIT_2 状态。
  1. 第三次挥手:客户端处理完剩余数据后,发送 FIN 报文,表示准备关闭连接。客户端此时进入 LAST_ACK 状态。
  1. 第四次挥手:服务端收到客户端的 FIN 后,发送 ACK 作为响应,并进入 TIME_WAIT 状态。客户端收到 ACK 后,立即关闭连接(CLOSED 状态)。
在这个过程中,服务端作为主动关闭方,最终进入 TIME_WAIT 状态,并在此状态下等待一段时间。


TCP 状态机,引用自 Vincent Bernat “Coping with the TCP TIME-WAIT state on busy Linux servers”






主机 TIME_WAIT 状态下的端口重用行为



关于 TIME_WAIT 状态以及端口重用的各种问题,在互联网上有诸多探讨,其中是否能成功重用端口的主要条件取决于「时间戳」和「序列号」。


但是在我们分析了 Linux 内核源码之后,对端口重用的行为判断,与部分互联网技术博主的观察并不完全一致。因此,为了验证 TIME_WAIT 状态下 Socket 对端口重用的处理机制,我们设计并执行了两组实验,旨在模拟不同场景下的端口重用行为,并观察系统的响应。


我们的主要目标是验证在 Linux 系统上,处于 TIME_WAIT 状态的 Socket 在接收到相同四元组(源 IP、源端口、目的 IP、目的端口)的 SYN 报文时的不同处理方式。特别地,我们关注以下几个方面:
  1. TIME_WAIT 状态下,服务器如何处理来自相同客户端、相同端口的新连接请求。
  1. 时间戳选项(TCP Timestamps)对端口重用行为的影响。
  1. 序列号(Sequence Number)对端口重用行为的影响。


我们的实验环境配置如下:
  • 客户端:IP 地址为 10.2.2.136
  • 服务端:IP 地址为 10.2.2.237,端口 80,运行 httpd 服务
  • 操作系统:CentOS Linux release 7.9.2009(Core)
  • 内核版本:Linux 3.10.0-1160.el7.x86_64


我们在服务端主机上进行抓包分析,获取最直接和准确的网络交互数据。以下是两个典型场景的详细分析:

端口重用成功的场景


重用端口的 SYN 报文,时间戳大于服务端最后收到的报文,但序列号小于服务端最后收到的报文。



在这个场景中,我们观察到以下关键阶段:
  1. 初始连接的建立与关闭
  • 数据传输完成后,双方通过 FIN 和 ACK 报文关闭连接。

  • 服务端回应 SYN-ACK,客户端确认,连接成功建立。

  • 源 IP 10.2.2.236 向目的 IP 10.2.2.237 发送 SYN 报文,源端口为 47982,目的端口为 80。

  1. TIME_WAIT 状态的产生与持续
  • 连接关闭后,服务端(主动关闭方)进入 TIME_WAIT 状态,预计持续 60 秒。
  1. 端口重用的尝试
  • 最后客户端 RST 重置,是因为模拟程序的缘故,并不会真正完成握手。如果是真实场景,客户端此时应当返回 ACK,完成 TCP 三次握手的全过程,进入数据传输阶段。
  • 服务端发送 SYN-ACK 响应,确认了重用端口的 SYN 报文,更新序列号和确认号,变迁为 SYN_RECV 状态。
  • 尽管新 SYN 的序列号 100 小于服务端最后收到的序列号 3262892139,服务端仍然接受了这个连接请求。
  • 新 SYN 报文的时间戳为 85878072,大于服务端最后收到的时间戳 83454786。
  • 在 TIME_WAIT 期间,源 IP 10.2.2.236 再次使用源端口 47982 向目的端口 80 发起新的 SYN 请求。



端口重用失败的场景


重用端口的 SYN 报文,时间戳小于服务端最后收到的报文,序列号大于服务端最后收到的报文。


在这个场景中,我们观察到:

  1. 初始连接的建立与关闭

  • 与场景一类似,建立并关闭了初始连接。

  1. TIME_WAIT 状态的产生与持续

  • 服务端进入 TIME_WAIT 状态,等待 2MSL 时间。

  • 端口重用的尝试

    • 源 IP 10.2.2.236 再次使用源端口 47990 向目的端口 80 发起新的 SYN 请求。

    • 新 SYN 报文的时间戳为 84489294,小于服务端最后收到报文的时间戳 85489294。

    • 新 SYN 报文的序列号为 587125688,大于服务端最后收到报文的序列号 586125781。

    • 服务端拒绝建立连接,返回 ACK 报文,该报文没有 SYN 标志位,同时重复了对客户端 FIN 报文的 ACK 确认号,表示拒绝建立连接。

    • 最后客户端 RST 重置,是因为模拟程序的缘故,并不会真正完成握手。而此时,如果是真实场景,处于 SYN_SENT 状态下的客户端,也的确会返回 RST。



    这张图读者可能会有疑问,为什么最后有两种状态 CLOSED 和 TIME_WAIT,这取决于 Linux 系统的内核参数 net.ipv4.tcp_rfc1337。
    • 参数值为 0,收到 RST 报文后,会结束 TIME_WAIT 状态,变迁为 CLOSED 状态,释放连接。
    • 参数值为 1,收到 RST 报文后直接丢弃,继续 TIME_WAIT 状态,直到 2MSL 时间结束。
    在我们的测试环境中,该参数值为 0,因此服务端收到 RST 后,TCP 状态变为 CLOSED,释放连接。


    实验结论


    以上仅是两个场景的结果,第一组实验,我们覆盖了四个场景,总结出SYN报文在不同序列号和时间戳组合下的端口重用行为:


    序列号 \ 时间戳
    时间戳大于最后收到的时间戳
    时间戳小于最后收到的时间戳
    序列号大于最后的序列号
    成功(SYN,SYN-ACK)
    失败(SYN,ACK)
    序列号小于最后的序列号
    成功(SYN,SYN-ACK)
    失败(SYN,ACK)


    从这些实验结果,我们可以得出结论:
    在启用时间戳的前提下:
    • 只要 SYN 报文的时间戳数值大于服务端最后收到的报文,哪怕序列号小于服务端最后收到的报文,即可成功复用端口,使处于 TIME_WAIT 状态的 Socket 从新进入 SYN_RECV 状态。
    • 只要 SYN 报文的时间戳数值小于服务端最后收到的报文,处于 TIME_WAIT 状态的 Socket 将拒绝连接,遵循 TIME_WAIT 机制,返回一个重复的 ACK 报文,并回到 TIME_WAIT 状态。

    第二组实验,我们总结出SYN报文在不同序列号下的端口重用行为:


    序列号
    不启用时间戳
    大于最后的序列号
    成功(SYN,SYN-ACK)
    小于最后的序列号
    失败(SYN,ACK)

    从这些实验结果,我们可以得出结论:
    在不启用时间戳的前提下:
    • SYN 报文的序列号数值大于服务端最后收到的报文,可成功复用端口,连接建立成功。

    • SYN 报文的序列号数值小于服务端最后收到的报文,连接被 Socket 拒绝,建立失败。

    这些实验结果为我们理解 TIME_WAIT 状态下的端口重用机制提供了事实依据。然而,当我们将这些结论与开篇案例中观察到的现象进行对比时,发现存在不一致之处。这种差异引发了我们进一步的思考和分析。






    案例现象与实验结果的差异


    回顾开篇的真实案例,我们观察到服务器直接返回 RST 报文拒绝客户端的新连接请求,而不是返回 ACK 报文。那么,是什么原因导致了这种差异呢?


    在标准的 TCP 协议实现中,“TIME_WAIT 状态下的 Socket 收到 SYN 报文”这种情况并不会直接触发 RST 机制。而根据《TCP/IP详解 卷1:协议》中的描述,触发 RST 的主要场景包括:

    1. 客户端连接请求,即 SYN 报文,发送到服务端不存在的端口。
    2. 客户端或服务端,主动终止一个连接。
    3. 在一个已关闭的 TCP 连接上收到了报文段。
    TIME_WAIT 状态下的 Socket 收到 SYN 报文,不属于上面任何一个场景,因此不应当返回 RST 报文。然而,我们的案例中出现了直接返回 RST 的情况,这暗示了可能存在其他因素影响了连接的建立过程。

    网络环境差异


    比对案例环境和实验环境,一个关键区别在于:网络环境的差异、捕获点的位置。在实验环境中,捕获点位于服务器主机上。而在案例中,捕获点位于客户端与服务端之间的网络路径上。从报文的 TTL 数值也可以看出,捕获点两侧并非紧邻客户端或服务端,而是经过了多次网络跳转,而且后面的这些网络设备,按通常的网络设计理解,也会有 NAT 转换。

    这一发现引导我们思考:可能是中间网络设备,如防火墙或负载均衡器,发出的 RST 报文。


    如果回到开篇案例的故障现象部分,我们可以确认的是:
    • 在捕获点 A、B 之间的网络设备上,做了SNAT 的转换。

    • 在网络设备 A 右侧的位置,的确在 10 秒内重用了 TCP 端口,随之而来的是 RST 报文。


    虽然缺乏更多观测数据,但根据网络路径来判断,这个 RST 报文来自靠近服务端的某个网络设备。如果更仔细的观察这个案例,还可以看到重用端口的 SYN 报文是一个合法的 SYN 报文,它的时间戳和序列号均大于”服务端“收到的最后一个报文。这也显示了是网络设备的一种特定策略,而非标准 TCP 实现。

    事实上,防火墙、负载均衡器等网络设备在处理 TIME_WAIT 状态的连接时,其实现机制可能与主机操作系统有所不同。这些设备通常会采用特定的优化策略来提高性能和安全性。许多防火墙和负载均衡器会维护一个连接状态表,用于跟踪活动连接。当检测到 TIME_WAIT 状态的连接尝试重用时,可能会采取更激进的处理方式,直接发送 RST 以快速清理连接状态。也有网络设备会选择丢弃,不作出任何响应的策略。






    结论与建议


    综合以上分析,我们可以推断,案例中观察到的问题现象,很可能是由于网络路径中的中间设备,如防火墙或负载均衡器的特定实现所导致的。这种行为虽然与标准TCP实现有所不同,但在实际网络环境中并不罕见,它反映了网络设备厂商在处理复杂网络场景时所做的权衡和优化。

    因此,在多样的网络环境和系统版本中,如果使用抓包方式观察分析此类问题,我们可能会看到相同的特征和不同的现象。


    总结端口重用导致的连接失败的相同特征在于:

    • NAT网络结构
    • 部分连接失败,而不是全部失败
    • 在短时间内发生了相同 TCP 四元组的重用,这个时间可能是 60 秒、120 秒
    不同的现象在于:TCP 连接握手失败的行为不一样,影响因素包括网络结构、系统版本等等。

    因此,遇事莫慌,先抓包,只要识别到上面的三个特征,即可往这个方向继续分析排查。


    参考资料与阅读推荐

    [1] Coping with the TCP TIME-WAIT state on busy Linux servers, by Vincent Bernat

    [2] 解Bug之路-NAT引发的性能瓶颈 by 无毁的湖光-Al

    [3] 在 TIME_WAIT 状态的 TCP 连接,收到 SYN 后会发生什么? by 小林coding

    [4] 机械工业出版社《TCP/IP详解 卷1:协议》 by  W. Richard Stevens










    天旦Netis
    上海天旦网络科技发展有限公司是国际领先的业务与网络性能管理领域的软件产品企业,针对关键业务保障、交易分析、大数据采集和挖掘等方面提供专业的产品和解决方案。