观察隧道建立、客户端 ping 对端,随后一个本应位于 VPN 之后的公网地址却从别处应答,或者完全不应答。这个症状通常与加密算法或证书无关。由提供商路由的前缀以 IP 路由的形式下发,而要通过 OpenVPN 承载它,意味着在两个层级上复现该路由,并在回程上保留源地址。不需要桥接 WAN,把地址加到接口上也不会创建上游路由。

前缀必须先被路由给你

询问提供商这个网段是如何下发的。路由到服务器主地址的前缀,与提供商期望在 WAN 段上直接到达的地址是两回事。这里的设计只适用于第一种模型。

如果提供商把 198.51.100.64/28 路由到你的入口服务器,该范围的报文会到达服务器并在那里停下,除非你转发它们。如果提供商改为把该范围桥接到你的上行链路,服务器就必须为它应答 ARP,那是另一个问题。

在改动 OpenVPN 之前先测试下发模型:从外部 ping 网段内的一个地址,观察哪个接口收到它。如果什么都没有到达,任何隧道配置都帮不上忙。如果地址直接在 WAN 上应答,说明提供商是在桥接而不是路由,下面的设计并不适用。

两个路由层级:内核路由与 iroute

OpenVPN 需要该前缀在两处生效。内核必须知道该前缀属于隧道设备,这样被转发的报文才会进入 OpenVPN 而不是默认路由。OpenVPN 还必须知道哪个通过认证的客户端拥有该前缀,这就是客户端专属目录中的 iroute 条目。

服务端路由大致如下,前缀与掩码必须与分配一致:

ip route add 198.51.100.64/28 dev tun0

客户端专属配置负责指定归属。把它放进以客户端证书通用名命名的 CCD 文件,并确认服务器真正读取的就是那个目录:

iroute 198.51.100.64 255.255.255.240

OpenVPN 关于路由网络的文档 描述了这两个层级以及它们为何分离。没有 iroute 的内核路由会把无主的报文送进隧道,OpenVPN 会丢弃它们。没有内核路由的 iroute 会让报文停留在服务器的默认路径上。两种错误从客户端看都像隧道坏了。

服务地址放在哪里

被委派的前缀与隧道自身的地址不是一回事。愿意的话,链路可以从私有空间编号,并把公网服务地址放在服务真正运行的位置。

在本地终结服务的 Linux 客户端上,一个主机地址就够了:

ip address add 198.51.100.70/32 dev lo

如果前缀继续路由到 VM 主机或另一个 LAN,那就改为配置那条下游路径及其回程路由。没有必要把网段中的每个地址都分配给 VPN 客户端,而且以后会造成冲突。

在回程上保留公网源地址

来自被委派前缀的回复必须经隧道离开,并到达提供商的路由。如果远端主机通过自己的 ISP 发送,源地址看起来就是伪造的,提供商或目的端会丢弃它。解决办法是 基于源地址的规则或专用路由表,而不是更宽的 NAT。没有它,服务只能从隧道对端可用,从其他任何地方都会失败。

在入口服务器上,过宽的 masquerade 规则通常是元凶。它把公网源地址改写成服务器主地址,客户端永远看不到真实的那个:

iptables -t nat -S POSTROUTING

把 NAT 限制在真正需要的私有 VPN 客户端上,并让被委派的前缀保持不翻译。然后确认服务器到底有没有转发该前缀,因为默认拒绝的转发链会悄悄丢掉它。

在信任之前验证两个方向

验证是四个观察,而不是一个。确认提供商的路由到达、内核把前缀送进隧道、OpenVPN 把它归属到正确的客户端,以及回复带着完整的公网源地址返回。每个观察都有自己的命令,所以失败指向某一层,而不是整个设计。每次改完配置都按这个顺序执行。

询问内核被委派的地址走哪条路径,并在请求运行期间观察隧道:

ip route get 198.51.100.70
sudo tcpdump -ni tun0 host 198.51.100.70

反向路径过滤可能让正确的路由失效。检查隧道和上行链路上的实际设置,并记住严格模式会拒绝源地址与入口接口不匹配的流量:

sysctl net.ipv4.conf.tun0.rp_filter net.ipv4.conf.eth0.rp_filter

最后,测试失败的情形。停掉隧道,确认公网前缀没有从普通上行链路泄漏。恢复 VPN 后服务应当回来,而且不扩大主机暴露的端口。

相关文章