summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift5
-rw-r--r--ios/MullvadREST/Relay/MultihopDecisionFlow.swift8
-rw-r--r--ios/MullvadREST/Relay/RelayPicking.swift5
-rw-r--r--ios/MullvadREST/Relay/RelaySelectorProtocol.swift10
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj34
-rw-r--r--ios/MullvadVPN/AppDelegate.swift9
-rw-r--r--ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift3
-rw-r--r--ios/MullvadVPN/TunnelManager/Tunnel+Settings.swift26
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelManager.swift40
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState+UI.swift12
-rw-r--r--ios/MullvadVPN/TunnelManager/TunnelState.swift23
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift9
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift10
-rw-r--r--ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift6
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift2
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelSettingsStrategyTests.swift101
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift36
-rw-r--r--ios/PacketTunnel/WireGuardAdapter/WireGuardAdapter+Async.swift12
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift4
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActor.swift101
-rw-r--r--ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift2
-rw-r--r--ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift6
-rw-r--r--ios/PacketTunnelCore/Actor/StartOptions.swift3
-rw-r--r--ios/PacketTunnelCore/Actor/State+Extensions.swift11
-rw-r--r--ios/PacketTunnelCore/Actor/State.swift4
-rw-r--r--ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift6
-rw-r--r--ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift5
27 files changed, 366 insertions, 127 deletions
diff --git a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
index f2f8952df4..f404aff1e2 100644
--- a/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
+++ b/ios/MullvadMockData/MullvadREST/RelaySelectorStub.swift
@@ -43,12 +43,13 @@ extension RelaySelectorStub {
cityCode: "got",
latitude: 0,
longitude: 0
- ), retryAttempts: 0
+ )
)
return SelectedRelays(
entry: cityRelay,
- exit: cityRelay
+ exit: cityRelay,
+ retryAttempt: 0
)
}
}
diff --git a/ios/MullvadREST/Relay/MultihopDecisionFlow.swift b/ios/MullvadREST/Relay/MultihopDecisionFlow.swift
index fa8431ed99..53d8d1a8bc 100644
--- a/ios/MullvadREST/Relay/MultihopDecisionFlow.swift
+++ b/ios/MullvadREST/Relay/MultihopDecisionFlow.swift
@@ -37,7 +37,7 @@ struct OneToOne: MultihopDecisionFlow {
let entryMatch = try relayPicker.findBestMatch(from: entryCandidates)
let exitMatch = try relayPicker.findBestMatch(from: exitCandidates)
- return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
}
func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
@@ -70,11 +70,11 @@ struct OneToMany: MultihopDecisionFlow {
case let (1, count) where count > 1:
let entryMatch = try multihopPicker.findBestMatch(from: entryCandidates)
let exitMatch = try multihopPicker.exclude(relay: entryMatch, from: exitCandidates)
- return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
default:
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
- return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
}
}
@@ -107,7 +107,7 @@ struct ManyToMany: MultihopDecisionFlow {
let exitMatch = try multihopPicker.findBestMatch(from: exitCandidates)
let entryMatch = try multihopPicker.exclude(relay: exitMatch, from: entryCandidates)
- return SelectedRelays(entry: entryMatch, exit: exitMatch)
+ return SelectedRelays(entry: entryMatch, exit: exitMatch, retryAttempt: relayPicker.connectionAttemptCount)
}
func canHandle(entryCandidates: [RelayCandidate], exitCandidates: [RelayCandidate]) -> Bool {
diff --git a/ios/MullvadREST/Relay/RelayPicking.swift b/ios/MullvadREST/Relay/RelayPicking.swift
index eec1003a1c..0877d8908e 100644
--- a/ios/MullvadREST/Relay/RelayPicking.swift
+++ b/ios/MullvadREST/Relay/RelayPicking.swift
@@ -30,8 +30,7 @@ extension RelayPicking {
return SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
- location: match.location,
- retryAttempts: connectionAttemptCount
+ location: match.location
)
}
}
@@ -50,7 +49,7 @@ struct SinglehopPicker: RelayPicking {
let match = try findBestMatch(from: candidates)
- return SelectedRelays(entry: nil, exit: match)
+ return SelectedRelays(entry: nil, exit: match, retryAttempt: connectionAttemptCount)
}
}
diff --git a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
index 390757c3dd..c1de0951aa 100644
--- a/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
+++ b/ios/MullvadREST/Relay/RelaySelectorProtocol.swift
@@ -25,15 +25,11 @@ public struct SelectedRelay: Equatable, Codable {
/// Relay geo location.
public let location: Location
- /// Number of retried attempts to connect to a relay.
- public let retryAttempts: UInt
-
/// Designated initializer.
- public init(endpoint: MullvadEndpoint, hostname: String, location: Location, retryAttempts: UInt) {
+ public init(endpoint: MullvadEndpoint, hostname: String, location: Location) {
self.endpoint = endpoint
self.hostname = hostname
self.location = location
- self.retryAttempts = retryAttempts
}
}
@@ -46,10 +42,12 @@ extension SelectedRelay: CustomDebugStringConvertible {
public struct SelectedRelays: Equatable, Codable {
public let entry: SelectedRelay?
public let exit: SelectedRelay
+ public let retryAttempt: UInt
- public init(entry: SelectedRelay?, exit: SelectedRelay) {
+ public init(entry: SelectedRelay?, exit: SelectedRelay, retryAttempt: UInt) {
self.entry = entry
self.exit = exit
+ self.retryAttempt = retryAttempt
}
}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index e4b9a2a532..fcea4faa22 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -477,7 +477,6 @@
7A3353932AAA089000F0A71C /* SimulatorTunnelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */; };
7A3353972AAA0F8600F0A71C /* OperationBlockObserverSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */; };
7A3AD5012C1068A800E9AD90 /* RelayPicking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3AD5002C1068A800E9AD90 /* RelayPicking.swift */; };
- 7A3EFAAB2BDFDAE800318736 /* RelaySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3EFAAA2BDFDAE800318736 /* RelaySelection.swift */; };
7A3FD1B52AD4465A0042BEA6 /* AppMessageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */; };
7A3FD1B72AD54ABD0042BEA6 /* AnyTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */; };
7A3FD1B82AD54AE60042BEA6 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
@@ -598,10 +597,8 @@
7ADCB2D82B6A6EB300C88F89 /* AnyRelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */; };
7ADCB2DA2B6A730400C88F89 /* IPOverrideRepositoryStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */; };
7AE044BB2A935726003915D8 /* Routing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A88DCD02A8FABBE00D2FF0E /* Routing.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 7AE2414A2C20682B0076CE33 /* FormsheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE241482C20682B0076CE33 /* FormsheetPresentationController.swift */; };
7AE90B682C2D726000375A60 /* NSParagraphStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE90B672C2D726000375A60 /* NSParagraphStyle+Extensions.swift */; };
7AEBA52A2C2179F20018BEC5 /* RelaySelectorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEBA5292C2179F20018BEC5 /* RelaySelectorWrapper.swift */; };
- 7AEBA52C2C22C65B0018BEC5 /* TimeInterval+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEBA52B2C22C65B0018BEC5 /* TimeInterval+Timeout.swift */; };
7AED35CC2BD13F60002A67D1 /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
7AED35CD2BD13FC4002A67D1 /* ApplicationTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C76A072A33850E00100D75 /* ApplicationTarget.swift */; };
7AEF7F1A2AD00F52006FE45D /* AppMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */; };
@@ -855,12 +852,15 @@
F0164EBE2B4BFF940020268D /* ShadowsocksLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */; };
F0164EC32B4C49D30020268D /* ShadowsocksLoaderStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */; };
F0164ED12B4F2DCB0020268D /* AccessMethodIterator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */; };
+ F01DAE332C2B032A00521E46 /* RelaySelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F01DAE322C2B032A00521E46 /* RelaySelection.swift */; };
F028A56A2A34D4E700C0CAA3 /* RedeemVoucherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */; };
F028A56C2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */; };
F02F41A02B9723AF00625A4F /* AddLocationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */; };
F02F41A12B9723AF00625A4F /* AddLocationsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */; };
F02F41A22B9723AF00625A4F /* AddLocationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */; };
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */; };
+ F03A69F72C2AD2D6000E2E7E /* TimeInterval+Timeout.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */; };
+ F03A69F92C2AD414000E2E7E /* FormsheetPresentationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */; };
F04413612BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04413622BA45CE30018A6EE /* CustomListLocationNodeBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */; };
F04FBE612A8379EE009278D7 /* AppPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04FBE602A8379EE009278D7 /* AppPreferences.swift */; };
@@ -900,6 +900,9 @@
F09D04BD2AEBB7C5003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
F09D04C02AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */; };
F09D04C12AF39EA2003D4F89 /* OutgoingConnectionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */; };
+ F0A0868E2C22D60100BF83E7 /* Tunnel+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */; };
+ F0A086902C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */; };
+ F0A086922C22E0F100BF83E7 /* Tunnel+Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */; };
F0ACE30D2BE4E478006D5333 /* MullvadMockData.h in Headers */ = {isa = PBXBuildFile; fileRef = F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */; settings = {ATTRIBUTES = (Public, ); }; };
F0ACE3102BE4E478006D5333 /* MullvadMockData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; };
F0ACE3112BE4E478006D5333 /* MullvadMockData.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -1841,7 +1844,6 @@
7A3353922AAA089000F0A71C /* SimulatorTunnelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelInfo.swift; sourceTree = "<group>"; };
7A3353962AAA0F8600F0A71C /* OperationBlockObserverSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationBlockObserverSupport.swift; sourceTree = "<group>"; };
7A3AD5002C1068A800E9AD90 /* RelayPicking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicking.swift; sourceTree = "<group>"; };
- 7A3EFAAA2BDFDAE800318736 /* RelaySelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelection.swift; sourceTree = "<group>"; };
7A3FD1B42AD4465A0042BEA6 /* AppMessageHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandlerTests.swift; sourceTree = "<group>"; };
7A42DEC82A05164100B209BE /* SettingsInputCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsInputCell.swift; sourceTree = "<group>"; };
7A45CFC22C05FF2F00D80B21 /* ScreenshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotTests.swift; sourceTree = "<group>"; };
@@ -1944,10 +1946,8 @@
7AD0AA202AD6CB0000119E10 /* URLRequestProxyStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLRequestProxyStub.swift; sourceTree = "<group>"; };
7ADCB2D72B6A6EB300C88F89 /* AnyRelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyRelay.swift; sourceTree = "<group>"; };
7ADCB2D92B6A730400C88F89 /* IPOverrideRepositoryStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IPOverrideRepositoryStub.swift; sourceTree = "<group>"; };
- 7AE241482C20682B0076CE33 /* FormsheetPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormsheetPresentationController.swift; sourceTree = "<group>"; };
7AE90B672C2D726000375A60 /* NSParagraphStyle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSParagraphStyle+Extensions.swift"; sourceTree = "<group>"; };
7AEBA5292C2179F20018BEC5 /* RelaySelectorWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelaySelectorWrapper.swift; sourceTree = "<group>"; };
- 7AEBA52B2C22C65B0018BEC5 /* TimeInterval+Timeout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Timeout.swift"; sourceTree = "<group>"; };
7AEF7F192AD00F52006FE45D /* AppMessageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppMessageHandler.swift; sourceTree = "<group>"; };
7AF10EB12ADE859200C090B9 /* AlertViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = "<group>"; };
7AF10EB32ADE85BC00C090B9 /* RelayFilterCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelayFilterCoordinator.swift; sourceTree = "<group>"; };
@@ -2106,12 +2106,15 @@
F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoader.swift; sourceTree = "<group>"; };
F0164EC22B4C49D30020268D /* ShadowsocksLoaderStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksLoaderStub.swift; sourceTree = "<group>"; };
F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessMethodIterator.swift; sourceTree = "<group>"; };
+ F01DAE322C2B032A00521E46 /* RelaySelection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelaySelection.swift; sourceTree = "<group>"; };
F028A5692A34D4E700C0CAA3 /* RedeemVoucherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedeemVoucherViewController.swift; sourceTree = "<group>"; };
F028A56B2A34D8E600C0CAA3 /* AddCreditSucceededViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCreditSucceededViewController.swift; sourceTree = "<group>"; };
F02F419A2B9723AE00625A4F /* AddLocationsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsViewController.swift; sourceTree = "<group>"; };
F02F419B2B9723AE00625A4F /* AddLocationsDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsDataSource.swift; sourceTree = "<group>"; };
F02F419C2B9723AF00625A4F /* AddLocationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddLocationsCoordinator.swift; sourceTree = "<group>"; };
F03580242A13842C00E5DAFD /* IncreasedHitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncreasedHitButton.swift; sourceTree = "<group>"; };
+ F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Timeout.swift"; sourceTree = "<group>"; };
+ F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormsheetPresentationController.swift; sourceTree = "<group>"; };
F04413602BA45CD70018A6EE /* CustomListLocationNodeBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomListLocationNodeBuilder.swift; sourceTree = "<group>"; };
F04DD3D72C130DF600E03E28 /* TunnelSettingsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsManager.swift; sourceTree = "<group>"; };
F04FBE602A8379EE009278D7 /* AppPreferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppPreferences.swift; sourceTree = "<group>"; };
@@ -2143,6 +2146,8 @@
F09D04BA2AE95396003D4F89 /* URLSessionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionStub.swift; sourceTree = "<group>"; };
F09D04BC2AEBB7C5003D4F89 /* OutgoingConnectionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionService.swift; sourceTree = "<group>"; };
F09D04BF2AF39D63003D4F89 /* OutgoingConnectionServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutgoingConnectionServiceTests.swift; sourceTree = "<group>"; };
+ F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Settings.swift"; sourceTree = "<group>"; };
+ F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsStrategyTests.swift; sourceTree = "<group>"; };
F0ACE3082BE4E478006D5333 /* MullvadMockData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MullvadMockData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F0ACE30A2BE4E478006D5333 /* MullvadMockData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MullvadMockData.h; sourceTree = "<group>"; };
F0ACE32E2BE4EA8B006D5333 /* MockProxyFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProxyFactory.swift; sourceTree = "<group>"; };
@@ -2542,6 +2547,7 @@
44DD7D262B6D18FB0005F67F /* MockTunnelInteractor.swift */,
44DD7D232B6CFFD70005F67F /* StartTunnelOperationTests.swift */,
A9A5F9A12ACB003D0083449F /* TunnelManagerTests.swift */,
+ F0A0868F2C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift */,
44BB5F992BE529FE002520EB /* TunnelStateTests.swift */,
A9E0317B2ACBFC7E0095D843 /* TunnelStore+Stubs.swift */,
A9E031792ACB0AE70095D843 /* UIApplication+Stubs.swift */,
@@ -2662,6 +2668,7 @@
58F2E145276A2C9900A79513 /* StopTunnelOperation.swift */,
58E0A98727C8F46300FE6BDD /* Tunnel.swift */,
5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */,
+ F0A0868D2C22D60100BF83E7 /* Tunnel+Settings.swift */,
5878A27229091D6D0096FC88 /* TunnelBlockObserver.swift */,
5803B4AF2940A47300C23744 /* TunnelConfiguration.swift */,
58968FAD28743E2000B799DC /* TunnelInteractor.swift */,
@@ -2781,7 +2788,7 @@
F0BE65362B9F136A005CC385 /* LocationSectionHeaderView.swift */,
5888AD86227B17950051EB06 /* LocationViewController.swift */,
7AB3BEB42BD7A6CB00E34384 /* LocationViewControllerWrapper.swift */,
- 7A3EFAAA2BDFDAE800318736 /* RelaySelection.swift */,
+ F01DAE322C2B032A00521E46 /* RelaySelection.swift */,
);
path = SelectLocation;
sourceTree = "<group>";
@@ -3865,7 +3872,7 @@
7AE241492C20682B0076CE33 /* Presentation controllers */ = {
isa = PBXGroup;
children = (
- 7AE241482C20682B0076CE33 /* FormsheetPresentationController.swift */,
+ F03A69F82C2AD413000E2E7E /* FormsheetPresentationController.swift */,
);
path = "Presentation controllers";
sourceTree = "<group>";
@@ -3873,7 +3880,7 @@
7AEBA52D2C2310D20018BEC5 /* Extensions */ = {
isa = PBXGroup;
children = (
- 7AEBA52B2C22C65B0018BEC5 /* TimeInterval+Timeout.swift */,
+ F03A69F62C2AD2D5000E2E7E /* TimeInterval+Timeout.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -5242,6 +5249,7 @@
A9A5F9EB2ACB05160083449F /* CharacterSet+IPAddress.swift in Sources */,
F0D8825C2B04F70E00D3EF9A /* OutgoingConnectionData.swift in Sources */,
44B3C43D2C00CBBD0079782C /* PacketTunnelActorReducerTests.swift in Sources */,
+ F0A086902C22D6A700BF83E7 /* TunnelSettingsStrategyTests.swift in Sources */,
A9A5F9EC2ACB05160083449F /* CodingErrors+CustomErrorDescription.swift in Sources */,
A9A5F9ED2ACB05160083449F /* NSRegularExpression+IPAddress.swift in Sources */,
A9A5F9EE2ACB05160083449F /* RESTCreateApplePaymentResponse+Localization.swift in Sources */,
@@ -5263,6 +5271,7 @@
A9A5F9F62ACB05160083449F /* TunnelStatusNotificationProvider.swift in Sources */,
A9A5F9F72ACB05160083449F /* NotificationProviderProtocol.swift in Sources */,
A9A5F9F82ACB05160083449F /* NotificationProviderIdentifier.swift in Sources */,
+ F0A086922C22E0F100BF83E7 /* Tunnel+Settings.swift in Sources */,
A9A5F9F92ACB05160083449F /* NotificationProvider.swift in Sources */,
A9A5F9FA2ACB05160083449F /* InAppNotificationDescriptor.swift in Sources */,
A9A5F9FB2ACB05160083449F /* InAppNotificationProvider.swift in Sources */,
@@ -5502,6 +5511,7 @@
5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */,
58E511E628DDDEAC00B0BCDE /* CodingErrors+CustomErrorDescription.swift in Sources */,
58C76A0B2A338E4300100D75 /* BackgroundTask.swift in Sources */,
+ F0A0868E2C22D60100BF83E7 /* Tunnel+Settings.swift in Sources */,
7A9CCCC32A96302800DD6A34 /* ApplicationCoordinator.swift in Sources */,
5864AF0729C78843005B0CD9 /* SettingsCellFactory.swift in Sources */,
587B75412668FD7800DEF7E9 /* AccountExpirySystemNotificationProvider.swift in Sources */,
@@ -5541,6 +5551,7 @@
7A9CCCB32A96302800DD6A34 /* WelcomeCoordinator.swift in Sources */,
587B753B2666467500DEF7E9 /* NotificationBannerView.swift in Sources */,
5827B0922B0CAB2800CCBBA1 /* MethodSettingsViewController.swift in Sources */,
+ F01DAE332C2B032A00521E46 /* RelaySelection.swift in Sources */,
58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */,
7A516C2E2B6D357500BBD33D /* URL+Scoping.swift in Sources */,
5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */,
@@ -5589,6 +5600,7 @@
58CEB30C2AFD586600E6E088 /* DynamicBackgroundConfiguration.swift in Sources */,
587B7536266528A200DEF7E9 /* NotificationManager.swift in Sources */,
5820EDA9288FE064006BF4E4 /* DeviceManagementInteractor.swift in Sources */,
+ F03A69F92C2AD414000E2E7E /* FormsheetPresentationController.swift in Sources */,
7A9CCCB92A96302800DD6A34 /* LocationCoordinator.swift in Sources */,
58FB865A26EA214400F188BC /* RelayCacheTrackerObserver.swift in Sources */,
7AE90B682C2D726000375A60 /* NSParagraphStyle+Extensions.swift in Sources */,
@@ -5710,7 +5722,6 @@
5807E2C02432038B00F5FF30 /* String+Split.swift in Sources */,
58B26E242943520C00D5980C /* NotificationProviderProtocol.swift in Sources */,
5877F94E2A0A59AA0052D9E9 /* NotificationResponse.swift in Sources */,
- 7AE2414A2C20682B0076CE33 /* FormsheetPresentationController.swift in Sources */,
7A6389E52B7E4247008E77E1 /* EditCustomListCoordinator.swift in Sources */,
58677712290976FB006F721F /* SettingsInteractor.swift in Sources */,
58EF875D2B1638BF00C098B2 /* ProxyConfigurationTesterProtocol.swift in Sources */,
@@ -5720,7 +5731,6 @@
5878F50029CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift in Sources */,
7A28826D2BAAC9DE00FD9F20 /* IPOverrideHeaderView.swift in Sources */,
A98502032B627B120061901E /* LocalNetworkProbe.swift in Sources */,
- 7A3EFAAB2BDFDAE800318736 /* RelaySelection.swift in Sources */,
7A6F2FA92AFD0842006D0856 /* CustomDNSDataSource.swift in Sources */,
58EF580B25D69D7A00AEBA94 /* ProblemReportSubmissionOverlayView.swift in Sources */,
5892A45E265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift in Sources */,
@@ -6093,11 +6103,11 @@
F0ACE31E2BE4E4F2006D5333 /* AccountsProxy+Stubs.swift in Sources */,
F0ACE3202BE4E4F2006D5333 /* AccessTokenManager+Stubs.swift in Sources */,
F0ACE32C2BE4E77E006D5333 /* DeviceMock.swift in Sources */,
- 7AEBA52C2C22C65B0018BEC5 /* TimeInterval+Timeout.swift in Sources */,
F0ACE3222BE4E4F2006D5333 /* APIProxy+Stubs.swift in Sources */,
F0ACE3332BE516F1006D5333 /* RESTRequestExecutor+Stubs.swift in Sources */,
F0ACE32D2BE4E784006D5333 /* AccountMock.swift in Sources */,
7A52F96A2C1735AE00B133B9 /* RelaySelectorStub.swift in Sources */,
+ F03A69F72C2AD2D6000E2E7E /* TimeInterval+Timeout.swift in Sources */,
F0ACE32F2BE4EA8B006D5333 /* MockProxyFactory.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 769954e553..124ec57517 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -79,6 +79,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
ipOverrideRepository: ipOverrideRepository
)
+ let constraintsUpdater = RelayConstraintsUpdater()
+ let multihopListener = MultihopStateListener()
+ let multihopUpdater = MultihopUpdater(listener: multihopListener)
+
relayCacheTracker = RelayCacheTracker(
relayCache: ipOverrideWrapper,
application: application,
@@ -86,11 +90,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
)
addressCacheTracker = AddressCacheTracker(application: application, apiProxy: apiProxy, store: addressCache)
- tunnelStore = TunnelStore(application: application)
- let constraintsUpdater = RelayConstraintsUpdater()
- let multihopListener = MultihopStateListener()
- let multihopUpdater = MultihopUpdater(listener: multihopListener)
+ tunnelStore = TunnelStore(application: application)
let relaySelector = RelaySelectorWrapper(
relayCache: ipOverrideWrapper,
diff --git a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
index 08b49d4b05..193f79b987 100644
--- a/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
+++ b/ios/MullvadVPN/SimulatorTunnelProvider/SimulatorTunnelProviderHost.swift
@@ -177,7 +177,8 @@ final class SimulatorTunnelProviderHost: SimulatorTunnelProviderDelegate {
networkReachability: .reachable,
connectionAttemptCount: 0,
transportLayer: .udp,
- remotePort: selectedRelays.exit.endpoint.ipv4Relay.port, // TODO: Multihop
+ remotePort: selectedRelays.entry?.endpoint.ipv4Relay.port ?? selectedRelays.exit.endpoint.ipv4Relay
+ .port,
isPostQuantum: settings.tunnelQuantumResistance.isEnabled
)
)
diff --git a/ios/MullvadVPN/TunnelManager/Tunnel+Settings.swift b/ios/MullvadVPN/TunnelManager/Tunnel+Settings.swift
new file mode 100644
index 0000000000..80c91b7618
--- /dev/null
+++ b/ios/MullvadVPN/TunnelManager/Tunnel+Settings.swift
@@ -0,0 +1,26 @@
+//
+// Tunnel+Settings.swift
+// MullvadVPN
+//
+// Created by Mojgan on 2024-06-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadLogging
+import MullvadSettings
+
+protocol TunnelSettingsStrategyProtocol {
+ func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool
+}
+
+struct TunnelSettingsStrategy: TunnelSettingsStrategyProtocol {
+ func shouldReconnectToNewRelay(oldSettings: LatestTunnelSettings, newSettings: LatestTunnelSettings) -> Bool {
+ switch (oldSettings, newSettings) {
+ case let (old, new) where old.relayConstraints != new.relayConstraints,
+ let (old, new) where old.tunnelMultihopState != new.tunnelMultihopState:
+ true
+ default:
+ false
+ }
+ }
+}
diff --git a/ios/MullvadVPN/TunnelManager/TunnelManager.swift b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
index d6d4be4d06..bc3dff4fc7 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelManager.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelManager.swift
@@ -939,16 +939,16 @@ final class TunnelManager: StorePaymentObserver {
let operation = AsyncBlockOperation(dispatchQueue: internalQueue) {
let currentSettings = self._tunnelSettings
var updatedSettings = self._tunnelSettings
+ let settingsStrategy = TunnelSettingsStrategy()
modificationBlock(&updatedSettings)
- // Select new relay only when relay constraints change.
- let currentConstraints = currentSettings.relayConstraints
- let updatedConstraints = updatedSettings.relayConstraints
- let selectNewRelay = currentConstraints != updatedConstraints
-
self.setSettings(updatedSettings, persist: true)
- self.reconnectTunnel(selectNewRelay: selectNewRelay, completionHandler: nil)
+ self.reconnectTunnel(
+ selectNewRelay: settingsStrategy
+ .shouldReconnectToNewRelay(oldSettings: currentSettings, newSettings: updatedSettings),
+ completionHandler: nil
+ )
}
operation.completionBlock = {
@@ -1146,27 +1146,27 @@ extension TunnelManager {
```
func delay(seconds: UInt) async throws {
- try await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
+ try await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
}
Task {
- print("Wait 5 seconds")
- try await delay(seconds: 5)
+ print("Wait 5 seconds")
+ try await delay(seconds: 5)
- print("Simulate active account")
- self.tunnelManager.simulateAccountExpiration(option: .active)
- try await delay(seconds: 5)
+ print("Simulate active account")
+ self.tunnelManager.simulateAccountExpiration(option: .active)
+ try await delay(seconds: 5)
- print("Simulate close to expiry")
- self.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
- try await delay(seconds: 10)
+ print("Simulate close to expiry")
+ self.tunnelManager.simulateAccountExpiration(option: .closeToExpiry)
+ try await delay(seconds: 10)
- print("Simulate expired account")
- self.tunnelManager.simulateAccountExpiration(option: .expired)
- try await delay(seconds: 5)
+ print("Simulate expired account")
+ self.tunnelManager.simulateAccountExpiration(option: .expired)
+ try await delay(seconds: 5)
- print("Simulate active account")
- self.tunnelManager.simulateAccountExpiration(option: .active)
+ print("Simulate active account")
+ self.tunnelManager.simulateAccountExpiration(option: .active)
}
```
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
index 3422c8602d..1f524f56e8 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelState+UI.swift
@@ -187,8 +187,8 @@ extension TunnelState {
value: "Quantum secure connection. Connected to %@, %@",
comment: ""
),
- tunnelInfo.exit.location.city, // TODO: Multihop
- tunnelInfo.exit.location.country // TODO: Multihop
+ tunnelInfo.exit.location.city,
+ tunnelInfo.exit.location.country
)
} else {
String(
@@ -198,8 +198,8 @@ extension TunnelState {
value: "Secure connection. Connected to %@, %@",
comment: ""
),
- tunnelInfo.exit.location.city, // TODO: Multihop
- tunnelInfo.exit.location.country // TODO: Multihop
+ tunnelInfo.exit.location.city,
+ tunnelInfo.exit.location.country
)
}
@@ -219,8 +219,8 @@ extension TunnelState {
value: "Reconnecting to %@, %@",
comment: ""
),
- tunnelInfo.exit.location.city, // TODO: Multihop
- tunnelInfo.exit.location.country // TODO: Multihop
+ tunnelInfo.exit.location.city,
+ tunnelInfo.exit.location.country
)
case .waitingForConnectivity(.noConnection), .error:
diff --git a/ios/MullvadVPN/TunnelManager/TunnelState.swift b/ios/MullvadVPN/TunnelManager/TunnelState.swift
index 2c5a6109b6..7d76a53bc4 100644
--- a/ios/MullvadVPN/TunnelManager/TunnelState.swift
+++ b/ios/MullvadVPN/TunnelManager/TunnelState.swift
@@ -83,24 +83,39 @@ enum TunnelState: Equatable, CustomStringConvertible {
"pending reconnect after disconnect"
case let .connecting(tunnelRelays, isPostQuantum):
if let tunnelRelays {
- "connecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
+ """
+ connecting \(isPostQuantum ? "(PQ) " : "")\
+ to \(tunnelRelays.exit.hostname)\
+ \(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
+ """
} else {
"connecting\(isPostQuantum ? " (PQ)" : ""), fetching relay"
}
case let .connected(tunnelRelays, isPostQuantum):
- "connected \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
+ """
+ connected \(isPostQuantum ? "(PQ) " : "")\
+ to \(tunnelRelays.exit.hostname)\
+ \(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
+ """
case let .disconnecting(actionAfterDisconnect):
"disconnecting and then \(actionAfterDisconnect)"
case .disconnected:
"disconnected"
case let .reconnecting(tunnelRelays, isPostQuantum):
- "reconnecting \(isPostQuantum ? "(PQ) " : "")to \(tunnelRelays.exit.hostname)" // TODO: Multihop
+ """
+ reconnecting \(isPostQuantum ? "(PQ) " : "")\
+ to \(tunnelRelays.exit.hostname)\
+ \(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
+ """
case .waitingForConnectivity:
"waiting for connectivity"
case let .error(blockedStateReason):
"error state: \(blockedStateReason)"
case let .negotiatingPostQuantumKey(tunnelRelays, _):
- "negotiating key with \(tunnelRelays.exit.hostname)" // TODO: Multihop
+ """
+ negotiating key with exit relay: \(tunnelRelays.exit.hostname)\
+ \(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")
+ """
}
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
index c627f85fa3..8e7cac0676 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
@@ -139,7 +139,7 @@ final class TunnelControlView: UIView {
connectButtonBlurView.isEnabled = model.enableButtons
cityLabel.attributedText = attributedStringForLocation(string: model.city)
countryLabel.attributedText = attributedStringForLocation(string: model.country)
- connectionPanel.connectedRelayName = model.connectedRelayName
+ connectionPanel.connectedRelayName = model.connectedRelaysName
connectionPanel.dataSource = model.connectionPanel
updateSecureLabel(tunnelState: tunnelState)
@@ -227,14 +227,15 @@ final class TunnelControlView: UIView {
private func updateTunnelRelays(tunnelRelays: SelectedRelays?) {
if let tunnelRelays {
cityLabel.attributedText = attributedStringForLocation(
- string: tunnelRelays.exit.location.city // TODO: Multihop
+ string: tunnelRelays.exit.location.city
)
countryLabel.attributedText = attributedStringForLocation(
- string: tunnelRelays.exit.location.country // TODO: Multihop
+ string: tunnelRelays.exit.location.country
)
connectionPanel.isHidden = false
- connectionPanel.connectedRelayName = tunnelRelays.exit.hostname // TODO: Multihop
+ connectionPanel.connectedRelayName = tunnelRelays.exit
+ .hostname + "\(tunnelRelays.entry.flatMap { " via \($0.hostname)" } ?? "")"
} else {
countryLabel.attributedText = attributedStringForLocation(string: " ")
cityLabel.attributedText = attributedStringForLocation(string: " ")
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift
index c0df319d25..d1ce7b3a7a 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelControlViewModel.swift
@@ -14,7 +14,7 @@ struct TunnelControlViewModel {
let enableButtons: Bool
let city: String
let country: String
- let connectedRelayName: String
+ let connectedRelaysName: String
let outgoingConnectionInfo: OutgoingConnectionInfo?
var connectionPanel: ConnectionPanelData? {
@@ -29,7 +29,7 @@ struct TunnelControlViewModel {
}
return ConnectionPanelData(
- inAddress: "\(tunnelRelays.exit.endpoint.ipv4Relay.ip)\(portAndTransport)", // TODO: Multihop
+ inAddress: "\(tunnelRelays.entry?.endpoint.ipv4Relay.ip ?? tunnelRelays.exit.endpoint.ipv4Relay.ip)\(portAndTransport)",
outAddress: outgoingConnectionInfo?.outAddress
)
}
@@ -41,7 +41,7 @@ struct TunnelControlViewModel {
enableButtons: true,
city: "",
country: "",
- connectedRelayName: "",
+ connectedRelaysName: "",
outgoingConnectionInfo: nil
)
}
@@ -53,7 +53,7 @@ struct TunnelControlViewModel {
enableButtons: enableButtons,
city: city,
country: country,
- connectedRelayName: connectedRelayName,
+ connectedRelaysName: connectedRelaysName,
outgoingConnectionInfo: nil
)
}
@@ -65,7 +65,7 @@ struct TunnelControlViewModel {
enableButtons: enableButtons,
city: city,
country: country,
- connectedRelayName: connectedRelayName,
+ connectedRelaysName: connectedRelaysName,
outgoingConnectionInfo: outgoingConnectionInfo
)
}
diff --git a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
index b5d0dfab64..95e9260d20 100644
--- a/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
+++ b/ios/MullvadVPN/View controllers/Tunnel/TunnelViewController.swift
@@ -150,15 +150,15 @@ class TunnelViewController: UIViewController, RootContainment {
case let .connecting(tunnelRelays, _):
mapViewController.removeLocationMarker()
contentView.setAnimatingActivity(true)
- mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated) // TODO: Multihop
+ mapViewController.setCenter(tunnelRelays?.exit.location.geoCoordinate, animated: animated)
case let .reconnecting(tunnelRelays, _), let .negotiatingPostQuantumKey(tunnelRelays, _):
mapViewController.removeLocationMarker()
contentView.setAnimatingActivity(true)
- mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated) // TODO: Multihop
+ mapViewController.setCenter(tunnelRelays.exit.location.geoCoordinate, animated: animated)
case let .connected(tunnelRelays, _):
- let center = tunnelRelays.exit.location.geoCoordinate // TODO: Multihop
+ let center = tunnelRelays.exit.location.geoCoordinate
mapViewController.setCenter(center, animated: animated) {
self.contentView.setAnimatingActivity(false)
diff --git a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
index 637e47c89e..1eefa09816 100644
--- a/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/PacketTunnelCore/PacketTunnelActorReducerTests.swift
@@ -25,7 +25,7 @@ final class PacketTunnelActorReducerTests: XCTestCase {
keyPolicy: keyPolicy,
networkReachability: .reachable,
connectionAttemptCount: 0,
- connectedEndpoint: selectedRelays.exit.endpoint, // TODO: Multihop
+ connectedEndpoint: selectedRelays.entry?.endpoint ?? selectedRelays.exit.endpoint,
transportLayer: .udp,
remotePort: 12345,
isPostQuantum: false
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelSettingsStrategyTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelSettingsStrategyTests.swift
new file mode 100644
index 0000000000..685c643d17
--- /dev/null
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelSettingsStrategyTests.swift
@@ -0,0 +1,101 @@
+//
+// TunnelSettingsStrategyTests.swift
+// MullvadVPNTests
+//
+// Created by Mojgan on 2024-06-19.
+// Copyright © 2024 Mullvad VPN AB. All rights reserved.
+//
+import MullvadSettings
+import MullvadTypes
+import XCTest
+
+final class TunnelSettingsStrategyTests: XCTestCase {
+ func testConnectToNewRelayOnMultihopChanges() {
+ var currentSettings = LatestTunnelSettings()
+ TunnelSettingsUpdate.multihop(.off).apply(to: &currentSettings)
+
+ var updatedSettings = currentSettings
+ TunnelSettingsUpdate.multihop(.on).apply(to: &updatedSettings)
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertTrue(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+
+ func testConnectToNewRelayOnRelaysConstraintChange() {
+ var currentSettings = LatestTunnelSettings()
+ TunnelSettingsUpdate.relayConstraints(RelayConstraints()).apply(to: &currentSettings)
+
+ var updatedSettings = currentSettings
+ TunnelSettingsUpdate.relayConstraints(RelayConstraints(
+ exitLocations: .only(UserSelectedRelays(locations: [.country("zz")])),
+ port: .only(9999),
+ filter: .only(.init(ownership: .rented, providers: .only(["foo", "bar"])))
+ )).apply(to: &updatedSettings)
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertTrue(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+
+ func testConnectToCurrentRelayOnDNSSettingsChange() {
+ let currentSettings = LatestTunnelSettings()
+
+ var updatedSettings = currentSettings
+ var dnsSettings = DNSSettings()
+ dnsSettings.blockingOptions = [.blockAdvertising, .blockTracking]
+ dnsSettings.enableCustomDNS = true
+ TunnelSettingsUpdate.dnsSettings(dnsSettings).apply(to: &updatedSettings)
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertFalse(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+
+ func testConnectToCurrentRelayOnQuantumResistanceChanges() {
+ var currentSettings = LatestTunnelSettings()
+ TunnelSettingsUpdate.quantumResistance(.off).apply(to: &currentSettings)
+
+ var updatedSettings = currentSettings
+ TunnelSettingsUpdate.quantumResistance(.on).apply(to: &updatedSettings)
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertFalse(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+
+ func testConnectToCurrentRelayOnWireGuardObfuscationChange() {
+ var currentSettings = LatestTunnelSettings()
+ TunnelSettingsUpdate.obfuscation(WireGuardObfuscationSettings(state: .off, port: .port80))
+ .apply(to: &currentSettings)
+
+ var updatedSettings = currentSettings
+ TunnelSettingsUpdate.obfuscation(WireGuardObfuscationSettings(state: .automatic, port: .automatic))
+ .apply(to: &updatedSettings)
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertFalse(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+
+ func testConnectToCurrentRelayWhenNothingChange() {
+ let currentSettings = LatestTunnelSettings()
+ let updatedSettings = currentSettings
+
+ let tunnelSettingsStrategy = TunnelSettingsStrategy()
+ XCTAssertFalse(tunnelSettingsStrategy.shouldReconnectToNewRelay(
+ oldSettings: currentSettings,
+ newSettings: updatedSettings
+ ))
+ }
+}
diff --git a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
index 3392e585ee..3351fcb554 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WgAdapter.swift
@@ -45,6 +45,42 @@ struct WgAdapter: TunnelAdapterProtocol {
}
}
+ func startMultihop(
+ entryConfiguration: TunnelAdapterConfiguration? = nil,
+ exitConfiguration: TunnelAdapterConfiguration
+ ) async throws {
+ let exitConfiguration = exitConfiguration.asWgConfig
+ let entryConfiguration = entryConfiguration?.asWgConfig
+
+ logger.info("\(exitConfiguration.peers)")
+
+ if let entryConfiguration {
+ logger.info("\(entryConfiguration.peers)")
+ }
+
+ do {
+ try await adapter.stop()
+ try await adapter.startMultihop(
+ entryConfiguration: entryConfiguration,
+ exitConfiguration: exitConfiguration
+ )
+ } catch WireGuardAdapterError.invalidState {
+ try await adapter.startMultihop(
+ entryConfiguration: entryConfiguration,
+ exitConfiguration: exitConfiguration
+ )
+ }
+
+ let exitTunAddresses = exitConfiguration.interface.addresses.map { $0.address }
+ let entryTunAddresses = entryConfiguration?.interface.addresses.map { $0.address } ?? []
+ let tunAddresses = exitTunAddresses + entryTunAddresses
+
+ // TUN addresses can be empty when adapter is configured for blocked state.
+ if !tunAddresses.isEmpty {
+ logIfDeviceHasSameIP(than: tunAddresses)
+ }
+ }
+
func stop() async throws {
try await adapter.stop()
}
diff --git a/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapter+Async.swift b/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapter+Async.swift
index 1644597b94..9c2322a71f 100644
--- a/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapter+Async.swift
+++ b/ios/PacketTunnel/WireGuardAdapter/WireGuardAdapter+Async.swift
@@ -22,6 +22,18 @@ extension WireGuardAdapter {
}
}
+ func startMultihop(entryConfiguration: TunnelConfiguration?, exitConfiguration: TunnelConfiguration) async throws {
+ return try await withCheckedThrowingContinuation { continuation in
+ startMultihop(exitConfiguration: exitConfiguration, entryConfiguration: entryConfiguration) { error in
+ if let error {
+ continuation.resume(throwing: error)
+ } else {
+ continuation.resume(returning: ())
+ }
+ }
+ }
+ }
+
func stop() async throws {
return try await withCheckedThrowingContinuation { continuation in
stop { error in
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
index 30348546f1..a03f0ae4f9 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor+PostQuantum.swift
@@ -75,8 +75,8 @@ extension PacketTunnelActor {
state = .connecting(connectionState)
try? await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
- // Resume tunnel monitoring and use IPv4 gateway as a probe address.
- tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway) // TODO: Multihop
+ // Resume tunnel monitoring and use exit IPv4 gateway as a probe address.
+ tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway)
// Restart default path observer and notify the observer with the current path that might have changed while
// path observer was paused.
startDefaultPathObserver(notifyObserverWithCurrentPath: false)
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
index b7a3bf204d..1b17312739 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActor.swift
@@ -20,11 +20,11 @@ import WireGuardKitTypes
- Actor receives events for execution over the `EventChannel`.
- Events are consumed in a detached task via for-await loop over the channel. Each event, once received, is executed in its entirety before the next
- event is processed. See the implementation of `consumeEvents()` which is the central task dispatcher inside of actor.
+ event is processed. See the implementation of `consumeEvents()` which is the central task dispatcher inside of actor.
- Most of calls that actor performs suspend for a very short amount of time. `EventChannel` proactively discards unwanted tasks as they arrive to prevent
- future execution, such as repeating commands to reconnect are coalesced and all events prior to stop are discarded entirely as the outcome would be the
- same anyway.
+ future execution, such as repeating commands to reconnect are coalesced and all events prior to stop are discarded entirely as the outcome would be the
+ same anyway.
*/
public actor PacketTunnelActor {
var state: State = .initial {
@@ -209,8 +209,8 @@ extension PacketTunnelActor {
Reconnect tunnel to new relays. Enters error state on failure.
- Parameters:
- - nextRelay: next relays to connect to
- - reason: reason for reconnect
+ - nextRelay: next relays to connect to
+ - reason: reason for reconnect
*/
private func reconnect(to nextRelays: NextRelays, reason: ActorReconnectReason) async {
do {
@@ -269,8 +269,8 @@ extension PacketTunnelActor {
- Reactivate default path observation (disabled when configuring tunnel adapter)
- Parameters:
- - nextRelays: which relays should be selected next.
- - reason: reason for reconnect
+ - nextRelays: which relays should be selected next.
+ - reason: reason for reconnect
*/
private func tryStartConnection(
withSettings settings: Settings,
@@ -289,16 +289,32 @@ extension PacketTunnelActor {
state = .reconnecting(connectionState)
}
- let configurationBuilder = ConfigurationBuilder(
+ let entryConfiguration: TunnelAdapterConfiguration? = if connectionState.selectedRelays.entry != nil {
+ try ConfigurationBuilder(
+ privateKey: activeKey,
+ interfaceAddresses: settings.interfaceAddresses,
+ dns: settings.dnsServers,
+ endpoint: connectionState.connectedEndpoint,
+ allowedIPs: [
+ IPAddressRange(from: "\(connectionState.selectedRelays.exit.endpoint.ipv4Relay.ip)/32")!,
+ ]
+ ).makeConfiguration()
+ } else {
+ nil
+ }
+
+ let exitConfiguration = try ConfigurationBuilder(
privateKey: activeKey,
interfaceAddresses: settings.interfaceAddresses,
dns: settings.dnsServers,
- endpoint: connectionState.connectedEndpoint,
+ endpoint: (entryConfiguration != nil)
+ ? connectionState.selectedRelays.exit.endpoint
+ : connectionState.connectedEndpoint,
allowedIPs: [
IPAddressRange(from: "0.0.0.0/0")!,
IPAddressRange(from: "::/0")!,
]
- )
+ ).makeConfiguration()
/*
Stop default path observer while updating WireGuard configuration since it will call the system method
@@ -313,19 +329,22 @@ extension PacketTunnelActor {
startDefaultPathObserver(notifyObserverWithCurrentPath: true)
}
- try await tunnelAdapter.start(configuration: configurationBuilder.makeConfiguration())
+ try await tunnelAdapter.startMultihop(
+ entryConfiguration: entryConfiguration,
+ exitConfiguration: exitConfiguration
+ )
// Resume tunnel monitoring and use IPv4 gateway as a probe address.
- tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway) // TODO: Multihop
+ tunnelMonitor.start(probeAddress: connectionState.selectedRelays.exit.endpoint.ipv4Gateway)
}
/**
Derive `ConnectionState` from current `state` updating it with new relays and settings.
- Parameters:
- - nextRelays: relay preference that should be used when selecting next relays.
- - settings: current settings
- - reason: reason for reconnect
+ - nextRelays: relay preference that should be used when selecting next relays.
+ - settings: current settings
+ - reason: reason for reconnect
- Returns: New connection state or `nil` if current state is at or past `.disconnecting` phase.
*/
@@ -348,8 +367,6 @@ extension PacketTunnelActor {
}
switch state {
- case .initial:
- break
// Handle PQ PSK separately as it doesn't interfere with either the `.connecting` or `.reconnecting` states.
case var .negotiatingPostQuantumKey(connectionState, _):
if reason == .connectionLoss {
@@ -359,8 +376,12 @@ extension PacketTunnelActor {
connectionState.selectedRelays,
connectionState.connectionAttemptCount
)
+ let connectedRelay = selectedRelays.entry ?? selectedRelays.exit
connectionState.selectedRelays = selectedRelays
connectionState.relayConstraints = settings.relayConstraints
+ connectionState.connectedEndpoint = connectedRelay.endpoint
+ connectionState.remotePort = connectedRelay.endpoint.ipv4Relay.port
+
return connectionState
case var .connecting(connectionState), var .reconnecting(connectionState):
if reason == .connectionLoss {
@@ -372,31 +393,37 @@ extension PacketTunnelActor {
connectionState.selectedRelays,
connectionState.connectionAttemptCount
)
+ let connectedRelay = selectedRelays.entry ?? selectedRelays.exit
connectionState.selectedRelays = selectedRelays
connectionState.relayConstraints = settings.relayConstraints
connectionState.currentKey = settings.privateKey
+ connectionState.connectedEndpoint = connectedRelay.endpoint
+ connectionState.remotePort = connectedRelay.endpoint.ipv4Relay.port
return connectionState
case let .error(blockedState):
keyPolicy = blockedState.keyPolicy
lastKeyRotation = blockedState.lastKeyRotation
networkReachability = blockedState.networkReachability
+ fallthrough
+ case .initial:
+ let selectedRelays = try callRelaySelector(nil, 0)
+ let connectedRelay = selectedRelays.entry ?? selectedRelays.exit
+ return State.ConnectionData(
+ selectedRelays: selectedRelays,
+ relayConstraints: settings.relayConstraints,
+ currentKey: settings.privateKey,
+ keyPolicy: keyPolicy,
+ networkReachability: networkReachability,
+ connectionAttemptCount: 0,
+ lastKeyRotation: lastKeyRotation,
+ connectedEndpoint: connectedRelay.endpoint,
+ transportLayer: .udp,
+ remotePort: connectedRelay.endpoint.ipv4Relay.port,
+ isPostQuantum: settings.quantumResistance.isEnabled
+ )
case .disconnecting, .disconnected:
return nil
}
- let selectedRelays = try callRelaySelector(nil, 0)
- return State.ConnectionData(
- selectedRelays: selectedRelays,
- relayConstraints: settings.relayConstraints,
- currentKey: settings.privateKey,
- keyPolicy: keyPolicy,
- networkReachability: networkReachability,
- connectionAttemptCount: 0,
- lastKeyRotation: lastKeyRotation,
- connectedEndpoint: selectedRelays.exit.endpoint, // TODO: Multihop
- transportLayer: .udp,
- remotePort: selectedRelays.exit.endpoint.ipv4Relay.port, // TODO: Multihop
- isPostQuantum: settings.quantumResistance.isEnabled
- )
}
internal func activeKey(from state: State.ConnectionData, in settings: Settings) -> PrivateKey {
@@ -417,9 +444,9 @@ extension PacketTunnelActor {
else { return nil }
let obfuscatedEndpoint = protocolObfuscator.obfuscate(
- connectionState.selectedRelays.exit.endpoint, // TODO: Multihop
+ connectionState.connectedEndpoint,
settings: settings,
- retryAttempts: connectionState.selectedRelays.exit.retryAttempts // TODO: Multihop
+ retryAttempts: connectionState.selectedRelays.retryAttempt
)
let transportLayer = protocolObfuscator.transportLayer.map { $0 } ?? .udp
@@ -442,10 +469,10 @@ extension PacketTunnelActor {
Select next relay to connect to based on `NextRelays` and other input parameters.
- Parameters:
- - nextRelays: next relays to connect to.
- - relayConstraints: relay constraints.
- - currentRelays: currently selected relays.
- - connectionAttemptCount: number of failed connection attempts so far.
+ - nextRelays: next relays to connect to.
+ - relayConstraints: relay constraints.
+ - currentRelays: currently selected relays.
+ - connectionAttemptCount: number of failed connection attempts so far.
- Returns: selector result that contains the credentials of the next relays that the tunnel should connect to.
*/
diff --git a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
index fd731a32e1..2579a7de39 100644
--- a/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
+++ b/ios/PacketTunnelCore/Actor/PacketTunnelActorCommand.swift
@@ -53,7 +53,7 @@ extension PacketTunnelActor {
case .random:
return "reconnect(random, \(stopTunnelMonitor))"
case let .preSelected(selectedRelays):
- return "reconnect(\(selectedRelays.exit.hostname), \(stopTunnelMonitor))" // TODO: Multihop
+ return "reconnect(\(selectedRelays.exit.hostname)\(selectedRelays.entry.flatMap { " via \($0.hostname)" } ?? ""), \(stopTunnelMonitor))"
}
case let .error(reason):
return "error(\(reason))"
diff --git a/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift b/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
index ea798075d2..e07ba4664e 100644
--- a/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
+++ b/ios/PacketTunnelCore/Actor/Protocols/TunnelAdapterProtocol.swift
@@ -17,6 +17,12 @@ public protocol TunnelAdapterProtocol {
/// Start tunnel adapter or update active configuration.
func start(configuration: TunnelAdapterConfiguration) async throws
+ /// Start tunnel adapter or update active configuration.
+ func startMultihop(
+ entryConfiguration: TunnelAdapterConfiguration?,
+ exitConfiguration: TunnelAdapterConfiguration
+ ) async throws
+
/// Stop tunnel adapter with the given configuration.
func stop() async throws
}
diff --git a/ios/PacketTunnelCore/Actor/StartOptions.swift b/ios/PacketTunnelCore/Actor/StartOptions.swift
index 4c8ad75878..9af92fe34c 100644
--- a/ios/PacketTunnelCore/Actor/StartOptions.swift
+++ b/ios/PacketTunnelCore/Actor/StartOptions.swift
@@ -27,7 +27,8 @@ public struct StartOptions {
public func logFormat() -> String {
var s = "Start the tunnel via \(launchSource)"
if let selectedRelays {
- s += ", connect to \(selectedRelays.exit.hostname)" // TODO: Multihop
+ s += ", connect to \(selectedRelays.exit.hostname)"
+ s += selectedRelays.entry.flatMap { " via \($0.hostname)" } ?? ""
}
s += "."
return s
diff --git a/ios/PacketTunnelCore/Actor/State+Extensions.swift b/ios/PacketTunnelCore/Actor/State+Extensions.swift
index 69d6579a9d..f7d8cbfae7 100644
--- a/ios/PacketTunnelCore/Actor/State+Extensions.swift
+++ b/ios/PacketTunnelCore/Actor/State+Extensions.swift
@@ -47,20 +47,19 @@ extension State {
func logFormat() -> String {
switch self {
case let .connecting(connState), let .connected(connState), let .reconnecting(connState):
- let hostname = connState.selectedRelays.exit.hostname // TODO: Multihop
-
- return """
- \(name) to \(hostname), \
+ """
+ \(name) to \(connState.selectedRelays.entry.flatMap { "entry location: \($0.hostname) " } ?? ""),\
+ exit location: \(connState.selectedRelays.exit.hostname), \
key: \(connState.keyPolicy.logFormat()), \
net: \(connState.networkReachability), \
attempt: \(connState.connectionAttemptCount)
"""
case let .error(blockedState):
- return "\(name): \(blockedState.reason)"
+ "\(name): \(blockedState.reason)"
case .initial, .disconnecting, .disconnected, .negotiatingPostQuantumKey:
- return name
+ name
}
}
diff --git a/ios/PacketTunnelCore/Actor/State.swift b/ios/PacketTunnelCore/Actor/State.swift
index df82b11286..ee7e00ded3 100644
--- a/ios/PacketTunnelCore/Actor/State.swift
+++ b/ios/PacketTunnelCore/Actor/State.swift
@@ -141,12 +141,12 @@ extension State {
}
/// The actual endpoint fed to WireGuard, can be a local endpoint if obfuscation is used.
- public let connectedEndpoint: MullvadEndpoint
+ public var connectedEndpoint: MullvadEndpoint
/// Via which transport protocol was the connection made to the relay
public let transportLayer: TransportLayer
/// The remote port that was chosen to connect to `connectedEndpoint`
- public let remotePort: UInt16
+ public var remotePort: UInt16
/// True if post-quantum key exchange is enabled
public let isPostQuantum: Bool
diff --git a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
index 680b438f83..8e2a42fd88 100644
--- a/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
+++ b/ios/PacketTunnelCoreTests/AppMessageHandlerTests.swift
@@ -100,9 +100,9 @@ final class AppMessageHandlerTests: XCTestCase {
exit: SelectedRelay(
endpoint: match.endpoint,
hostname: match.relay.hostname,
- location: match.location,
- retryAttempts: 0
- )
+ location: match.location
+ ),
+ retryAttempt: 0
)
_ = try? await appMessageHandler.handleAppMessage(
diff --git a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
index 4b7040f756..79b75a002d 100644
--- a/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
+++ b/ios/PacketTunnelCoreTests/Mocks/TunnelAdapterDummy.swift
@@ -11,6 +11,11 @@ import PacketTunnelCore
/// Dummy tunnel adapter that does nothing and reports no errors.
class TunnelAdapterDummy: TunnelAdapterProtocol {
+ func startMultihop(
+ entryConfiguration: TunnelAdapterConfiguration?,
+ exitConfiguration: TunnelAdapterConfiguration
+ ) async throws {}
+
func start(configuration: TunnelAdapterConfiguration) async throws {}
func stop() async throws {}