你有一条不能让步的要求:远端必须收到发送方发出的、原样的 1,500 字节 IP 报文,连报文头也算在内。隧道无法把这个报文连同自己的封装一起塞进第二个 1,500 字节 IP 报文,所以设计里必须有一样东西让步。下面的顺序就是我在变更窗口里用的顺序:先写字节预算,再证明底层实际能承载多少,然后只选一种承载机制,并在远端确认内层报文。

改配置文件之前先写字节预算

封装会在好几层上增加字节,总额取决于你自己的配置,而不是一个查得到的常数。地址族、加密算法、传输协议,以及任何压缩或分帧选项都会改变这个数字,所以从别处抄来的减法预算毫无价值。从内层要求出发,加上你自己配置实测出的每报文开销,在任何人动手改文件之前把算出的外层尺寸写进变更单。

设计由两个数字决定:路径在不分片时能承载的最大外层报文,以及封装所需的额外字节。当前者小于后者加 1,500 时,两者之中必须有一个改变。此时选择就不再有创意,而变得很窄,所以我在讨论方案之前先把两个数字写下来。隧道层没有任何东西能把这些字节压掉,也没有任何一个 MTU 整数能取消你收到的要求。

用不分片 ping 探测底层

要测量路径,而不是相信接口 MTU,因为外层路径上最小的一环才决定结果。在 Linux 上, -M do 设置不分片位, -s 则给出 ICMP 载荷,所以在减去 20 字节 IP 头和 8 字节 ICMP 头之后,1,500 字节的帧会用 1,472 字节的载荷回应。逐步提高载荷直到探测失败,然后跳到巨型尺寸,看看路径到底能不能承载。

ping -M do -s 1472 -c 2 198.51.100.10
ping -M do -s 8972 -c 2 198.51.100.10
tracepath -n 198.51.100.10

能通过 1,472、却在 8,972 上失败的路径,无法在一个外层报文里承载 1,500 字节的内层报文外加开销。这就是你打开 VPN 配置之前需要的答案。它还决定下面两节里哪一节适用于你,所以把两个探测结果都放在变更单旁边。在这里靠猜会赔上一个维护窗口,因为一个自称 up 的隧道并不能告诉你路径能接受多大的报文。

只有在每一跳都同意时才提高路径 MTU

巨型帧是端到端的属性,不是你在恰好能管的两个端点上打开的一个设置。外层路径上的每一台交换机、路由器和服务商交接点都必须同意更大的尺寸,只要有一跳仍是 1,500 字节,就会悄悄丢掉你刚刚变大的报文。只改本地接口是典型的半截变更:隧道看起来坏了,底层却在无声地丢包。

ip link set dev eth1 mtu 9000
ip link show dev eth1
ping -M do -s 8972 -c 2 198.51.100.10

改完接口,再去探测同一条路径,然后才动别的东西。如果探测仍然失败,用 ip link set dev eth1 mtu 1500 回退,转而使用分片,而不是为了一条不属于你的链路去和服务商争论。

底层无法变大时使用隧道分片

内层报文之下如果有受支持的分片机制,多个外层报文就可以承载一个内层报文,远端隧道端点会在把原始报文交给协议栈之前把它们重新组装起来。 Community OpenVPN 的 UDP fragment 选项 就是这种思路的一种实现,它和外层报文的普通 IP 分片不是一回事。两端都必须运行支持该格式的版本,数据路径也必须是你以为的那条:OpenVPN 3 客户端和内核加速的数据通道限制不同,所以在一个客户端上能用的配置在另一个上可能失败。

openvpn --config client.ovpn --tun-mtu 1500 --fragment 1400 --verb 4

普通的外层分片是另一个选项,也是更弱的一个。IPv6 路由器根本不会对转发的报文做分片,所以过大的外层报文直接消失,只有相关的 ICMPv6 消息能活着走完路径时,发送方才知情。分片还会招来过滤和丢包放大,所以我把它当成兜底而不是方案。

基于流的外层承载通过切分自己的字节流绕开了尺寸问题,重组后的内层报文仍然可以是 1,500 字节。这是一个有自身代价的传输决定,包括众所周知的 TCP 套 TCP 行为,所以要有意识地选它,而不是把它当成 MTU 争论的副作用。

确认内层报文原样到达

验证不是握手成功。 在两端隧道接口上抓包 ,把应用实际发出的报文大小和内容,与远端收到的那个做比较。

ip link show dev tun0
ping -M do -s 1472 -c 2 10.8.0.1
tcpdump -ni tun0 -c 5 'icmp and greater 1000'

如果抓包显示远端收到了完整尺寸的内层报文,说明承载选择成立,剩下的工作就是把它记录下来。如果隧道自称 up 却什么都看不到,说明外层报文在端点之间某处被丢弃了,这会把你带回探测,而不是带到加密设置。

关单之前把三件事写进变更单:实测的外层预算、你选的机制,以及证明内层报文存活的那条命令。下一个碰这条隧道的人三样都需要,而这三样都无法只从配置文件重建。

相关文章