-
Notifications
You must be signed in to change notification settings - Fork 354
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Upgrade settings to associate with multi-hop
- Loading branch information
Showing
41 changed files
with
1,112 additions
and
380 deletions.
There are no files selected for viewing
15 changes: 15 additions & 0 deletions
15
ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// NoRelaysSatisfyingConstraintsError.swift | ||
// MullvadREST | ||
// | ||
// Created by Mojgan on 2024-04-26. | ||
// Copyright © 2024 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct NoRelaysSatisfyingConstraintsError: LocalizedError { | ||
public var errorDescription: String? { | ||
"No relays satisfying constraints." | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// | ||
// RelaySelector+Shadowsocks.swift | ||
// MullvadREST | ||
// | ||
// Created by Mojgan on 2024-05-17. | ||
// Copyright © 2024 Mullvad VPN AB. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
import MullvadTypes | ||
|
||
extension RelaySelector { | ||
public enum Shadowsocks { | ||
/** | ||
Returns random shadowsocks TCP bridge, otherwise `nil` if there are no shadowdsocks bridges. | ||
*/ | ||
public static func tcpBridge(from relays: REST.ServerRelaysResponse) -> REST.ServerShadowsocks? { | ||
relays.bridge.shadowsocks.filter { $0.protocol == "tcp" }.randomElement() | ||
} | ||
|
||
/// Return a random Shadowsocks bridge relay, or `nil` if no relay were found. | ||
/// | ||
/// Non `active` relays are filtered out. | ||
/// - Parameter relays: The list of relays to randomly select from. | ||
/// - Returns: A Shadowsocks relay or `nil` if no active relay were found. | ||
public static func relay(from relaysResponse: REST.ServerRelaysResponse) -> REST.BridgeRelay? { | ||
relaysResponse.bridge.relays.filter { $0.active }.randomElement() | ||
} | ||
|
||
/// Returns the closest Shadowsocks relay using the given `location`, or a random relay if `constraints` were | ||
/// unsatisfiable. | ||
/// | ||
/// - Parameters: | ||
/// - location: The user selected `location` | ||
/// - port: The user selected port | ||
/// - 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( | ||
location: RelayConstraint<UserSelectedRelays>, | ||
port: RelayConstraint<UInt16>, | ||
filter: RelayConstraint<RelayFilter>, | ||
in relaysResponse: REST.ServerRelaysResponse | ||
) -> REST.BridgeRelay? { | ||
let mappedBridges = mapRelays(relays: relaysResponse.bridge.relays, locations: relaysResponse.locations) | ||
let filteredRelays = applyConstraints( | ||
location, | ||
portConstraint: port, | ||
filterConstraint: filter, | ||
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) | ||
}) | ||
|
||
return randomRelay?.relay ?? filteredRelays.randomElement()?.relay | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// | ||
// RelaySelector+Wireguard.swift | ||
// MullvadREST | ||
// | ||
// Created by Mojgan on 2024-05-17. | ||
// 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 | ||
) | ||
|
||
return exitCandidates | ||
} | ||
|
||
// MARK: - private functions | ||
|
||
private static func findBestMatch( | ||
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 { | ||
throw NoRelaysSatisfyingConstraintsError() | ||
} | ||
|
||
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 | ||
) | ||
} | ||
} | ||
} |
Oops, something went wrong.