卡恩算法,TIME_WAIT 及其对协议和可扩展客户端服务器系统的设计含义

Karn 算法解决了计算机网络中使用传输控制协议(TCP)获得消息往返时间的准确估计的问题该算法有时也称为 Karn-Partridge 算法[1],Phil KarnCraig Partridge于 1987 年在一篇论文中提出的[2]

由于重传段造成的歧义,TCP 中的准确往返估计可能难以计算。往返时间被估计为发送一个段的时间和它的确认返回给发送者的时间之间的差,但是当数据包被重新传输时有一个歧义:确认可能是对第一个的响应段的传输或到随后的重传。

Karn 算法在更新往返时间估计时忽略重传的段。往返时间估计仅基于明确的确认,即对仅发送一次的段的确认。

Karn 算法的这种简单实现也会导致问题。考虑一下当 TCP 在延迟急剧增加后发送一个段时会发生什么。使用先前的往返时间估计,TCP 计算超时并重新传输一个段。如果 TCP 忽略所有重传数据包的往返时间,则往返估计将永远不会更新,并且 TCP 将继续重传每个段,永远不会调整增加的延迟。

此问题的解决方案是将传输超时与计时器退避策略结合起来。计时器退避策略计算初始超时。如果定时器超时并导致重传,TCP 通常将超时增加两倍。该算法已被证明在平衡具有高丢包率的网络的性能和效率方面极为有效。[3] [需要页面]理想情况下,不需要Karn 算法。应使用根本原因分析技术调查具有高往返时间和重传超时的网络[4]

Karn 算法 - 维基百科

袜子压力 - 维基百科

Sockstress是一种利用TCP攻击 Internet 和其他网络上的服务器的方法,包括WindowsMacLinuxBSD和任何接受TCP连接的路由器或其他 Internet 设备[1]该方法通过尝试用完本地资源来使服务或整个机器崩溃,本质上是拒绝服务攻击。

Sockstress由已故的 Jack C. Louis 在Outpost24开发为内部概念验证Louis 发现了使用Unicornscan测试和探测企业安全网络的异常情况,这导致了Sockstress的发展[2]该概念于2008 年 9 月首次展示[3] [4] [5]研究人员计划在芬兰T2 会议上发布更多细节,在那里他们展示了攻击。相反,他们选择继续与供应商和标准社区密切合作,并为他们提供更多时间。在一篇博客文章中,他们说“我们不会让他们 [供应商] 承受不适当的压力,让他们匆忙修复实施不佳的问题。”

Fotis Chantzis aka ithilgore 在Phrack ezine 上发布了一个概念验证工具Nkiller2,它展示了一种类似于socktress 的攻击[6] Nkiller2 完全无状态地工作,使用数据包解析技术和虚拟状态,并利用 TCP 的固有机制 Persist Timer,从而能够以最少的网络流量执行和无限延长通用 DoS 攻击。

关于 Sockstress [编辑]

Sockstress 是一个用户端 TCP 套接字压力框架,它可以完成任意数量的打开套接字,而不会产生跟踪状态的典型开销。一旦建立了套接字,它就能够发送针对特定类型的内核和系统资源(例如计数器、定时器和内存池)的 TCP 攻击。显然,这里描述的一些攻击被认为是“众所周知的”。然而,这些攻击的全部影响鲜为人知。此外,还有更多的攻击有待发现/记录。随着研究人员记录消耗特定资源的方法,可以将攻击模块添加到sockstress 框架中。

袜子攻击工具由两个主要部分组成:

1) Fantaip:Fantaip [7]是一个“Phantom IP”程序,它对IP 地址执行ARP。要使用 fantaip,请输入“fantaip -i interface CIDR”,例如,“fantaip -i eth0 192.168.0.128/25”。根据本地网络拓扑的要求,可以选择通过其他方式提供此 ARP/第 2 层功能。由于sockstress 在用户态完成TCP 套接字,因此不建议使用带有为内核配置的IP 地址的sockstress,因为内核随后会RST 套接字。这不是严格要求的,因为使用防火墙丢弃带有 rst 标志的传入数据包可用于实现相同的目标并防止内核干扰攻击向量。

