客户端显示出一个全局 IPv6 地址,握手已经完成,可从互联网那边什么也回不来。地址池的设置看起来正确,因为它确实正确;地址有效,隧道也在承载它。失败的是投递,而投递取决于 VPN 服务器上游做出的决定,客户端侧的任何配置都无法影响。

误区:你的 /64 里有一部分已经可达

这个故事很有吸引力:服务器有一个公网 /64,把它一分为二,另一半交给 VPN 池,这些客户端就上互联网了。拆分前缀确实能在不冲突的情况下分配地址。但它不会改变上游网络如何决定发往这些地址的包该往哪走。

IPv6 没有可以回退的默认转换层,因此回程必须以路由或链路层投递的形式存在。发往客户端隧道地址的包到达上游路由器,那台路由器依据自己的表做转发决定。如果表指向物理链路,包就去那里,不管你 VPN 服务器怎么想。

整个故障就在这里:去程能通,因为你的服务器把包发出去;回程在一个跳之外死掉,因为上游从未知道客户端存在。

链路上的投递究竟需要什么

当上游认为整个 /64 都在物理链路上时,它会用邻居发现来解析每个目的地址。它在该链路上发出请求,并期望地址的所有者应答。位于隧道之后的 VPN 客户端不在那条链路上,无法应答,于是请求无人回应,包被丢弃。

切出一个 /65 并不会改变这种预期。上游仍然把这个范围当作在链路上,因为唯一能让它改变主意的是代表客户端应答的路由或代理。两者都不会因为改了一份 VPN 配置而出现。正是这个缺口解释了:为什么一个地址池可以在自己的客户端之间完美工作,却仍从其他任何地方都不可达。

代理邻居发现是能连接两个世界的机制。网关为它拥有的特定客户端地址应答请求,并把流量转发进隧道。这可行,但必须精确限定在这些地址上,并让生命周期跟随地址池,因为为一个不再服务的地址应答,会把别人的流量变成黑洞。有些上游不会接受它们没有预料到的代理应答,所以这个设计还需要服务商的同意。

有路由的投递需要什么

更干净的设计是让上游真正把一段分配路由到你的网关。它可以是一个委派前缀,也可以是为较小网段配置的静态路由,两种情况下要求相同:上游的表必须把客户端范围指向你的服务器。这样回程流量到达网关,网关再把它转发进隧道。

有路由的投递彻底消除了邻居发现的问题,所以在搭建任何取巧方案之前,值得先向服务商申请。请以书面形式申请路由,并确认下一跳是上游真正能到达的地址。

两种设计都仍然需要在网关上开启 IPv6 转发,并配置防火墙规则允许转发流量双向通行。投递是路径其余部分的前提,而不是替代品。一条能工作但关闭了转发的路由,产生的安静与缺少路由完全相同,所以在重新设计之前两项都要检查。

两条需要弄对的独立路由

地址池前缀和客户端自己的默认路由是两套设置,把它们混为一谈会产生看似隧道故障的部分连通。地址池告诉服务器分配哪些地址。客户端的路由决定客户端把流量送进隧道的目的地。

server-ipv6 2001:db8:42:100::/64
push "redirect-gateway ipv6 !ipv4"

上面的前缀是文档用地址空间,重定向只适用于 IPv6,这正是双栈部署在 IPv6 投递尚待修复时常常想要的。IPv4 重定向并不建立 IPv6 覆盖,两者互不蕴含。

无论实施什么策略,都要让 IPv6 依赖的控制流量继续工作。Packet Too Big 报文是只放行 TCP 和 UDP 的防火墙的典型受害者,丢弃它们会造成连接建立后在较大传输上卡住。

路由器通告是第三个活动部件。如果网关开始在上行链路上通告,可能会改变客户端和邻居对链路上前缀的认知,从而悄悄推翻你刚刚记录的设计。

记录这份设计,让重建能复现它

分别写下三个对象:客户端分配、客户端的目的地路由,以及上游对该分配的投递方式。三者齐全的读者能重建部署;只有地址池设置的读者会复现原来的缺陷。

然后用一个必须从外部返回的包,显式测试回程方向。openvpn(8) 手册涵盖 server-ipv6 和 redirect 选项,而服务商的路由确认补上了手册无法覆盖的那一半。

相关文章