Skip to content

Commit 840c1d8

Browse files
author
mojganii
committed
Apply multihop for normal connection
1 parent a16537e commit 840c1d8

27 files changed

+364
-127
lines changed

ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,13 @@ extension RelaySelectorStub {
4343
cityCode: "got",
4444
latitude: 0,
4545
longitude: 0
46-
), retryAttempts: 0
46+
)
4747
)
4848

4949
return SelectedRelays(
5050
entry: cityRelay,
51-
exit: cityRelay
51+
exit: cityRelay,
52+
retryAttempt: 0
5253
)
5354
}
5455
}

ios/MullvadREST/Relay/MultihopDecisionFlow.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct OneToOne: MultihopDecisionFlow {
3737

3838
let entryMatch = try relayPicker.findBestMatch(from: entryCandidates)
3939
let exitMatch = try relayPicker.findBestMatch(from: exitCandidates)
40-
return SelectedRelays(entry: entryMatch, exit: exitMatch)
40+
return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
4141
}
4242

4343
func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
@@ -70,11 +70,11 @@ struct OneToMany: MultihopDecisionFlow {
7070
case let (1, count) where count > 1:
7171
let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates)
7272
let exitMatch = try multihopPicker.exclude(relay: entryMatch, from: exitCandidates)
73-
return SelectedRelays(entry: entryMatch, exit: exitMatch)
73+
return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
7474
default:
7575
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
7676
let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
77-
return SelectedRelays(entry: entryMatch, exit: exitMatch)
77+
return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
7878
}
7979
}
8080

@@ -107,7 +107,7 @@ struct ManyToMany: MultihopDecisionFlow {
107107

108108
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
109109
let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
110-
return SelectedRelays(entry: entryMatch, exit: exitMatch)
110+
return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
111111
}
112112

113113
func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {

ios/MullvadREST/Relay/RelayPicking.swift

+2-3
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ extension RelayPicking {
3030
return SelectedRelay(
3131
endpoint: match.endpoint,
3232
hostname: match.relay.hostname,
33-
location: match.location,
34-
retryAttempts: connectionAttemptCount
33+
location: match.location
3534
)
3635
}
3736
}
@@ -50,7 +49,7 @@ struct SinglehopPicker: RelayPicking {
5049

5150
let match = try findBestMatch(from: candidates)
5251

53-
return SelectedRelays(entry: nil, exit: match)
52+
return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
5453
}
5554
}
5655

ios/MullvadREST/Relay/RelaySelectorProtocol.swift

