Skip to content

Commit

Permalink
Reconnect tunnel automatically after tunnel adapter error
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed Aug 20, 2024
1 parent bbd1bf1 commit 12e249e
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ extension PacketTunnelActor {
currentKey: connState.currentKey,
keyPolicy: connState.keyPolicy,
networkReachability: connState.networkReachability,
recoveryTask: startRecoveryTaskIfNeeded(reason: reason),
priorState: priorState
)
}
Expand Down Expand Up @@ -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))
}
}

Expand Down
5 changes: 3 additions & 2 deletions ios/PacketTunnelCore/Actor/State+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down
4 changes: 2 additions & 2 deletions ios/PacketTunnelCore/Actor/Timings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ 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.
public var wgKeyPropagationDelay: Duration

/// Designated initializer.
public init(
bootRecoveryPeriodicity: Duration = .seconds(10),
bootRecoveryPeriodicity: Duration = .seconds(5),
wgKeyPropagationDelay: Duration = .seconds(120)
) {
self.bootRecoveryPeriodicity = bootRecoveryPeriodicity
Expand Down
47 changes: 46 additions & 1 deletion ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -470,4 +513,6 @@ extension PacketTunnelActorTests {
}
}

struct TunnelAdapterErrorStub: Error {}

// swiftlint:disable:this file_length

0 comments on commit 12e249e

Please sign in to comment.