@@ -44,33 +44,55 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
4444 }
4545
4646 override func startTunnel(
47- options _: [ String : NSObject ] ?
48- ) async throws {
49- globalHelperXPCClient. ptp = self
50- guard let proto = protocolConfiguration as? NETunnelProviderProtocol ,
51- let baseAccessURL = proto. serverAddress
52- else {
53- logger. error ( " startTunnel called with nil protocolConfiguration " )
54- throw makeNSError ( suffix: " PTP " , desc: " Missing Configuration " )
55- }
56- // HACK: We can't write to the system keychain, and the NE can't read the user keychain.
57- guard let token = proto. providerConfiguration ? [ " token " ] as? String else {
58- logger. error ( " startTunnel called with nil token " )
59- throw makeNSError ( suffix: " PTP " , desc: " Missing Token " )
60- }
61- let headers = proto. providerConfiguration ? [ " literalHeaders " ] as? Data
62- logger. debug ( " retrieved token & access URL " )
63- guard let tunFd = tunnelFileDescriptor else {
64- logger. error ( " startTunnel called with nil tunnelFileDescriptor " )
65- throw makeNSError ( suffix: " PTP " , desc: " Missing Tunnel File Descriptor " )
66- }
67- try await globalHelperXPCClient. startDaemon (
68- accessURL: . init( string: baseAccessURL) !,
69- token: token,
70- tun: FileHandle ( fileDescriptor: tunFd) ,
71- headers: headers
72- )
73- }
47+ options: [ String : NSObject ] ? ,
48+ completionHandler: @Sendable @escaping ( Error ? ) -> Void
49+ ) {
50+ // Make a Sendable copy of the completion handler to avoid crossing concurrency domains with a non-Sendable closure
51+ let complete : @Sendable ( Error ? ) -> Void = { error in
52+ // Always bounce completion back to the main actor as NetworkExtension expects callbacks on the provider's queue/main.
53+ Task { @MainActor in completionHandler ( error) }
54+ }
55+ globalHelperXPCClient. ptp = self
56+
57+ // Resolve everything you need BEFORE hopping to async, so the Task
58+ // doesn’t need to capture `self` or `options`.
59+ guard let proto = protocolConfiguration as? NETunnelProviderProtocol ,
60+ let baseAccessURL = proto. serverAddress
61+ else {
62+ logger. error ( " startTunnel called with nil protocolConfiguration " )
63+ complete ( makeNSError ( suffix: " PTP " , desc: " Missing Configuration " ) )
64+ return
65+ }
66+
67+ guard let token = proto. providerConfiguration ? [ " token " ] as? String else {
68+ logger. error ( " startTunnel called with nil token " )
69+ complete ( makeNSError ( suffix: " PTP " , desc: " Missing Token " ) )
70+ return
71+ }
72+
73+ let headers = proto. providerConfiguration ? [ " literalHeaders " ] as? Data
74+
75+ guard let tunFd = tunnelFileDescriptor else {
76+ logger. error ( " startTunnel called with nil tunnelFileDescriptor " )
77+ complete ( makeNSError ( suffix: " PTP " , desc: " Missing Tunnel File Descriptor " ) )
78+ return
79+ }
80+
81+ // Bridge to async work
82+ Task . detached {
83+ do {
84+ try await globalHelperXPCClient. startDaemon (
85+ accessURL: URL ( string: baseAccessURL) !,
86+ token: token,
87+ tun: FileHandle ( fileDescriptor: tunFd) ,
88+ headers: headers
89+ )
90+ complete ( nil )
91+ } catch {
92+ complete ( error)
93+ }
94+ }
95+ }
7496
7597 override func stopTunnel(
7698 with _: NEProviderStopReason
@@ -111,3 +133,4 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
111133 try await setTunnelNetworkSettings ( currentSettings)
112134 }
113135}
136+
0 commit comments