2) Sockstress:在最基本的使用中,sockstress 只是打开 TCP 套接字并发送指定的 TCP 压力测试。它可以选择发送特定于应用程序的 TCP 负载(即“GET/HTTP/1.0”请求)。默认情况下,攻击后它会忽略已建立套接字上的后续通信。它可以选择对活动套接字进行 ACK 探测。攻击利用目标在握手后提供的暴露资源。

在博客、新闻和讨论列表中大量讨论的客户端 cookie 是sockstress 的一个实现细节,并不是执行这些攻击所必需的。

攻击场景[编辑]

Sockstress 框架中的每一次攻击都会对其所攻击的系统/服务产生一些影响。但是,对于特定的系统/服务组合,某些攻击比其他攻击更有效。

连接洪水压力[编辑]

Sockstress 没有用于执行简单连接泛洪攻击的特殊攻击模块,但是如果 -c-1(最大连接数无限制)和 -m-1(最大同步无限制)选项是用过的。这将通过执行连接泛滥来近似于石脑油攻击,如第 3.1.1 节 CPNI 文档中所述耗尽所有可用的 TCB

示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子 -A -c-1 -d 192.168.1.100 -m-1 -Mz -p22,80 -r300 -s192.168.1.128/25 -vv

零窗连接应力[编辑]

创建到侦听套接字的连接,并在 3 次握手时(在最后一个 ack 内)发送 0 窗口。

       同步 ->(4k 窗口)
               <- syn+ack(32k 窗口)
       确认 -> (0 窗口)

现在服务器将不得不“探测”客户端,直到零窗口打开。这是最容易理解的攻击类型。结果类似于连接泛滥,除了套接字可能无限期地保持打开状态(启用 -A/ACK 时)。这在 CPNI 文档的 2.2 节中有描述。这里的一个变体是在将窗口设置为 0 之前对客户端有效负载(即“GET/HTTP/1.0”)进行 PSH。这种变体类似于 CPNI 文档第 5.1.1 节中描述的内容。另一种变化是偶尔通告大于 0 的 TCP 窗口,然后返回到 0 窗口。

好对抗:

超时时间长的服务示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子 -A -c-1 -d 192.168.1.100 -m-1 -Mz -p22,80 -r300 -s192.168.1.128/25 -vv

小窗应力[编辑]

创建到侦听套接字的连接,并通过 3 次握手(在最后一个 ack 内)将窗口大小设置为 4 个字节,然后创建一个带有 tcp 有效负载的 ack/psh 数据包(进入一个希望足够大的窗口以接受它)窗口仍设置为 4 个字节。这可能会导致内核内存被消耗,因为它接受响应并将其拆分为 4 字节的小块。这与连接泛滥不同,现在每个请求都会消耗内存。这已经可靠地将 Linux/Apache 和 Linux/sendmail 系统置于不复存在的状态。它对其他系统也很有效。我们预计这与第 17 页倒数第二段 CPNI 文档中描述的效果类似。

查看sockstress 源中的payload.c 文件。查找 hport switch 语句。在该部分中,您可以指定要发送到特定端口的有效负载。发送将生成尽可能大的响应的有效负载是最有效的(即“GET /largefile.zip”)。

好对抗:

包含初始连接横幅的服务接受初始请求并发送大响应的服务(例如针对大网页的 GET 请求或文件下载)示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子 -A -c-1 -d 192.168.1.100 -m-1 -Mw -p22,80 -r300 -s192.168.1.128/25 -vv

段孔应力[编辑]

