在Linux中TCP连接异常缓慢

8
我编写了基于伯克利套接字的用户模式客户端-服务器应用程序,这些应用程序在某些私有网络上进行交互。
情况肯定很奇怪。在某些模糊的情况下,连接偶尔会变得非常缓慢。在我的情况下,正常的TCP数据交换是每个段约10-25 Kbytes有效负载,但有时它会变成每个段约200-500字节。

经过一些故障排除,我意识到其他网络服务无法重现此问题,因此看起来像是我的服务有问题。但我想不出哪里出错了。它在3.10 Linux内核上运行良好,但在4.4上表现出奇怪的行为。这可能是一些内部内核更改导致的问题吗?

我尝试使用Linux sysctl设置:

net.ipv4.tcp_congestion_control
net.ipv4.tcp_sack
net.ipv4.route.flush

但这并没有帮助。
似乎问题出现在监听套接字的一侧。在 tcpdump 中,TCP 窗口大小在握手时是正常的。但是在第一个传入数据包之后,窗口大小会减小(由监听方)。
UPD 这里是我的服务器端代码片段:
 serv_fd = socket(AF_INET, SOCK_STREAM, 0); 
 if (serv_fd == -1) {
      perror("socket");
      return;
 }   

 server.sin_family = AF_INET;
 server.sin_port = htons(LISTEN_PORT);
 server.sin_addr.s_addr = htonl(INADDR_ANY);

 #ifdef SET_BUF
 if (setsockopt(serv_fd, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(int)) == -1) {
      perror ("setsockopt");
      return;
 }   
 if (setsockopt(serv_fd, SOL_SOCKET, SO_SNDBUF, &buflen, sizeof(int)) == -1) {
      perror ("setsockopt");
      return;
 }   
 #endif // SET_BUF

 if (bind(serv_fd, (struct sockaddr *) &server, sizeof(server)) == -1) {
      perror("bind");
      return;
 }   

 if (listen(serv_fd, 3)) {
      perror("listen");
      return;
 }   

 printf("Server is listening on %u\n", LISTEN_PORT);

有人能为我解决问题吗?我将非常感激!
这可能与最近的Linux内核修改有关吗?我需要调整一些Linux内核设置或检查一些用户模式设置(例如套接字选项或其他内容)吗?

P.S. 问题是不稳定的。

更新:

tcpdump的输出:

IP 10.0.0.34.31334 > 10.0.0.99.12345: Flags [S], seq 426261790, win 43690, options [mss 65495,sackOK,TS val 799180610 ecr 0,nop,wscale 7], length 0
IP 10.0.0.99.12345 > 10.0.0.34.31334: Flags [S.], seq 803872704, ack 426261791, win 65483, options [mss 65495,sackOK,TS val 799180567 ecr 799180610,nop,wscale 0], length 0
IP 10.0.0.34.31334 > 10.0.0.99.12345: Flags [.], ack 1, win 342, options [nop,nop,TS val 799180610 ecr 799180567], length 0
IP 10.0.0.34.31334 > 10.0.0.99.12345: Flags [P.], seq 1:1301, ack 1, win 342, options [nop,nop,TS val 799180610 ecr 799180567], length 1300
IP 10.0.0.34.31334 > 10.0.0.99.12345: Flags [P.], seq 1301:1804, ack 1, win 342, options [nop,nop,TS val 799181412 ecr 799180610], length 503
IP 10.0.0.99.12345 > 10.0.0.34.31334: Flags [.], ack 1804, win 512, options [nop,nop,TS val 799181412 ecr 799181412], length 0

10.0.0.34.31334是客户端,10.0.0.99.12345是服务器。请注意最后一行中意外的win 512

更新2: 我在dmesg中看到了几条关于SYN-cookies的消息:

possible SYN flooding on port 12345. Sending cookies.

但它们与慢传输并不那么相关。


