PPPoE 上的 OpenVPN:该降低 link-mtu、tun-mtu 还是 mssfix?
Ping 能通,但大页面在 PPPoE 或 DS-Lite 链路上卡住。靠测量路径来决定用 link-mtu、tun-mtu 还是 mssfix,而不是靠猜。
Ping 有回应,DNS 能解析,而每个需要完整大小分段的页面都会卡住。在上行链路自行增加封装时,路径会悄悄少掉几十字节预算,为干净以太网写好的配置则不断索取线路根本承载不了的报文。连接并不慢;它恰恰在丢弃真正要紧的报文。
有三个选项能解决这个问题,它们作用在不同层次。选错要么毫无效果,要么以新的方式让隧道变得脆弱,所以选择要从路径实际支持什么开始。
三个旋钮,三个不同层次
link-mtu 是外层预算:加密载荷加上 OpenVPN 头、UDP 头和外部 IP 头。它描述的是离开本机的东西,所以物理路径限制的正是这个数字。
tun-mtu 是内层预算:你的流量看到的接口。它比路径能承载的量正好大出隧道开销,而 OpenVPN 通常会自动推导它,而不是让你去设置。
mssfix 是完全不同的机制。它会改写 TCP 握手中宣告的最大分段大小,让 TCP 对端选择封装后仍能放下的分段。它不改变接口 MTU,对 UDP 也毫无作用。
如果显式设置 link-mtu,OpenVPN 会减去自身开销来推导内层 MTU。这个开销不是固定常数;它取决于加密算法和认证方式,所以从论坛抄来的数值在另一份配置上可能是错的。同时设置 link-mtu 和 tun-mtu,等于给客户端两条互相矛盾的指令。
先测量路径,再选任何数字
要从物理接口测量,而不是从隧道内部,并记住 ping 报告的是载荷,不是报文大小。把载荷与 1500 字节的帧比较之前,先为 IPv4 和 ICMP 头加上 28 字节,并在对端报告不同数字时记住这项修正。
在 Linux 客户端上,ping -M do -s 1472 禁止分片,报文太大就会失败。把载荷降到能通过为止,然后加 28 得到路径 MTU。PPPoE 要花掉 8 字节,所以 DSL 上 1492 很正常。DS-Lite 和部分移动路径要花 40 或更多,答案通常低于标称链路速率给人的印象。
如果每种 ping 大小都成功,但大传输仍然卡住,那要怀疑黑洞,而不是简单的尺寸上限。某个 ICMP 需要分片的报文在路径某处被过滤掉了,于是发送方永远学不会缩小,只有需要大分段的数据流才会察觉。
先限制 TCP,再缩小隧道
TCP 握手是 mssfix 唯一触及的东西,这通常使它成为最先尝试、干扰最小的控制手段。把它设得比路径真实预算略低,从服务器端也推送同样的值让两端一致,然后重新测试大文件下载。如果卡顿消失,问题就在 TCP 分段大小,其他什么都不用改。
当受影响的流量不是 TCP 时,就降低内层 MTU。UDP 服务、隧道里的隧道,或者发送完整大小数据报的应用,在 MSS 限制之后仍会失败,因为没有任何东西会改写它的报文大小。这种情况要降低 tun-mtu,或者设置 link-mtu 让客户端推导内层值,但绝不要两个都设。
不要动旧的分片选项。它在隧道内部拆分报文、增加开销,并且有充分理由被弃用;现代 OpenVPN 期望路径被准确描述。
为什么小探测始终有效
DNS 查询、ICMP 回显和 TCP 握手都远低于失败尺寸。于是隧道在每次快速测试里都显得健康,而 TLS 握手和 HTTP 响应却在悄悄失败。恰恰是这个组合,能用的 traceroute 加上在第一个大响应上卡住的浏览器,才是要找的特征。先拿出完整大小的探测,再去动新配置。
用失败的流量来验证。抓一个大页面,如果受影响的就是 UDP 服务就发一个完整大小的 UDP 数据报,并从另一条链路上的第二个客户端重复一次。记录测得的路径 MTU 和你选的值,否则下一个动这份配置的人会把这个数字当成随意定的。
当 IPv6 跑在隧道上时,内层 MTU 绝不要低于 1280,因为那是每条 IPv6 链路必须承载的最小值。更小的 MTU 是关于路径的事实,不是凭感觉微调的性能设置。