创建到侦听套接字的连接,并通过 3 次握手(在最后一个 ack 内)向窗口的开头发送 4 个字节,如远程系统所通告的那样。然后向窗口末尾发送 4 个字节。然后 0-window 连接。根据堆栈的不同,这可能会导致远程系统为每个连接分配多个内核内存页面。这与连接泛滥不同,现在每次建立连接都会消耗内存。这种攻击最初是为了针对 Linux 而创建的。它对 Windows 也非常有效。这是我们在 sec-t 和 T2 演示中使用的攻击。我们预计这与 CPNI 文档第 5.2.2 节第 5 段和第 5.3 节中描述的效果类似。

好对抗:

响应此刺激分配多页内核内存的堆栈示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子 -A -c-1 -d 192.168.1.100 -m-1 -Ms -p22,80 -r300 -s192.168.1.128/25 -vv

请求鳍暂停压力[编辑]

创建到侦听套接字的连接。PSH 应用程序有效负载(即“GET / HTTP/1.0”)。FIN 连接和 0 窗口它。根据您所针对的堆栈/应用程序,此攻击将产生非常不同的结果。将此用于 Cisco 1700 (IOS) Web 服务器,我们观察到套接字无限期地留在 FIN_WAIT_1 中。使用足够多的此类套接字后,路由器无法再正确地进行 TCP 通信。

查看sockstress 源中的payload.c 文件。查找 hport switch 语句。在该部分中,您可以指定要发送到特定端口的有效负载。向正在与之交互的应用程序发送看起来像普通客户端的有效负载非常重要。针对我们的 cisco 1700,在使用这种攻击时,以非常慢的速度进行攻击很重要。

示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子-A -c-1 -d 192.168.1.100 -m-1 -MS -p80 -r10 -s192.168.1.128/25 -vv

激活雷诺压力压力[编辑]

创建到侦听套接字的连接。PSH 应用程序有效负载(即“GET / HTTP/1.0”)。三重重复 ACK。

查看sockstress 源中的payload.c 文件。查找 hport switch 语句。在该部分中,您可以指定要发送到特定端口的有效负载。向正在与之交互的应用程序发送看起来像普通客户端的有效负载非常重要。

好对抗:

支持这种激活 reno 或类似调度程序功能的方法的堆栈示例命令:

  1. fantaip -i eth0 192.168.1.128/25 -vvv
  2. 袜子压力 -A -c-1 -d 192.168.1.100 -m-1 -MR -p22,80 -r300 -s192.168.1.128/25 -vv

其他想法[编辑]

  • fin_wait_2 压力

创建到侦听套接字的连接。PSH 一个应用程序有效负载,它可能会导致另一端的应用程序关闭套接字(目标发送一个 FIN)。确认 FIN。

好对抗:

没有 FIN_WAIT_2 超时的堆栈。大拥塞窗口应力

  • 收缩路径 mtu 应力
  • md5压力

攻击的影响[编辑]

如果攻击成功发起永久停止的连接,服务器的连接表可以很快被填满,有效地为特定服务创建拒绝服务条件。在许多情况下,我们还看到攻击消耗了大量的事件队列和系统内存,从而加剧了攻击的影响。其结果是系统不再具有用于 TCP 通信的事件计时器、系统冻结和系统重新启动。攻击不需要大量带宽。

虽然让单个服务在几秒钟内变得不可用是微不足道的,但使整个系统失效可能需要几分钟,在某些情况下需要几个小时。作为一般规则,系统拥有的服务越多,它就越快屈服于攻击的破坏性(TCP 损坏、系统锁定、重启等)影响。或者,可以通过从大量 IP 地址进行攻击来实现攻击放大。我们通常在实验室中从 /29 到 /25 进行攻击。从 /32 攻击通常不太有效导致系统范围的故障。

开发注意事项[编辑]

攻击需要成功的 TCP 3 次握手才能有效地填充受害者连接表。这限制了攻击的有效性,因为攻击者无法通过欺骗客户端 IP 地址来避免可追溯性。

