From e6a35ed7c445b4262d68ac3ff8b31bbf8ca47bcd Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Tue, 28 Nov 2023 11:44:42 +0100 Subject: [PATCH] Throttle network path updates in packet tunnel --- .../PacketTunnelPathObserver.swift | 28 +++++++++++-------- .../PacketTunnelProvider.swift | 4 +-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift index b16c62f705fe..56953553dde2 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelPathObserver.swift @@ -6,17 +6,19 @@ // Copyright © 2023 Mullvad VPN AB. All rights reserved. // -import Foundation +import Combine import NetworkExtension import PacketTunnelCore final class PacketTunnelPathObserver: DefaultPathObserverProtocol { private weak var packetTunnelProvider: NEPacketTunnelProvider? private let stateLock = NSLock() - private var observationToken: NSKeyValueObservation? + private var pathUpdatePublisher: AnyCancellable? + private let eventQueue: DispatchQueue - init(packetTunnelProvider: NEPacketTunnelProvider) { + init(packetTunnelProvider: NEPacketTunnelProvider, eventQueue: DispatchQueue) { self.packetTunnelProvider = packetTunnelProvider + self.eventQueue = eventQueue } var defaultPath: NetworkPath? { @@ -25,22 +27,26 @@ final class PacketTunnelPathObserver: DefaultPathObserverProtocol { func start(_ body: @escaping (NetworkPath) -> Void) { stateLock.withLock { - observationToken?.invalidate() + pathUpdatePublisher?.cancel() // Normally packet tunnel provider should exist throughout the network extension lifetime. - observationToken = packetTunnelProvider?.observe(\.defaultPath, options: [.new]) { _, change in - let nwPath = change.newValue.flatMap { $0 } - if let nwPath { - body(nwPath) + pathUpdatePublisher = packetTunnelProvider?.publisher(for: \.defaultPath) + .removeDuplicates(by: { oldPath, newPath in + oldPath?.status == newPath?.status + }) + .throttle(for: .seconds(2), scheduler: eventQueue, latest: true) + .sink { change in + if let change { + body(change) + } } - } } } func stop() { stateLock.withLock { - observationToken?.invalidate() - observationToken = nil + pathUpdatePublisher?.cancel() + pathUpdatePublisher = nil } } } diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 47548879799a..96d657ce48af 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -61,7 +61,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { eventQueue: internalQueue, pinger: Pinger(replyQueue: internalQueue), tunnelDeviceInfo: adapter, - defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self), + defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self, eventQueue: internalQueue), timings: TunnelMonitorTimings() ) @@ -78,7 +78,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { timings: PacketTunnelActorTimings(), tunnelAdapter: adapter, tunnelMonitor: tunnelMonitor, - defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self), + defaultPathObserver: PacketTunnelPathObserver(packetTunnelProvider: self, eventQueue: internalQueue), blockedStateErrorMapper: BlockedStateErrorMapper(), relaySelector: RelaySelectorWrapper(relayCache: relayCache), settingsReader: SettingsReader()