-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathRelayPicking.swift
128 lines (112 loc) · 4.31 KB
/
RelayPicking.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//
// RelaySelectorPicker.swift
// MullvadREST
//
// Created by Jon Petersson on 2024-06-05.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//
import MullvadSettings
import MullvadTypes
protocol RelayPicking {
var relays: REST.ServerRelaysResponse { get }
var constraints: RelayConstraints { get }
var connectionAttemptCount: UInt { get }
func pick() throws -> SelectedRelays
}
extension RelayPicking {
func findBestMatch(
from candidates: [RelayWithLocation<REST.ServerRelay>]
) throws -> SelectedRelay {
let match = try RelaySelector.WireGuard.pickCandidate(
from: candidates,
relays: relays,
portConstraint: constraints.port,
numberOfFailedAttempts: connectionAttemptCount
)
return SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
location: match.location
)
}
}
struct SinglehopPicker: RelayPicking {
let constraints: RelayConstraints
let daitaSettings: DAITASettings
let relays: REST.ServerRelaysResponse
let connectionAttemptCount: UInt
func pick() throws -> SelectedRelays {
var exitCandidates = [RelayWithLocation<REST.ServerRelay>]()
do {
exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.state.isEnabled
)
} catch let error as NoRelaysSatisfyingConstraintsError where error.reason == .noDaitaRelaysFound {
#if DEBUG
// If DAITA is enabled 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.
var constraints = constraints
constraints.entryLocations = .any
return try MultihopPicker(
constraints: constraints,
daitaSettings: daitaSettings,
relays: relays,
connectionAttemptCount: connectionAttemptCount
).pick()
#endif
}
let match = try findBestMatch(from: exitCandidates)
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
}
}
struct MultihopPicker: RelayPicking {
let constraints: RelayConstraints
let daitaSettings: DAITASettings
let relays: REST.ServerRelaysResponse
let connectionAttemptCount: UInt
func pick() throws -> SelectedRelays {
let entryCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.entryLocations,
in: relays,
filterConstraint: constraints.filter,
daitaEnabled: daitaSettings.state.isEnabled
)
let exitCandidates = try RelaySelector.WireGuard.findCandidates(
by: constraints.exitLocations,
in: relays,
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. Either entry or exit constraint matches only a single relay and the other multiple relays. The single relays
is selected and excluded from the list of multiple relays.
3. 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: ManyToMany(
next: nil,
relayPicker: self
),
relayPicker: self
),
relayPicker: self
)
return try decisionFlow.pick(entryCandidates: entryCandidates, exitCandidates: exitCandidates)
}
func exclude(
relay: SelectedRelay,
from candidates: [RelayWithLocation<REST.ServerRelay>]
) throws -> SelectedRelay {
let filteredCandidates = candidates.filter { relayWithLocation in
relayWithLocation.relay.hostname != relay.hostname
}
return try findBestMatch(from: filteredCandidates)
}
}