summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-10-18 11:31:53 +0200
committerJon Petersson <jon.petersson@kvadrat.se>2023-10-26 15:37:31 +0200
commita248e02f1ac793ad916283ea53b457418a6f212e (patch)
tree73ba569cb37869e74fff8d4c1a866a2b4193b04b
parent06558567bf5a3355bc1401953157defbe8f911e8 (diff)
downloadmullvadvpn-a248e02f1ac793ad916283ea53b457418a6f212e.tar.xz
mullvadvpn-a248e02f1ac793ad916283ea53b457418a6f212e.zip
Prevent iOS from stopping the tunnel if it remains in connecting state for too long
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj8
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift49
-rw-r--r--ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift83
-rw-r--r--ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift3
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift2
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift4
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift20
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift2
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift16
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState+Extensions.swift53
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift22
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift15
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift12
-rw-r--r--ios/PacketTunnelCore/IPC/AppMessageHandler.swift2
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelRelay.swift37
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelStatus.swift43
16 files changed, 142 insertions, 229 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 5cb501c04d..8a12966bce 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 = "<group>"; };
585DA87626B024A600B8C587 /* CachedRelays.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedRelays.swift; sourceTree = "<group>"; };
585DA89226B0323E00B8C587 /* TunnelProviderMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelProviderMessage.swift; sourceTree = "<group>"; };
- 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelStatus.swift; sourceTree = "<group>"; };
585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = "<group>"; };
58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryInAppNotificationProvider.swift; sourceTree = "<group>"; };
586168682976F6BD00EF8598 /* DisplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayError.swift; sourceTree = "<group>"; };
@@ -1358,7 +1355,6 @@
5898D2AD290185D200EB5EBA /* ProxyURLResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyURLResponse.swift; sourceTree = "<group>"; };
5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLocation.swift; sourceTree = "<group>"; };
5898D2B12902A6DE00EB5EBA /* RelayConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraint.swift; sourceTree = "<group>"; };
- 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelRelay.swift; sourceTree = "<group>"; };
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 = "<group>"; };
589C6A7B2A45AE0100DAD3EF /* TunnelObfuscation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TunnelObfuscation.h; sourceTree = "<group>"; };
@@ -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 d09a3e94ab..eb3e590fbe 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,12 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
options: [String: NSObject]?,
completionHandler: @escaping (Error?) -> Void
) {
- dispatchQueue.async {
+ dispatchQueue.async { [weak self] in
+ guard let self else {
+ completionHandler(nil)
+ return
+ }
+
var selectedRelay: SelectedRelay?
do {
@@ -47,7 +53,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 +63,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 +76,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 +104,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 +118,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
case let .reconnectTunnel(nextRelay):
reasserting = true
+
switch nextRelay {
case let .preSelected(selectedRelay):
self.selectedRelay = selectedRelay
@@ -125,7 +129,10 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
case .current:
break
}
+
+ setInternalStateConnected(with: selectedRelay)
reasserting = false
+
completionHandler?(nil)
case let .sendURLRequest(proxyRequest):
@@ -166,6 +173,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 5282a2a7d5..d753f5e762 100644
--- a/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/MapConnectionStatusOperation.swift
@@ -46,30 +46,25 @@ 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 .reconnecting(connectionState):
+ return connectionState.isNetworkReachable
+ ? .reconnecting(connectionState.selectedRelay)
+ : .waitingForConnectivity(.noConnection)
+ case let .error(blockedState):
+ return .error(blockedState.reason)
+ case .initial, .disconnecting, .disconnected:
+ return .none
}
}
return
@@ -78,7 +73,7 @@ class MapConnectionStatusOperation: AsyncOperation {
handleDisconnectedState(tunnelState)
case .disconnecting:
- handleDisconnectionState(tunnelState)
+ handleDisconnectingState(tunnelState)
case .invalid:
setTunnelDisconnectedStatus()
@@ -94,38 +89,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 +134,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 525dade2cd..5f4aa8ec51 100644
--- a/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
+++ b/ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
@@ -91,8 +91,7 @@ class StartTunnelOperation: ResultOperation<Void> {
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 044901c37d..a553b0d0af 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<PacketTunnelStatus, Error>) -> Void
+ completionHandler: @escaping (Result<ObservedState, Error>) -> Void
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index e35827e114..0f32bf0512 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 aed7fb2cc2..877046cac8 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,10 +21,14 @@ struct TunnelStatus: Equatable, CustomStringConvertible {
var description: String {
var s = "\(state), network "
- if packetTunnelStatus.isNetworkReachable {
- s += "reachable"
+ if let connectionState = observedState.connectionState {
+ if connectionState.isNetworkReachable {
+ s += "reachable"
+ } else {
+ s += "unreachable"
+ }
} else {
- s += "unreachable"
+ s += "reachability unknown"
}
return s
@@ -44,10 +48,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 +64,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 +106,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 dc6669fb47..4abc59791c 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 cf8ec138f7..4754887979 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, .error:
+ 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 954f4450c5..05624a6565 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):
@@ -72,4 +41,26 @@ extension ObservedState {
return "Error"
}
}
+
+ 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
+ }
+ }
}
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index bdef364122..01f31e9abd 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -28,6 +28,25 @@ 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
+ }
+
+ 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
+ }
}
/// A serializable representation of internal blocked state.
@@ -65,7 +84,8 @@ extension ConnectionState {
selectedRelay: selectedRelay,
relayConstraints: relayConstraints,
networkReachability: networkReachability,
- connectionAttemptCount: connectionAttemptCount
+ connectionAttemptCount: connectionAttemptCount,
+ lastKeyRotation: lastKeyRotation
)
}
}
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
index 3bd2c56315..3610c3ff50 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+Extensions.swift
@@ -28,21 +28,6 @@ extension PacketTunnelActor {
}
}
- /// Wait until the `observedState` moved to `.connected`.
- /// Should return if the state is `.disconnected` as this is the final state of actor.
- public func waitUntilConnected() async {
- for await newState in observedStates {
- switch newState {
- case .connected, .disconnected:
- // Return once either desired or final state is reached.
- return
-
- case .connecting, .disconnecting, .error, .initial, .reconnecting:
- break
- }
- }
- }
-
/// Wait until the `observedState` moved to `.disiconnected`.
public func waitUntilDisconnected() async {
for await newState in observedStates {
diff --git a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift
index 8127008a8f..7cd3003537 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 4ad557f7ba..3a852660e1 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 4f69837240..0000000000
--- 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 f18f551037..0000000000
--- 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
- }
-}