Linux 中异常缓慢的 TCP 连接

Unusually slow TCP-connection in Linux

我编写了基于通过某些专用网络交互的 Berkeley 套接字的用户模式客户端-服务器 应用程序。
情况肯定很奇怪。在某些模糊的情况下,连接有时会变得很慢。在我的例子中,正常的 TCP 数据交换是每段大约 10-25 KB 的有效负载,但有时它变成每段大约 200-500 字节。

经过一些故障排除后,我意识到此问题无法在其他网络服务中重现,因此看起来我的服务是罪魁祸首。但我无法弄清楚,出了什么问题。它在 3.10 Linux 内核上运行良好,但在 4.4 上有这种奇怪的行为。会不会是一些内部内核更改导致了这个问题?

我试过 Linux sysctl 设置:

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

但这并没有帮助。

似乎问题出现在监听套接字端。在 tcpdump 中,握手时 TCP Window 大小正常。但是在第一个传入数据包 window 之后,大小减小了(在听众方面)。

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 内核设置或检查一些用户模式设置(f.e。套接字选项或其他)?

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.

UPD2: 我在 dmesg 中看到了一些关于 SYN-cookie 的消息,例如:

possible SYN flooding on port 12345. Sending cookies.

但它们与慢速传输的时间关系不大。

我不确定这是否正是您的情况,但它看起来很相似。好像是 known problem

原因

许多情况会导致这样的 Linux 内核行为:

  • SYN-cookies 上下文 中内核连接处理的特殊性,连接具有零 Window 比例(或者如果 WS 以其他方式修改)。
  • 零 Window 规模 你被 setsockopt()SO_RCVBUF 激怒了(见 tcp_select_initial_window()
  • 非常backlog

说明

关于"slow"传输:
Windows Scaling option 由两个主机在 [SYN - SYN+ACK] 阶段计算得出。粗略地说,主机 A 说 "imply my TCP window size on N during future exchange" (SYN) 然后主机 B 说 "imply my TCP window size on M during future exchange" (SYN+ACK) - 这里 N 和 M 可能是相同的。因此,在正常情况下,这些系数会被存储并最终在数据交换时使用。
但是 TCP SYN-cookies 技术意味着遗忘 [SYN - SYN+ACK] 连接阶段(一些声明的选项包括 WS 将在 SYN+ACK 之后丢失)。在那种情况下Linux内核重新计算WS当ACK到达(如果ACK已经到达,那么创建一个常规连接是需要)。但是第二次重新计算可能会有点不同,因为 setsockopt() 不会影响它(出于某些 objective 原因)。在这里,您遇到的情况是,当您的服务器使用 SYN+ACK 发送零 Window 缩放选项,然后忘记它,然后重新生成连接(当 ACK 到达时),因为它是一些默认的 Window 缩放(例如 7)并使用 little window 暗示客户端会将其乘以 128。但客户端不会忘记 WS 为 0 并将 little window 大小视为真实大小 - 因此它发送了一小部分数据 - 在此,您的 "slow" 连接登场了。

关于 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.

因此,如果您的 LAN 中没有 SYN-flood 攻击 - 您的服务器配置严重错误。只有在出现 SYN 泛洪攻击时,SYN-cookie 才应该发挥作用。


解决方案

总而言之,可以通过一些活动来消除问题:

  1. 如果您的网络中存在真正的 SYN-flood - 部分 SYN-cookie 解决这个信息安全问题。通过真正的攻击,有 没有时间考虑慢速连接。这是紧急情况。
  2. 如果没有,即某些 SYN 重传引发了 SYN-cookie:
    • thoughtfully increase backlog消除这种情况;
    • 不要在侦听套接字 上对 SO_RCVBUF 执行 setsockopt()。这没有多大意义。在不执行 setsockopt() 的情况下,您可以降低上述场景中内核进行不同 WS 计算的概率。顺便说一句,如果需要,您可以在接受的套接字上设置 SO_RCVBUF

复制

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

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

然后从客户端发起连接 - 至少在 4.4 内核上,连接将在所谓的 "SYN-cookies context" 中打开。您还可以在 3.10 内核上检查它,将 -c3 增加到 X 直至成功复制。