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

轻量服务器_扬州网站建设公司_代金券

小七 141 0

在野外处理SYN数据包

在Cloudflare,我们有很多在"野外"互联网上操作服务器的经验。但我们一直在提高我们对这种"妖术"的掌握。在这篇博客中,我们触及了互联网协议的多个黑暗角落:比如理解FIN-WAIT-2或接收缓冲区调优。CC BY 2.0 图片,作者:Isaí Moreno然而,有一个话题没有得到足够的重视——SYN洪水。我们使用Linux系统,事实证明Linux中的SYN包处理非常复杂。在这篇文章中,我们将对这个问题做一些阐述。有关两个队列的故事首先,我们必须要了解的是,每个绑定套接字在"监听"TCP状态下有两个独立的队列:SYN队列Accept(接受)队列在文献中,这些队列通常被赋予其他名称,如"reqsk_queue"、"ACK backlog"、"listen backlog",甚至"TCP backlog",但是为了避免混淆,我将坚持使用上面的名称。SYN队列SYN队列存储入站SYN数据包[1](具体是:struct inet_request_sock)。它负责发送SYN + ACK数据包,并在超时时重试。在Linux上,重试次数配置为:$ sysctl net.ipv4.tcp_synack_retries net.ipv4.tcp_synack_retries = 5这篇文档描述了这一toggle:tcp_synack_retries - INTEGER Number of times SYNACKs for a passive TCP connection attempt will be retransmitted. Should not be higher than 255. Default value is 5, which corresponds to 31 seconds till the last retransmission with the current initial RTO of 1second. With this the final timeout for a passive TCP connection will happen after 63 seconds.在传输SYN+ACK之后,SYN队列需要等待来自客户端的ACK包——这是三次握手中的最后一个数据包。所有接收到的ACK包必须首先与完全建立的连接表进行匹配,然后才与相关SYN队列中的数据匹配。在SYN队列匹配中,内核从SYN队列中删除该项,愉快地创建一个完全成熟的连接(具体地说是:struct inet_sock),并将其添加到Accept队列中。Accept队列Accept队列包含完全建立的连接:准备由应用程序获取。当进程调用accept()时,套接字被从队列中删除并传递给应用程序。这是Linux上SYN数据包处理的一个相当简化的视图。使用TCP_DEFER_ACCEPT[2] 和TCP_FASTOPEN之类的套接字toggle,事情的运作会稍有不同。队列大小限制Accept和SYN队列的最大允许长度来自应用程序传递给listen(2)系统调用的backlog参数。例如,这里将Accept和SYN队列大小设置为1024:listen(sfd, 1024)注意:在4.3之前的内核中,SYN队列长度的计数方式有所不同。该SYN队列上限以前是由net.ipv4.tcp_max_syn_backlog toggle配置的,但现在不再是这种情况了。如今,net.core.somaxconn为两个队列大小设置上限。在我们的服务器上,我们将其设置为16k:$ sysctl net.core.somaxconn net.core.somaxconn = 16384完美的backlog值了解了这些之后,我们可能会问这样一个问题:什么是理想的backlog参数值?答案是:视情况而定。对于大多数普通TCP服务器而言,这并不重要。例如,在1.11版之前,众所周知Golang并不支持自定义backlog值。尽管增加这个值是有正当理由的:当传入连接的速率非常大时,即使使用高性能应用程序,入站SYN队列也可能需要更多的插槽。backlog值控制SYN队列大小。这实际上可以被理解为"飞行中的ACK包"。到客户端的平均往返时间越大,使用的插槽就越多。在许多客户端远离服务器(几百毫秒之外)的情况下,增加backlog值是有意义的。TCP_DEFER_ACCEPT选项会使套接字保持在SYN-RECV状态的时间更长,并导致队列进一步受限。过度调整backlog同样也是不好的:SYN队列中的每个插槽都占用一些内存。在SYN洪水时,浪费资源来存储攻击数据包是没有意义的。SYN队列中的每个struct inet_request_sock条目都在4.14内核上占用256个字节的内存。要查看Linux上的SYN队列,我们​​可以使用ss命令并查找SYN-RECV套接字。例如,在Cloudflare的一台服务器上,我们可以看到tcp / 80的SYN队列使用了119个插槽,而tcp / 443则使用了78个插槽。$ ss -n state syn-recv sport = :80 | wc -l 119 $ ss -n state syn-recv sport = :443 | wc -l 78类似数据的显示也可以借助我们的overenginered SystemTap脚本:resq.stp。缓慢应用如果应用程序不能足够快地跟上调用accept()的速度,会发生什么?这时魔法发生了!当接受队列已满(大小为backlog+1)时:SYN队列的入站SYN数据包将被丢弃。SYN队列的入站ACK数据包将被丢弃。TcpExtListenOverflows      / LINUX_MIB_LISTENOVERFLOWS计数增加。TcpExtListenDrops      / LINUX_MIB_LISTENDROPS计数增加。丢弃入站数据包有一个强有力的理由:这是一个回推机制。另一方迟早会重新发送SYN或ACK包,这是希望,慢速的应用程序将得以恢复。对于几乎所有的服务器来说,这都是一种可取的行为。为了完整起见:我们可以使用全局net.ipv4.tcp_abort_on_overflow toggle进行调整,但最好还是不用它。如果您的服务器需要处理大量的入站连接,并且难以处理accept()吞吐量,请考虑阅读我们的Nginx调整/ Epoll工作分发文章以及显示有用的SystemTap脚本的后续文章。您可以通过查看nstat计数器来跟踪Accept队列的溢出状态:$ nstat -az TcpExtListenDrops TcpExtListenDrops 49199 0.0这是一个全局计数器。这并不理想——有时我们看到它在增长,而所有的应用程序看起来都很健康!第一步始终应该使用ss命令打印Accept队列大小:$ ss -plnt sport = :6443|cat State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 1024 *:6443 *:*该列中的Recv-Q显示Accept队列中的套接字数,并Send-Q显示backlog参数。在这种情况下,我们没有看到待处理的套接字accept(),但ListenDrops计数器仍在增加。结果我们的应用程序在一小段时间内卡在了。这足以让Accept队列在很短的时间内溢出。过了一会儿,它又恢复了。这种情况很难用ss调试,所以我们编写了一个acceptq.stp SystemTap脚本来帮助我们。它挂载到内核并打印要丢弃的SYN数据包:$ sudo stap -v acceptq.stp time (us) acceptq qmax local addr remote_addr 1495634198449075 1025 1024 0.0.0.0:6443 10.0.1.92:28585 1495634198449253 1025 1024 0.0.0.0:6443 10.0.1.92:50500 1495634198450062 1025 1024 0.0.0.0:6443 10.0.1.92:65434 ...在这里,您可以精确地看到哪些SYN数据包受到了ListenDrops的影响。使用此脚本,我们很容易就可以了解哪个应用程序断开了连接。CC BY 2.0 图片来自internets_dairySYN洪水如果Accept队列有可能会溢出,那么SYN队列必然也存在溢出的可能。在这种情况下会发生什么?这就是SYN洪水攻击的全部目的。过去,用伪造的SYN数据包充满SYN队列是一个真正的问题。在1996年之前,只要填满SYN队列,就可以用很少的带宽成功地拒绝几乎所有TCP服务器的服务。解决之道是SYN Cookies。SYN Cookies是一种允许无状态生成SYN + ACK的结构,实际上不需要保存入站SYN并浪费系统内存。SYN Cookies不会破坏合法流量。当另一方真实存在时,它将以有效的ACK数据包进行响应,其中包括反射的序列号,该序列号可以通过密码验证。默认情况下,SYN Cookie仅在需要时启用——用于SYN队列已满的套接字。Linux更新了SYN Cookie上的几个计数器。发送SYN Cookie时:TcpExtTCPReqQFullDoCookies      / LINUX_MIB_TCPREQQFULLDOCOOKIES递增。TcpExtSyncookiesSent      / LINUX_MIB_SYNCOOKIESSEN递增。Linux以前会递增TcpExtListenDrops,但从内核4.7开始不再如此。当一个入站ACK进入SYN队列时,SYN Cookie被占用:密码验证成功,则TcpExtSyncookiesRecv      / LINUX_MIB_SYNCOOKIESRECV递增。密码验证失败,则TcpExtSyncookiesFailed      / LINUX_MIB_SYNCOOKIESFAILED递增。sysctl net.ipv4.tcp_syncookies可以禁用SYN Cookies或强制启用它们。默认就好,无需更改。SYN Cookies和TCP时间戳SYN Cookies这一"魔法"是可行的,但它也不是没有缺点的。主要的问题是,可以保存在SYN Cookie中的数据非常少。具体来说,在ACK中只返回序列号的32位。这些位元的用法如下:+----------+--------+-------------------+ | 6 bits | 2 bits | 24 bits | | t mod 32 | MSS | hash(ip, port, t) | +----------+--------+-------------------+由于MSS设置仅被截断为4个不同的值,因此Linux不知道对方的任何可选TCP参数。有关时间戳,ECN,选择性ACK或窗口缩放的信息会丢失,并可能导致TCP会话性能下降。幸运的是,Linux可以解决。如果启用了TCP时间戳,则内核可以在"时间戳"字段中重新使用另一个32位插槽。它包含:+-----------+-------+-------+--------+ | 26 bits | 1 bit | 1 bit | 4 bits | | Timestamp | ECN | SACK | WScale | +-----------+-------+-------+--------+默认情况下,应启用TCP时间戳,从而验证查看sysctl:$ sysctl net.ipv4.tcp_timestamps net.ipv4.tcp_timestamps = 1历史上有很多关于TCP时间戳有用性的讨论。在过去,时间戳会泄露服务器运行时间(这是否重要又是另一个讨论了)。这在8个月前就被修复了。TCP时间戳占用大量的带宽——每个数据包12字节。它们可以为数据包校验增添额外的随机性,这可以帮助解决某些损坏的硬件。如上所述,如果启用了SYN Cookies,则TCP时间戳可以提高TCP连接的性能。目前在Cloudflare,我们禁用了TCP时间戳。最后,使用SYN Cookie时,一些很酷的特性将不起作用,比如TCP_SAVED_SYN、TCP_DEFER_ACCEPT或TCP_FAST_OPEN。Cloudflare规模的SYN洪水SYN Cookie是一项伟大的发明,它解决了SYN小洪水的问题。但在Cloudflare,我们尽可能避免使用它们。虽然每秒发送几千个可加密验证的SYN+ACK包是可行的,但我们看到的是每秒超过2亿个包的攻击。在这种规模下,我们的SYN+ACK