diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2022-02-25 15:24:47 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2022-03-17 09:51:04 +0100 |
| commit | 2a28572bc02ce8acff97d38925cdd01dbdeda365 (patch) | |
| tree | d8f01a2e6298880b6c03df7a065e47c22f4b202f | |
| parent | 46338f67333797d4623b61ef5bf017f0d7a7767a (diff) | |
| download | mullvadvpn-2a28572bc02ce8acff97d38925cdd01dbdeda365.tar.xz mullvadvpn-2a28572bc02ce8acff97d38925cdd01dbdeda365.zip | |
Wrap TunnelProviderManager into Tunnel
12 files changed, 300 insertions, 87 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 49ce76af67..80a279f542 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -270,6 +270,7 @@ 58D0C7A223F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */; }; 58D67A0A26D7AE3300557C3C /* OSLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA4F26CA690600283BF8 /* OSLogHandler.swift */; }; 58DF28A52417CB4B00E836B0 /* AppStorePaymentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */; }; + 58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E0A98727C8F46300FE6BDD /* Tunnel.swift */; }; 58E1336926D2BE3700CC316B /* PromiseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E1336826D2BE3700CC316B /* PromiseObserver.swift */; }; 58E1336A26D2BE3700CC316B /* PromiseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E1336826D2BE3700CC316B /* PromiseObserver.swift */; }; 58E1336B26D2BE3700CC316B /* PromiseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E1336826D2BE3700CC316B /* PromiseObserver.swift */; }; @@ -557,6 +558,7 @@ 58D0C79F23F1CECF00FE9BA7 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MullvadVPNScreenshots.swift; sourceTree = "<group>"; }; 58DF28A42417CB4B00E836B0 /* AppStorePaymentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStorePaymentManager.swift; sourceTree = "<group>"; }; + 58E0A98727C8F46300FE6BDD /* Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tunnel.swift; sourceTree = "<group>"; }; 58E1336826D2BE3700CC316B /* PromiseObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseObserver.swift; sourceTree = "<group>"; }; 58E1336C26D2BE7500CC316B /* AnyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyResult.swift; sourceTree = "<group>"; }; 58E1337026D2BE9C00CC316B /* AnyOptional.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyOptional.swift; sourceTree = "<group>"; }; @@ -989,6 +991,7 @@ 58FD5BF12424F7D700112C88 /* UserInterfaceInteractionRestriction.swift */, 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */, 5877152F23981F7B001F8237 /* WireguardKeysViewController.swift */, + 58E0A98727C8F46300FE6BDD /* Tunnel.swift */, ); path = MullvadVPN; sourceTree = "<group>"; @@ -1494,6 +1497,7 @@ 5815039D24D6ECE600C9C50E /* TextFileOutputStream.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, 588DD76B26FCB49E006F6233 /* Cancellable.swift in Sources */, + 58E0A98827C8F46300FE6BDD /* Tunnel.swift in Sources */, 58ACF64F26567A7100ACE4B7 /* CustomSwitchContainer.swift in Sources */, 5857F24324C8662600CF6F47 /* SelectLocationHeaderView.swift in Sources */, 5840BE35279EDB16002836BA /* OperationCompletion.swift in Sources */, diff --git a/ios/MullvadVPN/Tunnel.swift b/ios/MullvadVPN/Tunnel.swift new file mode 100644 index 0000000000..058b6b2a26 --- /dev/null +++ b/ios/MullvadVPN/Tunnel.swift @@ -0,0 +1,198 @@ +// +// Tunnel.swift +// MullvadVPN +// +// Created by pronebird on 25/02/2022. +// Copyright © 2022 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import NetworkExtension + +// Switch to stabs on simulator +#if targetEnvironment(simulator) +typealias TunnelProviderManagerType = SimulatorTunnelProviderManager +#else +typealias TunnelProviderManagerType = NETunnelProviderManager +#endif + +protocol TunnelStatusObserver { + func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) +} + +/// Tunnel wrapper class. +class Tunnel { + /// Tunnel provider manager. + fileprivate let tunnelProvider: TunnelProviderManagerType + + /// Tunnel start date. + /// + /// It's set to `distantPast` when the VPN connection was established prior to being observed + /// by the class. + var startDate: Date? { + lock.lock() + defer { lock.unlock() } + + return _startDate + } + + /// Tunnel connection status. + var status: NEVPNStatus { + return tunnelProvider.connection.status + } + + /// Whether on-demand VPN is enabled. + var isOnDemandEnabled: Bool { + get { + return tunnelProvider.isOnDemandEnabled + } + set { + tunnelProvider.isOnDemandEnabled = newValue + } + } + + private let lock = NSLock() + private var observerList = ObserverList<TunnelStatusObserver>() + + private var _startDate: Date? + + init(tunnelProvider: TunnelProviderManagerType) { + self.tunnelProvider = tunnelProvider + + NotificationCenter.default.addObserver( + self, selector: #selector(handleVPNStatusChangeNotification(_:)), + name: .NEVPNStatusDidChange, + object: tunnelProvider.connection + ) + + handleVPNStatus(tunnelProvider.connection.status) + } + + func start(options: [String: NSObject]?) throws { + try tunnelProvider.connection.startVPNTunnel(options: options) + } + + func stop() { + tunnelProvider.connection.stopVPNTunnel() + } + + func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws { + let session = tunnelProvider.connection as! VPNTunnelProviderSessionProtocol + + try session.sendProviderMessage(messageData, responseHandler: responseHandler) + } + + func saveToPreferences(_ completion: @escaping (Error?) -> Void) { + tunnelProvider.saveToPreferences(completionHandler: completion) + } + + func removeFromPreferences(completion: @escaping (Error?) -> Void) { + tunnelProvider.removeFromPreferences(completionHandler: completion) + } + + func addBlockObserver(queue: DispatchQueue? = nil, handler: @escaping (Tunnel, NEVPNStatus) -> Void) -> StatusBlockObserver { + let observer = StatusBlockObserver(tunnel: self, queue: queue, handler: handler) + + addObserver(observer) + + return observer + } + + func addObserver(_ observer: TunnelStatusObserver) { + observerList.append(observer) + } + + func removeObserver(_ observer: TunnelStatusObserver) { + observerList.remove(observer) + } + + @objc private func handleVPNStatusChangeNotification(_ notification: Notification) { + guard let connection = notification.object as? VPNConnectionProtocol else { return } + + let newStatus = connection.status + + handleVPNStatus(newStatus) + + observerList.forEach { observer in + observer.tunnel(self, didReceiveStatus: newStatus) + } + } + + private func handleVPNStatus(_ status: NEVPNStatus) { + switch status { + case .connecting: + lock.lock() + _startDate = Date() + lock.unlock() + + case .connected, .reasserting: + lock.lock() + if _startDate == nil { + _startDate = .distantPast + } + lock.unlock() + + case .disconnecting: + break + + case .disconnected, .invalid: + lock.lock() + _startDate = nil + lock.unlock() + + @unknown default: + break + } + } +} + +extension Tunnel: Equatable { + static func == (lhs: Tunnel, rhs: Tunnel) -> Bool { + return lhs.tunnelProvider == rhs.tunnelProvider + } +} + +extension Tunnel { + + final class StatusBlockObserver: TunnelStatusObserver { + typealias Handler = (Tunnel, NEVPNStatus) -> Void + + private weak var tunnel: Tunnel? + private let queue: DispatchQueue? + private let lock = NSLock() + private var handler: Handler? + + fileprivate init(tunnel: Tunnel, queue: DispatchQueue?, handler: @escaping Handler) { + self.tunnel = tunnel + self.queue = queue + self.handler = handler + } + + func invalidate() { + lock.lock() + handler = nil + lock.unlock() + + tunnel?.removeObserver(self) + } + + func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) { + if let queue = queue { + queue.async { + self.invokeHandler(tunnel: tunnel, status: status) + } + } else { + invokeHandler(tunnel: tunnel, status: status) + } + } + + private func invokeHandler(tunnel: Tunnel, status: NEVPNStatus) { + lock.lock() + let block = handler + lock.unlock() + + block?(tunnel, status) + } + } + +} diff --git a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift index 26ded3ff84..01c31491a1 100644 --- a/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift +++ b/ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift @@ -25,31 +25,30 @@ extension TunnelIPC { typealias CompletionHandler = (OperationCompletion<Output, TunnelIPC.Error>) -> Void private let queue: DispatchQueue - private let notificationQueue: OperationQueue - private let connection: VPNConnectionProtocol + private let tunnel: Tunnel private let request: TunnelIPC.Request private let options: RequestOptions private let decoderHandler: DecoderHandler private var completionHandler: CompletionHandler? - private var statusObserver: NSObjectProtocol? + private var statusObserver: Tunnel.StatusBlockObserver? private var timeoutWork: DispatchWorkItem? private var waitForConnectingStateWork: DispatchWorkItem? + private var requestSent = false + init(queue: DispatchQueue, - connection: VPNConnectionProtocol, + tunnel: Tunnel, request: TunnelIPC.Request, options: TunnelIPC.RequestOptions, decoderHandler: @escaping DecoderHandler, completionHandler: @escaping CompletionHandler) { self.queue = queue - self.notificationQueue = OperationQueue() - self.notificationQueue.underlyingQueue = queue - self.connection = connection + self.tunnel = tunnel self.request = request self.options = options @@ -79,29 +78,21 @@ extension TunnelIPC { return } - setTimeoutTimer(isConnectingState: false) - - statusObserver = NotificationCenter.default.addObserver( - forName: .NEVPNStatusDidChange, - object: connection, - queue: notificationQueue) { [weak self] notification in - guard let self = self else { return } - guard let connection = notification.object as? VPNConnectionProtocol else { return } + setTimeoutTimer(connectingStateWaitDelay: 0) - self.handleVPNStatus(connection.status) - } + statusObserver = tunnel.addBlockObserver(queue: queue) { [weak self] tunnel, status in + self?.handleVPNStatus(status) + } - handleVPNStatus(connection.status) + handleVPNStatus(tunnel.status) } private func removeVPNStatusObserver() { - if let statusObserver = statusObserver { - NotificationCenter.default.removeObserver(statusObserver) - self.statusObserver = nil - } + statusObserver?.invalidate() + statusObserver = nil } - private func setTimeoutTimer(isConnectingState: Bool) { + private func setTimeoutTimer(connectingStateWaitDelay: TimeInterval) { let workItem = DispatchWorkItem { [weak self] in self?.completeOperation(completion: .failure(.send(.timeout))) } @@ -112,9 +103,6 @@ extension TunnelIPC { // Assign new timeout work. timeoutWork = workItem - // Compute additional delay associated with connecting state. - let connectingStateWaitDelay = isConnectingState ? RequestOptions.connectingStateWaitDelay : 0 - // Schedule timeout work. let deadline: DispatchWallTime = .now() + options.timeout + connectingStateWaitDelay @@ -126,6 +114,10 @@ extension TunnelIPC { return } + guard !requestSent else { + return + } + switch status { case .connected: sendRequest() @@ -147,24 +139,43 @@ extension TunnelIPC { } private func waitForConnectingState(block: @escaping () -> Void) { - let workItem = DispatchWorkItem(block: block) + // Compute amount of time elapsed since the tunnel was launched. + let timeElapsed: TimeInterval + if let startDate = tunnel.startDate { + timeElapsed = Date().timeIntervalSince(startDate) + } else { + timeElapsed = 0 + } // Cancel pending work. waitForConnectingStateWork?.cancel() + waitForConnectingStateWork = nil + + // Execute right away if enough time passed since the tunnel was launched. + guard timeElapsed < RequestOptions.connectingStateWaitDelay else { + block() + return + } + + let waitDelay = RequestOptions.connectingStateWaitDelay - timeElapsed + let workItem = DispatchWorkItem(block: block) // Assign new work. waitForConnectingStateWork = workItem // Reschedule the timeout work. - setTimeoutTimer(isConnectingState: true) + setTimeoutTimer(connectingStateWaitDelay: waitDelay) // Schedule delayed work. - let deadline: DispatchWallTime = .now() + RequestOptions.connectingStateWaitDelay + let deadline: DispatchWallTime = .now() + waitDelay queue.asyncAfter(wallDeadline: deadline, execute: workItem) } private func sendRequest() { + // Mark request sent. + requestSent = true + // Release status observer. removeVPNStatusObserver() @@ -182,8 +193,7 @@ extension TunnelIPC { // Send IPC message. do { - let session = connection as! VPNTunnelProviderSessionProtocol - try session.sendProviderMessage(messageData) { [weak self] responseData in + try tunnel.sendProviderMessage(messageData) { [weak self] responseData in guard let self = self else { return } self.queue.async { @@ -218,7 +228,7 @@ extension TunnelIPC { extension TunnelIPC.RequestOperation where Output: Codable { convenience init( queue: DispatchQueue, - connection: VPNConnectionProtocol, + tunnel: Tunnel, request: TunnelIPC.Request, options: TunnelIPC.RequestOptions, completionHandler: @escaping CompletionHandler @@ -226,7 +236,7 @@ extension TunnelIPC.RequestOperation where Output: Codable { { self.init( queue: queue, - connection: connection, + tunnel: tunnel, request: request, options: options, decoderHandler: { data in @@ -246,14 +256,14 @@ extension TunnelIPC.RequestOperation where Output: Codable { extension TunnelIPC.RequestOperation where Output == Void { convenience init( queue: DispatchQueue, - connection: VPNConnectionProtocol, + tunnel: Tunnel, request: TunnelIPC.Request, options: TunnelIPC.RequestOptions, completionHandler: @escaping CompletionHandler ) { self.init( queue: queue, - connection: connection, + tunnel: tunnel, request: request, options: options, decoderHandler: { _ in .success(()) }, diff --git a/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift b/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift index dac8368fbc..5847af8b5c 100644 --- a/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift +++ b/ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift @@ -13,18 +13,18 @@ extension TunnelIPC { /// Wrapper class around `NETunnelProviderSession` that provides convenient interface for /// interacting with the Packet Tunnel process. final class Session { - private let connection: VPNConnectionProtocol + private let tunnel: Tunnel private let queue = DispatchQueue(label: "TunnelIPC.SessionQueue") private let operationQueue = OperationQueue() - init(connection: VPNConnectionProtocol) { - self.connection = connection + init(tunnel: Tunnel) { + self.tunnel = tunnel } func reloadTunnelSettings(completionHandler: @escaping (OperationCompletion<(), TunnelIPC.Error>) -> Void) -> Cancellable { let operation = RequestOperation( queue: queue, - connection: connection, + tunnel: tunnel, request: .reloadTunnelSettings, options: TunnelIPC.RequestOptions(), completionHandler: completionHandler @@ -40,7 +40,7 @@ extension TunnelIPC { func getTunnelConnectionInfo(completionHandler: @escaping (OperationCompletion<TunnelConnectionInfo?, TunnelIPC.Error>) -> Void) -> Cancellable { let operation = RequestOperation<TunnelConnectionInfo?>( queue: queue, - connection: connection, + tunnel: tunnel, request: .tunnelConnectionInfo, options: TunnelIPC.RequestOptions(), completionHandler: completionHandler diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift index 8cdb64bcab..fc393b7494 100644 --- a/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift @@ -118,7 +118,7 @@ class LoadTunnelOperation: AsyncOperation { let tunnelInfo = TunnelInfo(token: accountToken, tunnelSettings: keychainEntry.tunnelSettings) state.tunnelInfo = tunnelInfo - state.setTunnelProvider(tunnelProvider, shouldRefreshTunnelState: true) + state.setTunnel(Tunnel(tunnelProvider: tunnelProvider), shouldRefreshTunnelState: true) completionHandler(.success(())) diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index 6292b831b7..d69e2d1028 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -43,7 +43,7 @@ class MapConnectionStatusOperation: AsyncOperation { } private func execute() { - guard let tunnelProvider = state.tunnelProvider, !isCancelled else { + guard let tunnel = state.tunnel, !isCancelled else { finish() return } @@ -54,13 +54,27 @@ class MapConnectionStatusOperation: AsyncOperation { case .connecting: switch tunnelState { case .connecting(.some(_)): - logger.debug("Ignore repeating connecting state.") + break default: state.tunnelState = .connecting(nil) } + let session = TunnelIPC.Session(tunnel: tunnel) + + request = session.getTunnelConnectionInfo { [weak self] completion in + guard let self = self else { return } + + self.queue.async { + if case .success(.some(let connectionInfo)) = completion, !self.isCancelled { + self.state.tunnelState = .connecting(connectionInfo) + } + + self.finish() + } + } + case .reasserting: - let session = TunnelIPC.Session(connection: tunnelProvider.connection) + let session = TunnelIPC.Session(tunnel: tunnel) request = session.getTunnelConnectionInfo { [weak self] completion in guard let self = self else { return } @@ -77,7 +91,7 @@ class MapConnectionStatusOperation: AsyncOperation { return case .connected: - let session = TunnelIPC.Session(connection: tunnelProvider.connection) + let session = TunnelIPC.Session(tunnel: tunnel) request = session.getTunnelConnectionInfo { [weak self] completion in guard let self = self else { return } diff --git a/ios/MullvadVPN/TunnelManager/ReloadTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/ReloadTunnelOperation.swift index 3758c9e4e9..090a29f59f 100644 --- a/ios/MullvadVPN/TunnelManager/ReloadTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/ReloadTunnelOperation.swift @@ -29,12 +29,12 @@ class ReloadTunnelOperation: AsyncOperation { return } - guard let tunnelProvider = self.state.tunnelProvider else { + guard let tunnel = self.state.tunnel else { self.completeOperation(completion: .failure(.unsetAccount)) return } - let session = TunnelIPC.Session(connection: tunnelProvider.connection) + let session = TunnelIPC.Session(tunnel: tunnel) self.request = session.reloadTunnelSettings { [weak self] completion in guard let self = self else { return } diff --git a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift index f09ee90f68..b93eca2e72 100644 --- a/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SetAccountOperation.swift @@ -182,13 +182,13 @@ class SetAccountOperation: AsyncOperation { } // Finish immediately if tunnel provider is not set. - guard let tunnelProvider = state.tunnelProvider else { + guard let tunnel = state.tunnel else { completionHandler() return } // Remove VPN configuration - tunnelProvider.removeFromPreferences { error in + tunnel.removeFromPreferences { error in self.queue.async { // Ignore error but log it if let error = error { @@ -198,7 +198,7 @@ class SetAccountOperation: AsyncOperation { ) } - self.state.setTunnelProvider(nil, shouldRefreshTunnelState: false) + self.state.setTunnel(nil, shouldRefreshTunnelState: false) completionHandler() } diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index 1037253da5..2a332aab0b 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -114,7 +114,7 @@ class StartTunnelOperation: AsyncOperation { encodeErrorHandler = nil - state.setTunnelProvider(tunnelProvider, shouldRefreshTunnelState: false) + state.setTunnel(Tunnel(tunnelProvider: tunnelProvider), shouldRefreshTunnelState: false) state.tunnelState = .connecting(selectorResult.tunnelConnectionInfo) try tunnelProvider.connection.startVPNTunnel(options: tunnelOptions.rawOptions()) diff --git a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift index 297805eb8e..2d75946e7f 100644 --- a/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift @@ -38,7 +38,7 @@ class StopTunnelOperation: AsyncOperation { return } - guard let tunnelProvider = state.tunnelProvider else { + guard let tunnel = state.tunnel else { completionHandler(.failure(.unsetAccount)) return } @@ -51,14 +51,14 @@ class StopTunnelOperation: AsyncOperation { case .connected, .connecting: // Disable on-demand when stopping the tunnel to prevent it from coming back up - tunnelProvider.isOnDemandEnabled = false + tunnel.isOnDemandEnabled = false - tunnelProvider.saveToPreferences { error in + tunnel.saveToPreferences { error in self.queue.async { if let error = error { completionHandler(.failure(.saveVPNConfiguration(error))) } else { - tunnelProvider.connection.stopVPNTunnel() + tunnel.stop() completionHandler(.success(())) } } diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index 0ec7843ab0..0e4a379116 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -43,6 +43,7 @@ class TunnelManager: TunnelManagerStateDelegate private let operationQueue = OperationQueue() private let exclusivityController = ExclusivityController() + private var statusObserver: Tunnel.StatusBlockObserver? private var lastMapConnectionStatusOperation: Operation? private let observerList = ObserverList<TunnelObserver>() @@ -459,12 +460,12 @@ class TunnelManager: TunnelManagerStateDelegate } } - func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelProvider newTunnelProvider: TunnelProviderManagerType?, shouldRefreshTunnelState: Bool) { + func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelProvider newTunnelObject: Tunnel?, shouldRefreshTunnelState: Bool) { dispatchPrecondition(condition: .onQueue(stateQueue)) // Register for tunnel connection status changes - if let newTunnelProvider = newTunnelProvider { - subscribeVPNStatusObserver(for: newTunnelProvider) + if let newTunnelObject = newTunnelObject { + subscribeVPNStatusObserver(tunnel: newTunnelObject) } else { unsubscribeVPNStatusObserver() } @@ -477,24 +478,17 @@ class TunnelManager: TunnelManagerStateDelegate // MARK: - Private methods - private func subscribeVPNStatusObserver(for tunnelProvider: TunnelProviderManagerType) { + private func subscribeVPNStatusObserver(tunnel: Tunnel) { unsubscribeVPNStatusObserver() - NotificationCenter.default.addObserver( - self, selector: #selector(didReceiveVPNStatusChange(_:)), - name: .NEVPNStatusDidChange, - object: tunnelProvider.connection - ) + statusObserver = tunnel.addBlockObserver(queue: stateQueue) { [weak self] tunnel, status in + self?.updateTunnelState() + } } private func unsubscribeVPNStatusObserver() { - NotificationCenter.default.removeObserver(self, name: .NEVPNStatusDidChange, object: nil) - } - - @objc private func didReceiveVPNStatusChange(_ notification: Notification) { - stateQueue.async { - self.updateTunnelState() - } + statusObserver?.invalidate() + statusObserver = nil } /// Update `TunnelState` from `NEVPNStatus`. @@ -502,7 +496,7 @@ class TunnelManager: TunnelManagerStateDelegate private func updateTunnelState() { dispatchPrecondition(condition: .onQueue(stateQueue)) - guard let connectionStatus = self.state.tunnelProvider?.connection.status else { return } + guard let connectionStatus = self.state.tunnel?.status else { return } logger.debug("VPN status changed to \(connectionStatus)") diff --git a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift index f8ebaf86b6..0468d835a2 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManagerState.swift @@ -9,17 +9,10 @@ import Foundation import NetworkExtension -// Switch to stabs on simulator -#if targetEnvironment(simulator) -typealias TunnelProviderManagerType = SimulatorTunnelProviderManager -#else -typealias TunnelProviderManagerType = NETunnelProviderManager -#endif - protocol TunnelManagerStateDelegate: AnyObject { func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelInfo newTunnelInfo: TunnelInfo?) func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelState newTunnelState: TunnelState) - func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelProvider newTunnelProvider: TunnelProviderManagerType?, shouldRefreshTunnelState: Bool) + func tunnelManagerState(_ state: TunnelManager.State, didChangeTunnelProvider newTunnelObject: Tunnel?, shouldRefreshTunnelState: Bool) } extension TunnelManager { @@ -31,7 +24,7 @@ extension TunnelManager { private let queueMarkerKey = DispatchSpecificKey<Bool>() private var _tunnelInfo: TunnelInfo? - private var _tunnelProvider: TunnelProviderManagerType? + private var _tunnelObject: Tunnel? private var _tunnelState: TunnelState = .disconnected var tunnelInfo: TunnelInfo? { @@ -51,9 +44,9 @@ extension TunnelManager { } } - var tunnelProvider: TunnelProviderManagerType? { + var tunnel: Tunnel? { return performBlock { - return _tunnelProvider + return _tunnelObject } } @@ -84,12 +77,12 @@ extension TunnelManager { queue.setSpecific(key: queueMarkerKey, value: nil) } - func setTunnelProvider(_ newTunnelProvider: TunnelProviderManagerType?, shouldRefreshTunnelState: Bool) { + func setTunnel(_ newTunnelObject: Tunnel?, shouldRefreshTunnelState: Bool) { performBlock { - if _tunnelProvider != newTunnelProvider { - _tunnelProvider = newTunnelProvider + if _tunnelObject != newTunnelObject { + _tunnelObject = newTunnelObject - delegate?.tunnelManagerState(self, didChangeTunnelProvider: newTunnelProvider, shouldRefreshTunnelState: shouldRefreshTunnelState) + delegate?.tunnelManagerState(self, didChangeTunnelProvider: newTunnelObject, shouldRefreshTunnelState: shouldRefreshTunnelState) } } } |
