美文网首页
NetworkExtension2-Client开发

NetworkExtension2-Client开发

作者: 梦即是幻 | 来源:发表于2020-08-27 18:31 被阅读0次

环境

  • Xcode 11.6
  • iOS 13
  • MacOS 10.15

导航

1-总览

2-Client开发

3-Tunnel开发

4-Server开发

5-App和Extension通信

完整代码在此,熟悉的小伙伴可以直接试试。

第一步,先来搞定主App相关界面和功能。

首先,创建工程,大概是这样,一共3个target:

YYVPN
├── YYVPNLib  Client和Tunnel共享的代码
├── Client      主App,界面相关
├── Tunnel      Network Extension,获取流量的地方

Client和Tunnel需要开启App Groups和Network Extension的Packt Tunnel,如下图:

需要付费开发者账号

image

因为最后要连上自己的Server,所以得配置IP和端口,加上开启/关闭流量拦截等功能,界面如下:

image

比较简单,用Swift UI写着也很方便,代码如下:

ConfigView:

import SwiftUI

struct ConfigView: View {
    @ObservedObject var viewModel =
        ConfigViewModel(config: .init(hostname: "172.20.49.36", port: "8899"))

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Settings")) {
                    HStack(alignment: .center) {
                        Text("IP").font(.callout)
                        TextField("IP", text: $viewModel.config.hostname)
                            .multilineTextAlignment(.trailing)
                            .foregroundColor(.gray)
                    }
                    HStack(alignment: .center) {
                        Text("Port").font(.callout)
                        TextField("Port", text: $viewModel.config.port)
                            .multilineTextAlignment(.trailing)
                            .foregroundColor(.gray)
                    }
                }

                Section(header: Text("Status")) {
                    Text("Status: ") + Text(viewModel.status.rawValue)
                    if viewModel.status == .off || viewModel.status == .invalid {
                        Button(action: {
                            self.hideKeyboard()
                            self.viewModel.didTapStart()
                        }) {
                            Text("Start")
                        }
                    } else {
                        Button(action: {
                            self.hideKeyboard()
                            self.viewModel.didTapStop()
                        }) {
                            Text("Stop")
                        }
                    }
                }

                Section {
                    YYAlertButton(text: "Remove",
                                  title: "确定删除?",
                                  message: nil,
                                  confirm: {
                                      self.viewModel.didTapRemove()
                                  },
                                  cancel: nil)
                }
            }
            .navigationBarTitle("VPN Status")
        }
    }
}

ConfigViewModel:

使用Swift UI离不开Combine,之前写过一篇Combine最简流程源码解析,可以加深对Combine的理解。

import Foundation
import Combine
import YYVPNLib

final class ConfigViewModel: ObservableObject {
    @Published var config: YYVPNManager.Config
    @Published var status = YYVPNManager.Status.off
    
    init(config: YYVPNManager.Config) {
        self.config = config
        YYVPNManager.shared.statusDidChangeHandler = { [weak self] status in
            self?.status = status
        }
    }
    
    func didTapStart() {
        YYVPNManager.shared.start(with: config) { error in
            
        }
    }
    
    func didTapStop() {
        YYVPNManager.shared.stop()
    }
    
    func didTapRemove() {
        YYVPNManager.shared.removeFromPreferences { error in
            
        }
    }
}

YYVPNLib

一个单独的静态库,封装了管理VPN相关的逻辑,方便多个Target复用,核心方法都封装在YYVPNManager里。

详细的大家可以下载完整源码来看,这里简单说下流程:

  1. 要拦截流量,需要主App启动Network Extension进程,这通过调用NETunnelProviderManager对象tunnel的tunnel.connection.startVPNTunnel()方法。

  2. 而NETunnelProviderManager对象通过调用NETunnelProviderManager.loadAllFromPreferences获取,第一次肯定是nil,需要自己手动创建并保存,大概如下,有2个地方需要注意:

    let manager = NETunnelProviderManager()
    let proto = NETunnelProviderProtocol()
    /// providerBundleIdentifier必须是Network Extension的Target的Bundle ID
    proto.providerBundleIdentifier = config.bundleIDTunnel
    proto.serverAddress = "YYVPN"
    /// 如果要设置用户名和密码,passwordReference必须取keychain里面的值
    //proto.passwordReference = Data()
    manager.protocolConfiguration = proto
    manager.localizedDescription = "YYVPN"
    manager.isEnabled = true
    
    manager.saveToPreferences{ error in
     
    }
    
  3. NETunnelProviderManager.loadAllFromPreferences会读取本应用创建的所有VPN配置,保存在设置中,注意不要重复添加了。

参考链接

相关文章

网友评论

      本文标题:NetworkExtension2-Client开发

      本文链接:https://www.haomeiwen.com/subject/fhlhsktx.html