仅包含式还是仅排除式分流:默认该选哪一种
先定默认值,再建清单:只把点名要走的流量送进隧道,还是全部走隧道再挖出例外。然后用一个没人列进清单的应用去验证。
打开 VPN 客户端的路由设置,你会看到两个形状不同、却常常贴着同一张标签的控件。一个让你列出应该进入隧道的目标,其余流量继续走本地网络。另一个则假定一切都走隧道,然后问你该把什么挖回来。两者在第一次测试时都能给出可用的隧道,而且都靠往清单里加行来维护,所以除非有人装了新东西,这个差别一直看不出来。
这是默认值的选择,不是功能。在清单写到二十条之前就把它定下来。
默认值要从客户端里读,不要从宣传册上读
WireGuard 把这件事做得格外直白,因为 AllowedIPs 只是一张包含清单,别无他用。带 0.0.0.0/0, ::/0 的对端会把一切都送进隧道;带 10.20.0.0/16 的对端只把那个网段送进去。它没有排除语法,所以如果你想放过一个子网,要么把其余前缀逐一列出,要么在接口之外再加策略路由。
OpenVPN从反方向走到同一个地方:redirect-gateway def1 建立完整隧道,显式的 route 指令则添加或覆盖特定前缀。商业客户端通常把两者都藏在复选框和一张目标表后面,所以在相信标签之前,先看清表到底改的是哪一个。
要问的不是复选框叫什么名字。要问的是:一个目标不在任何一张清单里的数据包,会变成什么样。把答案写下来,其余的配置自然从它推出来。跳过这个问题,第一次说不清的故障就会由那天离客户端最近的人来定夺。
跟住一个没人列进清单的应用
在只包含的设计里,这个应用走本地网络和本地 DNS 直接出去。在只排除的设计里,它走隧道,范围取决于服务端允许什么;新的 SaaS 工具第一天就能用,还是收到一张说 VPN 把它弄坏了的工单,差别往往就在这里。
两种默认值本身都不更安全。只包含收窄了隧道承载的范围,让本地网络成为阻力最小的路径;只排除则把隧道当作正常路径,把例外当作例外,这更接近多数企业策略真正想要的。
| Question | Include-only | Exclude-only |
|---|---|---|
| Unlisted new app | goes direct | goes through the tunnel |
| A site that breaks over the VPN | add it to the list | carve it out |
| Internal names still resolve | only if routed and served | usually, until split DNS says otherwise |
| Upkeep | one entry per internal service | one entry per misbehaving public service |
注意平台列出的对象是哪一类
Android 的 VpnService.Builder 把允许的应用清单和禁止的应用清单分成两个模型暴露出来,而按应用的规则是按包名而不是按目标来选。放进这种清单的浏览器会把它打开的每一个目标都带上,这和路由表是两种粒度,也没法修好一个能正常工作的应用里那一个坏掉的站点。
桌面客户端常把两类混在同一个界面里,所以要确认你要加的这一行是一个包、一个主机名,还是一个前缀。它们失败的方式不同,解决的地方也不同。主机名那一行通常变成一条路由加一个名字,而包那一行变成客户端内部的策略判断。把你加的是哪一种写下来,因为之后来排查的人一定会假定成另一种。
测失败状态,不要只测顺利路径
被排除在隧道外的应用,并不会自动获得干净的直接访问。如果客户端同时还执行了无 VPN 即断网的规则,被排除的流量可能整体被丢弃,那看起来像应用坏了,而不像路由决策。这个情况要主动去测,别等用户报上来才知道。
然后把 DNS 和 IPv6 当作两个独立项目来查。只承载 IPv4 的隧道会给支持 v6 的应用留一条绕行路,而 split DNS 决定的是:对完全不进隧道的流量,内部名字还能不能解析。跑一遍清单内的应用、清单外的应用、一次名字解析和一次重连,把结果和配置放在一起。
把默认值写在下一个人找得到的地方
把选定的默认值、例外以及它们的责任人,记录在客户端配置文件所在的同一处,因为下一位管理员会不读这段理由就加一条记录。注上日期和每条例外的原因,这样某个站点后来修好了,那条过时的挖洞就能放心删掉。没有理由的清单会一直长下去。
有人报某一个目标坏了,改例外,不要改基准。为了修一个站点就翻转默认值,等于把所有不在清单里的应用悄悄挪到隧道的另一侧。