diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-04-24 10:55:42 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-04-24 10:55:42 +0200 |
| commit | 461720ae5c99236de02ac531d4f20df217371d80 (patch) | |
| tree | 0c0c0e5cf24b019be316ed07112b4bb78a3ef3f2 /ios/MullvadRustRuntime | |
| parent | e59bd99fe2d9671eaacfa8dd4c9ecd2b1c6aa682 (diff) | |
| parent | 8cb9795c392a41c6adae249007e0155115697e04 (diff) | |
| download | mullvadvpn-461720ae5c99236de02ac531d4f20df217371d80.tar.xz mullvadvpn-461720ae5c99236de02ac531d4f20df217371d80.zip | |
Merge branch 'expose-transport-selector-to-mullvad-api-ios-1042'
Diffstat (limited to 'ios/MullvadRustRuntime')
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 |
