云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

文件存储_浪潮英信服务器_三重好礼

小七 141 0

当TCP套接字拒绝死亡时

当我们在频谱服务器上工作时,我们注意到了一些奇怪的事情:我们认为应该关闭的TCP套接字在周围徘徊。我们意识到我们不知道什么时候TCP套接字应该超时!图像由Sergiodc2 CC by SA 3.0提供在我们的代码中,我们希望确保我们不会保留到死主机的连接。在我们早期的代码中,我们天真地认为启用TCP keepalives就足够了。。。但事实并非如此,事实证明,一个相当现代的TCP_USER_TIMEOUT socket选项同样重要。此外,它还以微妙的方式与TCP keepalives交互。很多人对此感到困惑。在这篇博客文章中,我们将尝试展示这些选项是如何工作的。我们将展示TCP套接字如何在其生命周期的各个阶段超时,以及TCP保持和用户超时如何影响这一点。为了更好地说明TCP连接的内部,我们将混合tcpdump和ss-o命令的输出。这很好地显示了传输的数据包和TCP连接的变化参数。同步发送让我们从一个最简单的例子开始——当一个人试图建立到一个丢弃入站SYN包的服务器的连接时会发生什么?这里使用的脚本可以在我们的Github上找到。$sudo./测试syn-发送.py#已丢弃所有数据包00:00.000 IP主机2>主机1:Flags[S]#初始SYN状态接收发送本地:端口对等:端口同步发送0 1主机:2台主机:1定时器:(开,940ms,0)00:01.028 IP主机2>主机1:Flags[S]#首次重试00:03.044 IP主机2>主机1:Flags[S]#第二次重试00:07.236 IP主机2>主机1:Flags[S]#第三次重试00:15.427 IP主机2>主机1:Flags[S]#第四次重试00:31.560 IP主机。2>主机。1:Flags[S]#第五次重试01:04.324 IP主机2>主机1:Flags[S]#第六次重试02:10.000连接ETIMEDOUT好吧,这很简单。在connect()系统调用之后,操作系统发送一个SYN包。由于它没有得到任何响应,操作系统将默认重试发送6次。这可以通过sysctl进行调整:$系统控制网络ipv4.tcp\u syn\u重试网络ipv4.tcp_syn_retries=6可以使用TCP\u SYNCNT setsockopt覆盖每个套接字的此设置:setsockopt(sd,IPPROTO\u TCP,TCP同步,6);重试间隔为1s、3s、7s、15s、31s、63s(间隔重试时间从2s开始,然后每次加倍)。默认情况下,整个过程需要130秒,直到内核放弃ETIMEDOUT errno。此时在连接的生存期内,因此忽略了"KEEPALIVE"设置,但TCP"USER"TIMEOUT没有被忽略。例如,将其设置为5000ms,将导致以下交互:$sudo./测试syn-发送.py5000#已丢弃所有数据包00:00.000 IP主机2>主机1:Flags[S]#初始SYN状态接收发送本地:端口对等:端口同步发送0 1主机:2台主机:1定时器:(开,996ms,0)00:01.016 IP主机2>主机1:Flags[S]#首次重试00:03.032 IP主机2>主机1:Flags[S]#第二次重试00:05.016 IP主机2>主机1:Flags[S]#这是什么?00:05.024 IP主机2>主机1:Flags[S]#这是什么?00:05.036 IP主机2>主机1:Flags[S]#这是什么?00:05.044 IP主机2>主机1:Flags[S]#这是什么?00:05.050连接ETIMEDOUT即使我们将用户超时设置为5s,我们仍然可以在网络上看到6次SYN重试。这种行为可能是一个bug(在5.2内核上测试过):我们期望只发送两次重试-在1s和3s标记处,套接字在5s标志处过期。相反,我们看到了这一点,但我们也看到了4个重新传输的SYN包与5s标记对齐-这是没有意义的。总之,我们学到了一件事——TCP_USER_TIMEOUT确实会影响connect()的行为。SYN-RECV公司SYN-RECV套接字通常对应用程序隐藏。它们就像SYN队列中的小套接字一样。我们以前写过SYN和Accept队列。有时,当启用SYN cookies时,套接字可能会完全跳过SYN-RECV状态。在SYN-RECV状态下,套接字将重试发送SYN+ACK 5次,由以下控制:$系统控制网络ipv4.tcp\u synack\u重试网络ipv4.tcp_synack_retries=5下面是它在电线上的样子:$sudo./测试syn-收款人00:00.000 IP主机2>主机1:标志#所有后续数据包都已丢弃00:00.000 IP主机。1>主机。2:标志[S.]#初始SYN+ACK状态接收发送本地:端口对等:端口同步接收0 0主机:1台主机:2定时器:(开,996ms,0)00:01.033 IP主机。1>主机。2:Flags[S.]#首次重试00:03.045 IP主机。1>主机。2:标志[S.]#第二次重试00:07.301 IP主机。1>主机。2:标志[S.]#第三次重试00:15.493 IP主机。1>主机。2:标志[S.]#第四次重试00:31.621 IP主机。1>主机。2:标志[S.]#第五次重试01:04:610 SYN-RECV消失在默认设置下,SYN+ACK在1s、3s、7s、15s、31s标记处重新传输,SYN-RECV套接字在64s标记处消失。无论是sokeepalive还是TCP\u USER_TIMEOUT都不会影响SYN-RECV套接字的生存期。最后握手确认在接收到TCP握手中的第二个数据包(SYN+ACK)之后,客户机套接字将进入已建立的状态。服务器套接字将保持在SYN-RECV中,直到它接收到最终的ACK包。丢失这个ACK并不会改变任何东西—服务器套接字只需要稍微长一点的时间才能从SYN-RECV移动到ESTAB。以下是它的外观:00:00.000 IP主机2>主机1:标志00:00.000 IP主机。1>主机。2:标志[S.]00:00.000 IP主机2>主机1:Flags[.]\initial ACK,丢弃状态接收发送本地:端口对等:端口同步接收0 0主机:1台主机:2定时器:(开,1秒,0)开始0 0主机:2台主机:1个00:01.014 IP主机。1>主机。2:标志[S.]00:01.014 IP主机2>主机1:Flags[.]\retried ACK,丢弃状态接收发送本地:端口对等:端口同步接收0 0主机:1台主机:2定时器:(开,1.012ms,1)开始0 0主机:2台主机:1个如您所见,SYN-RECV具有"打开"计时器,与前面的示例相同。我们可能会说,这最后一次确认并没有什么意义。这种思想导致了TCP_DEFER_ACCEPT特性的发展-它基本上导致第三个ACK被悄悄地丢弃。设置此标志后,套接字将保持SYN-RECV状态,直到它接收到包含实际数据的第一个数据包:$sudo./测试syn-确认页00:00.000 IP主机2>主机1:标志00:00.000 IP主机。1>主机。2:标志[S.]00:00.000 IP host.2>host.1:Flags[.]#已传递,但套接字保持为SYN-RECV状态接收发送本地:端口对等:端口同步接收0 0主机:1台主机:2定时器:(开,7.192ms,0)开始0 0主机:2台主机:1个00:08.020 IP host.2>host.1:Flags[P.],length 11#有效负载将套接字移动到ESTAB状态接收发送本地:端口对等:端口ESTAB 11 0号主机:1台主机:2个开始0 0主机:2台主机:1个即使在接收到最终的TCP握手ACK之后,服务器套接字仍然处于SYN-RECV状态。它有一个有趣的"开启"计时器,计数器停留在0次重试。在客户机发送数据包后或在TCP_DEFER_accept计时器过期后,它被转换为ESTAB并从SYN移到accept队列。基本上,使用DEFER ACCEPT,SYN-RECV mini socket丢弃数据较少的入站ACK。闲着是永远的让我们继续讨论连接到不健康(死机)对等机的完全建立的套接字。握手完成后,两边的插座都会移动到已建立的状态,如:状态接收发送本地:端口对等:端口开始0 0主机:2台主机:1个开始0 0主机:1台主机:2个默认情况下,这些套接字没有运行计时器-即使通信中断,它们也将永远保持该状态。只有当一方尝试发送数据时,TCP堆栈才会注意到问题。这就引出了一个问题——如果不打算通过连接发送任何数据,该怎么办?如何确保空闲连接正常,而不通过它发送任何数据?这就是TCP keepalives的用武之地。让我们看看它的实际操作-在本例中,我们使用了以下切换:所以,让我们启用KEEPALIVE=1。TCP_KEEPIDLE=5-空闲5秒后发送第一个keepalive探测。TCP_KEEPINTVL=3-3秒后发送后续keepalive探测。TCP_keepnt=3-三次探测失败后超时。$sudo/测试-空闲.py00:00.000 IP主机2>主机1:标志00:00.000 IP主机。1>主机。2:标志[S.]00:00.000 IP主机。2>主机。1:Flags[.]发送状态Q-V本地:端口对等:端口开始0 0主机:1台主机:2个开始0 0主机:2台主机:1计时器:(keepalive,2.992ms,0)#所有后续数据包都已丢弃00:05.083 IP主机2>主机1:Flags[.],ack 1#第一个keepalive探测00:08.155 IP主机2>主机1:Flags[.],ack 1#第二个keepalive探测00:11.231 IP主机2>主机1:Flags[.],ack 1#第三个keepalive探测00:14.299 IP主机2>主机1:标志[R.],序列1,确认1的确!我们可以清楚地看到第一个探针在5s标记处被发送,剩下的两个探针相距3s,与我们指定的完全一致。在总共发送了三个探测,再延迟3秒之后,连接会随着ETIMEDOUT而终止,最后发送RST。要使keepalives正常工作,发送缓冲区必须为空。您可以注意到keepalive计时器在"timer:(keepalive)"行中处于活动状态。带有TCP\u USER_TIMEOUT的Keepalives令人困惑我们之前提到过TCP_USER_TIMEOUT选项。它设置在内核强制关闭连接之前传输的数据保持未确认的最长时间。就其本身而言,它在空闲连接的情况下并没有多大作用。即使连接中断,套接字也将保持建立状态。但是,这个socket选项确实改变了TCP keepalives的语义。tcp(7)手册页有些混乱:此外,当与TCP keepalive(SO_keepalive)选项一起使用时,TCP_USER_TIMEOUT将覆盖keepalive,以确定何时由于keepalive失败而关闭连接。原始提交消息的细节稍显详细:tcp:添加tcp_USER_TIMEOUT socket选项要理解语义,我们需要查看linux/net/ipv4/tcp_timer中的内核代码。c:693:if((icsk->icsk_user_超时!=0&&运行时间>=毫秒到秒(icsk->icsk用户超时)&&icsk->icsk_探针_out>0)||要使用户超时生效,icsk\u probes_out不能为零。检查用户超时是d