Skip to content

Commit

Permalink
Allow relay selector to filter DAITA enabled relays
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson authored and buggmagnet committed Aug 21, 2024
1 parent 1ee3102 commit b5baa95
Show file tree
Hide file tree
Showing 25 changed files with 459 additions and 92 deletions.
2 changes: 1 addition & 1 deletion ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ extension RelaySelectorStub {
/// Returns a relay selector that cannot satisfy constraints .
public static func unsatisfied() -> RelaySelectorStub {
return RelaySelectorStub { _ in
throw NoRelaysSatisfyingConstraintsError()
throw NoRelaysSatisfyingConstraintsError(.relayConstraintNotMatching)
}
}
}
5 changes: 4 additions & 1 deletion ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ extension REST {
public let ipv4AddrIn: IPv4Address
public let weight: UInt64
public let includeInCountry: Bool
public var daita: Bool?

public func override(ipv4AddrIn: IPv4Address?) -> Self {
return BridgeRelay(
Expand All @@ -60,6 +61,7 @@ extension REST {
public let ipv6AddrIn: IPv6Address
public let publicKey: Data
public let includeInCountry: Bool
public let daita: Bool?

public func override(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self {
return ServerRelay(
Expand All @@ -72,7 +74,8 @@ extension REST {
ipv4AddrIn: ipv4AddrIn ?? self.ipv4AddrIn,
ipv6AddrIn: ipv6AddrIn ?? self.ipv6AddrIn,
publicKey: publicKey,
includeInCountry: includeInCountry
includeInCountry: includeInCountry,
daita: daita
)
}
}
Expand Down
1 change: 1 addition & 0 deletions ios/MullvadREST/Relay/AnyRelay.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public protocol AnyRelay {
var weight: UInt64 { get }
var active: Bool { get }
var includeInCountry: Bool { get }
var daita: Bool? { get }

func override(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self
}
Expand Down
8 changes: 4 additions & 4 deletions ios/MullvadREST/Relay/MultihopDecisionFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ struct OneToOne: MultihopDecisionFlow {
func pick(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) throws -> SelectedRelays {
guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
guard let next else {
throw NoRelaysSatisfyingConstraintsError()
throw NoRelaysSatisfyingConstraintsError(.multihopInvalidFlow)
}
return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
}

guard entryCandidates.first != exitCandidates.first else {
throw NoRelaysSatisfyingConstraintsError()
throw NoRelaysSatisfyingConstraintsError(.entryEqualsExit)
}

let entryMatch = try relayPicker.findBestMatch(from: entryCandidates)
Expand Down Expand Up @@ -61,7 +61,7 @@ struct OneToMany: MultihopDecisionFlow {

guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
guard let next else {
throw NoRelaysSatisfyingConstraintsError()
throw NoRelaysSatisfyingConstraintsError(.multihopInvalidFlow)
}
return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
}
Expand Down Expand Up @@ -100,7 +100,7 @@ struct ManyToMany: MultihopDecisionFlow {

guard canHandle(entryCandidates: entryCandidates, exitCandidates: exitCandidates) else {
guard let next else {
throw NoRelaysSatisfyingConstraintsError()
throw NoRelaysSatisfyingConstraintsError(.multihopInvalidFlow)
}
return try next.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
}
Expand Down
33 changes: 31 additions & 2 deletions ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,39 @@

import Foundation

public enum NoRelaysSatisfyingConstraintsReason {
case filterConstraintNotMatching
case invalidPort
case entryEqualsExit
case multihopInvalidFlow
case noActiveRelaysFound
case noDaitaRelaysFound
case relayConstraintNotMatching
}

public struct NoRelaysSatisfyingConstraintsError: LocalizedError {
public init() {}
public let reason: NoRelaysSatisfyingConstraintsReason

public var errorDescription: String? {
"No relays satisfying constraints."
switch reason {
case .filterConstraintNotMatching:
"Filter yields no matching relays"
case .invalidPort:
"Invalid port selected by RelaySelector"
case .entryEqualsExit:
"Entry and exit relays are the same"
case .multihopInvalidFlow:
"Invalid multihop decision flow"
case .noActiveRelaysFound:
"No active relays found"
case .noDaitaRelaysFound:
"No DAITA relays found"
case .relayConstraintNotMatching:
"Invalid constraint created to pick a relay"
}
}

public init(_ reason: NoRelaysSatisfyingConstraintsReason) {
self.reason = reason
}
}
38 changes: 30 additions & 8 deletions ios/MullvadREST/Relay/RelayPicking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,38 +37,60 @@ extension RelayPicking {

struct SinglehopPicker: RelayPicking {
let constraints: RelayConstraints
let daitaSettings: DAITASettings
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
)
var exitCandidates = [RelayWithLocation<REST.ServerRelay>]()

let match = try findBestMatch(from: candidates)
do {
exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.state.isEnabled
)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
#if DEBUG
// If DAITA is enabled and no supported relays are found, we should try to find the nearest
// available relay that supports DAITA and use it as entry in a multihop selection.
var constraints = constraints
constraints.entryLocations = .any

return try MultihopPicker(
constraints: constraints,
daitaSettings: daitaSettings,
relays: relays,
connectionAttemptCount: connectionAttemptCount
).pick()
#endif
}

let match = try findBestMatch(from: exitCandidates)
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
}
}

struct MultihopPicker: RelayPicking {
let constraints: RelayConstraints
let daitaSettings: DAITASettings
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
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.state.isEnabled
)

let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
filterConstraint: constraints.filter
filterConstraint: constraints.filter,
daitaEnabled: false
)

/*
Expand Down
5 changes: 3 additions & 2 deletions ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ extension RelaySelector {
in relaysResponse: REST.ServerRelaysResponse
) -> REST.BridgeRelay? {
let mappedBridges = mapRelays(relays: relaysResponse.bridge.relays, locations: relaysResponse.locations)
let filteredRelays = applyConstraints(
let filteredRelays = (try? applyConstraints(
location,
filterConstraint: filter,
daitaEnabled: false,
relays: mappedBridges
)
)) ?? []
guard filteredRelays.isEmpty == false else { return relay(from: relaysResponse) }

// Compute the midpoint location from all the filtered relays
Expand Down
15 changes: 11 additions & 4 deletions ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import MullvadTypes

extension RelaySelector {
Expand All @@ -14,13 +15,15 @@ extension RelaySelector {
public static func findCandidates(
by relayConstraint: RelayConstraint<UserSelectedRelays>,
in relays: REST.ServerRelaysResponse,
filterConstraint: RelayConstraint<RelayFilter>
filterConstraint: RelayConstraint<RelayFilter>,
daitaEnabled: Bool
) throws -> [RelayWithLocation<REST.ServerRelay>] {
let mappedRelays = mapRelays(relays: relays.wireguard.relays, locations: relays.locations)

return applyConstraints(
return try applyConstraints(
relayConstraint,
filterConstraint: filterConstraint,
daitaEnabled: daitaEnabled,
relays: mappedRelays
)
}
Expand All @@ -38,8 +41,12 @@ extension RelaySelector {
numberOfFailedAttempts: numberOfFailedAttempts
)

guard let port, let relayWithLocation = pickRandomRelayByWeight(relays: relayWithLocations) else {
throw NoRelaysSatisfyingConstraintsError()
guard let port else {
throw NoRelaysSatisfyingConstraintsError(.invalidPort)
}

guard let relayWithLocation = pickRandomRelayByWeight(relays: relayWithLocations) else {
throw NoRelaysSatisfyingConstraintsError(.relayConstraintNotMatching)
}

let endpoint = MullvadEndpoint(
Expand Down
Loading

0 comments on commit b5baa95

Please sign in to comment.