为什么你的 iPhone 应用无法打开或关闭系统 VPN
公开应用无法切换系统 VPN、读取其状态或强制建立连接。On Demand 规则、受监督设备上的 Always On 描述文件以及 MDM 是可选方案。
一位产品经理提出做一个打开公司 VPN 的按钮,这个需求听起来微不足道。在 iOS 上这并不是小功能;它位于公开契约之外,任何绕行方案要么过不了审核,要么在下个版本中失效。把需求放回平台真正允许的范围推演,设计基本就自己写出来了。应用可以拥有自己的 VPN 配置并启动它。应用无法触及系统开关,无法读取并非自己创建的描述文件的状态,也无法违背用户意愿让连接一直保持。
从用户想要的三种含义说起
先区分含义,再区分 API。有时用户想要的是自己应用的隧道处于连接状态,有时想要把「设置」里的开关打开某个别人安装的描述文件,有时想要的是在没有隧道时手机根本无法上网。这三者对应三种不同机制,只有第一种对普通应用开放。
第二种含义正是 Stack Overflow 上那些问题的来源,它没有受支持的答案。「设置」里的开关属于用户,在受管理设备上属于描述文件。没有任何公开 API 能列出 VPN 配置、报告系统隧道是否已建立,或切换它。
应用真正能创建什么
NetworkExtension 通过 NEVPNManager 让应用拥有自己的配置。凭借 Personal VPN 权限和用户同意,应用保存一份以自己名字出现在「设置」里的配置,并能启动或停止这一条连接。这是真实且可审核的能力,同时也是全部范围:管理器只读写它自己创建的内容。
权限不是自动获得的。你需要向 Apple 申请,并说明应用为什么需要隧道;而一个唯一目标是打开别人描述文件的客户端,拿不出好理由。数据包隧道提供方遵循同样的规则,只是类不同:NETunnelProviderManager 拥有应用自己安装的、基于提供方的配置。两条路都通不到 MDM 下发的描述文件。
同意在保存时同样生效。写入配置会询问用户,而这个询问不是应用可以为了省事而压掉的。
读取状态撞上同一堵墙
状态看起来比控制更友好,但同样封闭。NEVPNStatus 会为应用自己的配置报告无效、已断开、正在连接、已连接、正在重新确认或正在断开,且每次变化都会触发通知。它对管理描述文件安装的隧道一无所知,而那正是服务台真正想看到的状态。
设计评审中常把网络路径监测和接口变化当作替代方案。它们描述的是应用流量所走的路径,这条路径会因为与 VPN 状态无关的原因而变化,把它们当作 VPN 读数会得到一个两个方向都在说谎的界面。
On Demand 和 Always On 才是真正的杠杆
受支持的杠杆位于描述文件里,而不是应用里。On Demand 规则告诉系统何时连接:一份有序的条件与动作列表,由操作系统评估,而不是你的代码。一条在任何接口上都连接的规则会营造「始终在线」的感觉,却不会给应用任何控制权。
Always On VPN 是强制执行机制,并且与监督绑定。在受监督设备上,MDM 安装一份让隧道保持建立的描述文件;在未受监督的个人手机上,同样的键并不会产生同样的行为。这种不对称是刻意的,任何载荷键组合都无法消除它。当需求真的是「VPN 必须开着」时,需求其实是「设备必须被管理」。
两者之间还有更弱的近亲:一条在用户断开后重新连接的规则。它会在规则下次被评估时重连,这并不等于立刻,故意断开的用户会注意到差别。与其承诺一把锁,不如围绕这个空隙设计界面。
交付受支持的路径
在写任何代码之前,四个问题就能定下机制:设备归谁所有、是否受监督、由哪个客户端终结隧道,以及描述文件从哪来。其中任何一项不清楚,这个功能需求就还没准备好估算。
去戳系统 VPN 的私有 API 能过演示,却过不了审核;若用别的方式上线,它会在下一个 iOS 版本上失效。为一个小小的开关做这种交易,没有哪个版本值得。
管理遥测是另一条通道:它报告描述文件是否安装、设备是否合规,这属于报告,而不是逐应用的控制面。每次更新后都要在真机上测试流程,因为受支持的行为活在操作系统里,而操作系统会变。