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
|
//
// RelaySelector.swift
// PacketTunnel
//
// Created by pronebird on 11/06/2019.
// Copyright © 2019 Mullvad VPN AB. All rights reserved.
//
import Foundation
import Network
struct RelaySelectorResult: Codable {
var endpoint: MullvadEndpoint
var relay: REST.ServerRelay
var location: Location
}
private struct RelayWithLocation {
var relay: REST.ServerRelay
var location: Location
}
extension RelaySelectorResult {
var tunnelConnectionInfo: TunnelConnectionInfo {
return TunnelConnectionInfo(
ipv4Relay: self.endpoint.ipv4Relay,
ipv6Relay: self.endpoint.ipv6Relay,
hostname: self.relay.hostname,
location: self.location
)
}
}
enum RelaySelector {}
extension RelaySelector {
static func evaluate(relays: REST.ServerRelaysResponse, constraints: RelayConstraints) -> RelaySelectorResult? {
let filteredRelays = Self.applyConstraints(constraints, relays: Self.parseRelaysResponse(relays))
let totalWeight = filteredRelays.reduce(0) { $0 + $1.relay.weight }
guard totalWeight > 0 else { return nil }
guard var i = (0...totalWeight).randomElement() else { return nil }
let relayWithLocation = filteredRelays.first { (relayWithLocation) -> Bool in
i -= relayWithLocation.relay.weight
return i <= 0
}.unsafelyUnwrapped
guard let port = relays.wireguard.portRanges.randomElement()?.randomElement() else {
return nil
}
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 RelaySelectorResult(
endpoint: endpoint,
relay: relayWithLocation.relay,
location: relayWithLocation.location
)
}
/// Produce a list of `RelayWithLocation` items satisfying the given constraints
private static func applyConstraints(_ constraints: RelayConstraints, relays: [RelayWithLocation]) -> [RelayWithLocation] {
return relays.filter { (relayWithLocation) -> Bool in
switch constraints.location {
case .any:
return true
case .only(let relayConstraint):
switch relayConstraint {
case .country(let countryCode):
return relayWithLocation.location.countryCode == countryCode &&
relayWithLocation.relay.includeInCountry
case .city(let countryCode, let cityCode):
return relayWithLocation.location.countryCode == countryCode &&
relayWithLocation.location.cityCode == cityCode
case .hostname(let countryCode, let cityCode, let hostname):
return relayWithLocation.location.countryCode == countryCode &&
relayWithLocation.location.cityCode == cityCode &&
relayWithLocation.relay.hostname == hostname
}
}
}.filter { (relayWithLocation) -> Bool in
return relayWithLocation.relay.active
}
}
private static func parseRelaysResponse(_ response: REST.ServerRelaysResponse) -> [RelayWithLocation] {
return response.wireguard.relays.compactMap { (serverRelay) -> RelayWithLocation? in
guard let serverLocation = response.locations[serverRelay.location] else { return nil }
let locationComponents = serverRelay.location.split(separator: "-")
guard locationComponents.count > 1 else { return nil }
let location = Location(
country: serverLocation.country,
countryCode: String(locationComponents[0]),
city: serverLocation.city,
cityCode: String(locationComponents[1]),
latitude: serverLocation.latitude,
longitude: serverLocation.longitude
)
return RelayWithLocation(relay: serverRelay, location: location)
}
}
}
|