袜子式攻击还需要访问攻击机器上的原始套接字,因为必须在用户空间而不是操作系统的 connect() API 中处理数据包原始套接字在Windows XP SP2及更高版本上被禁用,但设备驱动程序很容易获得[8]来将此功能重新安装到 Windows 中。该漏洞可以在其他具有原始套接字(例如*nix)的平台上按原样执行,并且需要 root(超级用户)权限。

缓解[编辑]

由于攻击者必须能够建立 TCP 套接字来影响目标,因此将访问关键系统和路由器上的 TCP 服务列入白名单是目前最有效的缓解手段。使用IPsec也是一种有效的缓解措施。

根据 Cisco Response [9],当前的缓解建议是只允许可信来源访问基于 TCP 的服务。这种缓解措施对于关键基础设施设备尤为重要。Red Hat表示“由于上游决定不发布更新,Red Hat 不打算发布更新来解决这些问题;但是,可以减少这些攻击的影响。” 在 Linux 上使用具有连接跟踪和速率限制的iptables可以显着限制漏洞利用的影响。[10]

纳格尔算法Nagle 算法 - 维基百科

Nagle 算法是一种通过减少需要通过网络发送的数据包数量来提高TCP/IP网络效率的方法它是由 John Nagle 在为福特航空航天公司工作时定义的它于 1984 年作为评论请求(RFC) 发布,标题RFC 896中 IP/TCP Internetworks中的拥塞控制。  

RFC 描述了他所谓的“小包问题”,即应用程序以小块重复发送数据,通常只有 1个字节的大小。由于TCP数据包具有 40 字节的标头(TCP 为 20 字节,IPv4为 20 字节),这导致 1 字节有用信息的 41 字节数据包,这是一个巨大的开销。这种情况经常发生在Telnet会话中,其中大多数按键会生成立即传输的单个字节数据。更糟糕的是,在慢速链路上,许多这样的数据包可以同时传输,可能导致拥塞崩溃

Nagle 算法的工作原理是将许多小的外发消息组合在一起并一次发送它们。具体来说,只要存在发送方未收到确认的已发送数据包,发送方就应继续缓冲其输出,直到获得完整数据包的输出,从而允许一次性发送所有输出。

RFC 将算法定义为

如果任何先前在连接上传输的数据仍未得到确认,则在来自用户的新传出数据到达时禁止发送新的 TCP 段。

其中 MSS 是最大段大小,可以在此连接上发送的最大段,窗口大小是当前可接受的未确认数据窗口,这可以用伪代码编写为[需要引用]

如果有新数据要发送,那么
    如果窗口大小 ≥ MSS并且可用数据≥ MSS那么
        立即发送完整的 MSS 段
    否则,
        如果管道中仍有未经证实的数据,
            在缓冲区中排队数据,直到收到确认
        别的
            立即发送数据
        如果
    结束 如果
结束 如果结束

与延迟 ACK 的交互[编辑]

该算法与TCP 延迟确认(延迟 ACK)交互不良,该功能在 1980 年代初期大致同时引入 TCP,但由不同的组引入。在启用这两种算法的情况下,对 TCP 连接执行两次连续写入的应用程序,然后在第二次写入的数据到达目的地后才完成读取,会经历长达 500 毫秒的持续延迟,“ ACK延迟”。建议禁用任何一个,尽管传统上禁用 Nagle 更容易,因为这样的开关已经存在于实时应用程序中。

Nagle 推荐的解决方案是通过缓冲应用程序写入然后刷新缓冲区来避免算法发送过早的数据包:[1]

用户级解决方案是避免套接字上的写-写-读序列。写-读-写-读没问题。写-写-写没问题。但是写-写-读是一个杀手。因此,如果可以,请缓冲您对 TCP 的少量写入并一次性发送它们。使用标准 UNIX I/O 包并在每次读取之前刷新写入通常有效。

Nagle 认为延迟 ACK 是一个“坏主意”,因为应用层通常不会在时间窗口内响应。[2]对于典型的用例,他建议禁用“延迟 ACK”而不是他的算法,因为“快速”ACK 不会像许多小数据包那样产生过多的开销。[3]

