From 49750099ea3abb768435f47a5d1585def5220c3f Mon Sep 17 00:00:00 2001 From: anlicheng <244108715@qq.com> Date: Sun, 21 Jun 2026 22:38:49 +0800 Subject: [PATCH] fix command --- Tun/Context/SDLContextActor.swift | 25 +++++- Tun/Context/SDLContextBootstrap.swift | 108 +++++++++++++++++++++++++- Tun/PacketTunnelProvider.swift | 15 ++-- Tun/Session/SessionTable.swift | 24 +++++- tracelog.sh | 4 +- 5 files changed, 157 insertions(+), 19 deletions(-) diff --git a/Tun/Context/SDLContextActor.swift b/Tun/Context/SDLContextActor.swift index 9f9ec1f..07b41dd 100644 --- a/Tun/Context/SDLContextActor.swift +++ b/Tun/Context/SDLContextActor.swift @@ -149,7 +149,7 @@ actor SDLContextActor { do { try await self.runRootBody() } catch is CancellationError { - if let terminalError = await self.consumeTerminalError() { + if let terminalError = self.consumeTerminalError() { SDLLogger.fatal("[SDLContext] root task stopped by terminal error: \(terminalError)", category: .context) result = .failure(terminalError) } else { @@ -163,7 +163,7 @@ actor SDLContextActor { } await self.cleanupRoot() - await self.finishRootTask(id: rootTaskID) + self.finishRootTask(id: rootTaskID) try result.get() } self.rootTaskID = rootTaskID @@ -196,6 +196,27 @@ actor SDLContextActor { self.terminalError = nil } + public func recoverAfterWake() async throws { + SDLLogger.log("[SDLContext] recoverAfterWake requested", category: .context) + + guard self.rootTask != nil else { + throw TunnelError.invalidContext + } + + try await self.readySignal.wait(timeout: .seconds(30)) + + guard let dataCipher = self.dataCipher else { + throw TunnelError.invalidContext + } + + let prunedSessions = await self.sessionManager.pruneExpiredSessions() + await self.packetOutboundActor.updateRuntime(config: self.config, dataCipher: dataCipher) + await self.packetInboundActor.updateRuntime(config: self.config, dataCipher: dataCipher) + try await self.tunNetworkManager.apply(settings: .init(config: self.config), dnsServer: DNSHelper.dnsServer) + + SDLLogger.log("[SDLContext] recoverAfterWake completed, prunedSessions: \(prunedSessions)", category: .context) + } + private func runRootBody() async throws { self.prepareTunnelNotifier() diff --git a/Tun/Context/SDLContextBootstrap.swift b/Tun/Context/SDLContextBootstrap.swift index a4eeef7..d1657ef 100644 --- a/Tun/Context/SDLContextBootstrap.swift +++ b/Tun/Context/SDLContextBootstrap.swift @@ -8,6 +8,16 @@ import Foundation final class SDLContextBootstrap: @unchecked Sendable { + private typealias StartCompletion = (Error?) -> Void + private typealias StopCompletion = () -> Void + private typealias WakeCompletion = (Error?) -> Void + + private enum BootstrapCommand { + case start(config: SDLConfiguration, rsaCipher: CCRSACipher, completion: StartCompletion) + case stop(clearRuntimeConfiguration: Bool, completion: StopCompletion) + case recoverAfterWake(completion: WakeCompletion) + } + private enum RuntimeState { case idle case starting @@ -22,9 +32,22 @@ final class SDLContextBootstrap: @unchecked Sendable { private var rsaCipher: CCRSACipher? private var contextActor: SDLContextActor? private var startCompletionHandler: ((Error?) -> Void)? + private let commandContinuation: AsyncStream.Continuation + private var commandWorker: Task? init(provider: PacketTunnelProvider) { + let commandPair = AsyncStream.makeStream(of: BootstrapCommand.self, bufferingPolicy: .unbounded) + self.provider = provider + self.commandContinuation = commandPair.continuation + self.commandWorker = Task { [weak self, stream = commandPair.stream] in + await self?.runCommandLoop(stream) + } + } + + deinit { + self.commandContinuation.finish() + self.commandWorker?.cancel() } func startCached(completionHandler: @escaping (Error?) -> Void) { @@ -43,6 +66,46 @@ final class SDLContextBootstrap: @unchecked Sendable { } func start(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) { + self.submit(.start(config: config, rsaCipher: rsaCipher, completion: completionHandler)) + } + + func stop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) { + self.submit(.stop(clearRuntimeConfiguration: clearRuntimeConfiguration, completion: completionHandler)) + } + + func recoverAfterWake(completionHandler: @escaping (Error?) -> Void) { + self.submit(.recoverAfterWake(completion: completionHandler)) + } + + func currentContextActor() -> SDLContextActor? { + self.runtimeLock.lock() + let contextActor = self.contextActor + self.runtimeLock.unlock() + return contextActor + } + + private func submit(_ command: BootstrapCommand) { + self.commandContinuation.yield(command) + } + + private func runCommandLoop(_ stream: AsyncStream) async { + for await command in stream { + self.handle(command) + } + } + + private func handle(_ command: BootstrapCommand) { + switch command { + case .start(let config, let rsaCipher, let completion): + self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completion) + case .stop(let clearRuntimeConfiguration, let completion): + self.handleStop(clearRuntimeConfiguration: clearRuntimeConfiguration, completionHandler: completion) + case .recoverAfterWake(let completion): + self.handleRecoverAfterWake(completionHandler: completion) + } + } + + private func handleStart(config: SDLConfiguration, rsaCipher: CCRSACipher, completionHandler: @escaping (Error?) -> Void) { guard let provider = self.provider else { SDLLogger.fatal("[SDLContextBootstrap] start rejected: provider released", category: .app) completionHandler(TunnelError.invalidContext) @@ -84,7 +147,7 @@ final class SDLContextBootstrap: @unchecked Sendable { } } - func stop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) { + private func handleStop(clearRuntimeConfiguration: Bool, completionHandler: @escaping () -> Void) { self.runtimeLock.lock() let contextActor = self.contextActor let startCompletionHandler = self.startCompletionHandler @@ -122,11 +185,50 @@ final class SDLContextBootstrap: @unchecked Sendable { } } - func currentContextActor() -> SDLContextActor? { + private func handleRecoverAfterWake(completionHandler: @escaping (Error?) -> Void) { self.runtimeLock.lock() + let runtimeState = self.runtimeState let contextActor = self.contextActor + let config = self.config + let rsaCipher = self.rsaCipher self.runtimeLock.unlock() - return contextActor + + switch runtimeState { + case .running: + guard let contextActor else { + SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing running context", category: .app) + completionHandler(TunnelError.invalidContext) + return + } + + Task { + do { + try await contextActor.recoverAfterWake() + completionHandler(nil) + } catch { + SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: \(error)", category: .app) + completionHandler(error) + } + } + + case .idle: + guard let config, let rsaCipher else { + SDLLogger.fatal("[SDLContextBootstrap] recoverAfterWake failed: missing cached runtime configuration", category: .app) + completionHandler(TunnelError.invalidConfiguration) + return + } + + SDLLogger.log("[SDLContextBootstrap] recoverAfterWake will start cached context", category: .app) + self.handleStart(config: config, rsaCipher: rsaCipher, completionHandler: completionHandler) + + case .starting: + SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is starting", category: .app) + completionHandler(nil) + + case .stopping: + SDLLogger.log("[SDLContextBootstrap] recoverAfterWake ignored while context is stopping", category: .app) + completionHandler(nil) + } } private func finishContextStart(_ contextActor: SDLContextActor, error: Error?) { diff --git a/Tun/PacketTunnelProvider.swift b/Tun/PacketTunnelProvider.swift index 19db543..fa76168 100644 --- a/Tun/PacketTunnelProvider.swift +++ b/Tun/PacketTunnelProvider.swift @@ -56,21 +56,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider { } override func sleep(completionHandler: @escaping () -> Void) { - SDLLogger.fatal("[PacketTunnelProvider] sleep requested, will stop current context", category: .app) - self.contextBootstrap.stop(clearRuntimeConfiguration: false) { - SDLLogger.log("[PacketTunnelProvider] sleep", category: .app) - completionHandler() - } + SDLLogger.log("[PacketTunnelProvider] sleep requested", category: .app) + completionHandler() } override func wake() { SDLLogger.log("[PacketTunnelProvider] wake up!!!!!!!", category: .app) - self.contextBootstrap.startCached { err in + self.contextBootstrap.recoverAfterWake { err in if let err { - SDLLogger.fatal("[PacketTunnelProvider] wakeup start failed: \(err)", category: .app) - SDLLogger.log("[PacketTunnelProvider] wakeup start failed: \(err.localizedDescription)", category: .app) + SDLLogger.fatal("[PacketTunnelProvider] wakeup recovery failed: \(err)", category: .app) + SDLLogger.log("[PacketTunnelProvider] wakeup recovery failed: \(err.localizedDescription)", category: .app) } else { - SDLLogger.log("[PacketTunnelProvider] wakeup and try start", category: .app) + SDLLogger.log("[PacketTunnelProvider] wakeup recovery completed", category: .app) } } } diff --git a/Tun/Session/SessionTable.swift b/Tun/Session/SessionTable.swift index 0c35165..fb1c750 100644 --- a/Tun/Session/SessionTable.swift +++ b/Tun/Session/SessionTable.swift @@ -71,6 +71,15 @@ actor SessionManager { self.publishSnapshot() } + @discardableResult + func pruneExpiredSessions() -> Int { + let oldCount = self.sessionCount() + self.sessions = self.validSessions() + let newCount = self.sessionCount() + self.publishSnapshot() + return oldCount - newCount + } + nonisolated func snapshot() -> SessionSnapshot { return self.snapshotPublisher.current() } @@ -83,12 +92,21 @@ actor SessionManager { self.snapshotPublisher.publish(self.compileSnapshot()) } - private func compileSnapshot() -> SessionSnapshot { + private func validSessions() -> [Data: [Session.AddressType: Session]] { let timestamp = Int32(Date().timeIntervalSince1970) - let sessions = self.sessions.compactMapValues { peerSessions in + return self.sessions.compactMapValues { peerSessions in let validSessions = peerSessions.filter { $0.value.lastTimestamp + self.ttl >= timestamp } return validSessions.isEmpty ? nil : validSessions } - return SessionSnapshot(sessions: sessions) + } + + private func sessionCount() -> Int { + return self.sessions.values.reduce(0) { count, peerSessions in + count + peerSessions.count + } + } + + private func compileSnapshot() -> SessionSnapshot { + return SessionSnapshot(sessions: self.validSessions()) } } diff --git a/tracelog.sh b/tracelog.sh index 9845eaf..9c051f9 100755 --- a/tracelog.sh +++ b/tracelog.sh @@ -1,3 +1,3 @@ -#! /bin/sh +#!/bin/sh -log stream --predicate 'subsystem == "com.jihe.punchnet.debug"' --info --style compact +log stream --style compact --predicate 'subsystem == "com.jihe.punchnet.tun"'