Compare commits

...

2 Commits

Author SHA1 Message Date
Andrej Mihajlov 2909fdf41e Switch NSCondition to DispatchGroup
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2023-03-06 12:51:05 +01:00
Andrej Mihajlov 3149c50299 Kit: replace path monitor with KVO observer on defaultPath
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
2023-03-06 12:50:44 +01:00
+66 -30
View File
@@ -47,6 +47,12 @@ public class WireGuardAdapter {
/// Packet tunnel provider. /// Packet tunnel provider.
private weak var packetTunnelProvider: NEPacketTunnelProvider? private weak var packetTunnelProvider: NEPacketTunnelProvider?
/// KVO observer for `NEProvider.defaultPath`.
private var defaultPathObserver: NSKeyValueObservation?
/// Last known default path.
private var currentDefaultPath: NetworkExtension.NWPath?
/// Log handler closure. /// Log handler closure.
private let logHandler: LogHandler private let logHandler: LogHandler
@@ -181,11 +187,7 @@ public class WireGuardAdapter {
return return
} }
let networkMonitor = NWPathMonitor() self.addDefaultPathObserver()
networkMonitor.pathUpdateHandler = { [weak self] path in
self?.didReceivePathUpdate(path: path)
}
networkMonitor.start(queue: self.workQueue)
do { do {
let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration) let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration)
@@ -198,10 +200,10 @@ public class WireGuardAdapter {
try self.startWireGuardBackend(wgConfig: wgConfig), try self.startWireGuardBackend(wgConfig: wgConfig),
settingsGenerator settingsGenerator
) )
self.networkMonitor = networkMonitor
completionHandler(nil) completionHandler(nil)
} catch let error as WireGuardAdapterError { } catch let error as WireGuardAdapterError {
networkMonitor.cancel() self.removeDefaultPathObserver()
completionHandler(error) completionHandler(error)
} catch { } catch {
fatalError() fatalError()
@@ -225,8 +227,7 @@ public class WireGuardAdapter {
return return
} }
self.networkMonitor?.cancel() self.removeDefaultPathObserver()
self.networkMonitor = nil
self.state = .stopped self.state = .stopped
@@ -313,26 +314,29 @@ public class WireGuardAdapter {
/// - Returns: `PacketTunnelSettingsGenerator`. /// - Returns: `PacketTunnelSettingsGenerator`.
private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws { private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws {
var systemError: Error? var systemError: Error?
let condition = NSCondition()
// Activate the condition let dispatchGroup = DispatchGroup()
condition.lock()
defer { condition.unlock() } dispatchGroup.enter()
self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in
systemError = error systemError = error
condition.signal() dispatchGroup.leave()
} }
// Packet tunnel's `setTunnelNetworkSettings` times out in certain // Packet tunnel's `setTunnelNetworkSettings` times out in certain
// scenarios & never calls the given callback. // scenarios & never calls the given callback.
let setTunnelNetworkSettingsTimeout: TimeInterval = 5 // seconds let setTunnelNetworkSettingsTimeout: Int = 5 // seconds
if condition.wait(until: Date().addingTimeInterval(setTunnelNetworkSettingsTimeout)) { let waitResult = dispatchGroup.wait(wallTimeout: .now() + .seconds(setTunnelNetworkSettingsTimeout))
switch waitResult {
case .success:
if let systemError = systemError { if let systemError = systemError {
throw WireGuardAdapterError.setNetworkSettings(systemError) throw WireGuardAdapterError.setNetworkSettings(systemError)
} }
} else {
case .timedOut:
self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway") self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway")
} }
} }
@@ -411,19 +415,48 @@ public class WireGuardAdapter {
} }
} }
/// Helper method used by network path monitor. private func addDefaultPathObserver() {
guard let packetTunnelProvider = packetTunnelProvider else { return }
defaultPathObserver?.invalidate()
defaultPathObserver = packetTunnelProvider.observe(\.defaultPath, options: [.new]) { [weak self] _, change in
guard let self = self, let defaultPath = change.newValue?.flatMap({ $0 }) else { return }
self.workQueue.async {
self.didReceivePathUpdate(path: defaultPath)
}
}
currentDefaultPath = packetTunnelProvider.defaultPath
}
private func removeDefaultPathObserver() {
defaultPathObserver?.invalidate()
defaultPathObserver = nil
currentDefaultPath = nil
}
/// Method invoked by KVO observer when new network path is received.
/// - Parameter path: new network path /// - Parameter path: new network path
private func didReceivePathUpdate(path: Network.NWPath) { private func didReceivePathUpdate(path: NetworkExtension.NWPath) {
self.logHandler(.verbose, "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)") let isSamePath = currentDefaultPath?.isEqual(to: path) ?? false
currentDefaultPath = path
self.logHandler(.verbose, "Network change detected with \(path.status)")
#if os(macOS) #if os(macOS)
if case .started(let handle, _) = self.state { if case .started(let handle, _) = self.state, !isSamePath {
wgBumpSockets(handle) wgBumpSockets(handle)
} }
#elseif os(iOS) #elseif os(iOS)
let isSatisfiable = path.status == .satisfied || path.status == .satisfiable
switch self.state { switch self.state {
case .started(let handle, let settingsGenerator): case .started(let handle, let settingsGenerator):
if path.status.isSatisfiable { if isSatisfiable {
guard !isSamePath else { return }
let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration() let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration()
self.logEndpointResolutionResults(resolutionResults) self.logEndpointResolutionResults(resolutionResults)
@@ -438,7 +471,7 @@ public class WireGuardAdapter {
} }
case .temporaryShutdown(let settingsGenerator): case .temporaryShutdown(let settingsGenerator):
guard path.status.isSatisfiable else { return } guard isSatisfiable else { return }
self.logHandler(.verbose, "Connectivity online, resuming backend.") self.logHandler(.verbose, "Connectivity online, resuming backend.")
@@ -472,16 +505,19 @@ public enum WireGuardLogLevel: Int32 {
case error = 1 case error = 1
} }
private extension Network.NWPath.Status { extension NetworkExtension.NWPathStatus: CustomDebugStringConvertible {
/// Returns `true` if the path is potentially satisfiable. public var debugDescription: String {
var isSatisfiable: Bool {
switch self { switch self {
case .requiresConnection, .satisfied:
return true
case .unsatisfied: case .unsatisfied:
return false return "unsatisfied"
case .satisfied:
return "satisfied"
case .satisfiable:
return "satisfiable"
case .invalid:
return "invalid"
@unknown default: @unknown default:
return true return "unknown (rawValue = \(rawValue))"
} }
} }
} }