IOS NetWorkExtension - niancan/doc GitHub Wiki
NetworkExtension
NetworkExtension是苹果提供的用于配置 VPN 和定制、扩展核心网络功能的框架。NE 框架提供了可用于定制、扩展 iOS 和 MacOS 系统的核心网络功能的 API。
Network Extension 最早出现在 iOS 8,不过那个版本不支持虚拟网卡,只能简单调用 iOS 系统自带的 IPSec 和 IKEv2 协议的 VPN。在 iOS 9 中,开发者可以用 NETunnelProvider 扩展核心网络层,从而实现非标准化的私密VPN技术。最重要的两个类是 NETunnelProviderManager
和 NEPacketTunnelProvider
。
初始化项目
- 新建项目
- 建立普通 Swift 项目 Dsocket.
- Network Extension 无法在模拟器上调试。同时,你得有开发者账号,用来申请相关 Capabilities.
- 新建 PacketTunnel
- 新建 Target,选择 Network Extension。
然后选择 Provider Type 为 PacketTunnel。
如果 containing app 要与 extension 共享数据,则必须要开启 App Groups。
Personal VPN 和 Network Extensions(App Proxy、Content Filter、Packet Tunnel)也当然要开启。
代码
NETunnelProviderManager
它和 vpn 设置是一一对应关系。如果 App 有两个 vpn 设置,我们通过代码就能得到两个 NETunnelProviderManager
实例。我们要通过代码,对 NETunnelProviderManager
做四种操作。
- 创建 vpn 配置
fileprivate func createProviderManager() -> NETunnelProviderManager {
let manager = NETunnelProviderManager()
manager.protocolConfiguration = NETunnelProviderProtocol()
return manager
}
- 保存 vpn 配置
manager.saveToPreferences {
if let error = $0 {
complete(nil, error)
} else {
manager.loadFromPreferences(completionHandler: { (error) -> Void in
if let error = error {
complete(nil, error)
} else {
complete(manager, nil)
}
})
}
}
这段代码执行时会请求用户的授权,允许之后会添加一份 vpn 的配置 3. 开启和关闭 vpn
fileprivate func startVPNWithOptions(_ options: [String : NSObject]?, complete: ((NETunnelProviderManager?, Error?) -> Void)? = nil) {
// Load provider
loadAndCreateProviderManager { (manager, error) -> Void in
if let error = error {
complete?(nil, error)
} else {
guard let manager = manager else {
complete?(nil, ManagerError.invalidProvider)
return
}
if manager.connection.status == .disconnected || manager.connection.status == .invalid {
do {
try manager.connection.startVPNTunnel(options: options)
self.addVPNStatusObserver()
complete?(manager, nil)
}catch {
complete?(nil, error)
}
} else {
self.addVPNStatusObserver()
complete?(manager, nil)
}
}
}
}
public func stopVPN() {
// Stop provider
loadProviderManager {
guard let manager = $0 else {
return
}
manager.connection.stopVPNTunnel()
}
}
- 监听并更新 vpn 状态
/// 添加vpn的状态的监听
func addVPNStatusObserver() {
guard !observerDidAdd else {
return
}
loadProviderManager {
if let manager = $0 {
self.observerDidAdd = true
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: manager.connection, queue: OperationQueue.main, using: { [unowned self] (notification) -> Void in
self.updateVPNStatus(manager)
})
}
}
}
/// 更新vpn的连接状态
///
/// - Parameter manager: NEVPNManager
func updateVPNStatus(_ manager: NEVPNManager) {
switch manager.connection.status {
case .connected:
self.vpnStatus = .on
case .connecting, .reasserting:
self.vpnStatus = .connecting
case .disconnecting:
self.vpnStatus = .disconnecting
case .disconnected, .invalid:
self.vpnStatus = .off
}
}
- 数据传递
我们需要在主程序中传递类似账号、密码、端口、加密方式等参数给我们的VPN组件。
- 主程序写入
let conf = ["port":1000,"method":"AES-256-CFB","password":"hello"]
let providerProtocol = manager.protocolConfiguration as! NETunnelProviderProtocol
providerProtocol.providerConfiguration = conf
manager.protocolConfiguration = orignConf
- target读取
public var protocolConfiguration: NEVPNProtocol { get }
guard let conf = (protocolConfiguration as! NETunnelProviderProtocol).providerConfiguration else{
NSLog("[ERROR] No ProtocolConfiguration Found")
exit(EXIT_FAILURE)
}
let address = conf["address"] as! String
let port = conf["port"] as! Int
NEPacketTunnelProvider
真正的 vpn 核心代码。项目中 PacketTunnelProvider
是其子类,并且以下两个方法必须实现
@available(iOS 9.0, *)
open func startTunnel(options: [String : NSObject]? = nil, completionHandler: @escaping (Error?) -> Swift.Void)
@available(iOS 9.0, *)
open func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Swift.Void)
当 App 里的 NETunnelProviderManager
对象调用 startVPNWithOptions
时,控制流程就跳到 Extension 里的 startTunnel
方法。
startTunnel
有两个参数:options
是个字典,从 App 里传过来,具体有啥信息完全由开发者自己定义。completionHandler
是个闭包回调,由系统提供。我们可以将其保存到某个变量,待 vpn 启动完成时主动调用。
stopTunnel
也有两个参数:reason 表示 vpn 被关闭的理由。iOS 预先定义了一组 NEProviderStopReason
常数,但实际应用时,reason
基本不用。completionHandler
的用法同 startTunnel
第二个参数一样,不再重复。
调试 Network Extension
在已经完成 PacketTunnel 的代码情况下:
- 构建并运行应用。
- 停止运行。
- Xcode 菜单中
Debug
->attach to process by PID or name
,填入PacketTunnel
,然后Attach
。
- 在手机上运行(不要通过 Xcode)应用,点击连接的时候,进入了断点。