跨站点到站点 VPN 的 IPv6 前缀委派:检查清单
在站点到站点 VPN 上路由委派 IPv6 前缀的检查项:前缀映射、流量选择器、转发、MTU、返回路径与重新编号。
把隧道建起来只是容易的那一半。我见过一条站点到站点链路连着好几天,远端 LAN 上每台主机却只收到沉默,因为那个网关背后的委派前缀虽然配在了接口上,却从未被路由进隧道。隧道地址一直能回应 ping,而这正是没人去看前缀的原因。以下是委派 IPv6 前缀必须穿过站点 VPN 时我检查的顺序:前缀映射、流量选择器、转发与防火墙,然后是返回路径。
先写前缀映射,再动隧道
前缀委派只是把一个块交给提出请求的路由器。它不会在对端安装路由,不会把这个块当作 on-link 扩展,也不会告诉远端网关请求路由器背后有什么。这三件事都得你自己配,而且应该在任何配置文件改动之前写进地址规划。
为每个站点记录委派前缀、LAN 上实际使用的 /64、隧道地址以及每条路由的归属。运营商把 2001:db8:a1::/56 委派给站点 A,站点 A 在自己的 LAN 上提供 2001:db8:a1:10::/64;站点 B 保留 2001:db8:b2::/64。如果站点 A 只提供那一个 /64,两端都应写 2001:db8:a1:10::/64,而不是整个 /56。
两个错误几乎每次都会出现。一是把远端前缀当作 on-link,桥接它或把它放到隧道接口上,于是网关去回应它根本到不了的主机的邻居请求。二是只配了一个 /64 却宣告整个 /56:路由表声称这个块可达,而大多数地址都掉进黑洞。
让两端的流量选择器与路由一致
IKEv2 和 WireGuard 都会拒绝不匹配的流量,而且拒绝得足够安静,隧道看起来仍然健康。
在 strongSwan 里选择器是显式写明的。站点 A 上:
conn a-to-b
left=2001:db8:ff::1
leftsubnet=2001:db8:a1:10::/64
right=2001:db8:ff::2
rightsubnet=2001:db8:b2::/64
auto=start站点 B 的 conn 镜像这两个子网。两者不一致时,响应方会用 TS_UNACCEPTABLE 拒绝 CHILD_SA,因此即使 IKE_SA 起来了,child SA 也永远起不来。两端的配置子网都必须等于本地内核朝隧道方向路由的内容。
WireGuard 把同样的信息放在 AllowedIPs 里,有一个转折:AllowedIPs 在接收时是源过滤,在发送时是路由。如果站点 B 对站点 A 的 peer 条目漏掉 2001:db8:a1:10::/64,来自站点 A 的包会以有效握手到达,却被当作不允许而丢弃。这就形成那个熟悉的模式:握手不断更新,却始终没有回应发出。用 wg show wg0 allowed-ips 比较两端。每次委派变化都重查那张列表;过期的 /64 会让会话存活,却让新前缀不可达。
先排除转发、防火墙和 MTU
在再读一行日志之前,先确认网关到底会不会转发。sysctl net.ipv6.conf.all.forwarding 必须在两端都返回 1,并且也要应用到隧道和 LAN 接口上。随之而来还有一个陷阱:一旦开启转发,accept_ra=1 的接口就不再接受运营商的路由器通告,于是机器可能在隧道内路由得完美,却丢掉上游默认路由。仍需要运营商 RA 的地方要设 accept_ra=2。
接着防火墙规则必须双向放行这个前缀,ICMPv6 也必须活下来:邻居发现、packet-too-big 和 destination-unreachable 都不是可选项。丢掉它们,路径 MTU 发现就会坏掉,你会得到能打开、却在第一个大响应上卡住的连接。最小的一对规则像这样:
nft add rule inet filter forward ip6 saddr 2001:db8:a1:10::/64 ip6 daddr 2001:db8:b2::/64 accept
nft add rule inet filter forward ip6 saddr 2001:db8:b2::/64 ip6 daddr 2001:db8:a1:10::/64 accept
nft add rule inet filter forward icmpv6 type { packet-too-big, nd-neighbor-solicit, nd-neighbor-advert } acceptMTU 就在这背后。隧道把可用 MTU 压到 1500 以下,而 IPv6 要求每条链路至少能承载 1280。小 ping 能过,SSH 会话在大突发时冻住,/proc/net/snmp6 里则出现永远回不来的 packet-too-big 消息。显式设置隧道 MTU,或在 mangle 表里钳制 TCP MSS,并让报告更小 MTU 的 ICMPv6 回得来。
测返回路径,而不是隧道地址
最耗时的故障是单向路由。每个网关都把远端前缀路由进隧道,但主机会走自己的默认网关回包,因为它们的 /64 在那里。把包送到目的地只是半条路。
| What you see | Likely cause | First check |
|---|---|---|
| Tunnel up, every host silent | remote prefix not routed into the tunnel | ip -6 route get on both gateways |
| Handshake renews, no replies | source prefix missing from selectors or AllowedIPs | ipsec statusall, wg show allowed-ips |
| Small pings pass, transfers stall | MTU or ICMPv6 packet-too-big filtered | /proc/net/snmp6 counters |
| Old prefix still works | stale /64 left in routes or selectors | delegation lease and route table |
两条命令能解决大部分问题。在站点 A 上,ip -6 route get 2001:db8:b2::53 from 2001:db8:a1:10::5 会显示来自该源的回应会走哪个接口;在站点 B 上做镜像检查。然后从主机而不是网关生成流量,并比较两端的计数器。
如果远端前缀的邻居请求出现在隧道上,说明这个前缀在某处被当成了不该有的 on-link;在调整路由之前先修好它。Proxy NDP 覆盖确实需要桥接的情形,而不是路由的替代品。
为前缀在你脚下变化做计划
前缀委派是委派方与请求方路由器之间的租约,续租可能返回不同的块:运营商迁移后是另一个 /56,或者租期比隧道已存在的时间更短。每个写着旧前缀的地方都会同时过期:路由、选择器、AllowedIPs、防火墙规则、DNS 记录,以及任何硬编码字面地址的监控。消息流程见 RFC 8415。
在前缀预计会移动的地方,维护一个不依赖它的内部地址规划,并在边界做转换。NPTv6 是无状态选项,能让内部策略保持可读,但它是前缀转换,不是魔法层:/64 边界必须对齐,校验和会被调整,而嵌入字面地址的应用照样会坏。如果端到端保留运营商地址,就把租约行为和变更流程写进地址规划。
最后用一个经得起时间的测试收尾:每个 LAN 上一台被允许的主机能访问另一个 LAN 上的服务,回应经隧道返回,并且在隧道重启和委派续租之后这一对仍然可用。如果最后一部分失败,这个设计就依赖了撑不过一周的状态。