如何让一个 IPv4 目的地走 OpenVPN 隧道之外
通过 net_gateway 的单条主机路由可以让一个 IPv4 地址绕开 OpenVPN 隧道。请核查下一跳、DNS 范围、IPv6 以及客户端侧的任何拦截。
多数全隧道配置让每个数据包都走 VPN,而 redirect-gateway 正是实现这一点的指令。单个目的地仍然可以被豁免。为该地址添加一条带 net_gateway 占位符的主机路由,会告诉客户端通过机器原来的网关到达它,而其他所有流量继续使用隧道。
这一行看似无害,通常也的确如此,但它只解决一个问题:发往那个确切 IPv4 地址的数据包该往哪里走。它不会改变 DNS、IPv6、防火墙策略或由哪个进程发起连接,而其中任何一项都可能悄然让这条豁免失效。
net_gateway 实际指代什么
该豁免的配置行只有一行:route 198.51.100.77 255.255.255.255 net_gateway。第一个地址是目的地的文档用占位符,掩码使它成为单主机路由,最后一个词元指代网关占位符。OpenVPN 在安装路由时会解析网关占位符。vpn_gateway 表示隧道自身的网关,也就是 VPN 链路的远端。net_gateway 表示 VPN 改变任何东西之前主机所用的默认网关,通常是物理网络中的本地路由器。
第三个占位符 remote_host 指向 VPN 服务器的地址,它的存在是为了让隧道能沿物理路径到达服务器。把这三者混淆,是豁免走错地方或根本无法安装的最常见原因。
该值在连接时读取,而不是写入文件。如果机器移动到另一个网络,VPN 之前的网关会变化,路由也会跟随新的网关。这通常正是你想要的,但也意味着豁免的行为取决于客户端启动时所在的网络。
为什么主机路由会胜过隧道的默认路由
路由由最具体的前缀匹配决定。redirect-gateway def1 通常不会替换默认路由;它会添加两半,一条覆盖地址空间下半部分的路由和一条覆盖上半部分的,从而保留原来的默认路由供其他用途。这些半区比单个地址更宽泛。
掩码为 255.255.255.255 的单地址路由就是 /32,这是 IPv4 能达到的最高具体程度。当评估发往该地址的数据包时,/32 会匹配并获胜,与哪条路由先安装无关。这就是豁免背后的机制,只要路由确实存在,它在 Linux、macOS 和 Windows 上表现一致。
Windows 又加了一个维度。当两条路由同样具体时,度量值更低的那条获胜,而 VPN 适配器可能带有一个改变结果的接口度量。/32 仍然比两个半区更具体,因此豁免通常成立,但如果旧会话残留的路由同样具体,就可能产生令人困惑的结果。
豁免不覆盖什么
DNS 是第一个缺口。客户端可能推送内部解析器设置,操作系统会继续用它们做查询。在连接前解析名称的应用程序可能拿到本应直连的服务的内部地址,或者拿到能正确解析、随后却栽在下一个缺口上的外部地址。
IPv6 是第二个。该路由指令只处理 IPv4。双栈目的地可能优先选择 IPv6,而它会按策略走隧道或走主机的 IPv6 默认路由,于是 IPv4 豁免根本不会生效。你需要一条匹配的 IPv6 路由,或者有意为该客户端禁用 IPv6。
应用范围是第三个。规则基于目的地,因此连接该地址的每个进程都会走直连路径,无论那是不是你心里想的进程。反过来,一个网站可能从多个地址应答,所以豁免一个 /32 并不等于豁免该网站。如果真正的需求是按应用分流,那么 Community 客户端的路由表就是错误的工具。
如何在不自欺的情况下验证豁免
从已安装的路由开始,而不是从浏览器开始。在 Linux 上向内核询问它会为该特定地址使用哪条路由和下一跳;在 Windows 上读取该 /32 的路由表。确认网关是物理网关而不是隧道。如果条目缺失,说明配置未生效,或者客户端没有修改路由的权限。
然后测试两个目的地。发往被豁免地址的请求应当从物理接口出去,而发往某个内部对照地址的请求仍应使用隧道。只测试被豁免的地址并不能说明隧道其余部分如何,而测试一个公开网站测的是该网站解析到的地址,未必是规则里的那个地址。
最后检查可能压过路由的控制项。客户端侧的 kill switch 或 Windows 的 block-outside-dns 设置可能禁止直连流量,表现为路由正确但连接失败。不要为了让豁免看起来生效而关掉这些控制项;如果策略禁止直连路径,那么豁免对 VPN 服务所有者来说就是设计问题。
重新连接或切换网络后要再次检查,因为 VPN 之前的网关和可用路径是当下的属性,而不是配置文件的属性。