diff --git a/Tun/App/PacketTunnelProvider.swift b/Tun/App/PacketTunnelProvider.swift index 172c904..685feba 100644 --- a/Tun/App/PacketTunnelProvider.swift +++ b/Tun/App/PacketTunnelProvider.swift @@ -14,36 +14,32 @@ enum TunnelError: Error { } class PacketTunnelProvider: NEPacketTunnelProvider { - private var runtimeEnv: SDLRuntimeEnvironment? + private enum RuntimeState { + case idle + case starting + case running + case stopping + } + + private let runtimeLock = NSLock() + private var runtimeState: RuntimeState = .idle + private var config: SDLConfiguration? + private var rsaCipher: CCRSACipher? + private var contextActor: SDLContextActor? + private var startCompletionHandler: ((Error?) -> Void)? override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { - // 如果当前在运行状态,不允许重复请求 - guard self.runtimeEnv == nil else { - completionHandler(TunnelError.invalidContext) - return - } - guard let options, let config = SDLConfiguration.parse(options: options) else { completionHandler(TunnelError.invalidConfiguration) return } - - if self.runtimeEnv == nil { - let rsaCipher = try! CCRSACipher(keySize: 1024) - self.runtimeEnv = SDLRuntimeEnvironment(config: config, rsaCipher: rsaCipher, provider: self) - self.runtimeEnv?.run() - } - - self.runtimeEnv?.submitCommand(command: .start(completion: { err in - completionHandler(err) - })) + + let rsaCipher = try! CCRSACipher(keySize: 1024) + self.startContext(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler) } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - // Add code here to start the process of stopping the tunnel. - self.runtimeEnv?.submitCommand(command: .stop(completion: { - completionHandler() - })) + self.stopContext(clearRuntimeConfiguration: true, completionHandler: completionHandler) } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { @@ -65,29 +61,25 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } override func sleep(completionHandler: @escaping () -> Void) { - // Add code here to get ready to sleep. - guard let runtimeEnv else { - completionHandler() - return - } - - runtimeEnv.submitCommand(command: .stop(completion: { + self.stopContext(clearRuntimeConfiguration: false) { SDLLogger.log("[PacketTunnelProvider] sleep") completionHandler() - })) + } } override func wake() { SDLLogger.log("[PacketTunnelProvider] wake up!!!!!!!") - // Add code here to wake up. - // 重新启动 - self.runtimeEnv?.submitCommand(command: .start(completion: { err in - SDLLogger.log("[PacketTunnelProvider] wakeup and try start") - })) + self.startCachedContext { err in + if let err { + SDLLogger.log("[PacketTunnelProvider] wakeup start failed: \(err.localizedDescription)") + } else { + SDLLogger.log("[PacketTunnelProvider] wakeup and try start") + } + } } private func handleAppRequest(message: AppRequest) async throws -> Data? { - guard let contextActor = self.runtimeEnv?.getContextActor() else { + guard let contextActor = self.currentContextActor() else { throw TunnelError.invalidContext } @@ -117,3 +109,126 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } } + +private extension PacketTunnelProvider { + + func startCachedContext(completionHandler: @escaping (Error?) -> Void) { + self.runtimeLock.lock() + let config = self.config + let rsaCipher = self.rsaCipher + self.runtimeLock.unlock() + + guard let config, let rsaCipher else { + completionHandler(TunnelError.invalidConfiguration) + return + } + + self.startContext(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler) + } + + func startContext(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) { + self.runtimeLock.lock() + switch self.runtimeState { + case .idle: + SDLTunnelAppNotifier.shared.clear() + + let contextActor = SDLContextActor( + provider: self, + config: config, + rsaCipher: rsaCipher + ) + + self.config = config + self.rsaCipher = rsaCipher + self.contextActor = contextActor + self.startCompletionHandler = completionHandler + self.runtimeState = .starting + self.runtimeLock.unlock() + + Task { + do { + try await contextActor.start() + self.finishContextStart(contextActor, error: nil) + } catch { + self.finishContextStart(contextActor, error: error) + } + } + + case .starting, .running, .stopping: + self.runtimeLock.unlock() + completionHandler(TunnelError.invalidContext) + } + } + + func stopContext(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) { + self.runtimeLock.lock() + let contextActor = self.contextActor + let startCompletionHandler = self.startCompletionHandler + + guard contextActor != nil else { + self.runtimeState = .idle + self.startCompletionHandler = nil + if clearRuntimeConfiguration { + self.config = nil + self.rsaCipher = nil + } + self.runtimeLock.unlock() + startCompletionHandler?(TunnelError.invalidContext) + completionHandler() + return + } + + self.contextActor = nil + self.startCompletionHandler = nil + self.runtimeState = .stopping + if clearRuntimeConfiguration { + self.config = nil + self.rsaCipher = nil + } + self.runtimeLock.unlock() + + startCompletionHandler?(TunnelError.invalidContext) + + Task { + await contextActor?.stop() + self.markContextStopped() + completionHandler() + } + } + + func currentContextActor() -> SDLContextActor? { + self.runtimeLock.lock() + let contextActor = self.contextActor + self.runtimeLock.unlock() + return contextActor + } + + func finishContextStart(_ contextActor: SDLContextActor, error: Error?) { + self.runtimeLock.lock() + guard self.contextActor === contextActor else { + self.runtimeLock.unlock() + return + } + + let startCompletionHandler = self.startCompletionHandler + self.startCompletionHandler = nil + + if let error { + self.contextActor = nil + self.runtimeState = .idle + } else { + self.runtimeState = .running + } + self.runtimeLock.unlock() + + startCompletionHandler?(error) + } + + func markContextStopped() { + self.runtimeLock.lock() + if self.contextActor == nil { + self.runtimeState = .idle + } + self.runtimeLock.unlock() + } +} diff --git a/Tun/App/SDLRuntimeEnvironment.swift b/Tun/App/SDLRuntimeEnvironment.swift deleted file mode 100644 index 482e12e..0000000 --- a/Tun/App/SDLRuntimeEnvironment.swift +++ /dev/null @@ -1,112 +0,0 @@ -import Foundation - -enum SDLRuntimeEnvironmentCommand { - case start(completion: @Sendable (Error?) -> Void) - case stop(completion: @Sendable () -> Void) -} - -final class SDLRuntimeEnvironment { - private enum State { - case idle - case running - } - - private var state: State = .idle - private var contextActor: SDLContextActor? - - private var config: SDLConfiguration - private let rsaCipher: CCRSACipher - private let provider: PacketTunnelProvider - - private let commandStream: AsyncStream - private let commandCont: AsyncStream.Continuation - - private var commandTask: Task? - - init(config: SDLConfiguration, rsaCipher: CCRSACipher, provider: PacketTunnelProvider) { - self.config = config - self.rsaCipher = rsaCipher - self.provider = provider - - let pair = AsyncStream.makeStream(of: SDLRuntimeEnvironmentCommand.self) - self.commandStream = pair.stream - self.commandCont = pair.continuation - } - - func submitCommand(command: SDLRuntimeEnvironmentCommand) { - self.commandCont.yield(command) - } - - func getContextActor() -> SDLContextActor? { - self.contextActor - } - - func run() { - let stream = self.commandStream - - self.commandTask = Task { [weak self] in - for await command in stream { - guard let self else { - break - } - - await self.handle(command) - } - } - } - - private func handle(_ command: SDLRuntimeEnvironmentCommand) async { - switch command { - case .start(let handler): - do { - try await self.startCommand() - handler(nil) - } catch { - handler(error) - } - - case .stop(let handler): - await self.stopCommand() - handler() - } - } - - private func startCommand() async throws { - switch self.state { - case .idle: - SDLTunnelAppNotifier.shared.clear() - - let contextActor = SDLContextActor( - provider: provider, - config: config, - rsaCipher: self.rsaCipher - ) - - self.contextActor = contextActor - try await contextActor.start() - self.state = .running - case .running: - SDLLogger.log("[SDLRuntimeEnvironment] is running, ignore start command") - } - } - - private func stopCommand() async { - switch self.state { - case .idle: - SDLLogger.log("[SDLRuntimeEnvironment] is idle, ignore stop command") - - case .running: - let contextActor = self.contextActor - self.contextActor = nil - self.state = .idle - - await contextActor?.stop() - } - } - - deinit { - self.commandCont.finish() - self.commandTask?.cancel() - self.commandTask = nil - } -} diff --git a/Tun/Context/SDLContextActor.swift b/Tun/Context/SDLContextActor.swift index 40cf3b4..17c6c01 100644 --- a/Tun/Context/SDLContextActor.swift +++ b/Tun/Context/SDLContextActor.swift @@ -171,6 +171,7 @@ actor SDLContextActor { self.rootTask = nil rootTask?.cancel() + await self.readySignal.fail(CancellationError()) _ = try? await rootTask?.value await self.cleanupRoot()