上个月有两个人向我提出了相反的需求:一个希望 IPv6 由 VPN 覆盖,另一个希望办公室打印机继续可用。单条 ::/0 路由无法同时满足这两者。这条路由是否属于隧道内部,取决于三个条件,而第一个条件就让大多数商业出口出局。如果出口不承载 IPv6,把默认路由指进去,只会把你的流量藏进一条无处可送的隧道。

三个条件决定答案

默认路由只有一行,::/0,它匹配任何没有更具体路由的 IPv6 目的地(RFC 4291)。安装它很容易。该不该装,则归结为三个条件。

条件一:你连接的出口有可用的 IPv6,并且愿意转发它。条件二:你的客户端能保住通往隧道端点的路径,而端点通常就在你即将路由的范围内。条件三:你能说出必须留在隧道之外的本地 IPv6 目的地。

每个条件各有各的失败方式,所以靠猜要花掉一整晚。条件一失败,你得到的是沉默和一张看起来正确的路由表。条件二失败,握手会在开始几秒后死掉。条件三失败,留下的是一条能用的隧道和一个在打印机旁不高兴的同事。

What you seeDecision
Exit answers IPv6 probes, no local IPv6 devices to protectRoute ::/0 into the tunnel
Exit answers IPv6 probes, printer or NAS reachable by a global addressRoute ::/0, then give those prefixes a route of their own
Exit is IPv4-only inside the tunnelDo not route ::/0; block IPv6 on the client
Endpoint is reached over IPv6 and sits inside ::/0Install a host route for the endpoint first

出口真的承载 IPv6 吗?

带 IPv6 地址的隧道接口并不能证明对面怎样。许多商业配置内部只有 IPv4:服务器给你一个 v4 地址,其余全部丢弃。在其上装一条来自 AllowedIPs 的默认路由,内核就会乐意把 IPv6 送进一个对端永远看不到的接口。

在隧道建立时从客户端测试。ping -6 -c 3 2001:4860:4860::8888 和 ip -6 route get 2001:4860:4860::8888 回答的是不同问题:是否有回应返回,以及内核选择了哪个设备。当后者指向你的隧道而前者毫无返回时,问题在出口,不在路由。

如果你自己运营出口,客户端测试可能通过,路径却仍然走进死胡同。确认网关上的 net.ipv6.conf.all.forwarding=1 已开启,且其防火墙接受从隧道转发的数据包。一条正确路由进拒绝转发的出口的数据包,从客户端看就是彻头彻尾的路由故障。

客户端能保住通往端点的路径吗?

这就是那台只有 IPv6 的 WireGuard 端点的笔记本把我坑住的情形。端点地址是普通的全局地址,因此落在 ::/0 之内。把整个前缀路由进接口,握手数据包就会一头扎进它们正试图建立的那条隧道里。隧道永远起不来,客户端输出里也没有任何一句话说明原因。

wg-quick 在 Linux 上处理这件事。它从 AllowedIPs 推导路由,装进自己的表,并加一条策略规则,让发往端点的流量先走主表解析:即 suppress_prefixlength 0 规则,你可以在 ip -6 rule 里找到它。正因如此,wg-quick up 能在手写 ip -6 route add default dev wg0 切断连接时成功。

在抬起隧道前后,用 ip -6 route get <endpoint-v6> 分别检查两种状态。如果答案变成了你的隧道设备,那你就删掉了隧道赖以生存的路径。自己写路由时,给端点补一条经原网关的主机路由,或者用 IPv4 连接端点,把 IPv6 留给载荷。

哪些必须留在隧道之外?

更具体的路由会压过默认路由,所以链路本地前缀或自带路由的 ULA 网段仍能照常工作,无需额外费心。这正是许多全隧道 IPv4 配置能活下来的沉默原因:LAN 前缀比默认路由更具体,从不会进入隧道。

当本地设备以全局地址可达时,这套就崩了——同一台路由器给打印机、NAS 或电视发了公网前缀,再正常不过。那些地址只匹配 ::/0,不匹配别的,于是它们跟其他流量一起进了隧道,然后消失。

按目的地决定,而不是按协议。如果两三台本地主机很重要,就给它们的前缀一条走物理接口出去的路由,或者把它们从协商集合里拿掉。如果名单一直变长,诚实的答案是给 IPv6 做拆分隧道并附一份书面例外清单,而不是一条你不停打补丁的默认路由。

如果答案是不,就封掉

不给 IPv6 配路由,并不等于放着它不管。一台有可用 IPv6 默认路由的主机,在隧道承载 IPv4 时仍会继续使用它,于是流量从 VPN 旁边溜走,每个偏好 IPv6 的服务都会看到你的真实地址。那是泄漏,不是拆分隧道。

选一个,并写清楚你选了哪个。把默认路由指过去并接受上面的取舍,或者在隧道期间禁用 IPv6。仅从 AllowedIPs 里去掉该前缀还不够,因为操作系统保留着自己的默认路由。在 Linux 上,粗暴版是 sysctl -w net.ipv6.conf.all.disable_ipv6=1;精细版则用防火墙规则把 IPv6 挡在隧道之外。手机客户端自带明确的屏蔽 IPv6 开关,效果一样。

像查泄漏那样验证

抬起隧道,读状态,而不是相信开关。ip -6 route show default 应该只显示一条默认路由,而 ip -6 route get 对 LAN 之外的目的地应该指向隧道设备。断开连接后再重复一遍:比隧道活得还久的默认路由,就是你要找的那个故障。

然后测另一个方向。从本地网络中的一台主机,用客户端的本地 IPv6 地址去访问它。如果这不通了,那条件三从来没被满足过,无论当时路由表怎么说。

把结果写在下一个能找到的地方:哪些前缀留在本地、为什么,以及断开后客户端会怎么做。runbook 里的两行,能省下下一位工程师半夜重新发现端点环路的时间。

相关文章