+4-6
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,11 @@ public struct SelectedRelay: Equatable, Codable {
2525
/// Relay geo location.
2626
public let location: Location
2727

28-
/// Number of retried attempts to connect to a relay.
29-
public let retryAttempts: UInt
30-
3128
/// Designated initializer.
32-
public init(endpoint: MullvadEndpoint, hostname: String, location: Location, retryAttempts: UInt) {
29+
public init(endpoint: MullvadEndpoint, hostname: String, location: Location) {
3330
self.endpoint = endpoint
3431
self.hostname = hostname
3532
self.location = location
36-
self.retryAttempts = retryAttempts
3733
}
3834
}
3935

@@ -46,10 +42,12 @@ extension SelectedRelay: CustomDebugStringConvertible {
4642
public struct SelectedRelays: Equatable, Codable {
4743
public let entry: SelectedRelay?
4844
public let exit: SelectedRelay
45+
public let retryAttempt: UInt
4946

50-
public init(entry: SelectedRelay?, exit: SelectedRelay) {
47+
public init(entry: SelectedRelay?, exit: SelectedRelay, retryAttempt: UInt) {
5148
self.entry = entry
5249
self.exit = exit
50+
self.retryAttempt = retryAttempt
5351
}
5452
}
5553

ios/MullvadVPN.xcodeproj/project.pbxproj

+22-12
Large diffs are not rendered by default.

ios/MullvadVPN/AppDelegate.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -79,18 +79,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
7979
ipOverrideRepository: ipOverrideRepository
8080
)
8181

82+
let constraintsUpdater = RelayConstraintsUpdater()
83+
let multihopListener = MultihopStateListener()
84+
let multihopUpdater = MultihopUpdater(listener: multihopListener)
85+
8286
relayCacheTracker = RelayCacheTracker(
8387
relayCache: ipOverrideWrapper,
8488
application: application,
8589
apiProxy: apiProxy
8690
)
8791

8892
addressCacheTracker = AddressCacheTracker(application: application, apiProxy: apiProxy, store: addressCache)
89-
tunnelStore = TunnelStore(application: application)
9093

91-
let constraintsUpdater = RelayConstraintsUpdater()
92-
let multihopListener = MultihopStateListener()
93-
let multihopUpdater = MultihopUpdater(listener: multihopListener)
94+
tunnelStore = TunnelStore(application: application)
9495

9596
let relaySelector = RelaySelectorWrapper(
9697
relayCache: ipOverrideWrapper,

ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
177177
networkReachability: .reachable,
178178
connectionAttemptCount: 0,
179179
transportLayer: .udp,
180-
remotePort: selectedRelays.exit.endpoint.ipv4Relay.port, // TODO: Multihop
180+
remotePort: selectedRelays.entry?.endpoint.ipv4Relay.port ?? selectedRelays.exit.endpoint.ipv4Relay
181+
.port,
181182
isPostQuantum: settings.tunnelQuantumResistance.isEnabled
182183
)
183184
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// Tunnel+Settings.swift
3+
// MullvadVPN
4+
//
5+
// Created by Mojgan on 2024-06-19.
6+
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
7+
//
8+
9+
import MullvadLogging
10+
import MullvadSettings
11+
12+
protocol TunnelSettingsStrategyProtocol {
13+
func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool
14+
}
15+
16+
struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol {
17+
func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool {
18+
switch (oldSettings, newSettings) {
19+
case let (old, new) where old.relayConstraints != new.relayConstraints,
20+
let (old, new) where old.tunnelMultihopState != new.tunnelMultihopState:
21+
true
22+
default:
23+
false
24+
}
25+
}
26+
}

ios/MullvadVPN/TunnelManager/TunnelManager.swift

+20-20
Original file line numberDiff line numberDiff line change
@@ -939,16 +939,16 @@ final class TunnelManager: StorePaymentObserver {
939939
let operation = AsyncBlockOperation(dispatchQueue: internalQueue) {
940940
let currentSettings = self._tunnelSettings
941941
var updatedSettings = self._tunnelSettings
942+
let settingsStrategy = TunnelSettingsStrategy()
942943

943944
modificationBlock(&updatedSettings)
944945

945-
// Select new relay only when relay constraints change.
946-
let currentConstraints = currentSettings.relayConstraints
947-
let updatedConstraints = updatedSettings.relayConstraints
948-
let selectNewRelay = currentConstraints != updatedConstraints
949-
950946
self.setSettings(updatedSettings, persist: true)
951-
self.reconnectTunnel(selectNewRelay: selectNewRelay, completionHandler: nil)
947+
self.reconnectTunnel(
948+
selectNewRelay: settingsStrategy
949+
.shouldReconnectToNewRelay(oldSettings: currentSettings, newSettings: updatedSettings),
950+
completionHandler: nil
951+
)
952952
}
953953

954954
operation.completionBlock = {
@@ -1146,27 +1146,27 @@ extension TunnelManager {
11461146

11471147
```
11481148
func delay(seconds: UInt) async throws {
1149-
try await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
1149+
try await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
11501150
}
11511151

11521152
Task {
1153-
print("Wait 5 seconds")
1154-
try await delay(seconds: 5)
1153+
print("Wait 5 seconds")
1154+
try await delay(seconds: 5)
11551155

1156-
print("Simulate active account")
1157-
self.tunnelManager.simulateAccountExpiration(option: .active)
1158-
try await delay(seconds: 5)
1156+
print("Simulate active account")
1157+
self.tunnelManager.simulateAccountExpiration(option: .active)
1158+
try await delay(seconds: 5)
11591159

1160-
print("Simulate close to expiry")
1161-
self.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
1162-
try await delay(seconds: 10)
1160+
print("Simulate close to expiry")
1161+
self.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
1162+
try await delay(seconds: 10)
11631163

1164-
print("Simulate expired account")
1165-
self.tunnelManager.simulateAccountExpiration(option: .expired)
1166-
try await delay(seconds: 5)
1164+
print("Simulate expired account")
1165+
self.tunnelManager.simulateAccountExpiration(option: .expired)
1166+
try await delay(seconds: 5)
11671167

1168-
print("Simulate active account")
1169-
self.tunnelManager.simulateAccountExpiration(option: .active)
1168+
print("Simulate active account")
1169+
self.tunnelManager.simulateAccountExpiration(option: .active)
11701170
}
11711171
```
11721172

ios/MullvadVPN/TunnelManager/TunnelState+UI.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ extension TunnelState {
187187
value: "Quantum secure connection. Connected to %@, %@",
188188
comment: ""
189189
),
190-
tunnelInfo.exit.location.city, // TODO: Multihop
191-
tunnelInfo.exit.location.country // TODO: Multihop
190+
tunnelInfo.exit.location.city,
191+
tunnelInfo.exit.location.country
192192
)
193193
} else {
194194
String(
@@ -198,8 +198,8 @@ extension TunnelState {
198198
value: "Secure connection. Connected to %@, %@",
199199
comment: ""
200200
),
201-
tunnelInfo.exit.location.city, // TODO: Multihop
202-
tunnelInfo.exit.location.country // TODO: Multihop
201+
tunnelInfo.exit.location.city,
202+
tunnelInfo.exit.location.country
203203
)
204204
}
205205

@@ -219,8 +219,8 @@ extension TunnelState {
219219
value: "Reconnecting to %@, %@",
220220
comment: ""
221221
),
222-
tunnelInfo.exit.location.city, // TODO: Multihop
223-
tunnelInfo.exit.location.country // TODO: Multihop
222+
tunnelInfo.exit.location.city,
223+
tunnelInfo.exit.location.country
224224
)
225225

226226
case .waitingForConnectivity(.noConnection), .error:

ios/MullvadVPN/TunnelManager/TunnelState.swift

+19-4
Original file line numberDiff line numberDiff line change
@@ -83,24 +83,39 @@ enum TunnelState: Equatable, CustomStringConvertible {
8383
"pending reconnect after disconnect"
8484
case let .connecting(tunnelRelays, isPostQuantum):
8585
if let tunnelRelays {
86-
"connecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
86+
"""
87+
connecting \(isPostQuantum ? "(PQ) " : "")\
88+
to \(tunnelRelays.exit.hostname)\
89+
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
90+
"""
8791
} else {
8892
"connecting\(isPostQuantum ? " (PQ)" : ""), fetching relay"
8993
}
9094
case let .connected(tunnelRelays, isPostQuantum):
91-
"connected \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
95+
"""
96+
connected \(isPostQuantum ? "(PQ) " : "")\
97+
to \(tunnelRelays.exit.hostname)\
98+
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
99+
"""
92100
case let .disconnecting(actionAfterDisconnect):
93101
"disconnecting and then \(actionAfterDisconnect)"
94102
case .disconnected:
95103
"disconnected"
96104
case let .reconnecting(tunnelRelays, isPostQuantum):
97-
"reconnecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
105+
"""
106+
reconnecting \(isPostQuantum ? "(PQ) " : "")\
107+
to \(tunnelRelays.exit.hostname)\
108+
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
109+
"""
98110
case .waitingForConnectivity:
99111
"waiting for connectivity"
100112
case let .error(blockedStateReason):
101113
"error state: \(blockedStateReason)"
102114
case let .negotiatingPostQuantumKey(tunnelRelays, _):
103-
"negotiating key with \(tunnelRelays.exit.hostname)" // TODO: Multihop
115+
"""
116+
negotiating key with exit relay: \(tunnelRelays.exit.hostname)\
117+
\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
118+
"""
104119
}
105120
}
106121

ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift

+5-4
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ final class TunnelControlView: UIView {
139139
connectButtonBlurView.isEnabled = model.enableButtons
140140
cityLabel.attributedText = attributedStringForLocation(string: model.city)
141141
countryLabel.attributedText = attributedStringForLocation(string: model.country)
142-
connectionPanel.connectedRelayName = model.connectedRelayName
142+
connectionPanel.connectedRelayName = model.connectedRelaysName
143143
connectionPanel.dataSource = model.connectionPanel
144144

145145
updateSecureLabel(tunnelState: tunnelState)
@@ -227,14 +227,15 @@ final class TunnelControlView: UIView {
227227
private func updateTunnelRelays(tunnelRelays: SelectedRelays?) {
228228
if let tunnelRelays {
229229
cityLabel.attributedText = attributedStringForLocation(
230-
string: tunnelRelays.exit.location.city // TODO: Multihop
230+
string: tunnelRelays.exit.location.city
231231
)
232232
countryLabel.attributedText = attributedStringForLocation(
233-
string: tunnelRelays.exit.location.country // TODO: Multihop
233+
string: tunnelRelays.exit.location.country
234234
)
235235

236236
connectionPanel.isHidden = false
237-
connectionPanel.connectedRelayName = tunnelRelays.exit.hostname // TODO: Multihop
237+
connectionPanel.connectedRelayName = tunnelRelays.exit
238+
.hostname + "\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")"
238239
} else {
239240
countryLabel.attributedText = attributedStringForLocation(string: " ")
240241
cityLabel.attributedText = attributedStringForLocation(string: " ")

ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct TunnelControlViewModel {
1414
let enableButtons: Bool
1515
let city: String
1616
let country: String
17-
let connectedRelayName: String
17+
let connectedRelaysName: String
1818
let outgoingConnectionInfo: OutgoingConnectionInfo?
1919

2020
var connectionPanel: ConnectionPanelData? {
@@ -29,7 +29,7 @@ struct TunnelControlViewModel {
2929
}
3030

3131
return ConnectionPanelData(
32-
inAddress: "\(tunnelRelays.exit.endpoint.ipv4Relay.ip)\(portAndTransport)", // TODO: Multihop
32+
inAddress: "\(tunnelRelays.entry?.endpoint.ipv4Relay.ip ?? tunnelRelays.exit.endpoint.ipv4Relay.ip)\(portAndTransport)",
3333
outAddress: outgoingConnectionInfo?.outAddress
3434
)
3535
}
@@ -41,7 +41,7 @@ struct TunnelControlViewModel {
4141
enableButtons: true,
4242
city: "",
4343
country: "",
44-
connectedRelayName: "",
44+
connectedRelaysName: "",
4545
outgoingConnectionInfo: nil
4646
)
4747
}
@@ -53,7 +53,7 @@ struct TunnelControlViewModel {
5353
enableButtons: enableButtons,
5454
city: city,
5555
country: country,
56-
connectedRelayName: connectedRelayName,
56+
connectedRelaysName: connectedRelaysName,
5757
outgoingConnectionInfo: nil
5858
)
5959
}
@@ -65,7 +65,7 @@ struct TunnelControlViewModel {
6565
enableButtons: enableButtons,
6666
city: city,
6767
country: country,
68-
connectedRelayName: connectedRelayName,
68+
connectedRelaysName: connectedRelaysName,
6969
outgoingConnectionInfo: outgoingConnectionInfo
7070
)
7171
}

ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ class TunnelViewController: UIViewController, RootContainment {
150150
case let .connecting(tunnelRelays, _):
151151
mapViewController.removeLocationMarker()
152152
contentView.setAnimatingActivity(true)
153-
mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated) // TODO: Multihop
153+
mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated)
154154

155155
case let .reconnecting(tunnelRelays, _), let .negotiatingPostQuantumKey(tunnelRelays, _):
156156
mapViewController.removeLocationMarker()
157157
contentView.setAnimatingActivity(true)
158-
mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated) // TODO: Multihop
158+
mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated)
159159

160160
case let .connected(tunnelRelays, _):
161-
let center = tunnelRelays.exit.location.geoCoordinate // TODO: Multihop
161+
let center = tunnelRelays.exit.location.geoCoordinate
162162
mapViewController.setCenter(center, animated: animated) {
163163
self.contentView.setAnimatingActivity(false)
164164

0 commit comments

Comments
 (0)