From f619260782ace9670e689d9b3faa7b6471306bb8 Mon Sep 17 00:00:00 2001 From: Jon Petersson Date: Wed, 18 Oct 2023 11:31:53 +0200 Subject: [PATCH] Prevent iOS from stopping the tunnel if it remains in connecting state for too long --- ios/MullvadVPN.xcodeproj/project.pbxproj | 8 -- .../SimulatorTunnelProviderHost.swift | 46 ++++++++--- .../MapConnectionStatusOperation.swift | 79 ++++++------------- .../TunnelManager/StartTunnelOperation.swift | 3 +- .../TunnelManager/Tunnel+Messaging.swift | 2 +- .../TunnelManager/TunnelManager.swift | 4 +- .../TunnelManager/TunnelState.swift | 12 +-- .../Tunnel/TunnelControlView.swift | 2 +- .../PacketTunnelProvider.swift | 16 +++- .../Actor/ObservedState+Extensions.swift | 31 -------- .../Actor/ObservedState.swift | 46 ++++++++++- .../Protocols/RelaySelectorProtocol.swift | 12 --- .../IPC/AppMessageHandler.swift | 2 +- .../IPC/PacketTunnelRelay.swift | 37 --------- .../IPC/PacketTunnelStatus.swift | 43 ---------- 15 files changed, 131 insertions(+), 212 deletions(-) delete mode 100644 ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift delete mode 100644 ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ba0d868cd389..508b0120da7b 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -273,8 +273,6 @@ 58C7A4702A8649ED0060C66F /* PingerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C7A46F2A8649ED0060C66F /* PingerTests.swift */; }; 58C7AF112ABD8480007EDD7A /* TunnelProviderMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */; }; 58C7AF122ABD8480007EDD7A /* TunnelProviderReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5898D2A7290182B000EB5EBA /* TunnelProviderReply.swift */; }; - 58C7AF132ABD8480007EDD7A /* PacketTunnelStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */; }; - 58C7AF152ABD8480007EDD7A /* PacketTunnelRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */; }; 58C7AF162ABD84A8007EDD7A /* URLRequestProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D229B6298D1D5200BB5A2D /* URLRequestProxy.swift */; }; 58C7AF172ABD84AA007EDD7A /* ProxyURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 063687AF28EB083800BE7161 /* ProxyURLRequest.swift */; }; 58C7AF182ABD84AB007EDD7A /* ProxyURLResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5898D2AD290185D200EB5EBA /* ProxyURLResponse.swift */; }; @@ -1268,7 +1266,6 @@ 585CA70E25F8C44600B47C62 /* UIMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIMetrics.swift; sourceTree = ""; }; 585DA87626B024A600B8C587 /* CachedRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRelays.swift; sourceTree = ""; }; 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderMessage.swift; sourceTree = ""; }; - 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelStatus.swift; sourceTree = ""; }; 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = ""; }; 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryInAppNotificationProvider.swift; sourceTree = ""; }; 586168682976F6BD00EF8598 /* DisplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayError.swift; sourceTree = ""; }; @@ -1358,7 +1355,6 @@ 5898D2AD290185D200EB5EBA /* ProxyURLResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyURLResponse.swift; sourceTree = ""; }; 5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLocation.swift; sourceTree = ""; }; 5898D2B12902A6DE00EB5EBA /* RelayConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraint.swift; sourceTree = ""; }; - 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelRelay.swift; sourceTree = ""; }; 589A455228E094B300565204 /* OperationsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OperationsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 589C6A7A2A45ACCA00DAD3EF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 589C6A7B2A45AE0100DAD3EF /* TunnelObfuscation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TunnelObfuscation.h; sourceTree = ""; }; @@ -2599,8 +2595,6 @@ children = ( 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */, 587C575226D2615F005EF767 /* PacketTunnelOptions.swift */, - 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */, - 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */, 585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */, 5898D2A7290182B000EB5EBA /* TunnelProviderReply.swift */, ); @@ -4206,7 +4200,6 @@ 58C7AF112ABD8480007EDD7A /* TunnelProviderMessage.swift in Sources */, 58C7AF162ABD84A8007EDD7A /* URLRequestProxy.swift in Sources */, 58FE25D72AA72A8F003D1918 /* State.swift in Sources */, - 58C7AF132ABD8480007EDD7A /* PacketTunnelStatus.swift in Sources */, 58C7A4592A863FB90060C66F /* WgStats.swift in Sources */, 7AD0AA1F2AD6C8B900119E10 /* URLRequestProxyProtocol.swift in Sources */, 7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */, @@ -4223,7 +4216,6 @@ 58342C042AAB61FB003BA12D /* State+Extensions.swift in Sources */, 583832272AC3193600EA2071 /* PacketTunnelActor+SleepCycle.swift in Sources */, 58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */, - 58C7AF152ABD8480007EDD7A /* PacketTunnelRelay.swift in Sources */, 58FE25D92AA72A8F003D1918 /* AutoCancellingTask.swift in Sources */, 58FE25E12AA72A9B003D1918 /* SettingsReaderProtocol.swift in Sources */, 58C7A4582A863FB90060C66F /* TunnelMonitorProtocol.swift in Sources */, diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift index d09a3e94ab3a..9e650bdfbf82 100644 --- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift +++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift @@ -20,6 +20,7 @@ import RelayCache import RelaySelector final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { + private var observedState: ObservedState = .disconnected private var selectedRelay: SelectedRelay? private let urlRequestProxy: URLRequestProxy private let relayCacheTracker: RelayCacheTracker @@ -39,7 +40,9 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void ) { - dispatchQueue.async { + dispatchQueue.async { [weak self] in + guard let self else { return } + var selectedRelay: SelectedRelay? do { @@ -47,7 +50,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { selectedRelay = try tunnelOptions.getSelectedRelay() } catch { - self.providerLogger.error( + providerLogger.error( error: error, message: """ Failed to decode selected relay passed from the app. \ @@ -57,11 +60,10 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { } do { - self.selectedRelay = try selectedRelay ?? self.pickRelay() - + setInternalStateConnected(with: try selectedRelay ?? pickRelay()) completionHandler(nil) } catch { - self.providerLogger.error( + providerLogger.error( error: error, message: "Failed to pick relay." ) @@ -71,8 +73,9 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { } override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - dispatchQueue.async { - self.selectedRelay = nil + dispatchQueue.async { [weak self] in + self?.selectedRelay = nil + self?.observedState = .disconnected completionHandler() } @@ -98,12 +101,9 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { private func handleProviderMessage(_ message: TunnelProviderMessage, completionHandler: ((Data?) -> Void)?) { switch message { case .getTunnelStatus: - var tunnelStatus = PacketTunnelStatus() - tunnelStatus.tunnelRelay = self.selectedRelay?.packetTunnelRelay - var reply: Data? do { - reply = try TunnelProviderReply(tunnelStatus).encode() + reply = try TunnelProviderReply(observedState).encode() } catch { self.providerLogger.error( error: error, @@ -115,6 +115,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { case let .reconnectTunnel(nextRelay): reasserting = true + switch nextRelay { case let .preSelected(selectedRelay): self.selectedRelay = selectedRelay @@ -125,7 +126,10 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { case .current: break } + + setInternalStateConnected(with: selectedRelay) reasserting = false + completionHandler?(nil) case let .sendURLRequest(proxyRequest): @@ -166,6 +170,26 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate { location: selectorResult.location ) } + + private func setInternalStateConnected(with selectedRelay: SelectedRelay?) { + guard let selectedRelay = selectedRelay else { return } + + do { + observedState = .connected( + ObservedConnectionState( + selectedRelay: selectedRelay, + relayConstraints: try SettingsManager.readSettings().relayConstraints, + networkReachability: .reachable, + connectionAttemptCount: 0 + ) + ) + } catch { + providerLogger.error( + error: error, + message: "Failed to read device settings." + ) + } + } } #endif diff --git a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift index 5282a2a7d57a..747e379c4671 100644 --- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift +++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift @@ -46,30 +46,21 @@ class MapConnectionStatusOperation: AsyncOperation { let tunnelState = interactor.tunnelStatus.state switch connectionStatus { - case .connecting: - handleConnectingState(tunnelState, tunnel) - return - - case .reasserting: - fetchTunnelStatus(tunnel: tunnel) { packetTunnelStatus in - if let blockedStateReason = packetTunnelStatus.blockedStateReason { - return .error(blockedStateReason) - } else if packetTunnelStatus.isNetworkReachable { - return packetTunnelStatus.tunnelRelay.map { .reconnecting($0) } - } else { - return .waitingForConnectivity(.noConnection) - } - } - return - - case .connected: - fetchTunnelStatus(tunnel: tunnel) { packetTunnelStatus in - if let blockedStateReason = packetTunnelStatus.blockedStateReason { - return .error(blockedStateReason) - } else if packetTunnelStatus.isNetworkReachable { - return packetTunnelStatus.tunnelRelay.map { .connected($0) } - } else { - return .waitingForConnectivity(.noConnection) + case .connecting, .reasserting, .connected: + fetchTunnelStatus(tunnel: tunnel) { observedState in + switch observedState { + case let .connected(connectionState): + return connectionState.isNetworkReachable + ? .connected(connectionState.selectedRelay) + : .waitingForConnectivity(.noConnection) + case let .connecting(connectionState): + return connectionState.isNetworkReachable + ? .connecting(connectionState.selectedRelay) + : .waitingForConnectivity(.noConnection) + case let .error(blockedState): + return .error(blockedState.reason) + default: + return .none } } return @@ -78,7 +69,7 @@ class MapConnectionStatusOperation: AsyncOperation { handleDisconnectedState(tunnelState) case .disconnecting: - handleDisconnectionState(tunnelState) + handleDisconnectingState(tunnelState) case .invalid: setTunnelDisconnectedStatus() @@ -94,38 +85,16 @@ class MapConnectionStatusOperation: AsyncOperation { request?.cancel() } - private func handleConnectingState(_ tunnelState: TunnelState, _ tunnel: any TunnelProtocol) { - switch tunnelState { - case .connecting: - break - - default: - interactor.updateTunnelStatus { tunnelStatus in - tunnelStatus.state = .connecting(nil) - } - } - - fetchTunnelStatus(tunnel: tunnel) { packetTunnelStatus in - if let blockedStateReason = packetTunnelStatus.blockedStateReason { - return .error(blockedStateReason) - } else if packetTunnelStatus.isNetworkReachable { - return packetTunnelStatus.tunnelRelay.map { .connecting($0) } - } else { - return .waitingForConnectivity(.noConnection) - } - } - } - - private func handleDisconnectionState(_ tunnelState: TunnelState) { + private func handleDisconnectingState(_ tunnelState: TunnelState) { switch tunnelState { case .disconnecting: break default: interactor.updateTunnelStatus { tunnelStatus in - let packetTunnelStatus = tunnelStatus.packetTunnelStatus + let isNetworkReachable = tunnelStatus.observedState.connectionState?.isNetworkReachable ?? false tunnelStatus = TunnelStatus() - tunnelStatus.state = packetTunnelStatus.isNetworkReachable + tunnelStatus.state = isNetworkReachable ? .disconnecting(.nothing) : .waitingForConnectivity(.noNetwork) } @@ -161,17 +130,17 @@ class MapConnectionStatusOperation: AsyncOperation { private func fetchTunnelStatus( tunnel: any TunnelProtocol, - mapToState: @escaping (PacketTunnelStatus) -> TunnelState? + mapToState: @escaping (ObservedState) -> TunnelState? ) { - request = tunnel.getTunnelStatus { [weak self] completion in + request = tunnel.getTunnelStatus { [weak self] result in guard let self else { return } dispatchQueue.async { - if case let .success(packetTunnelStatus) = completion, !self.isCancelled { + if case let .success(observedState) = result, !self.isCancelled { self.interactor.updateTunnelStatus { tunnelStatus in - tunnelStatus.packetTunnelStatus = packetTunnelStatus + tunnelStatus.observedState = observedState - if let newState = mapToState(packetTunnelStatus) { + if let newState = mapToState(observedState) { tunnelStatus.state = newState } } diff --git a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift index 525dade2cd84..5f4aa8ec5104 100644 --- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift +++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift @@ -91,8 +91,7 @@ class StartTunnelOperation: ResultOperation { interactor.updateTunnelStatus { tunnelStatus in tunnelStatus = TunnelStatus() - tunnelStatus.packetTunnelStatus.tunnelRelay = selectedRelay?.packetTunnelRelay - tunnelStatus.state = .connecting(selectedRelay?.packetTunnelRelay) + tunnelStatus.state = .connecting(selectedRelay) } try tunnel.start(options: tunnelOptions.rawOptions()) diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift index 044901c37de0..a553b0d0af93 100644 --- a/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift +++ b/ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift @@ -43,7 +43,7 @@ extension TunnelProtocol { /// Request status from packet tunnel process. func getTunnelStatus( - completionHandler: @escaping (Result) -> Void + completionHandler: @escaping (Result) -> Void ) -> Cancellable { let operation = SendTunnelProviderMessageOperation( dispatchQueue: dispatchQueue, diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift index e35827e114c4..0f32bf0512e3 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift @@ -696,7 +696,7 @@ final class TunnelManager: StorePaymentObserver { // Packet tunnel may have attempted or rotated the key. // In that case we have to reload device state from Keychain as it's likely was modified by packet tunnel. - let newPacketTunnelKeyRotation = newTunnelStatus.packetTunnelStatus.lastKeyRotation + let newPacketTunnelKeyRotation = newTunnelStatus.observedState.connectionState?.lastKeyRotation if lastPacketTunnelKeyRotation != newPacketTunnelKeyRotation { lastPacketTunnelKeyRotation = newPacketTunnelKeyRotation refreshDeviceState() @@ -816,7 +816,7 @@ final class TunnelManager: StorePaymentObserver { let selectorResult = try RelaySelector.evaluate( relays: cachedRelays.relays, constraints: settings.relayConstraints, - numberOfFailedAttempts: tunnelStatus.packetTunnelStatus.numberOfFailedAttempts + numberOfFailedAttempts: tunnelStatus.observedState.connectionState?.connectionAttemptCount ?? 0 ) return SelectedRelay( diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift index aed7fb2cc28a..fc73b1862013 100644 --- a/ios/MullvadVPN/TunnelManager/TunnelState.swift +++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift @@ -13,7 +13,7 @@ import PacketTunnelCore /// A struct describing the tunnel status. struct TunnelStatus: Equatable, CustomStringConvertible { /// Tunnel status returned by tunnel process. - var packetTunnelStatus = PacketTunnelStatus() + var observedState: ObservedState = .disconnected /// Tunnel state. var state: TunnelState = .disconnected @@ -21,7 +21,7 @@ struct TunnelStatus: Equatable, CustomStringConvertible { var description: String { var s = "\(state), network " - if packetTunnelStatus.isNetworkReachable { + if observedState.connectionState?.isNetworkReachable == true { s += "reachable" } else { s += "unreachable" @@ -44,10 +44,10 @@ enum TunnelState: Equatable, CustomStringConvertible { case pendingReconnect /// Connecting the tunnel. - case connecting(PacketTunnelRelay?) + case connecting(SelectedRelay?) /// Connected the tunnel - case connected(PacketTunnelRelay) + case connected(SelectedRelay) /// Disconnecting the tunnel case disconnecting(ActionAfterDisconnect) @@ -60,7 +60,7 @@ enum TunnelState: Equatable, CustomStringConvertible { /// 1. Asking the running tunnel to reconnect to new relay via IPC. /// 2. Tunnel attempts to reconnect to new relay as the current relay appears to be /// dysfunctional. - case reconnecting(PacketTunnelRelay) + case reconnecting(SelectedRelay) /// Waiting for connectivity to come back up. case waitingForConnectivity(WaitingForConnectionReason) @@ -102,7 +102,7 @@ enum TunnelState: Equatable, CustomStringConvertible { } } - var relay: PacketTunnelRelay? { + var relay: SelectedRelay? { switch self { case let .connected(relay), let .reconnecting(relay): return relay diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift index dc6669fb4710..4abc59791cd4 100644 --- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift +++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift @@ -223,7 +223,7 @@ final class TunnelControlView: UIView { ) connectionPanel.dataSource = ConnectionPanelData( - inAddress: "\(tunnelRelay.ipv4Relay) UDP", + inAddress: "\(tunnelRelay.endpoint.ipv4Relay) UDP", outAddress: nil ) connectionPanel.isHidden = false diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index cf8ec138f7e3..47737de8acb4 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -100,7 +100,21 @@ class PacketTunnelProvider: NEPacketTunnelProvider { actor.start(options: startOptions) - await actor.waitUntilConnected() + for await state in await actor.observedStates { + switch state { + case .connected, .disconnected: + return + case let .connecting(connectionState): + // Give the tunnel a few tries to connect, otherwise return immediately. This will enable VPN in + // device settings, but the app will still report the true state via ObservedState over IPC. + // In essence, this prevents the 60s tunnel timeout to trigger. + if connectionState.connectionAttemptCount > 1 { + return + } + default: + break + } + } } override func stopTunnel(with reason: NEProviderStopReason) async { diff --git a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift index 954f4450c520..218ab796ee64 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift @@ -10,37 +10,6 @@ import Foundation import MullvadTypes extension ObservedState { - public var packetTunnelStatus: PacketTunnelStatus { - var status = PacketTunnelStatus() - - switch self { - case let .connecting(connState), - let .connected(connState), - let .reconnecting(connState), - let .disconnecting(connState): - switch connState.networkReachability { - case .reachable: - status.isNetworkReachable = true - case .unreachable: - status.isNetworkReachable = false - case .undetermined: - // TODO: fix me - status.isNetworkReachable = true - } - - status.numberOfFailedAttempts = connState.connectionAttemptCount - status.tunnelRelay = connState.selectedRelay.packetTunnelRelay - - case .disconnected, .initial: - break - - case let .error(blockedState): - status.blockedStateReason = blockedState.reason - } - - return status - } - public var relayConstraints: RelayConstraints? { switch self { case let .connecting(connState), let .connected(connState), let .reconnecting(connState): diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift index bdef36412295..4d3c84a537d8 100644 --- a/ios/PacketTunnelCore/Actor/ObservedState.swift +++ b/ios/PacketTunnelCore/Actor/ObservedState.swift @@ -20,6 +20,28 @@ public enum ObservedState: Equatable, Codable { case disconnecting(ObservedConnectionState) case disconnected case error(ObservedBlockedState) + + public var connectionState: ObservedConnectionState? { + switch self { + case + let .connecting(connectionState), + let .reconnecting(connectionState), + let .connected(connectionState), + let .disconnecting(connectionState): + connectionState + default: + nil + } + } + + public var blockedState: ObservedBlockedState? { + switch self { + case let .error(blockedState): + blockedState + default: + nil + } + } } /// A serializable representation of internal connection state. @@ -28,6 +50,27 @@ public struct ObservedConnectionState: Equatable, Codable { public var relayConstraints: RelayConstraints public var networkReachability: NetworkReachability public var connectionAttemptCount: UInt + public var lastKeyRotation: Date? + + public var isNetworkReachable: Bool { + networkReachability != .unreachable + } + + #if targetEnvironment(simulator) + public init( + selectedRelay: SelectedRelay, + relayConstraints: RelayConstraints, + networkReachability: NetworkReachability, + connectionAttemptCount: UInt, + lastKeyRotation: Date? = nil + ) { + self.selectedRelay = selectedRelay + self.relayConstraints = relayConstraints + self.networkReachability = networkReachability + self.connectionAttemptCount = connectionAttemptCount + self.lastKeyRotation = lastKeyRotation + } + #endif } /// A serializable representation of internal blocked state. @@ -65,7 +108,8 @@ extension ConnectionState { selectedRelay: selectedRelay, relayConstraints: relayConstraints, networkReachability: networkReachability, - connectionAttemptCount: connectionAttemptCount + connectionAttemptCount: connectionAttemptCount, + lastKeyRotation: lastKeyRotation ) } } diff --git a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift index 8127008a8f9a..7cd300353764 100644 --- a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift +++ b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift @@ -32,15 +32,3 @@ public struct SelectedRelay: Equatable, Codable { self.location = location } } - -extension SelectedRelay { - /// Converts `SelectedRelay` to `PacketTunnelRelay` for sharing with UI. - public var packetTunnelRelay: PacketTunnelRelay { - PacketTunnelRelay( - ipv4Relay: endpoint.ipv4Relay, - ipv6Relay: endpoint.ipv6Relay, - hostname: hostname, - location: location - ) - } -} diff --git a/ios/PacketTunnelCore/IPC/AppMessageHandler.swift b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift index 4ad557f7bad2..3a852660e135 100644 --- a/ios/PacketTunnelCore/IPC/AppMessageHandler.swift +++ b/ios/PacketTunnelCore/IPC/AppMessageHandler.swift @@ -47,7 +47,7 @@ public struct AppMessageHandler { return nil case .getTunnelStatus: - return await encodeReply(packetTunnelActor.observedState.packetTunnelStatus) + return await encodeReply(packetTunnelActor.observedState) case .privateKeyRotation: packetTunnelActor.notifyKeyRotation(date: Date()) diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift b/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift deleted file mode 100644 index 4f698372408b..000000000000 --- a/ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// PacketTunnelRelay.swift -// PacketTunnelCore -// -// Created by pronebird on 21/10/2022. -// Copyright © 2022 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadTypes - -/// Struct holding tunnel relay information. -public struct PacketTunnelRelay: Codable, Equatable { - /// IPv4 relay endpoint. - public let ipv4Relay: IPv4Endpoint - - /// IPv6 relay endpoint. - public let ipv6Relay: IPv6Endpoint? - - /// Relay hostname. - public let hostname: String - - /// Relay location. - public let location: Location - - public init( - ipv4Relay: IPv4Endpoint, - ipv6Relay: IPv6Endpoint? = nil, - hostname: String, - location: Location - ) { - self.ipv4Relay = ipv4Relay - self.ipv6Relay = ipv6Relay - self.hostname = hostname - self.location = location - } -} diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift b/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift deleted file mode 100644 index f18f5510374a..000000000000 --- a/ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// PacketTunnelStatus.swift -// PacketTunnelCore -// -// Created by pronebird on 27/07/2021. -// Copyright © 2021 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadTypes - -/// Struct describing packet tunnel process status. -public struct PacketTunnelStatus: Codable, Equatable { - /// The reason why packet tunnel entered error state. - /// Set to `nil` when tunnel is not in error state. - public var blockedStateReason: BlockedStateReason? - - /// Flag indicating whether network is reachable. - public var isNetworkReachable: Bool - - /// The date of last performed key rotation during device check. - public var lastKeyRotation: Date? - - /// Current relay. - public var tunnelRelay: PacketTunnelRelay? - - /// Number of consecutive connection failure attempts. - public var numberOfFailedAttempts: UInt - - public init( - blockStateReason: BlockedStateReason? = nil, - isNetworkReachable: Bool = true, - lastKeyRotation: Date? = nil, - tunnelRelay: PacketTunnelRelay? = nil, - numberOfFailedAttempts: UInt = 0 - ) { - self.blockedStateReason = blockStateReason - self.isNetworkReachable = isNetworkReachable - self.lastKeyRotation = lastKeyRotation - self.tunnelRelay = tunnelRelay - self.numberOfFailedAttempts = numberOfFailedAttempts - } -}