import Foundation import Libbox import NetworkExtension #if os(iOS) import WidgetKit #endif #if os(macOS) import CoreLocation #endif open class ExtensionProvider: NEPacketTunnelProvider { public var username: String? private var commandServer: LibboxCommandServer! private var platformInterface: ExtensionPlatformInterface! override open func startTunnel(options _: [String: NSObject]?) async throws { LibboxClearServiceError() let options = LibboxSetupOptions() options.basePath = FilePath.sharedDirectory.relativePath options.workingPath = FilePath.workingDirectory.relativePath options.tempPath = FilePath.cacheDirectory.relativePath var setupError: NSError? LibboxSetup(options, &setupError) if let setupError { writeFatalError("(packet-tunnel) error: setup service: \(setupError.localizedDescription)") return } var stderrError: NSError? LibboxRedirectStderr(FilePath.cacheDirectory.appendingPathComponent("stderr.log").relativePath, &stderrError) if let stderrError { writeFatalError("(packet-tunnel) redirect stderr error: \(stderrError.localizedDescription)") return } await LibboxSetMemoryLimit(!SharedPreferences.ignoreMemoryLimit.get()) if platformInterface == nil { platformInterface = ExtensionPlatformInterface(self) } var error: NSError? commandServer = LibboxNewCommandServer(platformInterface, platformInterface, &error) if let error { writeFatalError("(packet-tunnel): create command server error: \(error.localizedDescription)") return } do { try commandServer.start() } catch { writeFatalError("(packet-tunnel): start command server error: \(error.localizedDescription)") return } writeMessage("(packet-tunnel): Here I stand") await startService() #if os(iOS) if #available(iOS 18.0, *) { ControlCenter.shared.reloadControls(ofKind: ExtensionProfile.controlKind) } #endif } func writeMessage(_ message: String) { if let commandServer { commandServer.writeMessage(2, message: message) } } public func writeFatalError(_ message: String) { #if DEBUG NSLog(message) #endif if let commandServer { commandServer.setError(message) } cancelTunnelWithError(nil) } 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 } let options = LibboxOverrideOptions() do { try commandServer.startOrReloadService(configContent, options: options) } catch { writeFatalError("(packet-tunnel) error: start service: \(error.localizedDescription)") return } #if os(macOS) await SharedPreferences.startedByUser.set(true) if commandServer.needWIFIState() { if !Variant.useSystemExtension { locationManager = CLLocationManager() locationDelegate = stubLocationDelegate() locationManager?.delegate = locationDelegate locationManager?.requestLocation() } else { writeMessage("(packet-tunnel) WIFI SSID and BSSID information is not currently available in the standalone version of SFM. We are working on resolving this issue.") } } #endif } #if os(macOS) private var locationManager: CLLocationManager? private var locationDelegate: stubLocationDelegate? class stubLocationDelegate: NSObject, CLLocationManagerDelegate { func locationManagerDidChangeAuthorization(_: CLLocationManager) {} func locationManager(_: CLLocationManager, didUpdateLocations _: [CLLocation]) {} func locationManager(_: CLLocationManager, didFailWithError _: Error) {} } #endif private func stopService() { do { try commandServer.closeService() } catch { writeMessage("(packet-tunnel) error: stop service: \(error.localizedDescription)") } if let platformInterface { platformInterface.reset() } } func reloadService() async { writeMessage("(packet-tunnel) reloading service") reasserting = true defer { reasserting = false } await startService() } 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) server.close() commandServer = nil } #if os(macOS) if reason == .userInitiated { await SharedPreferences.startedByUser.set(reason == .userInitiated) } #endif #if os(iOS) if #available(iOS 18.0, *) { ControlCenter.shared.reloadControls(ofKind: ExtensionProfile.controlKind) } #endif } override open func handleAppMessage(_ messageData: Data) async -> Data? { messageData } override open func sleep() async { if let commandServer { commandServer.pause() } } override open func wake() { if let commandServer { commandServer.wake() } } }