网络代理丢弃 ACK 导致上传挂起

网络代理丢弃 ACK 导致上传挂起

我正在学习 Docker,在我的 Red Hat Enterprise Linux 7.4 机器(Linux 3.10.0-693)上安装 docker 1.12.6 并进行“入门”练习。

通过我公司的网络代理让它工作有点麻烦。(我不知道我们有哪种网络代理。)

我必须为 dockerd 设置 HTTP_PROXY/HTTPS_PROXY 环境变量,以便它使用代理;然后将公司的 CA 证书添加到系统的 pki 受信任存储中(因为 Web 代理会“中间人”插入 HTTPS 连接的证书)。但最后我能够从 docker.io 注册表加载 docker 镜像;下载到我的机器一切正常。

但我尝试“docker push”到docker.io失败了。即使是一个13KB的小docker镜像,上传也会挂起,最终会报告“对端重置连接”错误。

执行 tcpdump 后,基本问题似乎非常清楚。经过一些流量、设置后,我的 docker 客户端计算机连续向 Web 代理发送四个 HTTP 数据包……并且它只发送了三个 ACK​​ 数据包。四个传出的数据包中有一个从未被确认。此后,双方在几分钟内重新发送最后的 ACK,然后其中一个发送 FIN。

这是 tcpdump 输出的有趣结尾:

...

#   client sends HTTP to proxy, it sends an ACK back, everything fine.

17:17:17.132874 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [P.], seq 326:484, ack 3372, win 37648, options [nop,nop,TS val 175751308 ecr 3912953706], length 158: HTTP
17:17:17.133929 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 484, win 4863, options [nop,nop,TS val 3912953725 ecr 175751308], length 0

#   Now the proxy relays a HTTP reply from upstream in two packets, my client sends a single ACK for both.

17:17:17.138783 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [P.], seq 3372:3378, ack 484, win 4863, options [nop,nop,TS val 3912953729 ecr 175751308], length 6: HTTP
17:17:17.138801 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [P.], seq 3378:3423, ack 484, win 4863, options [nop,nop,TS val 3912953729 ecr 175751308], length 45: HTTP
17:17:17.138941 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [.], ack 3423, win 37648, options [nop,nop,TS val 175751314 ecr 3912953729], length 0

#   Now we have the problem.  My client sends four packets in a row to the proxy, with sequence numbers 1692, 3140, 3605, 3639 ...

17:17:17.139139 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [P.], seq 484:1692, ack 3423, win 37648, options [nop,nop,TS val 175751314 ecr 3912953729], length 1208: HTTP
17:17:17.139157 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [.], seq 1692:3140, ack 3423, win 37648, options [nop,nop,TS val 175751314 ecr 3912953729], length 1448: HTTP
17:17:17.139160 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [P.], seq 3140:3605, ack 3423, win 37648, options [nop,nop,TS val 175751314 ecr 3912953729], length 465: HTTP
17:17:17.139179 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [P.], seq 3605:3639, ack 3423, win 37648, options [nop,nop,TS val 175751314 ecr 3912953729], length 34: HTTP

#   ... and the proxy sends separate ACKs for just three of the four, missing 3140

17:17:17.140457 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 1692, win 6071, options [nop,nop,TS val 3912953731 ecr 175751314], length 0
17:17:17.140505 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 3605, win 7984, options [nop,nop,TS val 3912953731 ecr 175751314], length 0
17:17:17.140513 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 3639, win 8018, options [nop,nop,TS val 3912953731 ecr 175751314], length 0

#   Everything hangs for 30 seconds; client resends its last ACK, proxy retransmits its last ACK.

17:17:47.142582 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [.], ack 3423, win 37648, options [nop,nop,TS val 175781318 ecr 3912953731], length 0
17:17:47.143579 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 3639, win 8018, options [nop,nop,TS val 3912983735 ecr 175751314], length 0

#   Everything hangs for another 30 seconds; client resends its last ACK, so does proxy

17:18:17.152561 IP dockerclient.company.com.33484 > webproxy.local.webcache: Flags [.], ack 3423, win 37648, options [nop,nop,TS val 175811328 ecr 3912983735], length 0
17:18:17.153663 IP webproxy.local.webcache > dockerclient.company.com.33484: Flags [.], ack 3639, win 8018, options [nop,nop,TS val 3913013746 ecr 175751314], length 0

#  ... and so forth

我对此感到困惑(但我不是网络专家)。在上面的第二组数据包中,从时间 17:17:17.138783 开始,代理发送了两个数据包,然后我的客户端发送了一个 ACK​​ 数据包,确认了序列号中较大/最后一个的数据包,一切都很好。当代理跳过序列号 3140 的显式 ACK 但为更大四个数据包中最后两个数据包的序列号。但正如您所看到的,事情停止了,直到我的客户端每 30 秒重新发送最后一个 ACK​​,才会发生任何事情。

我将非常感激有关以下问题的所有建议:

Q1. 有人能告诉我这两台机器中哪台做错了吗?出于好奇,我很想知道 TCP/IP 标准的要求是什么,哪台机器出了问题。

Q2. 是否有配置设置可以调整,让 Linux 避免一次发送四个数据包,或者以其他方式解决问题?放慢速度,序列化传输,避免发送排队的数据包,直到代理确认前一次传输?这样就可以解决这个“丢失 ACK 数据包”问题,我认为这是我的问题的核心?

Q3. 还有其他方法可以修复这个问题并让 Docker 在公司防火墙后面工作吗?以前有人见过这样的情况吗,这是一个常见问题吗?

我无法以任何方式影响/改变防火墙,但我可以控制 Linux docker 客户端机器。

非常感谢您的建议!

答案1

这是很正常的,没有什么问题。TCP 不会确认单个数据包/段;它会确认字节范围。 参见维基百科。 在 17:17:17.138941 处,客户端 ack=3423 确认来自两个帧 17:17:17.138783 和 17:17:17.138801 的数据。类似地,在 17:17:17.140505 处,服务器 ack=3605 确认 17:17:17.139157 和 17:17:17.139160。

随后每 30 秒发送的帧必须保持活动状态,尽管这是一个异常短的间隔,因为窗口在两个方向上都打开,并且没有发送任何数据。此外,您说连接随后会用 FIN 关闭(FIN 应该在两个方向上流动,而不仅仅是一个方向);如果任一端点有未完成(未确认)的数据,则它会改用 RST。

您的客户端请求已成功且正确地传送至代理。如果代理无法将该请求发送到服务器并获取和中继响应,则这不是由于您与代理之间的链路存在问题。

答案2

我非常确定您遇到了 MTU 限制(这表明本地网络质量很差和/或存在中间人代理,但这并不奇怪)。您可以通过减少 NIC 上的最大 MTU 来限制机器发送的数据包的大小,如下所示:

ip link set mtu 1280 dev eth0

(替换eth0为 NIC 的名称)

后续操作tcpdump应显示数据包长度永远不会超过该数字(多加几个字节用于帧和协议开销应该没问题)。如果这样可以解决问题,您可以直接将最大 MTU 硬编码到机器的配置中,或者(最好)使用它iptables来限制仅与行为不当的代理建立连接的 MSS(“最大段大小”)。

相关内容