腾讯云虚拟网络MSS Clamp的问题导致特定场景下TCP数据包无法正确传输

》 记录一下偶然发现的腾讯云虚拟网络的一个问题。这个问题已工单反馈,并且承诺后续会有修复。

0. 太长不看

腾讯云的虚拟网络VPC对TCP MSS的clamp机制不当,导致当对端用户发送超过1500的TCP MSS的数据包给腾讯云端的服务器时,两端协商出的MSS会导致TCP传输过程中数据包过大(大于中间链路的MTU),被中间路由器丢弃,影响HTTPS连接等业务场景。

1. 问题分析

我在今天检查甲骨文云(Oracle Cloud)服务器上脚本运行情况的时候发现某个定时从腾讯云服务器上拉取数据的脚本全部超时,经过手动curl测试,发现HTTPS请求会卡在TLS握手阶段,无法接收到从腾讯云发回的Server Hello数据包。

卡在TLS Handshake

根据以往经验,多数情况下该问题由本地或者中间路由的MTU配置不当导致数据包过大,加上TLS握手包一般会被标记为不允许分片,从而中间路由器无法处理大包时会将其丢弃。经过检查,腾讯云的MTU为1500,而甲骨文云的MTU为9000(因为甲骨文的虚拟网络内部开启了Jumbo Frame)。

开始排查问题,使用tcpdump在两侧服务器抓包发起请求,甲骨文云侧的数据显示MSS为8960。

腾讯云侧收到的数据包MSS为8924,少36字节

而腾讯云回复出去的MSS为1460(网卡MTU 1500 – TCPIP头 40 = 1460)

而甲骨文一侧实际收到的MSS是1424,也少了36字节。

注意,此时握手已经成功,腾讯云侧的服务器在发送数据时按照MSS 1460发送(因为甲骨文收到的MSS是中间路由给clamp过后的mss,而非腾讯云服务器本身发出去的MSS),因此这个时候一个TLS握手发出去的TCP包就有可能实际长度为 1460 字节,大于中间链路所允许的MTU,因此数据包被丢弃,表现为你可以看到后续由443端口发出的Server Hello数据包重传。

经过多番测试,对于入向的TCP数据包,腾讯云对其进行clamp的方式是如果MSS大于某个数值,直接减少36字节,而不是将其设置为大于某个阈值之后clamp到链路MTU。如果对端服务器类似于甲骨文云开启Jumbo frame,且不对出向流量做clamp的话,就会因为clamp过后的实际MSS过大,而两边在约定发送的段长度时会以小的一方为主,但是在这种情况下显然 8000+>1460,因此腾讯云的服务器会误以为使用1460作为MSS即可正常发送TCP数据包,从而导致最终业务数据包长度超过中间路由器的限制,TLS握手这类不允许分片的数据出现丢包。

2. 额外发现

在工单里来回帮忙测试的时候,腾讯云的客服最开始使用ping大包的方式来测试网络是否正常。然而根据后续测试,我发现腾讯云虚拟网络对于ICMP数据包也有额外处理。

经过测试,即使对icmp设置了不允许分片,腾讯云的虚拟网络在传输的时候还是会将大包进行分片处理,所以是能通的,而对于TCP包设置了不允许分片的则会被丢弃,两种协议的行为不一致。

使用以下命令来设置DF(Don’t Fragment):

ping <ip> -M do -s 1472

发出去的包带了DF标记,收到的回复包则变成了两段数据包

可以看到腾讯云的回复数据包是存在分片的,而另一方面,腾讯云收到这个数据包的时候,DF标记却没了

腾讯云接收到的ICMP包,DF标记位没了

为了避免是我理解问题,我特地用这台测试机器ping了另一台日本的机器,结果是正常的,DF标记未被修改。说明腾讯云对ICMP的大包传输实际上是无视了DF标记位强行分片。

在另一台日本机器B上接收到的数据包,DF标记位未被修改,数据包也没有被分片。

3. 结论

这个问题是腾讯云虚拟网络对MSS的不当处理导致的,按照正常机制来说TCP MSS Clamp应该将其限制在链路能承载的范围之内,而不是盲减36这种奇葩做法。

此外,由于对ICMP的特别处理(无视分片标记位强制分片大包),这有可能会影响部分依赖于PMTUD的应用程序。

对于影响TCP业务的临时解决方案为,自行在mangle表内对MSS进行clamp,使其小于或等于1424。

iptables -t mangle -A INPUT -p tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1461:65535 -j TCPMSS --set-mss 1424

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注