Android 按应用 VPN 与 Chrome 无痕模式:这条规则覆盖什么
按应用规则选择的是应用包,而不是浏览器标签;通知在首次请求之后才到达。请检查适用范围、时机和所需的强制执行方式。
有人做了这样一套自动化:只要出现无痕标签就打开 VPN,但它的运行并不可靠。这背后有一个结构性的原因,值得在调校自动化之前先弄明白。Android 的按应用 VPN 规则选择的是应用包,而无痕模式是同一个包内部的状态,因此规则无法区分这两者。通知是在浏览器已经开始工作之后才触发的,所以即使触发器正确,也来得太晚。请按顺序完成下面的检查;如果在第四项检查上失败,说明需要改变的是思路,而不是配置。
检查一:确认按应用规则能选择什么
Android VPN 应用通过允许或排除的应用列表来构建隧道,而这些列表接收的是包名。普通模式下的 Chrome 和无痕模式下的 Chrome 是同一个包,用户 ID 相同、网络权限相同,因此包含或排除 Chrome 的规则对两者完全一样地生效。平台没有标签级的身份可以用来匹配。
这不是需要绕开的缺陷,而是 API 提供的粒度。如果你的需求是让 Chrome 的流量走隧道、其余流量走外部,按应用规则可以精确表达。如果你的需求是只让隐私标签走隧道、普通标签直连,那么任何按应用规则都无法表达,再多的通知逻辑也不会凭空加上平台没有的选择器。
检查时要注意包身份这个陷阱。测试版或厂商定制版是与稳定版浏览器不同的包,因此覆盖其中一个的规则会悄悄漏掉另一个:
adb shell pm list packages | grep -i chrome
检查二:测试触发器是否可能足够早
通知监听器观察的是它被允许看到的通知。观察不等于拦截:通知产生时,浏览器已经完成域名解析并打开了连接,这条链路上没有任何环节会等待你的自动化跑完。随后启动 VPN 客户端的任务还必须等客户端界面、可能的授权提示以及隧道握手,这些以秒计,而首个请求以毫秒计。
检测本身还因为第二个原因而脆弱。通知文本会随版本和语言而变化,针对某个版本写的匹配规则在更新后就会失效,而且失败是静默的。缺少通知并不能证明没有隐私浏览,它可能只是说明字符串变了,或者通知渠道被静音了。
这套流程有一个正当的版本,而且它是有序的,不是被动响应的:先连接已批准的 VPN,确认真正能证明隧道正在承载流量的就绪信号,然后通过受支持的浏览器控件打开无痕会话,一旦就绪检查失败就停止。这样保护的是你即将开始的会话,而不是假装保护一个已经在运行的会话。
检查三:审查你即将授予的权限
通知访问是一项敏感授权,它允许服务读取所有应用的通知,而不只是浏览器的通知。把它交给一个不熟悉的自动化工具、只为监视一种浏览器状态,是一笔糟糕的交易,而且实验结束后权限依然存在。由无障碍功能驱动的点击更糟:它是界面之上的脚本层,不是受支持的 VPN 控制接口,任何布局变化都会让它失效。
在授予任何权限之前,先问同样结果能否用受支持的机制达成。如果答案是策略要求,通常是可以的:平台可以在完全不需要监听服务的情况下,为某个应用或配置文件强制隧道。
检查四:当需求是强制执行时,使用始终开启和阻止
如果浏览器绝不能在隧道之外通信,强制执行属于平台设置,而不是宏。Android 支持始终开启的 VPN 并提供阻止模式,在隧道未建立时阻止流量,受管设备还可以通过策略下发。不要假设,而要确认设备当前的状态:
adb shell settings get global always_on_vpn_app
adb shell settings get global always_on_vpn_lockdown
应用值不为 null 表示某个包被配置为始终开启,阻止值为 1 表示隧道之外的连接被阻断。正是这一对组合把“尽力而为”的习惯变成了保证,同时也彻底消除了竞态,因为隧道会在应用流量被允许之前建立。
这里有两个边界值得说明。工作资料有自己独立的 VPN 配置,因此在个人资料中验证过的规则,对工作资料什么也说明不了。另外,单独安装的浏览器、由其他应用打开的自定义标签,或应用内的 webview,都是具有自己路由的另一个包或上下文,所以要验证实际的包,而不要假设样式不同的窗口就是独立域名。
检查五:把流程写下来,并测试失败路径
无论你选择哪种方式,都要记录顺序和中止条件。对便利流程来说,这意味着:隧道建立、就绪得到证明、无痕会话打开,一旦就绪失败就硬性停止。对强制执行路径来说,这意味着:始终开启的应用已配置、阻止已启用,以及在隧道完全无法建立时的预期行为,也就是没有流量,而不是未受保护的流量。
然后要测试失败,而不只是成功。在会话进行中断开隧道,观察浏览器会怎样;阻止 VPN 应用进程,确认没有泄漏。最后,在用户文档中把两种隐私模型分开写:无痕模式控制的是临时的浏览器状态,并不会对网络隐藏流量;而 VPN 承载流量,但不会清除登录状态或私有会话中提交的任何内容。