在 Windows VPN 默认路由中排除单台主机
全隧道配置拥有默认路由。某台主机需要在物理接口上使用更窄的前缀,并在每次连接后重新添加。
笔记本连上公司 VPN 后,一切都随之走隧道,包括打印机网页和只能在本地网络应答的内网。手工添加的路由能维持一小时,随后连接断开、重连,例外就消失了。隧道本身没有问题:Windows 只有一张路由表,VPN 适配器每次连接都会重写自己那部分。能留下来的,只有位于适配器自身条目之外的东西。
隧道接管的是哪一条
把路由表记录两次,一次在隧道断开时,一次在连上时,然后比较默认路由那几行。当配置声明了默认前缀时,适配器的条目就是表中范围最宽的一行,因此任何例外都必须比它更窄。这是关于前缀长度的算术,而不是关于信任或策略的声明。它也决定了例外必须指向哪个接口。
继续之前先记下接口索引和本地网关,因为机器接入其他网络时两者都会变。下面的命令从机器读取它们,而不是凭记忆填写。
route print -4
Find-NetRoute -RemoteIPAddress 198.51.100.30
第二条命令报告某个地址此刻会使用的接口和下一跳,这比读四十行再猜要强。在连接前后各跑一次,就得到一对值得附在变更记录里的输出。两份输出提供了本页其余部分用到的数字。
主机路由凭更具体的前缀取胜
最长前缀匹配决定胜者,因此一条经物理网关的 /32 路由胜过走隧道的默认路由,而无需改动隧道。发往该地址的流量从本地接口出去,其余流量保持原有路径。隧道仍然承载默认路由,只是不再是该目的地的唯一候选。前缀长度是路由表提供的唯一杠杆,对本任务而言已经够用。
更具体也排除了排除域名的显然捷径,因为路由表匹配的是地址,而一个域名可能解析出多个地址。先解析主机,再决定它的哪个地址值得例外。
下文一律假设连接名为 Office,本地网关为 10.0.0.1(接口 7),目标地址为 198.51.100.30。这些是占位符而非默认值,上面的命令会给出你机器实际使用的值。把这条替换说明和变更记录放在一起,下一位读者就不会照抄这些数字。
让路由挺过下一次重连
Windows 没有为 VPN 连接提供排除指令,因此例外必须存在于适配器不会重写的物理接口上。看起来像答案的那个 cmdlet 做的恰恰相反:Add-VpnConnectionRoute 会把前缀移进隧道,这正是 拆分配置应承载一个目的地、其余留在本地的那种镜像情形。在这里用它,只会保证你想挡在外面的流量反而被送进去。
手工形式就是一条命令。它的弱点在换网络,而不在重连。
route -p add 198.51.100.30 mask 255.255.255.255 10.0.0.1 metric 1 if 7
因为 -p 这条路由能在重启后保留,并把一个地址从接口 7 发出去,而隧道承载其余全部流量。把笔记本换到另一个网络,网关和索引都会失效,这条条目于是悄悄失灵。记下它归谁所有、何时应当删除。
连接脚本是更稳妥的版本,因为它在每次连接后重新添加例外,而不是指望条目自己活下来。下面两行属于启动隧道的东西,而不是登录脚本。
rasdial Office
route add 198.51.100.30 mask 255.255.255.255 10.0.0.1 metric 1 if 7
任何把隧道拉起来的东西都是第二行的正确位置,而 rasdial 只在连接建立后才返回,所以路由会落在适配器自身条目之后。从睡眠恢复后要再跑一次,因为挂起可能丢掉路由却让隧道仍然连着。如果任务相反,要把单个前缀推进隧道、其余留在本地,那么 Add-VpnConnectionRoute 才是正确的工具。
路由无法挑选进程
有时需求比一个目的地更窄:某个程序应走本地路径,其余全部留在隧道里。路由无法表达这一点,因为它匹配的是地址而非进程。可行的做法是把问题推给启动器。
让配置不占用默认路由,并让那一个程序绑定本地接口地址启动,例外就变成了启动命令,隧道则保留它原本处理的流量。路由表里什么都没变,这正是它能挺过重连的原因。这也意味着隧道从一开始就不能拥有默认路由,所以这是另一种设计,而不是在上一种设计上打补丁。
它的弱点是运维层面的,而非技术层面的。程序必须始终通过包装器启动;如果恢复后绑定丢失,它会悄悄走路由表提供的任意路径。对于你有意打开的浏览器,这可以接受;对于任何绝不能离开隧道的东西,则不可接受。
按用户划分的连接也解决不了这个问题。Windows 可以把连接限定到某个账户,而路由表仍是整台机器共用的,因此持有默认路由的账户级连接依旧会重定向机器上的每个进程,包括其他会话。只有当路由的作用域与账户一致时,作用域才真正成立。两人共用一台笔记本,也就共用一张路由表,第二个人会继承第一个人启动的隧道。
确认例外挺过了一次重连
按它将来会失效的方式来测试:断开、重连、打开一个新程序,然后请求一个必须留在本地的地址和一个必须走隧道的地址。把两者与连接状态下的 Find-NetRoute 对比。
如果重连后前缀不在表里,那它待错了对象,而不是格式错了。把它移到物理接口上,再测试一次。把两份命令输出和变更记录放在一起,因为一份可用状态的快照日后能回答的问题比一段解释更多。下一个人想知道的是连接产生了什么,而不是你打算让它产生什么。