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 { let options = LibboxSetupOptions() options.basePath = FilePath.sharedDirectory.relativePath options.workingPath = FilePath.workingDirectory.relativePath options.tempPath = FilePath.cacheDirectory.relativePath options.logMaxLines = 3000 #if os(tvOS) options.commandServerListenPort = await SharedPreferences.commandServerPort.get() options.commandServerSecret = await SharedPreferences.commandServerSecret.get() #endif var setupError: NSError? LibboxSetup(options, &setupError) if let setupError { throw ExtensionStartupError("(packet-tunnel) error: setup service: \(setupError.localizedDescription)") } var stderrError: NSError? LibboxRedirectStderr(FilePath.cacheDirectory.appendingPathComponent("stderr.log").relativePath, &stderrError) if let stderrError { throw ExtensionStartupError("(packet-tunnel) redirect stderr error: \(stderrError.localizedDescription)") } await LibboxSetMemoryLimit(!SharedPreferences.ignoreMemoryLimit.get()) if platformInterface == nil { platformInterface = ExtensionPlatformInterface(self) } var error: NSError? commandServer = LibboxNewCommandServer(platformInterface, platformInterface, &error) if let error { throw ExtensionStartupError("(packet-tunnel): create command server error: \(error.localizedDescription)") } do { try commandServer.start() } catch { throw ExtensionStartupError("(packet-tunnel): start command server error: \(error.localizedDescription)") } writeMessage("(packet-tunnel): Here I stand") try 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) } } private func startService() async throws { let profileID = await SharedPreferences.selectedProfileID.get() let profile: Profile? do { profile = try await ProfileManager.get(profileID) } catch { throw ExtensionStartupError("(packet-tunnel) error: read selected profile: \(error.localizedDescription)") } guard let profile else { throw ExtensionStartupError("(packet-tunnel) error: missing selected profile") } let configContent: String do { configContent = try profile.read() } catch { throw ExtensionStartupError("(packet-tunnel) error: read config file \(profile.path): \(error.localizedDescription)") } let options = LibboxOverrideOptions() do { try commandServer.startOrReloadService(configContent, options: options) } catch { throw ExtensionStartupError("(packet-tunnel) error: start service: \(error.localizedDescription)") } #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 func stopService() { do { try commandServer.closeService() } catch { writeMessage("(packet-tunnel) error: stop service: \(error.localizedDescription)") } if let platformInterface { platformInterface.reset() } } func reloadService() async throws { writeMessage("(packet-tunnel) reloading service") reasserting = true defer { reasserting = false } try 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() } } }