禁用 Nagle 或延迟 ACK [编辑]

TCP 实现通常为应用程序提供一个接口来禁用 Nagle 算法。这通常称为TCP_NODELAY选项。在 Microsoft Windows 上,TcpNoDelay注册表开关决定默认值。TCP_NODELAY自 1983 年 4.2BSD 中的 TCP/IP 堆栈以来就存在,这是一个具有许多后代的堆栈。[4]

禁用延迟 ACK 的接口在系统之间不一致。TCP_QUICKACK标志自 2001 年 (2.4.4) 起可在 Linux 上使用,也可能在官方界面为SIO_TCP_SET_ACK_FREQUENCY[5]TcpAckFrequency在 Windows 注册表中设置为 1 默认关闭延迟 ACK。[6]

对较大写入的负面影响[编辑]

Nagle 算法适用于任何大小的数据写入。如果单个写入中的数据跨越 2 n 个数据包,其中有 2 n -1 个全尺寸 TCP 段,后跟一个部分 TCP 段,则原始 Nagle 算法将保留最后一个数据包,等待发送更多数据(以填充数据包),或前一个数据包的 ACK(表示所有之前的数据包都已离开网络)。[7]

在请求数据可能大于数据包的任何非流水线停止等待请求-响应应用程序协议中,这会人为地在请求者和响应者之间强加几百毫秒的延迟。最初这并不觉得是一个问题,因为任何非流水线停止等待协议最初可能都不是为了实现高性能而设计的,所以几百毫秒的额外延迟应该没什么区别。后来对 Nagle 算法的改进,称为 Minshall 的修改,[8] 使用停止等待协议解决了这个问题,该协议发送一条消息,然后在发送下一条消息之前等待确认,消除了他们禁用 Nagle 算法的动机(尽管此类协议仍会受到其设计的限制,每个消息交换一次网络往返时间)。

一般来说,由于 Nagle 的算法只是针对粗心的应用程序的一种防御,禁用 Nagle 的算法不会使大多数精心编写的应用程序受益,这些应用程序适当地照顾缓冲。禁用 Nagle 算法将使应用程序能够同时在网络上传输许多小数据包,而不是数量较少的大数据包,这可能会增加网络负载,并且可能会或可能不会提高应用程序性能。

与实时系统的交互[编辑]

期望实时响应和低延迟的应用程序可能对 Nagle 的算法反应不佳。网络多人视频游戏或远程控制操作系统中的鼠标移动等应用程序期望立即发送操作,而算法有目的地延迟传输,延迟为代价提高带宽效率出于这个原因,具有低带宽时间敏感传输的应用程序通常使用绕过 Nagle 延迟的 ACK 延迟。[9]TCP_NODELAY

另一种选择是改用UDP

操作系统实现[编辑]

大多数现代操作系统都实现了 Nagle 的算法。在 AIX、[10] Linux 和 Windows [11] 中,它默认启用,并且可以使用该TCP_NODELAY选项在每个套接字的基础上禁用

HSTCP - 维基百科

TCP 拥塞控制 - 维基百科

最大段生命周期 - 维基百科

TIME_WAIT and its design implications for protocols and scalable client server systems - AsynchronousEvents

 

在构建 TCP 客户端服务器系统时,很容易犯一些严重限制可扩展性的简单错误。这些错误之一是没有考虑到TIME_WAIT国家。在这篇博文中,我将解释为什么TIME_WAIT存在、它可能导致的问题、如何解决它以及何时不应该。
TIME_WAIT是 TCP 状态转换图中经常被误解的状态。这是一些套接字可以进入并保持相对较长时间的状态,如果您有足够的套接字,TIME_WAIT那么您创建新套接字连接的能力可能会受到影响,这可能会影响您的客户端服务器系统的可扩展性。关于套接字TIME_WAIT最初是如何以及为什么最终出现的,通常存在一些误解,不应该存在,这并不神奇。从下面的 TCP 状态转换图可以看出,TIME_WAIT是 TCP 客户端通常最终处于的最终状态。
 
