Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade settings to associate with multi-hop #6257

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
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."
}
}
91 changes: 91 additions & 0 deletions ios/MullvadREST/Relay/RelaySelector+Shadowsocks.swift
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
}
}
}
78 changes: 78 additions & 0 deletions ios/MullvadREST/Relay/RelaySelector+Wireguard.swift
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
)
}
}
}
Loading
Loading