IOS-NetWorkExtension

技术文档网 2021-06-30
NetworkExtension

NetworkExtension是苹果提供的用于配置 VPN 和定制、扩展核心网络功能的框架。NE 框架提供了可用于定制、扩展 iOS 和 MacOS 系统的核心网络功能的 API。

Network Extension 最早出现在 iOS 8,不过那个版本不支持虚拟网卡,只能简单调用 iOS 系统自带的 IPSec 和 IKEv2 协议的 VPN。在 iOS 9 中,开发者可以用 NETunnelProvider 扩展核心网络层,从而实现非标准化的私密VPN技术。最重要的两个类是 NETunnelProviderManagerNEPacketTunnelProvider

初始化项目
  • 新建项目
  • 建立普通 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 做四种操作。

  1. 创建 vpn 配置
    fileprivate func createProviderManager() -> NETunnelProviderManager {
     let manager = NETunnelProviderManager()
     manager.protocolConfiguration = NETunnelProviderProtocol()
     return manager
    }
    
  2. 保存 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 ```swift 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() } }

4. 监听并更新 vpn 状态
```swift
/// 添加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
    }
}
  1. 数据传递

我们需要在主程序中传递类似账号、密码、端口、加密方式等参数给我们的VPN组件。

  • 主程序写入
    let conf = ["port":1000,"method":"AES-256-CFB","password":"hello"]
    let providerProtocol = manager.protocolConfiguration as! NETunnelProviderProtocol
    providerProtocol.providerConfiguration = conf
    manager.protocolConfiguration = orignConf
    
  • target读取 ```swift 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`` 是其子类,并且以下两个方法必须实现
```swift
@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 的代码情况下:

  1. 构建并运行应用。
  2. 停止运行。
  3. Xcode 菜单中 Debug -> attach to process by PID or name,填入 PacketTunnel,然后 Attach

  1. 在手机上运行(不要通过 Xcode)应用,点击连接的时候,进入了断点。

相关文章

  1. 硅谷互联网公司的开发流程

    开发流程包括这么几个阶段: OKR 的设立; 主项目及其子项目的确立; 每个子项目的生命周期; 主项目的生命周期; 收尾、维护、复盘。 第一点,OKR 的设立 所有项目的起始,都应该从 Ro

  2. RESTful-表述性状态转移风格

    REST英文全拼:Representational State Transfer 面向资源编程 资源指的就是一类数据 产品表->就是产品资源 最重要的是如何表示一个资源 地址即

  3. 稳定性思考

    产品功能线 0-1: 当系统从无到有的时候,首要考虑的是研发效率,功能快速迭代,满足快速增长的业务需求 1-10 系统已经搭建起来,此时考虑的是系统的稳定性。 可用性:1.隔离:区分出核心和非核心功能

  4. Supervisor守护队列发邮件

    安装 CentOS: yum -y install supervisor Debien/Ubuntu适用:apt-get install supervisor 配置 修改主配置文件:vim /et

  5. 安装libsodium,让服务器支持chacha20等加密方式

    用chacha20加密方式需要安装libsodium 注意:libsodium从1.0.15开始就废弃了aes-128-ctr yum install wget m2crypto git libsod

随机推荐

  1. 硅谷互联网公司的开发流程

    开发流程包括这么几个阶段: OKR 的设立; 主项目及其子项目的确立; 每个子项目的生命周期; 主项目的生命周期; 收尾、维护、复盘。 第一点,OKR 的设立 所有项目的起始,都应该从 Ro

  2. RESTful-表述性状态转移风格

    REST英文全拼:Representational State Transfer 面向资源编程 资源指的就是一类数据 产品表->就是产品资源 最重要的是如何表示一个资源 地址即

  3. 稳定性思考

    产品功能线 0-1: 当系统从无到有的时候,首要考虑的是研发效率,功能快速迭代,满足快速增长的业务需求 1-10 系统已经搭建起来,此时考虑的是系统的稳定性。 可用性:1.隔离:区分出核心和非核心功能

  4. Supervisor守护队列发邮件

    安装 CentOS: yum -y install supervisor Debien/Ubuntu适用:apt-get install supervisor 配置 修改主配置文件:vim /et

  5. 安装libsodium,让服务器支持chacha20等加密方式

    用chacha20加密方式需要安装libsodium 注意:libsodium从1.0.15开始就废弃了aes-128-ctr yum install wget m2crypto git libsod