每次连接后临时输入的路由只解决当下,不解决这一周。在 macOS 上,持久的做法通常是把前缀存进 VPN 服务,因为服务在每次连接时重新安装它,并在服务停止时移除它。另一种做法是用于测试的临时 route add,以及用于在协商之后才知道地址的连接钩子。

先判断路由属于服务还是属于会话

先问这个前缀依赖什么。如果它是某个原生 VPN 服务后面的固定专用网络,路由就属于该服务。如果它依赖每次连接都会变化的对端地址,那么钩子或由状态驱动的任务可能是更合适的归属。如果你只是在检查未来五分钟内的一条路径,临时路由就够了。

绑定到服务的路由拥有最干净的生命周期。它随服务出现,在服务未连接时消失,因此不会比它所指向的隧道活得更久。这个特性比一行命令的便利更有价值。

绑定到会话的路由属于接口,而这个编号会在毫无通知的情况下改变。重连可能给隧道一个与上一次会话不同的 utun 名称,于是写死的命令就变成一条指向空处、甚至指向另一条隧道的路由。

读取当前的路由和服务状态

从系统实际使用的名称开始。网络设置里的服务名称是下面这些命令的标识符,当前的路由表则显示已连接的客户端安装了什么。在隧道处于连接状态时把两者都读一遍。

scutil --nc list
networksetup -listnetworkserviceorder
netstat -rn | head -30
route get 10.20.5.5

这次查询会显示内核此刻会为这个地址选择哪个网关和接口。如果它已经选择隧道,说明前缀已被覆盖,再添加路由就是多余的。如果它选择本地路由器,说明隧道缺少这个前缀。

连接时记下对端地址。它会成为绑定到服务的路由中的网关值,通常也是 netstat -rn 里为隧道默认路由显示的目的地。

用 networksetup 保存前缀

networksetup 工具会把额外路由附加到某个网络服务上。对于名为 OfficeVPN 的服务和经由隧道对端可达的专用前缀,形式如下:

sudo networksetup -setadditionalroutes OfficeVPN 10.20.0.0 255.255.0.0 10.8.0.6
networksetup -getadditionalroutes OfficeVPN

服务名之后的每一组三元组给出一个目的地、它的网络掩码以及对端作为网关。 一次调用可以携带多个前缀,空调用则清空已保存的列表。 读取端是验证步骤;如果列表为空,说明设置并没有应用到你以为了的那个服务上。

网关必须是隧道对端,而不是物理路由器。指定本地网关的路由会把专用前缀从物理接口发出去,这正是原本的故障,只是多了几步。过期的对端是看起来正确的路由却从不传输流量的最常见原因。请从已连接状态重新读取对端,而不是从旧笔记里复制地址。

route add 只用于测试

直接路由适合在把前缀变成永久配置之前证明它能用。接口形式可以避免查询网关:

sudo route -n add -net 10.20.0.0/16 -interface utun4

这条路由会在下次重连时消失,并依赖今天存在的接口编号。因此它是诊断手段,而不是部署方案。如果测试可用、而绑定到服务的路由不可用,先比较网关值;那是最常出错的字段。

对用替换 VPN 默认路由来强制分流的主意要谨慎。强制全隧道的客户端会在重连或策略刷新后重新安装自己的路由,手动改动因此被悄悄撤销。少数专用前缀是路由问题;在全隧道策略下把所有互联网流量留在本地,则是服务器侧的决策。

只有地址需要协商时才选择钩子

对于基于 PPP 的链路,例如 L2TP,链路建立后 macOS 会执行 /etc/ppp/ip-up。脚本会收到接口名以及协商出的本地和远端地址,因此可以添加一条依赖服务器所分配对端的路由。这正是固定服务路由无法表达的场合。

IKEv2 和厂商客户端可能完全不走这条 PPP 路径,也就是说脚本在整个会话里都可能保持沉默。遇到这种情况,由 launchd 任务轮询 scutil --nc status 并对状态变化做出反应,就是等价机制,只是略有延迟。

无论使用哪种钩子,都要让它幂等,并且只删除自己创建的路由。一个删除所有提到该隧道的路由的脚本,最终会删掉维持 VPN 服务器可达的那条路由,连接一旦成功就会断开。

最后检查专用目的地、普通浏览,以及一次重连或睡眠周期。然后查看 scutil --dns,因为名称解析是另一套策略,路由能用并不能证明专用名称能解析。

相关文章