summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-01-16 15:52:16 +0100
committerBug Magnet <marco.nikic@mullvad.net>2025-04-24 10:41:25 +0200
commit8cb9795c392a41c6adae249007e0155115697e04 (patch)
tree0c0c0e5cf24b019be316ed07112b4bb78a3ef3f2
parente59bd99fe2d9671eaacfa8dd4c9ecd2b1c6aa682 (diff)
downloadmullvadvpn-8cb9795c392a41c6adae249007e0155115697e04.tar.xz
mullvadvpn-8cb9795c392a41c6adae249007e0155115697e04.zip
Expose TransportSelector to mullvad-ios
-rw-r--r--Cargo.lock3
-rw-r--r--ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift24
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTDefaults.swift7
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift9
-rw-r--r--ios/MullvadREST/Transport/AccessMethodIterator.swift11
-rw-r--r--ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift11
-rw-r--r--ios/MullvadREST/Transport/TransportStrategy.swift14
-rw-r--r--ios/MullvadRESTTests/MullvadApiTests.swift25
-rw-r--r--ios/MullvadRESTTests/ShadowsocksLoaderStub.swift6
-rw-r--r--ios/MullvadRESTTests/TransportStrategyTests.swift6
-rw-r--r--ios/MullvadRustRuntime/MullvadAccessMethodReceiver.swift42
-rw-r--r--ios/MullvadRustRuntime/MullvadApiContext.swift35
-rw-r--r--ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift99
-rw-r--r--ios/MullvadRustRuntime/MullvadShadowsocksBridgeProvider.swift29
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h157
-rw-r--r--ios/MullvadSettings/AccessMethodRepositoryProtocol.swift1
-rw-r--r--ios/MullvadTypes/AccessMethodKind.swift (renamed from ios/MullvadSettings/AccessMethodKind.swift)0
-rw-r--r--ios/MullvadTypes/PersistentAccessMethod.swift (renamed from ios/MullvadSettings/PersistentAccessMethod.swift)16
-rw-r--r--ios/MullvadTypes/ShadowsocksBridgeProviding.swift25
-rw-r--r--ios/MullvadTypes/ShadowsocksCipherOptions.swift (renamed from ios/MullvadSettings/ShadowsocksCipherOptions.swift)0
-rw-r--r--ios/MullvadTypes/ShadowsocksConfiguration.swift (renamed from ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift)6
-rw-r--r--ios/MullvadTypes/SwiftConnectionModeProvider.swift29
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj59
-rw-r--r--ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme5
-rw-r--r--ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift2
-rw-r--r--ios/MullvadVPN/AppDelegate.swift63
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/AccessMethodViewModelEditing.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodInteractor.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentAccessMethod+ViewModel.swift1
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentProxyConfiguration+ViewModel.swift2
-rw-r--r--ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift1
-rw-r--r--ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift1
-rw-r--r--ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift39
-rw-r--r--ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift36
-rw-r--r--mullvad-api/src/access_mode.rs39
-rw-r--r--mullvad-api/src/lib.rs4
-rw-r--r--mullvad-ios/Cargo.toml3
-rw-r--r--mullvad-ios/src/api_client/access_method_resolver.rs89
-rw-r--r--mullvad-ios/src/api_client/access_method_settings.rs193
-rw-r--r--mullvad-ios/src/api_client/account.rs9
-rw-r--r--mullvad-ios/src/api_client/api.rs6
-rw-r--r--mullvad-ios/src/api_client/cancellation.rs12
-rw-r--r--mullvad-ios/src/api_client/completion.rs3
-rw-r--r--mullvad-ios/src/api_client/helpers.rs108
-rw-r--r--mullvad-ios/src/api_client/mock.rs6
-rw-r--r--mullvad-ios/src/api_client/mod.rs186
-rw-r--r--mullvad-ios/src/api_client/problem_report.rs10
-rw-r--r--mullvad-ios/src/api_client/shadowsocks_loader.rs75
-rw-r--r--mullvad-ios/src/encrypted_dns_proxy.rs6
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs14
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/mod.rs13
-rw-r--r--mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs2
-rw-r--r--mullvad-ios/src/lib.rs2
-rw-r--r--mullvad-ios/src/shadowsocks_proxy/ffi.rs50
-rw-r--r--mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs32
58 files changed, 1408 insertions, 224 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e9aba835aa..24d4ca3ad8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2799,7 +2799,9 @@ dependencies = [
name = "mullvad-ios"
version = "0.0.0"
dependencies = [
+ "async-trait",
"cbindgen 0.28.0",
+ "futures",
"hyper",
"hyper-util",
"libc",
@@ -2807,6 +2809,7 @@ dependencies = [
"mockito",
"mullvad-api",
"mullvad-encrypted-dns-proxy",
+ "mullvad-types",
"oslog",
"serde_json",
"shadowsocks-service",
diff --git a/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
index 73fd82f359..6e7dbb93ef 100644
--- a/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
+++ b/ios/MullvadMockData/MullvadREST/AccessMethodRepository+Stub.swift
@@ -8,6 +8,7 @@
import Combine
import MullvadSettings
+import MullvadTypes
public struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource, @unchecked Sendable {
public var directAccess: PersistentAccessMethod
@@ -36,4 +37,27 @@ public struct AccessMethodRepositoryStub: AccessMethodRepositoryDataSource, @unc
public func infoHeaderConfig(for id: UUID) -> InfoHeaderConfig? {
nil
}
+
+ public static var stub: AccessMethodRepositoryStub {
+ AccessMethodRepositoryStub(accessMethods: [
+ PersistentAccessMethod(
+ id: UUID(),
+ name: "direct",
+ isEnabled: true,
+ proxyConfiguration: .direct
+ ),
+ PersistentAccessMethod(
+ id: UUID(),
+ name: "bridges",
+ isEnabled: true,
+ proxyConfiguration: .bridges
+ ),
+ PersistentAccessMethod(
+ id: UUID(),
+ name: "Encrypted DNS",
+ isEnabled: true,
+ proxyConfiguration: .encryptedDNS
+ ),
+ ])
+ }
}
diff --git a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
index d115abc37a..b2a3972fb7 100644
--- a/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTDefaults.swift
@@ -29,13 +29,6 @@ extension REST {
/// Default network timeout for API requests.
public static let defaultAPINetworkTimeout: Duration = .seconds(10)
-
- /// API context used for API requests via Rust runtime.
- // swiftlint:disable:next force_try
- public static let apiContext = try! MullvadApiContext(
- host: defaultAPIHostname,
- address: defaultAPIEndpoint
- )
}
// swiftlint:enable force_cast
diff --git a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
index 7515b92c5c..ff3751c5bd 100644
--- a/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
+++ b/ios/MullvadREST/ApiHandlers/RESTProxyFactory.swift
@@ -58,7 +58,16 @@ extension REST {
}
public func createAPIProxy() -> APIQuerying {
+ #if DEBUG
+ MullvadAPIProxy(
+ transportProvider: configuration.apiTransportProvider,
+ dispatchQueue: DispatchQueue(label: "MullvadAPIProxy.dispatchQueue"),
+ responseDecoder: Coding.makeJSONDecoder()
+ )
+
+ #else
REST.APIProxy(configuration: configuration)
+ #endif
}
public func createAccountsProxy() -> RESTAccountHandling {
diff --git a/ios/MullvadREST/Transport/AccessMethodIterator.swift b/ios/MullvadREST/Transport/AccessMethodIterator.swift
index 0b55c7683a..91de54bbd7 100644
--- a/ios/MullvadREST/Transport/AccessMethodIterator.swift
+++ b/ios/MullvadREST/Transport/AccessMethodIterator.swift
@@ -9,8 +9,9 @@
import Combine
import Foundation
import MullvadSettings
+import MullvadTypes
-final class AccessMethodIterator: @unchecked Sendable {
+final class AccessMethodIterator: @unchecked Sendable, SwiftConnectionModeProviding {
private let dataSource: AccessMethodRepositoryDataSource
private var index = 0
@@ -24,6 +25,10 @@ final class AccessMethodIterator: @unchecked Sendable {
dataSource.fetchLastReachable().id
}
+ public var domainName: String {
+ REST.encryptedDNSHostname
+ }
+
init(dataSource: AccessMethodRepositoryDataSource) {
self.dataSource = dataSource
@@ -63,4 +68,8 @@ final class AccessMethodIterator: @unchecked Sendable {
return configurations[circularIndex]
}
}
+
+ func accessMethods() -> [PersistentAccessMethod] {
+ dataSource.fetchAll()
+ }
}
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
index 1fac755454..1232383f26 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
+++ b/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksLoader.swift
@@ -10,12 +10,7 @@ import Foundation
import MullvadSettings
import MullvadTypes
-public protocol ShadowsocksLoaderProtocol: Sendable {
- func load() throws -> ShadowsocksConfiguration
- func clear() throws
-}
-
-public final class ShadowsocksLoader: ShadowsocksLoaderProtocol, Sendable {
+public final class ShadowsocksLoader: ShadowsocksLoaderProtocol, SwiftShadowsocksBridgeProviding, Sendable {
let cache: ShadowsocksConfigurationCacheProtocol
let relaySelector: ShadowsocksRelaySelectorProtocol
let settingsUpdater: SettingsUpdater
@@ -88,4 +83,8 @@ public final class ShadowsocksLoader: ShadowsocksLoaderProtocol, Sendable {
cipher: bridgeConfiguration.cipher
)
}
+
+ public func bridge() -> ShadowsocksConfiguration? {
+ try? load()
+ }
}
diff --git a/ios/MullvadREST/Transport/TransportStrategy.swift b/ios/MullvadREST/Transport/TransportStrategy.swift
index 8daf686f73..53c22b36c9 100644
--- a/ios/MullvadREST/Transport/TransportStrategy.swift
+++ b/ios/MullvadREST/Transport/TransportStrategy.swift
@@ -8,6 +8,7 @@
import Foundation
import Logging
+@preconcurrency import MullvadRustRuntime
import MullvadSettings
import MullvadTypes
@@ -49,12 +50,25 @@ public struct TransportStrategy: Equatable, Sendable {
private let accessMethodIterator: AccessMethodIterator
+ private let connectionModeProviderProxy: SwiftConnectionModeProviderProxy
+
+ public let opaqueAccessMethodSettingsWrapper: SwiftAccessMethodSettingsWrapper
+
public init(
datasource: AccessMethodRepositoryDataSource,
shadowsocksLoader: ShadowsocksLoaderProtocol
) {
self.shadowsocksLoader = shadowsocksLoader
self.accessMethodIterator = AccessMethodIterator(dataSource: datasource)
+ self.connectionModeProviderProxy = SwiftConnectionModeProviderProxy(
+ provider: accessMethodIterator,
+ domainName: REST.encryptedDNSHostname
+ )
+ self
+ .opaqueAccessMethodSettingsWrapper = initAccessMethodSettingsWrapper(
+ methods: connectionModeProviderProxy
+ .accessMethods()
+ )
}
/// Rotating between enabled configurations by what order they were added in
diff --git a/ios/MullvadRESTTests/MullvadApiTests.swift b/ios/MullvadRESTTests/MullvadApiTests.swift
index ab218623d2..0290f11e66 100644
--- a/ios/MullvadRESTTests/MullvadApiTests.swift
+++ b/ios/MullvadRESTTests/MullvadApiTests.swift
@@ -6,6 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
+import MullvadMockData
@testable import MullvadREST
import MullvadRustRuntime
import MullvadTypes
@@ -19,9 +20,27 @@ class MullvadApiTests: XCTestCase {
let encoder = JSONEncoder()
func makeApiProxy(port: UInt16) throws -> APIQuerying {
- let context = try MullvadApiContext(host: "localhost", address: .ipv4(
- .init(ip: IPv4Address.loopback, port: port)
- ), disable_tls: true)
+ let shadowsocksLoader = ShadowsocksLoaderStub(configuration: ShadowsocksConfiguration(
+ address: .ipv4(.loopback),
+ port: 1080,
+ password: "123",
+ cipher: CipherIdentifiers.CHACHA20.description
+ ))
+
+ let accessMethodsRepository = AccessMethodRepositoryStub.stub
+
+ let context = try MullvadApiContext(
+ host: "localhost",
+ address: "\(IPv4Address.loopback.debugDescription):\(port)",
+ domain: REST.encryptedDNSHostname,
+ disableTls: true,
+ shadowsocksProvider: shadowsocksLoader,
+ accessMethodWrapper: initAccessMethodSettingsWrapper(
+ methods: accessMethodsRepository
+ .fetchAll()
+ )
+ )
+
let proxy = REST.MullvadAPIProxy(
transportProvider: APITransportProvider(
requestFactory: .init(apiContext: context)
diff --git a/ios/MullvadRESTTests/ShadowsocksLoaderStub.swift b/ios/MullvadRESTTests/ShadowsocksLoaderStub.swift
index c28f532ce0..969ae865a5 100644
--- a/ios/MullvadRESTTests/ShadowsocksLoaderStub.swift
+++ b/ios/MullvadRESTTests/ShadowsocksLoaderStub.swift
@@ -11,7 +11,11 @@ import Foundation
import MullvadSettings
import MullvadTypes
-struct ShadowsocksLoaderStub: ShadowsocksLoaderProtocol {
+struct ShadowsocksLoaderStub: ShadowsocksLoaderProtocol, SwiftShadowsocksBridgeProviding {
+ func bridge() -> ShadowsocksConfiguration? {
+ try? load()
+ }
+
var configuration: ShadowsocksConfiguration
var error: Error?
diff --git a/ios/MullvadRESTTests/TransportStrategyTests.swift b/ios/MullvadRESTTests/TransportStrategyTests.swift
index a838b74499..6fdcef8db2 100644
--- a/ios/MullvadRESTTests/TransportStrategyTests.swift
+++ b/ios/MullvadRESTTests/TransportStrategyTests.swift
@@ -177,10 +177,12 @@ class TransportStrategyTests: XCTestCase {
func testNoLoopOnFailureAtLoadingConfigurationWhenBridgeIsOnlyEnabled() {
shadowsocksLoader.error = IOError.fileNotFound
directAccess.isEnabled = false
+ encryptedDNS.isEnabled = false
let transportStrategy = TransportStrategy(
datasource: AccessMethodRepositoryStub(accessMethods: [
directAccess,
bridgeAccess,
+ encryptedDNS,
]),
shadowsocksLoader: shadowsocksLoader
)
@@ -190,7 +192,7 @@ class TransportStrategyTests: XCTestCase {
}
}
- func testUsesSocks5WithAuthenticationWhenItReaches() throws {
+ func testUsesSocks5WithAuthentication() throws {
let username = "user"
let password = "pass"
let authentication = PersistentProxyConfiguration.SocksAuthentication
@@ -203,10 +205,12 @@ class TransportStrategyTests: XCTestCase {
port: 1080,
authentication: authentication
)
+ encryptedDNS.isEnabled = false
let transportStrategy = TransportStrategy(
datasource: AccessMethodRepositoryStub(accessMethods: [
directAccess,
bridgeAccess,
+ encryptedDNS,
PersistentAccessMethod(
id: UUID(),
name: "",
diff --git a/ios/MullvadRustRuntime/MullvadAccessMethodReceiver.swift b/ios/MullvadRustRuntime/MullvadAccessMethodReceiver.swift
new file mode 100644
index 0000000000..c683a10af6
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadAccessMethodReceiver.swift
@@ -0,0 +1,42 @@
+//
+// MullvadAccessMethodReceiver.swift
+// MullvadRustRuntime
+//
+// Created by Marco Nikic on 2025-03-31.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+import MullvadTypes
+
+public class MullvadAccessMethodReceiver {
+ private var cancellables = Set<Combine.AnyCancellable>()
+ let apiContext: MullvadApiContext
+
+ public init(
+ apiContext: MullvadApiContext,
+ accessMethodsDataSource: AnyPublisher<[PersistentAccessMethod], Never>,
+ lastReachableDataSource: AnyPublisher<PersistentAccessMethod, Never>
+ ) {
+ self.apiContext = apiContext
+
+ lastReachableDataSource.sink { [weak self] in
+ self?.saveLastReachable($0)
+ }
+ .store(in: &cancellables)
+
+ accessMethodsDataSource.sink { [weak self] in
+ self?.updateAccessMethods($0)
+ }.store(in: &cancellables)
+ }
+
+ private func saveLastReachable(_ lastReachable: PersistentAccessMethod) {
+ mullvad_api_use_access_method(apiContext.context, lastReachable.id.uuidString)
+ }
+
+ private func updateAccessMethods(_ accessMethods: [PersistentAccessMethod]) {
+ let settingsWrapper = initAccessMethodSettingsWrapper(methods: accessMethods)
+ mullvad_api_update_access_methods(apiContext.context, settingsWrapper)
+ }
+}
diff --git a/ios/MullvadRustRuntime/MullvadApiContext.swift b/ios/MullvadRustRuntime/MullvadApiContext.swift
index 2cdcb7b728..5517348cc1 100644
--- a/ios/MullvadRustRuntime/MullvadApiContext.swift
+++ b/ios/MullvadRustRuntime/MullvadApiContext.swift
@@ -8,19 +8,44 @@
import MullvadTypes
-public struct MullvadApiContext: Sendable {
+public struct MullvadApiContext: @unchecked Sendable {
enum MullvadApiContextError: Error {
case failedToConstructApiClient
}
public let context: SwiftApiContext
+ private let shadowsocksBridgeProvider: SwiftShadowsocksBridgeProviding!
+ private let shadowsocksBridgeProviderWrapper: SwiftShadowsocksLoaderWrapper!
- public init(host: String, address: AnyIPEndpoint, disable_tls: Bool = false) throws {
- context = switch disable_tls {
+ public init(
+ host: String,
+ address: String,
+ domain: String,
+ disableTls: Bool = false,
+ shadowsocksProvider: SwiftShadowsocksBridgeProviding,
+ accessMethodWrapper: SwiftAccessMethodSettingsWrapper
+ ) throws {
+ let bridgeProvider = SwiftShadowsocksBridgeProvider(provider: shadowsocksProvider)
+ self.shadowsocksBridgeProvider = bridgeProvider
+ self.shadowsocksBridgeProviderWrapper = initMullvadShadowsocksBridgeProvider(provider: bridgeProvider)
+
+ context = switch disableTls {
case true:
- mullvad_api_init_new_tls_disabled(host, address.description)
+ mullvad_api_init_new_tls_disabled(
+ host,
+ address,
+ domain,
+ shadowsocksBridgeProviderWrapper,
+ accessMethodWrapper
+ )
case false:
- mullvad_api_init_new(host, address.description)
+ mullvad_api_init_new(
+ host,
+ address,
+ domain,
+ shadowsocksBridgeProviderWrapper,
+ accessMethodWrapper
+ )
}
if context._0 == nil {
diff --git a/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift b/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift
new file mode 100644
index 0000000000..f7d2e0238f
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadConnectionModeProvider.swift
@@ -0,0 +1,99 @@
+//
+// MullvadConnectionModeProvider.swift
+// MullvadRustRuntime
+//
+// Created by Marco Nikic on 2025-02-20.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+
+// swiftlint:disable:next function_body_length
+public func initAccessMethodSettingsWrapper(methods: [PersistentAccessMethod])
+ -> SwiftAccessMethodSettingsWrapper {
+ // 1. Get all the built in access methods, it is expected that they are always available
+ let directMethod = methods.first(where: { $0.proxyConfiguration == .direct })!
+ let bridgesMethod = methods.first(where: { $0.proxyConfiguration == .bridges })!
+ let encryptedDNSMethod = methods.first(where: { $0.proxyConfiguration == .encryptedDNS })!
+
+ // 2. Get the custom access methods
+ let defaultMethods: [PersistentProxyConfiguration] = [.direct, .bridges, .encryptedDNS]
+ let customMethods = methods.filter { defaultMethods.contains($0.proxyConfiguration) == false }
+
+ // 3. Convert the builtin access methods
+ let directMethodRaw = convert_builtin_access_method_setting(
+ directMethod.id.uuidString,
+ directMethod.name,
+ directMethod.isEnabled,
+ UInt8(KindDirect.rawValue),
+ nil
+ )
+ let bridgesMethodRaw = convert_builtin_access_method_setting(
+ bridgesMethod.id.uuidString,
+ bridgesMethod.name,
+ bridgesMethod.isEnabled,
+ UInt8(KindBridge.rawValue),
+ nil
+ )
+ let encryptedDNSMethodRaw = convert_builtin_access_method_setting(
+ encryptedDNSMethod.id.uuidString,
+ encryptedDNSMethod.name,
+ encryptedDNSMethod.isEnabled,
+ UInt8(KindEncryptedDnsProxy.rawValue),
+ nil
+ )
+
+ var rawCustomMethods = ContiguousArray<UnsafeRawPointer?>([])
+ // 4. Convert the custom access methods (all takes different parameters)
+ for method in customMethods {
+ if case let .shadowsocks(config) = method.proxyConfiguration {
+ let serverAddress = config.server.rawValue.map { $0 }
+ let shadowsocksConfiguration = new_shadowsocks_access_method_setting(
+ serverAddress,
+ UInt(serverAddress.count),
+ config.port,
+ config.password,
+ config.cipher.rawValue.rawValue
+ )
+ let shadowsocksMethodRaw = convert_builtin_access_method_setting(
+ method.id.uuidString,
+ method.name,
+ method.isEnabled,
+ UInt8(KindShadowsocks.rawValue),
+ shadowsocksConfiguration
+ )
+ rawCustomMethods.append(shadowsocksMethodRaw)
+ }
+ if case let .socks5(config) = method.proxyConfiguration {
+ let serverAddress = config.server.rawValue.map { $0 }
+ let socks5Configuration = new_socks5_access_method_setting(
+ serverAddress,
+ UInt(serverAddress.count),
+ config.port,
+ config.credential?.username,
+ config.credential?.password
+ )
+ let socks5MethodRaw = convert_builtin_access_method_setting(
+ method.id.uuidString,
+ method.name,
+ method.isEnabled,
+ UInt8(KindSocks5Local.rawValue),
+ socks5Configuration
+ )
+ rawCustomMethods.append(socks5MethodRaw)
+ }
+ }
+
+ // 5. Reunite them all in one, and pass it to rust
+ return rawCustomMethods.withUnsafeMutableBufferPointer(
+ {
+ init_access_method_settings_wrapper(
+ directMethodRaw,
+ bridgesMethodRaw,
+ encryptedDNSMethodRaw,
+ $0.baseAddress!,
+ UInt(customMethods.count)
+ )
+ }
+ )
+}
diff --git a/ios/MullvadRustRuntime/MullvadShadowsocksBridgeProvider.swift b/ios/MullvadRustRuntime/MullvadShadowsocksBridgeProvider.swift
new file mode 100644
index 0000000000..16f7f7ca34
--- /dev/null
+++ b/ios/MullvadRustRuntime/MullvadShadowsocksBridgeProvider.swift
@@ -0,0 +1,29 @@
+//
+// MullvadShadowsocksBridgeProvider.swift
+// MullvadRustRuntime
+//
+// Created by Marco Nikic on 2025-03-24.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import MullvadTypes
+
+public func initMullvadShadowsocksBridgeProvider(provider: SwiftShadowsocksBridgeProvider)
+ -> SwiftShadowsocksLoaderWrapper {
+ let rawProvider = Unmanaged.passUnretained(provider).toOpaque()
+ return init_swift_shadowsocks_loader_wrapper(rawProvider)
+}
+
+@_cdecl("swift_get_shadowsocks_bridges")
+func getShadowsocksBridges(rawBridgeProvider: UnsafeMutableRawPointer) -> UnsafeRawPointer? {
+ let bridgeProvider = Unmanaged<SwiftShadowsocksBridgeProvider>.fromOpaque(rawBridgeProvider).takeUnretainedValue()
+ guard let bridge = bridgeProvider.bridge() else { return nil }
+ let bridgeAddress = bridge.address.rawValue.map { $0 }
+ return new_shadowsocks_access_method_setting(
+ bridgeAddress,
+ UInt(bridgeAddress.count),
+ bridge.port,
+ bridge.password,
+ bridge.cipher
+ )
+}
diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
index 4de4a02b37..6300b04902 100644
--- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
+++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h
@@ -6,6 +6,18 @@
#include <stdlib.h>
/**
+ * Used by Swift to instruct which access method kind it is trying to convert
+ */
+enum SwiftAccessMethodKind {
+ KindDirect = 0,
+ KindBridge,
+ KindEncryptedDnsProxy,
+ KindShadowsocks,
+ KindSocks5Local,
+};
+typedef uint8_t SwiftAccessMethodKind;
+
+/**
* SAFETY: `TunnelObfuscatorProtocol` values must either be `0` or `1`
*/
enum TunnelObfuscatorProtocol {
@@ -30,10 +42,24 @@ typedef struct RequestCancelHandle RequestCancelHandle;
typedef struct RetryStrategy RetryStrategy;
+typedef struct SwiftAccessMethodSettingsContext SwiftAccessMethodSettingsContext;
+
typedef struct SwiftApiContext {
const struct ApiContext *_0;
} SwiftApiContext;
+typedef struct SwiftAccessMethodSettingsWrapper {
+ struct SwiftAccessMethodSettingsContext *_0;
+} SwiftAccessMethodSettingsWrapper;
+
+typedef struct SwiftShadowsocksLoaderWrapperContext {
+ const void *shadowsocks_loader;
+} SwiftShadowsocksLoaderWrapperContext;
+
+typedef struct SwiftShadowsocksLoaderWrapper {
+ struct SwiftShadowsocksLoaderWrapperContext _0;
+} SwiftShadowsocksLoaderWrapper;
+
typedef struct SwiftCancelHandle {
struct RequestCancelHandle *ptr;
} SwiftCancelHandle;
@@ -101,6 +127,22 @@ typedef struct EphemeralPeerParameters {
extern const uint16_t CONFIG_SERVICE_PORT;
/**
+ * Called by Swift to set the available access methods
+ */
+void mullvad_api_update_access_methods(struct SwiftApiContext api_context,
+ struct SwiftAccessMethodSettingsWrapper settings_wrapper);
+
+/**
+ * Called by Swift to update the currently used access methods
+ *
+ * # SAFETY
+ * `access_method_id` must point to a null terminated string in a UUID format
+ *
+ */
+void mullvad_api_use_access_method(struct SwiftApiContext api_context,
+ const char *access_method_id);
+
+/**
* # Safety
*
* `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host.
@@ -114,8 +156,11 @@ extern const uint16_t CONFIG_SERVICE_PORT;
*
* This function is safe.
*/
-struct SwiftApiContext mullvad_api_init_new_tls_disabled(const uint8_t *host,
- const uint8_t *address);
+struct SwiftApiContext mullvad_api_init_new_tls_disabled(const char *host,
+ const char *address,
+ const char *domain,
+ struct SwiftShadowsocksLoaderWrapper bridge_provider,
+ struct SwiftAccessMethodSettingsWrapper settings_provider);
/**
* # Safety
@@ -131,8 +176,63 @@ struct SwiftApiContext mullvad_api_init_new_tls_disabled(const uint8_t *host,
*
* This function is safe.
*/
-struct SwiftApiContext mullvad_api_init_new(const uint8_t *host,
- const uint8_t *address);
+struct SwiftApiContext mullvad_api_init_new(const char *host,
+ const char *address,
+ const char *domain,
+ struct SwiftShadowsocksLoaderWrapper bridge_provider,
+ struct SwiftAccessMethodSettingsWrapper settings_provider);
+
+/**
+ * # Safety
+ *
+ * `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host.
+ * This hostname will be used for TLS validation but not used for domain name resolution.
+ *
+ * `address` must be a pointer to a null terminated string representing a socket address through which
+ * the Mullvad API can be reached directly.
+ *
+ * If a context cannot be constructed this function will panic since the call site would not be able
+ * to proceed in a meaningful way anyway.
+ *
+ * This function is safe.
+ */
+struct SwiftApiContext mullvad_api_init_inner(const char *host,
+ const char *address,
+ const char *domain,
+ bool disable_tls,
+ struct SwiftShadowsocksLoaderWrapper bridge_provider,
+ struct SwiftAccessMethodSettingsWrapper settings_provider);
+
+/**
+ * Converts parameters into a `Box<AccessMethodSetting>` raw representation that
+ * can be passed across the FFI boundary
+ *
+ * # SAFETY:
+ * `unique_identifier` and `name` must point to valid memory regions and contain NULL terminators.
+ * They are only valid for the duration of this call.
+ *
+ * `proxy_configuration` can be NULL, or must be a pointer gotten through
+ * either the `convert_shadowsocks` or `convert_socks5` methods.
+ */
+void *convert_builtin_access_method_setting(const char *unique_identifier,
+ const char *name,
+ bool is_enabled,
+ SwiftAccessMethodKind method_kind,
+ const void *proxy_configuration);
+
+/**
+ * Creates a wrapper around a `Settings` object that can be safely sent across the FFI boundary.
+ *
+ * # SAFETY
+ * `direct_method_raw`, `bridges_method_raw` and `encrypted_dns_method_raw` must be raw pointers
+ * resulting from a call to `convert_builtin_access_method_setting`
+ * `custom_methods_raw` is an array of pointers to instances of `AccessMethodSetting`
+ */
+struct SwiftAccessMethodSettingsWrapper init_access_method_settings_wrapper(const void *direct_method_raw,
+ const void *bridges_method_raw,
+ const void *encrypted_dns_method_raw,
+ const void *custom_methods_raw,
+ uintptr_t custom_method_count);
/**
* # Safety
@@ -261,6 +361,35 @@ extern void mullvad_api_completion_finish(struct SwiftMullvadApiResponse respons
struct CompletionCookie completion_cookie);
/**
+ * Converts parameters into a boxed `Shadowsocks` configuration that is safe
+ * to send across the FFI boundary
+ *
+ * # SAFETY
+ * `address` must be a pointer to at least `address_len` bytes.
+ * `c_password` and `c_cipher` must be pointers to null terminated strings
+ */
+const void *new_shadowsocks_access_method_setting(const uint8_t *address,
+ uintptr_t address_len,
+ uint16_t port,
+ const char *c_password,
+ const char *c_cipher);
+
+/**
+ * Converts parameters into a boxed `Socks5Remote` configuration that is safe
+ *
+ * to send across the FFI boundary
+ *
+ * # SAFETY
+ * `address` must be a pointer to at least `address_len` bytes.
+ * `c_username` and `c_password` must be pointers to null terminated strings, or null
+ */
+const void *new_socks5_access_method_setting(const uint8_t *address,
+ uintptr_t address_len,
+ uint16_t port,
+ const char *c_username,
+ const char *c_password);
+
+/**
* # Safety
*
* `method` must be a pointer to a null terminated string representing the http method.
@@ -376,6 +505,26 @@ struct SwiftRetryStrategy mullvad_api_retry_strategy_exponential(uintptr_t max_r
uint64_t max_delay_sec);
/**
+ * Creates a `Shadowsocks` configuration.
+ *
+ * # SAFETY
+ * `rawBridgeProvider` **must** be provided by a call to `init_swift_shadowsocks_loader_wrapper`
+ * It is okay to persist it, and use it across multiple threads.
+ */
+extern const void *swift_get_shadowsocks_bridges(const void *rawBridgeProvider);
+
+/**
+ * Called by the Swift side in order to provide an object to rust that can create
+ * Shadowsocks configurations
+ *
+ * # SAFETY
+ * `shadowsocks_loader` **must be** pointing to a valid instance of a `SwiftShadowsocksBridgeProvider`
+ * That instance's lifetime has to be equivalent to a `'static` lifetime in Rust
+ * This function does not take ownership of `shadowsocks_loader`
+ */
+struct SwiftShadowsocksLoaderWrapper init_swift_shadowsocks_loader_wrapper(const void *shadowsocks_loader);
+
+/**
* Initializes a valid pointer to an instance of `EncryptedDnsProxyState`.
*
* # Safety
diff --git a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
index ab8ea4fd47..35f97442f5 100644
--- a/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
+++ b/ios/MullvadSettings/AccessMethodRepositoryProtocol.swift
@@ -7,6 +7,7 @@
//
import Combine
+import MullvadTypes
public protocol AccessMethodRepositoryDataSource: Sendable {
/// Publisher that propagates a snapshot of all access methods upon modifications.
diff --git a/ios/MullvadSettings/AccessMethodKind.swift b/ios/MullvadTypes/AccessMethodKind.swift
index f01dd8b845..f01dd8b845 100644
--- a/ios/MullvadSettings/AccessMethodKind.swift
+++ b/ios/MullvadTypes/AccessMethodKind.swift
diff --git a/ios/MullvadSettings/PersistentAccessMethod.swift b/ios/MullvadTypes/PersistentAccessMethod.swift
index bc00cbf2bb..8b9d6d57ae 100644
--- a/ios/MullvadSettings/PersistentAccessMethod.swift
+++ b/ios/MullvadTypes/PersistentAccessMethod.swift
@@ -7,7 +7,6 @@
//
import Foundation
-import MullvadTypes
import Network
/// Persistent access method container model.
@@ -17,6 +16,11 @@ public struct PersistentAccessMethodStore: Codable {
/// Persistent access method models.
public var accessMethods: [PersistentAccessMethod]
+
+ public init(lastReachableAccessMethod: PersistentAccessMethod, accessMethods: [PersistentAccessMethod]) {
+ self.lastReachableAccessMethod = lastReachableAccessMethod
+ self.accessMethods = accessMethods
+ }
}
/// Persistent access method model.
@@ -59,7 +63,7 @@ public struct PersistentAccessMethod: Identifiable, Codable, Equatable {
}
/// Persistent proxy configuration.
-public enum PersistentProxyConfiguration: Codable {
+public enum PersistentProxyConfiguration: Codable, Equatable {
/// Direct communication without proxy.
case direct
@@ -78,12 +82,12 @@ public enum PersistentProxyConfiguration: Codable {
extension PersistentProxyConfiguration {
/// Socks autentication method.
- public enum SocksAuthentication: Codable {
+ public enum SocksAuthentication: Codable, Equatable {
case noAuthentication
case authentication(UserCredential)
}
- public struct UserCredential: Codable {
+ public struct UserCredential: Codable, Equatable {
public let username: String
public let password: String
@@ -94,7 +98,7 @@ extension PersistentProxyConfiguration {
}
/// Socks v5 proxy configuration.
- public struct SocksConfiguration: Codable {
+ public struct SocksConfiguration: Codable, Equatable {
/// Proxy server address.
public var server: AnyIPAddress
@@ -128,7 +132,7 @@ extension PersistentProxyConfiguration {
}
/// Shadowsocks configuration.
- public struct ShadowsocksConfiguration: Codable {
+ public struct ShadowsocksConfiguration: Codable, Equatable {
/// Server address.
public var server: AnyIPAddress
diff --git a/ios/MullvadTypes/ShadowsocksBridgeProviding.swift b/ios/MullvadTypes/ShadowsocksBridgeProviding.swift
new file mode 100644
index 0000000000..3d0610bf07
--- /dev/null
+++ b/ios/MullvadTypes/ShadowsocksBridgeProviding.swift
@@ -0,0 +1,25 @@
+//
+// ShadowsocksBridgeProviding.swift
+// MullvadTypes
+//
+// Created by Marco Nikic on 2025-03-24.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public protocol SwiftShadowsocksBridgeProviding: Sendable {
+ func bridge() -> ShadowsocksConfiguration?
+}
+
+public final class SwiftShadowsocksBridgeProvider: SwiftShadowsocksBridgeProviding, Sendable {
+ let provider: SwiftShadowsocksBridgeProviding
+
+ public init(provider: SwiftShadowsocksBridgeProviding) {
+ self.provider = provider
+ }
+
+ public func bridge() -> ShadowsocksConfiguration? {
+ provider.bridge()
+ }
+}
diff --git a/ios/MullvadSettings/ShadowsocksCipherOptions.swift b/ios/MullvadTypes/ShadowsocksCipherOptions.swift
index 19ecfadd38..19ecfadd38 100644
--- a/ios/MullvadSettings/ShadowsocksCipherOptions.swift
+++ b/ios/MullvadTypes/ShadowsocksCipherOptions.swift
diff --git a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift b/ios/MullvadTypes/ShadowsocksConfiguration.swift
index b8cf90b7cc..f54276348d 100644
--- a/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfiguration.swift
+++ b/ios/MullvadTypes/ShadowsocksConfiguration.swift
@@ -7,9 +7,13 @@
//
import Foundation
-import MullvadTypes
import Network
+public protocol ShadowsocksLoaderProtocol: Sendable {
+ func load() throws -> ShadowsocksConfiguration
+ func clear() throws
+}
+
public struct ShadowsocksConfiguration: Codable, Equatable, Sendable {
public let address: AnyIPAddress
public let port: UInt16
diff --git a/ios/MullvadTypes/SwiftConnectionModeProvider.swift b/ios/MullvadTypes/SwiftConnectionModeProvider.swift
new file mode 100644
index 0000000000..95fc8f4b83
--- /dev/null
+++ b/ios/MullvadTypes/SwiftConnectionModeProvider.swift
@@ -0,0 +1,29 @@
+//
+// SwiftConnectionModeProvider.swift
+// MullvadTypes
+//
+// Created by Marco Nikic on 2025-02-19.
+// Copyright © 2025 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+public protocol SwiftConnectionModeProviding: Sendable {
+ var domainName: String { get }
+
+ func accessMethods() -> [PersistentAccessMethod]
+}
+
+public final class SwiftConnectionModeProviderProxy: SwiftConnectionModeProviding, Sendable {
+ let provider: SwiftConnectionModeProviding
+ public let domainName: String
+
+ public init(provider: SwiftConnectionModeProviding, domainName: String) {
+ self.provider = provider
+ self.domainName = domainName
+ }
+
+ public func accessMethods() -> [PersistentAccessMethod] {
+ provider.accessMethods()
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 3e2407ccac..26406d4230 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -779,9 +779,15 @@
A935594C2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A935594B2B4C2DA900D5D524 /* APIAvailabilityTestRequest.swift */; };
A939661B2CAE6CE1008128CA /* MigrationManagerMultiProcessUpgradeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A939661A2CAE6CE1008128CA /* MigrationManagerMultiProcessUpgradeTests.swift */; };
A93969812CE606190032A7A0 /* Maybenot.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9840BB32C69F78A0030F05E /* Maybenot.swift */; };
+ A959E23E2D75F33300F95DDB /* PersistentAccessMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */; };
+ A959E2412D75F39A00F95DDB /* MullvadTypes.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58D223D5294C8E5E0029F5F8 /* MullvadTypes.framework */; };
+ A959E2422D75F3D200F95DDB /* AccessMethodKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588D7ED72AF3A533005DF40A /* AccessMethodKind.swift */; };
+ A959E2432D75F42500F95DDB /* ShadowsocksCipherOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */; };
A95EEE362B722CD600A8A39B /* TunnelMonitorState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */; };
A95EEE382B722DFC00A8A39B /* PingStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = A95EEE372B722DFC00A8A39B /* PingStats.swift */; };
+ A96D0B452D675F0400DD6C59 /* MullvadConnectionModeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A96D0B442D675F0400DD6C59 /* MullvadConnectionModeProvider.swift */; };
A970C89D2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = A970C89C2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift */; };
+ A9711B2B2D662AE3003DA71D /* SwiftConnectionModeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9711B2A2D662AE3003DA71D /* SwiftConnectionModeProvider.swift */; };
A97275562CE36CAE00029F15 /* DaitaV2Parameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97275552CE36CAE00029F15 /* DaitaV2Parameters.swift */; };
A97D25AE2B0BB18100946B2D /* ProtocolObfuscator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */; };
A97D25B02B0BB5C400946B2D /* ProtocolObfuscationStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */; };
@@ -789,6 +795,10 @@
A97D25B42B0CB59300946B2D /* TunnelObfuscationStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D25B32B0CB59300946B2D /* TunnelObfuscationStub.swift */; };
A97D30172AE6B5E90045C0E4 /* StoredWgKeyData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97D30162AE6B5E90045C0E4 /* StoredWgKeyData.swift */; };
A97FF5502A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */; };
+ A98207EB2D9190F100654558 /* ShadowsocksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4132B220458006B57A7 /* ShadowsocksConfiguration.swift */; };
+ A98207EF2D9192A300654558 /* ShadowsocksBridgeProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98207EE2D9192A300654558 /* ShadowsocksBridgeProviding.swift */; };
+ A98207F12D91A0AC00654558 /* MullvadShadowsocksBridgeProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98207F02D91A0AC00654558 /* MullvadShadowsocksBridgeProvider.swift */; };
+ A98207F32D9ACE4C00654558 /* MullvadAccessMethodReceiver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98207F22D9ACE4C00654558 /* MullvadAccessMethodReceiver.swift */; };
A98502032B627B120061901E /* LocalNetworkProbe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98502022B627B120061901E /* LocalNetworkProbe.swift */; };
A988A3E22AFE54AC0008D2C7 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; };
A988DF272ADE86ED00D807EF /* WireGuardObfuscationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */; };
@@ -991,8 +1001,6 @@
F07C9D952B220C77006F1C5E /* libmullvad_ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 01F1FF1D29F0627D007083C3 /* libmullvad_ios.a */; };
F07CFF2029F2720E008C0343 /* NewDeviceNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F07CFF1F29F2720E008C0343 /* NewDeviceNotificationProvider.swift */; };
F07F63CE2C63E5790027A351 /* AccessMethodRepository+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0164EB92B4456D30020268D /* AccessMethodRepository+Stub.swift */; };
- F08827872B318C840020A383 /* ShadowsocksCipherOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */; };
- F08827882B318F960020A383 /* PersistentAccessMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */; };
F08827892B3192110020A383 /* AccessMethodRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EF875A2B16385400C098B2 /* AccessMethodRepositoryProtocol.swift */; };
F08B6B772C52878400D0A121 /* EphemeralPeerExchangeActorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98F1B502C19C48D003C869E /* EphemeralPeerExchangeActorTests.swift */; };
F08B6B782C528B8A00D0A121 /* EphemeralPeerExchangingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = F059197E2C454CE000C301F3 /* EphemeralPeerExchangingProtocol.swift */; };
@@ -1058,7 +1066,6 @@
F0D5591E2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D5591D2D3805050072B63F /* LatestChangesNotificationProvider.swift */; };
F0D5591F2D38051C0072B63F /* LatestChangesNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D5591D2D3805050072B63F /* LatestChangesNotificationProvider.swift */; };
F0D7FF8F2B31DF5900E0FDE5 /* AccessMethodRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5827B0A02B0E064E00CCBBA1 /* AccessMethodRepository.swift */; };
- F0D7FF902B31E00B00E0FDE5 /* AccessMethodKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 588D7ED72AF3A533005DF40A /* AccessMethodKind.swift */; };
F0D8825B2B04F53600D3EF9A /* OutgoingConnectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D8825A2B04F53600D3EF9A /* OutgoingConnectionData.swift */; };
F0D8825C2B04F70E00D3EF9A /* OutgoingConnectionData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0D8825A2B04F53600D3EF9A /* OutgoingConnectionData.swift */; };
F0DA87472A9CB9A2006044F1 /* AccountExpiryRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DA87462A9CB9A2006044F1 /* AccountExpiryRow.swift */; };
@@ -1067,7 +1074,6 @@
F0DAC8AD2C16EFE400F80144 /* TunnelSettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F04DD3D72C130DF600E03E28 /* TunnelSettingsManager.swift */; };
F0DDE4152B220458006B57A7 /* ShadowsocksConfigurationCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4102B220458006B57A7 /* ShadowsocksConfigurationCache.swift */; };
F0DDE4162B220458006B57A7 /* TransportProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4112B220458006B57A7 /* TransportProvider.swift */; };
- F0DDE4182B220458006B57A7 /* ShadowsocksConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4132B220458006B57A7 /* ShadowsocksConfiguration.swift */; };
F0DDE42A2B220A15006B57A7 /* Haversine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4272B220A15006B57A7 /* Haversine.swift */; };
F0DDE42B2B220A15006B57A7 /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4282B220A15006B57A7 /* RelaySelector.swift */; };
F0DDE42C2B220A15006B57A7 /* Midpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0DDE4292B220A15006B57A7 /* Midpoint.swift */; };
@@ -1375,6 +1381,13 @@
remoteGlobalIDString = 58D223D4294C8E5E0029F5F8;
remoteInfo = MullvadTypes;
};
+ A959E23F2D75F37600F95DDB /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 58CE5E58224146200008646E /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 58D223D4294C8E5E0029F5F8;
+ remoteInfo = MullvadTypes;
+ };
A9609B6D2D004D1F0065A3D3 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 58CE5E58224146200008646E /* Project object */;
@@ -2319,7 +2332,9 @@
A948809A2BC9308D0090A44C /* EphemeralPeerExchangeActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralPeerExchangeActor.swift; sourceTree = "<group>"; };
A95EEE352B722CD600A8A39B /* TunnelMonitorState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelMonitorState.swift; sourceTree = "<group>"; };
A95EEE372B722DFC00A8A39B /* PingStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PingStats.swift; sourceTree = "<group>"; };
+ A96D0B442D675F0400DD6C59 /* MullvadConnectionModeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadConnectionModeProvider.swift; sourceTree = "<group>"; };
A970C89C2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Socks5UsernamePasswordCommand.swift; sourceTree = "<group>"; };
+ A9711B2A2D662AE3003DA71D /* SwiftConnectionModeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftConnectionModeProvider.swift; sourceTree = "<group>"; };
A97275552CE36CAE00029F15 /* DaitaV2Parameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaitaV2Parameters.swift; sourceTree = "<group>"; };
A97D25AD2B0BB18100946B2D /* ProtocolObfuscator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscator.swift; sourceTree = "<group>"; };
A97D25AF2B0BB5C400946B2D /* ProtocolObfuscationStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolObfuscationStub.swift; sourceTree = "<group>"; };
@@ -2327,6 +2342,9 @@
A97D25B32B0CB59300946B2D /* TunnelObfuscationStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelObfuscationStub.swift; sourceTree = "<group>"; };
A97D30162AE6B5E90045C0E4 /* StoredWgKeyData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredWgKeyData.swift; sourceTree = "<group>"; };
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSFileCoordinator+Extensions.swift"; sourceTree = "<group>"; };
+ A98207EE2D9192A300654558 /* ShadowsocksBridgeProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowsocksBridgeProviding.swift; sourceTree = "<group>"; };
+ A98207F02D91A0AC00654558 /* MullvadShadowsocksBridgeProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadShadowsocksBridgeProvider.swift; sourceTree = "<group>"; };
+ A98207F22D9ACE4C00654558 /* MullvadAccessMethodReceiver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadAccessMethodReceiver.swift; sourceTree = "<group>"; };
A9840BB32C69F78A0030F05E /* Maybenot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Maybenot.swift; sourceTree = "<group>"; };
A98502022B627B120061901E /* LocalNetworkProbe.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkProbe.swift; sourceTree = "<group>"; };
A988DF252ADE86ED00D807EF /* WireGuardObfuscationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardObfuscationSettings.swift; sourceTree = "<group>"; };
@@ -2543,6 +2561,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ A959E2412D75F39A00F95DDB /* MullvadTypes.framework in Frameworks */,
44A2625F2D63745000085380 /* WireGuardKitTypes in Frameworks */,
58FE25BB2AA72188003D1918 /* MullvadLogging.framework in Frameworks */,
);
@@ -2989,6 +3008,8 @@
581943F228F8014500B0CB5E /* MullvadTypes */ = {
isa = PBXGroup;
children = (
+ F0DDE4132B220458006B57A7 /* ShadowsocksConfiguration.swift */,
+ 588D7ED72AF3A533005DF40A /* AccessMethodKind.swift */,
584D26BE270C550B004EA533 /* AnyIPAddress.swift */,
586A951329013235007BAF2B /* AnyIPEndpoint.swift */,
06AC113628F83FD70037AF9A /* Cancellable.swift */,
@@ -3011,6 +3032,7 @@
7AB401842DA53D4E00522E17 /* NewAccountData.swift */,
A97FF54F2A0D2FFC00900996 /* NSFileCoordinator+Extensions.swift */,
58CC40EE24A601900019D96E /* ObserverList.swift */,
+ 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */,
58CAFA01298530DC00BE19F7 /* Promise.swift */,
449EBA242B975B7C00DFA4EB /* Protocols */,
5898D2B12902A6DE00EB5EBA /* RelayConstraint.swift */,
@@ -3019,9 +3041,12 @@
5898D2AF2902A67C00EB5EBA /* RelayLocation.swift */,
581DA2722A1E227D0046ED47 /* RESTTypes.swift */,
58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */,
+ 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */,
F0ADF1CC2CFDFF3100299F09 /* StringConversionError.swift */,
A91614D02B108D1B00F416EB /* TransportLayer.swift */,
58E511E028DDB7F100B0BCDE /* WrappingError.swift */,
+ A9711B2A2D662AE3003DA71D /* SwiftConnectionModeProvider.swift */,
+ A98207EE2D9192A300654558 /* ShadowsocksBridgeProviding.swift */,
);
path = MullvadTypes;
sourceTree = "<group>";
@@ -3686,7 +3711,6 @@
58B2FDD42AA71D2A003EB5C6 /* MullvadSettings */ = {
isa = PBXGroup;
children = (
- 588D7ED72AF3A533005DF40A /* AccessMethodKind.swift */,
5827B0A02B0E064E00CCBBA1 /* AccessMethodRepository.swift */,
58EF875A2B16385400C098B2 /* AccessMethodRepositoryProtocol.swift */,
F0164EBB2B482E430020268D /* AppStorage.swift */,
@@ -3705,12 +3729,10 @@
A9D96B192A8247C100A5C673 /* MigrationManager.swift */,
58B2FDD52AA71D2A003EB5C6 /* MullvadSettings.h */,
F0E61CA92BF2911D000C4A95 /* MultihopSettings.swift */,
- 586C0D962B04E0AC00E7CDD7 /* PersistentAccessMethod.swift */,
44DD7D2C2B74E44A0005F67F /* QuantumResistanceSettings.swift */,
58FF2C02281BDE02009EF542 /* SettingsManager.swift */,
06410E03292D0F7100AFC18C /* SettingsParser.swift */,
06410E06292D108E00AFC18C /* SettingsStore.swift */,
- 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */,
A92ECC232A7802520052F1B1 /* StoredAccountData.swift */,
A92ECC272A7802AB0052F1B1 /* StoredDeviceData.swift */,
A97D30162AE6B5E90045C0E4 /* StoredWgKeyData.swift */,
@@ -4540,6 +4562,9 @@
F0DDE40F2B220458006B57A7 /* ShadowSocksProxy.swift */,
F0A89CB62D9D922300580C27 /* String+UnsafePointer.swift */,
584023212A406BF5007B27AC /* TunnelObfuscator.swift */,
+ A96D0B442D675F0400DD6C59 /* MullvadConnectionModeProvider.swift */,
+ A98207F02D91A0AC00654558 /* MullvadShadowsocksBridgeProvider.swift */,
+ A98207F22D9ACE4C00654558 /* MullvadAccessMethodReceiver.swift */,
);
path = MullvadRustRuntime;
sourceTree = "<group>";
@@ -4748,7 +4773,6 @@
F0DC77A22B2314EF0087F09D /* Shadowsocks */ = {
isa = PBXGroup;
children = (
- F0DDE4132B220458006B57A7 /* ShadowsocksConfiguration.swift */,
F0DDE4102B220458006B57A7 /* ShadowsocksConfigurationCache.swift */,
F0164EBD2B4BFF940020268D /* ShadowsocksLoader.swift */,
F01528BA2BFF3FEE00B01D00 /* ShadowsocksRelaySelector.swift */,
@@ -5038,6 +5062,7 @@
buildRules = (
);
dependencies = (
+ A959E2402D75F37600F95DDB /* PBXTargetDependency */,
58FE25BE2AA72188003D1918 /* PBXTargetDependency */,
);
name = MullvadSettings;
@@ -5797,7 +5822,6 @@
A970C89D2B29E38C000A7684 /* Socks5UsernamePasswordCommand.swift in Sources */,
A90763B32B2857D50045ADF0 /* Socks5Authentication.swift in Sources */,
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 */,
@@ -6055,7 +6079,6 @@
F050AE572B7376C6003F4EDB /* CustomListRepositoryProtocol.swift in Sources */,
58B2FDE52AA71D5C003EB5C6 /* TunnelSettingsV2.swift in Sources */,
A97D30172AE6B5E90045C0E4 /* StoredWgKeyData.swift in Sources */,
- F08827882B318F960020A383 /* PersistentAccessMethod.swift in Sources */,
58B2FDE32AA71D5C003EB5C6 /* StoredDeviceData.swift in Sources */,
58B2FDDF2AA71D5C003EB5C6 /* DNSSettings.swift in Sources */,
58B2FDE02AA71D5C003EB5C6 /* TunnelSettings.swift in Sources */,
@@ -6076,12 +6099,10 @@
449872E12B7BBC5400094DDC /* TunnelSettingsUpdate.swift in Sources */,
58B2FDE72AA71D5C003EB5C6 /* SettingsStore.swift in Sources */,
44DD7D2D2B74E44A0005F67F /* QuantumResistanceSettings.swift in Sources */,
- F08827872B318C840020A383 /* ShadowsocksCipherOptions.swift in Sources */,
58B2FDE92AA71D5C003EB5C6 /* SettingsParser.swift in Sources */,
F08827892B3192110020A383 /* AccessMethodRepositoryProtocol.swift in Sources */,
F050AE5A2B7376F4003F4EDB /* CustomList.swift in Sources */,
58B2FDE22AA71D5C003EB5C6 /* StoredAccountData.swift in Sources */,
- F0D7FF902B31E00B00E0FDE5 /* AccessMethodKind.swift in Sources */,
7A5869BC2B56EF3400640D27 /* IPOverrideRepository.swift in Sources */,
7A9F293B2CAC4443005F2089 /* InfoHeaderConfig.swift in Sources */,
F0E61CAB2BF2911D000C4A95 /* MultihopSettings.swift in Sources */,
@@ -6663,6 +6684,7 @@
buildActionMask = 2147483647;
files = (
A91614D12B108D1B00F416EB /* TransportLayer.swift in Sources */,
+ A959E23E2D75F33300F95DDB /* PersistentAccessMethod.swift in Sources */,
58D22406294C90210029F5F8 /* IPv4Endpoint.swift in Sources */,
7A307ADB2A8F56DF0017618B /* Duration+Extensions.swift in Sources */,
58D22407294C90210029F5F8 /* IPv6Endpoint.swift in Sources */,
@@ -6682,10 +6704,13 @@
A9A8A8EB2A262AB30086D569 /* FileCache.swift in Sources */,
A90C48692C36BF3900DCB94C /* TunnelProvider.swift in Sources */,
58D2240D294C90210029F5F8 /* CustomErrorDescriptionProtocol.swift in Sources */,
+ A98207EF2D9192A300654558 /* ShadowsocksBridgeProviding.swift in Sources */,
58D2240E294C90210029F5F8 /* Error+Chain.swift in Sources */,
586168692976F6BD00EF8598 /* DisplayError.swift in Sources */,
58D2240F294C90210029F5F8 /* KeychainError.swift in Sources */,
+ A959E2422D75F3D200F95DDB /* AccessMethodKind.swift in Sources */,
58D22410294C90210029F5F8 /* Location.swift in Sources */,
+ A98207EB2D9190F100654558 /* ShadowsocksConfiguration.swift in Sources */,
58D22411294C90210029F5F8 /* MullvadEndpoint.swift in Sources */,
58D22412294C90210029F5F8 /* RelayConstraint.swift in Sources */,
7A7AD14F2BF21EF200B30B3C /* NameInputFormatter.swift in Sources */,
@@ -6696,8 +6721,10 @@
58D22414294C90210029F5F8 /* RelayLocation.swift in Sources */,
581DA2732A1E227D0046ED47 /* RESTTypes.swift in Sources */,
449EBA262B975B9700DFA4EB /* EphemeralPeerReceiving.swift in Sources */,
+ A959E2432D75F42500F95DDB /* ShadowsocksCipherOptions.swift in Sources */,
58D22417294C90210029F5F8 /* FixedWidthInteger+Arithmetics.swift in Sources */,
F0F56B092C0E058A009D676B /* ObserverList.swift in Sources */,
+ A9711B2B2D662AE3003DA71D /* SwiftConnectionModeProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -6839,8 +6866,11 @@
A9D9A4BB2C36D397004088DD /* EphemeralPeerNegotiator.swift in Sources */,
F0A89CB72D9D923300580C27 /* String+UnsafePointer.swift in Sources */,
A9D9A4B22C36D12D004088DD /* TunnelObfuscator.swift in Sources */,
+ A98207F12D91A0AC00654558 /* MullvadShadowsocksBridgeProvider.swift in Sources */,
7AB931262D43D22F005FCEBA /* MullvadApiResponse.swift in Sources */,
+ A98207F32D9ACE4C00654558 /* MullvadAccessMethodReceiver.swift in Sources */,
A9173C322C36CCDD00F6A08C /* EphemeralPeerReceiver.swift in Sources */,
+ A96D0B452D675F0400DD6C59 /* MullvadConnectionModeProvider.swift in Sources */,
7A99D3712D56222000891FF7 /* MullvadApiCancellable.swift in Sources */,
A93969812CE606190032A7A0 /* Maybenot.swift in Sources */,
F05919802C45515200C301F3 /* EphemeralPeerExchangeActor.swift in Sources */,
@@ -7091,6 +7121,11 @@
target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */;
targetProxy = A9173C332C36CCFB00F6A08C /* PBXContainerItemProxy */;
};
+ A959E2402D75F37600F95DDB /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 58D223D4294C8E5E0029F5F8 /* MullvadTypes */;
+ targetProxy = A959E23F2D75F37600F95DDB /* PBXContainerItemProxy */;
+ };
A9609B6E2D004D1F0065A3D3 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 58FBDA9722A519BC00EB69A3 /* WireGuardGoBridge */;
diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
index 8e3306fc2b..f2cc3507bd 100644
--- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
+++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme
@@ -310,6 +310,11 @@
value = "1"
isEnabled = "YES">
</EnvironmentVariable>
+ <EnvironmentVariable
+ key = "RUST_BACKTRACE"
+ value = "1"
+ isEnabled = "YES">
+ </EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
diff --git a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
index c28598c383..6ebc7aa655 100644
--- a/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
+++ b/ios/MullvadVPN/AccessMethodRepository/ProxyConfigurationTesterProtocol.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import MullvadSettings
+import MullvadTypes
/// Type implementing access method proxy configuration testing.
protocol ProxyConfigurationTesterProtocol {
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 16d40ce140..2b8b15042d 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -48,12 +48,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
nonisolated(unsafe) private(set) var accessMethodRepository = AccessMethodRepository()
private(set) var appPreferences = AppPreferences()
- private(set) var shadowsocksLoader: ShadowsocksLoaderProtocol!
+ private(set) var shadowsocksLoader: ShadowsocksLoader!
private(set) var configuredTransportProvider: ProxyConfigurationTransportProvider!
private(set) var ipOverrideRepository = IPOverrideRepository()
private(set) var relaySelector: RelaySelectorWrapper!
private var launchArguments = LaunchArguments()
private var encryptedDNSTransport: EncryptedDNSTransport!
+ var apiContext: MullvadApiContext!
+ var accessMethodReceiver: MullvadAccessMethodReceiver!
// MARK: - Application lifecycle
@@ -77,8 +79,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
addressCache = REST.AddressCache(canWriteToCache: true, cacheDirectory: containerURL)
addressCache.loadFromFile()
- setUpProxies(containerURL: containerURL)
-
let ipOverrideWrapper = IPOverrideWrapper(
relayCache: RelayCache(cacheDirectory: containerURL),
ipOverrideRepository: ipOverrideRepository
@@ -87,6 +87,38 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let tunnelSettingsListener = TunnelSettingsListener()
let tunnelSettingsUpdater = SettingsUpdater(listener: tunnelSettingsListener)
+ let shadowsocksCache = ShadowsocksConfigurationCache(cacheDirectory: containerURL)
+ let shadowsocksRelaySelector = ShadowsocksRelaySelector(
+ relayCache: ipOverrideWrapper
+ )
+
+ shadowsocksLoader = ShadowsocksLoader(
+ cache: shadowsocksCache,
+ relaySelector: shadowsocksRelaySelector,
+ settingsUpdater: tunnelSettingsUpdater
+ )
+
+ let transportStrategy = TransportStrategy(
+ datasource: accessMethodRepository,
+ shadowsocksLoader: shadowsocksLoader
+ )
+
+ // swiftlint:disable:next force_try
+ apiContext = try! MullvadApiContext(
+ host: REST.defaultAPIHostname,
+ address: REST.defaultAPIEndpoint.description,
+ domain: REST.encryptedDNSHostname,
+ shadowsocksProvider: shadowsocksLoader,
+ accessMethodWrapper: transportStrategy.opaqueAccessMethodSettingsWrapper
+ )
+
+ accessMethodReceiver = MullvadAccessMethodReceiver(
+ apiContext: apiContext,
+ accessMethodsDataSource: accessMethodRepository.accessMethodsPublisher,
+ lastReachableDataSource: accessMethodRepository.lastReachableAccessMethodPublisher
+ )
+
+ setUpProxies(containerURL: containerURL)
let backgroundTaskProvider = BackgroundTaskProvider(
backgroundTimeRemaining: application.backgroundTimeRemaining,
application: application
@@ -126,18 +158,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
accountsProxy: accountsProxy,
transactionLog: .default
)
- let urlSessionTransport = URLSessionTransport(urlSession: REST.makeURLSession(addressCache: addressCache))
- let shadowsocksCache = ShadowsocksConfigurationCache(cacheDirectory: containerURL)
- let shadowsocksRelaySelector = ShadowsocksRelaySelector(
- relayCache: ipOverrideWrapper
- )
-
- shadowsocksLoader = ShadowsocksLoader(
- cache: shadowsocksCache,
- relaySelector: shadowsocksRelaySelector,
- settingsUpdater: tunnelSettingsUpdater
- )
+ let urlSessionTransport = URLSessionTransport(urlSession: REST.makeURLSession(addressCache: addressCache))
encryptedDNSTransport = EncryptedDNSTransport(urlSession: urlSessionTransport.urlSession)
configuredTransportProvider = ProxyConfigurationTransportProvider(
@@ -146,11 +168,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
encryptedDNSTransport: encryptedDNSTransport
)
- let transportStrategy = TransportStrategy(
- datasource: accessMethodRepository,
- shadowsocksLoader: shadowsocksLoader
- )
-
let transportProvider = TransportProvider(
urlSessionTransport: urlSessionTransport,
addressCache: addressCache,
@@ -158,7 +175,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
encryptedDNSTransport: encryptedDNSTransport
)
- let apiRequestFactory = MullvadApiRequestFactory(apiContext: REST.apiContext)
+ let apiRequestFactory = MullvadApiRequestFactory(apiContext: apiContext)
let apiTransportProvider = APITransportProvider(requestFactory: apiRequestFactory)
apiTransportMonitor = APITransportMonitor(
@@ -208,8 +225,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
},
apiTransportProvider: REST.AnyAPITransportProvider { [weak self] in
self?.apiTransportMonitor.makeTransport()
- },
- addressCache: addressCache
+ }, addressCache: addressCache
)
} else {
proxyFactory = REST.ProxyFactory.makeProxyFactory(
@@ -218,8 +234,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
},
apiTransportProvider: REST.AnyAPITransportProvider { [weak self] in
self?.apiTransportMonitor.makeTransport()
- },
- addressCache: addressCache
+ }, addressCache: addressCache
)
}
apiProxy = proxyFactory.createAPIProxy()
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
index 55591b8a08..cd456d2fc5 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Add/AddAccessMethodCoordinator.swift
@@ -8,6 +8,7 @@
import Combine
import MullvadSettings
+import MullvadTypes
import Routing
import UIKit
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/AccessMethodViewModelEditing.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/AccessMethodViewModelEditing.swift
index a0ea080a06..5eaefc97cb 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/AccessMethodViewModelEditing.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Common/AccessMethodViewModelEditing.swift
@@ -6,7 +6,7 @@
// Copyright © 2025 Mullvad VPN AB. All rights reserved.
//
-import MullvadSettings
+import MullvadTypes
protocol AccessMethodEditing: AnyObject {
func accessMethodDidSave(_ accessMethod: PersistentAccessMethod)
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
index c4d1894381..3d249c841a 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Edit/EditAccessMethodCoordinator.swift
@@ -8,6 +8,7 @@
import Combine
import MullvadSettings
+import MullvadTypes
import Routing
import UIKit
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodInteractor.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodInteractor.swift
index ac10ffa484..e262f7bcab 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodInteractor.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/List/ListAccessMethodInteractor.swift
@@ -8,6 +8,7 @@
import Combine
import MullvadSettings
+import MullvadTypes
/// A concrete implementation of an API access list interactor.
struct ListAccessMethodInteractor: ListAccessMethodInteractorProtocol {
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift
index 571e26f3fd..814a3da68d 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/AccessMethodKind.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadSettings
+import MullvadTypes
/// A kind of API access method.
enum AccessMethodKind: Equatable, Hashable, CaseIterable {
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentAccessMethod+ViewModel.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentAccessMethod+ViewModel.swift
index 3bc58f51b3..1d699eed91 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentAccessMethod+ViewModel.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentAccessMethod+ViewModel.swift
@@ -8,6 +8,7 @@
import Foundation
import MullvadSettings
+import MullvadTypes
extension PersistentAccessMethod {
/// Convert persistent model into view model.
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentProxyConfiguration+ViewModel.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentProxyConfiguration+ViewModel.swift
index 81bd4e193f..10f806950c 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentProxyConfiguration+ViewModel.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Models/PersistentProxyConfiguration+ViewModel.swift
@@ -7,7 +7,7 @@
//
import Foundation
-import MullvadSettings
+import MullvadTypes
extension PersistentProxyConfiguration {
/// View model for socks configuration.
diff --git a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
index c2a7b524ee..16711f31a4 100644
--- a/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
+++ b/ios/MullvadVPN/Coordinators/Settings/APIAccess/Pickers/ShadowsocksCipherPicker.swift
@@ -7,6 +7,7 @@
//
import MullvadSettings
+import MullvadTypes
import UIKit
/// Type implementing the shadowsocks cipher picker.
diff --git a/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift b/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift
index 9c602d0eae..72e1ae4cb0 100644
--- a/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift
+++ b/ios/MullvadVPNTests/MullvadSettings/APIAccessMethodsTests.swift
@@ -8,6 +8,7 @@
import Combine
@testable import MullvadSettings
+@testable import MullvadTypes
import XCTest
final class APIAccessMethodsTests: XCTestCase {
diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
index b44927f931..391e637e23 100644
--- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
+++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift
@@ -8,6 +8,7 @@
@testable import MullvadREST
@testable import MullvadMockData
+@testable import MullvadRustRuntime
@testable import MullvadSettings
@testable import MullvadTypes
@testable import WireGuardKitTypes
@@ -25,8 +26,8 @@ class TunnelManagerTests: XCTestCase {
var devicesProxy: DevicesProxyStub!
var apiProxy: APIProxyStub!
var addressCache: REST.AddressCache!
-
var transportProvider: TransportProvider!
+ var apiContext: MullvadApiContext!
override static func setUp() {
SettingsManager.unitTestStore = store
@@ -43,6 +44,15 @@ class TunnelManagerTests: XCTestCase {
accessTokenManager = AccessTokenManagerStub()
devicesProxy = DevicesProxyStub(deviceResult: .success(Device.mock(publicKey: PrivateKey().publicKey)))
apiProxy = APIProxyStub()
+ let shadowsocksLoader = ShadowsocksLoader(
+ cache: ShadowsocksConfigurationCacheStub(),
+ relaySelector: ShadowsocksRelaySelectorStub(relays: .mock()),
+ settingsUpdater: SettingsUpdater(listener: TunnelSettingsListener())
+ )
+ let transportStrategy = TransportStrategy(
+ datasource: AccessMethodRepositoryStub.stub,
+ shadowsocksLoader: shadowsocksLoader
+ )
addressCache = REST.AddressCache(
canWriteToCache: false,
fileCache: MockFileCache(initialState: .fileNotFound)
@@ -54,19 +64,16 @@ class TunnelManagerTests: XCTestCase {
canWriteToCache: true,
cacheDirectory: FileManager.default.temporaryDirectory
),
- transportStrategy: TransportStrategy(
- datasource: AccessMethodRepositoryStub(accessMethods: [PersistentAccessMethod(
- id: UUID(),
- name: "direct",
- isEnabled: true,
- proxyConfiguration: .direct
- )]),
- shadowsocksLoader: ShadowsocksLoader(
- cache: ShadowsocksConfigurationCacheStub(),
- relaySelector: ShadowsocksRelaySelectorStub(relays: .mock()),
- settingsUpdater: SettingsUpdater(listener: TunnelSettingsListener())
- )
- ), encryptedDNSTransport: RESTTransportStub()
+ transportStrategy: transportStrategy,
+ encryptedDNSTransport: RESTTransportStub()
+ )
+
+ apiContext = try MullvadApiContext(
+ host: REST.defaultAPIHostname,
+ address: REST.defaultAPIEndpoint.description,
+ domain: REST.encryptedDNSHostname,
+ shadowsocksProvider: shadowsocksLoader,
+ accessMethodWrapper: transportStrategy.opaqueAccessMethodSettingsWrapper
)
try SettingsManager.writeSettings(LatestTunnelSettings())
@@ -149,7 +156,7 @@ class TunnelManagerTests: XCTestCase {
relaySelector: relaySelector,
transportProvider: transportProvider,
apiTransportProvider: APITransportProvider(
- requestFactory: MullvadApiRequestFactory(apiContext: REST.apiContext)
+ requestFactory: MullvadApiRequestFactory(apiContext: apiContext)
)
)
SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost
@@ -220,7 +227,7 @@ class TunnelManagerTests: XCTestCase {
relaySelector: relaySelector,
transportProvider: transportProvider,
apiTransportProvider: APITransportProvider(
- requestFactory: MullvadApiRequestFactory(apiContext: REST.apiContext)
+ requestFactory: MullvadApiRequestFactory(apiContext: apiContext)
)
)
SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost
diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
index 4b943396a6..90f1aa8d69 100644
--- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
+++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift
@@ -37,6 +37,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
EphemeralPeerReceiver(tunnelProvider: adapter, keyReceiver: self)
}()
+ var apiContext: MullvadApiContext!
+ var accessMethodReceiver: MullvadAccessMethodReceiver!
+
// swiftlint:disable:next function_body_length
override init() {
Self.configureLogging()
@@ -65,7 +68,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
)
let apiTransportProvider = APITransportProvider(
- requestFactory: MullvadApiRequestFactory(apiContext: REST.apiContext)
+ requestFactory: MullvadApiRequestFactory(apiContext: apiContext)
)
adapter = WgAdapter(packetTunnelProvider: self)
@@ -241,13 +244,32 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable {
relayCache: ipOverrideWrapper
)
+ let shadowsocksLoader = ShadowsocksLoader(
+ cache: shadowsocksCache,
+ relaySelector: shadowsocksRelaySelector,
+ settingsUpdater: tunnelSettingsUpdater
+ )
+
+ let accessMethodRepository = AccessMethodRepository()
+
let transportStrategy = TransportStrategy(
- datasource: AccessMethodRepository(),
- shadowsocksLoader: ShadowsocksLoader(
- cache: shadowsocksCache,
- relaySelector: shadowsocksRelaySelector,
- settingsUpdater: tunnelSettingsUpdater
- )
+ datasource: accessMethodRepository,
+ shadowsocksLoader: shadowsocksLoader
+ )
+
+ // swiftlint:disable:next force_try
+ apiContext = try! MullvadApiContext(
+ host: REST.defaultAPIHostname,
+ address: REST.defaultAPIEndpoint.description,
+ domain: REST.encryptedDNSHostname,
+ shadowsocksProvider: shadowsocksLoader,
+ accessMethodWrapper: transportStrategy.opaqueAccessMethodSettingsWrapper
+ )
+
+ accessMethodReceiver = MullvadAccessMethodReceiver(
+ apiContext: apiContext,
+ accessMethodsDataSource: accessMethodRepository.accessMethodsPublisher,
+ lastReachableDataSource: accessMethodRepository.lastReachableAccessMethodPublisher
)
encryptedDNSTransport = EncryptedDNSTransport(urlSession: urlSession)
diff --git a/mullvad-api/src/access_mode.rs b/mullvad-api/src/access_mode.rs
index 25b739a014..ecd90b9d82 100644
--- a/mullvad-api/src/access_mode.rs
+++ b/mullvad-api/src/access_mode.rs
@@ -190,7 +190,7 @@ pub struct AccessModeConnectionModeProvider {
}
impl AccessModeConnectionModeProvider {
- fn new(
+ pub fn new(
handle: AccessModeSelectorHandle,
initial_connection_mode: ApiConnectionMode,
change_rx: mpsc::UnboundedReceiver<ApiConnectionMode>,
@@ -234,6 +234,7 @@ pub struct AccessModeSelector<B: AccessMethodResolver> {
cmd_rx: mpsc::UnboundedReceiver<Message>,
method_resolver: B,
access_method_settings: Settings,
+ #[cfg(not(target_os = "ios"))]
access_method_event_sender: mpsc::UnboundedSender<(AccessMethodEvent, oneshot::Sender<()>)>,
connection_mode_provider_sender: mpsc::UnboundedSender<ApiConnectionMode>,
current: ResolvedConnectionMode,
@@ -247,7 +248,10 @@ impl<B: AccessMethodResolver + 'static> AccessModeSelector<B> {
#[cfg_attr(not(feature = "api-override"), allow(unused_mut))]
mut access_method_settings: Settings,
#[cfg(feature = "api-override")] api_endpoint: ApiEndpoint,
- access_method_event_sender: mpsc::UnboundedSender<(AccessMethodEvent, oneshot::Sender<()>)>,
+ #[cfg(not(target_os = "ios"))] access_method_event_sender: mpsc::UnboundedSender<(
+ AccessMethodEvent,
+ oneshot::Sender<()>,
+ )>,
) -> Result<(AccessModeSelectorHandle, AccessModeConnectionModeProvider)> {
let (cmd_tx, cmd_rx) = mpsc::unbounded();
@@ -273,6 +277,7 @@ impl<B: AccessMethodResolver + 'static> AccessModeSelector<B> {
cmd_rx,
method_resolver,
access_method_settings,
+ #[cfg(not(target_os = "ios"))]
access_method_event_sender,
connection_mode_provider_sender: change_tx,
current: initial_connection_mode,
@@ -380,6 +385,24 @@ impl<B: AccessMethodResolver + 'static> AccessModeSelector<B> {
async fn set_current(&mut self, access_method: AccessMethodSetting) {
let resolved = Self::resolve_with_default(&access_method, &mut self.method_resolver).await;
+ #[cfg(not(target_os = "ios"))]
+ self.notify_daemon(&resolved);
+
+ // Notify REST client
+ let _ = self
+ .connection_mode_provider_sender
+ .unbounded_send(resolved.connection_mode.clone());
+
+ self.current = resolved;
+
+ log::info!(
+ "A new API access method has been selected: {name}",
+ name = self.current.setting.name
+ );
+ }
+
+ #[cfg(not(target_os = "ios"))]
+ fn notify_daemon(&mut self, resolved: &ResolvedConnectionMode) {
// Note: If the daemon is busy waiting for a call to this function
// to complete while we wait for the daemon to fully handle this
// `NewAccessMethodEvent`, then we find ourselves in a deadlock.
@@ -402,18 +425,6 @@ impl<B: AccessMethodResolver + 'static> AccessModeSelector<B> {
.send(sender)
.await;
});
-
- // Notify REST client
- let _ = self
- .connection_mode_provider_sender
- .unbounded_send(resolved.connection_mode.clone());
-
- self.current = resolved;
-
- log::info!(
- "A new API access method has been selected: {name}",
- name = self.current.setting.name
- );
}
/// Find the next access method to use.
diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs
index d535ce7cbc..2d068fdf24 100644
--- a/mullvad-api/src/lib.rs
+++ b/mullvad-api/src/lib.rs
@@ -479,8 +479,8 @@ impl Runtime {
)
}
- pub fn handle(&mut self) -> &mut tokio::runtime::Handle {
- &mut self.handle
+ pub fn handle(&self) -> &tokio::runtime::Handle {
+ &self.handle
}
pub fn availability_handle(&self) -> ApiAvailability {
diff --git a/mullvad-ios/Cargo.toml b/mullvad-ios/Cargo.toml
index 7a87c03079..519f7852c9 100644
--- a/mullvad-ios/Cargo.toml
+++ b/mullvad-ios/Cargo.toml
@@ -15,6 +15,7 @@ workspace = true
api-override = ["mullvad-api/api-override"]
[target.'cfg(target_os = "ios")'.dependencies]
+futures = { workspace = true }
libc = "0.2"
log = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
@@ -29,8 +30,10 @@ talpid-types = { path = "../talpid-types" }
talpid-tunnel-config-client = { path = "../talpid-tunnel-config-client" }
mullvad-encrypted-dns-proxy = { path = "../mullvad-encrypted-dns-proxy" }
mullvad-api = { path = "../mullvad-api", default-features = false }
+mullvad-types = { path = "../mullvad-types" }
serde_json = { workspace = true }
mockito = "1.6.1"
+async-trait = "0.1"
shadowsocks-service = { workspace = true, features = [
"local",
diff --git a/mullvad-ios/src/api_client/access_method_resolver.rs b/mullvad-ios/src/api_client/access_method_resolver.rs
new file mode 100644
index 0000000000..5ce22cc5cc
--- /dev/null
+++ b/mullvad-ios/src/api_client/access_method_resolver.rs
@@ -0,0 +1,89 @@
+use mullvad_api::{
+ access_mode::AccessMethodResolver,
+ proxy::{ApiConnectionMode, ProxyConfig},
+ ApiEndpoint,
+};
+use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState;
+use mullvad_types::access_method::{AccessMethod, BuiltInAccessMethod};
+use talpid_types::net::{
+ proxy::CustomProxy, AllowedClients, AllowedEndpoint, Endpoint, TransportProtocol,
+};
+use tonic::async_trait;
+
+use super::shadowsocks_loader::SwiftShadowsocksLoaderWrapper;
+
+#[derive(Debug)]
+pub struct SwiftAccessMethodResolver {
+ endpoint: ApiEndpoint,
+ domain: String,
+ state: EncryptedDnsProxyState,
+ bridge_provider: SwiftShadowsocksLoaderWrapper,
+}
+
+impl SwiftAccessMethodResolver {
+ pub fn new(
+ endpoint: ApiEndpoint,
+ domain: String,
+ state: EncryptedDnsProxyState,
+ bridge_provider: SwiftShadowsocksLoaderWrapper,
+ ) -> Self {
+ Self {
+ endpoint,
+ domain,
+ state,
+ bridge_provider,
+ }
+ }
+}
+
+#[async_trait]
+impl AccessMethodResolver for SwiftAccessMethodResolver {
+ async fn resolve_access_method_setting(
+ &mut self,
+ access_method: &AccessMethod,
+ ) -> Option<(AllowedEndpoint, ApiConnectionMode)> {
+ let connection_mode = match access_method {
+ AccessMethod::BuiltIn(BuiltInAccessMethod::Direct) => ApiConnectionMode::Direct,
+ AccessMethod::BuiltIn(BuiltInAccessMethod::Bridge) => {
+ let bridge = self.bridge_provider.get_bridges()?;
+ let proxy = CustomProxy::Shadowsocks(bridge);
+ ApiConnectionMode::Proxied(ProxyConfig::from(proxy))
+ }
+ AccessMethod::BuiltIn(BuiltInAccessMethod::EncryptedDnsProxy) => {
+ if let Err(error) = self.state.fetch_configs(self.domain.as_str()).await {
+ log::error!("{error:#?}");
+ }
+ let Some(edp) = self.state.next_configuration() else {
+ log::warn!("Could not select next Encrypted DNS proxy config");
+ return None;
+ };
+ ApiConnectionMode::Proxied(ProxyConfig::from(edp))
+ }
+ AccessMethod::Custom(config) => {
+ ApiConnectionMode::Proxied(ProxyConfig::from(config.clone()))
+ }
+ };
+
+ let allowed_endpoint = {
+ let endpoint = connection_mode.get_endpoint().unwrap_or_else(|| {
+ Endpoint::from_socket_address(
+ self.endpoint.address.unwrap(),
+ TransportProtocol::Tcp,
+ )
+ });
+ let clients = AllowedClients::All;
+ AllowedEndpoint { endpoint, clients }
+ };
+
+ Some((allowed_endpoint, connection_mode))
+ }
+
+ async fn default_connection_mode(&self) -> AllowedEndpoint {
+ // TODO: Call the iOS Address cache implementation instead of returning the default endpoint
+ let endpoint = ApiConnectionMode::Direct.get_endpoint().unwrap();
+ AllowedEndpoint {
+ endpoint,
+ clients: AllowedClients::All,
+ }
+ }
+}
diff --git a/mullvad-ios/src/api_client/access_method_settings.rs b/mullvad-ios/src/api_client/access_method_settings.rs
new file mode 100644
index 0000000000..35a90d1f0c
--- /dev/null
+++ b/mullvad-ios/src/api_client/access_method_settings.rs
@@ -0,0 +1,193 @@
+use std::{
+ ffi::{c_char, c_void},
+ ptr::null_mut,
+ slice,
+};
+
+use mullvad_types::access_method::{
+ AccessMethod, AccessMethodSetting,
+ BuiltInAccessMethod::{Bridge, Direct, EncryptedDnsProxy},
+ Id, Settings,
+};
+use talpid_types::net::proxy::{self, Shadowsocks, Socks5Remote};
+
+use super::helpers::convert_c_string;
+
+/// Converts parameters into a `Box<AccessMethodSetting>` raw representation that
+/// can be passed across the FFI boundary
+///
+/// # SAFETY:
+/// `unique_identifier` and `name` must point to valid memory regions and contain NULL terminators.
+/// They are only valid for the duration of this call.
+///
+/// `proxy_configuration` can be NULL, or must be a pointer gotten through
+/// either the `convert_shadowsocks` or `convert_socks5` methods.
+#[unsafe(no_mangle)]
+unsafe extern "C" fn convert_builtin_access_method_setting(
+ unique_identifier: *const c_char,
+ name: *const c_char,
+ is_enabled: bool,
+ method_kind: SwiftAccessMethodKind,
+ proxy_configuration: *const c_void,
+) -> *mut c_void {
+ match convert_builtin_access_method_setting_inner(
+ unique_identifier,
+ name,
+ is_enabled,
+ method_kind,
+ proxy_configuration,
+ ) {
+ Some(access_method) => Box::into_raw(Box::new(access_method)) as *mut c_void,
+ None => null_mut(),
+ }
+}
+
+/// Converts parameters into an `AccessMethodSetting`
+///
+/// This function copies the strings from the conversion of the variables
+/// `unique_identifier`, `name`, and takes ownership of `proxy_configuration`
+fn convert_builtin_access_method_setting_inner(
+ unique_identifier: *const c_char,
+ name: *const c_char,
+ enabled: bool,
+ method_kind: SwiftAccessMethodKind,
+ proxy_configuration: *const c_void,
+) -> Option<AccessMethodSetting> {
+ // SAFETY: See `convert_builtin_access_method_setting`
+ let id = Id::from_string(unsafe { convert_c_string(unique_identifier) })?;
+ // SAFETY: See `convert_builtin_access_method_setting`
+ let name = unsafe { convert_c_string(name) };
+ match method_kind {
+ SwiftAccessMethodKind::KindDirect => Some(AccessMethodSetting::with_id(
+ id,
+ name,
+ enabled,
+ AccessMethod::BuiltIn(Direct),
+ )),
+ SwiftAccessMethodKind::KindBridge => Some(AccessMethodSetting::with_id(
+ id,
+ name,
+ enabled,
+ AccessMethod::BuiltIn(Bridge),
+ )),
+
+ SwiftAccessMethodKind::KindEncryptedDnsProxy => Some(AccessMethodSetting::with_id(
+ id,
+ name,
+ enabled,
+ AccessMethod::BuiltIn(EncryptedDnsProxy),
+ )),
+
+ SwiftAccessMethodKind::KindShadowsocks => match proxy_configuration.is_null() {
+ true => None,
+ false => {
+ // SAFETY: See `convert_builtin_access_method_setting`
+ let configuration: Shadowsocks =
+ unsafe { *Box::from_raw(proxy_configuration as *mut _) };
+ Some(AccessMethodSetting::with_id(
+ id,
+ name,
+ enabled,
+ AccessMethod::Custom(proxy::CustomProxy::Shadowsocks(configuration)),
+ ))
+ }
+ },
+ SwiftAccessMethodKind::KindSocks5Local => match proxy_configuration.is_null() {
+ true => None,
+ false => {
+ // SAFETY: See `convert_builtin_access_method_setting`
+ let configuration: Socks5Remote =
+ unsafe { *Box::from_raw(proxy_configuration as *mut _) };
+ Some(AccessMethodSetting::with_id(
+ id,
+ name,
+ enabled,
+ AccessMethod::Custom(proxy::CustomProxy::Socks5Remote(configuration)),
+ ))
+ }
+ },
+ }
+}
+
+/// Used by Swift to instruct which access method kind it is trying to convert
+#[allow(dead_code)]
+#[repr(u8)]
+pub enum SwiftAccessMethodKind {
+ KindDirect = 0,
+ KindBridge,
+ KindEncryptedDnsProxy,
+ KindShadowsocks,
+ KindSocks5Local,
+}
+
+/// Creates a wrapper around a `Settings` object that can be safely sent across the FFI boundary.
+///
+/// # SAFETY
+/// `direct_method_raw`, `bridges_method_raw` and `encrypted_dns_method_raw` must be raw pointers
+/// resulting from a call to `convert_builtin_access_method_setting`
+/// `custom_methods_raw` is an array of pointers to instances of `AccessMethodSetting`
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn init_access_method_settings_wrapper(
+ direct_method_raw: *const c_void,
+ bridges_method_raw: *const c_void,
+ encrypted_dns_method_raw: *const c_void,
+ custom_methods_raw: *const c_void,
+ custom_method_count: usize,
+) -> SwiftAccessMethodSettingsWrapper {
+ // SAFETY: See `init_access_method_settings_wrapper`
+ let (direct, mullvad_bridges, encrypted_dns_proxy) = unsafe {
+ (
+ *Box::from_raw(direct_method_raw as *mut _),
+ *Box::from_raw(bridges_method_raw as *mut _),
+ *Box::from_raw(encrypted_dns_method_raw as *mut _),
+ )
+ };
+
+ let custom = access_methods_from_raw_array(custom_methods_raw, custom_method_count);
+ let settings = Settings::new(direct, mullvad_bridges, encrypted_dns_proxy, custom);
+ let context = SwiftAccessMethodSettingsContext { settings };
+ SwiftAccessMethodSettingsWrapper::new(context)
+}
+
+/// Creates a vector of `AccessMethodSetting` objects from a C array
+///
+/// SAFETY: `raw_array` must be aligned, non-null and initialized for `count` reads
+unsafe fn access_methods_from_raw_array(
+ raw_array: *const c_void,
+ number_of_elements: usize,
+) -> Vec<AccessMethodSetting> {
+ let raw_array: *mut *mut AccessMethodSetting = raw_array as _;
+ // SAFETY: See notice above
+ let slice = unsafe { slice::from_raw_parts(raw_array, number_of_elements) };
+ slice
+ .iter()
+ .map(|&ptr| {
+ // SAFETY: `slice` is a slice of pointers to `AccessMethodSetting` created with `Box::into_raw`
+ *unsafe { Box::from_raw(ptr) }
+ })
+ .collect()
+}
+
+#[repr(C)]
+pub struct SwiftAccessMethodSettingsWrapper(*mut SwiftAccessMethodSettingsContext);
+
+impl SwiftAccessMethodSettingsWrapper {
+ pub fn new(context: SwiftAccessMethodSettingsContext) -> SwiftAccessMethodSettingsWrapper {
+ SwiftAccessMethodSettingsWrapper(Box::into_raw(Box::new(context)))
+ }
+
+ pub unsafe fn into_rust_context(self) -> Box<SwiftAccessMethodSettingsContext> {
+ Box::from_raw(self.0)
+ }
+}
+
+#[derive(Debug)]
+pub struct SwiftAccessMethodSettingsContext {
+ pub settings: Settings,
+}
+
+impl SwiftAccessMethodSettingsContext {
+ pub fn convert_access_method(&self) -> Option<Settings> {
+ Some(self.settings.clone())
+ }
+}
diff --git a/mullvad-ios/src/api_client/account.rs b/mullvad-ios/src/api_client/account.rs
index 768832fcb7..36b601ad2d 100644
--- a/mullvad-ios/src/api_client/account.rs
+++ b/mullvad-ios/src/api_client/account.rs
@@ -41,7 +41,8 @@ pub unsafe extern "C" fn mullvad_ios_get_account(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See documentation for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
// SAFETY: See param documentation for `account_number`.
let account_number = unsafe { CStr::from_ptr(account_number.cast()) }
@@ -92,7 +93,8 @@ pub unsafe extern "C" fn mullvad_ios_create_account(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See notes for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
let completion = completion_handler.clone();
@@ -135,7 +137,8 @@ pub unsafe extern "C" fn mullvad_ios_delete_account(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See notes for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
// SAFETY: See param documentation for `account_number`.
let account_number = unsafe { CStr::from_ptr(account_number.cast()) }
diff --git a/mullvad-ios/src/api_client/api.rs b/mullvad-ios/src/api_client/api.rs
index 62060dfacb..ed37695f6f 100644
--- a/mullvad-ios/src/api_client/api.rs
+++ b/mullvad-ios/src/api_client/api.rs
@@ -38,7 +38,8 @@ pub unsafe extern "C" fn mullvad_ios_get_addresses(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See notes for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
let completion = completion_handler.clone();
@@ -81,7 +82,8 @@ pub unsafe extern "C" fn mullvad_ios_get_relays(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See notes for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
let mut maybe_etag: Option<String> = None;
diff --git a/mullvad-ios/src/api_client/cancellation.rs b/mullvad-ios/src/api_client/cancellation.rs
index 3c18340478..5ea2c902c1 100644
--- a/mullvad-ios/src/api_client/cancellation.rs
+++ b/mullvad-ios/src/api_client/cancellation.rs
@@ -16,11 +16,13 @@ impl SwiftCancelHandle {
/// This consumes and nulls out the pointer. The caller is responsible for the pointer being valid
/// when calling `to_handle`.
+ ///
+ /// SAFETY:
+ /// This call is safe as long as the pointer is only ever used from a single thread and the
+ /// instance of `SwiftCancelHandle` was created with a valid pointer to
+ /// `RequestCancelHandle`.
unsafe fn into_handle(mut self) -> RequestCancelHandle {
- // # Safety
- // This call is safe as long as the pointer is only ever used from a single thread and the
- // instance of `SwiftCancelHandle` was created with a valid pointer to
- // `RequestCancelHandle`.
+ // SAFETY: See safety notes above
let handle = unsafe { *Box::from_raw(self.ptr) };
self.ptr = null_mut();
@@ -67,6 +69,7 @@ extern "C" fn mullvad_api_cancel_task(handle_ptr: SwiftCancelHandle) {
return;
}
+ // SAFETY: See notes for `into_handle`
let handle = unsafe { handle_ptr.into_handle() };
handle.cancel()
}
@@ -84,5 +87,6 @@ extern "C" fn mullvad_api_cancel_task_drop(handle_ptr: SwiftCancelHandle) {
return;
}
+ // SAFETY: See notes for `into_handle`
let _handle = unsafe { handle_ptr.into_handle() };
}
diff --git a/mullvad-ios/src/api_client/completion.rs b/mullvad-ios/src/api_client/completion.rs
index db15a6b8b6..7689ad0f6a 100644
--- a/mullvad-ios/src/api_client/completion.rs
+++ b/mullvad-ios/src/api_client/completion.rs
@@ -23,6 +23,8 @@ extern "C" {
pub struct CompletionCookie {
inner: *mut std::ffi::c_void,
}
+/// SAFETY: Access to `CompletionCookie` should always be done through a `SwiftCompletionHandler`
+/// It is safe to be used and sent from any threads.
unsafe impl Send for CompletionCookie {}
impl CompletionCookie {
/// `inner` must be pointing to a valid instance of Swift object `MullvadApiCompletion`.
@@ -54,6 +56,7 @@ impl SwiftCompletionHandler {
return;
};
+ // SAFETY: See safety notes for `mullvad_api_completion_finish`
unsafe { mullvad_api_completion_finish(response, cookie) };
}
}
diff --git a/mullvad-ios/src/api_client/helpers.rs b/mullvad-ios/src/api_client/helpers.rs
new file mode 100644
index 0000000000..f331e56a9b
--- /dev/null
+++ b/mullvad-ios/src/api_client/helpers.rs
@@ -0,0 +1,108 @@
+use std::{
+ ffi::{c_char, c_void, CStr},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
+};
+
+use talpid_types::net::proxy::{Shadowsocks, Socks5Remote, SocksAuth};
+
+/// Constructs a new IP address from a pointer containing bytes representing an IP address.
+///
+/// SAFETY: `addr` pointer must be non-null, aligned, and point to at least addr_len bytes
+pub(crate) unsafe fn parse_ip_addr(addr: *const u8, addr_len: usize) -> Option<IpAddr> {
+ match addr_len {
+ 4 => {
+ // SAFETY: `addr` pointer must be non-null, aligned, and point to at least addr_len bytes
+ let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
+ Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).into())
+ }
+ 16 => {
+ // SAFETY: `addr` pointer must be non-null, aligned, and point to at least addr_len bytes
+ let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
+ let mut addr_arr = [0u8; 16];
+ addr_arr.as_mut_slice().copy_from_slice(bytes);
+
+ Some(Ipv6Addr::from(addr_arr).into())
+ }
+ anything_else => {
+ log::error!("Bad IP address length {anything_else}");
+ None
+ }
+ }
+}
+
+/// Converts a pointer to a C style string into an owned Rust `String`
+///
+/// # SAFETY
+/// `c_str` must point to a valid, null terminated C string.
+pub unsafe fn convert_c_string(c_str: *const c_char) -> String {
+ // SAFETY: c_str points to a valid region of memory and contains a null terminator.
+ let str = unsafe { CStr::from_ptr(c_str) };
+ String::from_utf8_lossy(str.to_bytes()).into_owned()
+}
+
+/// Converts parameters into a boxed `Shadowsocks` configuration that is safe
+/// to send across the FFI boundary
+///
+/// # SAFETY
+/// `address` must be a pointer to at least `address_len` bytes.
+/// `c_password` and `c_cipher` must be pointers to null terminated strings
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn new_shadowsocks_access_method_setting(
+ address: *const u8,
+ address_len: usize,
+ port: u16,
+ c_password: *const c_char,
+ c_cipher: *const c_char,
+) -> *const c_void {
+ let endpoint: SocketAddr = if let Some(ip_address) = parse_ip_addr(address, address_len) {
+ SocketAddr::new(ip_address, port)
+ } else {
+ return std::ptr::null();
+ };
+
+ let password = convert_c_string(c_password);
+ let cipher = convert_c_string(c_cipher);
+
+ let shadowsocks_configuration = Shadowsocks {
+ endpoint,
+ password,
+ cipher,
+ };
+
+ Box::into_raw(Box::new(shadowsocks_configuration)) as *mut c_void
+}
+
+/// Converts parameters into a boxed `Socks5Remote` configuration that is safe
+///
+/// to send across the FFI boundary
+///
+/// # SAFETY
+/// `address` must be a pointer to at least `address_len` bytes.
+/// `c_username` and `c_password` must be pointers to null terminated strings, or null
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn new_socks5_access_method_setting(
+ address: *const u8,
+ address_len: usize,
+ port: u16,
+ c_username: *const c_char,
+ c_password: *const c_char,
+) -> *const c_void {
+ let endpoint: SocketAddr = if let Some(ip_address) = parse_ip_addr(address, address_len) {
+ SocketAddr::new(ip_address, port)
+ } else {
+ return std::ptr::null();
+ };
+
+ let auth = {
+ if c_username.is_null() || c_password.is_null() {
+ None
+ } else {
+ let username = convert_c_string(c_username);
+ let password = convert_c_string(c_password);
+ SocksAuth::new(username, password).ok()
+ }
+ };
+
+ let socks5_configuration = Socks5Remote { endpoint, auth };
+ Box::into_raw(Box::new(socks5_configuration)) as *mut c_void
+}
diff --git a/mullvad-ios/src/api_client/mock.rs b/mullvad-ios/src/api_client/mock.rs
index 1c8dd1a1e1..005a6c10c0 100644
--- a/mullvad-ios/src/api_client/mock.rs
+++ b/mullvad-ios/src/api_client/mock.rs
@@ -39,9 +39,11 @@ pub unsafe extern "C" fn mullvad_api_mock_get(
response_code: usize,
response_body: *const u8,
) -> SwiftServerMock {
+ // SAFETY: See notes above
let path = unsafe { std::ffi::CStr::from_ptr(path.cast()) }
.to_str()
.unwrap();
+ // SAFETY: See notes above
let response_body = unsafe { std::ffi::CStr::from_ptr(response_body.cast()) }
.to_str()
.unwrap();
@@ -70,9 +72,11 @@ pub unsafe extern "C" fn mullvad_api_mock_post(
response_code: usize,
match_body: *const c_char,
) -> SwiftServerMock {
+ // SAFETY: See notes above
let path = unsafe { std::ffi::CStr::from_ptr(path.cast()) }
.to_str()
.unwrap();
+ // SAFETY: See notes above
let match_body = unsafe { std::ffi::CStr::from_ptr(match_body.cast()) }
.to_str()
.unwrap();
@@ -96,9 +100,11 @@ pub unsafe extern "C" fn mullvad_api_mock_post(
#[unsafe(no_mangle)]
extern "C" fn mullvad_api_mock_drop(mock_ptr: SwiftServerMock) {
if !mock_ptr.mock_ptr.is_null() {
+ // SAFETY: See notes above
unsafe { drop(Box::from_raw(mock_ptr.mock_ptr as *mut Mock)) };
}
if !mock_ptr.server_ptr.is_null() {
+ // SAFETY: See notes above
unsafe { drop(Box::from_raw(mock_ptr.server_ptr as *mut ServerGuard)) };
}
}
diff --git a/mullvad-ios/src/api_client/mod.rs b/mullvad-ios/src/api_client/mod.rs
index 9ab6eef199..e8fe1ced24 100644
--- a/mullvad-ios/src/api_client/mod.rs
+++ b/mullvad-ios/src/api_client/mod.rs
@@ -1,22 +1,32 @@
-use std::{ffi::CStr, future::Future, sync::Arc};
+use std::{ffi::c_char, future::Future, sync::Arc};
+use access_method_resolver::SwiftAccessMethodResolver;
+use access_method_settings::SwiftAccessMethodSettingsWrapper;
+use helpers::convert_c_string;
use mullvad_api::{
- proxy::{ApiConnectionMode, StaticConnectionModeProvider},
+ access_mode::{AccessModeSelector, AccessModeSelectorHandle},
rest::{self, MullvadRestHandle},
ApiEndpoint, Runtime,
};
+use mullvad_encrypted_dns_proxy::state::EncryptedDnsProxyState;
+use mullvad_types::access_method::{Id, Settings};
use response::SwiftMullvadApiResponse;
use retry_strategy::RetryStrategy;
+use shadowsocks_loader::SwiftShadowsocksLoaderWrapper;
use talpid_future::retry::retry_future;
+mod access_method_resolver;
+mod access_method_settings;
mod account;
mod api;
mod cancellation;
mod completion;
+pub(super) mod helpers;
mod mock;
mod problem_report;
mod response;
mod retry_strategy;
+mod shadowsocks_loader;
#[repr(C)]
pub struct SwiftApiContext(*const ApiContext);
@@ -25,20 +35,82 @@ impl SwiftApiContext {
SwiftApiContext(Arc::into_raw(Arc::new(context)))
}
- pub unsafe fn into_rust_context(self) -> Arc<ApiContext> {
- Arc::increment_strong_count(self.0);
- Arc::from_raw(self.0)
+ /// Extracts an `ApiContext` from `self`
+ ///
+ /// The `ApiContext` extracted is meant to live as long as the process it's used in.
+ pub fn rust_context(self) -> Arc<ApiContext> {
+ // SAFETY: This will never be deallocated
+ unsafe {
+ Arc::increment_strong_count(self.0);
+ Arc::from_raw(self.0)
+ }
}
}
pub struct ApiContext {
- _api_client: Runtime,
+ api_client: Runtime,
rest_client: MullvadRestHandle,
+ access_mode_handler: AccessModeSelectorHandle,
}
impl ApiContext {
pub fn rest_handle(&self) -> MullvadRestHandle {
self.rest_client.clone()
}
+
+ /// Sets the access method referenced by `id` as currently in use.
+ ///
+ /// This function will block the current thread until it is complete,
+ /// make sure to not call this from a UI Thread if possible.
+ pub fn use_access_method(&self, id: Id) {
+ _ = self
+ .api_client
+ .handle()
+ .block_on(async { self.access_mode_handler.use_access_method(id).await });
+ }
+
+ /// Replaces the current set of access methods with `access_methods.
+ ///
+ /// This function will block the current thread until it is complete,
+ /// make sure to not call this from a UI Thread if possible.
+ pub fn update_access_methods(&self, access_methods: Settings) {
+ _ = self.api_client.handle().block_on(async {
+ self.access_mode_handler
+ .update_access_methods(access_methods)
+ .await
+ });
+ }
+}
+
+/// Called by Swift to set the available access methods
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_api_update_access_methods(
+ api_context: SwiftApiContext,
+ settings_wrapper: SwiftAccessMethodSettingsWrapper,
+) {
+ let access_methods = settings_wrapper.into_rust_context().settings;
+ api_context
+ .rust_context()
+ .update_access_methods(access_methods);
+}
+
+/// Called by Swift to update the currently used access methods
+///
+/// # SAFETY
+/// `access_method_id` must point to a null terminated string in a UUID format
+///
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn mullvad_api_use_access_method(
+ api_context: SwiftApiContext,
+ access_method_id: *const c_char,
+) {
+ let api_context = api_context.rust_context();
+ // SAFETY: See Safety notes for `convert_c_string`
+ let id = unsafe { convert_c_string(access_method_id) };
+
+ let Some(id) = Id::from_string(id) else {
+ return;
+ };
+ api_context.use_access_method(id);
}
/// # Safety
@@ -56,10 +128,20 @@ impl ApiContext {
#[cfg(feature = "api-override")]
#[no_mangle]
pub extern "C" fn mullvad_api_init_new_tls_disabled(
- host: *const u8,
- address: *const u8,
+ host: *const c_char,
+ address: *const c_char,
+ domain: *const c_char,
+ bridge_provider: SwiftShadowsocksLoaderWrapper,
+ settings_provider: SwiftAccessMethodSettingsWrapper,
) -> SwiftApiContext {
- mullvad_api_init_inner(host, address, true)
+ mullvad_api_init_inner(
+ host,
+ address,
+ domain,
+ true,
+ bridge_provider,
+ settings_provider,
+ )
}
/// # Safety
@@ -75,26 +157,61 @@ pub extern "C" fn mullvad_api_init_new_tls_disabled(
///
/// This function is safe.
#[no_mangle]
-pub extern "C" fn mullvad_api_init_new(host: *const u8, address: *const u8) -> SwiftApiContext {
+pub extern "C" fn mullvad_api_init_new(
+ host: *const c_char,
+ address: *const c_char,
+ domain: *const c_char,
+ bridge_provider: SwiftShadowsocksLoaderWrapper,
+ settings_provider: SwiftAccessMethodSettingsWrapper,
+) -> SwiftApiContext {
#[cfg(feature = "api-override")]
- return mullvad_api_init_inner(host, address, false);
+ return mullvad_api_init_inner(
+ host,
+ address,
+ domain,
+ true,
+ bridge_provider,
+ settings_provider,
+ );
#[cfg(not(feature = "api-override"))]
- return mullvad_api_init_inner(host, address);
+ mullvad_api_init_inner(host, address, domain, bridge_provider, settings_provider)
}
-fn mullvad_api_init_inner(
- host: *const u8,
- address: *const u8,
+/// # Safety
+///
+/// `host` must be a pointer to a null terminated string representing a hostname for Mullvad API host.
+/// This hostname will be used for TLS validation but not used for domain name resolution.
+///
+/// `address` must be a pointer to a null terminated string representing a socket address through which
+/// the Mullvad API can be reached directly.
+///
+/// If a context cannot be constructed this function will panic since the call site would not be able
+/// to proceed in a meaningful way anyway.
+///
+/// This function is safe.
+#[unsafe(no_mangle)]
+pub extern "C" fn mullvad_api_init_inner(
+ host: *const c_char,
+ address: *const c_char,
+ domain: *const c_char,
#[cfg(feature = "api-override")] disable_tls: bool,
+ bridge_provider: SwiftShadowsocksLoaderWrapper,
+ settings_provider: SwiftAccessMethodSettingsWrapper,
) -> SwiftApiContext {
- let host = unsafe { CStr::from_ptr(host.cast()) };
- let address = unsafe { CStr::from_ptr(address.cast()) };
-
- let host = host.to_str().unwrap();
- let address = address.to_str().unwrap();
+ // Safety: See notes for `convert_c_string`
+ let (host, address, domain) = unsafe {
+ (
+ convert_c_string(host),
+ convert_c_string(address),
+ convert_c_string(domain),
+ )
+ };
+ // The iOS client provides a different default endpoint based on its configuration
+ // Debug and Release builds use the standard endpoints
+ // Staging builds will use the staging endpoint
let endpoint = ApiEndpoint {
- host: Some(String::from(host)),
+ host: Some(host),
address: Some(address.parse().unwrap()),
#[cfg(feature = "api-override")]
disable_tls,
@@ -104,16 +221,37 @@ fn mullvad_api_init_inner(
let tokio_handle = crate::mullvad_ios_runtime().unwrap();
+ // SAFETY: See notes for `into_rust_context`
+ let settings_context = unsafe { settings_provider.into_rust_context() };
+ let access_method_settings = settings_context.convert_access_method().unwrap();
+ let encrypted_dns_proxy_state = EncryptedDnsProxyState::default();
+
+ let method_resolver = SwiftAccessMethodResolver::new(
+ endpoint.clone(),
+ domain,
+ encrypted_dns_proxy_state,
+ bridge_provider,
+ );
+
let api_context = tokio_handle.clone().block_on(async move {
+ let (access_mode_handler, access_mode_provider) = AccessModeSelector::spawn(
+ method_resolver,
+ access_method_settings,
+ #[cfg(feature = "api-override")]
+ endpoint.clone(),
+ )
+ .await
+ .expect("Could now spawn AccessModeSelector");
+
// It is imperative that the REST runtime is created within an async context, otherwise
// ApiAvailability panics.
let api_client = mullvad_api::Runtime::new(tokio_handle, &endpoint);
- let rest_client = api_client
- .mullvad_rest_handle(StaticConnectionModeProvider::new(ApiConnectionMode::Direct));
+ let rest_client = api_client.mullvad_rest_handle(access_mode_provider);
ApiContext {
- _api_client: api_client,
+ api_client,
rest_client,
+ access_mode_handler,
}
});
diff --git a/mullvad-ios/src/api_client/problem_report.rs b/mullvad-ios/src/api_client/problem_report.rs
index a12238c789..8c47da0eef 100644
--- a/mullvad-ios/src/api_client/problem_report.rs
+++ b/mullvad-ios/src/api_client/problem_report.rs
@@ -47,9 +47,11 @@ pub unsafe extern "C" fn mullvad_ios_send_problem_report(
return SwiftCancelHandle::empty();
};
- let api_context = api_context.into_rust_context();
+ let api_context = api_context.rust_context();
+ // SAFETY: See safety notes for `into_rust`
let retry_strategy = unsafe { retry_strategy.into_rust() };
+ // SAFETY: See safety notes for `from_swift_parameters`
let result = unsafe { ProblemReportRequest::from_swift_parameters(request) };
let Some(problem_report_request) = result else {
let err = Error::ApiError(
@@ -114,8 +116,6 @@ struct ProblemReportRequest {
metadata: BTreeMap<String, String>,
}
-unsafe impl Send for SwiftProblemReportRequest {}
-
impl ProblemReportRequest {
// SAFETY: the members of `SwiftProblemReportRequest` must point to null-terminated strings
unsafe fn from_swift_parameters(request: SwiftProblemReportRequest) -> Option<Self> {
@@ -183,8 +183,8 @@ impl Map {
"value must not be null (violates safety contract)"
);
- let key = unsafe { CStr::from_ptr(key) };
- let value = unsafe { CStr::from_ptr(value) };
+ // SAFETY: See notes above
+ let (key, value) = unsafe { (CStr::from_ptr(key), CStr::from_ptr(value)) };
match key.to_str() {
Ok(key_str) => match value.to_str() {
diff --git a/mullvad-ios/src/api_client/shadowsocks_loader.rs b/mullvad-ios/src/api_client/shadowsocks_loader.rs
new file mode 100644
index 0000000000..161ee8a30a
--- /dev/null
+++ b/mullvad-ios/src/api_client/shadowsocks_loader.rs
@@ -0,0 +1,75 @@
+use std::ffi::c_void;
+use talpid_types::net::proxy::Shadowsocks;
+
+extern "C" {
+ /// Creates a `Shadowsocks` configuration.
+ ///
+ /// # SAFETY
+ /// `rawBridgeProvider` **must** be provided by a call to `init_swift_shadowsocks_loader_wrapper`
+ /// It is okay to persist it, and use it across multiple threads.
+ pub fn swift_get_shadowsocks_bridges(rawBridgeProvider: *const c_void) -> *const c_void;
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct SwiftShadowsocksLoaderWrapper(SwiftShadowsocksLoaderWrapperContext);
+impl SwiftShadowsocksLoaderWrapper {
+ pub fn new(context: SwiftShadowsocksLoaderWrapperContext) -> SwiftShadowsocksLoaderWrapper {
+ SwiftShadowsocksLoaderWrapper(context)
+ }
+
+ pub fn get_bridges(&self) -> Option<Shadowsocks> {
+ self.context_ref().get_bridges()
+ }
+
+ fn context_ref(&self) -> &SwiftShadowsocksLoaderWrapperContext {
+ &self.0
+ }
+}
+
+// SAFETY: The context stored inside `SwiftShadowsocksLoaderWrapper` points to an object that is guaranteed to be thread safe
+unsafe impl Sync for SwiftShadowsocksLoaderWrapper {}
+// SAFETY: The context stored inside `SwiftShadowsocksLoaderWrapper` points to an object that is guaranteed to be Sendable
+unsafe impl Send for SwiftShadowsocksLoaderWrapper {}
+
+#[derive(Debug)]
+#[repr(C)]
+pub struct SwiftShadowsocksLoaderWrapperContext {
+ // This pointer is a reference to a Swift object, and is only ever read by Rust.
+ // It is used to call that Swift object across the FFI
+ shadowsocks_loader: *const c_void,
+}
+
+// SAFETY: `shadowsocks_loader` inside the `SwiftShadowsocksLoaderWrapperContext ` points to an object that is guaranteed to be thread safe
+unsafe impl Sync for SwiftShadowsocksLoaderWrapperContext {}
+// SAFETY: `shadowsocks_loader` inside the `SwiftShadowsocksLoaderWrapperContext ` points to an object that is guaranteed to be Sendable
+unsafe impl Send for SwiftShadowsocksLoaderWrapperContext {}
+
+impl SwiftShadowsocksLoaderWrapperContext {
+ pub fn get_bridges(&self) -> Option<Shadowsocks> {
+ // SAFETY: See notice for `swift_get_shadowsocks_bridges`
+ let raw_configuration = unsafe { swift_get_shadowsocks_bridges(self.shadowsocks_loader) };
+ if raw_configuration.is_null() {
+ return None;
+ }
+ // SAFETY: The pointer returned by `swift_get_shadowsocks_bridges` is guaranteed
+ // to point to a valid `Shadowsocks` configuration, and has been null-checked
+ let bridges: Shadowsocks = unsafe { *Box::from_raw(raw_configuration as *mut _) };
+ Some(bridges)
+ }
+}
+
+/// Called by the Swift side in order to provide an object to rust that can create
+/// Shadowsocks configurations
+///
+/// # SAFETY
+/// `shadowsocks_loader` **must be** pointing to a valid instance of a `SwiftShadowsocksBridgeProvider`
+/// That instance's lifetime has to be equivalent to a `'static` lifetime in Rust
+/// This function does not take ownership of `shadowsocks_loader`
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn init_swift_shadowsocks_loader_wrapper(
+ shadowsocks_loader: *const c_void,
+) -> SwiftShadowsocksLoaderWrapper {
+ let context = SwiftShadowsocksLoaderWrapperContext { shadowsocks_loader };
+ SwiftShadowsocksLoaderWrapper::new(context)
+}
diff --git a/mullvad-ios/src/encrypted_dns_proxy.rs b/mullvad-ios/src/encrypted_dns_proxy.rs
index 3551d74179..f424e72bb0 100644
--- a/mullvad-ios/src/encrypted_dns_proxy.rs
+++ b/mullvad-ios/src/encrypted_dns_proxy.rs
@@ -118,6 +118,7 @@ pub unsafe extern "C" fn encrypted_dns_proxy_init(
/// once.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn encrypted_dns_proxy_free(ptr: *mut EncryptedDnsProxyState) {
+ // SAFETY: See notes above
let _ = unsafe { Box::from_raw(ptr) };
}
@@ -143,17 +144,20 @@ pub unsafe extern "C" fn encrypted_dns_proxy_start(
}
};
+ // SAFETY: See notes above
let mut encrypted_dns_proxy = unsafe { Box::from_raw(encrypted_dns_proxy) };
let proxy_result = handle.block_on(encrypted_dns_proxy.start());
mem::forget(encrypted_dns_proxy);
match proxy_result {
+ // SAFETY: `proxy_handle` is guaranteed to be a valid pointer
Ok(handle) => unsafe { ptr::write(proxy_handle, handle) },
Err(err) => {
let empty_handle = ProxyHandle {
context: ptr::null_mut(),
port: 0,
};
+ // SAFETY: `proxy_handle` is guaranteed to be a valid pointer
unsafe { ptr::write(proxy_handle, empty_handle) }
log::error!("Failed to create a proxy connection: {err:?}");
return err.into();
@@ -168,8 +172,10 @@ pub unsafe extern "C" fn encrypted_dns_proxy_start(
/// [`encrypted_dns_proxy_start`]. It should only ever be called once.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn encrypted_dns_proxy_stop(proxy_config: *mut ProxyHandle) -> i32 {
+ // SAFETY: See notes above
let ptr = unsafe { (*proxy_config).context };
if !ptr.is_null() {
+ // SAFETY: `ptr` is guaranteed to be non-null and valid
let handle: Box<JoinHandle<()>> = unsafe { Box::from_raw(ptr.cast()) };
handle.abort();
}
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs b/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
index 1be9a18bf3..5b6de86a62 100644
--- a/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
+++ b/mullvad-ios/src/ephemeral_peer_proxy/ios_tcp_connection.rs
@@ -35,6 +35,7 @@ impl WgTcpConnectionFunctions {
/// This function is safe to call so long as the function pointer is valid for its declared
/// signature.
pub unsafe fn open(&self, tunnel_handle: i32, address: *const u8, timeout: u64) -> i32 {
+ // SAFETY: See above
unsafe { (self.open_fn)(tunnel_handle, address.cast(), timeout) }
}
@@ -42,6 +43,7 @@ impl WgTcpConnectionFunctions {
/// This function is safe to call so long as the function pointer is valid for its declared
/// signature.
pub unsafe fn close(&self, tunnel_handle: i32, socket_handle: i32) -> i32 {
+ // SAFETY: See above
unsafe { (self.close_fn)(tunnel_handle, socket_handle) }
}
@@ -54,6 +56,7 @@ impl WgTcpConnectionFunctions {
.len()
.try_into()
.expect("Cannot receive a buffer larger than 2GiB");
+ // SAFETY: See notes for this function
unsafe { (self.recv_fn)(tunnel_handle, socket_handle, ptr.cast(), len) }
}
@@ -66,6 +69,7 @@ impl WgTcpConnectionFunctions {
.len()
.try_into()
.expect("Cannot send a buffer larger than 2GiB");
+ // SAFETY: See notes for this function
unsafe { (self.send_fn)(tunnel_handle, socket_handle, ptr.cast(), len) }
}
}
@@ -108,9 +112,9 @@ impl IosTcpProvider {
let tunnel_handle = self.tunnel_handle;
let timeout = self.timeout.as_secs();
let funcs = self.funcs;
+ // SAFETY:
+ // The `open_fn` function pointer in `funcs` must be valid.
let result = tokio::task::spawn_blocking(move || unsafe {
- // SAFETY
- // The `open_fn` function pointer in `funcs` must be valid.
funcs.open(tunnel_handle, address.as_ptr() as *const _, timeout)
})
.await
@@ -132,7 +136,7 @@ impl IosTcpProvider {
impl Drop for IosTcpConnection {
fn drop(&mut self) {
- // Safety
+ // Safety:
// `funcs.close_fn` must be a valid function pointer.
unsafe { self.funcs.close(self.tunnel_handle, self.socket_handle) };
}
@@ -163,7 +167,7 @@ impl AsyncWrite for IosTcpConnection {
let data = buf.to_vec();
let funcs = self.funcs;
let task = tokio::task::spawn_blocking(move || {
- // Safety
+ // Safety:
// `funcs.send_fn` must be a valid function pointer.
let result = unsafe { funcs.send(tunnel_handle, socket_handle, data.as_slice()) };
if result < 0 {
@@ -227,7 +231,7 @@ impl AsyncRead for IosTcpConnection {
let funcs = self.funcs;
let mut buffer = vec![0u8; buf.remaining()];
let task = tokio::task::spawn_blocking(move || {
- // Safety
+ // Safety:
// `funcs.receive_fn` must be a valid function pointer.
let result =
unsafe { funcs.receive(tunnel_handle, socket_handle, buffer.as_mut_slice()) };
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/mod.rs b/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
index 7318cef649..9942c78f62 100644
--- a/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
+++ b/mullvad-ios/src/ephemeral_peer_proxy/mod.rs
@@ -17,7 +17,7 @@ pub struct PacketTunnelBridge {
impl PacketTunnelBridge {
fn fail_exchange(self) {
- // # Safety
+ // # Safety:
// Call is safe as long as the `packet_tunnel` pointer is valid. Since a valid instance of
// `PacketTunnelBridge` requires the packet tunnel pointer to be valid, it is assumed this
// call is safe.
@@ -42,7 +42,7 @@ impl PacketTunnelBridge {
.as_ref()
.map(|params| params as *const _)
.unwrap_or(ptr::null());
- // # Safety
+ // # Safety:
// The `packet_tunnel` pointer must be valid, much like the call in `fail_exchange`, but
// since the other arguments here are non-null, these pointers (`preshared_ptr`,
// `ephmerela_ptr` and `daita_ptr`) have to be valid too. Since they point to local
@@ -53,6 +53,7 @@ impl PacketTunnelBridge {
}
}
+// SAFETY: See notes for `EphemeralPeerExchange`
unsafe impl Send for PacketTunnelBridge {}
#[repr(C)]
@@ -85,7 +86,7 @@ impl DaitaParameters {
impl Drop for DaitaParameters {
fn drop(&mut self) {
- // # Safety
+ // # Safety:
// `machines` pointer must be a valid pointer to a CString. This can be achieved by
// ensuring that `DaitaParameters` are constructed via `DaitaParameters::new` and the
// `machines` pointer is never written to.
@@ -122,6 +123,7 @@ extern "C" {
pub unsafe extern "C" fn cancel_ephemeral_peer_exchange(
sender: *mut peer_exchange::ExchangeCancelToken,
) {
+ // SAFETY: See notes above
let sender = unsafe { Box::from_raw(sender) };
sender.cancel();
}
@@ -136,6 +138,7 @@ pub unsafe extern "C" fn cancel_ephemeral_peer_exchange(
pub unsafe extern "C" fn drop_ephemeral_peer_exchange_token(
sender: *mut peer_exchange::ExchangeCancelToken,
) {
+ // SAFETY: See notes above
// drop the cancel token
let _sender = unsafe { Box::from_raw(sender) };
}
@@ -162,10 +165,10 @@ pub unsafe extern "C" fn request_ephemeral_peer(
.init();
});
- // # Safety
+ // # Safety:
// `public_key` pointer must be a valid pointer to 32 unsigned bytes.
let pub_key: [u8; 32] = unsafe { ptr::read(public_key as *const [u8; 32]) };
- // # Safety
+ // # Safety:
// `ephemeral_key` pointer must be a valid pointer to 32 unsigned bytes.
let eph_key: [u8; 32] = unsafe { ptr::read(ephemeral_key as *const [u8; 32]) };
diff --git a/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs b/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs
index f65ccd0166..56b2407438 100644
--- a/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs
+++ b/mullvad-ios/src/ephemeral_peer_proxy/peer_exchange.rs
@@ -48,7 +48,7 @@ pub struct EphemeralPeerExchange {
peer_parameters: EphemeralPeerParameters,
}
-// # Safety
+// # Safety:
// This is safe because the void pointer in PacketTunnelBridge is valid for the lifetime of the
// process where this type is intended to be used.
unsafe impl Send for EphemeralPeerExchange {}
diff --git a/mullvad-ios/src/lib.rs b/mullvad-ios/src/lib.rs
index 2fea39f9c7..fa23672e29 100644
--- a/mullvad-ios/src/lib.rs
+++ b/mullvad-ios/src/lib.rs
@@ -1,6 +1,4 @@
#![cfg(target_os = "ios")]
-#![allow(clippy::undocumented_unsafe_blocks)]
-
mod api_client;
mod encrypted_dns_proxy;
mod ephemeral_peer_proxy;
diff --git a/mullvad-ios/src/shadowsocks_proxy/ffi.rs b/mullvad-ios/src/shadowsocks_proxy/ffi.rs
index 229993445e..815862fb52 100644
--- a/mullvad-ios/src/shadowsocks_proxy/ffi.rs
+++ b/mullvad-ios/src/shadowsocks_proxy/ffi.rs
@@ -1,7 +1,7 @@
use super::{run_forwarding_proxy, ShadowsocksHandle};
+use crate::api_client::helpers::parse_ip_addr;
use crate::ProxyHandle;
-
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+use std::net::SocketAddr;
#[cfg(any(target_os = "macos", target_os = "ios"))]
use std::sync::Once;
@@ -35,17 +35,16 @@ pub unsafe extern "C" fn start_shadowsocks_proxy(
.init();
});
- let forward_ip = if let Some(forward_address) =
- unsafe { parse_ip_addr(forward_address, forward_address_len) }
- {
- forward_address
- } else {
- return -1;
- };
+ let forward_ip =
+ if let Some(forward_address) = { parse_ip_addr(forward_address, forward_address_len) } {
+ forward_address
+ } else {
+ return -1;
+ };
let forward_socket_addr = SocketAddr::new(forward_ip, forward_port);
- let bridge_ip = if let Some(addr) = unsafe { parse_ip_addr(addr, addr_len) } {
+ let bridge_ip = if let Some(addr) = { parse_ip_addr(addr, addr_len) } {
addr
} else {
return -1;
@@ -53,12 +52,14 @@ pub unsafe extern "C" fn start_shadowsocks_proxy(
let bridge_socket_addr = SocketAddr::new(bridge_ip, port);
+ // SAFETY: See notes for `parse_str`
let password = if let Some(password) = unsafe { parse_str(password, password_len) } {
password
} else {
return -1;
};
+ // SAFETY: See notes for `parse_str`
let cipher = if let Some(cipher) = unsafe { parse_str(cipher, cipher_len) } {
cipher
} else {
@@ -75,6 +76,8 @@ pub unsafe extern "C" fn start_shadowsocks_proxy(
};
let handle = Box::new(handle);
+ // SAFETY: `proxy_config` is guaranteed to be writeable for the duration of this call.
+ // It does not overlap with `handle`
unsafe {
std::ptr::write(
proxy_config,
@@ -92,40 +95,19 @@ pub unsafe extern "C" fn start_shadowsocks_proxy(
/// `start_shadowsocks_proxy`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stop_shadowsocks_proxy(proxy_config: *mut ProxyHandle) -> i32 {
+ // SAFETY: `proxy_config` is guaranteed to be a valid pointer
let context_ptr = unsafe { (*proxy_config).context };
if context_ptr.is_null() {
return -1;
}
+ // SAFETY: `context_ptr` is guaranteed to be a valid, non-null pointer
let proxy_handle: Box<ShadowsocksHandle> = unsafe { Box::from_raw(context_ptr as *mut _) };
proxy_handle.stop();
+ // SAFETY: `proxy_config` is guaranteed to be a valid pointer
unsafe { (*proxy_config).context = std::ptr::null_mut() };
0
}
-/// Constructs a new IP address from a pointer containing bytes representing an IP address.
-///
-/// SAFETY: `addr` must be a pointer to at least `addr_len` bytes.
-unsafe fn parse_ip_addr(addr: *const u8, addr_len: usize) -> Option<IpAddr> {
- match addr_len {
- 4 => {
- // SAFETY: addr pointer must point to at least addr_len bytes
- let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
- Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).into())
- }
- 16 => {
- // SAFETY: addr pointer must point to at least addr_len bytes
- let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
- let mut addr_arr = [0u8; 16];
- addr_arr.as_mut_slice().copy_from_slice(bytes);
-
- Some(Ipv6Addr::from(addr_arr).into())
- }
- anything_else => {
- log::error!("Bad IP address length {anything_else}");
- None
- }
- }
-}
/// Allocates a new string with the contents of `data` if it contains only valid UTF-8 bytes.
///
diff --git a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
index d36e0c6ff4..d92ce28a42 100644
--- a/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
+++ b/mullvad-ios/src/tunnel_obfuscator_proxy/ffi.rs
@@ -1,9 +1,8 @@
use super::{TunnelObfuscatorHandle, TunnelObfuscatorRuntime};
use crate::ProxyHandle;
-use std::{
- net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
- sync::Once,
-};
+use std::{net::SocketAddr, sync::Once};
+
+use crate::api_client::helpers::parse_ip_addr;
static INIT_LOGGING: Once = Once::new();
@@ -58,36 +57,17 @@ pub unsafe extern "C" fn start_tunnel_obfuscator_proxy(
#[unsafe(no_mangle)]
pub unsafe extern "C" fn stop_tunnel_obfuscator_proxy(proxy_handle: *mut ProxyHandle) -> i32 {
+ // SAFETY: `proxy_config` is guaranteed to be a valid pointer
let context_ptr = unsafe { (*proxy_handle).context };
if context_ptr.is_null() {
return -1;
}
+ // SAFETY: `context_ptr` is guaranteed to be a valid, non-null pointer
let obfuscator_handle: Box<TunnelObfuscatorHandle> =
unsafe { Box::from_raw(context_ptr as *mut _) };
obfuscator_handle.stop();
+ // SAFETY: `proxy_config` is guaranteed to be a valid pointer
unsafe { (*proxy_handle).context = std::ptr::null_mut() };
0
}
-
-/// Constructs a new IP address from a pointer containing bytes representing an IP address.
-///
-/// SAFETY: `addr` must be a pointer to at least `addr_len` bytes.
-unsafe fn parse_ip_addr(addr: *const u8, addr_len: usize) -> Option<IpAddr> {
- match addr_len {
- 4 => {
- // SAFETY: addr pointer must point to at least addr_len bytes
- let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
- Some(Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]).into())
- }
- 16 => {
- // SAFETY: addr pointer must point to at least addr_len bytes
- let bytes = unsafe { std::slice::from_raw_parts(addr, addr_len) };
- let mut addr_arr = [0u8; 16];
- addr_arr.as_mut_slice().copy_from_slice(bytes);
-
- Some(Ipv6Addr::from(addr_arr).into())
- }
- _ => None,
- }
-}