规则很简单:只在连接办公室 Wi-Fi 时过滤流量。它维持了几个月,然后某天早上,手机停在同一 SSID 上、钥匙图标也还在,过滤却已经停了。网络本身没有任何变化;变化的是应用还能不能看到网络名。在 Android 上,「我在哪个 Wi-Fi」和「拦下这段流量」是两件不同的事,而把两者混为一谈的防火墙,恰恰在网络名取不到时放开了限制。

症状:身份消失,策略随之失效

SSID 规则先读一个值,再选择分支。当读取没有返回有用的结果时,代码总得做点什么,而常见的默认做法是「不是预期网络,那就不过滤」。这个默认把权限问题变成了流量泄漏。钥匙图标仍然显示,因为 VPN 服务还在运行;它只是在执行另一条分支。

要看事件顺序,而不是看网络。拒绝权限弹窗、把 SSID 访问挪到另一项权限之后的系统更新、关闭定位服务,或工作资料策略变更,都可能在手机仍停在同一个接入点时产生同样的结果。

为什么网络名会变成未知

WifiInfo.getSSID() 返回当前网络的服务集标识,而 Android 文档说明:对位置敏感字段的访问取决于权限。访问不可用时,这个调用返回 UNKNOWN_SSID,而不是大声报错。因此空值或未知值并不能证明手机离开了 Wi-Fi;它证明的是应用无法对网络分类。

两项检查可以区分这两种情况。先确认手机已关联并查看系统报告什么,再确认应用在此 Android 版本上确实持有它需要的权限:

adb shell cmd wifi status
adb shell dumpsys package <your.app> | grep -i -E "ACCESS_FINE_LOCATION|NEARBY_WIFI_DEVICES"

如果状态行显示已连接,而应用看到的是未知名称,那是权限或 API 层面的问题,不是 Wi-Fi 问题。在改动防火墙规则之前先修好它,因为下游每条规则都是拿应用读不到的值去评估的。

过滤到底在哪里执行

本地 Android 防火墙通常使用 VpnService 模型,而不把流量送往远程服务器:它接管虚拟接口,套用自己的规则,并转发允许的流量。平台每个用户资料只允许一个活动的 VPN 服务,第二个会替换第一个,而不是叠加。因此单独的防火墙应用和单独的工作 VPN 不构成两级链条;其中一个会胜出。

这对 SSID 规则很关键,因为执行点和观察点是不同的组件。VpnService 决定什么离开设备;Wi-Fi API 只提供一个标签。应用可以正确执行、却错误分类,而这正是本文所说的故障。

给未知状态单独的策略

把网络身份当作三种状态,并在规则上线前把每一种的策略写下来。第三种状态最容易被忘掉,而攻击者或错误配置恰恰会把你放进那一种。

ObservationPolicy to specify
Expected SSID is readableThe explicit filtering policy you designed
A different SSID is readableThe default policy for untrusted networks
Unknown, changing or unreadableFail closed until classification is reliable

失败时保持关闭,意味着未知状态继承最严格的合理策略,而不是最方便的那一个。如果这会让手机在新网络上无法使用,正确的做法是刻意而可见的例外,而不是藏在空值检查里的悄悄信任。

熟悉的 SSID 只是标签,不是网络运营者的证明。任何人都能架一个用办公室名字的接入点,所以名称匹配绝不应是敏感会话与不可信网段之间唯一的东西。

测试状态转换,而不是稳态

稳态测试会在你搭规则的那个网络里通过。故障藏在转换里:权限被拒、Wi-Fi 切到蜂窝、从一个 SSID 换到另一个、VPN 服务重启,以及另一个 VPN 应用接管接口。对每一种都要决定:已有连接是否重新评估,状态未知时受保护流量是否仍然被阻断。

把结果写成三句话:观察到的条件、执行决定的组件,以及刻意设计的退路。三者缺一,这条规则就还不是策略,只是一个在办公室网络里碰巧可用的猜测。

相关文章