两条隧道都能建立,每个客户端都能到达网关,而一旦一方去试另一方的地址,数据包就消失了。两次握手本身都没有问题,重新签发密钥也不会改变这一点。网关在自己两个接口之间转发普通的未加密 IP 包,所以两种协议根本不需要互相理解;它们需要的是握手无法提供的三件事。源客户端上的目标覆盖、网关处的源地址归属,以及远端的返回路径。三种情况表现完全相同,因此要一起诊断。

症状每次只缺一块

该设计在 wg1 上使用 10.71.0.0/24 的 WireGuard 地址池,在 tun1 上使用 10.72.0.0/24 的 OpenVPN 地址池,两者都终止在同一台网关上。被测请求从 10.71.0.5 发往 10.72.0.30 上的 HTTPS。下面每一步都只针对这一条流。每种检查的失败特征各不相同,尽管用户感受一模一样,而通常一次只缺三项中的一项。

先确认两个地址池确实都在这台网关上,并且隧道所在命名空间里的转发是开启的。把两种协议终止在不同命名空间的网关,在它们之间根本不转发任何东西,读任何 peer 配置之前就该排除这种情况。

扩大分支端的目标覆盖

在 WireGuard 客户端上,现有 peer 条目必须为 OpenVPN 目标选择网关,也就是说目标前缀必须出现在该 peer 的允许列表里。加上它并不会建立第二条隧道;它改变的是客户端查该地址时选择哪个 peer,而在 wg-quick 下路由通常会随列表自动生效。

AllowedIPs = 10.71.0.0/24, 10.72.0.0/24

真正的密钥和端点行保持原样,并记住使用 Table = off 的管理器根本不会从这份列表里取任何路由。那种情况下,同一前缀必须由拥有它的一方写入系统路由表,而那里缺失的路由看起来和缺失的允许前缀完全一样。

把源地址归属留在网关上

该客户端在网关上的 peer 条目应当继续只拥有这个客户端的源地址。允许地址会在两个方向上被检查:它们为出站流量选择 peer,也校验入站包的源地址确实属于发出它的 peer。因为分支想访问 OpenVPN 地址池就把它塞进分支的条目,正是把流量送往错误参与方的那个错误。

AllowedIPs = 10.71.0.5/32

把那条目当作归属声明来读,而不是当作路由上的便利。客户端还要访问的其他一切都属于客户端的目标列表,而不属于网关对「这个 peer 允许发什么」的设想。

给 OpenVPN 一侧一条返回路由

目标现在需要一条回到 WireGuard 源的路由,对地址池内的客户端来说,它通常以 OpenVPN 服务器推送路由的形式出现。只推给确实需要它的客户端,并在客户端上确认它被接受了,而不是假定指令已经生效。

push "route 10.71.0.0 255.255.255.0"

分发路由不是授权。当目标不在地址池里,而是位于另一台 OpenVPN 客户端之后时,那台客户端自己对局域网的归属和它的转发要求也会进入设计,而推送的路由只是前半部分。在添加指令之前,把需求重写成一张图:哪个节点拥有哪个前缀,哪个节点向它转发。

只放行你本来想放行的那一个服务

在转发开启、两个地址池都可路由之后,剩下的问题是策略。规则要针对具体这条流,而不是笼统地针对接口对;应用前先预览,并保留带状态的返回路径,让回包不会被当成新连接挡掉。试运行不花什么代价,还能精确显示会加上什么。

sudo ufw --dry-run route allow in on wg1 out on tun1 proto tcp from 10.71.0.5 to 10.72.0.30 port 443

这些是针对被转发流量的规则,与网关面向两个 VPN 服务的监听规则不同。打开外层 UDP 端口并不授权内层请求,而授权内层请求也不需要到网关的无限制 SSH 或转发上的默认放行。目标自身的防火墙和监听地址也必须一致,因为绑定在 loopback 上的服务连路由得最仔细的包都会拒收。只覆盖会话一个方向的策略,通常表现为请求成功而回复无声,这是三种结果里最让人困惑的一种。

互指的路由通常意味着不需要源地址转换,这会在目标处保留 WireGuard 地址,也让日志可读。不要因为涉及两种协议就加一刀切的 masquerading,也不要在不清楚谁依赖现有转换规则时删掉它。然后端到端验证这一条流,再测一个被禁止的源或端口:两侧握手都能通,既证明不了交叉路由,也证明不了隔离。

相关文章