summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadRustRuntime
diff options
context:
space:
mode:
authorBug Magnet <marco.nikic@mullvad.net>2025-04-24 10:55:42 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-04-24 10:55:42 +0200
commit461720ae5c99236de02ac531d4f20df217371d80 (patch)
tree0c0c0e5cf24b019be316ed07112b4bb78a3ef3f2 /ios/MullvadRustRuntime
parente59bd99fe2d9671eaacfa8dd4c9ecd2b1c6aa682 (diff)
parent8cb9795c392a41c6adae249007e0155115697e04 (diff)
downloadmullvadvpn-461720ae5c99236de02ac531d4f20df217371d80.tar.xz
mullvadvpn-461720ae5c99236de02ac531d4f20df217371d80.zip
Merge branch 'expose-transport-selector-to-mullvad-api-ios-1042'
Diffstat (limited to 'ios/MullvadRustRuntime')
-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
5 files changed, 353 insertions, 9 deletions
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