Linux 无法解释 ACK,不断重新发送 SYN+ACK

Linux 无法解释 ACK,不断重新发送 SYN+ACK

以下是发生问题的 wireshark 转储,IP 地址被替换为“客户端”和“服务器”:

4414.229553  client -> server TCP 62464 > http [SYN] Seq=0 Win=65535 Len=0 MSS=1452 WS=3 TSV=116730231 TSER=0
4414.229633 server -> client  TCP http > 62464 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=2406364374 TSER=116730231 WS=6
4414.263330  client -> server TCP 62464 > http [ACK] Seq=1 Ack=1 Win=524280 Len=0 TSV=116730231 TSER=2406364374
4418.812859 server -> client  TCP http > 62464 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=2406365520 TSER=116730231 WS=6
4418.892176  client -> server TCP [TCP Dup ACK 778#1] 62464 > http [ACK] Seq=1 Ack=1 Win=524280 Len=0 TSV=116730278 TSER=2406365520
4424.812864 server -> client  TCP http > 62464 [SYN, ACK] Seq=0 Ack=1 Win=5792 Len=0 MSS=1460 TSV=2406367020 TSER=116730278 WS=6
4424.891240  client -> server TCP [TCP Dup ACK 778#2] 62464 > http [ACK] Seq=1 Ack=1 Win=524280 Len=0 TSV=116730337 TSER=2406367020

因此,正常的 SYN、SYN+ACK、ACK 序列似乎出现了,只是服务器似乎没有解释 ACK。相反,它不断重新发送 SYN+ACK,而客户端则尽职尽责地用上一个 ACK​​ 的副本进行响应。我不明白这怎么可能发生。

我注意到这个问题是因为 iptables 连接跟踪认为这些连接已经建立,并将它们保存在内存中长达 120 小时。我有一些防火墙规则来阻止大量并发连接,人们已经达到了这些连接的极限,但实际上并没有那么多连接处于活动状态。该netstat命令不会显示这些幻影连接。

其他信息:

该服务器是带有库存内核的标准 debian lenny 系统:

Linux tb 2.6.26-2-686 #1 SMP Wed Aug 19 06:06:52 UTC 2009 i686 GNU/Linux

跑步:

Apache/2.2.9 (Debian) mod_ssl/2.2.9 OpenSSL/0.9.8g

我没有客户端上的所有信息(我无法在本地重现),但它是运行 Chrome 浏览器的 Mac。

我没有任何防火墙规则来干扰 ACK 数据包。基本上我只过滤 SYN 数据包,所有其他 TCP 数据包都允许通过。因此,除了计算并发连接数和绘制 TCP-Established 数据包与其他数据包类型的比较图之外,我实际上并没有使用连接跟踪来进行防火墙。

编辑:我的与 TCP 端口 80 相关的 iptables 规则:

iptables -P INPUT ACCEPT
iptables -A INPUT -p tcp --syn --dport 80 -m connlimit --connlimit-above 50 -j LOGDROP-CONN
iptables -A INPUT -p tcp --syn -m multiport --dports 80,443 -j ACCEPT
iptables -A INPUT -p tcp --syn -j REJECT --reject-with tcp-reset
iptables -A LOGDROP-CONN -m limit --limit 1/minute --limit-burst 1 -j LOG --log-prefix "ConConn "
iptables -A LOGDROP-CONN -j DROP

编辑2:另一次转储,这次使用 tcpdump -vv:

16:05:52.999525 IP (tos 0x0, ttl 55, id 46466, offset 0, flags [DF], proto TCP (6), length 64) client.50538 > server.www: S, cksum 0x4429 (correct), 38417001:38417001(0) win 65535 <mss 1452,nop,wscale 3,nop,nop,timestamp 117224762 0,sackOK,eol>
16:05:52.999580 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) server.www > client.50538: S, cksum 0xa2ab (correct), 3062713115:3062713115(0) ack 38417002 win 5792 <mss 1460,sackOK,timestamp 2418739698 117224762,nop,wscale 6>
16:05:53.321788 IP (tos 0x0, ttl 55, id 24299, offset 0, flags [DF], proto TCP (6), length 52) client.50538 > server.www: ., cksum 0xe813 (correct), 1:1(0) ack 1 win 65535 <nop,nop,timestamp 117224765 2418739698>
16:05:56.252697 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) server.www > client.50538: S, cksum 0x9f7a (correct), 3062713115:3062713115(0) ack 38417002 win 5792 <mss 1460,sackOK,timestamp 2418740512 117224765,nop,wscale 6>
16:05:56.277250 IP (tos 0x0, ttl 55, id 15533, offset 0, flags [DF], proto TCP (6), length 52) client.50538 > server.www: ., cksum 0xe4c4 (correct), 1:1(0) ack 1 win 65535 <nop,nop,timestamp 117224798 2418740512>

答案1

我会强行破解它。首先,我会尝试在 iptables 停止的情况下它是否能正常工作。如果能,那么问题就出在 iptables 上。

然后我会逐一添加规则,并观察哪一条会导致连接失败。然后我会尝试这条规则,直到它按照我的要求运行,而不会完全中断流量。

如果它在 iptables 停止后不起作用,那么它就会开始变得非常奇怪。

答案2

好吧,我没有完整的答案,但自从这个问题首次出现以来,我已经学到了很多东西。我将在这里分享我的见解。

  • 首先,问题是由 Google Chrome 向你用它打开的任何网站打开大量套接字(在我的测试中是 6 个)引起的。它这样做是为了开始并行下载网站的各种元素。对于没有很多项目的简单网站(比如我的网站),其中一些预先打开的套接字将处于空闲状态。我读到过其他现代浏览器也这样做。
  • 触发连接跟踪问题的客户端可能有一个损坏的家庭路由器或类似的东西,因为空闲套接字往往会消失,而不会发送任何 FIN 或 RST 数据包来将其拆除。
  • 重复 SYN+ACK 数据包似乎是正常的 TCP 行为,我能够在各种第三方 Web 服务器上通过运行数据包转储然后执行类似操作telnet www.website.com 80而不发送任何数据来见证这一点。不过,这仍然可能是 Linux 的特有现象,因为我测试的服务器可能都运行 Linux。这种行为也会在未加载任何 iptables 规则的情况下发生,因此它确实与内核有关。
  • 在发送几个 SYN+ACK 数据包而没有得到响应后,Linux 内核将断开其一侧的连接。iptables连接跟踪似乎不共享此逻辑,因此连接将保持此状态ESTABLISHED直到超时。默认超时时间为 5 天 (!)。我计划将其缩短为更合理的时间,例如几个小时。
  • 在收到 ACK 后重复 SYN+ACK 并非完全默认行为,因为当我打开监听端口nc -l ###并连接到该端口时,我没有看到它。因此,它可能是 Apache 在其监听套接字上设置的某个 TCP 选项。或者它可能是 Apache 独有的东西。大多数其他守护进程在连接后立即宣布自己,因此它们不是有效的测试用例。连接需要进行三次握手,然后立即进入空闲状态。

不过,感谢您上述给出的见解,它们对弄清此事很有帮助。

相关内容