Skip to content

Commit

Permalink
Upgrade settings to associate with multi-hop
Browse files Browse the repository at this point in the history
  • Loading branch information
mojganii committed May 15, 2024
1 parent b09ebf9 commit 27ea6e3
Show file tree
Hide file tree
Showing 20 changed files with 471 additions and 344 deletions.
15 changes: 15 additions & 0 deletions ios/MullvadREST/Relay/NoRelaysSatisfyingConstraintsError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// NoRelaysSatisfyingConstraintsError.swift
// MullvadREST
//
// Created by Mojgan on 2024-04-26.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation

public struct NoRelaysSatisfyingConstraintsError: LocalizedError {
public var errorDescription: String? {
"No relays satisfying constraints."
}
}
452 changes: 243 additions & 209 deletions ios/MullvadREST/Relay/RelaySelector.swift

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions ios/MullvadREST/Relay/RelaySelectorResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// RelaySelectorResult.swift
// MullvadREST
//
// Created by Mojgan on 2024-05-14.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes

public typealias RelaySelectorResult = RelaySelectorMatch

public struct RelaySelectorMatch: Codable, Equatable {
public var endpoint: MullvadEndpoint
public var relay: REST.ServerRelay
public var location: Location
}
4 changes: 2 additions & 2 deletions ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ public class ShadowsocksLoader: ShadowsocksLoaderProtocol {
/// Returns a randomly selected shadowsocks configuration.
private func create() throws -> ShadowsocksConfiguration {
let cachedRelays = try relayCache.read()
let bridgeConfiguration = RelaySelector.shadowsocksTCPBridge(from: cachedRelays.relays)
let closestRelay = RelaySelector.closestShadowsocksRelayConstrained(
let bridgeConfiguration = RelaySelector.Shadowsocks.tcpBridge(from: cachedRelays.relays)
let closestRelay = RelaySelector.Shadowsocks.closestRelayConstrained(
by: relayConstraints,
in: cachedRelays.relays
)
Expand Down
15 changes: 15 additions & 0 deletions ios/MullvadSettings/MultihopSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// MultihopSettings.swift
// MullvadSettings
//
// Created by Mojgan on 2024-04-26.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation

/// Whether Multi-hop is enabled
public enum MultihopState: Codable {
case on
case off
}
12 changes: 9 additions & 3 deletions ios/MullvadSettings/TunnelSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

/// Alias to the latest version of the `TunnelSettings`.
public typealias LatestTunnelSettings = TunnelSettingsV4
public typealias LatestTunnelSettings = TunnelSettingsV5

/// Protocol all TunnelSettings must adhere to, for upgrade purposes.
public protocol TunnelSettings: Codable {
Expand All @@ -27,14 +27,19 @@ public enum SchemaVersion: Int, Equatable {
/// V2 format with WireGuard obfuscation options, stored as `TunnelSettingsV3`.
case v3 = 3

/// V3 format with post quantum options, stored as `TunnelSettingsV4`.
case v4 = 4

/// V4 format with multi-hop options, stored as `TunnelSettingsV5`.
case v5 = 5

var settingsType: any TunnelSettings.Type {
switch self {
case .v1: return TunnelSettingsV1.self
case .v2: return TunnelSettingsV2.self
case .v3: return TunnelSettingsV3.self
case .v4: return TunnelSettingsV4.self
case .v5: return TunnelSettingsV5.self
}
}

Expand All @@ -43,10 +48,11 @@ public enum SchemaVersion: Int, Equatable {
case .v1: return .v2
case .v2: return .v3
case .v3: return .v4
case .v4: return .v4
case .v4: return .v5
case .v5: return .v5
}
}

/// Current schema version.
public static let current = SchemaVersion.v4
public static let current = SchemaVersion.v5
}
8 changes: 7 additions & 1 deletion ios/MullvadSettings/TunnelSettingsV4.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public struct TunnelSettingsV4: Codable, Equatable, TunnelSettings {
}

public func upgradeToNextVersion() -> any TunnelSettings {
self
TunnelSettingsV5(
relayConstraints: relayConstraints,
dnsSettings: dnsSettings,
wireGuardObfuscation: wireGuardObfuscation,
tunnelQuantumResistance: tunnelQuantumResistance,
tunnelMultihopState: .off
)
}
}
46 changes: 46 additions & 0 deletions ios/MullvadSettings/TunnelSettingsV5.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// TunnelSettingsV5.swift
// MullvadSettings
//
// Created by Mojgan on 2024-05-13.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes

public struct TunnelSettingsV5: Codable, Equatable, TunnelSettings {
/// Relay constraints.
public var relayConstraints: RelayConstraints

/// DNS settings.
public var dnsSettings: DNSSettings

/// WireGuard obfuscation settings
public var wireGuardObfuscation: WireGuardObfuscationSettings

/// Whether Post Quantum exchanges are enabled.
public var tunnelQuantumResistance: TunnelQuantumResistance

/// Whether Multi-hop is enabled.
public var tunnelMultihopState: MultihopState

public init(
relayConstraints: RelayConstraints = RelayConstraints(),
dnsSettings: DNSSettings = DNSSettings(),
wireGuardObfuscation: WireGuardObfuscationSettings = WireGuardObfuscationSettings(),
tunnelQuantumResistance: TunnelQuantumResistance = .automatic,
tunnelMultihopState: MultihopState = .off

) {
self.relayConstraints = relayConstraints
self.dnsSettings = dnsSettings
self.wireGuardObfuscation = wireGuardObfuscation
self.tunnelQuantumResistance = tunnelQuantumResistance
self.tunnelMultihopState = tunnelMultihopState
}

public func upgradeToNextVersion() -> any TunnelSettings {
self
}
}
41 changes: 27 additions & 14 deletions ios/MullvadTypes/RelayConstraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,31 @@ public class RelayConstraintsUpdater: ConstraintsPropagation {
}
}

public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible {
public struct RelayConstraints: Codable, Equatable {
@available(*, deprecated, renamed: "locations")
private var location: RelayConstraint<RelayLocation> = .only(.country("se"))

// Added in 2023.3
public var port: RelayConstraint<UInt16>
public var filter: RelayConstraint<RelayFilter>

// Added in 2024.1
// Changed from RelayLocations to UserSelectedRelays in 2024.3
public var locations: RelayConstraint<UserSelectedRelays>
@available(*, deprecated, renamed: "exitLocations")
private var locations: RelayConstraint<UserSelectedRelays> = .only(UserSelectedRelays(locations: [.country("se")]))

public var debugDescription: String {
"RelayConstraints { locations: \(locations), port: \(port), filter: \(filter) }"
}
// Added in 2024.5 to support multi-hop
public var entryLocations: RelayConstraint<UserSelectedRelays>?
public var exitLocations: RelayConstraint<UserSelectedRelays>

// Added in 2023.3
public var port: RelayConstraint<UInt16>
public var filter: RelayConstraint<RelayFilter>

public init(
locations: RelayConstraint<UserSelectedRelays> = .only(UserSelectedRelays(locations: [.country("se")])),
entryLocations: RelayConstraint<UserSelectedRelays>? = nil,
exitLocations: RelayConstraint<UserSelectedRelays> = .only(UserSelectedRelays(locations: [.country("se")])),
port: RelayConstraint<UInt16> = .any,
filter: RelayConstraint<RelayFilter> = .any
) {
self.locations = locations
self.entryLocations = entryLocations
self.exitLocations = exitLocations
self.port = port
self.filter = filter
}
Expand All @@ -53,9 +56,19 @@ public struct RelayConstraints: Codable, Equatable, CustomDebugStringConvertible
port = try container.decodeIfPresent(RelayConstraint<UInt16>.self, forKey: .port) ?? .any
filter = try container.decodeIfPresent(RelayConstraint<RelayFilter>.self, forKey: .filter) ?? .any

// Added in 2024.1
locations = try container.decodeIfPresent(RelayConstraint<UserSelectedRelays>.self, forKey: .locations)
?? Self.migrateRelayLocation(decoder: decoder)
// Added in 2024.5
entryLocations = try container.decodeIfPresent(
RelayConstraint<UserSelectedRelays>.self,
forKey: .entryLocations
) ?? nil

exitLocations = try container
.decodeIfPresent(RelayConstraint<UserSelectedRelays>.self, forKey: .exitLocations) ??
container.decodeIfPresent(
RelayConstraint<UserSelectedRelays>.self,
forKey: .locations
) ??
Self.migrateRelayLocation(decoder: decoder)
?? .only(UserSelectedRelays(locations: [.country("se")]))
}
}
Expand Down
22 changes: 19 additions & 3 deletions ios/MullvadVPN.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,8 @@
F0DDE42B2B220A15006B57A7 /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4282B220A15006B57A7 /* RelaySelector.swift */; };
F0DDE42C2B220A15006B57A7 /* Midpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4292B220A15006B57A7 /* Midpoint.swift */; };
F0E3618B2A4ADD2F00AEEF2B /* WelcomeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E3618A2A4ADD2F00AEEF2B /* WelcomeContentView.swift */; };
F0E61CAA2BF2911D000C4A95 /* TunnelSettingsV5.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E61CA82BF2911D000C4A95 /* TunnelSettingsV5.swift */; };
F0E61CAB2BF2911D000C4A95 /* MultihopSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E61CA92BF2911D000C4A95 /* MultihopSettings.swift */; };
F0E8CC032A4C753B007ED3B4 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8CC022A4C753B007ED3B4 /* WelcomeViewController.swift */; };
F0E8CC0A2A4EE127007ED3B4 /* SetupAccountCompletedContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8CC092A4EE127007ED3B4 /* SetupAccountCompletedContentView.swift */; };
F0E8CC0C2A4EE672007ED3B4 /* SetupAccountCompletedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8CC0B2A4EE672007ED3B4 /* SetupAccountCompletedController.swift */; };
Expand All @@ -923,6 +925,8 @@
F0E8E4C92A604E7400ED26A3 /* AccountDeletionInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E8E4C82A604E7400ED26A3 /* AccountDeletionInteractor.swift */; };
F0EF50D32A8FA47E0031E8DF /* ChangeLogInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EF50D22A8FA47E0031E8DF /* ChangeLogInteractor.swift */; };
F0EF50D52A949F8E0031E8DF /* ChangeLogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */; };
F0F316192BF3572B0078DBCF /* RelaySelectorResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */; };
F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */; };
F0FADDEA2BE90AAA000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
F0FADDEB2BE90AAE000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
F0FADDEC2BE90AB0000D0B02 /* LaunchArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */; };
Expand Down Expand Up @@ -2097,6 +2101,8 @@
F0DDE4282B220A15006B57A7 /* RelaySelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; };
F0DDE4292B220A15006B57A7 /* Midpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Midpoint.swift; sourceTree = "<group>"; };
F0E3618A2A4ADD2F00AEEF2B /* WelcomeContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentView.swift; sourceTree = "<group>"; };
F0E61CA82BF2911D000C4A95 /* TunnelSettingsV5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV5.swift; sourceTree = "<group>"; };
F0E61CA92BF2911D000C4A95 /* MultihopSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultihopSettings.swift; sourceTree = "<group>"; };
F0E8CC022A4C753B007ED3B4 /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
F0E8CC092A4EE127007ED3B4 /* SetupAccountCompletedContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupAccountCompletedContentView.swift; sourceTree = "<group>"; };
F0E8CC0B2A4EE672007ED3B4 /* SetupAccountCompletedController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupAccountCompletedController.swift; sourceTree = "<group>"; };
Expand All @@ -2108,6 +2114,8 @@
F0EF50D22A8FA47E0031E8DF /* ChangeLogInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChangeLogInteractor.swift; sourceTree = "<group>"; };
F0EF50D42A949F8E0031E8DF /* ChangeLogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogViewModel.swift; sourceTree = "<group>"; };
F0F1EF8C2BE8FF0A00CED01D /* LaunchArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchArguments.swift; sourceTree = "<group>"; };
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelectorResult.swift; sourceTree = "<group>"; };
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoRelaysSatisfyingConstraintsError.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -2582,13 +2590,13 @@
5820676326E771DB00655B05 /* TunnelManagerErrors.swift */,
5823FA5326CE49F600283BF8 /* TunnelObserver.swift */,
58B93A1226C3F13600A55733 /* TunnelState.swift */,
A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */,
5803B4B12940A48700C23744 /* TunnelStore.swift */,
A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */,
5842102F282D8A3C00F24E46 /* UpdateAccountDataOperation.swift */,
58421031282E42B000F24E46 /* UpdateDeviceDataOperation.swift */,
A9F360332AAB626300F53531 /* VPNConnectionProtocol.swift */,
581DA2742A1E283E0046ED47 /* WgKeyRotation.swift */,
A9E031762ACB08950095D843 /* UIApplication+Extensions.swift */,
A9E0317D2ACC32920095D843 /* TunnelStatusBlockObserver.swift */,
);
path = TunnelManager;
sourceTree = "<group>";
Expand Down Expand Up @@ -3231,6 +3239,7 @@
068CE5732927B7A400A068BB /* Migration.swift */,
A9D96B192A8247C100A5C673 /* MigrationManager.swift */,
58B2FDD52AA71D2A003EB5C6 /* MullvadSettings.h */,
F0E61CA92BF2911D000C4A95 /* MultihopSettings.swift */,
586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */,
44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */,
58FF2C02281BDE02009EF542 /* SettingsManager.swift */,
Expand All @@ -3241,11 +3250,12 @@
A92ECC272A7802AB0052F1B1 /* StoredDeviceData.swift */,
A97D30162AE6B5E90045C0E4 /* StoredWgKeyData.swift */,
A92ECC202A77FFAF0052F1B1 /* TunnelSettings.swift */,
449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */,
587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */,
580F8B8228197881002E0998 /* TunnelSettingsV2.swift */,
A988DF282ADE880300D807EF /* TunnelSettingsV3.swift */,
A93181A02B727ED700E341D2 /* TunnelSettingsV4.swift */,
449872E02B7BBC5400094DDC /* TunnelSettingsUpdate.swift */,
F0E61CA82BF2911D000C4A95 /* TunnelSettingsV5.swift */,
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */,
);
path = MullvadSettings;
Expand Down Expand Up @@ -4003,8 +4013,10 @@
F0DDE4272B220A15006B57A7 /* Haversine.swift */,
7A516C392B7111A700BBD33D /* IPOverrideWrapper.swift */,
F0DDE4292B220A15006B57A7 /* Midpoint.swift */,
F0F3161A2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift */,
5820675A26E6576800655B05 /* RelayCache.swift */,
F0DDE4282B220A15006B57A7 /* RelaySelector.swift */,
F0F316182BF3572B0078DBCF /* RelaySelectorResult.swift */,
);
path = Relay;
sourceTree = "<group>";
Expand Down Expand Up @@ -5023,6 +5035,7 @@
buildActionMask = 2147483647;
files = (
F05F39982B21C73C006E60A7 /* CachedRelays.swift in Sources */,
F0F316192BF3572B0078DBCF /* RelaySelectorResult.swift in Sources */,
F05F39972B21C735006E60A7 /* RelayCache.swift in Sources */,
A932D9EF2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift in Sources */,
06799AE728F98E4800ACD94E /* RESTURLSession.swift in Sources */,
Expand Down Expand Up @@ -5073,6 +5086,7 @@
F0164ED12B4F2DCB0020268D /* AccessMethodIterator.swift in Sources */,
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */,
A90763BB2B2857D50045ADF0 /* Socks5AddressType.swift in Sources */,
F0F3161B2BF358590078DBCF /* NoRelaysSatisfyingConstraintsError.swift in Sources */,
06799AE028F98E4800ACD94E /* RESTCoding.swift in Sources */,
A90763B72B2857D50045ADF0 /* Socks5DataStreamHandler.swift in Sources */,
A90763B22B2857D50045ADF0 /* Socks5EndpointReader.swift in Sources */,
Expand Down Expand Up @@ -5275,6 +5289,7 @@
buildActionMask = 2147483647;
files = (
F050AE582B7376C6003F4EDB /* CustomListRepository.swift in Sources */,
F0E61CAA2BF2911D000C4A95 /* TunnelSettingsV5.swift in Sources */,
7A5869BD2B56EF7300640D27 /* IPOverride.swift in Sources */,
58B2FDEE2AA72098003EB5C6 /* ApplicationConfiguration.swift in Sources */,
F050AE572B7376C6003F4EDB /* CustomListRepositoryProtocol.swift in Sources */,
Expand Down Expand Up @@ -5305,6 +5320,7 @@
58B2FDE22AA71D5C003EB5C6 /* StoredAccountData.swift in Sources */,
F0D7FF902B31E00B00E0FDE5 /* AccessMethodKind.swift in Sources */,
7A5869BC2B56EF3400640D27 /* IPOverrideRepository.swift in Sources */,
F0E61CAB2BF2911D000C4A95 /* MultihopSettings.swift in Sources */,
58B2FDE82AA71D5C003EB5C6 /* KeychainSettingsStore.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,29 +78,30 @@ class ListCustomListCoordinator: Coordinator, Presentable, Presenting {
private func updateRelayConstraints(for action: EditCustomListCoordinator.FinishAction, in list: CustomList) {
var relayConstraints = tunnelManager.settings.relayConstraints

guard let customListSelection = relayConstraints.locations.value?.customListSelection,
guard let customListSelection = relayConstraints.exitLocations.value?.customListSelection,
customListSelection.listId == list.id
else { return }

switch action {
case .save:
// TODO: - Add entry locations
if customListSelection.isList {
let selectedRelays = UserSelectedRelays(
locations: list.locations,
customListSelection: UserSelectedRelays.CustomListSelection(listId: list.id, isList: true)
)
relayConstraints.locations = .only(selectedRelays)
relayConstraints.exitLocations = .only(selectedRelays)
} else {
let selectedConstraintIsRemovedFromList = list.locations.filter {
relayConstraints.locations.value?.locations.contains($0) ?? false
relayConstraints.exitLocations.value?.locations.contains($0) ?? false
}.isEmpty

if selectedConstraintIsRemovedFromList {
relayConstraints.locations = .only(UserSelectedRelays(locations: []))
relayConstraints.exitLocations = .only(UserSelectedRelays(locations: []))
}
}
case .delete:
relayConstraints.locations = .only(UserSelectedRelays(locations: []))
relayConstraints.exitLocations = .only(UserSelectedRelays(locations: []))
}

tunnelManager.updateSettings([.relayConstraints(relayConstraints)]) { [weak self] in
Expand Down
Loading

0 comments on commit 27ea6e3

Please sign in to comment.