![网关虚拟机神秘拒绝“需要分片”](https://linux22.com/image/800921/%E7%BD%91%E5%85%B3%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%A5%9E%E7%A7%98%E6%8B%92%E7%BB%9D%E2%80%9C%E9%9C%80%E8%A6%81%E5%88%86%E7%89%87%E2%80%9D.png)
我一直在排除严重的 WAN 速度问题。我已修复此问题,但为了其他人的利益:
通过 WireShark、日志记录和简化配置,我将其缩小到网关对内部网络上的服务器执行 DNAT 的某些奇怪行为。网关(CentOS 机箱)和服务器都在同一台 VMware ESXi 5 主机上运行(事实证明这很重要)。
当我尝试使用直接连接到网关 WAN 侧的测试客户端(绕过通常在此处使用的实际 Internet 连接)从 DNAT 后面的 HTTP 服务器下载文件时,发生了以下事件的顺序 - 相当一致:
通常的 TCP 连接建立(SYN、SYN ACK、ACK)正常进行;网关双向正确地重新映射服务器的 IP。
客户端使用 HTTP GET 发送单个 TCP 段,并且该段也正确地 DNAT 到目标服务器。
服务器通过网关发送一个 1460 字节的 TCP 段,其中包含 200 响应和部分文件。线路上的帧大小为 1514 字节 - 有效载荷为 1500。此段应该穿过网关,但实际上没有。
服务器通过网关发送第二个 1460 字节 TCP 段,继续传输文件。同样,链接有效负载为 1500 字节。此段也不会跨越网关,因此永远不会被记录。
网关向服务器发回一个 ICMP 类型 3 代码 4(目标不可达 - 需要分片)数据包,引用事件 3 中发送的数据包。ICMP 数据包指示下一跳 MTU 为 1500。 这似乎是无稽之谈,因为网络是 1500 字节干净的,并且 3 和 4 中的链路有效载荷已在规定的 1500 字节限制内。服务器理所当然地忽略了此响应。(最初,ICMP 被过于严密的防火墙丢弃,但这个问题已得到修复。)
经过相当长的延迟(在某些配置中,服务器会重复发送 ACK)后,服务器决定重新发送事件 3 中的段,这次是单独发送。除了 IP 标识字段和校验和外,该帧与事件 3 中的帧完全相同。它们是长度相同和新的仍然设置了“不碎片”标志。 然而,这一次,网关很乐意将该段完整地传递给客户端,而不是发送 ICMP 拒绝。
客户端确认此消息,传输继续,尽管极其缓慢地,因为后续段大致经历相同的模式:被拒绝、超时、重新发送,然后通过。
如果将客户端移到局域网内直接访问服务器,那么客户端和服务器就可以正常协同工作。
这种奇怪的行为根据目标服务器看似不相关的细节而不可预测地变化。
例如,在 Server 2003 R2 上,如果启用了 Windows 防火墙(即使允许 HTTP 和所有 ICMP),7MB 的测试文件将需要 7 小时以上才能传输,而这个问题根本不会出现,而且矛盾的是网关根本不会发送拒绝信息如果禁用了 Windows 防火墙,则不会产生任何影响。另一方面,在 Server 2008 R2 上,禁用 Windows 防火墙没有任何影响,但传输虽然仍然受到影响,但速度比启用防火墙的 Server 2003 R2 快得多。(我认为这是因为 2008 R2 使用了更智能的超时启发式方法和 TCP 快速重传。)
更奇怪的是,如果目标服务器上安装了 WireShark,问题就会消失。因此,为了诊断问题,我必须在单独的 VM 上安装 WireShark 来监视 LAN 端网络流量(出于其他原因,这可能是一个更好的主意。)
ESXi 主机是版本 5.0 U2。
答案1
您不能丢弃 ICMP 碎片化要求消息。它们是 pMTU 发现所必需的,而 pMTU 发现是 TCP 正常工作所必需的。请 LART 防火墙管理员。
根据透明规则,充当防火墙的数据包过滤路由器允许设置了“不分段 (DF)”位的传出 IP 数据包,但不得阻止响应传出数据包而发送的传入 ICMP 目标不可达 / 需要分段错误到达防火墙内的主机,因为这会破坏生成合法流量的主机对路径 MTU 发现的标准兼容使用。—— 防火墙要求 - RFC2979(原文重点)
十多年来,人们一直认为这种配置存在根本性缺陷。ICMP 不是可选的。
答案2
我终于弄清楚了。原来这是 VMware 在目标服务器虚拟网卡中实施 TCP 分段卸载时出现的问题。
服务器的 TCP/IP 堆栈会将一个大块发送到 NIC,期望 NIC 会将其分解为受链路 MTU 限制的 TCP 段。但是,VMware 决定将其保留在一个大段中,直到 - 嗯,我不确定什么时候。
当它到达网关虚拟机的 TCP/IP 堆栈时,它似乎实际上停留在一个大段,从而引发拒绝。
生成的 ICMP 数据包中隐藏着一个重要线索:被拒绝的数据包的 IP 标头显示其大小为 2960 字节 - 比它似乎拒绝的实际数据包大得多。如果 TCP 段结合了以下数据,那么它也正是该数据段在线路上的大小:两个部分已发送至目前位置。
导致问题很难诊断的一个原因是,传输的数据实际上曾是拆分成 1500 字节的帧,就运行在另一个 VM 上的 WireShark(连接到单独的混杂端口组上的同一 vSwitch)而言。我真的不确定为什么网关 VM 看到一个数据包,而 WireShark VM 看到两个。FWIW,网关没有启用大型接收卸载 - 如果启用了,我可以理解。WireShark VM 运行的是 Windows 7。
我认为 VMware 延迟分段的逻辑是,如果数据要从物理 NIC 发出,则可以利用 NIC 的实际硬件卸载。然而,它确实存在错误,在发送到另一个 VM 之前无法分段,而且不一致。我曾在其他地方看到过这种行为被提及为 VMware 错误。
解决方案很简单,就是关闭目标服务器中的 TCP 分段卸载。具体过程因操作系统而异,但请注意:
在 Windows 中,在连接的属性、常规选项卡或网络选项卡上,单击适配器旁边的“配置...”,然后查看高级选项卡。对于 Server 2003 R2,它显示为“IPv4 TCP 分段卸载”。对于 Server 2008 R2,它显示为“大型发送卸载 (IPv4)”。
这个解决方案有点不完善,可能会影响某些环境中的性能,所以我仍然会接受任何更好的答案。
答案3
我有同样的症状,问题原来是这个内核错误:https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=754294
答案4
我在 Linux 主机上看到了同样的问题。
解决方案是停用网关机器的网络驱动程序 (vmxnet) 上的大型接收卸载 (LRO)。
引用 VMware KB 中的话:
LRO 将传入的网络数据包重新组合到更大的缓冲区中,并将生成的更大但更少的数据包传输到主机或虚拟机的网络堆栈。与禁用 LRO 时相比,CPU 需要处理的数据包更少,从而降低了其网络利用率。
看http://kb.vmware.com/kb/2055140
因此,到达网关机器的数据包由网络驱动程序合并并发送到网络堆栈,网络堆栈将它们丢弃为大于 MTU 的数据包......