fix(apple): call completionHandler only after initialized (#10606)

Apple's [docs
state](https://developer.apple.com/documentation/networkextension/nepackettunnelprovider/starttunnel(options:completionhandler:)#Discussion)
that we should only call the PacketTunnelProvider's `completionHandler`
once the tunnel is ready to route packets. Calling it prematurely, while
shouldn't cause packets to get routed to us (we haven't added the routes
yet), will however cause the system to think our VPN is "online", which
disconnects other VPNs and communicates to the user Firezone is
"connected".

If the portal is then slow to send us the init, we will be stuck in this
quasi-connected state for more than a brief moment of time.

To fix this, we thread `completionHandler` through to `Adapter` and call
this if we are configuring the tun interface for the first time. This
way, we remain in the `connecting` state until the tunnel is fully
configured.
This commit is contained in:
Jamil
2025-10-17 08:53:26 -07:00
committed by GitHub
parent fbade40e66
commit 97895c499a
2 changed files with 15 additions and 7 deletions

View File

@@ -53,6 +53,9 @@ class Adapter: @unchecked Sendable {
/// Packet tunnel provider.
private weak var packetTunnelProvider: PacketTunnelProvider?
/// Start completion handler, used to signal to the system the interface is ready to use.
private var startCompletionHandler: (Error?) -> Void
/// Network routes monitor.
private var networkMonitor: NWPathMonitor?
@@ -155,7 +158,8 @@ class Adapter: @unchecked Sendable {
logFilter: String,
accountSlug: String,
internetResourceEnabled: Bool,
packetTunnelProvider: PacketTunnelProvider
packetTunnelProvider: PacketTunnelProvider,
startCompletionHandler: @escaping (Error?) -> Void
) {
self.apiURL = apiURL
self.token = token
@@ -164,6 +168,7 @@ class Adapter: @unchecked Sendable {
self.accountSlug = accountSlug
self.internetResourceEnabled = internetResourceEnabled
self.packetTunnelProvider = packetTunnelProvider
self.startCompletionHandler = startCompletionHandler
}
// Could happen abruptly if the process is killed.
@@ -342,6 +347,8 @@ class Adapter: @unchecked Sendable {
let ipv4, let ipv6, let dns, let searchDomain, let ipv4Routes, let ipv6Routes):
Log.log("Received TunInterfaceUpdated event")
let firstStart = self.networkSettings == nil
// Convert UniFFI types to NetworkExtension types
let routes4 = ipv4Routes.compactMap { cidr in
NetworkSettings.Cidr(address: cidr.address, prefix: Int(cidr.prefix)).asNEIPv4Route
@@ -367,7 +374,11 @@ class Adapter: @unchecked Sendable {
networkSettings.setSearchDomain(domain: searchDomain)
self.networkSettings = networkSettings
networkSettings.apply()
networkSettings.apply {
if firstStart {
self.startCompletionHandler(nil)
}
}
case .resourcesUpdated(let resourceList):
Log.log("Received ResourcesUpdated event with \(resourceList.count) resources")

View File

@@ -114,7 +114,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
logFilter: logFilter,
accountSlug: accountSlug,
internetResourceEnabled: internetResourceEnabled,
packetTunnelProvider: self
packetTunnelProvider: self,
startCompletionHandler: completionHandler
)
// Start the adapter
@@ -122,10 +123,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
self.adapter = adapter
// Tell the system the tunnel is up, moving the tunnel manager status to
// `connected`.
completionHandler(nil)
} catch {
Log.error(error)