Skip to content

Commit 004ae25

Browse files
author
mojganii
committed
Reconnect tunnel automatically after tunnel adapter error
1 parent 0f2ec13 commit 004ae25

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

ios/PacketTunnelCore/Actor/PacketTunnelActor+ErrorState.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ extension PacketTunnelActor {
104104
currentKey: connState.currentKey,
105105
keyPolicy: connState.keyPolicy,
106106
networkReachability: connState.networkReachability,
107+
recoveryTask: startRecoveryTaskIfNeeded(reason: reason),
107108
priorState: priorState
108109
)
109110
}
@@ -151,7 +152,7 @@ extension PacketTunnelActor {
151152
try await Task.sleepUsingContinuousClock(for: timings.bootRecoveryPeriodicity)
152153

153154
// Schedule task to reconnect.
154-
eventChannel.send(.reconnect(.random))
155+
eventChannel.send(.reconnect(.current))
155156
}
156157
}
157158

ios/PacketTunnelCore/Actor/State+Extensions.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,15 @@ extension BlockedStateReason {
188188
- Keychain and filesystem are locked on boot until user unlocks device in the very first time.
189189
- 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
190190
user opens the app which performs migration.
191+
- Packet tunnel will be automatically restarted when there is tunnel adapter error.
191192
*/
192193
var shouldRestartAutomatically: Bool {
193194
switch self {
194-
case .deviceLocked:
195+
case .deviceLocked, .tunnelAdapter:
195196
return true
196197

197198
case .noRelaysSatisfyingConstraints, .readSettings, .invalidAccount, .accountExpired, .deviceRevoked,
198-
.tunnelAdapter, .unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey:
199+
.unknown, .deviceLoggedOut, .outdatedSchema, .invalidRelayPublicKey:
199200
return false
200201
}
201202
}

ios/PacketTunnelCore/Actor/Timings.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import MullvadTypes
1111

1212
/// Struct holding all timings used by tunnel actor.
1313
public struct PacketTunnelActorTimings {
14-
/// Periodicity at which actor will attempt to restart when an error occurred on system boot when filesystem is locked until device is unlocked.
14+
/// 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.
1515
public var bootRecoveryPeriodicity: Duration
1616

1717
/// Time that takes for new WireGuard key to propagate across relays.
1818
public var wgKeyPropagationDelay: Duration
1919

2020
/// Designated initializer.
2121
public init(
22-
bootRecoveryPeriodicity: Duration = .seconds(10),
22+
bootRecoveryPeriodicity: Duration = .seconds(5),
2323
wgKeyPropagationDelay: Duration = .seconds(120)
2424
) {
2525
self.bootRecoveryPeriodicity = bootRecoveryPeriodicity

ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift

+46-1
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import Combine
1212
@testable import MullvadSettings
1313
import MullvadTypes
1414
import Network
15-
@testable import PacketTunnelCore
1615
import WireGuardKitTypes
1716
import XCTest
1817

18+
@testable import PacketTunnelCore
19+
1920
final class PacketTunnelActorTests: XCTestCase {
2021
private var stateSink: Combine.Cancellable?
2122
private let launchOptions = StartOptions(launchSource: .app)
@@ -446,6 +447,48 @@ final class PacketTunnelActorTests: XCTestCase {
446447
actor.reconnect(to: .random, reconnectReason: .userInitiated)
447448
await fulfillment(of: [stopMonitorExpectation], timeout: .UnitTest.timeout)
448449
}
450+
451+
func testRecoveringConnectionAfterTunnelAdaptorError() async throws {
452+
let errorStateExpectation = expectation(description: "Expect error state")
453+
let connectingStateExpectation = expectation(description: "Expect connecting state")
454+
connectingStateExpectation.expectedFulfillmentCount = 2
455+
let connectedStateExpectation = expectation(description: "Expect connected state")
456+
457+
let blockedStateMapper = BlockedStateErrorMapperStub { error in
458+
if error is TunnelAdapterErrorStub {
459+
return .tunnelAdapter
460+
} else {
461+
return .unknown
462+
}
463+
}
464+
465+
let actor = PacketTunnelActor.mock(blockedStateErrorMapper: blockedStateMapper)
466+
467+
actor.start(options: launchOptions)
468+
469+
stateSink = await actor.$observedState
470+
.receive(on: DispatchQueue.main)
471+
.sink { newState in
472+
switch newState {
473+
case .error:
474+
errorStateExpectation.fulfill()
475+
case .connecting:
476+
connectingStateExpectation.fulfill()
477+
case .connected:
478+
connectedStateExpectation.fulfill()
479+
default:
480+
break
481+
}
482+
}
483+
484+
actor.setErrorState(reason: .tunnelAdapter)
485+
486+
await fulfillment(
487+
of: [errorStateExpectation, connectingStateExpectation, connectedStateExpectation],
488+
timeout: .UnitTest.timeout,
489+
enforceOrder: true
490+
)
491+
}
449492
}
450493

451494
extension PacketTunnelActorTests {
@@ -470,4 +513,6 @@ extension PacketTunnelActorTests {
470513
}
471514
}
472515

516+
struct TunnelAdapterErrorStub: Error {}
517+
473518
// swiftlint:disable:this file_length

0 commit comments

Comments
 (0)