summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2024-09-06 11:28:36 +0200
committerJon Petersson <jon.petersson@kvadrat.se>2024-09-06 11:28:36 +0200
commita9ead2f7a78dcf7eb1f48aa1a1cf797184392c91 (patch)
tree54488562fe64a0d8838ef62cfbf8feaa774aec3b
parent50c9d86f2f802af421d8ca9a2a9278c3d1237940 (diff)
downloadmullvadvpn-closest-daita-relay.tar.xz
mullvadvpn-closest-daita-relay.zip
Smart routing should select the closest relay with Daitaclosest-daita-relay
-rw-r--r--ios/MullvadREST/Relay/RelayPicking.swift41
-rw-r--r--ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift39
-rw-r--r--ios/MullvadREST/Relay/RelaySelector+Wireguard.swift75
-rw-r--r--ios/MullvadREST/Relay/RelaySelector.swift38
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Relay/RelayPickingTests.swift18
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift6
-rw-r--r--ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift2
8 files changed, 153 insertions, 68 deletions
diff --git a/ios/MullvadREST/Relay/RelayPicking.swift b/ios/MullvadREST/Relay/RelayPicking.swift
index 15ae2ee90c..1c54953ef1 100644
--- a/ios/MullvadREST/Relay/RelayPicking.swift
+++ b/ios/MullvadREST/Relay/RelayPicking.swift
@@ -13,18 +13,18 @@ protocol RelayPicking {
var relays: REST.ServerRelaysResponse { get }
var constraints: RelayConstraints { get }
var connectionAttemptCount: UInt { get }
+ var preferClosest: Bool { get }
func pick() throws -> SelectedRelays
}
extension RelayPicking {
- func findBestMatch(
- from candidates: [RelayWithLocation<REST.ServerRelay>]
- ) throws -> SelectedRelay {
+ func findBestMatch(from candidates: [RelayWithLocation<REST.ServerRelay>]) throws -> SelectedRelay {
let match = try RelaySelector.WireGuard.pickCandidate(
from: candidates,
relays: relays,
portConstraint: constraints.port,
- numberOfFailedAttempts: connectionAttemptCount
+ numberOfFailedAttempts: connectionAttemptCount,
+ preferClosest: preferClosest
)
return SelectedRelay(
@@ -40,6 +40,21 @@ struct SinglehopPicker: RelayPicking {
let daitaSettings: DAITASettings
let relays: REST.ServerRelaysResponse
let connectionAttemptCount: UInt
+ let preferClosest: Bool
+
+ init(
+ constraints: RelayConstraints,
+ daitaSettings: DAITASettings,
+ relays: REST.ServerRelaysResponse,
+ connectionAttemptCount: UInt,
+ preferClosest: Bool = false
+ ) {
+ self.constraints = constraints
+ self.daitaSettings = daitaSettings
+ self.relays = relays
+ self.connectionAttemptCount = connectionAttemptCount
+ self.preferClosest = preferClosest
+ }
func pick() throws -> SelectedRelays {
var exitCandidates = [RelayWithLocation<REST.ServerRelay>]()
@@ -62,7 +77,8 @@ struct SinglehopPicker: RelayPicking {
constraints: constraints,
daitaSettings: daitaSettings,
relays: relays,
- connectionAttemptCount: connectionAttemptCount
+ connectionAttemptCount: connectionAttemptCount,
+ preferClosest: true
).pick()
#endif
}
@@ -77,6 +93,21 @@ struct MultihopPicker: RelayPicking {
let daitaSettings: DAITASettings
let relays: REST.ServerRelaysResponse
let connectionAttemptCount: UInt
+ let preferClosest: Bool
+
+ init(
+ constraints: RelayConstraints,
+ daitaSettings: DAITASettings,
+ relays: REST.ServerRelaysResponse,
+ connectionAttemptCount: UInt,
+ preferClosest: Bool = false
+ ) {
+ self.constraints = constraints
+ self.daitaSettings = daitaSettings
+ self.relays = relays
+ self.connectionAttemptCount = connectionAttemptCount
+ self.preferClosest = preferClosest
+ }
func pick() throws -> SelectedRelays {
let entryCandidates = try RelaySelector.WireGuard.findCandidates(
diff --git a/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift b/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
index f529b9b924..bbc97c170f 100644
--- a/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
+++ b/ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
@@ -36,7 +36,7 @@ extension RelaySelector {
/// - filter: The user filtered criteria
/// - relays: The list of relays to randomly select from.
/// - Returns: A Shadowsocks relay or `nil` if no active relay were found.
- public static func closestRelay(
+ public static func closestBridge(
location: RelayConstraint<UserSelectedRelays>,
port: RelayConstraint<UInt16>,
filter: RelayConstraint<RelayFilter>,
@@ -49,43 +49,10 @@ extension RelaySelector {
daitaEnabled: false,
relays: mappedBridges
)) ?? []
- guard filteredRelays.isEmpty == false else { return relay(from: relaysResponse) }
-
- // Compute the midpoint location from all the filtered relays
- // Take *either* the first five relays, OR the relays below maximum bridge distance
- // sort all of them by Haversine distance from the computed midpoint location
- // then use the roulette selection to pick a bridge
-
- let midpointDistance = Midpoint.location(in: filteredRelays.map { $0.serverLocation.geoCoordinate })
- let maximumBridgeDistance = 1500.0
- let relaysWithDistance = filteredRelays.map {
- RelayWithDistance(
- relay: $0.relay,
- distance: Haversine.distance(
- midpointDistance.latitude,
- midpointDistance.longitude,
- $0.serverLocation.latitude,
- $0.serverLocation.longitude
- )
- )
- }.sorted {
- $0.distance < $1.distance
- }.filter {
- $0.distance <= maximumBridgeDistance
- }.prefix(5)
- var greatestDistance = 0.0
- relaysWithDistance.forEach {
- if $0.distance > greatestDistance {
- greatestDistance = $0.distance
- }
- }
-
- let randomRelay = rouletteSelection(relays: Array(relaysWithDistance), weightFunction: { relay in
- UInt64(1 + greatestDistance - relay.distance)
- })
+ guard filteredRelays.isEmpty == false else { return relay(from: relaysResponse) }
- return randomRelay?.relay ?? filteredRelays.randomElement()?.relay
+ return closestRelay(from: filteredRelays) ?? filteredRelays.randomElement()?.relay
}
}
}
diff --git a/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift b/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
index 4c8561f38b..4c517a3b48 100644
--- a/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
+++ b/ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
@@ -33,38 +33,69 @@ extension RelaySelector {
from relayWithLocations: [RelayWithLocation<REST.ServerRelay>],
relays: REST.ServerRelaysResponse,
portConstraint: RelayConstraint<UInt16>,
- numberOfFailedAttempts: UInt
+ numberOfFailedAttempts: UInt,
+ preferClosest: Bool = false
) throws -> RelaySelectorMatch {
- let port = applyPortConstraint(
- portConstraint,
- rawPortRanges: relays.wireguard.portRanges,
+ let port = try evaluatePort(
+ relays: relays,
+ portConstraint: portConstraint,
numberOfFailedAttempts: numberOfFailedAttempts
)
- guard let port else {
- throw NoRelaysSatisfyingConstraintsError(.invalidPort)
+ var relayWithLocation: RelayWithLocation<REST.ServerRelay>?
+ if preferClosest {
+ let relay = closestRelay(from: relayWithLocations)
+ relayWithLocation = relayWithLocations.first(where: { $0.relay == relay })
}
- guard let relayWithLocation = pickRandomRelayByWeight(relays: relayWithLocations) else {
+ guard
+ let relayWithLocation = relayWithLocation ?? pickRandomRelayByWeight(relays: relayWithLocations)
+ else {
throw NoRelaysSatisfyingConstraintsError(.relayConstraintNotMatching)
}
- let endpoint = MullvadEndpoint(
- ipv4Relay: IPv4Endpoint(
- ip: relayWithLocation.relay.ipv4AddrIn,
- port: port
- ),
- ipv6Relay: nil,
- ipv4Gateway: relays.wireguard.ipv4Gateway,
- ipv6Gateway: relays.wireguard.ipv6Gateway,
- publicKey: relayWithLocation.relay.publicKey
- )
+ return createMatch(for: relayWithLocation, port: port, relays: relays)
+ }
+ }
- return RelaySelectorMatch(
- endpoint: endpoint,
- relay: relayWithLocation.relay,
- location: relayWithLocation.serverLocation
- )
+ private static func evaluatePort(
+ relays: REST.ServerRelaysResponse,
+ portConstraint: RelayConstraint<UInt16>,
+ numberOfFailedAttempts: UInt
+ ) throws -> UInt16 {
+ let port = applyPortConstraint(
+ portConstraint,
+ rawPortRanges: relays.wireguard.portRanges,
+ numberOfFailedAttempts: numberOfFailedAttempts
+ )
+
+ guard let port else {
+ throw NoRelaysSatisfyingConstraintsError(.invalidPort)
}
+
+ return port
+ }
+
+ private static func createMatch(
+ for relayWithLocation: RelayWithLocation<REST.ServerRelay>,
+ port: UInt16,
+ relays: REST.ServerRelaysResponse
+ ) -> RelaySelectorMatch {
+ let endpoint = MullvadEndpoint(
+ ipv4Relay: IPv4Endpoint(
+ ip: relayWithLocation.relay.ipv4AddrIn,
+ port: port
+ ),
+ ipv6Relay: nil,
+ ipv4Gateway: relays.wireguard.ipv4Gateway,
+ ipv6Gateway: relays.wireguard.ipv6Gateway,
+ publicKey: relayWithLocation.relay.publicKey
+ )
+
+ return RelaySelectorMatch(
+ endpoint: endpoint,
+ relay: relayWithLocation.relay,
+ location: relayWithLocation.serverLocation
+ )
}
}
diff --git a/ios/MullvadREST/Relay/RelaySelector.swift b/ios/MullvadREST/Relay/RelaySelector.swift
index 98279daca5..41803505ea 100644
--- a/ios/MullvadREST/Relay/RelaySelector.swift
+++ b/ios/MullvadREST/Relay/RelaySelector.swift
@@ -32,6 +32,44 @@ public enum RelaySelector {
// MARK: - private
+ /// Computes the midpoint location from all the filtered relays.
+ ///
+ /// Take *either* the first five relays, OR the relays below maximum relay distance,
+ /// sort all of them by Haversine distance from the computed midpoint location,
+ /// then use the roulette selection to pick a bridge.
+ static func closestRelay<T: AnyRelay>(from relayWithLocations: [RelayWithLocation<T>]) -> T? {
+ let midpointDistance = Midpoint.location(in: relayWithLocations.map { $0.serverLocation.geoCoordinate })
+ let maximumRelayDistance = 1500.0
+ let relaysWithDistance = relayWithLocations.map {
+ RelayWithDistance(
+ relay: $0.relay,
+ distance: Haversine.distance(
+ midpointDistance.latitude,
+ midpointDistance.longitude,
+ $0.serverLocation.latitude,
+ $0.serverLocation.longitude
+ )
+ )
+ }.sorted {
+ $0.distance < $1.distance
+ }.filter {
+ $0.distance <= maximumRelayDistance
+ }.prefix(5)
+
+ var greatestDistance = 0.0
+ relaysWithDistance.forEach {
+ if $0.distance > greatestDistance {
+ greatestDistance = $0.distance
+ }
+ }
+
+ let closestRelay = rouletteSelection(relays: Array(relaysWithDistance), weightFunction: { relay in
+ UInt64(1 + greatestDistance - relay.distance)
+ })
+
+ return closestRelay?.relay
+ }
+
static func pickRandomRelayByWeight<T: AnyRelay>(relays: [RelayWithLocation<T>])
-> RelayWithLocation<T>? {
rouletteSelection(relays: relays, weightFunction: { relayWithLocation in relayWithLocation.relay.weight })
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
index c0d83bc701..f5dd4e433e 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksRelaySelector.swift
@@ -33,7 +33,7 @@ final public class ShadowsocksRelaySelector: ShadowsocksRelaySelectorProtocol {
case .off: settings.relayConstraints.exitLocations
}
- return RelaySelector.Shadowsocks.closestRelay(
+ return RelaySelector.Shadowsocks.closestBridge(
location: locationConstraint,
port: settings.relayConstraints.port,
filter: settings.relayConstraints.filter,
diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelayPickingTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelayPickingTests.swift
index 9229d4ab8f..29f227e589 100644
--- a/ios/MullvadVPNTests/MullvadREST/Relay/RelayPickingTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelayPickingTests.swift
@@ -74,4 +74,22 @@ class RelayPickingTests: XCTestCase {
XCTAssertEqual(error?.reason, .entryEqualsExit)
}
}
+
+ func testPickingClosestRelay() throws {
+ let constraints = RelayConstraints(
+ exitLocations: .only(UserSelectedRelays(locations: [.country("se")]))
+ )
+
+ let picker = SinglehopPicker(
+ constraints: constraints,
+ daitaSettings: DAITASettings(state: .off),
+ relays: sampleRelays,
+ connectionAttemptCount: 0,
+ preferClosest: true
+ )
+
+ let selectedRelays = try picker.pick()
+print(selectedRelays.exit.hostname)
+ XCTAssertTrue(["se6-wireguard", "se10-wireguard"].contains(selectedRelays.exit.hostname))
+ }
}
diff --git a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
index 5b2a775a4f..3db93f3d53 100644
--- a/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Relay/RelaySelectorTests.swift
@@ -136,12 +136,12 @@ class RelaySelectorTests: XCTestCase {
XCTAssertTrue(allPorts.contains(result.endpoint.ipv4Relay.port))
}
- func testClosestShadowsocksRelay() throws {
+ func testClosestRelay() throws {
let constraints = RelayConstraints(
exitLocations: .only(UserSelectedRelays(locations: [.city("se", "sto")]))
)
- let selectedRelay = RelaySelector.Shadowsocks.closestRelay(
+ let selectedRelay = RelaySelector.Shadowsocks.closestBridge(
location: constraints.exitLocations,
port: constraints.port,
filter: constraints.filter,
@@ -156,7 +156,7 @@ class RelaySelectorTests: XCTestCase {
exitLocations: .only(UserSelectedRelays(locations: [.country("INVALID COUNTRY")]))
)
- let selectedRelay = try XCTUnwrap(RelaySelector.Shadowsocks.closestRelay(
+ let selectedRelay = try XCTUnwrap(RelaySelector.Shadowsocks.closestBridge(
location: constraints.exitLocations,
port: constraints.port,
filter: constraints.filter,
diff --git a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
index c8cfc4308f..3654de5a6a 100644
--- a/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
+++ b/ios/MullvadVPNTests/MullvadREST/Shadowsocks/ShadowsocksLoaderTests.swift
@@ -82,7 +82,7 @@ class ShadowsocksLoaderTests: XCTestCase {
filter: RelayConstraint<RelayFilter>,
in: REST.ServerRelaysResponse
) -> REST.BridgeRelay? {
- RelaySelector.Shadowsocks.closestRelay(
+ RelaySelector.Shadowsocks.closestBridge(
location: location,
port: port,
filter: filter,