diff --git a/swift/apple/FirezoneNetworkExtension/Adapter.swift b/swift/apple/FirezoneNetworkExtension/Adapter.swift
index 28eeb8287..91f22b46a 100644
--- a/swift/apple/FirezoneNetworkExtension/Adapter.swift
+++ b/swift/apple/FirezoneNetworkExtension/Adapter.swift
@@ -52,8 +52,9 @@ class Adapter {
/// Track our last fetched DNS resolvers to know whether to tell connlib they've updated
private var lastFetchedResolvers: [String] = []
- /// Used to avoid needlessly sending resets to connlib
- private var primaryInterfaceName: String?
+ /// Remembers the last _relevant_ path update.
+ /// A path update is considered relevant if certain properties change that require us to reset connlib's network state.
+ private var lastRelevantPath: Network.NWPath?
/// Private queue used to ensure consistent ordering among path update and connlib callbacks
/// This is the primary async primitive used in this class.
@@ -281,24 +282,20 @@ extension Adapter {
guard case .tunnelStarted(let session) = state else { return }
if path.status == .unsatisfied {
- Log.tunnel.log("\(#function): Detected network change: Offline.")
-
// Check if we need to set reasserting, avoids OS log spam and potentially other side effects
if packetTunnelProvider?.reasserting == false {
// Tell the UI we're not connected
packetTunnelProvider?.reasserting = true
}
} else {
- Log.tunnel.log("\(#function): Detected network change: Online.")
-
- // Hint to connlib we're back online, but only do so if our primary interface changes,
- // and therefore we need to bump sockets. On darwin, this is needed to send packets
+ // Tell connlib to reset network state, but only do so if our connectivity has
+ // meaningfully changed. On darwin, this is needed to send packets
// out of a different interface even when 0.0.0.0 is used as the source.
// If our primary interface changes, we can be certain the old socket shouldn't be
// used anymore.
- if path.availableInterfaces.first?.name != primaryInterfaceName {
+ if lastRelevantPath?.connectivityDifferentFrom(path: path) != false {
+ lastRelevantPath = path
session.reset()
- primaryInterfaceName = path.availableInterfaces.first?.name
}
if shouldFetchSystemResolvers(path: path) {
@@ -492,3 +489,14 @@ extension Adapter: CallbackHandlerDelegate {
}
}
#endif
+
+extension Network.NWPath {
+ func connectivityDifferentFrom(path: Network.NWPath) -> Bool {
+ // We define a path as different from another if the following properties change
+ return path.supportsIPv4 != self.supportsIPv4 ||
+ path.supportsIPv6 != self.supportsIPv6 ||
+ path.availableInterfaces.first?.name != self.availableInterfaces.first?.name ||
+ // Apple provides no documentation on whether order is meaningful, so assume it isn't.
+ Set(self.gateways) != Set(path.gateways)
+ }
+}
diff --git a/website/src/components/Changelog/Apple.tsx b/website/src/components/Changelog/Apple.tsx
index ff940c82f..99b167398 100644
--- a/website/src/components/Changelog/Apple.tsx
+++ b/website/src/components/Changelog/Apple.tsx
@@ -9,6 +9,16 @@ export default function Apple() {
href="https://apps.apple.com/us/app/firezone/id6443661826"
title="macOS / iOS"
>
+ {/*
+
+
+
+ Gracefully handles cases where the device's local interface IPv4/IPv6 address or
+ local network gateway changes while the client is connected.
+
+
+
+ */}