From 12e249e7f4807f553e875afcc8d282fd70b525cc Mon Sep 17 00:00:00 2001 From: mojganii Date: Tue, 20 Aug 2024 13:55:12 +0200 Subject: [PATCH] Reconnect tunnel automatically after tunnel adapter error --- .../Actor/PacketTunnelActor+ErrorState.swift | 3 +- .../Actor/State+Extensions.swift | 5 +- ios/PacketTunnelCore/Actor/Timings.swift | 4 +- .../PacketTunnelActorTests.swift | 47 ++++++++++++++++++- 4 files changed, 53 insertions(+), 6 deletions(-) diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift index de3fe8cb7ac1..ac78ee8d849c 100644 --- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift +++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift @@ -104,6 +104,7 @@ extension PacketTunnelActor { currentKey: connState.currentKey, keyPolicy: connState.keyPolicy, networkReachability: connState.networkReachability, + recoveryTask: startRecoveryTaskIfNeeded(reason: reason), priorState: priorState ) } @@ -151,7 +152,7 @@ extension PacketTunnelActor { try await Task.sleepUsingContinuousClock(for: timings.bootRecoveryPeriodicity) // Schedule task to reconnect. - eventChannel.send(.reconnect(.random)) + eventChannel.send(.reconnect(.current)) } } diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift index f7d8cbfae796..7aba08c3e47d 100644 --- a/ios/PacketTunnelCore/Actor/State+Extensions.swift +++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift @@ -188,14 +188,15 @@ extension BlockedStateReason { - Keychain and filesystem are locked on boot until user unlocks device in the very first time. - App update that requires settings schema migration. Packet tunnel will be automatically restarted after update but it would not be able to read settings until user opens the app which performs migration. + - Packet tunnel will be automatically restarted when there is tunnel adapter error. */ var shouldRestartAutomatically: Bool { switch self { - case .deviceLocked: + case .deviceLocked, .tunnelAdapter: return true case .noRelaysSatisfyingConstraints, .readSettings, .invalidAccount, .accountExpired, .deviceRevoked, - .tunnelAdapter, .unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey: + .unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey: return false } } diff --git a/ios/PacketTunnelCore/Actor/Timings.swift b/ios/PacketTunnelCore/Actor/Timings.swift index 4e2d5d8b7706..5a62f7b05858 100644 --- a/ios/PacketTunnelCore/Actor/Timings.swift +++ b/ios/PacketTunnelCore/Actor/Timings.swift @@ -11,7 +11,7 @@ import MullvadTypes /// Struct holding all timings used by tunnel actor. public struct PacketTunnelActorTimings { - /// Periodicity at which actor will attempt to restart when an error occurred on system boot when filesystem is locked until device is unlocked. + /// Periodicity at which actor will attempt to restart when an error occurred on system boot when filesystem is locked until device is unlocked or tunnel adapter error. public var bootRecoveryPeriodicity: Duration /// Time that takes for new WireGuard key to propagate across relays. @@ -19,7 +19,7 @@ public struct PacketTunnelActorTimings { /// Designated initializer. public init( - bootRecoveryPeriodicity: Duration = .seconds(10), + bootRecoveryPeriodicity: Duration = .seconds(5), wgKeyPropagationDelay: Duration = .seconds(120) ) { self.bootRecoveryPeriodicity = bootRecoveryPeriodicity diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift index 1d649b59a397..cdea29460d12 100644 --- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift +++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift @@ -12,10 +12,11 @@ import Combine @testable import MullvadSettings import MullvadTypes import Network -@testable import PacketTunnelCore import WireGuardKitTypes import XCTest +@testable import PacketTunnelCore + final class PacketTunnelActorTests: XCTestCase { private var stateSink: Combine.Cancellable? private let launchOptions = StartOptions(launchSource: .app) @@ -446,6 +447,48 @@ final class PacketTunnelActorTests: XCTestCase { actor.reconnect(to: .random, reconnectReason: .userInitiated) await fulfillment(of: [stopMonitorExpectation], timeout: .UnitTest.timeout) } + + func testRecoveringConnectionAfterTunnelAdaptorError() async throws { + let errorStateExpectation = expectation(description: "Expect error state") + let connectingStateExpectation = expectation(description: "Expect connecting state") + connectingStateExpectation.expectedFulfillmentCount = 2 + let connectedStateExpectation = expectation(description: "Expect connected state") + + let blockedStateMapper = BlockedStateErrorMapperStub { error in + if error is TunnelAdapterErrorStub { + return .tunnelAdapter + } else { + return .unknown + } + } + + let actor = PacketTunnelActor.mock(blockedStateErrorMapper: blockedStateMapper) + + actor.start(options: launchOptions) + + stateSink = await actor.$observedState + .receive(on: DispatchQueue.main) + .sink { newState in + switch newState { + case .error: + errorStateExpectation.fulfill() + case .connecting: + connectingStateExpectation.fulfill() + case .connected: + connectedStateExpectation.fulfill() + default: + break + } + } + + actor.setErrorState(reason: .tunnelAdapter) + + await fulfillment( + of: [errorStateExpectation, connectingStateExpectation, connectedStateExpectation], + timeout: .UnitTest.timeout, + enforceOrder: true + ) + } } extension PacketTunnelActorTests { @@ -470,4 +513,6 @@ extension PacketTunnelActorTests { } } +struct TunnelAdapterErrorStub: Error {} + // swiftlint:disable:this file_length