为什么 VPN 只在大文件传输时卡住,小文件却正常
小请求能正常响应,大传输却会冻住。在照搬某个固定 MTU 之前,先比较接口上限、路径上限和通告的 TCP 载荷。
页面能打开,ping 也通,但文件复制每次都在同一个位置卡住。这种模式指向的是数据包大小,而不是带宽或凭据。封装会给每个数据包增加字节,所以原本能在原路径上通过的载荷,一旦走隧道就可能超出限制。这类讨论里常把三个不同的数字都叫 MTU,混淆它们就会调出一个能修好一个应用、却弄坏另一个应用的设置。
人们口中的三种 MTU
接口 MTU 是该链路上为数据包配置的上限。路径 MTU 是整条路由上最小的上限,它是探测出来的,而不是配置出来的。TCP MSS 是对端声明自己能接收的载荷大小,它由前两者推导而来,并不是独立的物理上限。
隧道有内层和外层两种视角。外层数据包承载被封装的数据包以及协议所需的头部,因此可用的内层尺寸小于外层路径所允许的值。到 VPN 服务器的路径,也与服务器到目的地的路径不同。
路径 MTU 探测让发送方得知可用尺寸,但它依赖反馈能否到达。当中间设备过滤掉相关 ICMP 消息时,发送方会持续发出永远无法完成的数据包,这就是典型的黑洞。症状很有辨识度:握手成功,第一个满尺寸载荷消失,连接最终被重置或挂起。应用日志里看不出任何网络问题的迹象。
用自己可控的载荷探测路径
一次探测只是关于一条路径和一个尺寸的证据,而不是推荐设置。使用会响应的主机,并且只改变载荷大小。
ping -M do -s 1372 -c 2 203.0.113.20 ping -4 -f -l 1372 vpn.example
第一条命令在 Linux 上关闭分片、发送 1372 字节的载荷,第二条则在用 -l 指定载荷的 Windows 上做同样的事。有回复说明该尺寸通过了这条路径。需要分片的消息是有用的信息,而超时则含义模糊,因为主机或中间设备可能只是丢弃了 ICMP。
用几个台阶把尺寸降下来,而不是一直二分到字节。你要确认的是是否存在尺寸上限,而不是为每个网络公布一个数字。你找到的数字只适用于这条路径、这个协议和这个地址族。
IPv4 与 IPv6 的分片方式不同,所以一个地址族的结果对另一个不说明任何问题。测试应用真正使用的那个地址族。
MSS 钳制只对 TCP 有用
在 TCP 中,有效载荷在握手时协商,路径上的路由器可以把这个值向下改写,让两端发送更小的分段。这就是 MSS 钳制所做的,而且它完全不需要触碰客户端。
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu ip link show tun0
第一条规则把转发 TCP 握手时通告的 MSS 钳制到路径 MTU,第二条则显示隧道接口自身的 MTU,方便你与路径允许的值做对比。对于握手之后卡住的 TCP 应用,钳制是侵入性最小的修复。
对 UDP 它什么也做不了。发送大块数据报的语音或游戏客户端会完全忽略 MSS 设置,因为这里没有可改写的握手。如果出问题的是 UDP 应用,就改为查看客户端自身的数据包大小选项。两种协议的失败方式不同,修复工具也不同,所以协议应该写在工单的第一句。
OpenVPN 提供 tun-mtu、mssfix 以及与分片相关的设置,它们的行为取决于版本以及数据通道是否为 AEAD。请阅读所用版本的手册,并且每次只改一个选项。
只改一个受支持的设置,然后把两次运行都重做一遍
保留原配置,只改一个受支持的数值,然后同时重跑那个曾经成功的小请求和那个失败的大传输。如果只有大传输发生变化,说明你一直在追尺寸。如果两者都变了,说明还有别的东西跟着动了。
更小的值可能减轻症状,却会增加开销,所以尽可能小的数字并不是目标。目标是路径能可靠承载、浪费又最少的尺寸。把数值、路径和客户端版本记进工单,因为把同一个数字套到别处只是猜测。
如果同一份配置在另一个可信网络中能正常工作,就把这个差异留在诊断里,而不是宣布客户端已修好。真正改变的变量是路径,而路径正是探测所测量的东西。