summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-12-08 16:16:02 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-12-08 16:16:02 +0100
commitbdbd4047299f4ee07ca24d652f3893f35741a98b (patch)
tree1cd5bee82ac321654de77d681010fbc2d46ab5ed
parent81c3e7fc12f29ceefb394f11d1486c83e847bcc7 (diff)
parent53f158a122adf02a4627c4d32952b58347a50fa5 (diff)
downloadmullvadvpn-bdbd4047299f4ee07ca24d652f3893f35741a98b.tar.xz
mullvadvpn-bdbd4047299f4ee07ca24d652f3893f35741a98b.zip
Merge branch 'tunnels'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme2
-rw-r--r--ios/MullvadVPN/AppDelegate.swift22
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider.swift14
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProviderHost.swift2
-rw-r--r--ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift11
-rw-r--r--ios/MullvadVPN/TransportMonitor/TransportMonitor.swift97
-rw-r--r--ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift18
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift69
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel.swift55
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelConfiguration.swift26
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelInteractor.swift3
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift54
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelStore.swift119
14 files changed, 315 insertions, 185 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 244617b56f..03f0efe43a 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -71,6 +71,8 @@
06D9845428F99133003AABE9 /* libMullvadLogging.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 581943D628F800C900B0CB5E /* libMullvadLogging.a */; };
06D9845A28F9918C003AABE9 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 06D9845928F9918C003AABE9 /* Logging */; };
06D9846328F9A049003AABE9 /* libMullvadLogging.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 581943D628F800C900B0CB5E /* libMullvadLogging.a */; };
+ 5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */; };
+ 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5803B4B12940A48700C23744 /* TunnelStore.swift */; };
5806767C27048E9B00C858CB /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58CE5E7B224146470008646E /* PacketTunnelProvider.swift */; };
5807483B27DB8A980020ECBF /* WireGuardKitTypes in Frameworks */ = {isa = PBXBuildFile; productRef = 5807483A27DB8A980020ECBF /* WireGuardKitTypes */; };
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5807E2BF2432038B00F5FF30 /* String+Split.swift */; };
@@ -571,6 +573,8 @@
06FAE67B28F83CA50033DD93 /* REST.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = REST.swift; sourceTree = "<group>"; };
06FAE67C28F83CA50033DD93 /* URLSessionTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSessionTransport.swift; sourceTree = "<group>"; };
06FAE67D28F83CA50033DD93 /* RESTTransport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RESTTransport.swift; sourceTree = "<group>"; };
+ 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelConfiguration.swift; sourceTree = "<group>"; };
+ 5803B4B12940A48700C23744 /* TunnelStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStore.swift; sourceTree = "<group>"; };
58059DDB28465E8F002B1049 /* TransformOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransformOperation.swift; sourceTree = "<group>"; };
58059DDD28468158002B1049 /* OutputOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputOperation.swift; sourceTree = "<group>"; };
58059DDF2846823E002B1049 /* ResultOperation+Output.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResultOperation+Output.swift"; sourceTree = "<group>"; };
@@ -1092,6 +1096,8 @@
58F2E143276A13F300A79513 /* StartTunnelOperation.swift */,
58F2E145276A2C9900A79513 /* StopTunnelOperation.swift */,
58E0A98727C8F46300FE6BDD /* Tunnel.swift */,
+ 5803B4B12940A48700C23744 /* TunnelStore.swift */,
+ 5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */,
5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */,
58968FAD28743E2000B799DC /* TunnelInteractor.swift */,
5835B7CB233B76CB0096D79F /* TunnelManager.swift */,
@@ -2159,6 +2165,7 @@
5846227126E229F20035F7C2 /* StoreSubscription.swift in Sources */,
58421030282D8A3C00F24E46 /* UpdateAccountDataOperation.swift in Sources */,
58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */,
+ 5803B4B02940A47300C23744 /* TunnelConfiguration.swift in Sources */,
587EB672271451E300123C75 /* PreferencesViewModel.swift in Sources */,
586A950C290125EE007BAF2B /* AlertPresenter.swift in Sources */,
584D26C6270C8741004EA533 /* SettingsDNSTextCell.swift in Sources */,
@@ -2298,6 +2305,7 @@
58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */,
587EB67027143B6500123C75 /* DataSourceSnapshot.swift in Sources */,
580F8B8328197881002E0998 /* TunnelSettingsV2.swift in Sources */,
+ 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */,
586A950F29012BEE007BAF2B /* AddressCacheTracker.swift in Sources */,
587B753D2666468F00DEF7E9 /* NotificationController.swift in Sources */,
);
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
index c0ec326af9..dc70ad4ec7 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
@@ -88,7 +88,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- launchStyle = "1"
+ launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 27515a798e..eec04a0851 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -31,6 +31,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
return operationQueue
}()
+ private(set) var tunnelStore: TunnelStore!
private(set) var tunnelManager: TunnelManager!
private(set) var addressCache: REST.AddressCache!
@@ -80,8 +81,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
store: addressCache
)
+ tunnelStore = TunnelStore(application: application)
+
tunnelManager = TunnelManager(
application: application,
+ tunnelStore: tunnelStore,
relayCacheTracker: relayCacheTracker,
accountsProxy: accountsProxy,
devicesProxy: devicesProxy
@@ -94,7 +98,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy: accountsProxy
)
- transportMonitor = TransportMonitor(tunnelManager: tunnelManager)
+ transportMonitor = TransportMonitor(tunnelManager: tunnelManager, tunnelStore: tunnelStore)
#if targetEnvironment(simulator)
// Configure mock tunnel provider on simulator
@@ -109,6 +113,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
setupNotificationHandler()
addApplicationNotifications(application: application)
+ let loadTunnelsOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in
+ self.tunnelStore.loadPersistentTunnels { error in
+ if let error = error {
+ self.logger.error(
+ error: error,
+ message: "Failed to load persistent tunnels."
+ )
+ }
+ operation.finish()
+ }
+ }
+
let migrateSettingsOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in
SettingsManager.migrateStore(with: self.proxyFactory) { error in
guard let error = error else {
@@ -128,6 +144,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
}
}
}
+ migrateSettingsOperation.addDependency(loadTunnelsOperation)
let loadTunnelConfigurationOperation =
AsyncBlockOperation(dispatchQueue: .main) { operation in
@@ -148,7 +165,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
loadTunnelConfigurationOperation.addDependency(migrateSettingsOperation)
operationQueue.addOperations(
- [migrateSettingsOperation, loadTunnelConfigurationOperation],
+ [loadTunnelsOperation, migrateSettingsOperation, loadTunnelConfigurationOperation],
waitUntilFinished: false
)
@@ -179,7 +196,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
// MARK: - Notifications
@objc private func didBecomeActive(_ notification: Notification) {
- tunnelManager.refreshTunnelStatus()
tunnelManager.startPeriodicPrivateKeyRotation()
relayCacheTracker.startPeriodicUpdates()
addressCacheTracker.startPeriodicUpdates()
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider.swift b/ios/MullvadVPN/SimulatorTunnelProvider.swift
index 4c7e0e091a..bf30237905 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider.swift
@@ -85,7 +85,7 @@ class SimulatorTunnelProviderDelegate {
}
}
-class SimulatorTunnelProvider {
+final class SimulatorTunnelProvider {
static let shared = SimulatorTunnelProvider()
private let lock = NSLock()
@@ -223,7 +223,9 @@ class SimulatorVPNConnection: NSObject, VPNConnectionProtocol {
// MARK: - NETunnelProviderSession stubs
-class SimulatorTunnelProviderSession: SimulatorVPNConnection, VPNTunnelProviderSessionProtocol {
+final class SimulatorTunnelProviderSession: SimulatorVPNConnection,
+ VPNTunnelProviderSessionProtocol
+{
func sendProviderMessage(_ messageData: Data, responseHandler: ((Data?) -> Void)?) throws {
SimulatorTunnelProvider.shared.handleAppMessage(
messageData,
@@ -267,7 +269,7 @@ private struct SimulatorTunnelInfo {
init() {}
}
-class SimulatorTunnelProviderManager: VPNTunnelProviderManagerProtocol, Equatable {
+final class SimulatorTunnelProviderManager: NSObject, VPNTunnelProviderManagerProtocol {
static let tunnelsLock = NSRecursiveLock()
fileprivate static var tunnels = [SimulatorTunnelInfo]()
@@ -370,12 +372,14 @@ class SimulatorTunnelProviderManager: VPNTunnelProviderManagerProtocol, Equatabl
completionHandler(tunnelProviders, nil)
}
- required convenience init() {
- self.init(tunnelInfo: SimulatorTunnelInfo())
+ override required init() {
+ tunnelInfo = SimulatorTunnelInfo()
+ super.init()
}
private init(tunnelInfo: SimulatorTunnelInfo) {
self.tunnelInfo = tunnelInfo
+ super.init()
}
func loadFromPreferences(completionHandler: (Error?) -> Void) {
diff --git a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
index 6068036cfe..1ddf7b295f 100644
--- a/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProviderHost.swift
@@ -17,7 +17,7 @@ import RelayCache
import RelaySelector
import TunnelProviderMessaging
-class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
+final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
private var selectorResult: RelaySelectorResult?
private let urlSession = REST.makeURLSession()
private var proxiedRequests = [UUID: URLSessionDataTask]()
diff --git a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
index 8c583afb4c..65f8f9196f 100644
--- a/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
+++ b/ios/MullvadVPN/TransportMonitor/PacketTunnelTransport.swift
@@ -11,15 +11,14 @@ import protocol MullvadREST.RESTTransport
import MullvadTypes
import TunnelProviderMessaging
-final class PacketTunnelTransport: RESTTransport {
+struct PacketTunnelTransport: RESTTransport {
var name: String {
return "packet-tunnel"
}
- let tunnelManager: TunnelManager
-
- init(tunnelManager: TunnelManager) {
- self.tunnelManager = tunnelManager
+ let tunnel: Tunnel
+ init(tunnel: Tunnel) {
+ self.tunnel = tunnel
}
func sendRequest(
@@ -28,7 +27,7 @@ final class PacketTunnelTransport: RESTTransport {
) throws -> Cancellable {
let proxyRequest = try ProxyURLRequest(id: UUID(), urlRequest: request)
- return try tunnelManager.sendRequest(proxyRequest) { result in
+ return tunnel.sendRequest(proxyRequest) { result in
switch result {
case .cancelled:
completion(nil, nil, URLError(.cancelled))
diff --git a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
index 972faef6b0..9f02429348 100644
--- a/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
+++ b/ios/MullvadVPN/TransportMonitor/TransportMonitor.swift
@@ -9,93 +9,42 @@
import Foundation
import MullvadREST
-class TransportMonitor: TunnelObserver {
+final class TransportMonitor {
private let tunnelManager: TunnelManager
- private let packetTunnelTransport: PacketTunnelTransport
+ private let tunnelStore: TunnelStore
private let urlSessionTransport: REST.URLSessionTransport
- private let nslock = NSLock()
- private var _transport: RESTTransport?
-
- var transport: RESTTransport? {
- nslock.lock()
- defer { nslock.unlock() }
-
- return _transport
- }
-
- init(tunnelManager: TunnelManager) {
+ init(tunnelManager: TunnelManager, tunnelStore: TunnelStore) {
self.tunnelManager = tunnelManager
+ self.tunnelStore = tunnelStore
- packetTunnelTransport = PacketTunnelTransport(tunnelManager: tunnelManager)
urlSessionTransport = REST.URLSessionTransport(urlSession: REST.makeURLSession())
-
- tunnelManager.addObserver(self)
-
- setTransports()
- }
-
- // MARK: - TunnelObserver
-
- func tunnelManager(_ manager: TunnelManager, didUpdateTunnelStatus tunnelStatus: TunnelStatus) {
- setTransports()
- }
-
- func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- setTransports()
}
- func tunnelManagerDidLoadConfiguration(_ manager: TunnelManager) {
- setTransports()
- }
-
- func tunnelManager(
- _ manager: TunnelManager,
- didUpdateTunnelSettings tunnelSettings: TunnelSettingsV2
- ) {}
-
- func tunnelManager(_ manager: TunnelManager, didFailWithError error: Error) {}
-
- // MARK: - Private
-
- private func setTransports() {
- nslock.lock()
- defer { nslock.unlock() }
-
- _transport = stateUpdated(
- tunnelState: tunnelManager.tunnelStatus.state,
- deviceState: tunnelManager.deviceState
- )
- }
-
- private func stateUpdated(
- tunnelState: TunnelState,
- deviceState: DeviceState
- ) -> RESTTransport {
- switch (tunnelState, deviceState) {
- case (.connected, .revoked):
- return packetTunnelTransport
-
- case (.pendingReconnect, _):
- return urlSessionTransport
+ var transport: RESTTransport? {
+ let tunnel = tunnelStore.getPersistentTunnels().first { tunnel in
+ return tunnel.status == .connecting ||
+ tunnel.status == .reasserting ||
+ tunnel.status == .connected
+ }
- case (.waitingForConnectivity, _):
+ if let tunnel = tunnel, shouldByPassVPN(tunnel: tunnel) {
+ return PacketTunnelTransport(tunnel: tunnel)
+ } else {
return urlSessionTransport
+ }
+ }
- case (.connecting, _):
- return packetTunnelTransport
-
- case (.reconnecting, _):
- return packetTunnelTransport
+ private func shouldByPassVPN(tunnel: Tunnel) -> Bool {
+ switch tunnel.status {
+ case .connected:
+ return tunnelManager.isConfigurationLoaded && tunnelManager.deviceState == .revoked
- case (.disconnecting, _):
- return urlSessionTransport
+ case .connecting, .reasserting:
+ return true
- case (.disconnected, _):
- return urlSessionTransport
-
- case (.connected, _):
- return urlSessionTransport
+ default:
+ return false
}
}
}
diff --git a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
index 715ca595b2..4f5bfa60b8 100644
--- a/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/LoadTunnelConfigurationOperation.swift
@@ -22,25 +22,11 @@ class LoadTunnelConfigurationOperation: ResultOperation<Void, Error> {
}
override func main() {
- TunnelProviderManagerType.loadAllFromPreferences { tunnels, error in
- self.dispatchQueue.async {
- if let error = error {
- self.finish(completion: .failure(error))
- } else {
- self.didLoadVPNConfigurations(tunnels: tunnels)
- }
- }
- }
- }
-
- private func didLoadVPNConfigurations(tunnels: [TunnelProviderManagerType]?) {
let settingsResult = readSettings()
let deviceStateResult = readDeviceState()
- let tunnel = tunnels?.first.map { tunnelProvider in
- return Tunnel(tunnelProvider: tunnelProvider)
- }
-
+ let persistentTunnels = interactor.getPersistentTunnels()
+ let tunnel = persistentTunnels.first
let settings = settingsResult.flattenValue()
let deviceState = deviceStateResult.flattenValue()
diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
index e2f9fa1c37..12934a4790 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -69,13 +69,11 @@ class StartTunnelOperation: ResultOperation<Void, Error> {
selectorResult: RelaySelectorResult,
completionHandler: @escaping (Error?) -> Void
) {
- Self.makeTunnelProvider { result in
+ makeTunnelProvider { result in
self.dispatchQueue.async {
do {
- let tunnelProvider = try result.get()
-
try self.startTunnel(
- tunnelProvider: tunnelProvider,
+ tunnel: try result.get(),
selectorResult: selectorResult
)
@@ -87,10 +85,7 @@ class StartTunnelOperation: ResultOperation<Void, Error> {
}
}
- private func startTunnel(
- tunnelProvider: TunnelProviderManagerType,
- selectorResult: RelaySelectorResult
- ) throws {
+ private func startTunnel(tunnel: Tunnel, selectorResult: RelaySelectorResult) throws {
var tunnelOptions = PacketTunnelOptions()
do {
@@ -102,10 +97,7 @@ class StartTunnelOperation: ResultOperation<Void, Error> {
)
}
- interactor.setTunnel(
- Tunnel(tunnelProvider: tunnelProvider),
- shouldRefreshTunnelState: false
- )
+ interactor.setTunnel(tunnel, shouldRefreshTunnelState: false)
interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus = TunnelStatus()
@@ -113,54 +105,35 @@ class StartTunnelOperation: ResultOperation<Void, Error> {
tunnelStatus.state = .connecting(selectorResult.packetTunnelRelay)
}
- try tunnelProvider.connection.startVPNTunnel(options: tunnelOptions.rawOptions())
+ try tunnel.start(options: tunnelOptions.rawOptions())
}
- private class func makeTunnelProvider(
- completionHandler: @escaping (Result<
- TunnelProviderManagerType,
- Error
- >) -> Void
- ) {
- TunnelProviderManagerType.loadAllFromPreferences { tunnelProviders, error in
- if let error = error {
- completionHandler(.failure(error))
- return
- }
-
- let tunnelProvider = tunnelProviders?.first ?? TunnelProviderManagerType()
-
- configureTunnelProvider(tunnelProvider)
+ private func makeTunnelProvider(completionHandler: @escaping (Result<Tunnel, Error>) -> Void) {
+ let persistentTunnels = interactor.getPersistentTunnels()
+ let tunnel = persistentTunnels.first ?? interactor.createNewTunnel()
+ let configuration = Self.makeTunnelConfiguration()
- tunnelProvider.saveToPreferences { error in
- if let error = error {
- completionHandler(.failure(error))
- } else {
- // Refresh connection status after saving the tunnel preferences.
- // Basically it's only necessary to do for new instances of
- // `NETunnelProviderManager`, but we do that for the existing ones too
- // for simplicity as it has no side effects.
- tunnelProvider.loadFromPreferences { error in
- completionHandler(error.map { .failure($0) } ?? .success(tunnelProvider))
- }
- }
- }
+ tunnel.setConfiguration(configuration)
+ tunnel.saveToPreferences { error in
+ completionHandler(error.map { .failure($0) } ?? .success(tunnel))
}
}
- private class func configureTunnelProvider(_ tunnelProvider: TunnelProviderManagerType) {
+ private class func makeTunnelConfiguration() -> TunnelConfiguration {
let protocolConfig = NETunnelProviderProtocol()
protocolConfig.providerBundleIdentifier = ApplicationConfiguration
.packetTunnelExtensionIdentifier
protocolConfig.serverAddress = ""
- tunnelProvider.isEnabled = true
- tunnelProvider.localizedDescription = "WireGuard"
- tunnelProvider.protocolConfiguration = protocolConfig
-
let alwaysOnRule = NEOnDemandRuleConnect()
alwaysOnRule.interfaceTypeMatch = .any
- tunnelProvider.onDemandRules = [alwaysOnRule]
- tunnelProvider.isOnDemandEnabled = true
+
+ return TunnelConfiguration(
+ isEnabled: true,
+ localizedDescription: "WireGuard",
+ protocolConfiguration: protocolConfig,
+ onDemandRules: [alwaysOnRule],
+ isOnDemandEnabled: true
+ )
}
}
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel.swift b/ios/MullvadVPN/TunnelManager/Tunnel.swift
index 260037b7a4..36ef198246 100644
--- a/ios/MullvadVPN/TunnelManager/Tunnel.swift
+++ b/ios/MullvadVPN/TunnelManager/Tunnel.swift
@@ -21,9 +21,29 @@ protocol TunnelStatusObserver {
}
/// Tunnel wrapper class.
-class Tunnel {
- /// Tunnel provider manager.
- private let tunnelProvider: TunnelProviderManagerType
+final class Tunnel: Equatable {
+ /// Unique identifier assigned to instance at the time of creation.
+ let identifier = UUID()
+
+ #if DEBUG
+ /// System VPN configuration identifier.
+ /// This property performs a private call to obtain system configuration ID so it does not
+ /// guarantee to return anything, also it may not return anything for newly created tunnels.
+ var systemIdentifier: UUID? {
+ let configurationKey = "configuration"
+ let identifierKey = "identifier"
+
+ guard tunnelProvider.responds(to: NSSelectorFromString(configurationKey)),
+ let config = tunnelProvider.value(forKey: configurationKey) as? NSObject,
+ config.responds(to: NSSelectorFromString(identifierKey)),
+ let identifier = config.value(forKey: identifierKey) as? UUID
+ else {
+ return nil
+ }
+
+ return identifier
+ }
+ #endif
/// Tunnel start date.
///
@@ -51,10 +71,21 @@ class Tunnel {
}
}
+ func logFormat() -> String {
+ var s = identifier.uuidString
+ #if DEBUG
+ if let configurationIdentifier = systemIdentifier?.uuidString {
+ s += " (system profile ID: \(configurationIdentifier))"
+ }
+ #endif
+ return s
+ }
+
private let lock = NSLock()
private var observerList = ObserverList<TunnelStatusObserver>()
private var _startDate: Date?
+ private let tunnelProvider: TunnelProviderManagerType
init(tunnelProvider: TunnelProviderManagerType) {
self.tunnelProvider = tunnelProvider
@@ -82,8 +113,22 @@ class Tunnel {
try session.sendProviderMessage(messageData, responseHandler: responseHandler)
}
+ func setConfiguration(_ configuration: TunnelConfiguration) {
+ configuration.apply(to: tunnelProvider)
+ }
+
func saveToPreferences(_ completion: @escaping (Error?) -> Void) {
- tunnelProvider.saveToPreferences(completionHandler: completion)
+ tunnelProvider.saveToPreferences { error in
+ if let error = error {
+ completion(error)
+ } else {
+ // Refresh connection status after saving the tunnel preferences.
+ // Basically it's only necessary to do for new instances of
+ // `NETunnelProviderManager`, but we do that for the existing ones too
+ // for simplicity as it has no side effects.
+ self.tunnelProvider.loadFromPreferences(completionHandler: completion)
+ }
+ }
}
func removeFromPreferences(completion: @escaping (Error?) -> Void) {
@@ -147,9 +192,7 @@ class Tunnel {
break
}
}
-}
-extension Tunnel: Equatable {
static func == (lhs: Tunnel, rhs: Tunnel) -> Bool {
return lhs.tunnelProvider == rhs.tunnelProvider
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelConfiguration.swift b/ios/MullvadVPN/TunnelManager/TunnelConfiguration.swift
new file mode 100644
index 0000000000..2af8b57263
--- /dev/null
+++ b/ios/MullvadVPN/TunnelManager/TunnelConfiguration.swift
@@ -0,0 +1,26 @@
+//
+// TunnelConfiguration.swift
+// MullvadVPN
+//
+// Created by pronebird on 07/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import NetworkExtension
+
+struct TunnelConfiguration {
+ var isEnabled: Bool
+ var localizedDescription: String
+ var protocolConfiguration: NETunnelProviderProtocol
+ var onDemandRules: [NEOnDemandRule]
+ var isOnDemandEnabled: Bool
+
+ func apply(to manager: TunnelProviderManagerType) {
+ manager.isEnabled = isEnabled
+ manager.localizedDescription = localizedDescription
+ manager.protocolConfiguration = protocolConfiguration
+ manager.onDemandRules = onDemandRules
+ manager.isOnDemandEnabled = isOnDemandEnabled
+ }
+}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
index eec47e1863..2f95622fbc 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
@@ -14,6 +14,9 @@ protocol TunnelInteractor {
// MARK: - Tunnel manipulation
var tunnel: Tunnel? { get }
+
+ func getPersistentTunnels() -> [Tunnel]
+ func createNewTunnel() -> Tunnel
func setTunnel(_ tunnel: Tunnel?, shouldRefreshTunnelState: Bool)
// MARK: - Tunnel status
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index c163f992f2..0489d70229 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -50,6 +50,7 @@ final class TunnelManager: StorePaymentObserver {
// MARK: - Internal variables
private let application: UIApplication
+ fileprivate let tunnelStore: TunnelStore
private let relayCacheTracker: RelayCacheTracker
private let accountsProxy: REST.AccountsProxy
private let devicesProxy: REST.DevicesProxy
@@ -87,16 +88,25 @@ final class TunnelManager: StorePaymentObserver {
init(
application: UIApplication,
+ tunnelStore: TunnelStore,
relayCacheTracker: RelayCacheTracker,
accountsProxy: REST.AccountsProxy,
devicesProxy: REST.DevicesProxy
) {
self.application = application
+ self.tunnelStore = tunnelStore
self.relayCacheTracker = relayCacheTracker
self.accountsProxy = accountsProxy
self.devicesProxy = devicesProxy
self.operationQueue.name = "TunnelManager.operationQueue"
self.operationQueue.underlyingQueue = internalQueue
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(applicationDidBecomeActive(_:)),
+ name: UIApplication.didBecomeActiveNotification,
+ object: application
+ )
}
// MARK: - Periodic private key rotation
@@ -242,13 +252,6 @@ final class TunnelManager: StorePaymentObserver {
operationQueue.addOperation(loadTunnelOperation)
}
- func refreshTunnelStatus() {
- #if DEBUG
- logger.debug("Refresh tunnel status due to application becoming active.")
- #endif
- _refreshTunnelStatus()
- }
-
func startTunnel(completionHandler: ((OperationCompletion<Void, Error>) -> Void)? = nil) {
let operation = StartTunnelOperation(
dispatchQueue: internalQueue,
@@ -538,20 +541,6 @@ final class TunnelManager: StorePaymentObserver {
)
}
- /// Send URL request via packet tunnel process bypassing VPN.
- /// This function is primarily used by `PacketTunnelTransport` to go outside of VPN when the
- /// tunnel is broken.
- func sendRequest(
- _ proxyRequest: ProxyURLRequest,
- completionHandler: @escaping (OperationCompletion<ProxyURLResponse, Error>) -> Void
- ) throws -> Cancellable {
- if let tunnel = tunnel {
- return tunnel.sendRequest(proxyRequest, completionHandler: completionHandler)
- } else {
- throw UnsetTunnelError()
- }
- }
-
// MARK: - Tunnel observeration
/// Add tunnel observer.
@@ -663,7 +652,7 @@ final class TunnelManager: StorePaymentObserver {
// Update the existing state
if shouldRefreshTunnelState {
logger.debug("Refresh tunnel status for new tunnel.")
- _refreshTunnelStatus()
+ refreshTunnelStatus()
}
}
@@ -785,6 +774,13 @@ final class TunnelManager: StorePaymentObserver {
// MARK: - Private methods
+ @objc private func applicationDidBecomeActive(_ notification: Notification) {
+ #if DEBUG
+ logger.debug("Refresh tunnel status due to application becoming active.")
+ #endif
+ refreshTunnelStatus()
+ }
+
fileprivate func selectRelay() throws -> RelaySelectorResult {
let cachedRelays = try relayCacheTracker.getCachedRelays()
@@ -839,7 +835,7 @@ final class TunnelManager: StorePaymentObserver {
switch tunnelStatus.state {
case .connecting, .reconnecting:
logger.debug("Refresh tunnel status due to reconnect.")
- _refreshTunnelStatus()
+ refreshTunnelStatus()
default:
break
@@ -869,7 +865,7 @@ final class TunnelManager: StorePaymentObserver {
statusObserver = nil
}
- private func _refreshTunnelStatus() {
+ private func refreshTunnelStatus() {
nslock.lock()
defer { nslock.unlock() }
@@ -989,7 +985,7 @@ final class TunnelManager: StorePaymentObserver {
let timer = DispatchSource.makeTimerSource(queue: .main)
timer.setEventHandler { [weak self] in
- self?._refreshTunnelStatus()
+ self?.refreshTunnelStatus()
}
timer.schedule(wallDeadline: .now() + interval, repeating: interval)
timer.activate()
@@ -1020,6 +1016,14 @@ private struct TunnelInteractorProxy: TunnelInteractor {
return tunnelManager.tunnel
}
+ func getPersistentTunnels() -> [Tunnel] {
+ return tunnelManager.tunnelStore.getPersistentTunnels()
+ }
+
+ func createNewTunnel() -> Tunnel {
+ return tunnelManager.tunnelStore.createNewTunnel()
+ }
+
func setTunnel(_ tunnel: Tunnel?, shouldRefreshTunnelState: Bool) {
tunnelManager.setTunnel(tunnel, shouldRefreshTunnelState: shouldRefreshTunnelState)
}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelStore.swift b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
new file mode 100644
index 0000000000..07266bcf17
--- /dev/null
+++ b/ios/MullvadVPN/TunnelManager/TunnelStore.swift
@@ -0,0 +1,119 @@
+//
+// TunnelStore.swift
+// MullvadVPN
+//
+// Created by pronebird on 07/12/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import MullvadLogging
+import NetworkExtension
+import UIKit
+
+/// Wrapper around system VPN tunnels.
+final class TunnelStore: TunnelStatusObserver {
+ private let logger = Logger(label: "TunnelStore")
+ private let lock = NSLock()
+
+ /// Persistent tunnels registered with the system.
+ private var persistentTunnels: [Tunnel] = []
+
+ /// Newly created tunnels, stored as collection of weak boxes.
+ private var newTunnels: [WeakBox<Tunnel>] = []
+
+ init(application: UIApplication) {
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(applicationDidBecomeActive(_:)),
+ name: UIApplication.didBecomeActiveNotification,
+ object: application
+ )
+ }
+
+ func getPersistentTunnels() -> [Tunnel] {
+ lock.lock()
+ defer { lock.unlock() }
+
+ return persistentTunnels
+ }
+
+ func loadPersistentTunnels(completion: @escaping (Error?) -> Void) {
+ TunnelProviderManagerType.loadAllFromPreferences { managers, error in
+ self.lock.lock()
+ defer {
+ self.lock.unlock()
+
+ completion(error)
+ }
+
+ guard error == nil else { return }
+
+ self.persistentTunnels.forEach { tunnel in
+ tunnel.removeObserver(self)
+ }
+
+ self.persistentTunnels = managers?.map { manager in
+ let tunnel = Tunnel(tunnelProvider: manager)
+ tunnel.addObserver(self)
+
+ self.logger.debug(
+ "Loaded persistent tunnel: \(tunnel.logFormat()) with status: \(tunnel.status)."
+ )
+
+ return tunnel
+ } ?? []
+ }
+ }
+
+ func createNewTunnel() -> Tunnel {
+ lock.lock()
+ defer { lock.unlock() }
+
+ let tunnelProviderManager = TunnelProviderManagerType()
+ let tunnel = Tunnel(tunnelProvider: tunnelProviderManager)
+ tunnel.addObserver(self)
+
+ newTunnels = newTunnels.filter { $0.value != nil }
+ newTunnels.append(WeakBox(tunnel))
+
+ logger.debug("Create new tunnel: \(tunnel.logFormat()).")
+
+ return tunnel
+ }
+
+ func tunnel(_ tunnel: Tunnel, didReceiveStatus status: NEVPNStatus) {
+ lock.lock()
+ defer { lock.unlock() }
+
+ handleTunnelStatus(tunnel: tunnel, status: status)
+ }
+
+ private func handleTunnelStatus(tunnel: Tunnel, status: NEVPNStatus) {
+ if status == .invalid, let index = persistentTunnels.firstIndex(of: tunnel) {
+ persistentTunnels.remove(at: index)
+ logger.debug("Persistent tunnel was removed: \(tunnel.logFormat()).")
+ }
+
+ if status != .invalid, let index = newTunnels.firstIndex(where: { $0.value == tunnel }) {
+ newTunnels.remove(at: index)
+ persistentTunnels.append(tunnel)
+ logger.debug("New tunnel became persistent: \(tunnel.logFormat()).")
+ }
+ }
+
+ @objc private func applicationDidBecomeActive(_ notification: Notification) {
+ refreshStatus()
+ }
+
+ private func refreshStatus() {
+ lock.lock()
+ defer { lock.unlock() }
+
+ let allTunnels = persistentTunnels + newTunnels.compactMap { $0.value }
+
+ for tunnel in allTunnels {
+ handleTunnelStatus(tunnel: tunnel, status: tunnel.status)
+ }
+ }
+}