diff options
| author | Jon Petersson <jon.petersson@kvadrat.se> | 2024-06-04 12:18:44 +0200 |
|---|---|---|
| committer | Emīls <emils@mullvad.net> | 2024-07-11 16:53:24 +0200 |
| commit | 67e9e0ef08723cd991659a106398cbe0cb88493e (patch) | |
| tree | a56eb3baae5b5120afb2e9491027994ccaa80aa2 | |
| parent | 5bd43f95a4189aff24bc9ea120b2841b222c6921 (diff) | |
| download | mullvadvpn-67e9e0ef08723cd991659a106398cbe0cb88493e.tar.xz mullvadvpn-67e9e0ef08723cd991659a106398cbe0cb88493e.zip | |
Allow relay selector to select an entry peer
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") |
