经由 OpenVPN 隧道打通 IPv6:家用路由器逐项排查
当隧道下发的地址前缀路由器无法路由时,IPv6 会中断或泄漏。按顺序排查客户端、地址池、转发、代理 NDP 与回程路径。
笔记本已经有 IPv6 地址,隧道也在 IPv4 之上正常运行,可每一项 IPv6 测试都失败。更糟的情况是测试通过,而流量根本没有进入隧道,而是从运营商路径溜了出去。这两种结果都指向同一个缺失的决定:没有人给路由器一个能向客户端路由的前缀。从客户端向外逐段排查,就能找出缺的是哪一块。
先从客户端看起
先看客户端,再动服务器。ip -6 addr 应该显示来自地址池的隧道地址,ip -6 route 应该显示指向隧道设备的已下发前缀。地址在而没有路由,说明下发缺失,或者客户端忽略了它。两者都没有,说明服务器根本没有分配任何东西。
然后看客户端对外部世界的判断。隧道上的默认路由会接管全部 IPv6 流量,而指向 2000::/3 的路由只接管全局单播。两者都可能正确,只是回答的是不同的泄漏问题。
完全没有 IPv6 默认路由的客户端,即使隧道本身完全健康,也会在每一项公网测试中失败。这是策略缺口,不是隧道损坏。
检查服务器真正能路由的地址池
服务器只能从自己真正掌控的前缀里分配地址。如果 ISP 委派了 /56,就为隧道保留一个 /64,其余留给局域网。如果 ISP 只在 WAN 上给了一个 /64 且不再往下路由,那就别动局域网前缀,给隧道一个唯一的本地网段,并接受客户端没有全局出口这一事实。
地址池和下发是一体的。server-ipv6 2001:db8:80:10::/64 负责分配地址,push "route-ipv6 2000::/3" 告诉客户端该往隧道里送什么。下发 2000::/3 覆盖全局单播,同时把链路本地与组播网段留在隧道之外。
把选定的前缀写进部署记录。下一次改动 ISP 交接的人,必须知道隧道已经占用了哪个 /64。
为第二个协议族打开转发
IPv4 转发并不等于 IPv6 转发。设置 net.ipv6.conf.all.forwarding=1,并照搬 IPv4 FORWARD 策略已经允许的全部内容。在 FORWARD 链默认丢弃的路由器上,规则集缺失看起来和隧道死掉一模一样。
握手成功,客户端拿到地址,却什么也回不来。要为两个方向和 WAN 一侧都补上规则,因为能到局域网却到不了互联网的隧道会在第三项测试中失败。失败一次之后去看链上的计数器,因为从未命中的规则和命中后丢弃的规则是两种不同的问题。
ip6tables -A FORWARD -i br0 -o tun0 -j ACCEPT
ip6tables -A FORWARD -i tun0 -o br0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
ip6tables -A FORWARD -i tun0 -o wan0 -j ACCEPT
有一个副作用专坑最后把锅甩给隧道的人。开启转发后,内核会跳过路由器通告的地址配置,除非另行指定,而这样学到的默认路由也会随之消失。要么在 WAN 接口上钉一条明确的默认路由,要么特意把那里的 accept_ra 设为 2。两种改法做完,都要先确认路由器仍有可用的 IPv6 出网路径,再重新测试客户端。
上游不路由时,回答邻居发现请求
上游设备有时会把委派出去的 /64 当成自己链路的一部分,拒绝把任何子前缀路由到 VPN 主机。这时必须有东西为每个客户端地址单独应答邻居请求。
OpenVPN 会把分配到的地址暴露给 client-connect 钩子,因此条目可以跟随真实地址池,而不必依赖手工维护的清单。钩子添加 ip -6 neigh add proxy $ifconfig_pool_remote_ip6 dev eth0,对应的 client-disconnect 钩子再把它删掉。不删的话代理表会过期,后来的客户端可能继承一条它从未拿到过的地址条目。用 ip -6 neigh show proxy 确认状态。
把代理当作权宜之计。为隧道的 /64 指向路由器的一条静态路由,能扛住客户端更换,而且完全不需要特权 shell 钩子。
沿路径一段一段往外查
分三步向外测试:先测路由器的隧道地址,再测局域网主机,第三步用 traceroute6 测公网目的地。第一步证明地址池,第二步证明转发与局域网防火墙,第三步证明经由上游的回程。每一次失败都把范围收窄到一台设备,而不是把整个设计推倒重来。
留意隧道的 MTU,因为路径上的 IPv6 路由器不会替你分片。MTU 过大会表现为 packet-too-big 报错和 TLS 卡住,而不是干脆的失败。要显式设置,并用接近上限的载荷测试。