一部手机上的两个 VPN:选架构,而不是应用数量
切换、配置文件隔离和嵌套链路是三种不同的设计。按应用在同一时刻的需求来选择,然后检查 MTU 和路径。
使用两个 VPN 并不是一个请求。它可能意味着先后访问两个组织,把工作应用和个人应用分开,或者让一条隧道跑在另一条隧道里面。Android 以完全不同的方式处理这三种情况,而手机上安装的 VPN 应用数量并不是架构。把它们区分开来的问题很简单:同一个应用是否必须在同一时刻拥有两条路径?
三种架构,一个问题
如果答案是否,因为两个网络在不同时间使用,你要的是有意的切换。如果答案是否,因为应用本身是分开的,你要的是配置文件隔离,即每个应用位于拥有其隧道的那一个上下文里。如果答案是是,并且一条路径必须在物理上承载另一条,你要的是链路,而这是三者中唯一把顺序当作设计一部分的做法。
其余一切都由这个选择决定。切换有过渡窗口,隔离有策略边界,链路则有额外开销和依赖成本。选错不会干净地失败;它会产生一部看起来已连接、而应用却什么也访问不到的机器。
在一个用户上下文内切换
Android 只允许每个用户有一个处于活动状态的 VPN 服务。在同一上下文里启动第二个 VPN 应用会替换第一个,或者被直接拒绝;它从不会把两套路由拼在一起,更不会叠加两者的加密。在同一个用户内,两条隧道是先后顺序,而不是求和。
当访问确实是先后发生的时候,这种做法可行。给配置文件起名,让当前生效的那个一目了然;切换前把敏感的事情做完;并预期有一段两条路径都没有完全建立的短窗口。关闭浏览器标签页并不等于结束会话:标签页可以保留其账户状态,只是把下一个请求走新选的网络发出去,而这正是人们事后才注意到的泄漏。
切换给不了你的,是一台同时处在两个网络上的机器。如果某个应用需要两侧各给出一个私有地址,再多的客户端纪律也变不出来。
按配置文件隔离
受管理的工作配置文件是一个独立的用户,拥有自己的应用、密钥库和网络策略。工作邮件客户端可以使用组织的隧道,而个人浏览器使用个人的隧道,两者可以同时连接,因为它们不在同一个上下文里争抢那个位置。
隔离也正是边界。个人浏览器不会借用工作隧道,看不到工作的 DNS 后缀,也不会仅仅因为两个服务都显示已连接就能到达工作网络。当有人报告某个链接打不开时,先查清是应用的哪一份副本处理了它、哪个配置文件拥有那个账户,然后再去动两个 VPN 中的任何一个。
把边界当作固定的。为了绕开雇主的选路决定而新建第二个配置文件或删掉工作配置文件,破坏的正是让这套架构成立的那层隔离;在受管理的设备上,这通常不是配置调整,而是策略违规。
链路:顺序就是设计
链路是人们在说「两个同时用」时通常指的那种架构:手机的隧道走在路由器的隧道里。它可以工作,而限制是具体的。外层服务商必须允许一条通往手机 VPN 端点的可用路径,对于基于 UDP 的协议,这意味着外层服务不得阻断或限速那部分流量。
弄清谁能看到什么。外层服务商看到的是通往内层端点的连接。内层服务商从外层出口收到手机的流量,并把它继续转发出去。最终目的地通常看到的是内层服务的出口,链路沿线没有哪个服务商是完全看不见的;应用账户无论如何都能识别用户。
要预期实际开销。每一层都增加封装,因此有效 MTU 下降,某个对单条隧道本来没问题的数值现在会分片或卡住,直到把内层隧道的 MTU 保守地设小。两层也意味着两处依赖和两套保活消息;而一条基于 TCP 的内层隧道跑在有丢包的外层链路上时,会以看起来像服务器已经死掉的方式卡住。
最重要的是,链路并不把两个私有网络合并。路由、重叠的前缀、DNS 解析和访问规则仍然是各自独立的决定;路由器带一条 VPN、手机带另一条,并不会因此把两个地址空间都给到手机。
先检查设计,再去调试
用一句话把架构写下来,并拿失败的那个应用去对照。先后访问应当在任一时刻只显示一条隧道;配置文件隔离应当显示每个应用属于各自的用户;链路则应当显示内层隧道是建立在外层接口上,而不是物理接口上。
adb shell ip -br addr
adb shell ip route get 10.20.0.8
adb shell dumpsys connectivity | grep -i vpn如果路由查找给出的是物理接口,而某个应用却在期待隧道,那就是设计与应用上下文不一致,再装一个 VPN 应用也无法让它们调和。如果你需要的是策略细节而不是路由细节,受管理配置文件的文档涵盖了隔离规则。