TCP-StateTransitionDiagram-NormalTransitions.png
 
尽管状态转换图显示TIME_WAIT为客户端的最终状态,但它不一定是以TIME_WAIT事实上,发起“主动关闭”的对等方最终处于最终状态,这可以是客户端或服务器。那么,发出“主动关闭”是什么意思呢?
 
如果 TCP 对等方是第一个调用Close()连接的对等方,则它会发起“主动关闭” 在许多协议和客户端/服务器设计中,这就是客户端。在 HTTP 和 FTP 服务器中,这通常是服务器。导致 peer 结束的实际事件顺序TIME_WAIT如下。
 
TCP-StateTransitionDiagram-ClosureTransitions.png
 
既然我们知道了套接字是如何结束的,TIME_WAIT那么理解为什么会存在这种状态以及为什么它会成为潜在问题是很有用的。
 
TIME_WAIT通常也称为 2MSL 等待状态。这是因为转换到的套接字在TIME_WAIT那里停留的时间为2 x 最大段生命周期的持续时间。MSL 是任何段,对于所有意图和目的,构成 TCP 协议一部分的数据报,在被丢弃之前可以在网络上保持有效的最长时间。此时间限制最终受用于传输 TCP 段的 IP 数据报中的 TTL 字段限制。不同的实现选择不同的 MSL 值,常见的值为 30 秒、1 分钟或 2 分钟。RFC 793 将 MSL 指定为 2 分钟,Windows 系统默认为该值,但可以使用TcpTimedWaitDelay注册表设置进行调整
 
TIME_WAIT可能影响系统可扩展性 的原因是,干净关闭的 TCP 连接中的一个套接字将保持该TIME_WAIT状态大约 4 分钟。如果许多连接被快速打开和关闭,那么套接字的输入TIME_WAIT可能会开始在系统上累积;您可以TIME_WAIT使用netstat查看套接字一次可以建立的套接字连接数量是有限的,限制此数量的因素之一是可用本地端口的数量。如果有太多套接字,TIME_WAIT您会发现很难建立新的出站连接,因为缺少可用于新连接的本地端口。但是为什么会TIME_WAIT存在呢?
 
国家有两个原因TIME_WAIT第一个是防止来自一个连接的延迟段被误解为后续连接的一部分。任何在连接处于 2MSL 等待状态时到达的段都将被丢弃。
 
TIME_WAIT-why.png
 
在上图中,我们有两个从端点 1 到端点 2 的连接。每个端点的地址和端口在每个连接中都是相同的。第一个连接以端点 2 发起的主动关闭终止。如果端点 2 没有保持TIME_WAIT足够长的时间以确保来自前一个连接的所有段都已无效,则可以使用延迟段(具有适当的序列号)误认为是第二次连接的一部分...
 
请注意,这是非常不可能的延迟段会造成这样的问题。首先每个端点的地址和端口需要相同;这通常不太可能,因为操作系统通常从临时端口范围中为您选择客户端的端口,从而在连接之间发生变化。其次,延迟段的序列号需要在新连接中有效,这也是不可能的。但是,如果这两种情况都发生,TIME_WAIT则将防止新连接的数据被破坏。
 
状态的第二个原因TIME_WAIT是为了可靠地实现 TCP 的全双工连接终止。如果ACK端点 2的 final被丢弃,则端点 1 将重新发送 final FIN如果连接已转换到CLOSED端点 2,则唯一可能的响应是发送 ,RST因为重传FIN将是意外的。即使所有数据都正确传输,这也会导致端点 1 接收到错误。
 
