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

Apply the obfuscation port to the entry configuration only #7199

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
6 changes: 3 additions & 3 deletions ios/MullvadREST/ApiHandlers/ServerRelaysResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ extension REST {
public var daita: Bool?

public func override(ipv4AddrIn: IPv4Address?) -> Self {
return BridgeRelay(
BridgeRelay(
hostname: hostname,
active: active,
owned: owned,
Expand Down Expand Up @@ -65,7 +65,7 @@ extension REST {
public let shadowsocksExtraAddrIn: [String]?

public func override(ipv4AddrIn: IPv4Address?, ipv6AddrIn: IPv6Address?) -> Self {
return ServerRelay(
ServerRelay(
hostname: hostname,
active: active,
owned: owned,
Expand All @@ -82,7 +82,7 @@ extension REST {
}

public func override(daita: Bool) -> Self {
return ServerRelay(
ServerRelay(
hostname: hostname,
active: active,
owned: owned,
Expand Down
24 changes: 16 additions & 8 deletions ios/MullvadREST/Relay/MultihopDecisionFlow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ protocol MultihopDecisionFlow {
struct OneToOne: MultihopDecisionFlow {
let next: MultihopDecisionFlow?
let relayPicker: RelayPicking

init(next: (any MultihopDecisionFlow)?, relayPicker: RelayPicking) {
self.next = next
self.relayPicker = relayPicker
Expand All @@ -47,10 +48,11 @@ struct OneToOne: MultihopDecisionFlow {
throw NoRelaysSatisfyingConstraintsError(.entryEqualsExit)
}

let exitMatch = try relayPicker.findBestMatch(from: exitCandidates)
let exitMatch = try relayPicker.findBestMatch(from: exitCandidates, useObfuscatedPortIfAvailable: false)
let entryMatch = try relayPicker.findBestMatch(
from: entryCandidates,
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil,
useObfuscatedPortIfAvailable: true
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand Down Expand Up @@ -95,8 +97,12 @@ struct OneToMany: MultihopDecisionFlow {
.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates, daitaAutomaticRouting: true)
}

let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates)
let exitMatch = try multihopPicker.exclude(relay: entryMatch, from: exitCandidates)
let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates, useObfuscatedPortIfAvailable: true)
let exitMatch = try multihopPicker.exclude(
relay: entryMatch,
from: exitCandidates,
useObfuscatedPortIfAvailable: false
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
}
Expand Down Expand Up @@ -135,11 +141,12 @@ struct ManyToOne: MultihopDecisionFlow {
)
}

let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates, useObfuscatedPortIfAvailable: false)
let entryMatch = try multihopPicker.exclude(
relay: exitMatch,
from: entryCandidates,
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil,
useObfuscatedPortIfAvailable: true
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand Down Expand Up @@ -179,11 +186,12 @@ struct ManyToMany: MultihopDecisionFlow {
)
}

let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates, useObfuscatedPortIfAvailable: false)
let entryMatch = try multihopPicker.exclude(
relay: exitMatch,
from: entryCandidates,
closeTo: daitaAutomaticRouting ? exitMatch.location : nil
closeTo: daitaAutomaticRouting ? exitMatch.location : nil,
useObfuscatedPortIfAvailable: true
)

return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
Expand Down
7 changes: 4 additions & 3 deletions ios/MullvadREST/Relay/ObfuscatorPortSelector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import MullvadSettings
import MullvadTypes

struct ObfuscatorPortSelectorResult {
struct ObfuscatorPortSelection {
let relays: REST.ServerRelaysResponse
let port: RelayConstraint<UInt16>
let method: WireGuardObfuscationState
}

struct ObfuscatorPortSelector {
Expand All @@ -20,7 +21,7 @@ struct ObfuscatorPortSelector {
func obfuscate(
tunnelSettings: LatestTunnelSettings,
connectionAttemptCount: UInt
) throws -> ObfuscatorPortSelectorResult {
) throws -> ObfuscatorPortSelection {
var relays = relays
var port = tunnelSettings.relayConstraints.port
let obfuscationMethod = ObfuscationMethodSelector.obfuscationMethodBy(
Expand All @@ -44,7 +45,7 @@ struct ObfuscatorPortSelector {
break
}

return ObfuscatorPortSelectorResult(relays: relays, port: port)
return ObfuscatorPortSelection(relays: relays, port: port, method: obfuscationMethod)
}

private func obfuscateShadowsocksRelays(tunnelSettings: LatestTunnelSettings) -> REST.ServerRelaysResponse {
Expand Down
63 changes: 54 additions & 9 deletions ios/MullvadREST/Relay/RelayPicking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

import MullvadSettings
import MullvadTypes
import Network

protocol RelayPicking {
var obfuscation: ObfuscatorPortSelection { get }
var relays: REST.ServerRelaysResponse { get }
var constraints: RelayConstraints { get }
var connectionAttemptCount: UInt { get }
Expand All @@ -20,30 +22,64 @@ protocol RelayPicking {
extension RelayPicking {
func findBestMatch(
from candidates: [RelayWithLocation<REST.ServerRelay>],
closeTo location: Location? = nil
closeTo location: Location? = nil,
useObfuscatedPortIfAvailable: Bool
) throws -> SelectedRelay {
let match = try RelaySelector.WireGuard.pickCandidate(
var match = try RelaySelector.WireGuard.pickCandidate(
from: candidates,
relays: relays,
portConstraint: constraints.port,
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(relays.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)
}
}

struct SinglehopPicker: RelayPicking {
let relays: REST.ServerRelaysResponse
let obfuscation: ObfuscatorPortSelection
let constraints: RelayConstraints
let connectionAttemptCount: UInt
let daitaSettings: DAITASettings

var relays: REST.ServerRelaysResponse {
obfuscation.relays
}

func pick() throws -> SelectedRelays {
do {
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
Expand All @@ -53,14 +89,14 @@ struct SinglehopPicker: RelayPicking {
daitaEnabled: daitaSettings.daitaState.isEnabled
)

let match = try findBestMatch(from: exitCandidates)
let match = try findBestMatch(from: exitCandidates, useObfuscatedPortIfAvailable: true)
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
// If DAITA is on and Direct only is off, and no supported relays are found, we should try to find the nearest
// available relay that supports DAITA and use it as entry in a multihop selection.
if daitaSettings.isAutomaticRouting {
return try MultihopPicker(
relays: relays,
obfuscation: obfuscation,
constraints: constraints,
connectionAttemptCount: connectionAttemptCount,
daitaSettings: daitaSettings
Expand All @@ -73,11 +109,15 @@ struct SinglehopPicker: RelayPicking {
}

struct MultihopPicker: RelayPicking {
let relays: REST.ServerRelaysResponse
let obfuscation: ObfuscatorPortSelection
let constraints: RelayConstraints
let connectionAttemptCount: UInt
let daitaSettings: DAITASettings

var relays: REST.ServerRelaysResponse {
obfuscation.relays
}

func pick() throws -> SelectedRelays {
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
Expand Down Expand Up @@ -129,12 +169,17 @@ struct MultihopPicker: RelayPicking {
func exclude(
relay: SelectedRelay,
from candidates: [RelayWithLocation<REST.ServerRelay>],
closeTo location: Location? = nil
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)
return try findBestMatch(
from: filteredCandidates,
closeTo: location,
useObfuscatedPortIfAvailable: useObfuscatedPortIfAvailable
)
}
}
13 changes: 5 additions & 8 deletions ios/MullvadREST/Relay/RelaySelectorWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,25 @@ public final class RelaySelectorWrapper: RelaySelectorProtocol {
tunnelSettings: LatestTunnelSettings,
connectionAttemptCount: UInt
) throws -> SelectedRelays {
let obfuscationResult = try ObfuscatorPortSelector(
let obfuscation = try ObfuscatorPortSelector(
relays: try relayCache.read().relays
).obfuscate(
tunnelSettings: tunnelSettings,
connectionAttemptCount: connectionAttemptCount
)

var constraints = tunnelSettings.relayConstraints
constraints.port = obfuscationResult.port

return switch tunnelSettings.tunnelMultihopState {
case .off:
try SinglehopPicker(
relays: obfuscationResult.relays,
constraints: constraints,
obfuscation: obfuscation,
constraints: tunnelSettings.relayConstraints,
connectionAttemptCount: connectionAttemptCount,
daitaSettings: tunnelSettings.daita
).pick()
case .on:
try MultihopPicker(
relays: obfuscationResult.relays,
constraints: constraints,
obfuscation: obfuscation,
constraints: tunnelSettings.relayConstraints,
connectionAttemptCount: connectionAttemptCount,
daitaSettings: tunnelSettings.daita
).pick()
Expand Down
4 changes: 1 addition & 3 deletions ios/MullvadSettings/TunnelSettingsStrategy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ public struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol {
newSettings: LatestTunnelSettings
) -> Bool {
switch (oldSettings, newSettings) {
case let (old, new) where old.relayConstraints != new.relayConstraints,
let (old, new) where old.tunnelMultihopState != new.tunnelMultihopState,
let (old, new) where old.daita != new.daita:
case let (old, new) where old != new:
true
default:
false
Expand Down
10 changes: 10 additions & 0 deletions ios/MullvadTypes/MullvadEndpoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ public struct MullvadEndpoint: Equatable, Codable {
self.ipv6Gateway = ipv6Gateway
self.publicKey = publicKey
}

public func override(ipv4Relay: IPv4Endpoint) -> Self {
MullvadEndpoint(
ipv4Relay: ipv4Relay,
ipv6Relay: ipv6Relay,
ipv4Gateway: ipv4Gateway,
ipv6Gateway: ipv6Gateway,
publicKey: publicKey
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,16 @@ class MultihopDecisionFlowTests: XCTestCase {

extension MultihopDecisionFlowTests {
var picker: MultihopPicker {
let obfuscation = try? ObfuscatorPortSelector(relays: sampleRelays)
.obfuscate(tunnelSettings: LatestTunnelSettings(), connectionAttemptCount: 0)

let constraints = RelayConstraints(
entryLocations: .only(UserSelectedRelays(locations: [.city("se", "sto")])),
exitLocations: .only(UserSelectedRelays(locations: [.city("se", "sto")]))
)

return MultihopPicker(
relays: sampleRelays,
obfuscation: obfuscation.unsafelyUnwrapped,
constraints: constraints,
connectionAttemptCount: 0,
daitaSettings: DAITASettings(daitaState: .off)
Expand Down
Loading
Loading