diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index 75dfa457c142..c3cc16472d7d 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -21,9 +21,13 @@ Line wrap the file at 100 chars. Th * **Fixed**: for any bug fixes. * **Security**: in case of vulnerabilities. +## Unreleased +### Added +- Add a new access method that uses the encrypted DNS proxy to reach our API. + ## [2024.7 - 2024-09-16] ### Added -- Add DAITA (Defence against AI-guided Traffic Analysis) setting +- Add DAITA (Defence against AI-guided Traffic Analysis) setting. ## [2024.6 - 2024.09-02] ### Fixed diff --git a/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift index e0b983d8532d..896b63530d3b 100644 --- a/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift +++ b/ios/MullvadREST/Transport/EncryptedDNS/EncryptedDNSTransport.swift @@ -16,21 +16,70 @@ public final class EncryptedDNSTransport: RESTTransport { /// The `URLSession` used to send requests via `encryptedDNSProxy` public let urlSession: URLSession + private let encryptedDnsProxy: EncryptedDNSProxy + private let dispatchQueue = DispatchQueue(label: "net.mullvad.EncryptedDNSTransport") + private var dnsProxyTask: URLSessionTask! - public init( - urlSession: URLSession, - addressCache: REST.AddressCache - ) { + public init(urlSession: URLSession) { self.urlSession = urlSession + self.encryptedDnsProxy = EncryptedDNSProxy() } + public func stop() { + dispatchQueue.async { [weak self] in + self?.encryptedDnsProxy.stop() + self?.dnsProxyTask = nil + } + } + + /// Sends a request via the encrypted DNS proxy. + /// + /// Starting the proxy can take a very long time due to domain resolution + /// Cancellation will only take place if the data task was already started, at which point, + /// most of the time starting the DNS proxy was already spent. 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 + dispatchQueue.async { [weak self] in + guard let self else { return } + do { + try self.encryptedDnsProxy.start() + } catch { + completion(nil, nil, error) + return + } + + var urlRequestCopy = request + urlRequestCopy.url = request.url.flatMap { url in + var components = URLComponents(url: url, resolvingAgainstBaseURL: false) + components?.host = "127.0.0.1" + components?.port = Int(self.encryptedDnsProxy.localPort()) + return components?.url + } + + let wrappedCompletionHandler: (Data?, URLResponse?, (any Error)?) + -> Void = { [weak self] data, response, maybeError in + if maybeError != nil { + self?.encryptedDnsProxy.stop() + } + // Do not call the completion handler if the request was cancelled in flight + if let cancelledError = maybeError as? URLError, cancelledError.code == .cancelled { + return + } + + completion(data, response, maybeError) + } + + let dataTask = urlSession.dataTask(with: urlRequestCopy, completionHandler: wrappedCompletionHandler) + dataTask.resume() + dnsProxyTask = dataTask + } + + return AnyCancellable { [weak self] in + self?.dispatchQueue.async { + self?.dnsProxyTask.cancel() + } + } } } diff --git a/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift b/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift index b65edfa52adf..57605d7af75d 100644 --- a/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift +++ b/ios/MullvadREST/Transport/ProxyConfigurationTransportProvider.swift @@ -33,7 +33,7 @@ public class ProxyConfigurationTransportProvider { addressCache: addressCache ) case .encryptedDNS: - return EncryptedDNSTransport(urlSession: urlSession, addressCache: addressCache) + return EncryptedDNSTransport(urlSession: urlSession) case let .shadowsocks(shadowSocksConfiguration): return ShadowsocksTransport( urlSession: urlSession, diff --git a/ios/MullvadREST/Transport/TransportProvider.swift b/ios/MullvadREST/Transport/TransportProvider.swift index a102f61cd883..89394041536d 100644 --- a/ios/MullvadREST/Transport/TransportProvider.swift +++ b/ios/MullvadREST/Transport/TransportProvider.swift @@ -17,6 +17,7 @@ public final class TransportProvider: RESTTransportProvider { private var currentTransport: RESTTransport? private var currentTransportType: TransportStrategy.Transport private let parallelRequestsMutex = NSLock() + private let encryptedDNSTransport: EncryptedDNSTransport! public init( urlSessionTransport: URLSessionTransport, @@ -27,6 +28,7 @@ public final class TransportProvider: RESTTransportProvider { self.addressCache = addressCache self.transportStrategy = transportStrategy self.currentTransportType = transportStrategy.connectionTransport() + self.encryptedDNSTransport = EncryptedDNSTransport(urlSession: urlSessionTransport.urlSession) } public func makeTransport() -> RESTTransport? { @@ -53,6 +55,9 @@ public final class TransportProvider: RESTTransportProvider { defer { parallelRequestsMutex.unlock() } if strategy == transportStrategy { + if strategy.connectionTransport() == .encryptedDNS { + encryptedDNSTransport.stop() + } transportStrategy.didFail() currentTransport = nil } @@ -82,10 +87,7 @@ public final class TransportProvider: RESTTransportProvider { addressCache: addressCache ) case .encryptedDNS: - currentTransport = EncryptedDNSTransport( - urlSession: urlSessionTransport.urlSession, - addressCache: addressCache - ) + currentTransport = encryptedDNSTransport case .none: currentTransport = nil } diff --git a/ios/MullvadRustRuntime/EncryptedDNSProxy.swift b/ios/MullvadRustRuntime/EncryptedDNSProxy.swift new file mode 100644 index 000000000000..690e2459d099 --- /dev/null +++ b/ios/MullvadRustRuntime/EncryptedDNSProxy.swift @@ -0,0 +1,61 @@ +// +// EncryptedDNSProxy.swift +// MullvadRustRuntime +// +// Created by Emils on 24/09/2024. +// Copyright © 2024 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadRustRuntimeProxy + +enum EncryptedDnsProxyError: Error { + case start(err: Int32) +} + +public class EncryptedDNSProxy { + private var proxyConfig: ProxyHandle + private var stateLock = NSLock() + private var didStart = false + private let state: OpaquePointer + + public init() { + state = encrypted_dns_proxy_init() + proxyConfig = ProxyHandle(context: nil, port: 0) + } + + public func localPort() -> UInt16 { + stateLock.lock() + defer { stateLock.unlock() } + return proxyConfig.port + } + + public func start() throws { + stateLock.lock() + defer { stateLock.unlock() } + guard didStart == false else { return } + + let err = encrypted_dns_proxy_start(state, &proxyConfig) + if err != 0 { + throw EncryptedDnsProxyError.start(err: err) + } + didStart = true + } + + public func stop() { + stateLock.lock() + defer { stateLock.unlock() } + guard didStart == true else { return } + didStart = false + + encrypted_dns_proxy_stop(&proxyConfig) + } + + deinit { + if didStart { + encrypted_dns_proxy_stop(&proxyConfig) + } + + encrypted_dns_proxy_free(state) + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 07fc35037aa1..ee362edf09b2 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 014449952CA293B100C0C2F2 /* EncryptedDNSProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */; }; 01EF6F342B6A590700125696 /* libmullvad_api.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01EF6F332B6A590700125696 /* libmullvad_api.a */; }; 062B45A328FD4CA700746E77 /* le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 06799AB428F98CE700ACD94E /* le_root_cert.cer */; }; 062B45BC28FD8C3B00746E77 /* RESTDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 062B45BB28FD8C3B00746E77 /* RESTDefaults.swift */; }; @@ -1329,6 +1330,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptedDNSProxy.swift; sourceTree = ""; }; 01EF6F2D2B6A51B100125696 /* mullvad-api.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "mullvad-api.h"; path = "../mullvad-api/include/mullvad-api.h"; sourceTree = ""; }; 01EF6F2F2B6A588300125696 /* aarch64-apple-ios */ = {isa = PBXFileReference; lastKnownFileType = folder; name = "aarch64-apple-ios"; path = "../target/aarch64-apple-ios"; sourceTree = ""; }; 01EF6F312B6A58F000125696 /* debug */ = {isa = PBXFileReference; lastKnownFileType = folder; name = debug; path = "../target/aarch64-apple-ios/debug"; sourceTree = ""; }; @@ -2356,6 +2358,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 014449932CA1B55200C0C2F2 /* EncryptedDnsProxy */ = { + isa = PBXGroup; + children = ( + ); + path = EncryptedDnsProxy; + sourceTree = ""; + }; 062B45A228FD4C0F00746E77 /* Assets */ = { isa = PBXGroup; children = ( @@ -4026,6 +4035,7 @@ A9EB4F9C2B7FAB21002A2D7A /* EphemeralPeerNegotiator.swift */, F0DDE40F2B220458006B57A7 /* ShadowSocksProxy.swift */, 584023212A406BF5007B27AC /* UDPOverTCPObfuscator.swift */, + 014449942CA293B100C0C2F2 /* EncryptedDNSProxy.swift */, ); path = MullvadRustRuntime; sourceTree = ""; @@ -4177,6 +4187,7 @@ F0DC77A02B2223290087F09D /* Transport */ = { isa = PBXGroup; children = ( + 014449932CA1B55200C0C2F2 /* EncryptedDnsProxy */, F0164ED02B4F2DCB0020268D /* AccessMethodIterator.swift */, F0DC77A32B2315800087F09D /* Direct */, F0E5B2F62C9C689C0007F78C /* EncryptedDNS */, @@ -6146,6 +6157,7 @@ buildActionMask = 2147483647; files = ( A9D9A4B12C36D10E004088DD /* ShadowSocksProxy.swift in Sources */, + 014449952CA293B100C0C2F2 /* EncryptedDNSProxy.swift in Sources */, A9D9A4BB2C36D397004088DD /* EphemeralPeerNegotiator.swift in Sources */, A9D9A4B22C36D12D004088DD /* UDPOverTCPObfuscator.swift in Sources */, A9173C322C36CCDD00F6A08C /* PacketTunnelProvider+TCPConnection.swift in Sources */,