不幸的是,某些操作系统的实现方式TIME_WAIT似乎有些幼稚。只有与需要的套接字完全匹配的连接TIME_WAIT才会被阻塞以提供所提供的保护TIME_WAIT这意味着由客户端地址、客户端端口、服务器地址和服务器端口标识的连接。但是,某些操作系统施加了更严格的限制,并防止本地端口号被重用,而该端口号包含在TIME_WAIT如果有足够的套接字结束,TIME_WAIT则无法建立新的出站连接,因为没有剩余的本地端口可分配给新连接。
 
Windows不这样做,只是防止被建立了出站连接正好匹配的连接TIME_WAIT
 
入站连接受 的影响较小TIME_WAIT虽然服务器主动关闭的TIME_WAIT连接与客户端连接完全相同,但不会阻止服务器正在侦听的本地端口成为新入站连接的一部分。在 Windows 上,服务器正在侦听的众所周知的端口可以构成随后接受的连接的一部分,如果从远程地址和端口建立新连接,而该远程地址和端口当前构成TIME_WAIT该本地地址和端口的连接的一部分,则只要新序列号大于当前位于 中的连接的最终序列号,就允许连接TIME_WAIT然而,TIME_WAIT服务器上的累积可能会影响性能和资源使用,因为需要的连接TIME_WAIT最终会超时,这样做需要一些工作,并且在TIME_WAIT状态结束之前,连接仍在占用(少量)服务器上的资源。
 
鉴于TIME_WAIT由于本地端口号耗尽而影响出站连接建立,并且这些连接通常使用操作系统从临时端口范围自动分配的本地端口,您可以做的第一件事是确保您正在使用一个大小合适的临时端口范围。在 Windows 上,您可以通过调整MaxUserPort注册表设置来完成此操作有关详细信息,请参见此处请注意,默认情况下,许多 Windows 系统的临时端口范围约为 4000,这对于许多客户端服务器系统来说可能太低了。
 
虽然可以减少套接字在TIME_WAIT这方面花费的时间长度实际上并没有帮助。鉴于这TIME_WAIT只是在建立和主动关闭许多连接时出现的问题,调整 2MSL 等待时间通常只会导致在给定时间内可以建立和关闭更多连接的情况,因此您必须不断向下调整 2MSL,直到它是如此之低,以至于您可能会因为延迟段似乎是后来连接的一部分而开始遇到问题;如果您连接到相同的远程地址和端口并且非常快速地使用所有本地端口范围,或者如果您连接到相同的远程地址和端口并将本地端口绑定到固定值,这只会变得可能。
 
更改 2MSL 延迟通常是机器范围的配置更改。您可以尝试TIME_WAIT使用SO_REUSEADDRsocket 选项在套接字级别解决这允许在已存在具有相同地址和端口的现有套接字的同时创建套接字。新套接字实质上是劫持旧套接字。您可以使用SO_REUSEADDR允许在具有相同端口的套​​接字已经存在时创建套接字,TIME_WAIT但这也可能导致诸如拒绝服务攻击或数据盗窃之类的问题。在 Windows 平台上,另一个套接字选项SO_EXCLUSIVEADDRUSE可以帮助防止 的一些缺点SO_REUSEADDR,请参阅此处,但在我看来,最好避免这些尝试来解决问题TIME_WAIT,而是设计您的系统,以便TIME_WAIT 不是问题。
 
上面的 TCP 状态转换图都显示了有序的连接终止。还有另外一种方式来终止TCP连接,这就是通过中止连接并发送RST,而不是一个FIN这通常是通过将SO_LINGERsocket 选项设置为 0来实现的。这会导致挂起的数据被丢弃,连接被中止,RST而不是传输挂起的数据,并用 干净地关闭连接FIN重要的是要意识到,当连接中止时,可能在对等方之间流动的任何数据都将被丢弃,并且RST立即交付;通常作为错误表示“连接已被对等方重置”这一事实。远程对等方知道连接已中止,并且双方都没有进入TIME_WAIT
 
