WireGuard 里的 IPIP:那多出来的 20 字节没人算进预算
IPIP 在 WireGuard 内部要多花 20 字节。在把传输卡住归咎于分片之前,先在最合适的层次把预算算清楚。
小 ping 整天穿过隧道,SSH 横幅到了,然后文件传输一开始会话就卡死。这个模式指向一个算错层次的字节预算:外层隧道只按自己的开销定尺寸,没人把内层那份减掉。嵌套的 IPIP 包对 WireGuard 来说只是又一个 IP 包,正因如此它才会吃掉你从未预留的字节。修正就是算术,三个命令加一张表就够了。
暴露问题的症状
分片问题总是以尺寸现身,从不出现在地址上。对方的 56 字节 ping 一小时都有回应,到 1400 字节却突然沉默,而隧道仍在、路由未变。握手过得去,第一个完整 TCP 窗口过不去,传输在看起来健康几秒后就停住。
先测天花板再动手,没测过的天花板你会追两次。用 do-not-fragment 位发一个低于可疑尺寸的探测包,ping -M do -s 1000 10.9.0.2,再在一个已知过大的尺寸上重复。如果目的地不变而翻转只随载荷尺寸变化,那说明路由在正常工作,是预算里少算了一个包头。
每个包头只算一次,而且算在正确的层
从路径上最小的 MTU 起算,而不是从隧道接口起算,因为那堵墙在两个端点之上。WireGuard 在 IPv4 外层传输上花 60 字节:IP 20、UDP 8、自身包头 32;外层换成 IPv6 则是 80 字节。1420 字节的 wg0 在 1500 字节路径真正能送达的 1440 之下留了 20 字节余量,所以从 wiki 抄来的那个数字放在这里仍然成立。把整条栈画进图里,因为下一个改 MTU 的人否则会随手挑一个新的整数,然后继承你的 bug。
| Layer | Bytes | Remaining |
|---|---|---|
| Path MTU on the wired link | none yet | 1500 |
| WireGuard outer: IP, UDP, WG header | 60 | 1440 |
| Slack kept for safety | 20 | 1420 (wg0) |
| IPIP header inside the tunnel | 20 | 1400 (ipip0) |
| Original IPv4 and TCP headers | 40 | 1360 payload |
第五行才是预算出错的地方,因为嵌套接口承载的 1400 字节里已经包含了原包自己的 40 字节包头。真正到达读文件进程的只有 1360 字节。把 1400 当成应用载荷——任何缺那一列的图都在暗示你这么做——内层设备就会在没动过的墙前面多出 40 字节的慷慨。
两个 MTU 分别设置
承载方和嵌套设备是两个独立对象,所以各自需要自己的值。按这个顺序应用,因为嵌套设备不会从承载方继承任何东西:
ip link set wg0 mtu 1420
ip tunnel add ipip0 mode ipip remote 203.0.113.9 local 198.51.100.4
ip link set ipip0 mtu 1400
值得记住的规则:内层 MTU = 外层 MTU - 20(IPIP 隧道承载的是 IPv4 包时),一旦内层包任一侧是 IPv6 就变成 - 40,因为嵌套包头从 20 字节长到 40 字节。这个减法里不该有别的东西。你选 1420 时 WireGuard 的开销已经付过了,再收一遍只会得到一个完美工作、却始终很慢的隧道——只要没人去测它。两台设备都挂整数 1400 的部署,正是这个错误的指纹。
嵌套包任一侧出现 IPv6 都会让上面的数字全部作废,所以要重算,而不是从相似站点抄一个数。IPv6 一旦出现在外层路径或原始数据报里,被减掉的包头就是每个 40 字节,载荷数字也正好下降这么多。
你大概不需要的那条防火墙规则
两个 WireGuard 对端之间,线上只有 UDP,所以为协议 4(RFC 2003 分配给 IPv4-in-IPv4 封装的那个号)开一条入站规则,既不能建立嵌套路径,也不能维持它。IPIP 包头活在加密载荷内部,任何边界防火墙都看不到。反正开协议 4 除了多一条被允许进入主机的路,什么也买不到。
提到原包源和目的的策略属于解封装之后,落在隧道接口和路由表上。放在那里,边界规则就可以保持关闭。
WireGuard 实际验证的是什么
AllowedIPs 判定的是 WireGuard 承载的包的源,而它承载的是 IPIP 封装后的包。被允许的 IPIP 源即使内部包头写的是你并不想路由的主机,也会被接受。因此原始源并不是对端替你检查的东西,那对内部地址需要在解封装之后有自己的执行。
包装层提供的是路由结构,不是第二层密码学。把 IPIP 说成 WireGuard 内部的额外保护,描述的是另一种设计。
用梯子验证,而不是一个 ping
试探跨过你算出边界的尺寸,然后确认内核对两台设备的看法。1360 字节载荷加 40 字节包头正好 1400,下面第一个回显过得去,第二个就坐在天花板外一个字节:
ping -M do -s 1372 10.9.0.2
ping -M do -s 1373 10.9.0.2
wg show wg0
ip -d link show ipip0
天花板下干净通过、在上方第一个尺寸就失败,说明算术从头到尾成立。然后把最初卡住的那次传输再跑一遍,因为合成探测只证明预算,对发送方自己的写入尺寸一无所知。如果嵌套路径在你证明能过的尺寸上仍然断,先检查远端解封装和路由,再考虑从 MTU 上再削十字节。