Always-on 和 kill switch 在同一个设置界面上只隔两行,于是人们把它们当成同一功能的两个名字。并非如此。Always-on 是重连策略:开机就启动隧道,崩溃或换网之后把它恢复,连不上就一直重试。kill switch 是流量策略:它规定隧道断开期间哪些包仍然可以离开这台机器。你可以只开前者不开后者,而且多数平台的默认效果是在那段空档里照样放流量出去。

检查 1:阻断是它自己的开关吗

在 Android 10 及更高版本上,已保存的 VPN 配置在齿轮图标后面展开两个彼此独立的开关:Always-on VPN 与 Block connections without VPN。只有第二个才是 kill switch。只开 always-on 时,隧道断开后确实会回来,但在回来之前,任何正在发请求的应用都可以经 Wi-Fi 或移动数据访问网络。

受管设备在策略 API 里保持同样的拆分。setAlwaysOnVpnPackage() 接收包名和一个 lockdownEnabled 标志;只设置包名而把标志留成 false 的管理员,造出的是一条常驻隧道,而不是一台封闭的设备。控制台的措辞很少把这点差别显示出来。

状态要从设备上读,不要只看控制台:adb shell settings get global always_on_vpn_app 和 adb shell settings get global always_on_vpn_lockdown。前者返回 null 说明平台层没有强制任何包;后者返回 0 说明平台并没有在阻截。 Android VPN 指南把这两项当作彼此独立的能力,而不是一个模式。

检查 2:阻断归哪一层

标签活在客户端里,阻截通常活在防火墙里,所以同一个词在每个平台上的含义都不一样。下面这几行,是值得在信任其中任何一条之前先确认的。

Where you see the labelWhat the label promisesWhat actually blocks
Android VPN profileAlways-on VPNBlock connections without VPN
Android device policyAlways-on packagelockdownEnabled flag
Windows built-in VPNAlways On tunnelOutbound firewall rules
WireGuard for WindowsTunnel serviceBlock untunneled traffic option
wg-quick on LinuxRoute via AllowedIPsYour own nftables or iptables rules

Windows 是不阻截的标签最清楚的例子。那里的 Always On 描述的是隧道的持续性:用于机器认证的设备隧道和用于已登录账户的用户隧道,通过配置文件 XML 或 Add-VpnConnection 设置。那份配置里没有任何东西会在隧道缺失时挡住出站包,而出站过滤默认并不开启,所以一台连着 Wi-Fi 的笔记本在 VPN 掉线时照常上网。

WireGuard 也这样分,但按客户端分。WireGuard for Windows 为每条隧道提供 Block untunneled traffic 选项,由它安装过滤规则;Linux 上的工具没有对应的开关。wg-quick 会为每个 AllowedIPs 条目加一条路由,再加一条 fwmark 规则,而 wg-quick down 会把两者一起撤掉,于是物理默认路由重新接手,中间一个包都不丢。

在 Apple 平台上,always-on 是面向受监管设备的管理配置文件功能,表达为按需规则而不是一个勾选框。需要从 payload 里回答的问题是:隧道失败时是拒绝流量,还是只是再请求一次连接。菜单标题不会告诉你答案。

检查 3:量空档,不要看图标

有四种切换值得关注,它们的失败方式各不相同:任何人登录之前的开机、睡眠与唤醒、Wi-Fi 切到移动数据,以及客户端崩溃后隧道接口停在半配置状态。在其中一种上重连干净,并不能说明另外三种。

最便宜的测试是故意停掉隧道,然后试着出去。隧道断开时,curl -m 5 -sS -o /dev/null -w '%{http_code}' https://example.com 返回 200 说明策略是 fail-open;超时或者连接被拒,说明还有东西在挡。ip route get 1.1.1.1 会指出承载该包的接口,通常比托盘图标更有信息量,systemctl status wg-quick@wg0 则显示接口是否被干净地移除。

把同样的检查放到第二个窗口里,在网络正在变化的时候跑,而不只是等它稳定之后。断开与重试之间的间隔才是关键,查名字是发现流量漏出去最快的方法:如果隧道断开时本地解析器还在应答,阻断就不完整。

检查 4:锁定之前先定好例外

有两种例外是结构性的。客户端必须能到达 VPN 服务器本身,所以一条丢掉一切的规则集,也把本该恢复保护的重连一起挡掉了。强制门户同样不能穿过一条还没起来的隧道,所以酒店 Wi-Fi 上被锁住的设备需要一条有据可查的开门户办法,而不是临时凑。

最小形态:放行端点,其余全丢:

table inet vpnlock {
  chain output {
    type filter hook output priority 0; policy drop;
    oifname "lo" accept
    ip daddr 203.0.113.10 udp dport 51820 accept
    oifname "wg0" accept
  }
}

把那一行端点当成变量,而不是常量。服务商会换地址,而一个过期的例外正是原本好用的 kill switch 变成连不上的设备的常见原因。本地打印机、NAS 共享和开发服务属于同一份写下来的清单,否则它们会按支持工单一张一张被找出来。

检查 5:记下你真正证明了什么

维护一张四列表格,靠测试来填:状态、预期行为、观察到的行为、证据。行是检查 3 里的那些切换,再加一次故意的失败。填不进命令输出的那一行,就是没测过的一行;而能通过重连行、却在开机行失败的设备足够常见,多花十分钟是值得的。

在宣布完成之前:

  • 确认阻断设置与重连设置是分开的,并在设备上读出它的值。
  • 知道是哪一层在执行阻断——客户端选项、设备策略还是防火墙——以及那一层在哪里配置。
  • 留一条能证明阻断的检查命令,并在切换过程中运行,而不是之后。
  • 把例外清单写下来,包含端点,并注明由谁更新。
  • 在受管硬件上别动被强制执行的策略;去掉阻断只是改了要求,并没有修好隧道。

相关文章