refactor: Fix macOS standalone application

This commit is contained in:
世界
2026-01-05 01:21:24 +08:00
parent ef1c924c64
commit 59317f3c79
66 changed files with 2799 additions and 585 deletions
+147 -50
View File
@@ -1,6 +1,7 @@
import Foundation
import Libbox
import NetworkExtension
import os.log
#if os(iOS)
import WidgetKit
#endif
@@ -9,20 +10,108 @@ import NetworkExtension
#endif
open class ExtensionProvider: NEPacketTunnelProvider {
public var username: String?
private var commandServer: LibboxCommandServer!
private static let logger = Logger(category: "ExtensionProvider")
public private(set) var commandServer: LibboxCommandServer?
private var platformInterface: ExtensionPlatformInterface!
public var tunnelOptions: [String: NSObject]?
private var startOptionsURL: URL?
public struct OverridePreferences {
public var includeAllNetworks: Bool = false
public var systemProxyEnabled: Bool = true
public var excludeDefaultRoute: Bool = false
public var autoRouteUseSubRangesByDefault: Bool = false
public var excludeAPNsRoute: Bool = false
}
public var overridePreferences: OverridePreferences?
private func applyStartOptions(_ options: [String: NSObject]) {
tunnelOptions = options
var prefs = OverridePreferences()
prefs.includeAllNetworks = (options["includeAllNetworks"] as? NSNumber)?.boolValue ?? false
prefs.systemProxyEnabled = (options["systemProxyEnabled"] as? NSNumber)?.boolValue ?? true
prefs.excludeDefaultRoute = (options["excludeDefaultRoute"] as? NSNumber)?.boolValue ?? false
prefs.autoRouteUseSubRangesByDefault = (options["autoRouteUseSubRangesByDefault"] as? NSNumber)?.boolValue ?? false
prefs.excludeAPNsRoute = (options["excludeAPNsRoute"] as? NSNumber)?.boolValue ?? false
overridePreferences = prefs
}
private func persistStartOptions(_ options: [String: NSObject]) throws {
guard let startOptionsURL else {
return
}
let data = try ExtensionStartOptions.encode(options)
try data.write(to: startOptionsURL, options: .atomic)
}
#if os(macOS)
private var xpcListener: NSXPCListener?
private var xpcService: CommandXPCService?
private var locationManager: CLLocationManager?
private var locationDelegate: stubLocationDelegate?
#endif
override open func startTunnel(options startOptions: [String: NSObject]?) async throws {
let basePath: String
let workingPath: String
let tempPath: String
#if os(macOS)
if Variant.useSystemExtension {
let containerURL = FileManager.default.homeDirectoryForCurrentUser
basePath = containerURL.path
workingPath = containerURL.appendingPathComponent("Working").path
tempPath = containerURL.appendingPathComponent("Temp").path
} else {
basePath = FilePath.sharedDirectory.relativePath
workingPath = FilePath.workingDirectory.relativePath
tempPath = FilePath.cacheDirectory.relativePath
}
#else
basePath = FilePath.sharedDirectory.relativePath
workingPath = FilePath.workingDirectory.relativePath
tempPath = FilePath.cacheDirectory.relativePath
#endif
startOptionsURL = URL(fileURLWithPath: basePath).appendingPathComponent(ExtensionStartOptions.snapshotFileName)
var effectiveOptions = startOptions
if let startOptions {
do {
try persistStartOptions(startOptions)
} catch {
throw ExtensionStartupError("(packet-tunnel) error: persist start options: \(error.localizedDescription)")
}
} else if let startOptionsURL, FileManager.default.fileExists(atPath: startOptionsURL.path) {
do {
let data = try Data(contentsOf: startOptionsURL)
effectiveOptions = try ExtensionStartOptions.decode(data)
} catch {
throw ExtensionStartupError("(packet-tunnel) error: load start options: \(error.localizedDescription)")
}
} else {
throw ExtensionStartupError("(packet-tunnel) error: missing start options")
}
if let effectiveOptions {
applyStartOptions(effectiveOptions)
}
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.basePath = basePath
options.workingPath = workingPath
options.tempPath = tempPath
options.logMaxLines = 3000
#if os(tvOS)
options.commandServerListenPort = await SharedPreferences.commandServerPort.get()
options.commandServerSecret = await SharedPreferences.commandServerSecret.get()
if let port = effectiveOptions?["commandServerPort"] as? NSNumber {
options.commandServerListenPort = port.int32Value
}
if let secret = effectiveOptions?["commandServerSecret"] as? String {
options.commandServerSecret = secret
}
#endif
var setupError: NSError?
@@ -31,13 +120,8 @@ open class ExtensionProvider: NEPacketTunnelProvider {
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())
let ignoreMemoryLimit = (effectiveOptions?["ignoreMemoryLimit"] as? NSNumber)?.boolValue ?? false
LibboxSetMemoryLimit(!ignoreMemoryLimit)
if platformInterface == nil {
platformInterface = ExtensionPlatformInterface(self)
@@ -48,12 +132,31 @@ open class ExtensionProvider: NEPacketTunnelProvider {
throw ExtensionStartupError("(packet-tunnel): create command server error: \(error.localizedDescription)")
}
do {
try commandServer.start()
try commandServer!.start()
} catch {
throw ExtensionStartupError("(packet-tunnel): start command server error: \(error.localizedDescription)")
}
#if os(macOS)
if Variant.useSystemExtension {
let socketPath = options.basePath + "/command.sock"
xpcService = CommandXPCService(socketPath: socketPath)
let machServiceName = AppConfiguration.appGroupID + ".system"
xpcListener = NSXPCListener(machServiceName: machServiceName)
xpcListener!.delegate = xpcService
xpcListener!.resume()
Self.logger.info("set Command Server")
xpcService!.commandServer = commandServer
}
#endif
writeMessage("(packet-tunnel): Here I stand")
try await startService()
#if os(macOS)
if Variant.useSystemExtension {
xpcService!.markServiceReady()
}
#endif
#if os(iOS)
if #available(iOS 18.0, *) {
ControlCenter.shared.reloadControls(ofKind: ExtensionProfile.controlKind)
@@ -68,48 +171,28 @@ open class ExtensionProvider: NEPacketTunnelProvider {
}
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)")
guard let configContent = tunnelOptions?["configContent"] as? String else {
throw ExtensionStartupError("(packet-tunnel) error: missing configContent in tunnel options")
}
let options = LibboxOverrideOptions()
do {
try commandServer.startOrReloadService(configContent, options: options)
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.")
}
if !Variant.useSystemExtension, commandServer!.needWIFIState() {
locationManager = CLLocationManager()
locationDelegate = stubLocationDelegate()
locationManager?.delegate = locationDelegate
locationManager?.requestLocation()
}
#endif
}
#if os(macOS)
private var locationManager: CLLocationManager?
private var locationDelegate: stubLocationDelegate?
class stubLocationDelegate: NSObject, CLLocationManagerDelegate {
func locationManagerDidChangeAuthorization(_: CLLocationManager) {}
@@ -122,9 +205,9 @@ open class ExtensionProvider: NEPacketTunnelProvider {
func stopService() {
do {
try commandServer.closeService()
try commandServer?.closeService()
} catch {
writeMessage("(packet-tunnel) error: stop service: \(error.localizedDescription)")
writeMessage("(packet-tunnel) stop service: \(error.localizedDescription)")
}
if let platformInterface {
platformInterface.reset()
@@ -149,9 +232,15 @@ open class ExtensionProvider: NEPacketTunnelProvider {
commandServer = nil
}
#if os(macOS)
if reason == .userInitiated {
await SharedPreferences.startedByUser.set(reason == .userInitiated)
if Variant.useSystemExtension {
xpcListener?.invalidate()
xpcListener = nil
xpcService?.commandServer = nil
xpcService = nil
UserServiceEndpointRegistry.shared.clear()
}
locationManager = nil
locationDelegate = nil
#endif
#if os(iOS)
if #available(iOS 18.0, *) {
@@ -161,7 +250,15 @@ open class ExtensionProvider: NEPacketTunnelProvider {
}
override open func handleAppMessage(_ messageData: Data) async -> Data? {
messageData
do {
let options = try ExtensionStartOptions.decode(messageData)
applyStartOptions(options)
try persistStartOptions(options)
try await reloadService()
return nil
} catch {
return error.localizedDescription.data(using: .utf8)
}
}
override open func sleep() async {