Android 上的 IKEv2 VPN 配置档属于用户,而不属于设备
隧道按用户和按调用包分别创建,因此手机上的那条记录可能并不是你的。查看设置之前,先确认归属。
在 Android 上,「内置 VPN」这个说法同时指两件并不相干的事:一件是平台替你运行的协议实现,另一件是厂商决定暴露出来的那套设置界面。它们不是同一个对象,也都不能保证隧道属于真正需要它的应用所属的那个用户。配置是在某个特定用户、由某个特定调用包完成的,之后的一切都继承这个上下文。先读懂归属链,再去读设置页。
平台替你搭了什么
从 Android 11 起,平台通过 VpnManager 提供 VPN 配置能力。一个获得批准的应用描述一份 IKEv2 配置档,由平台自己完成 IPsec 与 IKE 交换,所以应用从不接触数据包。这正是它与自建 VPN 应用的本质区别——后者自行实现 VpnService,在自己的进程里搭建隧道。
val profile = Ikev2VpnProfile.Builder(serverAddress, identity)
.setAuthUsernamePassword(user, secret, rootCa)
.build()
getSystemService(VpnManager::class.java).provisionVpnProfile(profile)上面这段调用只是形态示意,不是可以直接粘贴的代码:构造器有多种认证重载,随 API 级别不同而不同,配置档还携带服务器证书的信任材料。对归属这个问题来说,关键点比语法要窄得多。
这个设计带来两个性质。配置档是数据,因此可以由管理员下发,而不必手工敲进去。隧道是平台网络,因此应用看到的是一条带 VPN 传输的网络,而不是别的应用的套接字。
归属先看用户,再看包
Android 的多用户是真正的隔离,不是一个标签。工作资料是一个独立的用户,有自己的已安装包、自己的应用数据、自己的 keystore、自己的信任锚。同一个产品在两处安装,就是两个用户 ID 不同的实例,即便启动器图标和产品名称一模一样。
配置是记在该用户下那个调用包的名下的。所以跑在个人资料里的配置应用创建的是个人隧道,工作资料里的工作应用完全看不到它。反过来同样成立,这也正是为什么没有明确策略说明时,工作资料的隧道对个人应用是不可见的。
设备策略控制器可以代表受管资料进行配置,这就解释了办公室里没人记得创建过的那条记录。如果你在审计一台手机,那条灰掉的配置档多半就归它,而把它当作实验对象删掉并不是你该做的事。
数据包移动之前的三道门
第一道门是权限:管理层必须允许使用 VPN,有些部署还会同时限制类型而不只是是否允许。第二道是身份:客户端证书或凭据必须存在于拥有该配置档的那个用户里,因为导入个人 keystore 的证书对工作资料不可见。
第三道是同意与策略:用户通常在首次使用时批准连接,管理员还可以把它固定为始终开启,并可选启用锁定,使流量无法绕过隧道。这些设置决定隧道断开时会发生什么,而这恰恰是用户能感知到的部分。
范围是三者交汇之处。配置档决定网关和身份,而策略层决定哪些应用可以使用这条隧道、流量是否被强制走隧道。因此两台网关地址完全相同的手机,行为可能天差地别。
为什么个人连接覆盖不到工作应用
工作应用产生的流量发生在工作用户里,适用的网络策略也是那个用户的策略。在个人资料中建立的隧道不会承载它,指示图标看起来再健康也没用。仅凭这一条,就能解释大多数「用户坚称 VPN 已连接、某个应用却连不上内网服务」的报告。
域名解析遵循同一条边界。私有 DNS 后缀和信任锚是按用户划分的,因此在个人资料里能解析的内网主机名,在工作资料里可能失败,即使隧道看起来等价,因为解析器策略和 CA 存储是两个不同的对象。
要在真实注册环境里验证,而不是实验室手机
在设备上,先确认这条记录的归属:属于哪个用户、哪个包,配置档是来自平台配置器,还是来自自行实现 VPN 服务的应用。然后再确认配置档期望的身份,确实存在于那个用户的 keystore 中。
要测生命周期,而不只是连接。登录、解锁凭据存储、让工作资料在晚上暂停,再把它唤起,看隧道能否在无需手动点击的情况下恢复。下班后关闭的工作资料会把隧道一并带走,这是预期行为而非故障,前提是预期中的不可用状态已被写明。
最后,拿工作应用本身去访问一个已知的内网端点,并与个人资料的结果对比。两者允许不同;不允许不同的是归属模型。如果隧道属于错误的用户,重签多少次证书都无济于事。
不要靠启动隐藏的设置 Activity 去翻出菜单来强行解决,也不要在资料之间复制私钥来让连接「看起来能用」。VpnManager 参考文档写明了调用方被允许配置什么;如果获批的路径不包含你的场景,那是管理员的决定,而不是设置问题。