Work around Xcode 26 tunnel isolation
Some checks are pending
Build Apple / Build App (iOS Simulator) (push) Waiting to run
Build Apple / Build App (macOS) (push) Waiting to run
Build Rust / Cargo Test (push) Waiting to run
Build Site / Next.js Build (push) Waiting to run

This commit is contained in:
Conrad Kramer 2026-03-19 03:51:56 -07:00
parent 028627bfcb
commit 5c0a9b3f54

View file

@ -5,19 +5,17 @@ import libburrow
@preconcurrency import NetworkExtension @preconcurrency import NetworkExtension
import os import os
// Xcode 26 imports `startTunnel(options:)` as `[String: NSObject]?` and treats the
// override as crossing a nonisolated boundary. The extension target does not
// mutate or forward these Cocoa objects, so treat them as an unchecked escape hatch.
extension NSObject: @retroactive @unchecked Sendable {}
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
enum Error: Swift.Error { enum Error: Swift.Error {
case missingTunnelConfiguration case missingTunnelConfiguration
} }
private let logger = Logger.logger(for: PacketTunnelProvider.self) private static let logger = Logger.logger(for: PacketTunnelProvider.self)
private var client: TunnelClient {
get throws { try _client.get() }
}
private let _client: Result<TunnelClient, Swift.Error> = Result {
try TunnelClient.unix(socketURL: Constants.socketURL)
}
override init() { override init() {
do { do {
@ -26,31 +24,33 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
databasePath: try Constants.databaseURL.path(percentEncoded: false) databasePath: try Constants.databaseURL.path(percentEncoded: false)
) )
} catch { } catch {
logger.error("Failed to spawn networking thread: \(error)") Self.logger.error("Failed to spawn networking thread: \(error)")
} }
} }
override func startTunnel(options: [String: NSObject]? = nil) async throws { nonisolated override func startTunnel(options: [String: NSObject]? = nil) async throws {
do { do {
let client = try TunnelClient.unix(socketURL: Constants.socketURL)
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
guard let settings = configuration?.settings else { guard let settings = configuration?.settings else {
throw Error.missingTunnelConfiguration throw Error.missingTunnelConfiguration
} }
try await setTunnelNetworkSettings(settings) try await setTunnelNetworkSettings(settings)
_ = try await client.tunnelStart(.init()) _ = try await client.tunnelStart(.init())
logger.log("Started tunnel with network settings: \(settings)") Self.logger.log("Started tunnel with network settings: \(settings)")
} catch { } catch {
logger.error("Failed to start tunnel: \(error)") Self.logger.error("Failed to start tunnel: \(error)")
throw error throw error
} }
} }
override func stopTunnel(with reason: NEProviderStopReason) async { nonisolated override func stopTunnel(with reason: NEProviderStopReason) async {
do { do {
let client = try TunnelClient.unix(socketURL: Constants.socketURL)
_ = try await client.tunnelStop(.init()) _ = try await client.tunnelStop(.init())
logger.log("Stopped client") Self.logger.log("Stopped client")
} catch { } catch {
logger.error("Failed to stop tunnel: \(error)") Self.logger.error("Failed to stop tunnel: \(error)")
} }
} }
} }