iOS VPN API:NEVPNManager、隧道提供程序还是配置描述文件?
按你必须使用的传输层选择 API,然后证明隧道确实承载流量:save-load-start 顺序、provider entitlement 和按需规则。
iOS VPN 这个名字对应三种不同的产品,它们并不共用同一套 API。Personal VPN 使用系统内置的 IPsec 和 IKEv2 传输。Custom tunnel 在网络扩展里运行你自己的协议。Configuration profile 可以配置其中任意一种,还能添加按需行为。按你必须使用的传输层来选择,比按哪个教程先编译通过来选择更快。
请按顺序执行下面的检查,因为每一步都假设前一步已经产生了有效的配置。
第 1 步:按传输层选择
如果服务器是 IKEv2 或 Cisco 风格的 IPsec,NEVPNManager 就够了,系统传输是免费的。如果服务器说的是 OpenVPN、WireGuard 或其他协议,NEVPNManager 帮不上忙:它没有接入第三方协议的钩子。这种情况需要 NETunnelProviderManager 搭配实现该协议的数据包隧道提供程序。Profile 只负责分发配置,无法添加操作系统本身不支持的传输。
PPTP 不是当前系统的目标。共享密钥 IPsec 仍然要求 remoteIdentifier 中的组名、保存密钥的 keychain 条目,以及服务器要求用户名时的扩展认证。每个缺失的部分都会表现为同一个通用配置错误,所以在调试其他任何东西之前,先把它们全部补齐。
第 2 步:遵守保存、加载、启动的顺序
NEVPNManager 先写入 preferences,再读回来,然后才启动。填写协议和 manager 对象,调用 saveToPreferences,调用 loadFromPreferences,然后在 load completion handler 中调用 startVPNTunnel。在加载返回之前就启动,会让本来正确的配置报出 configuration-invalid 错误,NEVPNErrorDomain code 1。
写法短到可以放在一个地方:
manager.protocolConfiguration = proto
manager.isEnabled = true
manager.saveToPreferences { }
manager.loadFromPreferences { manager.connection.startVPNTunnel() }startVPNTunnel 在请求被接受时返回,而不是在隧道建立时返回。观察 NEVPNStatusDidChange,并读取 connection.status 获取真实状态。在调用点就假定成功的应用,会在扩展仍在协商时报告 VPN 已连接。connecting 状态短暂出现是正常的;始终停留在该状态才是值得记录下来的故障。
第 3 步:为 provider 进程提供它需要的东西
Custom transport 运行在拥有自己的 bundle identifier 和 entitlement 的独立进程中。容器应用和 provider 在签名时都需要网络扩展 entitlement,App Store 之外的 Developer ID 构建同样必须把它放进 provisioning profile。缺少 entitlement 看起来像协议缺陷,因为扩展根本不会启动。
没有可以借用的公共 socket descriptor。NEPacketTunnelProvider 提供带有 read、write 和 cancel 的 NEPacketTunnelFlow,而伸手去够私有密钥路径的代码会在版本之间失效。应用进入后台后 provider 仍在运行,所以容器应用不需要为此启用后台模式。
第 4 步:按需是规则集合,不是开关
isOnDemandEnabled 只是武装规则;隧道何时建立由 NEOnDemandRuleConnect 及其同类决定。带探测 URL 的 evaluate-connection 规则,效果取决于那个 URL。如果探测在没有隧道时也有响应,规则就会断定私有网络已经可达,从而永不连接,所以要把它指向只有内网才会响应的地址。
Always-on 是另一个承诺。它需要受监管的设备和受管理的描述文件,而未受监管的用户随时可以移除描述文件。除非设备群受监管,否则不要交付依赖 always-on 的设计。
第 5 步:证明路径,而不是标签
扫描 tun、tap、ppp 或 ipsec 接口只能证明 VPN 已配置,不能证明你的请求走了它。NWPathMonitor 配合 usesInterfaceType(.other) 只能说明存在虚拟接口,viability handler 只能测试一台主机。两者都不能证明出口路径,所以要把它们与一个只有隧道才能访问的资源的请求结合起来。接口名称只是诊断辅助,绝不该成为应用逻辑分叉的关卡。
现场检查时,比较有隧道和没有隧道时的公网地址,并抓取一个仅内网可访问的页面。status 调用可能在 provider 协商期间就报告已连接,所以让资源结果来决定,把 status 当作辅助证据。