// // VPNManager.swift // sdlan // // Created by 安礼成 on 2024/1/17. // import Foundation import NetworkExtension import SwiftUI import Observation enum VPNManagerError: Error { case disconnected } // vpn管理类 @Observable class VPNManager { static let shared = VPNManager() private var manager: NETunnelProviderManager? private var statusObserver: NSObjectProtocol? var vpnStatus: VPNStatus = .disconnected var isConnected: Bool = false var vpnStatusStream: AsyncStream private var vpnStatusCont: AsyncStream.Continuation enum VPNStatus { case connected case disconnected } private init() { (self.vpnStatusStream, self.vpnStatusCont) = AsyncStream.makeStream(of: VPNStatus.self) } // 开启vpn func enableVpn(options: [String : NSObject]) async throws { NSLog("enable vpn with options: \(options)") let manager = try await loadAndCreateProviderManager() try await manager.loadFromPreferences() self.addVPNStatusObserver(manager) try manager.connection.startVPNTunnel(options: options) self.manager = manager } // 关闭vpn func disableVpn() async throws { guard let manager = self.manager else { return } try await manager.loadFromPreferences() manager.connection.stopVPNTunnel() self.manager = nil } func sendMessage(_ message: Data) async throws -> Data { guard let session = self.manager?.connection as? NETunnelProviderSession else { throw VPNManagerError.disconnected } guard session.status == .connected || session.status == .connecting else { throw VPNManagerError.disconnected } return try await withCheckedThrowingContinuation { continuation in do { try session.sendProviderMessage(message) { responseData in // 收到响应,恢复异步挂起点 continuation.resume(returning: responseData ?? Data()) } } catch { // 发送失败,抛出错误 continuation.resume(throwing: error) } } } // MARK: - Private Methods // 监控系统VPN的状态的变化 private func addVPNStatusObserver(_ manager: NETunnelProviderManager) { if let statusObserver { NotificationCenter.default.removeObserver(statusObserver) self.statusObserver = nil } self.statusObserver = NotificationCenter.default.addObserver(forName: .NEVPNStatusDidChange, object: manager.connection, queue: .main) {[weak self] _ in NSLog("status channge: \(manager.connection.status)") switch manager.connection.status { case .invalid, .disconnected, .disconnecting: self?.vpnStatusCont.yield(.disconnected) self?.vpnStatus = .disconnected self?.isConnected = false case .connecting, .connected, .reasserting: self?.vpnStatusCont.yield(.connected) self?.vpnStatus = .connected self?.isConnected = true @unknown default: self?.vpnStatusCont.yield(.disconnected) self?.vpnStatus = .disconnected self?.isConnected = false } } } // MARK: - Private Methods // 加载或者创建相关的配置 private func loadAndCreateProviderManager() async throws -> NETunnelProviderManager { let managers = try await NETunnelProviderManager.loadAllFromPreferences() let manager = managers.first ?? NETunnelProviderManager() manager.localizedDescription = "punchnetmac" manager.isEnabled = true // 设置相关参数,在PacketTunnel中可以用 let protocolConfiguration = NETunnelProviderProtocol() protocolConfiguration.serverAddress = "punchnetmac" protocolConfiguration.providerConfiguration = [String:AnyObject]() protocolConfiguration.providerBundleIdentifier = "com.jihe.punchnetmac.tun" manager.protocolConfiguration = protocolConfiguration manager.isOnDemandEnabled = false manager.isEnabled = true try await manager.saveToPreferences() return manager } deinit { if let statusObserver { NotificationCenter.default.removeObserver(statusObserver) } } }