summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2024-06-04 12:18:44 +0200
committerEmīls <emils@mullvad.net>2024-07-11 16:53:24 +0200
commit67e9e0ef08723cd991659a106398cbe0cb88493e (patch)
treea56eb3baae5b5120afb2e9491027994ccaa80aa2
parent5bd43f95a4189aff24bc9ea120b2841b222c6921 (diff)
downloadmullvadvpn-67e9e0ef08723cd991659a106398cbe0cb88493e.tar.xz
mullvadvpn-67e9e0ef08723cd991659a106398cbe0cb88493e.zip
Allow relay selector to select an entry peer
-rw-r--r--ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift (renamed from ios/PacketTunnelCoreTests/Mocks/RelaySelectorStub.swift)24
-rw-r--r--ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift2
-rw-r--r--ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift1
-rw-r--r--ios/MullvadREST/Relay/RelaySelector+Wireguard.swift47
-rw-r--r--ios/MullvadREST/Relay/RelaySelector.swift1
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorPicker.swift200
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorProtocol.swift (renamed from ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift)19
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorWrapper.swift63
-rw-r--r--ios/MullvadREST/Relay/RelayWithLocation.swift10
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj23
-rw-r--r--ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved22
-rw-r--r--ios/MullvadVPN/AppDelegate.swift26
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift23
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelInteractor.swift1
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift21
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift1
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift1
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift107
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift26
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift4
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift12
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift5
-rw-r--r--ios/PacketTunnelCore/Actor/ObservedState.swift1
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift7
-rw-r--r--ios/PacketTunnelCore/Actor/StartOptions.swift1
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift1
-rw-r--r--ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift1
-rw-r--r--ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift20
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift2
-rw-r--r--ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift3
30 files changed, 451 insertions, 224 deletions
diff --git a/ios/PacketTunnelCoreTests/Mocks/RelaySelectorStub.swift b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
index 4922c080c5..2fe06dafe3 100644
--- a/ios/PacketTunnelCoreTests/Mocks/RelaySelectorStub.swift
+++ b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
@@ -6,30 +6,29 @@
// Copyright © 2023 Mullvad VPN AB. All rights reserved.
//
-import Foundation
import MullvadTypes
-import PacketTunnelCore
+import MullvadREST
import WireGuardKitTypes
/// Relay selector stub that accepts a block that can be used to provide custom implementation.
-struct RelaySelectorStub: RelaySelectorProtocol {
- let block: (RelayConstraints, UInt) throws -> SelectedRelay
+public struct RelaySelectorStub: RelaySelectorProtocol {
+ let block: (RelayConstraints, UInt) throws -> SelectedRelays
- func selectRelay(
+ public func selectRelays(
with constraints: RelayConstraints,
- connectionAttemptFailureCount: UInt
- ) throws -> SelectedRelay {
- return try block(constraints, connectionAttemptFailureCount)
+ connectionAttemptCount: UInt
+ ) throws -> SelectedRelays {
+ return try block(constraints, connectionAttemptCount)
}
}
extension RelaySelectorStub {
/// Returns a relay selector that never fails.
- static func nonFallible() -> RelaySelectorStub {
+ public static func nonFallible() -> RelaySelectorStub {
let publicKey = PrivateKey().publicKey.rawValue
return RelaySelectorStub { _, _ in
- return SelectedRelay(
+ let cityRelay = SelectedRelay(
endpoint: MullvadEndpoint(
ipv4Relay: IPv4Endpoint(ip: .loopback, port: 1300),
ipv4Gateway: .loopback,
@@ -46,6 +45,11 @@ extension RelaySelectorStub {
longitude: 0
), retryAttempts: 0
)
+
+ return SelectedRelays(
+ entry: cityRelay,
+ exit: cityRelay
+ )
}
}
}
diff --git a/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift b/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
index 9435929db6..b435428930 100644
--- a/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
+++ b/ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
@@ -9,6 +9,8 @@
import Foundation
public struct NoRelaysSatisfyingConstraintsError: LocalizedError {
+ public init() {}
+
public var errorDescription: String? {
"No relays satisfying constraints."
}
diff --git a/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift b/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
index 273b9afe03..1f678e6027 100644
--- a/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
+++ b/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
@@ -45,7 +45,6 @@ extension RelaySelector {
let mappedBridges = mapRelays(relays: relaysResponse.bridge.relays, locations: relaysResponse.locations)
let filteredRelays = applyConstraints(
location,
- portConstraint: port,
filterConstraint: filter,
relays: mappedBridges
)
diff --git a/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift b/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
index 4607838ac2..382479ee20 100644
--- a/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
+++ b/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
@@ -6,54 +6,39 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
-import Foundation
import MullvadTypes
extension RelaySelector {
public enum WireGuard {
- /**
- Filters relay list using given constraints and selects random relay for exit relay.
- Throws an error if there are no relays satisfying the given constraints.
- */
- public static func evaluate(
- by constraints: RelayConstraints,
- in relaysResponse: REST.ServerRelaysResponse,
- numberOfFailedAttempts: UInt
- ) throws -> RelaySelectorResult {
- let exitCandidates = try findBestMatch(
- relays: relaysResponse,
- relayConstraint: constraints.exitLocations,
- portConstraint: constraints.port,
- filterConstraint: constraints.filter,
- numberOfFailedAttempts: numberOfFailedAttempts
- )
+ /// Filters relay list using given constraints and selects random relay for exit relay.
+ public static func findCandidates(
+ by relayConstraint: RelayConstraint<UserSelectedRelays>,
+ in relays: REST.ServerRelaysResponse,
+ filterConstraint: RelayConstraint<RelayFilter>
+ ) throws -> [RelayWithLocation<REST.ServerRelay>] {
+ let mappedRelays = mapRelays(relays: relays.wireguard.relays, locations: relays.locations)
- return exitCandidates
+ return applyConstraints(
+ relayConstraint,
+ filterConstraint: filterConstraint,
+ relays: mappedRelays
+ )
}
- // MARK: - private functions
-
- private static func findBestMatch(
+ // TODO: Add comment.
+ public static func pickCandidate(
+ from relayWithLocations: [RelayWithLocation<REST.ServerRelay>],
relays: REST.ServerRelaysResponse,
- relayConstraint: RelayConstraint<UserSelectedRelays>,
portConstraint: RelayConstraint<UInt16>,
- filterConstraint: RelayConstraint<RelayFilter>,
numberOfFailedAttempts: UInt
) throws -> RelaySelectorMatch {
- let mappedRelays = mapRelays(relays: relays.wireguard.relays, locations: relays.locations)
- let filteredRelays = applyConstraints(
- relayConstraint,
- portConstraint: portConstraint,
- filterConstraint: filterConstraint,
- relays: mappedRelays
- )
let port = applyPortConstraint(
portConstraint,
rawPortRanges: relays.wireguard.portRanges,
numberOfFailedAttempts: numberOfFailedAttempts
)
- guard let relayWithLocation = pickRandomRelayByWeight(relays: filteredRelays), let port else {
+ guard let port, let relayWithLocation = pickRandomRelayByWeight(relays: relayWithLocations) else {
throw NoRelaysSatisfyingConstraintsError()
}
diff --git a/ios/MullvadREST/Relay/RelaySelector.swift b/ios/MullvadREST/Relay/RelaySelector.swift
index 44062134cc..da4082a1b1 100644
--- a/ios/MullvadREST/Relay/RelaySelector.swift
+++ b/ios/MullvadREST/Relay/RelaySelector.swift
@@ -134,7 +134,6 @@ public enum RelaySelector {
/// Produce a list of `RelayWithLocation` items satisfying the given constraints
static func applyConstraints<T: AnyRelay>(
_ relayConstraint: RelayConstraint<UserSelectedRelays>,
- portConstraint: RelayConstraint<UInt16>,
filterConstraint: RelayConstraint<RelayFilter>,
relays: [RelayWithLocation<T>]
) -> [RelayWithLocation<T>] {
diff --git a/ios/MullvadREST/Relay/RelaySelectorPicker.swift b/ios/MullvadREST/Relay/RelaySelectorPicker.swift
new file mode 100644
index 0000000000..2ca41cb30e
--- /dev/null
+++ b/ios/MullvadREST/Relay/RelaySelectorPicker.swift
@@ -0,0 +1,200 @@
+//
+// RelaySelectorPicker.swift
+// MullvadREST
+//
+// Created by Jon Petersson on 2024-06-05.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadSettings
+import MullvadTypes
+
+protocol RelaySelectorPicker {
+ var relays: REST.ServerRelaysResponse { get }
+ var constraints: RelayConstraints { get }
+ var connectionAttemptCount: UInt { get }
+ func pick() throws -> SelectedRelays
+}
+
+extension RelaySelectorPicker {
+ func findBestMatch(
+ from candidates: [RelayWithLocation<REST.ServerRelay>]
+ ) throws -> SelectedRelay {
+ let match = try RelaySelector.WireGuard.pickCandidate(
+ from: candidates,
+ relays: relays,
+ portConstraint: constraints.port,
+ numberOfFailedAttempts: connectionAttemptCount
+ )
+
+ return SelectedRelay(
+ endpoint: match.endpoint,
+ hostname: match.relay.hostname,
+ location: match.location,
+ retryAttempts: connectionAttemptCount
+ )
+ }
+}
+
+struct SinglehopPicker: RelaySelectorPicker {
+ let constraints: RelayConstraints
+ let relays: REST.ServerRelaysResponse
+ let connectionAttemptCount: UInt
+
+ func pick() throws -> SelectedRelays {
+ let candidates = try RelaySelector.WireGuard.findCandidates(
+ by: constraints.exitLocations,
+ in: relays,
+ filterConstraint: constraints.filter
+ )
+
+ let match = try findBestMatch(from: candidates)
+
+ return SelectedRelays(entry: nil, exit: match)
+ }
+}
+
+struct MultihopPicker: RelaySelectorPicker {
+ let constraints: RelayConstraints
+ let relays: REST.ServerRelaysResponse
+ let connectionAttemptCount: UInt
+
+ func pick() throws -> SelectedRelays {
+ let entryCandidates = try RelaySelector.WireGuard.findCandidates(
+ by: constraints.entryLocations,
+ in: relays,
+ filterConstraint: constraints.filter
+ )
+
+ let exitCandidates = try RelaySelector.WireGuard.findCandidates(
+ by: constraints.exitLocations,
+ in: relays,
+ filterConstraint: constraints.filter
+ )
+
+ let decisionChain = OneToOne(
+ next: OneToMany(next: ManyToMany(next: nil, relaySelectorPicker: self), relaySelectorPicker: self),
+ relaySelectorPicker: self
+ )
+
+ return try decisionChain.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
+ }
+
+ func exclude(
+ relay: SelectedRelay,
+ from candidates: [RelayWithLocation<REST.ServerRelay>]
+ ) throws -> SelectedRelay {
+ let filteredCandidates = candidates.filter { relayWithLocation in
+ relayWithLocation.serverLocation != relay.location
+ }
+
+ return try findBestMatch(from: filteredCandidates)
+ }
+}
+
+protocol MultihopDescionMaker {
+ typealias RelayCandidate = RelayWithLocation<REST.ServerRelay>
+ init(next: MultihopDescionMaker?, relaySelectorPicker: RelaySelectorPicker)
+ func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool
+ func pick(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) throws -> SelectedRelays
+}
+
+private struct OneToOne: MultihopDescionMaker {
+ let next: MultihopDescionMaker?
+ let relaySelectorPicker: RelaySelectorPicker
+ init(next: (any MultihopDescionMaker)?, relaySelectorPicker: RelaySelectorPicker) {
+ self.next = next
+ self.relaySelectorPicker = relaySelectorPicker
+ }
+
+ func pick(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) throws -> SelectedRelays {
+ guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
+ guard let next else {
+ throw NoRelaysSatisfyingConstraintsError()
+ }
+ return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
+ }
+
+ guard entryCandidates.first != exitCandidates.first else {
+ throw NoRelaysSatisfyingConstraintsError()
+ }
+
+ let entryMatch = try relaySelectorPicker.findBestMatch(from: entryCandidates)
+ let exitMatch = try relaySelectorPicker.findBestMatch(from: exitCandidates)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ }
+
+ func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
+ entryCandidates.count == 1 && exitCandidates.count == 1
+ }
+}
+
+private struct OneToMany: MultihopDescionMaker {
+ let next: MultihopDescionMaker?
+ let relaySelectorPicker: RelaySelectorPicker
+
+ init(next: (any MultihopDescionMaker)?, relaySelectorPicker: RelaySelectorPicker) {
+ self.next = next
+ self.relaySelectorPicker = relaySelectorPicker
+ }
+
+ func pick(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) throws -> SelectedRelays {
+ guard let multihopPicker = relaySelectorPicker as? MultihopPicker else {
+ fatalError("Could not cast picker to MultihopPicker")
+ }
+
+ guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
+ guard let next else {
+ throw NoRelaysSatisfyingConstraintsError()
+ }
+ return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
+ }
+
+ switch (entryCandidates.count, exitCandidates.count) {
+ case let (1, count) where count > 1:
+ let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates)
+ let exitMatch = try multihopPicker.exclude(relay: entryMatch, from: exitCandidates)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ default:
+ let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
+ let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ }
+ }
+
+ func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
+ (entryCandidates.count == 1 && exitCandidates.count > 1) ||
+ (entryCandidates.count > 1 && exitCandidates.count == 1)
+ }
+}
+
+private struct ManyToMany: MultihopDescionMaker {
+ let next: MultihopDescionMaker?
+ let relaySelectorPicker: RelaySelectorPicker
+
+ init(next: (any MultihopDescionMaker)?, relaySelectorPicker: RelaySelectorPicker) {
+ self.next = next
+ self.relaySelectorPicker = relaySelectorPicker
+ }
+
+ func pick(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) throws -> SelectedRelays {
+ guard let multihopPicker = relaySelectorPicker as? MultihopPicker else {
+ fatalError("Could not cast picker to MultihopPicker")
+ }
+
+ guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
+ guard let next else {
+ throw NoRelaysSatisfyingConstraintsError()
+ }
+ return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
+ }
+
+ let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
+ let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ }
+
+ func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
+ entryCandidates.count > 1 && exitCandidates.count > 1
+ }
+}
diff --git a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
index a4408392e3..390757c3dd 100644
--- a/ios/PacketTunnelCore/Actor/Protocols/RelaySelectorProtocol.swift
+++ b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
@@ -11,7 +11,7 @@ import MullvadTypes
/// Protocol describing a type that can select a relay.
public protocol RelaySelectorProtocol {
- func selectRelay(with constraints: RelayConstraints, connectionAttemptFailureCount: UInt) throws -> SelectedRelay
+ func selectRelays(with constraints: RelayConstraints, connectionAttemptCount: UInt) throws -> SelectedRelays
}
/// Struct describing the selected relay.
@@ -42,3 +42,20 @@ extension SelectedRelay: CustomDebugStringConvertible {
"\(hostname) -> \(endpoint.ipv4Relay.description)"
}
}
+
+public struct SelectedRelays: Equatable, Codable {
+ public let entry: SelectedRelay?
+ public let exit: SelectedRelay
+
+ public init(entry: SelectedRelay?, exit: SelectedRelay) {
+ self.entry = entry
+ self.exit = exit
+ }
+}
+
+extension SelectedRelays: CustomDebugStringConvertible {
+ public var debugDescription: String {
+ "Entry: \(entry?.hostname ?? "-") -> \(entry?.endpoint.ipv4Relay.description ?? "-"), " +
+ "Exit: \(exit.hostname) -> \(exit.endpoint.ipv4Relay.description)"
+ }
+}
diff --git a/ios/MullvadREST/Relay/RelaySelectorWrapper.swift b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift
new file mode 100644
index 0000000000..eed453e163
--- /dev/null
+++ b/ios/MullvadREST/Relay/RelaySelectorWrapper.swift
@@ -0,0 +1,63 @@
+//
+// RelaySelectorWrapper.swift
+// PacketTunnel
+//
+// Created by pronebird on 08/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadSettings
+import MullvadTypes
+
+public final class RelaySelectorWrapper: RelaySelectorProtocol {
+ let relayCache: RelayCacheProtocol
+ let multihopUpdater: MultihopUpdater
+ private var multihopState: MultihopState = .off
+ private var observer: MultihopObserverBlock!
+
+ deinit {
+ self.multihopUpdater.removeObserver(observer)
+ }
+
+ public init(
+ relayCache: RelayCacheProtocol,
+ multihopUpdater: MultihopUpdater,
+ multihopState: MultihopState
+ ) {
+ self.relayCache = relayCache
+ self.multihopState = multihopState
+ self.multihopUpdater = multihopUpdater
+
+ self.addObserver()
+ }
+
+ public func selectRelays(
+ with constraints: RelayConstraints,
+ connectionAttemptCount: UInt
+ ) throws -> SelectedRelays {
+ let relays = try relayCache.read().relays
+
+ switch multihopState {
+ case .off:
+ return try SinglehopPicker(
+ constraints: constraints,
+ relays: relays,
+ connectionAttemptCount: connectionAttemptCount
+ ).pick()
+ case .on:
+ return try MultihopPicker(
+ constraints: constraints,
+ relays: relays,
+ connectionAttemptCount: connectionAttemptCount
+ ).pick()
+ }
+ }
+
+ private func addObserver() {
+ self.observer = MultihopObserverBlock(didUpdateMultihop: { [weak self] _, multihopState in
+ self?.multihopState = multihopState
+ })
+
+ multihopUpdater.addObserver(observer)
+ }
+}
diff --git a/ios/MullvadREST/Relay/RelayWithLocation.swift b/ios/MullvadREST/Relay/RelayWithLocation.swift
index c80cc34a3a..e6cdcac8af 100644
--- a/ios/MullvadREST/Relay/RelayWithLocation.swift
+++ b/ios/MullvadREST/Relay/RelayWithLocation.swift
@@ -9,9 +9,9 @@
import Foundation
import MullvadTypes
-struct RelayWithLocation<T: AnyRelay> {
+public struct RelayWithLocation<T: AnyRelay> {
let relay: T
- let serverLocation: Location
+ public let serverLocation: Location
func matches(location: RelayLocation) -> Bool {
return switch location {
@@ -29,3 +29,9 @@ struct RelayWithLocation<T: AnyRelay> {
}
}
}
+
+extension RelayWithLocation: Equatable {
+ public static func == (lhs: RelayWithLocation<T>, rhs: RelayWithLocation<T>) -> Bool {
+ lhs.serverLocation == rhs.serverLocation
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d6b4e6a4a1..1e25f62290 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -81,7 +81,6 @@
5820EDAB288FF0D2006BF4E4 /* DeviceRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5820EDAA288FF0D2006BF4E4 /* DeviceRowView.swift */; };
58238CB92AD57EC700768310 /* MullvadREST.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 06799ABC28F98E1D00ACD94E /* MullvadREST.framework */; };
5823FA5426CE49F700283BF8 /* TunnelObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA5326CE49F600283BF8 /* TunnelObserver.swift */; };
- 582403822A827E1500163DE8 /* RelaySelectorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582403812A827E1500163DE8 /* RelaySelectorWrapper.swift */; };
5826B6CB2ABD83E200B1CA13 /* PacketTunnelOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587C575226D2615F005EF767 /* PacketTunnelOptions.swift */; };
5827B0902B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5827B08F2B0CAA0500CCBBA1 /* EditAccessMethodCoordinator.swift */; };
5827B0922B0CAB2800CCBBA1 /* MethodSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5827B0912B0CAB2800CCBBA1 /* MethodSettingsViewController.swift */; };
@@ -448,12 +447,10 @@
58FE25DA2AA72A8F003D1918 /* PacketTunnelActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58E9C3852A4EF1CB00CFDEAC /* PacketTunnelActor.swift */; };
58FE25DB2AA72A8F003D1918 /* StartOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ED3A132A7C199C0085CE65 /* StartOptions.swift */; };
58FE25DC2AA72A8F003D1918 /* AnyTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEBA02A9CA14B00F578F2 /* AnyTask.swift */; };
- 58FE25DF2AA72A9B003D1918 /* RelaySelectorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5824037F2A827DF300163DE8 /* RelaySelectorProtocol.swift */; };
58FE25E12AA72A9B003D1918 /* SettingsReaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586E7A2C2A987689006DAB1B /* SettingsReaderProtocol.swift */; };
58FE25E62AA738E8003D1918 /* TunnelAdapterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5819ABC22A8CF02C007B59A6 /* TunnelAdapterProtocol.swift */; };
58FE25EC2AA77639003D1918 /* TunnelMonitorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25EB2AA77638003D1918 /* TunnelMonitorStub.swift */; };
58FE25EE2AA7764E003D1918 /* TunnelAdapterDummy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */; };
- 58FE25F02AA77664003D1918 /* RelaySelectorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */; };
58FE25F22AA77674003D1918 /* SettingsReaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */; };
58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25F32AA9D730003D1918 /* PacketTunnelActor+Extensions.swift */; };
58FE65952AB1D90600E53CB5 /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; };
@@ -490,15 +487,19 @@
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; };
7A3353972AAA0F8600F0A71C /* OperationBlockObserverSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */; };
7A3EFAAB2BDFDAE800318736 /* RelaySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3EFAAA2BDFDAE800318736 /* RelaySelection.swift */; };
+ 7A3AD5012C1068A800E9AD90 /* RelaySelectorPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3AD5002C1068A800E9AD90 /* RelaySelectorPicker.swift */; };
7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */; };
7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */; };
7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
7A42DEC92A05164100B209BE /* SettingsInputCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A42DEC82A05164100B209BE /* SettingsInputCell.swift */; };
7A45CFC62C05FF6A00D80B21 /* ScreenshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A45CFC22C05FF2F00D80B21 /* ScreenshotTests.swift */; };
7A45CFC72C071DD400D80B21 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C79D23F1CEBA00FE9BA7 /* SnapshotHelper.swift */; };
+ 7A4D849D2C0F289400687980 /* RelaySelectorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 582403812A827E1500163DE8 /* RelaySelectorWrapper.swift */; };
+ 7A4D849E2C0F289800687980 /* RelaySelectorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5824037F2A827DF300163DE8 /* RelaySelectorProtocol.swift */; };
7A516C2E2B6D357500BBD33D /* URL+Scoping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A516C2D2B6D357500BBD33D /* URL+Scoping.swift */; };
7A516C3A2B7111A700BBD33D /* IPOverrideWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A516C392B7111A700BBD33D /* IPOverrideWrapper.swift */; };
7A516C3C2B712F0B00BBD33D /* IPOverrideWrapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A516C3B2B712F0B00BBD33D /* IPOverrideWrapperTests.swift */; };
+ 7A52F96A2C1735AE00B133B9 /* RelaySelectorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */; };
7A5869952B32E9C700640D27 /* LinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869942B32E9C700640D27 /* LinkButton.swift */; };
7A5869972B32EA4500640D27 /* AppButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A5869962B32EA4500640D27 /* AppButton.swift */; };
7A58699B2B482FE200640D27 /* UITableViewCell+Disable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A58699A2B482FE200640D27 /* UITableViewCell+Disable.swift */; };
@@ -1893,6 +1894,7 @@
7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = "<group>"; };
7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBlockObserverSupport.swift; sourceTree = "<group>"; };
7A3EFAAA2BDFDAE800318736 /* RelaySelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelection.swift; sourceTree = "<group>"; };
+ 7A3AD5002C1068A800E9AD90 /* RelaySelectorPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorPicker.swift; sourceTree = "<group>"; };
7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandlerTests.swift; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A45CFC22C05FF2F00D80B21 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
@@ -2502,8 +2504,8 @@
440E9EF42BDA943B00B1FD11 /* ApiHandlers */ = {
isa = PBXGroup;
children = (
- F0ACE3342BE51745006D5333 /* ServerRelaysResponse+Stubs.swift */,
A9CF11FC2A0518E7001D9565 /* AddressCacheTests.swift */,
+ F0ACE3342BE51745006D5333 /* ServerRelaysResponse+Stubs.swift */,
);
path = ApiHandlers;
sourceTree = "<group>";
@@ -3730,7 +3732,6 @@
isa = PBXGroup;
children = (
580D6B8B2AB3369300B2D6E0 /* BlockedStateErrorMapperProtocol.swift */,
- 5824037F2A827DF300163DE8 /* RelaySelectorProtocol.swift */,
586E7A2C2A987689006DAB1B /* SettingsReaderProtocol.swift */,
5819ABC22A8CF02C007B59A6 /* TunnelAdapterProtocol.swift */,
);
@@ -3746,7 +3747,6 @@
58EC067B2A8D2A0B00BEB973 /* NetworkCounters.swift */,
58FE25EB2AA77638003D1918 /* TunnelMonitorStub.swift */,
58FE25ED2AA7764E003D1918 /* TunnelAdapterDummy.swift */,
- 58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */,
58FE25F12AA77674003D1918 /* SettingsReaderStub.swift */,
58F7753C2AB8473200425B47 /* BlockedStateErrorMapperStub.swift */,
5838321A2AC1B18400EA2071 /* PacketTunnelActor+Mocks.swift */,
@@ -4182,6 +4182,7 @@
A900E9BD2ACC654100C95F67 /* APIProxy+Stubs.swift */,
A900E9BB2ACC609200C95F67 /* DevicesProxy+Stubs.swift */,
F0ACE32E2BE4EA8B006D5333 /* MockProxyFactory.swift */,
+ 58FE25EF2AA77664003D1918 /* RelaySelectorStub.swift */,
A900E9B92ACC5D0600C95F67 /* RESTRequestExecutor+Stubs.swift */,
);
path = MullvadREST;
@@ -4209,9 +4210,12 @@
F0DDE4282B220A15006B57A7 /* RelaySelector.swift */,
F0B894F42BF7528700817A42 /* RelaySelector+Shadowsocks.swift */,
F0B894F22BF7526700817A42 /* RelaySelector+Wireguard.swift */,
+ 5824037F2A827DF300163DE8 /* RelaySelectorProtocol.swift */,
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */,
+ 582403812A827E1500163DE8 /* RelaySelectorWrapper.swift */,
F0B894F02BF751E300817A42 /* RelayWithDistance.swift */,
F0B894EE2BF751C500817A42 /* RelayWithLocation.swift */,
+ 7A3AD5002C1068A800E9AD90 /* RelaySelectorPicker.swift */,
);
path = Relay;
sourceTree = "<group>";
@@ -5317,9 +5321,12 @@
F0DDE4162B220458006B57A7 /* TransportProvider.swift in Sources */,
06799AEF28F98E4800ACD94E /* RetryStrategy.swift in Sources */,
06799AE128F98E4800ACD94E /* SSLPinningURLSessionDelegate.swift in Sources */,
+ 7A4D849E2C0F289800687980 /* RelaySelectorProtocol.swift in Sources */,
F0164EBE2B4BFF940020268D /* ShadowsocksLoader.swift in Sources */,
A9A1DE792AD5708E0073F689 /* TransportStrategy.swift in Sources */,
A90763BF2B2857D50045ADF0 /* Socks5Handshake.swift in Sources */,
+ 7A3AD5012C1068A800E9AD90 /* RelaySelectorPicker.swift in Sources */,
+ 7A4D849D2C0F289400687980 /* RelaySelectorWrapper.swift in Sources */,
A90763C52B2858B40045ADF0 /* AnyIPEndpoint+Socks5.swift in Sources */,
F06045EC2B2322A500B2D37A /* Jittered.swift in Sources */,
F0DDE4152B220458006B57A7 /* ShadowsocksConfigurationCache.swift in Sources */,
@@ -5603,7 +5610,6 @@
files = (
58FE25F42AA9D730003D1918 /* PacketTunnelActor+Extensions.swift in Sources */,
58DDA18F2ABC32380039C360 /* Timings.swift in Sources */,
- 58FE25DF2AA72A9B003D1918 /* RelaySelectorProtocol.swift in Sources */,
58C7A4522A863FB50060C66F /* Pinger.swift in Sources */,
580D6B8C2AB3369300B2D6E0 /* BlockedStateErrorMapperProtocol.swift in Sources */,
58C7AF172ABD84AA007EDD7A /* ProxyURLRequest.swift in Sources */,
@@ -5670,7 +5676,6 @@
5838321D2AC1C54600EA2071 /* TaskSleepTests.swift in Sources */,
58092E542A8B832E00C3CC72 /* TunnelMonitorTests.swift in Sources */,
7AD0AA212AD6CB0000119E10 /* URLRequestProxyStub.swift in Sources */,
- 58FE25F02AA77664003D1918 /* RelaySelectorStub.swift in Sources */,
581F23AF2A8CF94D00788AB6 /* PingerMock.swift in Sources */,
A97D25B42B0CB59300946B2D /* TunnelObfuscationStub.swift in Sources */,
A97D25B02B0BB5C400946B2D /* ProtocolObfuscationStub.swift in Sources */,
@@ -6075,7 +6080,6 @@
583FE02429C1ACB3006E85F9 /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
58CE38C728992C8700A6D6E5 /* WireGuardAdapterError+Localization.swift in Sources */,
58E511E828DDDF2400B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
- 582403822A827E1500163DE8 /* RelaySelectorWrapper.swift in Sources */,
58FDF2D92A0BA11A00C2B061 /* DeviceCheckOperation.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -6286,6 +6290,7 @@
F0ACE3222BE4E4F2006D5333 /* APIProxy+Stubs.swift in Sources */,
F0ACE3332BE516F1006D5333 /* RESTRequestExecutor+Stubs.swift in Sources */,
F0ACE32D2BE4E784006D5333 /* AccountMock.swift in Sources */,
+ 7A52F96A2C1735AE00B133B9 /* RelaySelectorStub.swift in Sources */,
F0ACE32F2BE4EA8B006D5333 /* MockProxyFactory.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 929180afa4..0000000000
--- a/ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "pins" : [
- {
- "identity" : "swift-log",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-log.git",
- "state" : {
- "revision" : "173f567a2dfec11d74588eea82cecea555bdc0bc",
- "version" : "1.4.0"
- }
- },
- {
- "identity" : "wireguard-apple",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/mullvad/wireguard-apple.git",
- "state" : {
- "revision" : "15242e1698fc45261285d7417ed2cd5130d7332e"
- }
- }
- ],
- "version" : 2
-}
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index fc6c746cd6..8ff9c3d6b3 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -85,14 +85,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
addressCacheTracker = AddressCacheTracker(application: application, apiProxy: apiProxy, store: addressCache)
-
tunnelStore = TunnelStore(application: application)
- tunnelManager = createTunnelManager(application: application)
let constraintsUpdater = RelayConstraintsUpdater()
let multihopListener = MultihopStateListener()
let multihopUpdater = MultihopUpdater(listener: multihopListener)
+ let relaySelector = RelaySelectorWrapper(
+ relayCache: ipOverrideWrapper,
+ multihopUpdater: multihopUpdater,
+ multihopState: multihopState
+ )
+ tunnelManager = createTunnelManager(application: application, relaySelector: relaySelector)
+
settingsObserver = TunnelBlockObserver(didLoadConfiguration: { tunnelManager in
multihopListener.onNewMultihop?(tunnelManager.settings.tunnelMultihopState)
constraintsUpdater.onNewConstraints?(tunnelManager.settings.relayConstraints)
@@ -139,7 +144,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
transportStrategy: transportStrategy
)
setUpTransportMonitor(transportProvider: transportProvider)
- setUpSimulatorHost(transportProvider: transportProvider)
+ setUpSimulatorHost(transportProvider: transportProvider, relaySelector: relaySelector)
registerBackgroundTasks()
setupPaymentHandler()
@@ -151,7 +156,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
return true
}
- private func createTunnelManager(application: UIApplication) -> TunnelManager {
+ private func createTunnelManager(
+ application: UIApplication,
+ relaySelector: RelaySelectorProtocol
+ ) -> TunnelManager {
return TunnelManager(
application: application,
tunnelStore: tunnelStore,
@@ -159,7 +167,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy: accountsProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
- accessTokenManager: proxyFactory.configuration.accessTokenManager
+ accessTokenManager: proxyFactory.configuration.accessTokenManager,
+ relaySelector: relaySelector
)
}
@@ -192,11 +201,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
}
- private func setUpSimulatorHost(transportProvider: TransportProvider) {
+ private func setUpSimulatorHost(
+ transportProvider: TransportProvider,
+ relaySelector: RelaySelectorWrapper
+ ) {
#if targetEnvironment(simulator)
// Configure mock tunnel provider on simulator
simulatorTunnelProviderHost = SimulatorTunnelProviderHost(
- relayCacheTracker: relayCacheTracker,
+ relaySelector: relaySelector,
transportProvider: transportProvider
)
SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index e53c5a50b2..bc3ce91307 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
@@ -20,13 +20,13 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
private var observedState: ObservedState = .disconnected
private var selectedRelay: SelectedRelay?
private let urlRequestProxy: URLRequestProxy
- private let relayCacheTracker: RelayCacheTracker
+ private let relaySelector: RelaySelectorProtocol
private let providerLogger = Logger(label: "SimulatorTunnelProviderHost")
private let dispatchQueue = DispatchQueue(label: "SimulatorTunnelProviderHostQueue")
- init(relayCacheTracker: RelayCacheTracker, transportProvider: TransportProvider) {
- self.relayCacheTracker = relayCacheTracker
+ init(relaySelector: RelaySelectorProtocol, transportProvider: TransportProvider) {
+ self.relaySelector = relaySelector
self.urlRequestProxy = URLRequestProxy(
dispatchQueue: dispatchQueue,
transportProvider: transportProvider
@@ -157,19 +157,12 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
}
private func pickRelay() throws -> SelectedRelay {
- let cachedRelays = try relayCacheTracker.getCachedRelays()
let tunnelSettings = try SettingsManager.readSettings()
- let selectorResult = try RelaySelector.WireGuard.evaluate(
- by: tunnelSettings.relayConstraints,
- in: cachedRelays.relays,
- numberOfFailedAttempts: 0
- )
- return SelectedRelay(
- endpoint: selectorResult.endpoint,
- hostname: selectorResult.relay.hostname,
- location: selectorResult.location,
- retryAttempts: 0
- )
+
+ return try relaySelector.selectRelays(
+ with: tunnelSettings.relayConstraints,
+ connectionAttemptCount: 0
+ ).exit // TODO: Multihop
}
private func setInternalStateConnected(with selectedRelay: SelectedRelay?) {
diff --git a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
index 3ec3a9791b..ef76cd8b85 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelInteractor.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadREST
import MullvadSettings
import PacketTunnelCore
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index 13a2d661dd..4e4d44cd4c 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -59,6 +59,7 @@ final class TunnelManager: StorePaymentObserver {
private var lastMapConnectionStatusOperation: Operation?
private let observerList = ObserverList<TunnelObserver>()
private var networkMonitor: NWPathMonitor?
+ private let relaySelector: RelaySelectorProtocol
private var privateKeyRotationTimer: DispatchSourceTimer?
public private(set) var isRunningPeriodicPrivateKeyRotation = false
@@ -86,7 +87,8 @@ final class TunnelManager: StorePaymentObserver {
accountsProxy: RESTAccountHandling,
devicesProxy: DeviceHandling,
apiProxy: APIQuerying,
- accessTokenManager: RESTAccessTokenManagement
+ accessTokenManager: RESTAccessTokenManagement,
+ relaySelector: RelaySelectorProtocol
) {
self.application = application
self.tunnelStore = tunnelStore
@@ -97,6 +99,7 @@ final class TunnelManager: StorePaymentObserver {
self.operationQueue.name = "TunnelManager.operationQueue"
self.operationQueue.underlyingQueue = internalQueue
self.accessTokenManager = accessTokenManager
+ self.relaySelector = relaySelector
NotificationCenter.default.addObserver(
self,
@@ -780,20 +783,12 @@ final class TunnelManager: StorePaymentObserver {
}
fileprivate func selectRelay() throws -> SelectedRelay {
- let cachedRelays = try relayCacheTracker.getCachedRelays()
let retryAttempts = tunnelStatus.observedState.connectionState?.connectionAttemptCount ?? 0
- let selectorResult = try RelaySelector.WireGuard.evaluate(
- by: settings.relayConstraints,
- in: cachedRelays.relays,
- numberOfFailedAttempts: retryAttempts
- )
- return SelectedRelay(
- endpoint: selectorResult.endpoint,
- hostname: selectorResult.relay.hostname,
- location: selectorResult.location,
- retryAttempts: retryAttempts
- )
+ return try relaySelector.selectRelays(
+ with: settings.relayConstraints,
+ connectionAttemptCount: retryAttempts
+ ).exit // TODO: Multihop
}
fileprivate func prepareForVPNConfigurationDeletion() {
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift
index 76148bdbb8..43ae78e17d 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelState.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadREST
import MullvadTypes
import PacketTunnelCore
import WireGuardKitTypes
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
index 584a3c7ff3..6ef103f93c 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
@@ -7,6 +7,7 @@
//
import MapKit
+import MullvadREST
import MullvadTypes
import PacketTunnelCore
import UIKit
diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
index 9163613bbe..50df5635a0 100644
--- a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
@@ -24,12 +24,7 @@ class RelaySelectorTests: XCTestCase {
exitLocations: .only(UserSelectedRelays(locations: [.country("es")]))
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertEqual(result.relay.hostname, "es1-wireguard")
}
@@ -38,11 +33,7 @@ class RelaySelectorTests: XCTestCase {
exitLocations: .only(UserSelectedRelays(locations: [.city("se", "got")]))
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertEqual(result.relay.hostname, "se10-wireguard")
}
@@ -51,12 +42,7 @@ class RelaySelectorTests: XCTestCase {
exitLocations: .only(UserSelectedRelays(locations: [.hostname("se", "sto", "se6-wireguard")]))
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertEqual(result.relay.hostname, "se6-wireguard")
}
@@ -87,7 +73,6 @@ class RelaySelectorTests: XCTestCase {
let constrainedLocations = RelaySelector.applyConstraints(
constraints.exitLocations,
- portConstraint: constraints.port,
filterConstraint: constraints.filter,
relays: relayWithLocations
)
@@ -111,12 +96,7 @@ class RelaySelectorTests: XCTestCase {
port: .only(1)
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertEqual(result.endpoint.ipv4Relay.port, 1)
}
@@ -126,39 +106,19 @@ class RelaySelectorTests: XCTestCase {
)
let allPorts = portRanges.flatMap { $0 }
- var result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
+ var result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertTrue(allPorts.contains(result.endpoint.ipv4Relay.port))
- result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 1
- )
+ result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 1)
XCTAssertTrue(allPorts.contains(result.endpoint.ipv4Relay.port))
- result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 2
- )
+ result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 2)
XCTAssertEqual(result.endpoint.ipv4Relay.port, defaultPort)
- result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 3
- )
+ result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 3)
XCTAssertEqual(result.endpoint.ipv4Relay.port, defaultPort)
- result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 4
- )
+ result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 4)
XCTAssertTrue(allPorts.contains(result.endpoint.ipv4Relay.port))
}
@@ -200,12 +160,7 @@ class RelaySelectorTests: XCTestCase {
filter: .only(filter)
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertTrue(result.relay.owned)
}
@@ -217,13 +172,7 @@ class RelaySelectorTests: XCTestCase {
filter: .only(filter)
)
- let result = try? RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
- XCTAssertNil(result)
+ XCTAssertThrowsError(try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0))
}
func testRelayFilterConstraintWithCorrectProvider() throws {
@@ -235,12 +184,7 @@ class RelaySelectorTests: XCTestCase {
filter: .only(filter)
)
- let result = try RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
- )
-
+ let result = try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0)
XCTAssertEqual(result.relay.provider, provider)
}
@@ -253,14 +197,27 @@ class RelaySelectorTests: XCTestCase {
filter: .only(filter)
)
- let result = try? RelaySelector.WireGuard.evaluate(
- by: constraints,
- in: sampleRelays,
- numberOfFailedAttempts: 0
+ XCTAssertThrowsError(try pickRelay(by: constraints, in: sampleRelays, failedAttemptCount: 0))
+ }
+}
+
+extension RelaySelectorTests {
+ private func pickRelay(
+ by constraints: RelayConstraints,
+ in relays: REST.ServerRelaysResponse,
+ failedAttemptCount: UInt
+ ) throws -> RelaySelectorMatch {
+ let candidates = try RelaySelector.WireGuard.findCandidates(
+ by: constraints.exitLocations,
+ in: relays,
+ filterConstraint: constraints.filter
)
- XCTAssertNil(result)
+ return try RelaySelector.WireGuard.pickCandidate(
+ from: candidates,
+ relays: relays,
+ portConstraint: constraints.port,
+ numberOfFailedAttempts: failedAttemptCount
+ )
}
-
- // MARK: - Multi-Hop tests
}
diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
index a57d78bd39..b538b910d8 100644
--- a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
@@ -7,29 +7,19 @@
//
import MullvadTypes
+import MullvadMockData
@testable import PacketTunnelCore
+@testable import PacketTunnelCoreTests
import WireGuardKitTypes
import XCTest
final class PacketTunnelActorReducerTests: XCTestCase {
- // test data
- let selectedRelay = SelectedRelay(
- endpoint: MullvadEndpoint(
- ipv4Relay: IPv4Endpoint(ip: .loopback, port: 1300),
- ipv4Gateway: .loopback,
- ipv6Gateway: .loopback,
- publicKey: PrivateKey().publicKey.rawValue
- ),
- hostname: "se-got",
- location: Location(
- country: "",
- countryCode: "se",
- city: "",
- cityCode: "got",
- latitude: 0,
- longitude: 0
- ), retryAttempts: 0
- )
+ // swiftlint:disable:next force_try
+ let selectedRelay = try! RelaySelectorStub
+ .nonFallible()
+ .selectRelays(with: RelayConstraints(), connectionAttemptCount: 0)
+ .exit // TODO: Multihop
+
func makeConnectionData(keyPolicy: State.KeyPolicy = .useCurrent) -> State.ConnectionData {
State.ConnectionData(
selectedRelay: selectedRelay,
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
index 49784143e8..622c5269ad 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/MockTunnelInteractor.swift
@@ -7,8 +7,8 @@
//
import Foundation
+import MullvadREST
import MullvadSettings
-import PacketTunnelCore
// this is still very minimal, and will be fleshed out as needed.
class MockTunnelInteractor: TunnelInteractor {
@@ -73,7 +73,7 @@ class MockTunnelInteractor: TunnelInteractor {
struct NotImplementedError: Error {}
- func selectRelay() throws -> PacketTunnelCore.SelectedRelay {
+ func selectRelay() throws -> SelectedRelay {
throw NotImplementedError()
}
}
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
index e9843b5dab..3b9dff23d0 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
@@ -33,6 +33,7 @@ final class TunnelManagerTests: XCTestCase {
let devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
let apiProxy = APIProxyStub()
let accessTokenManager = AccessTokenManagerStub()
+ let relaySelector = RelaySelectorStub.nonFallible()
let tunnelManager = TunnelManager(
application: application,
tunnelStore: tunnelStore,
@@ -40,7 +41,8 @@ final class TunnelManagerTests: XCTestCase {
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
- accessTokenManager: accessTokenManager
+ accessTokenManager: accessTokenManager,
+ relaySelector: relaySelector
)
XCTAssertNotNil(tunnelManager)
}
@@ -54,6 +56,7 @@ final class TunnelManagerTests: XCTestCase {
let apiProxy = APIProxyStub()
let accessTokenManager = AccessTokenManagerStub()
accountProxy.createAccountResult = .success(REST.NewAccountData.mockValue())
+ let relaySelector = RelaySelectorStub.nonFallible()
let tunnelManager = TunnelManager(
application: application,
tunnelStore: tunnelStore,
@@ -61,7 +64,8 @@ final class TunnelManagerTests: XCTestCase {
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
- accessTokenManager: accessTokenManager
+ accessTokenManager: accessTokenManager,
+ relaySelector: relaySelector
)
_ = try await tunnelManager.setNewAccount()
XCTAssertEqual(tunnelManager.isRunningPeriodicPrivateKeyRotation, true)
@@ -76,6 +80,7 @@ final class TunnelManagerTests: XCTestCase {
let apiProxy = APIProxyStub()
let accessTokenManager = AccessTokenManagerStub()
accountProxy.createAccountResult = .success(REST.NewAccountData.mockValue())
+ let relaySelector = RelaySelectorStub.nonFallible()
let tunnelManager = TunnelManager(
application: application,
tunnelStore: tunnelStore,
@@ -83,7 +88,8 @@ final class TunnelManagerTests: XCTestCase {
accountsProxy: accountProxy,
devicesProxy: devicesProxy,
apiProxy: apiProxy,
- accessTokenManager: accessTokenManager
+ accessTokenManager: accessTokenManager,
+ relaySelector: relaySelector
)
_ = try await tunnelManager.setNewAccount()
await tunnelManager.unsetAccount()
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index ab464a2274..ebefeee786 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -92,10 +92,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
protocolObfuscator: ProtocolObfuscator<UDPOverTCPObfuscator>()
)
- postQuantumActor = PostQuantumKeyExchangeActor(
- packetTunnel: self,
- onFailure: self.keyExchangeFailed
- )
+ postQuantumActor = PostQuantumKeyExchangeActor(packetTunnel: self, onFailure: self.keyExchangeFailed)
let urlRequestProxy = URLRequestProxy(dispatchQueue: internalQueue, transportProvider: transportProvider)
appMessageHandler = AppMessageHandler(packetTunnelActor: actor, urlRequestProxy: urlRequestProxy)
diff --git a/ios/PacketTunnelCore/Actor/ObservedState.swift b/ios/PacketTunnelCore/Actor/ObservedState.swift
index bdb85a8e51..6975191e04 100644
--- a/ios/PacketTunnelCore/Actor/ObservedState.swift
+++ b/ios/PacketTunnelCore/Actor/ObservedState.swift
@@ -8,6 +8,7 @@
import Combine
import Foundation
+import MullvadREST
import MullvadTypes
import Network
import WireGuardKitTypes
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index f497df07ae..f3bc9cdc5a 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadLogging
+import MullvadREST
import MullvadTypes
import NetworkExtension
import TunnelObfuscation
@@ -464,10 +465,10 @@ extension PacketTunnelActor {
}
case .random:
- return try relaySelector.selectRelay(
+ return try relaySelector.selectRelays(
with: relayConstraints,
- connectionAttemptFailureCount: connectionAttemptCount
- )
+ connectionAttemptCount: connectionAttemptCount
+ ).exit // TODO: Multihop
case let .preSelected(selectedRelay):
return selectedRelay
diff --git a/ios/PacketTunnelCore/Actor/StartOptions.swift b/ios/PacketTunnelCore/Actor/StartOptions.swift
index 9dd3ffeb68..0e484ef58f 100644
--- a/ios/PacketTunnelCore/Actor/StartOptions.swift
+++ b/ios/PacketTunnelCore/Actor/StartOptions.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadREST
/// Packet tunnel start options parsed from dictionary passed to packet tunnel with a call to `startTunnel()`.
public struct StartOptions {
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index 1afc4ca768..0aae1ac602 100644
--- a/ios/PacketTunnelCore/Actor/State.swift
+++ b/ios/PacketTunnelCore/Actor/State.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadREST
import MullvadTypes
import TunnelObfuscation
import WireGuardKitTypes
diff --git a/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift b/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift
index 742fb1f12f..ad632baa16 100644
--- a/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift
+++ b/ios/PacketTunnelCore/IPC/PacketTunnelOptions.swift
@@ -7,6 +7,7 @@
//
import Foundation
+import MullvadREST
public struct PacketTunnelOptions {
/// Keys for options dictionary
diff --git a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
index 0adb3ec0a6..dc85adcaa7 100644
--- a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
+++ b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
@@ -81,16 +81,24 @@ final class AppMessageHandlerTests: XCTestCase {
let relayConstraints = RelayConstraints(
exitLocations: .only(UserSelectedRelays(locations: [.hostname("se", "sto", "se6-wireguard")]))
)
- let selectorResult = try XCTUnwrap(try? RelaySelector.WireGuard.evaluate(
- by: relayConstraints,
+
+ let candidates = try RelaySelector.WireGuard.findCandidates(
+ by: relayConstraints.exitLocations,
in: ServerRelaysResponseStubs.sampleRelays,
+ filterConstraint: relayConstraints.filter
+ )
+
+ let match = try RelaySelector.WireGuard.pickCandidate(
+ from: candidates,
+ relays: ServerRelaysResponseStubs.sampleRelays,
+ portConstraint: relayConstraints.port,
numberOfFailedAttempts: 0
- ))
+ )
let selectedRelay = SelectedRelay(
- endpoint: selectorResult.endpoint,
- hostname: selectorResult.relay.hostname,
- location: selectorResult.location,
+ endpoint: match.endpoint,
+ hostname: match.relay.hostname,
+ location: match.location,
retryAttempts: 0
)
diff --git a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
index c33f20457d..49705b5dc5 100644
--- a/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/PacketTunnelActor+Mocks.swift
@@ -7,6 +7,8 @@
//
import Foundation
+import MullvadMockData
+import MullvadREST
import PacketTunnelCore
extension PacketTunnelActorTimings {
diff --git a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
index 4419f5b961..b656230c70 100644
--- a/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
+++ b/ios/PacketTunnelCoreTests/PacketTunnelActorTests.swift
@@ -208,7 +208,8 @@ final class PacketTunnelActorTests: XCTestCase {
3. The issue goes away on the second attempt to read settings.
4. An actor should transition through `.connecting` towards`.connected` state.
*/
- func testLockedDeviceErrorOnBoot() async throws { // swiftlint:disable:this function_body_length
+ // swiftlint:disable:next function_body_length
+ func testLockedDeviceErrorOnBoot() async throws {
let initialStateExpectation = expectation(description: "Expect initial state")
let errorStateExpectation = expectation(description: "Expect error state")
let connectingStateExpectation = expectation(description: "Expect connecting state")