import Foundation import NetworkExtension open class ExtensionProvider: NEPacketTunnelProvider { public var username: String? = nil private var commandServer: LibboxCommandServer! private var boxService: LibboxBoxService! private var systemProxyAvailable = false private var systemProxyEnabled = false private var platformInterface: ExtensionPlatformInterface! override open func startTunnel(options _: [String: NSObject]?) async throws { LibboxClearServiceError() if let username { var error: NSError? LibboxSetupWithUsername(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, username, &error) if let error { writeFatalError("(packet-tunnel) error: setup service: \(error.localizedDescription)") return } } else { var isTVOS = false #if os(tvOS) isTVOS = true #endif LibboxSetup(FilePath.sharedDirectory.relativePath, FilePath.workingDirectory.relativePath, FilePath.cacheDirectory.relativePath, isTVOS) } var error: NSError? LibboxRedirectStderr(FilePath.cacheDirectory.appendingPathComponent("stderr.log").relativePath, &error) if let error { writeFatalError("(packet-tunnel) redirect stderr error: \(error.localizedDescription)") } await LibboxSetMemoryLimit(!SharedPreferences.ignoreMemoryLimit.get()) if platformInterface == nil { platformInterface = ExtensionPlatformInterface(self) } commandServer = await LibboxNewCommandServer(platformInterface, Int32(SharedPreferences.maxLogLines.get())) do { try commandServer.start() } catch { writeFatalError("(packet-tunnel): log server start error: \(error.localizedDescription)") return } writeMessage("(packet-tunnel): Here I stand") await startService() } func writeMessage(_ message: String) { if let commandServer { commandServer.writeMessage(message) } else { NSLog(message) } } public func writeFatalError(_ message: String) { #if DEBUG NSLog(message) #endif writeMessage(message) var error: NSError? LibboxWriteServiceError(message, &error) cancelTunnelWithError(NSError(domain: message, code: 0)) } private func startService() async { let profile: Profile? do { profile = try await ProfileManager.get(Int64(SharedPreferences.selectedProfileID.get())) } catch { writeFatalError("(packet-tunnel) error: read selected profile: \(error.localizedDescription)") return } guard let profile else { writeFatalError("(packet-tunnel) error: missing selected profile") return } let configContent: String do { configContent = try profile.read() } catch { writeFatalError("(packet-tunnel) error: read config file \(profile.path): \(error.localizedDescription)") return } var error: NSError? let service = LibboxNewService(configContent, platformInterface, &error) if let error { writeFatalError("(packet-tunnel) error: create service: \(error.localizedDescription)") return } guard let service else { return } commandServer.setService(service) do { try service.start() } catch { commandServer.setService(nil) writeFatalError("(packet-tunnel) error: start service: \(error.localizedDescription)") return } boxService = service #if os(macOS) await SharedPreferences.startedByUser.set(true) #endif } private func stopService() { if let service = boxService { do { try service.close() } catch { writeMessage("(packet-tunnel) error: stop service: \(error.localizedDescription)") } boxService = nil commandServer.setService(nil) } if let platformInterface { platformInterface.reset() } } func reloadService() async { writeMessage("(packet-tunnel) reloading service") reasserting = true defer { reasserting = false } stopService() commandServer.resetLog() await startService() } func postServiceClose() { boxService = nil } override open func stopTunnel(with reason: NEProviderStopReason) async { writeMessage("(packet-tunnel) stopping, reason: \(reason)") stopService() if let server = commandServer { try? await Task.sleep(nanoseconds: 100 * NSEC_PER_MSEC) try? server.close() commandServer = nil } #if os(macOS) if reason == .userInitiated { await SharedPreferences.startedByUser.set(reason == .userInitiated) } #endif } override open func handleAppMessage(_ messageData: Data) async -> Data? { messageData } override open func sleep() async { if let boxService { boxService.pause() } } override open func wake() { if let boxService { boxService.wake() } } }