当然,使用中止的连接的新化身RST可能成为TIME_WAIT阻止的延迟段问题的受害者,但无论如何,此问题成为问题所需的条件极不可能,请参阅上文以获取更多详细信息。为了防止已中止的连接导致延迟段问题,两个对等点都必须转换到,TIME_WAIT因为连接关闭可能是由中间设备(例如路由器)引起的。但是,这不会发生,连接的两端都只是关闭了。
 
您可以做几件事来避免TIME_WAIT成为您的问题。其中一些假设您有能力更改客户端和服务器之间所说的协议,但通常,对于自定义服务器设计,您可以这样做。
 
对于从不建立自己的出站连接的服务器,除了维护连接的资源和性能影响外TIME_WAIT,您不必过分担心。
 
对于建立出站连接并接受入站连接的服务器,黄金法则是始终确保如果TIME_WAIT需要发生,它最终会出现在另一个对等方而不是服务器上。最好的方法是永远不要从服务器发起主动关闭,无论是什么原因。如果您的对等点超时,请使用 中止连接RST而不是关闭它。如果您的对等方发送无效数据、中止连接等。这个想法是,如果您的服务器从未启动主动关闭,则它永远不会累积TIME_WAIT套接字,因此永远不会受到它们引起的可扩展性问题的影响。虽然很容易看出在发生错误情况时如何中止连接,但是正常的连接终止呢?理想情况下,您应该在您的协议中设计一种方法,让服务器告诉客户端它应该断开连接,而不是简单地让服务器发起主动关闭。因此,如果服务器需要终止连接,则服务器会发送应用程序级别的“我们已完成”消息,客户端将其作为关闭连接的理由。如果客户端未能在合理的时间内关闭连接,则服务器将中止连接。
 
在客户端,事情稍微复杂一些,毕竟,必须有人发起主动关闭才能干净地终止 TCP 连接,如果是客户端,那么这就是TIME_WAIT最终的结果。但是,将TIME_WAIT最终结果放在客户端有几个优点。首先,如果由于某种原因,客户端由于套接字的积累而最终出现连接问题,TIME_WAIT它只是一个客户端。其他客户端不会受到影响。其次,快速打开和关闭到同一台服务器的 TCP 连接是低效的,所以它的意义超出了TIME_WAIT尝试在更长的时间内而不是在更短的时间内保持连接。不要设计一个协议,让客户端每分钟连接到服务器,并通过打开一个新连接来实现。而是使用持久连接设计,并且仅在连接失败时重新连接,如果中间路由器拒绝在没有数据流的情况下保持连接打开,那么您可以实现应用程序级别的 ping,使用 TCP 保持活动状态或仅接受路由器正在重置您的连接; 好消息是你没有在积累TIME_WAIT插座。如果您在连接上所做的工作自然是短暂的,那么请考虑某种形式的“连接池”设计,从而使连接保持打开和重用。最后,如果您绝对必须快速打开和关闭从客户端到同一服务器的连接,那么也许您可以设计一个可以使用的应用程序级关闭序列,然后使用中止关闭来执行此操作。您的客户端可以发送“我完成了”消息,然后您的服务器可以发送“再见”消息,然后客户端可以中止连接。
 
TIME_WAIT存在是有原因的,通过缩短 2MSL 周期或允许地址重用来解决它SO_REUSEADDR并不总是一个好主意。如果您能够在设计协议时TIME_WAIT考虑避免,那么您通常可以完全避免问题。
 
如果您想了解更多有关TIME_WAIT其影响和解决方法的信息那么这篇文章非常有用这篇文章也是如此
 
请注意,服务器框架附带了一些示例,这些示例清楚地显示了用于连接终止的各种选项。请参阅此处了解更多详情。
 
这篇文章的初始版本有几个地方不清楚,包含一些错误。感谢 jwoyame 在下面的评论中指出了我推理中的潜在错误,并鼓励我重新审视我的研究并重新编写这篇文章以提高清晰度和正确性
 
 
posted @ 2021-12-31 13:44  CharyGao  阅读(115)  评论(0)    收藏  举报