Skip to content

Commit

Permalink
Upgrade settings schema to associate with multihop
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed May 30, 2024
1 parent 71d3cfa commit 79c8762
Show file tree
Hide file tree
Showing 41 changed files with 1,115 additions and 380 deletions.
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

0 comments on commit 79c8762

Please sign in to comment.