Skip to content

Commit

Permalink
Add general support for multiple selected relays
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson authored and pinkisemils committed Jul 11, 2024
1 parent 67e9e0e commit 5a0554f
Show file tree
Hide file tree
Showing 31 changed files with 203 additions and 201 deletions.
2 changes: 1 addition & 1 deletion ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//

import MullvadTypes
import MullvadREST
import MullvadTypes
import WireGuardKitTypes

/// Relay selector stub that accepts a block that can be used to provide custom implementation.
Expand Down
4 changes: 2 additions & 2 deletions ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import MullvadTypes

extension RelaySelector {
public enum WireGuard {
/// Filters relay list using given constraints and selects random relay for exit relay.
/// Filters relay list using given constraints.
public static func findCandidates(
by relayConstraint: RelayConstraint<UserSelectedRelays>,
in relays: REST.ServerRelaysResponse,
Expand All @@ -25,7 +25,7 @@ extension RelaySelector {
)
}

// TODO: Add comment.
/// Picks a random relay from a list.
public static func pickCandidate(
from relayWithLocations: [RelayWithLocation<REST.ServerRelay>],
relays: REST.ServerRelaysResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import PacketTunnelCore

final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
private var observedState: ObservedState = .disconnected
private var selectedRelay: SelectedRelay?
private var selectedRelays: SelectedRelays?
private let urlRequestProxy: URLRequestProxy
private let relaySelector: RelaySelectorProtocol

Expand All @@ -43,12 +43,12 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
return
}

var selectedRelay: SelectedRelay?
var selectedRelays: SelectedRelays?

do {
let tunnelOptions = PacketTunnelOptions(rawOptions: options ?? [:])

selectedRelay = try tunnelOptions.getSelectedRelay()
selectedRelays = try tunnelOptions.getSelectedRelays()
} catch {
providerLogger.error(
error: error,
Expand All @@ -60,7 +60,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}

do {
setInternalStateConnected(with: try selectedRelay ?? pickRelay())
setInternalStateConnected(with: try selectedRelays ?? pickRelays())
completionHandler(nil)
} catch {
providerLogger.error(
Expand All @@ -74,7 +74,7 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {

override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
dispatchQueue.async { [weak self] in
self?.selectedRelay = nil
self?.selectedRelays = nil
self?.observedState = .disconnected

completionHandler()
Expand Down Expand Up @@ -117,17 +117,17 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
reasserting = true

switch nextRelay {
case let .preSelected(selectedRelay):
self.selectedRelay = selectedRelay
case let .preSelected(selectedRelays):
self.selectedRelays = selectedRelays
case .random:
if let nextRelay = try? pickRelay() {
self.selectedRelay = nextRelay
if let nextRelays = try? pickRelays() {
self.selectedRelays = nextRelays
}
case .current:
break
}

setInternalStateConnected(with: selectedRelay)
setInternalStateConnected(with: selectedRelays)
reasserting = false

completionHandler?(nil)
Expand Down Expand Up @@ -156,28 +156,28 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
}

private func pickRelay() throws -> SelectedRelay {
private func pickRelays() throws -> SelectedRelays {
let tunnelSettings = try SettingsManager.readSettings()

return try relaySelector.selectRelays(
with: tunnelSettings.relayConstraints,
connectionAttemptCount: 0
).exit // TODO: Multihop
)
}

private func setInternalStateConnected(with selectedRelay: SelectedRelay?) {
guard let selectedRelay = selectedRelay else { return }
private func setInternalStateConnected(with selectedRelays: SelectedRelays?) {
guard let selectedRelays = selectedRelays else { return }

do {
let settings = try SettingsManager.readSettings()
observedState = .connected(
ObservedConnectionState(
selectedRelay: selectedRelay,
selectedRelays: selectedRelays,
relayConstraints: settings.relayConstraints,
networkReachability: .reachable,
connectionAttemptCount: 0,
transportLayer: .udp,
remotePort: selectedRelay.endpoint.ipv4Relay.port,
remotePort: selectedRelays.exit.endpoint.ipv4Relay.port, // TODO: Multihop
isPostQuantum: settings.tunnelQuantumResistance.isEnabled
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ class MapConnectionStatusOperation: AsyncOperation {
switch observedState {
case let .connected(connectionState):
return connectionState.isNetworkReachable
? .connected(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
? .connected(connectionState.selectedRelays, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .connecting(connectionState):
return connectionState.isNetworkReachable
? .connecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
? .connecting(connectionState.selectedRelays, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .negotiatingPostQuantumKey(connectionState, privateKey):
return connectionState.isNetworkReachable
? .negotiatingPostQuantumKey(connectionState.selectedRelay, privateKey)
? .negotiatingPostQuantumKey(connectionState.selectedRelays, privateKey)
: .waitingForConnectivity(.noConnection)
case let .reconnecting(connectionState):
return connectionState.isNetworkReachable
? .reconnecting(connectionState.selectedRelay, isPostQuantum: connectionState.isPostQuantum)
? .reconnecting(connectionState.selectedRelays, isPostQuantum: connectionState.isPostQuantum)
: .waitingForConnectivity(.noConnection)
case let .error(blockedState):
return .error(blockedState.reason)
Expand Down
8 changes: 4 additions & 4 deletions ios/MullvadVPN/TunnelManager/StartTunnelOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ class StartTunnelOperation: ResultOperation<Void> {
}

private func startTunnel(tunnel: any TunnelProtocol) throws {
let selectedRelay = try? interactor.selectRelay()
let selectedRelays = try? interactor.selectRelays()
var tunnelOptions = PacketTunnelOptions()

do {
if let selectedRelay {
try tunnelOptions.setSelectedRelay(selectedRelay)
if let selectedRelays {
try tunnelOptions.setSelectedRelays(selectedRelays)
}
} catch {
logger.error(
Expand All @@ -91,7 +91,7 @@ class StartTunnelOperation: ResultOperation<Void> {
interactor.updateTunnelStatus { tunnelStatus in
tunnelStatus = TunnelStatus()
tunnelStatus.state = .connecting(
selectedRelay,
selectedRelays,
isPostQuantum: interactor.settings.tunnelQuantumResistance.isEnabled
)
}
Expand Down
6 changes: 3 additions & 3 deletions ios/MullvadVPN/TunnelManager/Tunnel+Messaging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ private let dispatchQueue = DispatchQueue(label: "Tunnel.dispatchQueue")
private let proxyRequestTimeout = REST.defaultAPINetworkTimeout + 2

extension TunnelProtocol {
/// Request packet tunnel process to reconnect the tunnel with the given relay.
/// Request packet tunnel process to reconnect the tunnel with the given relays.
func reconnectTunnel(
to nextRelay: NextRelay,
to nextRelays: NextRelays,
completionHandler: @escaping (Result<Void, Error>) -> Void
) -> Cancellable {
let operation = SendTunnelProviderMessageOperation(
dispatchQueue: dispatchQueue,
application: .shared,
tunnel: self,
message: .reconnectTunnel(nextRelay),
message: .reconnectTunnel(nextRelays),
completionHandler: completionHandler
)

Expand Down
2 changes: 1 addition & 1 deletion ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ protocol TunnelInteractor {

func startTunnel()
func prepareForVPNConfigurationDeletion()
func selectRelay() throws -> SelectedRelay
func selectRelays() throws -> SelectedRelays
}
8 changes: 4 additions & 4 deletions ios/MullvadVPN/TunnelManager/TunnelManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -782,13 +782,13 @@ final class TunnelManager: StorePaymentObserver {
updateTunnelStatus(tunnel?.status ?? .disconnected)
}

fileprivate func selectRelay() throws -> SelectedRelay {
fileprivate func selectRelays() throws -> SelectedRelays {
let retryAttempts = tunnelStatus.observedState.connectionState?.connectionAttemptCount ?? 0

return try relaySelector.selectRelays(
with: settings.relayConstraints,
connectionAttemptCount: retryAttempts
).exit // TODO: Multihop
)
}

fileprivate func prepareForVPNConfigurationDeletion() {
Expand Down Expand Up @@ -1260,8 +1260,8 @@ private struct TunnelInteractorProxy: TunnelInteractor {
tunnelManager.prepareForVPNConfigurationDeletion()
}

func selectRelay() throws -> SelectedRelay {
try tunnelManager.selectRelay()
func selectRelays() throws -> SelectedRelays {
try tunnelManager.selectRelays()
}

func handleRestError(_ error: Error) {
Expand Down
12 changes: 6 additions & 6 deletions ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ extension TunnelState {
value: "Quantum secure connection. Connected to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
tunnelInfo.exit.location.city, // TODO: Multihop
tunnelInfo.exit.location.country // TODO: Multihop
)
} else {
String(
Expand All @@ -198,8 +198,8 @@ extension TunnelState {
value: "Secure connection. Connected to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
tunnelInfo.exit.location.city, // TODO: Multihop
tunnelInfo.exit.location.country // TODO: Multihop
)
}

Expand All @@ -219,8 +219,8 @@ extension TunnelState {
value: "Reconnecting to %@, %@",
comment: ""
),
tunnelInfo.location.city,
tunnelInfo.location.country
tunnelInfo.exit.location.city, // TODO: Multihop
tunnelInfo.exit.location.country // TODO: Multihop
)

case .waitingForConnectivity(.noConnection), .error:
Expand Down
40 changes: 20 additions & 20 deletions ios/MullvadVPN/TunnelManager/TunnelState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ enum TunnelState: Equatable, CustomStringConvertible {
case pendingReconnect

/// Connecting the tunnel.
case connecting(SelectedRelay?, isPostQuantum: Bool)
case connecting(SelectedRelays?, isPostQuantum: Bool)

/// Negotiating a key for post-quantum resistance
case negotiatingPostQuantumKey(SelectedRelay, PrivateKey)
case negotiatingPostQuantumKey(SelectedRelays, PrivateKey)

/// Connected the tunnel
case connected(SelectedRelay, isPostQuantum: Bool)
case connected(SelectedRelays, isPostQuantum: Bool)

/// Disconnecting the tunnel
case disconnecting(ActionAfterDisconnect)
Expand All @@ -66,10 +66,10 @@ enum TunnelState: Equatable, CustomStringConvertible {

/// Reconnecting the tunnel.
/// Transition to this state happens when:
/// 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
/// 1. Asking the running tunnel to reconnect to new relays via IPC.
/// 2. Tunnel attempts to reconnect to new relays as the current relays appear to be
/// dysfunctional.
case reconnecting(SelectedRelay, isPostQuantum: Bool)
case reconnecting(SelectedRelays, isPostQuantum: Bool)

/// Waiting for connectivity to come back up.
case waitingForConnectivity(WaitingForConnectionReason)
Expand All @@ -81,26 +81,26 @@ enum TunnelState: Equatable, CustomStringConvertible {
switch self {
case .pendingReconnect:
"pending reconnect after disconnect"
case let .connecting(tunnelRelay, isPostQuantum):
if let tunnelRelay {
"connecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)"
case let .connecting(tunnelRelays, isPostQuantum):
if let tunnelRelays {
"connecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
} else {
"connecting\(isPostQuantum ? " (PQ)" : ""), fetching relay"
}
case let .connected(tunnelRelay, isPostQuantum):
"connected \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)"
case let .connected(tunnelRelays, isPostQuantum):
"connected \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
case let .disconnecting(actionAfterDisconnect):
"disconnecting and then \(actionAfterDisconnect)"
case .disconnected:
"disconnected"
case let .reconnecting(tunnelRelay, isPostQuantum):
"reconnecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelay.hostname)"
case let .reconnecting(tunnelRelays, isPostQuantum):
"reconnecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
case .waitingForConnectivity:
"waiting for connectivity"
case let .error(blockedStateReason):
"error state: \(blockedStateReason)"
case let .negotiatingPostQuantumKey(tunnelRelay, _):
"negotiating key with \(tunnelRelay.hostname)"
case let .negotiatingPostQuantumKey(tunnelRelays, _):
"negotiating key with \(tunnelRelays.exit.hostname)" // TODO: Multihop
}
}

Expand All @@ -114,12 +114,12 @@ enum TunnelState: Equatable, CustomStringConvertible {
}
}

var relay: SelectedRelay? {
var relays: SelectedRelays? {
switch self {
case let .connected(relay, _), let .reconnecting(relay, _), let .negotiatingPostQuantumKey(relay, _):
relay
case let .connecting(relay, _):
relay
case let .connected(relays, _), let .reconnecting(relays, _), let .negotiatingPostQuantumKey(relays, _):
relays
case let .connecting(relays, _):
relays
case .disconnecting, .disconnected, .waitingForConnectivity, .pendingReconnect, .error:
nil
}
Expand Down
14 changes: 7 additions & 7 deletions ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ final class TunnelControlView: UIView {
updateSecureLabel(tunnelState: tunnelState)
updateActionButtons(tunnelState: tunnelState)
if tunnelState.isSecured {
updateTunnelRelay(tunnelRelay: tunnelState.relay)
updateTunnelRelays(tunnelRelays: tunnelState.relays)
} else {
updateTunnelRelay(tunnelRelay: nil)
updateTunnelRelays(tunnelRelays: nil)
}
}

Expand Down Expand Up @@ -224,17 +224,17 @@ final class TunnelControlView: UIView {
connectButtonBlurView.isEnabled = shouldEnableButtons
}

private func updateTunnelRelay(tunnelRelay: SelectedRelay?) {
if let tunnelRelay {
private func updateTunnelRelays(tunnelRelays: SelectedRelays?) {
if let tunnelRelays {
cityLabel.attributedText = attributedStringForLocation(
string: tunnelRelay.location.city
string: tunnelRelays.exit.location.city // TODO: Multihop
)
countryLabel.attributedText = attributedStringForLocation(
string: tunnelRelay.location.country
string: tunnelRelays.exit.location.country // TODO: Multihop
)

connectionPanel.isHidden = false
connectionPanel.connectedRelayName = tunnelRelay.hostname
connectionPanel.connectedRelayName = tunnelRelays.exit.hostname // TODO: Multihop
} else {
countryLabel.attributedText = attributedStringForLocation(string: " ")
cityLabel.attributedText = attributedStringForLocation(string: " ")
Expand Down
Loading

0 comments on commit 5a0554f

Please sign in to comment.