summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-02-25 15:24:47 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-03-17 09:51:04 +0100
commit2a28572bc02ce8acff97d38925cdd01dbdeda365 (patch)
treed8f01a2e6298880b6c03df7a065e47c22f4b202f
parent46338f67333797d4623b61ef5bf017f0d7a7767a (diff)
downloadmullvadvpn-2a28572bc02ce8acff97d38925cdd01dbdeda365.tar.xz
mullvadvpn-2a28572bc02ce8acff97d38925cdd01dbdeda365.zip
Wrap TunnelProviderManager into Tunnel
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/Tunnel.swift198
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelIPCRequestOperation.swift80
-rw-r--r--ios/MullvadVPN/TunnelIPC/TunnelIPCSession.swift10
-rw-r--r--ios/MullvadVPN/TunnelManager/LoadTunnelOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift22
-rw-r--r--ios/MullvadVPN/TunnelManager/ReloadTunnelOperation.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/SetAccountOperation.swift6
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/StopTunnelOperation.swift8
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift28
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManagerState.swift23
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)
}
}
}