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

金山云_广州网站建设工作室_免费1年

小七 141 0

这完全违反了TCP规范

我被要求在我们的网络上调试另一个奇怪的问题。显然,通过CloudFlare的连接时不时会出现522http错误而超时。克里斯·科姆的2.0图片CloudFlare上的522错误表示边缘服务器和源服务器之间存在连接问题。最常见的原因是源服务器端-源服务器速度慢、离线或遇到高数据包丢失。问题很少发生在我们这边。在我调试的情况下,两者都不是。CloudFlare和origin之间的互联网连接非常完美。无丢包,延迟平坦。那么为什么我们看到522错误呢?这个问题的根本原因相当复杂。经过大量的调试之后,我们发现了一个重要的症状:有时,在数千次运行中,我们的测试程序无法在同一台机器上的两个守护程序之间建立连接。准确地说,一个NGINX实例正在尝试与本地主机上的内部加速服务建立TCP连接。由于超时错误而失败。一旦我们知道要找什么,我们就可以用好的老网猫复制这个。经过几十次运行,我们看到的是:$nc 127.0.0.1 5000-vnc:连接到127.0.0.1端口5000(tcp)失败:连接超时斯特拉斯的观点:套接字(PF_INET、SOCK_STREAM、IPPROTO_TCP)=3connect(3,{sa_family=AF_INET,sin_port=htons(5000),sin_addr=INET_addr("127.0.0.1")},16)=-110 ETIMEDOUTnetcat调用connect()来建立到本地主机的连接。这需要很长时间,最终失败,并出现ETIMEDOUT错误。Tcpdump确认connect()确实通过环回发送了SYN包,但从未收到任何SYN+ack:$sudo tcpdump-ni-lo端口5000-ttttt-S00:00:02.405887 IP 127.0.0.12.59220>127.0.0.1.5000:Flags[S],序列号220451580,win 43690,选项[mss 65495,sackOK,TS val 15971607 ecr 0,nop,wscale 7],长度000:00:03.406625 IP 127.0.0.12.59220>127.0.0.1.5000:Flags[S],序列号220451580,win 43690,选项[mss 65495,sackOK,TS val 15971857 ecr 0,nop,wscale 7],长度0... 还有5个。。。等等。刚才发生什么事了?好吧,我们调用connect()到localhost,它超时了。SYN包通过环回发送到本地主机,但从未应答。环回拥塞CC BY 2.0图像akj1706第一个想法是关于互联网的稳定性。也许SYN包丢失了?一个鲜为人知的事实是,环回接口上不可能有任何包丢失或拥塞。回送的工作原理很神奇:当应用程序向其发送数据包时,它会立即(仍在send syscall处理中)被传递到适当的目标。环回上没有缓冲。通过环回调用send over loopback触发iptables、网络堆栈传递机制,并将数据包传递到目标应用程序的适当队列。假设目标应用程序在其缓冲区中有一些空间,则不可能通过环回丢失数据包。可能是监听应用程序行为不当?在正常情况下,到本地主机的连接不应超时。但有一种情况可能会发生,即侦听应用程序调用accept()的速度不够快。当发生这种情况时,默认行为是丢弃新的SYN包。如果侦听套接字有一个完整的接受队列,那么新的SYN包将被丢弃。其目的是引起回推,以减慢传入连接的速率。对等端最终应该重新发送SYN包,希望到那时accept队列将被释放。此行为由tcp_abort_on_overflow sysctl控制。但是这个接受队列溢出在我们的例子中没有发生。我们的侦听应用程序有一个空的接受队列。我们用ss命令检查过了:$ss-n4lt"运动=:5000"本地状态接收-Q发送-Q地址:端口对等地址:港口听0 128*:5000*:*Send-Q列显示给listen()syscall-128的backlog/accept队列大小。Recv-Q报告接受队列中未完成连接的数量-0。问题简而言之:我们正在建立到本地主机的连接。它们大多数都可以正常工作,但有时connect()系统调用会超时。SYN包通过环回发送。因为它是环回,它们被传送到监听套接字。侦听套接字接受队列为空,但我们没有看到SYN+ack。进一步的调查发现了一些奇怪的东西。我们注意到数以百计的关闭等待插座:$ss-n4t |头本地状态接收-Q发送-Q地址:端口对等地址:港口关闭等待1 0 127.0.0.1:5000 127.0.0.1:36599关闭等待1 0 127.0.0.1:5000 127.0.0.1:36467关闭等待1 0 127.0.0.1:5000 127.0.0.1:36154关闭等待1 0 127.0.0.1:5000 127.0.0.1:36412关闭等待1 0 127.0.0.1:5000 127.0.0.1:36536...到底什么叫CLOSE-hu WAIT?DaveBleasdale的CC BY 2.0图像引用红帽文件:CLOSE_WAIT-表示服务器已从客户端接收到第一个FIN信号,连接正在关闭中。这意味着套接字正在等待应用程序执行close()。在应用程序关闭套接字之前,它可以无限期地处于CLOSE_WAIT状态。错误的情况类似于文件描述符泄漏:服务器没有对套接字执行close(),导致close\u WAIT套接字堆积。这是有道理的。实际上,我们能够确认监听应用程序泄漏套接字。万岁,进展顺利!泄漏的插座并不能解释一切。通常一个Linux进程最多可以打开1024个文件描述符。如果我们的应用程序确实用完了文件描述符,accept syscall将返回EMFILE错误。如果应用程序进一步错误地处理了这个错误情况,这可能导致丢失传入的SYN包。失败的accept调用不会将套接字从accept queue中出列,从而导致accept队列增长。接受队列将不会被清空,最终将溢出。溢出的接受队列可能导致SYN数据包丢失和连接尝试失败。但这里不是这样。我们的应用程序还没有用完文件描述符。这可以通过计算/proc//fd目录中的文件描述符来验证:$ls/proc/`pidof listener`/fd | wc-l517517个文件描述符远远超出1024个文件描述符的限制。另外,我们在前面向ss展示了accept队列是空的。那为什么我们的连接超时了?到底发生了什么问题的根本原因肯定是我们的应用程序泄漏套接字。但症状,连接超时,仍然无法解释。是时候揭开怀疑的帷幕了。下面是发生的事情。侦听应用程序泄漏套接字,它们将永远处于CLOSE\u WAIT TCP状态。这些套接字看起来像(127.0.0.1:5000,127.0.0.1:some port)。连接另一端的客户机套接字是(127.0.0.1:some port,127.0.0.1:5000),并已正确关闭和清理。当客户端应用程序退出时,(127.0.0.1:some port,127.0.0.1:5000)套接字进入FIN_WAIT_1状态,然后快速转换到FIN_WAIT_2。如果客户机接收到FIN包,FIN_WAIT_2状态应该转移到TIME_WAIT,但这从来没有发生过。FIN_WAIT_2最终超时。在Linux上,这是60秒,由网络ipv4.tcp\u fin_超时sysctl。这就是问题的开始。一些端口0.0.127已经被重新使用(0.1.127),而一些0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0。当这种情况发生时,结果就是一团糟。套接字的一部分将无法从SYN\u SENT状态前进,而另一部分则卡在CLOSE_WAIT中。SYN_nusent套接字最终将放弃使用ETIMEDOUT失败的情况。如何繁殖这一切都是从一个监听应用程序开始的,该应用程序泄漏套接字并忘记调用close()。这种错误在复杂的应用程序中确实会发生。这里提供了一个示例bug代码。当您运行它时,最初不会发生任何事情。ss将显示一个常用的监听插座:$开始建造侦听器.开始&&/听众&$ss-n4tpl'运动=:5000'本地状态接收-Q发送-Q地址:端口对等地址:港口监听0 128*:5000*:*用户:(("listener",81425,3))然后我们有一个客户端应用程序。客户机的行为正常-它建立了一个连接,过了一段时间它就会关闭它。我们可以用nc来证明这一点:$nc-4本地主机5000&$ss-n4tp'(dport=:5000或sport=:5000)'本地状态接收-Q发送-Q地址:端口对等地址:港口ESTAB 0 0 127.0.0.1:5000 127.0.0.1:36613用户:(("侦听器",81425,5))ESTAB 0 0 127.0.0.1:36613 127.0.0.1:5000用户:(("nc",81456,3))如上所示,ss显示了两个TCP套接字,表示TCP连接的两端。客户端是(127.0.0.1:36613,127.0.0.1:5000),服务器端(127.0.0.1:5000,127.0.0.1:36613)。下一步是优雅地关闭客户端连接:$kill`pidof nc`现在,连接进入TCP清理阶段:FIN_WAIT_2用于客户端连接,CLOSE_WAIT for the server one(如果您想了解更多有关这些TCP状态的信息,建议阅读以下内容):$ss-n4tp本地状态接收-Q发送-Q地址:端口对等地址:港口CLOSE-WAIT 1 0 127.0.0.1:5000 127.0.0.1:36613用户:(("listener",81425,5))FIN-WAIT-2 0 0 127.0.0.1:36613 127.0.0.1:5000一段时间后,FIN_WAIT_2将过期:$ss-n4tp本地状态接收-Q发送-Q地址:端口对等地址:港口CLOSE-WAIT 1 0 127.0.0.1:5000 127.0.0.1:36613用户:(("listener",81425,5))但关闭等待插座留在里面!由于侦听器程序中有一个泄漏的文件描述符,所以内核不允许将其移到FIN_WAIT状态。它陷入了无限期的等待。如果只重用同一个端口对,这种杂散的CLOSE_WAIT就不会是问题。不幸的是,它发生并导致了问题。为了看到这一点,我们需要启动数百个nc实例,并希望内核将冲突的端口号分配给其中一个。第