两个 LAN、一个 WireGuard 枢纽,以及一份必须回答三个独立问题的 IPv6 地址规划:用哪个前缀、每个路由器占哪个地址、哪个对端拥有哪个子网。其中任何一个出错,都会得到一条能握手却不路由任何流量的隧道。下面的规划按这个顺序推进一套私有双站点设计。它有意不提供 IPv6 互联网接入。

先确定前缀的用途

当目标是站点之间的私有可达性时,从 fd00::/8 中取一个唯一本地前缀是自然的选择。无论每个站点由哪家运营商接入,它都保持稳定,而且不会被通告到全球路由表,因此不会把内部编号泄露到互联网。

同一种属性也正是局限。ULA 没有公网回程路径,因为没有任何上游知道要把它路由到你的网关。如果需求包含 IPv6 互联网出口,你需要的是 由能够把它路由到你这里的提供商所分配的地址段,而那是一项独立的设计决策,有自己的前缀长度。

不要假设每个部署都需要 /48。 RFC 6177已经不再要求终端站点使用单一固定长度,合适的长度取决于你将运营的网段数量。请按 RFC 4193 的流程生成 ULA,而不是随手编一个别的组织也可能使用的前缀。在任何设备开始配置之前,先把结果写进地址规划,因为没人记录的前缀,迟早会被人重复使用。

把前缀切分成角色

下面的示例使用 fd7c:2e91:4a00::/48,表格按每个地址块写在哪里、而不是按站点来组织。请把这些取值当作示例,在真正部署之前自行生成。

Where it is writtenWhat appears thereBlock usedWhat it establishes
Hub wg0.conf, interface addressA single host address on the tunnelfd7c:2e91:4a00:ff00::1/128The hub's own tunnel identity
Hub wg0.conf, Site A peerTwo ranges in one AllowedIPs line::2/128 and :10::/64That A owns its tunnel address and its LAN
Hub wg0.conf, Site B peerTwo ranges in one AllowedIPs line::3/128 and :20::/64That B owns its tunnel address and its LAN
Site A wg0.conf, interface addressA single host address on the tunnelfd7c:2e91:4a00:ff00::2/128A's identity as the hub sees it
Site B wg0.conf, interface addressA single host address on the tunnelfd7c:2e91:4a00:ff00::3/128B's identity as the hub sees it

LAN 块用 /64,因为普通 SLAAC 需要 64 位的接口标识符。把 LAN 切成更小的块会破坏大多数主机的这一前提,而在私有规划里省下来的地址并不值得这些运维成本。两个 LAN 块都取自 /48 的中段,把低位区间留给以后的站点。

隧道地址是一个管理用的地址池,而不是链路上的网段。每个接口从中取一个 /128,这样没有主机会以为隧道上存在共享子网,也不需要的地方不会跑邻居发现。请让各角色互不重叠,并把它们记录在组织的地址规划里,以免后来的站点重复使用某个地址块。

让每个路由器只有一个隧道地址

为每个路由器分配一个 /128,并沿用正在运行的枢纽已有的密钥和端点。下面三个值的唯一区别是主机部分。

Hub:    Address = fd7c:2e91:4a00:ff00::1/128
Site A: Address = fd7c:2e91:4a00:ff00::2/128
Site B: Address = fd7c:2e91:4a00:ff00::3/128

这个 /128 描述的是分配给该接口的唯一地址。它并不说明对端可以路由多少目的地,因为可达性由 AllowedIPs 表达,而不是由接口前缀表达。

每个站点路由器还需要一个位于自己 LAN 块内的地址,以及分发该地址的手段,通常是面向 SLAAC 的路由器通告。WireGuard 不会替你发送这些通告,也不会把两个 LAN 桥接起来。站点路由器在两个方向上都是路由器。

把子网归属写进 AllowedIPs

枢纽为站点 A 配置的对端条目拥有 A 的隧道地址和 A 的 LAN,这一对就是全部的归属声明。该对端段落里不需要再描述站点。

AllowedIPs = fd7c:2e91:4a00:ff00::2/128, fd7c:2e91:4a00:10::/64

站点 B 的对端条目拥有与之对应的一对,这里的对称性比简短更重要。把两行放在一起看,归属的分工一眼就能看出来。

AllowedIPs = fd7c:2e91:4a00:ff00::3/128, fd7c:2e91:4a00:20::/64

这是两个独立的对端段落,而不是两份列表在争抢同一个对端。WireGuard 双向使用 AllowedIPs:它按出站目的地选择对端,也会拒绝源地址不匹配解密它的那个对端所分配范围的入站包。把整个 /48 都交给两个对端,就会关掉第二道检查,也会模糊哪个站点拥有哪个块。正是这条区分,让错误的源地址暴露出来,而不是被默默接受。

每个站点只需要通过自己的枢纽对端可达的目的地:站点 A 列出枢纽地址、站点 B 的隧道地址和 B 的 LAN,站点 B 则列出相反的一组。这些列表故意不对称,因为每个路由器看到的是拓扑的不同一侧。请从地址规划出发写这两份列表,而不是从另一个站点的配置里抄,否则过时的范围就会在改号后继续存在。

在 wg-quick 下,这些范围还会生成路由。在 systemd-networkd 或手动配置下,需要自己补上等价的路由;否则内核永远不会把包送进这条隧道。

完成转发并测试两个方向

在枢纽和两个站点路由器上都开启 IPv6 转发,并在转发策略中放行所需的跨站点服务。请保持 ICMPv6 Packet Too Big 报文可用,因为丢弃它们会破坏路径 MTU 发现,并造成大流量传输卡顿,看起来像应用故障。枢纽如果在防火墙上没有放行,就会在路由与隧道之间把包丢掉。这种故障模式值得了解,因为隧道仍然在线,只有部分流会死掉。

sysctl net.ipv6.conf.all.forwarding
ip -6 route
ping -6 -c 3 fd7c:2e91:4a00:20::1

从站点 A 的主机测到站点 B 的主机,再反向测一次。ping 枢纽的隧道地址只会走第一跳,即使 LAN 路由错了也能通过。真正能证明这套规划的,是主机到主机的流量。

外层 WireGuard 连接可以跑在 IPv4 上,而内层流量是 IPv6,这种独立性在迁移期间很有用。如果以后要加上全球 IPv6 出口,请把提供商分配、源地址选择和回程路由当作新的设计步骤重新审视;上面的私有规划对站点间流量仍然有效。

相关文章