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

Relay selector doesn't force a blocked state with daita and obfuscation on #7324

Merged
merged 1 commit into from
Dec 13, 2024
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
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
Loading