From 741a5751c5ecee58a6ae197b3285eabc7bb5c015 Mon Sep 17 00:00:00 2001 From: mojganii Date: Wed, 25 Sep 2024 12:21:04 +0200 Subject: [PATCH] Add UI for encrypted dns proxy --- .../EncryptedDNS/EncryptedDNSTransport.swift | 36 +++++++++++++++++++ .../ProxyConfigurationTransportProvider.swift | 2 ++ .../Transport/TransportProvider.swift | 5 +++ .../Transport/TransportStrategy.swift | 17 ++++++--- .../TransportStrategyTests.swift | 20 +++++++++-- ios/MullvadSettings/AccessMethodKind.swift | 7 +++- .../AccessMethodRepository.swift | 28 +++++++++++++-- .../AccessMethodRepositoryProtocol.swift | 2 +- .../PersistentAccessMethod.swift | 3 ++ ios/MullvadVPN.xcodeproj/project.pbxproj | 18 +++++++++- ios/MullvadVPN/AppDelegate.swift | 2 +- ...ethodSettingsDataSourceConfiguration.swift | 4 +-- .../MethodSettingsItemIdentifier.swift | 2 +- .../APIAccess/Models/AccessMethodKind.swift | 11 ++++-- .../AccessMethodViewModel+Persistent.swift | 2 ++ .../APIAccessMethodsTests.swift | 15 ++------ 16 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift diff --git a/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift new file mode 100644 index 000000000000..e0b983d8532d --- /dev/null +++ b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift @@ -0,0 +1,36 @@ +// +// EncryptedDNSTransport.swift +// MullvadVPN +// +// Created by Mojgan on 2024-09-19. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// +import Foundation +import MullvadRustRuntime +import MullvadTypes + +public final class EncryptedDNSTransport: RESTTransport { + public var name: String { + "encrypted-dns-url-session" + } + + /// The `URLSession` used to send requests via `encryptedDNSProxy` + public let urlSession: URLSession + + public init( + urlSession: URLSession, + addressCache: REST.AddressCache + ) { + self.urlSession = urlSession + } + + public func sendRequest( + _ request: URLRequest, + completion: @escaping (Data?, URLResponse?, (any Error)?) -> Void + ) -> any Cancellable { + // TODO: Start proxy once the backend is integrated into the Swift code. + let dataTask = urlSession.dataTask(with: request, completionHandler: completion) + dataTask.resume() + return dataTask + } +} diff --git a/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift b/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift index 356b9c1b1953..b65edfa52adf 100644 --- a/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift +++ b/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift @@ -32,6 +32,8 @@ public class ProxyConfigurationTransportProvider { configuration: shadowsocksConfiguration, addressCache: addressCache ) + case .encryptedDNS: + return EncryptedDNSTransport(urlSession: urlSession, addressCache: addressCache) case let .shadowsocks(shadowSocksConfiguration): return ShadowsocksTransport( urlSession: urlSession, diff --git a/ios/MullvadREST/Transport/TransportProvider.swift b/ios/MullvadREST/Transport/TransportProvider.swift index 06aaf6395ce1..a102f61cd883 100644 --- a/ios/MullvadREST/Transport/TransportProvider.swift +++ b/ios/MullvadREST/Transport/TransportProvider.swift @@ -81,6 +81,11 @@ public final class TransportProvider: RESTTransportProvider { configuration: configuration, addressCache: addressCache ) + case .encryptedDNS: + currentTransport = EncryptedDNSTransport( + urlSession: urlSessionTransport.urlSession, + addressCache: addressCache + ) case .none: currentTransport = nil } diff --git a/ios/MullvadREST/Transport/TransportStrategy.swift b/ios/MullvadREST/Transport/TransportStrategy.swift index a1d029b2b6ef..394e7cac40b9 100644 --- a/ios/MullvadREST/Transport/TransportStrategy.swift +++ b/ios/MullvadREST/Transport/TransportStrategy.swift @@ -23,19 +23,24 @@ public struct TransportStrategy: Equatable { /// Connecting via socks proxy case socks5(configuration: Socks5Configuration) - /// Failing to retrive transport + /// Connecting via encrypted DNS proxy + case encryptedDNS + + /// Failing to retrieve transport case none public static func == (lhs: Self, rhs: Self) -> Bool { switch (lhs, rhs) { case(.direct, .direct), (.none, .none): - return true + true case let (.shadowsocks(lhsConfiguration), .shadowsocks(rhsConfiguration)): - return lhsConfiguration == rhsConfiguration + lhsConfiguration == rhsConfiguration case let (.socks5(lhsConfiguration), .socks5(rhsConfiguration)): - return lhsConfiguration == rhsConfiguration + lhsConfiguration == rhsConfiguration + case (.encryptedDNS, .encryptedDNS): + true default: - return false + false } } } @@ -70,6 +75,8 @@ public struct TransportStrategy: Equatable { switch configuration.proxyConfiguration { case .direct: return .direct + case .encryptedDNS: + return .encryptedDNS case .bridges: do { let configuration = try shadowsocksLoader.load() diff --git a/ios/MullvadRESTTests/TransportStrategyTests.swift b/ios/MullvadRESTTests/TransportStrategyTests.swift index b1875c87e529..4aa26c8606d3 100644 --- a/ios/MullvadRESTTests/TransportStrategyTests.swift +++ b/ios/MullvadRESTTests/TransportStrategyTests.swift @@ -15,6 +15,7 @@ import XCTest class TransportStrategyTests: XCTestCase { private var directAccess: PersistentAccessMethod! private var bridgeAccess: PersistentAccessMethod! + private var encryptedDNS: PersistentAccessMethod! private var shadowsocksLoader: ShadowsocksLoaderStub! @@ -41,15 +42,24 @@ class TransportStrategyTests: XCTestCase { isEnabled: true, proxyConfiguration: .bridges ) + + encryptedDNS = PersistentAccessMethod( + id: UUID(uuidString: "831CB1F8-1829-42DD-B9DC-82902F298EC0")!, + name: "Encrypted DNS proxy", + isEnabled: true, + proxyConfiguration: .encryptedDNS + ) } func testDefaultStrategyIsDirectWhenAllMethodsAreDisabled() throws { directAccess.isEnabled = false bridgeAccess.isEnabled = false + encryptedDNS.isEnabled = false let transportStrategy = TransportStrategy( datasource: AccessMethodRepositoryStub(accessMethods: [ directAccess, bridgeAccess, + encryptedDNS, ]), shadowsocksLoader: shadowsocksLoader ) @@ -61,10 +71,12 @@ class TransportStrategyTests: XCTestCase { func testReuseSameStrategyWhenEverythingElseIsDisabled() throws { directAccess.isEnabled = false + encryptedDNS.isEnabled = false let transportStrategy = TransportStrategy( datasource: AccessMethodRepositoryStub(accessMethods: [ directAccess, bridgeAccess, + encryptedDNS, ]), shadowsocksLoader: shadowsocksLoader ) @@ -84,6 +96,7 @@ class TransportStrategyTests: XCTestCase { datasource: AccessMethodRepositoryStub(accessMethods: [ directAccess, bridgeAccess, + encryptedDNS, PersistentAccessMethod( id: UUID(uuidString: "8586E75A-CA7B-4432-B70D-EE65F3F95090")!, name: "", @@ -98,7 +111,7 @@ class TransportStrategyTests: XCTestCase { ]), shadowsocksLoader: shadowsocksLoader ) - let accessMethodsCount = 3 + let accessMethodsCount = 4 for i in 0 ..< (accessMethodsCount * 2) { let previousOne = transportStrategy.connectionTransport() transportStrategy.didFail() @@ -113,10 +126,12 @@ class TransportStrategyTests: XCTestCase { func testUsesNextWhenItIsNotReachable() { bridgeAccess.isEnabled = false + encryptedDNS.isEnabled = false let transportStrategy = TransportStrategy( datasource: AccessMethodRepositoryStub(accessMethods: [ directAccess, bridgeAccess, + encryptedDNS, PersistentAccessMethod( id: UUID(uuidString: "8586E75A-CA7B-4432-B70D-EE65F3F95090")!, name: "", @@ -150,12 +165,13 @@ class TransportStrategyTests: XCTestCase { datasource: AccessMethodRepositoryStub(accessMethods: [ directAccess, bridgeAccess, + encryptedDNS, ]), shadowsocksLoader: shadowsocksLoader ) transportStrategy.didFail() - XCTAssertEqual(transportStrategy.connectionTransport(), .direct) + XCTAssertEqual(transportStrategy.connectionTransport(), .encryptedDNS) } func testNoLoopOnFailureAtLoadingConfigurationWhenBridgeIsOnlyEnabled() { diff --git a/ios/MullvadSettings/AccessMethodKind.swift b/ios/MullvadSettings/AccessMethodKind.swift index 7f8ab380dce5..7f59ab00c34f 100644 --- a/ios/MullvadSettings/AccessMethodKind.swift +++ b/ios/MullvadSettings/AccessMethodKind.swift @@ -16,6 +16,9 @@ public enum AccessMethodKind: Equatable, Hashable, CaseIterable { /// Communication over bridges. case bridges + /// Communication over proxy address from a DNS. + case encryptedDNS + /// Communication over shadowsocks. case shadowsocks @@ -27,7 +30,7 @@ public extension AccessMethodKind { /// Returns `true` if the method is permanent and cannot be deleted. var isPermanent: Bool { switch self { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: true case .shadowsocks, .socks5: false @@ -48,6 +51,8 @@ extension PersistentAccessMethod { .direct case .bridges: .bridges + case .encryptedDNS: + .encryptedDNS case .shadowsocks: .shadowsocks case .socks5: diff --git a/ios/MullvadSettings/AccessMethodRepository.swift b/ios/MullvadSettings/AccessMethodRepository.swift index a1193f917b92..1ffa3c68e152 100644 --- a/ios/MullvadSettings/AccessMethodRepository.swift +++ b/ios/MullvadSettings/AccessMethodRepository.swift @@ -28,6 +28,13 @@ public class AccessMethodRepository: AccessMethodRepositoryProtocol { proxyConfiguration: .bridges ) + private let encryptedDNS = PersistentAccessMethod( + id: UUID(uuidString: "831CB1F8-1829-42DD-B9DC-82902F298EC0")!, + name: "Encrypted DNS proxy", + isEnabled: true, + proxyConfiguration: .encryptedDNS + ) + private let accessMethodsSubject: CurrentValueSubject<[PersistentAccessMethod], Never> public var accessMethodsPublisher: AnyPublisher<[PersistentAccessMethod], Never> { accessMethodsSubject.eraseToAnyPublisher() @@ -46,7 +53,7 @@ public class AccessMethodRepository: AccessMethodRepositoryProtocol { accessMethodsSubject = CurrentValueSubject([]) lastReachableAccessMethodSubject = CurrentValueSubject(direct) - add([direct, bridge]) + addDefaultsMethods() accessMethodsSubject.send(fetchAll()) lastReachableAccessMethodSubject.send(fetchLastReachable()) @@ -107,15 +114,30 @@ public class AccessMethodRepository: AccessMethodRepositoryProtocol { } public func fetchAll() -> [PersistentAccessMethod] { + #if DEBUG readApiAccessMethodStore().accessMethods + #else + readApiAccessMethodStore().accessMethods.filter { $0.id != encryptedDNS.id } + #endif } public func fetchLastReachable() -> PersistentAccessMethod { readApiAccessMethodStore().lastReachableAccessMethod } - public func reloadWithDefaultsAfterDataRemoval() { - add([direct, bridge]) + public func addDefaultsMethods() { + #if DEBUG + add([ + direct, + bridge, + encryptedDNS, + ]) + #else + add([ + direct, + bridge, + ]) + #endif } private func add(_ methods: [PersistentAccessMethod]) { diff --git a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift index 02e0fa71f951..0239919f4cd5 100644 --- a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift +++ b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift @@ -44,5 +44,5 @@ public protocol AccessMethodRepositoryProtocol: AccessMethodRepositoryDataSource func fetch(by id: UUID) -> PersistentAccessMethod? /// Refreshes the storage with default values. - func reloadWithDefaultsAfterDataRemoval() + func addDefaultsMethods() } diff --git a/ios/MullvadSettings/PersistentAccessMethod.swift b/ios/MullvadSettings/PersistentAccessMethod.swift index 2194167a8e27..49c9f9c6681d 100644 --- a/ios/MullvadSettings/PersistentAccessMethod.swift +++ b/ios/MullvadSettings/PersistentAccessMethod.swift @@ -66,6 +66,9 @@ public enum PersistentProxyConfiguration: Codable { /// Communication over bridges. case bridges + /// Communication over proxy address from a DNS. + case encryptedDNS + /// Communication over shadowsocks. case shadowsocks(ShadowsocksConfiguration) diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index f939a417806e..f8f9d646bda8 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -970,6 +970,7 @@ 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 */; }; + F0E5B2F82C9C68CF0007F78C /* EncryptedDNSTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0E5B2F72C9C68CD0007F78C /* EncryptedDNSTransport.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 */; }; @@ -2155,6 +2156,7 @@ F0DDE4282B220A15006B57A7 /* RelaySelector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = ""; }; F0DDE4292B220A15006B57A7 /* Midpoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Midpoint.swift; sourceTree = ""; }; F0E3618A2A4ADD2F00AEEF2B /* WelcomeContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeContentView.swift; sourceTree = ""; }; + F0E5B2F72C9C68CD0007F78C /* EncryptedDNSTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedDNSTransport.swift; sourceTree = ""; }; F0E61CA82BF2911D000C4A95 /* TunnelSettingsV5.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV5.swift; sourceTree = ""; }; F0E61CA92BF2911D000C4A95 /* MultihopSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultihopSettings.swift; sourceTree = ""; }; F0E8CC022A4C753B007ED3B4 /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = ""; }; @@ -4176,8 +4178,9 @@ isa = PBXGroup; children = ( F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */, - A932D9EE2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift */, F0DC77A32B2315800087F09D /* Direct */, + F0E5B2F62C9C689C0007F78C /* EncryptedDNS */, + A932D9EE2B5ADD0700999395 /* ProxyConfigurationTransportProvider.swift */, 06FAE67D28F83CA50033DD93 /* RESTTransport.swift */, 58E7BA182A975DF70068EC3A /* RESTTransportProvider.swift */, F0DC77A22B2314EF0087F09D /* Shadowsocks */, @@ -4228,6 +4231,14 @@ path = Welcome; sourceTree = ""; }; + F0E5B2F62C9C689C0007F78C /* EncryptedDNS */ = { + isa = PBXGroup; + children = ( + F0E5B2F72C9C68CD0007F78C /* EncryptedDNSTransport.swift */, + ); + path = EncryptedDNS; + sourceTree = ""; + }; F0E8CC082A4EE0DC007ED3B4 /* Completed */ = { isa = PBXGroup; children = ( @@ -5200,6 +5211,7 @@ 06799ADB28F98E4800ACD94E /* RESTProxyFactory.swift in Sources */, F0DDE4182B220458006B57A7 /* ShadowsocksConfiguration.swift in Sources */, 7AA7046A2C8EFE2B0045699D /* StoredRelays.swift in Sources */, + F0E5B2F82C9C68CF0007F78C /* EncryptedDNSTransport.swift in Sources */, 06799AF228F98E4800ACD94E /* RESTAccessTokenManager.swift in Sources */, A90763B12B2857D50045ADF0 /* Socks5Endpoint.swift in Sources */, 06799AF328F98E4800ACD94E /* RESTAuthenticationProxy.swift in Sources */, @@ -7183,6 +7195,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin"; @@ -7195,6 +7208,7 @@ baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin"; @@ -7551,6 +7565,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_OPTIMIZATION_LEVEL = 0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin"; @@ -8388,6 +8403,7 @@ baseConfigurationReference = 5808273928487E3E006B77A4 /* Base.xcconfig */; buildSettings = { DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ""; PATH = "${PATH}:/opt/homebrew/opt/go@1.19/bin"; diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 48314ee2af15..55d6153fee54 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -537,7 +537,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD try? SettingsManager.writeSettings(LatestTunnelSettings()) // Default access methods need to be repopulated again after settings wipe. - self.accessMethodRepository.reloadWithDefaultsAfterDataRemoval() + self.accessMethodRepository.addDefaultsMethods() // At app startup, the relay cache tracker will get populated with a list of overriden IPs. // The overriden IPs will get wiped, therefore, the cache needs to be pruned as well. try? self.relayCacheTracker.refreshCachedRelays() diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift index c0f1e750d0b2..7e88cfc1c55e 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsDataSourceConfiguration.swift @@ -49,7 +49,7 @@ class MethodSettingsDataSourceConfiguration { } switch newValue.method { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: break case .shadowsocks: @@ -106,7 +106,7 @@ class MethodSettingsDataSourceConfiguration { } let itemsToReload: [MethodSettingsItemIdentifier] = switch viewModel.method { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: [] case .shadowsocks: MethodSettingsItemIdentifier.allShadowsocksItems diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsItemIdentifier.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsItemIdentifier.swift index b24b94ad3240..87e0e2e45d55 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsItemIdentifier.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/MethodSettings/MethodSettingsItemIdentifier.swift @@ -87,7 +87,7 @@ enum MethodSettingsItemIdentifier: Hashable { selectedMethod: AccessMethodKind ) -> [MethodSettingsItemIdentifier] { switch selectedMethod { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: [] case .shadowsocks: errors.compactMap { error in diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift index 35fe1fd43ce3..559b3fce040f 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift @@ -20,13 +20,16 @@ enum AccessMethodKind: Equatable, Hashable, CaseIterable { /// Communication over shadowsocks. case shadowsocks + /// Communication over proxy address from a DNS. + case encryptedDNS + /// Communication over socks v5 proxy. case socks5 /// Returns `true` if the method is permanent and cannot be deleted. var isPermanent: Bool { switch self { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: true case .shadowsocks, .socks5: false @@ -41,7 +44,7 @@ enum AccessMethodKind: Equatable, Hashable, CaseIterable { /// Returns localized description describing the access method. var localizedDescription: String { switch self { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: "" case .shadowsocks: NSLocalizedString("SHADOWSOCKS", tableName: "APIAccess", value: "Shadowsocks", comment: "") @@ -54,7 +57,7 @@ enum AccessMethodKind: Equatable, Hashable, CaseIterable { /// Methods that aren't configurable do not offer any additional configuration. var hasProxyConfiguration: Bool { switch self { - case .direct, .bridges: + case .direct, .bridges, .encryptedDNS: false case .shadowsocks, .socks5: true @@ -68,6 +71,8 @@ extension PersistentAccessMethod { switch proxyConfiguration { case .direct: .direct + case .encryptedDNS: + .encryptedDNS case .bridges: .bridges case .shadowsocks: diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel+Persistent.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel+Persistent.swift index 0d8182b5afb6..4ad1cea25b71 100644 --- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel+Persistent.swift +++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodViewModel+Persistent.swift @@ -58,6 +58,8 @@ extension AccessMethodViewModel { .direct case .bridges: .bridges + case .encryptedDNS: + .encryptedDNS case .socks5: try socks.intoPersistentProxyConfiguration() case .shadowsocks: diff --git a/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift b/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift index 4ed3bdc1a39e..1f3869b9bd08 100644 --- a/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift +++ b/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift @@ -32,16 +32,7 @@ final class APIAccessMethodsTests: XCTestCase { let repository = AccessMethodRepository() let storedMethods = repository.fetchAll() - let hasDirectMethod = storedMethods.contains { method in - method.kind == .direct - } - - let hasBridgesMethod = storedMethods.contains { method in - method.kind == .bridges - } - - XCTAssertEqual(storedMethods.count, 2) - XCTAssertTrue(hasDirectMethod && hasBridgesMethod) + XCTAssertEqual(storedMethods.filter { $0.isEnabled && $0.kind.isPermanent }.count, 3) } func testAddingSocks5AccessMethod() throws { @@ -78,8 +69,8 @@ final class APIAccessMethodsTests: XCTestCase { let storedMethods = repository.fetchAll() - // Account for .direct and .bridges that are always added by default. - XCTAssertEqual(storedMethods.count, 3) + // Account for .direct, .bridges and .encryptedDNS that are always added by default. + XCTAssertEqual(storedMethods.count, 4) } func testUpdatingAccessMethod() throws {