diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/IPCClient.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/IPCClient.swift index 25c2d172a..801eca058 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/IPCClient.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Helpers/IPCClient.swift @@ -50,14 +50,24 @@ class IPCClient { let decoder = PropertyListDecoder() // Auto-connect - func start() throws { - try session().startTunnel(options: nil) + @MainActor + func start(configuration: Configuration) throws { + let tunnelConfiguration = configuration.toTunnelConfiguration() + let configData = try encoder.encode(tunnelConfiguration) + let options: [String: NSObject] = [ + "configuration": configData as NSObject + ] + try session().startTunnel(options: options) } // Sign in - func start(token: String) throws { + @MainActor + func start(token: String, configuration: Configuration) throws { + let tunnelConfiguration = configuration.toTunnelConfiguration() + let configData = try encoder.encode(tunnelConfiguration) let options: [String: NSObject] = [ - "token": token as NSObject + "token": token as NSObject, + "configuration": configData as NSObject, ] try session().startTunnel(options: options) @@ -76,8 +86,14 @@ class IPCClient { // On macOS, IPC calls to the system extension won't work after it's been upgraded, until the startTunnel call. // Since we rely on IPC for the GUI to function, we need to send a dummy `startTunnel` that doesn't actually // start the tunnel, but causes the system to wake the extension. - func dryStartStopCycle() throws { - let options: [String: NSObject] = ["dryRun": true as NSObject] + @MainActor + func dryStartStopCycle(configuration: Configuration) throws { + let tunnelConfiguration = configuration.toTunnelConfiguration() + let configData = try encoder.encode(tunnelConfiguration) + let options: [String: NSObject] = [ + "dryRun": true as NSObject, + "configuration": configData as NSObject, + ] try session().startTunnel(options: options) } #endif @@ -87,6 +103,10 @@ class IPCClient { let tunnelConfiguration = configuration.toTunnelConfiguration() let message = ProviderMessage.setConfiguration(tunnelConfiguration) + if sessionStatus() != .connected { + Log.trace("Not setting configuration whilst not connected") + return + } try await sendMessageWithoutResponse(message) } diff --git a/swift/apple/FirezoneKit/Sources/FirezoneKit/Stores/Store.swift b/swift/apple/FirezoneKit/Sources/FirezoneKit/Stores/Store.swift index 7259d9cf5..81b3ed114 100644 --- a/swift/apple/FirezoneKit/Sources/FirezoneKit/Stores/Store.swift +++ b/swift/apple/FirezoneKit/Sources/FirezoneKit/Stores/Store.swift @@ -193,7 +193,7 @@ public final class Store: ObservableObject { private func maybeAutoConnect() async throws { if configuration.connectOnStart { try await manager().enable() - try ipcClient().start() + try ipcClient().start(configuration: configuration) } } func installVPNConfiguration() async throws { @@ -232,7 +232,7 @@ public final class Store: ObservableObject { if vpnStatus == .connected || vpnStatus == .connecting || vpnStatus == .reasserting { try ipcClient().stop() } else { - try ipcClient().dryStartStopCycle() + try ipcClient().dryStartStopCycle(configuration: configuration) } #else try ipcClient().stop() @@ -251,14 +251,13 @@ public final class Store: ObservableObject { Telemetry.accountSlug = accountSlug try await manager().enable() - try await ipcClient().setConfiguration(configuration) // Clear shown alerts when starting a new session so user can see new errors shownAlertIds.removeAll() UserDefaults.standard.removeObject(forKey: "shownAlertIds") - // Bring the tunnel up and send it a token to start - try ipcClient().start(token: authResponse.token) + // Bring the tunnel up and send it a token and configuration to start + try ipcClient().start(token: authResponse.token, configuration: configuration) } func signOut() async throws { diff --git a/swift/apple/FirezoneNetworkExtension/PacketTunnelProvider.swift b/swift/apple/FirezoneNetworkExtension/PacketTunnelProvider.swift index a09659e3a..856409e84 100644 --- a/swift/apple/FirezoneNetworkExtension/PacketTunnelProvider.swift +++ b/swift/apple/FirezoneNetworkExtension/PacketTunnelProvider.swift @@ -68,6 +68,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "unknown" Log.info("Starting tunnel - Version: \(version), Build: \(build)") + // Try to load configuration from options first (passed from client at startup) + if let configData = options?["configuration"] as? Data { + do { + let decoder = PropertyListDecoder() + let configFromOptions = try decoder.decode(TunnelConfiguration.self, from: configData) + Log.info("Loaded configuration from startTunnel options") + // Save it for future fallback (e.g., system-initiated restarts) + configFromOptions.save() + self.tunnelConfiguration = configFromOptions + } catch { + Log.error(error) + } + } + // If the tunnel starts up before the GUI after an upgrade crossing the 1.4.15 version boundary, // the old system settings-based config will still be present and the new configuration will be empty. // So handle that edge case gracefully. diff --git a/swift/apple/FirezoneNetworkExtension/SessionEventLoop.swift b/swift/apple/FirezoneNetworkExtension/SessionEventLoop.swift index 06cb3f44a..734fb98a2 100644 --- a/swift/apple/FirezoneNetworkExtension/SessionEventLoop.swift +++ b/swift/apple/FirezoneNetworkExtension/SessionEventLoop.swift @@ -48,7 +48,9 @@ private func forwardEvents(from session: Session, to eventSender: Sender) } /// Forwards commands from the command receiver to the session. -private func forwardCommands(from commandReceiver: Receiver, to session: Session) async { +private func forwardCommands(from commandReceiver: Receiver, to session: Session) + async +{ for await command in commandReceiver.stream { if Task.isCancelled { Log.log("Command forwarding cancelled")