Skip to content

Commit

Permalink
Fix relay selector to force a blocked state with daita and obfuscatio…
Browse files Browse the repository at this point in the history
…n on
  • Loading branch information
rablador committed Dec 11, 2024
1 parent 8024c16 commit dff2ead
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 178 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public enum NoRelaysSatisfyingConstraintsReason {
case multihopInvalidFlow
case noActiveRelaysFound
case noDaitaRelaysFound
case noObfuscatedRelaysFound
case relayConstraintNotMatching
}

Expand All @@ -35,6 +36,8 @@ public struct NoRelaysSatisfyingConstraintsError: LocalizedError {
"No active relays found"
case .noDaitaRelaysFound:
"No DAITA relays found"
case .noObfuscatedRelaysFound:
"No obfuscated relays found"
case .relayConstraintNotMatching:
"Invalid constraint created to pick a relay"
}
Expand Down
2 changes: 2 additions & 0 deletions ios/MullvadREST/Relay/ObfuscatorPortSelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import MullvadTypes
struct ObfuscatorPortSelection {
let entryRelays: REST.ServerRelaysResponse
let exitRelays: REST.ServerRelaysResponse
let unfilteredRelays: REST.ServerRelaysResponse
let port: RelayConstraint<UInt16>
let method: WireGuardObfuscationState

Expand Down Expand Up @@ -61,6 +62,7 @@ struct ObfuscatorPortSelector {
return ObfuscatorPortSelection(
entryRelays: entryRelays,
exitRelays: exitRelays,
unfilteredRelays: relays,
port: port,
method: obfuscationMethod
)
Expand Down
176 changes: 0 additions & 176 deletions ios/MullvadREST/Relay/RelayPicking.swift

This file was deleted.

82 changes: 82 additions & 0 deletions ios/MullvadREST/Relay/RelayPicking/MultihopPicker.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// MultihopPicker.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-12-11.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import MullvadTypes

struct MultihopPicker: RelayPicking {
let obfuscation: ObfuscatorPortSelection
let constraints: RelayConstraints
let connectionAttemptCount: UInt
let daitaSettings: DAITASettings

func pick() throws -> SelectedRelays {
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: obfuscation.exitRelays,
filterConstraint: constraints.filter,
daitaEnabled: false
)

/*
Relay selection is prioritised in the following order:
1. Both entry and exit constraints match only a single relay. Both relays are selected.
2. Entry constraint matches only a single relay and the other multiple relays. The single relay
is selected and excluded from the list of multiple relays.
3. Exit constraint matches multiple relays and the other a single relay. The single relay
is selected and excluded from the list of multiple relays.
4. Both entry and exit constraints match multiple relays. Exit relay is picked first and then
excluded from the list of entry relays.
*/
let decisionFlow = OneToOne(
next: OneToMany(
next: ManyToOne(
next: ManyToMany(
next: nil,
relayPicker: self
),
relayPicker: self
),
relayPicker: self
),
relayPicker: self
)

do {
let entryCandidates = try RelaySelector.WireGuard.findCandidates(
by: daitaSettings.isAutomaticRouting ? .any : constraints.entryLocations,
in: obfuscation.entryRelays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.daitaState.isEnabled
)

return try decisionFlow.pick(
entryCandidates: entryCandidates,
exitCandidates: exitCandidates,
daitaAutomaticRouting: daitaSettings.isAutomaticRouting
)
}
}

func exclude(
relay: SelectedRelay,
from candidates: [RelayWithLocation<REST.ServerRelay>],
closeTo location: Location? = nil,
useObfuscatedPortIfAvailable: Bool
) throws -> SelectedRelay {
let filteredCandidates = candidates.filter { relayWithLocation in
relayWithLocation.relay.hostname != relay.hostname
}

return try findBestMatch(
from: filteredCandidates,
closeTo: location,
useObfuscatedPortIfAvailable: useObfuscatedPortIfAvailable
)
}
}
70 changes: 70 additions & 0 deletions ios/MullvadREST/Relay/RelayPicking/RelayPicking.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// RelaySelectorPicker.swift
// MullvadREST
//
// Created by Jon Petersson on 2024-06-05.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import MullvadSettings
import MullvadTypes
import Network

protocol RelayPicking {
var obfuscation: ObfuscatorPortSelection { get }
var constraints: RelayConstraints { get }
var connectionAttemptCount: UInt { get }
var daitaSettings: DAITASettings { get }
func pick() throws -> SelectedRelays
}

extension RelayPicking {
func findBestMatch(
from candidates: [RelayWithLocation<REST.ServerRelay>],
closeTo location: Location? = nil,
useObfuscatedPortIfAvailable: Bool
) throws -> SelectedRelay {
var match = try RelaySelector.WireGuard.pickCandidate(
from: candidates,
wireguard: obfuscation.wireguard,
portConstraint: useObfuscatedPortIfAvailable ? obfuscation.port : constraints.port,
numberOfFailedAttempts: connectionAttemptCount,
closeTo: location
)

if useObfuscatedPortIfAvailable && obfuscation.method == .shadowsocks {
match = applyShadowsocksIpAddress(in: match)
}

return SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
location: match.location
)
}

private func applyShadowsocksIpAddress(in match: RelaySelectorMatch) -> RelaySelectorMatch {
let port = match.endpoint.ipv4Relay.port
let portRanges = RelaySelector.parseRawPortRanges(obfuscation.wireguard.shadowsocksPortRanges)
let portIsWithinRange = portRanges.contains(where: { $0.contains(port) })

var endpoint = match.endpoint

// If the currently selected obfuscation port is not within the allowed range (as specified
// in the relay list), we should use one of the extra Shadowsocks IP addresses instead of
// the default one.
if !portIsWithinRange {
var ipv4Address = match.endpoint.ipv4Relay.ip
if let shadowsocksAddress = match.relay.shadowsocksExtraAddrIn?.randomElement() {
ipv4Address = IPv4Address(shadowsocksAddress) ?? ipv4Address
}

endpoint = match.endpoint.override(ipv4Relay: IPv4Endpoint(
ip: ipv4Address,
port: port
))
}

return RelaySelectorMatch(endpoint: endpoint, relay: match.relay, location: match.location)
}
}
Loading

0 comments on commit dff2ead

Please sign in to comment.