主App已经OK,下面就来看看Network Extension中的流程。

默认模板代码如下:

class PacketTunnelProvider: NEPacketTunnelProvider {
    override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
        // Add code here to start the process of connecting the tunnel.
    override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
        // Add code here to start the process of stopping the tunnel.
        completionHandler()
    override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
        // Add code here to handle the message.
        if let handler = completionHandler {
            handler(messageData)
    override func sleep(completionHandler: @escaping () -> Void) {
        // Add code here to get ready to sleep.
        completionHandler()
    override func wake() {
        // Add code here to wake up.

简单说明:

  • startTunnel(options:,completionHandler:)方法:启动网络隧道,当主App调用startVPNTunnel()后执行;最后通过调用completionHandler(nil or error),完成建立隧道或由于错误而无法启动隧道。
  • stopTunnel(with reason:, completionHandler:)方法:停止网络隧道,当主App调用stopVPNTunnel()或其他原因停止网络隧道时候执行;如果想在PacketTunnelProvider内部停止,不能调用这个方法,应该调用cancelTunnelWithError()。
  • handleAppMessage(_ messageData: , completionHandler:)方法:处理主App发送过来的消息,主App可以通过let session = manager.connection as? NETunnelProviderSession,再调用session.sendProviderMessage(_ messageData: Data, responseHandler:)向tunnel发送数据,tunnel回调completionHandler返回数据。
  • sleep(completionHandler:)方法:当设备即将进入睡眠状态时,系统会调用此方法。
  • wake()方法:当设备从睡眠模式唤醒时,系统会调用此方法。
  • 我们这里只需关心startTunnel方法,其他默认即可,第一步是拿到配置信息:

    class PacketTunnelProvider: NEPacketTunnelProvider {
        private var pendingCompletion: ((Error?) -> Void)?
        private var config: YYVPNManager.Config!
        private var udpSession: NWUDPSession!
        private var observer: AnyObject?
        override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
            os_log(.default, log: .default, "Starting tunnel, options: %{public}@", "\(String(describing: options))")
                guard let proto = protocolConfiguration as? NETunnelProviderProtocol else {
                    throw NEVPNError(.configurationInvalid)
                config = try YYVPNManager.Config(proto: proto)
            } catch {
                os_log(.default, log: .default, "Get configuration failed: %{public}@", error.localizedDescription)
                completionHandler(error)
            os_log(.default, log: .default, "Get configuration: %{public}@", "\(String(describing: config))")
            pendingCompletion = completionHandler
            setupUDPSession()
    

    比较简单,这里要说明一下,扩展在另外一个进程,使用print是无法看到打印信息的,可以使用os_log,然后通过mac上的控制台app可以实时查看日志,如图:

    连接UDP Server

    继续之前最后2行代码:

    pendingCompletion = completionHandler
    setupUDPSession()
    

    先用pendingCompletion持有completionHandler,因为必须要在连上Server后才能配置tunnel,拦截流量。

    然后是setupUDPSession方法:

    private extension PacketTunnelProvider {
        func setupUDPSession() {
            let endPoint = NWHostEndpoint(hostname: config.hostname, port: config.port)
            udpSession = createUDPSession(to: endPoint, from: nil)
            observer = udpSession.observe(\.state, options: [.new]) { [weak self] session, _ in
                self?.udpSession(session, didUpdateState: session.state)
        func udpSession(_ session: NWUDPSession, didUpdateState state: NWUDPSessionState) {
            switch state {
            case .ready:
                setupTunnelNetworkSettings()
                localPacketsToServer()
            case .failed:
    						os_log(.default, log: .default, "Connet UDP Server failed")
                pendingCompletion?(NEVPNError(.connectionFailed))
                pendingCompletion = nil
            default:
                break
    
  • 创建createUDPSession,并监听状态
  • OK后调用setupTunnelNetworkSettings()和localPacketsToServer(),配置tunnel,拦截本地流量
  • 拦截本地IP包

    要能拦截流量,需要2步:

    首先,通过setTunnelNetworkSettings指定隧道的网络设置:开启一个虚拟网卡Tun接口,需要配置虚拟IP,DNS,代理设置,MTU和IP路由等信息。

    然后使用packetFlow.readPackets即可从虚拟网卡文件中读取IP数据包再发送给代理Server。

    tun虚拟网卡通过映射一个文件的方式从协议栈获取IP数据,或将数据写入协议栈,具体工作原理可以参考这个博客

    最简单的配置如下:

    private extension PacketTunnelProvider {
        func setupTunnelNetworkSettings() {
            let ip = "192.168.0.2"
            let subnet = "255.255.255.0"
            let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: config.hostname)
            /// 分配给TUN接口的IPv4地址和网络掩码
            let ipv4Settings = NEIPv4Settings(addresses: [ip], subnetMasks: [subnet])
            /// 指定哪些IPv4网络流量的路由将被路由到TUN接口
            ipv4Settings.includedRoutes = [NEIPv4Route.default()]
            settings.ipv4Settings = ipv4Settings
            setTunnelNetworkSettings(settings) { [weak self] error in
                self?.pendingCompletion?(error)
                if let error = error {
                    os_log(.default, log: .default, "setTunnelNetworkSettings error: %{public}@", error.localizedDescription)
                } else {
                    self?.remotePacketsToLocal()
        func localPacketsToServer() {
            os_log(.default, log: .default, "LocalPacketsToServer")
            packetFlow.readPackets { packets, _ in
                os_log(.default, log: .default, "readPackets")
                packets.forEach {
                    self.udpSession.writeDatagram($0) { error in
                        if let error = error {
                            os_log(.default, log: .default, "udpSession.writeDatagram error: %{public}@", "\(error)")
                self.localPacketsToServer()
    

    从代理Server获取Response

    监听udpSession数据回调。

    使用packetFlow.writePackets将数据写入虚拟网卡,再通过协议栈通知应用层获取数据。

    func remotePacketsToLocal() {
        udpSession.setReadHandler({ [weak self] packets, _ in
            if let packets = packets {
                packets.forEach {
                    self?.packetFlow.writePackets([$0], withProtocols: [AF_INET as NSNumber])
        }, maxDatagrams: .max)
    

    Network Extension中的代码也搞定了,不过这个时候点击Start是连不上的,因为我们的代理服务器还没有开启~~

    Let's Build a VPN Protocol系列

    网络虚拟化技术(二): TUN/TAP MACVLAN MACVTAP

    分类:
    iOS
    标签: