被排除的应用仍在线?应用规则与路由规则各管一段
把 Android 应用从 VPN 中排除并不会拦截它。允许列表、UID 策略规则和下发的路由各自决定路径的不同环节。
排除一个包是路由指令,不是拦截,所以 VPN 连接后照常加载的页面属于预期结果,而不是故障证据。排除只是把应用从隧道中移出,并没有封禁它,流量改由普通网络承载。这里有两套彼此独立的策略相遇:一套选择进程,另一套选择目标。按这个顺序阅读,就能解释为什么“已连接”图标和正常工作的应用并不矛盾。
为什么排除之后网络依然畅通
VpnService.Builder 只能接受允许列表或排除列表之一,不可能两者并存。只有允许列表中的包才会走隧道。排除列表则相反:除列表中的应用外,其余应用都使用隧道,列表中的应用保留物理路径。允许列表下未被列入的应用同样在隧道之外,因此在这种模式下,对大多数应用来说“被排除”才是常态。
因此,本地排除条目是一项路由决策,而不是防火墙规则。如果需求是阻止某个应用访问某台主机,就必须让隧道覆盖它,并由服务器或本地策略拒绝该流量。把应用从隧道中移除,只改变了由哪个接口承载它的数据包。
Play Store 是常见的刻意示例。排除商店包后,当 VPN 出口影响传输时,下载可以改走底层连接;这也说明排除意味着绕过,而不是封禁。
一个配置文件,只能有一种列表
两个调用同时使用会让构建器抛出异常,所以眼前的配置要么是允许列表,要么是排除列表。仅凭这一点,就能确定后续每一项观察该怎么读。先查代码或托管配置,再看路由表,因为列表决定了其余输出的含义。混合型配置并不是可能存在的状态。
这一区别也能预测新安装应用的行为。在允许列表下,它一直待在隧道外,直到有人把它加进去;在排除列表下,它默认加入隧道。
托管配置用不同的键名表达同一选择,有些控制台把它称作“按应用 VPN 模式”而不是列表。行为最终仍然归结为这两个调用,所以要顺着设置回溯到包列表,而不是相信标签。如果标签与列表不一致,以列表为准。
沿着 UID 规则找到隧道的路由表
Android 为每个应用分配独立的 Linux 用户 ID,VPN 服务会安装与 UID 区间匹配的策略规则。匹配的流量被送入一张路由表,其默认路由指向 tun 设备。表号按 VPN 实例分配,因此重连或创建第二个 VPN 配置后会发生变化。
规则输出就是地图。每一行给出一个查找表,那张表里保存着决定匹配数据包是否进入隧道的默认路由。
adb shell ip rule show
adb shell ip route show table all
adb shell cmd package list packages -U | grep <package>
先运行第三条命令取得 UID,再找到区间包含它的规则。该规则指向一个表号,用这个表号执行 ip route show table <n> 就能看出是否存在经过 tun 设备的默认路由。如果表里没有默认路由,说明应用已被选中却没有可用路径,这属于另一种故障。
这些命令需要可调试的版本,或者允许 adb shell 读取网络状态的设备。量产手机上包管理器仍会应答,但路由检查可能被拒绝,所以能记录多少就记录多少,其余部分视为未验证。
路由声明的是前缀,不是应用
Builder.addRoute 为隧道声明目标前缀。只添加 10.0.0.0/8 的配置会把该前缀送入隧道,其余目标仍走物理路径。这是普通的分流隧道,与规则选中了哪些应用无关。
服务器下发的配置遵循同一模型。逐条推送的路由会添加前缀,而 redirect-gateway 指令会替换默认路由,为隧道覆盖的应用捕获全部流量。删掉该指令,就可能在不改动设备的前提下,把一条正常工作的隧道缩小到只剩一个子网。
网关或度量限定符异常的推送路由也可能安装失败。优先使用普通前缀形式,把转发和 NAT 交给服务器处理。
改设置之前,先看清交集
隧道承载的是“被覆盖的进程”与“被声明的目标”的交集。被覆盖的应用若请求已发布前缀之外的地址,就会从物理网络出去;被排除的包即便请求这些前缀之内的地址,也依然留在外面。两种情况都不是缺陷,而是两套策略各司其职。
按应用 VPN 也可能让关联应用失去连接。当 on-demand 关闭且隧道断开时,应用仍绑定在该按应用 VPN 上,没有回退路径,看起来像应用坏了,其实是策略状态。开启 on-demand,或通过管理端解除关联,都能改变这一点。
| Layer | What it selects | Where to look | Typical mismatch |
|---|---|---|---|
| Package list | Processes | Builder calls or managed configuration | App excluded but expected to be blocked |
| UID policy rule | Which table a UID uses | ip rule show | Tunnel icon on, app still on Wi-Fi |
| Routing table | Destinations for matched traffic | ip route show table <n> | Covered app reaches only one subnet |
| Server push | Prefixes offered to the client | Server route configuration | Tunnel shrinks after a server edit |
一次只诊断一个维度。先确认是允许还是排除,其次读取已安装的 UID 规则和路由表,最后用同一个目标测试第二个应用。第二个应用能在一次请求里把进程选择与目标选择区分开。
把结果记成“应用、目标、表号”。下一次改动要保住的正是这组三元组。