在 RouterOS 中配置 MSS Clamping 解决部分网站图片无法加载的问题

换成自己用 RouterOS 拨号之后,经常发现有的图片加载不出来,网上一顿冲浪之后发现可能是在 RouterOS 中没有正确配置 MTU 及 MSS,导致部分包被丢弃,也就是传说中的 PMTU 黑洞。

症状

最常见的就是微信公众号的图片始终加载不出来,就像这样(自己当时没截图,借用知乎文章的图):

说来也很奇怪,我在 iOS 的微信上是能正常加载的,但是换到我妈的 Android 微信就会出现这样的情况,之前一直以为是我妈的手机出毛病了……

PMTU 黑洞

所谓 MTU,指的是一条链路上可以通过的三层数据包的最大尺寸(包含 IP 包头)。以太网默认的 MTU 是 1500 字节。但是从我的设备到目标服务器之间的路径上可能存在 MTU 小于 1500 的链路,那么这条路径上最小的 MTU,就是整条链路的 Path MTU(PMTU)。

路由器在转发包的时候,如果包的大小超过了 MTU,那么这个包会被分片(fragmentation)。而终端设备在发包时,也可以设置 DF 标志位(Don’t Fragment)来告诉路由器不要对这个包分片,此时如果这个包大小超过了 MTU,那么路由器就会丢掉这个包,并回复一条 ICMP Fragmentation Needed 消息。发送者收到这个消息后,下次就会发送小一点的包。这个过程叫做 PMTU 发现(PMTU Discovery)。

但是互联网中有大量的设备因为各种原因,会配置为不回应 ICMP Fragmentation Needed 消息,这使得大小超过 MTU 的包会被无声地丢掉,直到 TCP 协议发现超时丢包并进行重传。这种情况就是 PMTU黑洞

此外,IPv6 包不支持分片,换句话说就是所有 IPv6 数据包全都带有 DF 标记。中间的路由器在遇到尺寸大于 MTU 的包的时候,应该回应 ICMPv6 Packet Too Big 消息,而同样的,由于各种原因,某些中间设备可能会直接丢掉这个包而不返回这条消息,直到 TCP 协议发现超时而进行重传。

为什么用光猫或者硬路由拨号就没有这个问题

这是因为,多数家用路由器默认开启了一个叫 MSS Clamping 的功能。这是针对 PMTU 黑洞的一个 workaround,简单来说就是在 TCP 握手时,服务器会通过一个字段告知客户端它愿意接收的 TCP 包的最大尺寸,这样客户端就可以限制自己发送的包的大小,保证不会超出服务端要求的尺寸。

在 RouterOS 中配置 MSS Clamping

配置非常简单,分别对 IPv4 和 IPv6 的防火墙 Mangle 表添加如下配置即可。

1
2
3
4
5
6
7
8
9
10
# 将命令中 out-interface 参数的值替换成你的PPPoE接口的名字
/ip/firewall/mangle
add action=change-mss chain=forward comment="IPv4 MSS clamp to PMTU" \
new-mss=clamp-to-pmtu out-interface="China Telecom" passthrough=yes \
protocol=tcp tcp-flags=syn

/ipv6/firewall/mangle
add action=change-mss chain=forward comment="IPv6 MSS clamp to PMTU" \
new-mss=clamp-to-pmtu out-interface="China Telecom" passthrough=yes \
protocol=tcp tcp-flags=syn

参考文档

  • 开启 IPv6 后网速变得很慢?可能是 PMTU 黑洞的问题 - V2EX
  • ROS 修改 MTU 和 MSS 解决上网慢和页面显示不全问题
  • 什么是最大分段大小 (MSS)? - CloudFlare