1
非常奇怪的情况。如果“问题不稳定”,你怎么能确定问题没有出现在3.10内核上呢?看一下dmesg输出。在传输缓慢的时候有什么异常吗? - red0ct
2
请注意,Cloudflare博客文章“SYN packet handling in the wild”指出:“在4.3之前的内核中,SYN队列长度的计算方式不同。”我没有仔细看过,但我认为您的3个积压请求在4.3之前被舍入为16。这也表明,将积压请求增加至至少16可能会使性能类似于您在3.10中看到的性能。博客中提供的链接更改在此处:https://github.com/torvalds/linux/commit/ef547f2ac16bd9d77a780a0e7c70857e69e8f23f#diff-56ecfd3cd70d57cde321f395f0d8d743L43。 - JimD.
2
你还应该检查一下在你的3.10部署中是否启用了SYN cookies。如果没有启用,SYN 将会被丢弃,客户端将会重新传输。这里有其他人观察到了类似的窗口大小和 SYN cookies 问题,当启用时间戳时。如果你看不懂中文,在 Chrome 中加载,右键点击并选择翻译,这样可以做一个不错的工作。根据 Cloudflare 博客的说法,他们已经禁用了时间戳(wscale 存储在此处),因此不可能看到这个问题。 - JimD.
2
@z0lupka,请看一下这个更改,它消除了nr_table_entries,该值的最小值为8+1,向上舍入为2的幂 = 16。我实际上没有尝试跟踪listen()中的backlog值。要真正证明基于backlog值为3的SYN队列长度为16需要进行更多的调查。 - JimD.
2
@z0lupka 有人已经在这里完成了跟进工作(https://blog.dubbelboer.com/2012/04/09/syn-cookies.html)。 - JimD.
显示剩余12条评论
1个回答

9

我不确定这是否完全符合您的情况,但看起来很相似。似乎是一个已知问题

原因

一些情况可能导致Linux内核出现这种行为:

  • SYN-cookies上下文中处理具有零窗口比例的连接的内核连接处理特定性(或者如果WS以某种其他方式修改)。
  • 通过setsockopt()使用SO_RCVBUF引发你所引发的零窗口比例(参见tcp_select_initial_window())。
  • 极其小的backlog

解释

关于“慢”传输:
窗口缩放选项在[SYN-SYN+ACK]阶段由两个主机计算。粗略地说,主机A说“在未来的交流中将我的TCP窗口大小暗示给N”(SYN),然后主机B说“在未来的交流中将我的TCP窗口大小暗示给M”(SYN+ACK)-这里的N和M可能是相同的。因此,在正常情况下,这些系数被存储并最终在数据交换过程中使用。
但是TCP SYN-cookies技术意味着忘记连接的[SYN-SYN+ACK]阶段(一些声明的选项包括WS将在SYN+ACK之后丢失)。在这种情况下,当ACK到达时Linux内核重新计算WS值(如果需要创建常规连接,则需要ACK到达)。但第二次重新计算可能会有所不同,因为setsockopt()不会受到影响(由于某些客观原因)。在这种情况下,您的服务器会发送带有SYN+ACK的零窗口比例选项,然后忘记它,然后像以前一样重新生成连接(当ACK到达时),并使用一些默认窗口比例(例如7)并使用较小的窗口,暗示客户端将其乘以128。但客户端不会忘记WS为0,并将小型窗口大小视为真实大小-因此发送少量数据-因此您的“慢”连接就出现了。

关于SYN-flood:
当您的后备队列很小时,简单的3个SYN重传就会引发SYN Cookie(即填充您的后备队列)。 顺便问一下,在tcpdump中是否看到了重传?
来自ip-sysctl.txt

Note, that syncookies is fallback facility.
It MUST NOT be used to help highly loaded servers to stand
against legal connection rate. If you see SYN flood warnings
in your logs, but investigation shows that they occur
because of overload with legal connections, you should tune
another parameters until this warning disappear.
See: tcp_max_syn_backlog, tcp_synack_retries, tcp_abort_on_overflow.

syncookies seriously violate TCP protocol, do not allow
to use TCP extensions, can result in serious degradation
of some services (f.e. SMTP relaying), visible not by you,
but your clients and relays, contacting you. While you see
SYN flood warnings in logs not being really flooded, your server
is seriously misconfigured.

如果你的局域网中没有SYN洪水攻击,则说明你的服务器配置严重不当。SYN cookies只有在存在SYN-flood攻击时才能发挥作用。


解决方案

综上所述,有一些措施可以消除这个问题:

  1. 如果你的网络中确实存在SYN洪水攻击 - SYN cookies部分解决了这个信息安全问题。在真正的攻击中,没有时间考虑慢速连接。这是紧急情况。
  2. 如果不存在SYN洪水攻击,即某些SYN重传引起了SYN-cookies:
    • 考虑增加backlog以消除这种情况;
    • 不要对监听套接字进行setsockopt()操作,使用SO_RCVBUF参数。这没有多大意义。不使用setsockopt()可以减少内核在上述情况下进行不同WS计算的概率。顺便说一句,如果需要,可以在已接受的套接字上设置SO_RCVBUF。

复现

我使用简单的客户端和服务器,在近似条件下使用hping3重现了你的问题。因此,你可以利用客户端来填充服务器的backlog队列:

hping3 -c 3 -S -p 12345 --fast 10.0.0.99

然后从客户端发起连接,连接将在所谓的"SYN-cookies context" 中打开,至少在4.4内核上会这样。您还可以通过将-c3增加到X来检查它,在3.10内核上进行尝试以获得成功的复制。


好的,但是对于3.10内核呢?这是我的生产代码,所以我必须意识到可能存在的不良行为。 - z0lupka
是的,在我的局域网中没有SYN洪水攻击,但有几个SYN重传。 - z0lupka
1
@z0lupka,你可以通过hping3工具模拟这种SYN重传,并检查3.10内核的行为。类似于hping3 -d 120 -S -p 12345 10.0.0.99 --fast,然后立即从客户端发起连接。 - red0ct
非常感谢!增加了待办事项的帮助。 - z0lupka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接