diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-02-15 17:05:21 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-02-15 17:05:21 +0100 |
| commit | bf4fc6d570e6ef468c040dadeaabb1b943124f41 (patch) | |
| tree | ec55e9c17f429851b7e1236fc672eb2b7acb0eb9 | |
| parent | 110e037e84e233aac0796e1d18502640bc241ca3 (diff) | |
| parent | 6980530b51fe1e17a5a7e5b36d56b40c66d5f484 (diff) | |
| download | mullvadvpn-bf4fc6d570e6ef468c040dadeaabb1b943124f41.tar.xz mullvadvpn-bf4fc6d570e6ef468c040dadeaabb1b943124f41.zip | |
Merge branch 'add-api-access-method-daemon-tests-des-607'
29 files changed, 367 insertions, 99 deletions
diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index 6ffe3527cc..828e44860a 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -37,4 +37,5 @@ jobs: # Ignored audit issues. This list should be kept short, and effort should be # put into removing items from the list. # RUSTSEC-2023-0057,RUSTSEC-2023-0058 - Unsoundness in `inventory`. - ignore: RUSTSEC-2023-0057,RUSTSEC-2023-0058 + # RUSTSEC-2023-0079 - KyberSlash in `pqc_kyber`. + ignore: RUSTSEC-2023-0057,RUSTSEC-2023-0058,RUSTSEC-2023-0079 diff --git a/Cargo.lock b/Cargo.lock index 09cdeb8401..f34ef3c6bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1890,6 +1890,7 @@ dependencies = [ "simple-signal", "talpid-core", "talpid-dbus", + "talpid-future", "talpid-platform-metadata", "talpid-time", "talpid-types", @@ -2021,7 +2022,7 @@ dependencies = [ "parking_lot", "rand 0.8.5", "serde_json", - "talpid-core", + "talpid-future", "talpid-types", "tokio", ] @@ -2041,6 +2042,7 @@ dependencies = [ "mullvad-version", "once_cell", "talpid-core", + "talpid-future", "talpid-types", "tokio", ] @@ -3567,7 +3569,6 @@ dependencies = [ "once_cell", "parking_lot", "pfctl", - "proptest", "rand 0.8.5", "resolv-conf", "subslice", @@ -3606,6 +3607,16 @@ dependencies = [ ] [[package]] +name = "talpid-future" +version = "0.0.0" +dependencies = [ + "proptest", + "rand 0.8.5", + "talpid-time", + "tokio", +] + +[[package]] name = "talpid-openvpn" version = "0.0.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index d3ad0a462a..e2a95e7d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,23 +11,25 @@ members = [ "android/translations-converter", "ios/MullvadREST/Transport/Shadowsocks/shadowsocks-proxy", "ios/TunnelObfuscation/tunnel-obfuscator-proxy", - "mullvad-daemon", + "mullvad-api", "mullvad-cli", + "mullvad-daemon", + "mullvad-exclude", "mullvad-fs", - "mullvad-setup", - "mullvad-problem-report", "mullvad-jni", + "mullvad-management-interface", + "mullvad-nsis", "mullvad-paths", + "mullvad-problem-report", "mullvad-relay-selector", + "mullvad-setup", "mullvad-types", - "mullvad-api", - "mullvad-exclude", "mullvad-version", - "mullvad-nsis", - "talpid-openvpn-plugin", "talpid-core", "talpid-dbus", + "talpid-future", "talpid-openvpn", + "talpid-openvpn-plugin", "talpid-platform-metadata", "talpid-routing", "talpid-time", @@ -35,7 +37,6 @@ members = [ "talpid-tunnel-config-client", "talpid-windows", "talpid-wireguard", - "mullvad-management-interface", "tunnel-obfuscation", ] diff --git a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts index 45f8d98cee..0d6e8a0672 100644 --- a/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts +++ b/gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts @@ -125,7 +125,7 @@ test('App should edit access method', async () => { await inputs.nth(3).fill(process.env.SHADOWSOCKS_SERVER_PASSWORD!); await page.getByTestId('ciphers').click(); - await page.getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER! }).click(); + await page.getByRole('option', { name: process.env.SHADOWSOCKS_SERVER_CIPHER!, exact: true }).click(); expect( await util.waitForNavigation(async () => await saveButton.click()) diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index 17e80b66c0..d714d5ee3f 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -104,6 +104,13 @@ impl<T> Deref for LazyManual<T> { } } +pub mod env { + pub const API_HOST_VAR: &str = "MULLVAD_API_HOST"; + pub const API_ADDR_VAR: &str = "MULLVAD_API_ADDR"; + pub const API_FORCE_DIRECT_VAR: &str = "MULLVAD_API_FORCE_DIRECT"; + pub const DISABLE_TLS_VAR: &str = "MULLVAD_API_DISABLE_TLS"; +} + /// A hostname and socketaddr to reach the Mullvad REST API over. #[derive(Debug)] pub struct ApiEndpoint { @@ -128,6 +135,23 @@ pub struct ApiEndpoint { pub disable_address_cache: bool, #[cfg(feature = "api-override")] pub disable_tls: bool, + #[cfg(feature = "api-override")] + /// Whether bridges/proxies can be used to access the API or not. This is + /// useful primarily for testing purposes. + /// + /// * If `force_direct` is `true`, bridges and proxies will not be used to + /// reach the API. + /// * If `force_direct` is `false`, bridges and proxies can be used to reach the API. + /// + /// # Note + /// + /// By default, `force_direct` will be `true` if the `api-override` feature + /// is enabled. This is supposedely less error prone, as common targets such + /// as Devmole might be unreachable from behind a bridge server. + /// + /// To disable `force_direct`, set the environment variable + /// `MULLVAD_API_FORCE_DIRECT=0` before starting the daemon. + pub force_direct: bool, } impl ApiEndpoint { @@ -135,10 +159,6 @@ impl ApiEndpoint { const API_IP_DEFAULT: IpAddr = IpAddr::V4(Ipv4Addr::new(45, 83, 223, 196)); const API_PORT_DEFAULT: u16 = 443; - const API_HOST_VAR: &'static str = "MULLVAD_API_HOST"; - const API_ADDR_VAR: &'static str = "MULLVAD_API_ADDR"; - const DISABLE_TLS_VAR: &'static str = "MULLVAD_API_DISABLE_TLS"; - /// Returns the endpoint to connect to the API over. /// /// # Panics @@ -147,15 +167,19 @@ impl ApiEndpoint { /// `MULLVAD_API_DISABLE_TLS` has invalid contents. #[cfg(feature = "api-override")] pub fn from_env_vars() -> ApiEndpoint { - let host_var = Self::read_var(ApiEndpoint::API_HOST_VAR); - let address_var = Self::read_var(ApiEndpoint::API_ADDR_VAR); - let disable_tls_var = Self::read_var(ApiEndpoint::DISABLE_TLS_VAR); + let host_var = Self::read_var(env::API_HOST_VAR); + let address_var = Self::read_var(env::API_ADDR_VAR); + let disable_tls_var = Self::read_var(env::DISABLE_TLS_VAR); + let force_direct = Self::read_var(env::API_FORCE_DIRECT_VAR); let mut api = ApiEndpoint { host: None, address: None, disable_address_cache: true, disable_tls: false, + force_direct: force_direct + .map(|force_direct_env| force_direct_env.to_lowercase() != "0") + .unwrap_or(true), }; match (host_var, address_var) { @@ -164,8 +188,8 @@ impl ApiEndpoint { use std::net::ToSocketAddrs; log::debug!( "{api_addr} not found. Resolving API IP address from {api_host}={host}", - api_addr = ApiEndpoint::API_ADDR_VAR, - api_host = ApiEndpoint::API_HOST_VAR + api_addr = env::API_ADDR_VAR, + api_host = env::API_HOST_VAR ); api.address = format!("{}:{}", host, ApiEndpoint::API_PORT_DEFAULT) .to_socket_addrs() @@ -181,7 +205,7 @@ impl ApiEndpoint { let addr = address.parse().unwrap_or_else(|_| { panic!( "{api_addr}={address} is not a valid socketaddr", - api_addr = ApiEndpoint::API_ADDR_VAR, + api_addr = env::API_ADDR_VAR, ) }); api.address = Some(addr); @@ -193,9 +217,9 @@ impl ApiEndpoint { if disable_tls_var.is_some() { log::warn!( "{disable_tls} is ignored since {api_host} and {api_addr} are not set", - disable_tls = ApiEndpoint::DISABLE_TLS_VAR, - api_host = ApiEndpoint::API_HOST_VAR, - api_addr = ApiEndpoint::API_ADDR_VAR, + disable_tls = env::DISABLE_TLS_VAR, + api_host = env::API_HOST_VAR, + api_addr = env::API_ADDR_VAR, ); } } else { @@ -226,16 +250,17 @@ impl ApiEndpoint { /// `MULLVAD_API_DISABLE_TLS` has invalid contents. #[cfg(not(feature = "api-override"))] pub fn from_env_vars() -> ApiEndpoint { - let host_var = Self::read_var(ApiEndpoint::API_HOST_VAR); - let address_var = Self::read_var(ApiEndpoint::API_ADDR_VAR); - let disable_tls_var = Self::read_var(ApiEndpoint::DISABLE_TLS_VAR); + let env_vars = [ + env::API_HOST_VAR, + env::API_ADDR_VAR, + env::DISABLE_TLS_VAR, + env::API_FORCE_DIRECT_VAR, + ]; - if host_var.is_some() || address_var.is_some() || disable_tls_var.is_some() { + if env_vars.map(Self::read_var).iter().any(Option::is_some) { log::warn!( - "These variables are ignored in production builds: {api_host}, {api_addr}, {disable_tls}", - api_host = ApiEndpoint::API_HOST_VAR, - api_addr = ApiEndpoint::API_ADDR_VAR, - disable_tls = ApiEndpoint::DISABLE_TLS_VAR + "These variables are ignored in production builds: {env_vars_pretty}", + env_vars_pretty = env_vars.join(", ") ); } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 543151941a..8677d18857 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -34,9 +34,10 @@ mullvad-api = { path = "../mullvad-api" } mullvad-fs = { path = "../mullvad-fs" } mullvad-version = { path = "../mullvad-version" } talpid-core = { path = "../talpid-core" } -talpid-types = { path = "../talpid-types" } +talpid-future = { path = "../talpid-future" } talpid-platform-metadata = { path = "../talpid-platform-metadata" } talpid-time = { path = "../talpid-time" } +talpid-types = { path = "../talpid-types" } [target.'cfg(not(target_os="android"))'.dependencies] clap = { workspace = true } diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs index 5db03c2008..1e6ba296a3 100644 --- a/mullvad-daemon/src/api.rs +++ b/mullvad-daemon/src/api.rs @@ -328,10 +328,7 @@ impl AccessModeSelector { #[cfg(feature = "api-override")] { use mullvad_api::API; - // If the API address has been explicitly overridden, it should - // always be used. This implies that a direct API connection mode is - // used. - if API.address.is_some() { + if API.force_direct { log::debug!("API proxies are disabled"); let endpoint = resolve_allowed_endpoint( &ApiConnectionMode::Direct, @@ -350,8 +347,8 @@ impl AccessModeSelector { } log::debug!( - "The `api-override` feature is enabled, but the API address \ - was not overridden. Selecting API access methods as normal" + "The `api-override` feature is enabled, but a direct connection \ + is not enforced. Selecting API access methods as normal" ); } diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs index 4f62466b6f..c4c949ceba 100644 --- a/mullvad-daemon/src/device/service.rs +++ b/mullvad-daemon/src/device/service.rs @@ -17,7 +17,7 @@ use mullvad_api::{ rest::{self, MullvadRestHandle}, AccountsProxy, DevicesProxy, }; -use talpid_core::future_retry::{retry_future, ConstantInterval, ExponentialBackoff, Jittered}; +use talpid_future::retry::{retry_future, ConstantInterval, ExponentialBackoff, Jittered}; /// Retry strategy used for user-initiated actions that require immediate feedback const RETRY_ACTION_STRATEGY: ConstantInterval = ConstantInterval::new(Duration::ZERO, Some(3)); /// Retry strategy used for background tasks diff --git a/mullvad-daemon/src/geoip.rs b/mullvad-daemon/src/geoip.rs index 3f357abc3e..14e46a914b 100644 --- a/mullvad-daemon/src/geoip.rs +++ b/mullvad-daemon/src/geoip.rs @@ -7,10 +7,8 @@ use mullvad_api::{ }; use mullvad_types::location::{AmIMullvad, GeoIpLocation, LocationEventData}; use once_cell::sync::Lazy; -use talpid_core::{ - future_retry::{retry_future, ExponentialBackoff, Jittered}, - mpsc::Sender, -}; +use talpid_core::mpsc::Sender; +use talpid_future::retry::{retry_future, ExponentialBackoff, Jittered}; use talpid_types::ErrorExt; use crate::{DaemonEventSender, InternalDaemonEvent}; diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs index d7c7eaa805..69d2d61643 100644 --- a/mullvad-daemon/src/version_check.rs +++ b/mullvad-daemon/src/version_check.rs @@ -15,7 +15,8 @@ use std::{ str::FromStr, time::Duration, }; -use talpid_core::{future_retry::ConstantInterval, mpsc::Sender}; +use talpid_core::mpsc::Sender; +use talpid_future::retry::{retry_future, ConstantInterval}; use talpid_types::ErrorExt; use tokio::fs::{self, File}; @@ -193,7 +194,7 @@ impl VersionUpdater { .map_err(Error::Download) }; - Box::pin(talpid_core::future_retry::retry_future( + Box::pin(retry_future( download_future_factory, move |result| Self::should_retry_immediate(result, &api_handle), IMMEDIATE_RETRY_STRATEGY, @@ -233,7 +234,7 @@ impl VersionUpdater { } }; - Box::pin(talpid_core::future_retry::retry_future( + Box::pin(retry_future( download_future_factory, |result| result.is_err(), std::iter::repeat(UPDATE_INTERVAL_ERROR), diff --git a/mullvad-relay-selector/Cargo.toml b/mullvad-relay-selector/Cargo.toml index 6ca82364fe..078415f31a 100644 --- a/mullvad-relay-selector/Cargo.toml +++ b/mullvad-relay-selector/Cargo.toml @@ -21,7 +21,7 @@ rand = "0.8.5" serde_json = "1.0" tokio = { workspace = true, features = ["fs", "io-util", "time"] } -talpid-core = { path = "../talpid-core" } +talpid-future = { path = "../talpid-future" } talpid-types = { path = "../talpid-types" } mullvad-api = { path = "../mullvad-api" } mullvad-types = { path = "../mullvad-types" } diff --git a/mullvad-relay-selector/src/updater.rs b/mullvad-relay-selector/src/updater.rs index e9d65d1dd8..9e86cbb413 100644 --- a/mullvad-relay-selector/src/updater.rs +++ b/mullvad-relay-selector/src/updater.rs @@ -12,7 +12,7 @@ use std::{ sync::Arc, time::{Duration, SystemTime, UNIX_EPOCH}, }; -use talpid_core::future_retry::{retry_future, ExponentialBackoff, Jittered}; +use talpid_future::retry::{retry_future, ExponentialBackoff, Jittered}; use talpid_types::ErrorExt; use tokio::fs::File; diff --git a/mullvad-setup/Cargo.toml b/mullvad-setup/Cargo.toml index c873252355..bb602303db 100644 --- a/mullvad-setup/Cargo.toml +++ b/mullvad-setup/Cargo.toml @@ -30,4 +30,5 @@ mullvad-api = { path = "../mullvad-api" } mullvad-types = { path = "../mullvad-types" } mullvad-version = { path = "../mullvad-version" } talpid-core = { path = "../talpid-core" } +talpid-future = { path = "../talpid-future" } talpid-types = { path = "../talpid-types" } diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs index cf93b2d039..4b14319414 100644 --- a/mullvad-setup/src/main.rs +++ b/mullvad-setup/src/main.rs @@ -1,13 +1,12 @@ use clap::Parser; +use once_cell::sync::Lazy; +use std::{path::PathBuf, process, str::FromStr, time::Duration}; + use mullvad_api::{self, proxy::ApiConnectionMode, DEVICE_NOT_FOUND}; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::version::ParsedAppVersion; -use once_cell::sync::Lazy; -use std::{path::PathBuf, process, str::FromStr, time::Duration}; -use talpid_core::{ - firewall::{self, Firewall}, - future_retry::{retry_future, ConstantInterval}, -}; +use talpid_core::firewall::{self, Firewall}; +use talpid_future::retry::{retry_future, ConstantInterval}; use talpid_types::ErrorExt; static APP_VERSION: Lazy<ParsedAppVersion> = diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index 1ec70876ff..2256efedd6 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -11,22 +11,21 @@ rust-version.workspace = true workspace = true [dependencies] +chrono = { workspace = true, features = ["clock"] } err-derive = { workspace = true } futures = "0.3.15" ipnetwork = "0.16" -once_cell = { workspace = true } libc = "0.2" log = { workspace = true } +once_cell = { workspace = true } parking_lot = "0.12.0" +rand = "0.8.5" talpid-routing = { path = "../talpid-routing" } -talpid-types = { path = "../talpid-types" } -talpid-time = { path = "../talpid-time" } -talpid-tunnel-config-client = { path = "../talpid-tunnel-config-client" } talpid-tunnel = { path = "../talpid-tunnel" } +talpid-tunnel-config-client = { path = "../talpid-tunnel-config-client" } +talpid-types = { path = "../talpid-types" } talpid-wireguard = { path = "../talpid-wireguard" } -chrono = { workspace = true, features = ["clock"] } tokio = { workspace = true, features = ["process", "rt-multi-thread", "fs"] } -rand = "0.8.5" [target.'cfg(not(target_os="android"))'.dependencies] talpid-openvpn = { path = "../talpid-openvpn" } @@ -47,13 +46,14 @@ duct = "0.13" [target.'cfg(target_os = "macos")'.dependencies] +async-trait = "0.1" +duct = "0.13" pfctl = "0.4.4" +subslice = "0.2" system-configuration = "0.5.1" -trust-dns-server = { version = "0.23.0", features = ["resolver"] } +talpid-time = { path = "../talpid-time" } trust-dns-proto = "0.23.0" -subslice = "0.2" -async-trait = "0.1" -duct = "0.13" +trust-dns-server = { version = "0.23.0", features = ["resolver"] } [target.'cfg(windows)'.dependencies] @@ -92,7 +92,3 @@ features = [ [build-dependencies] tonic-build = { workspace = true, default-features = false, features = ["transport", "prost"] } - -[dev-dependencies] -proptest = "1.4" -tokio = { workspace = true, features = [ "test-util" ] } diff --git a/talpid-core/src/lib.rs b/talpid-core/src/lib.rs index d0b6a98b38..84bb4ad90c 100644 --- a/talpid-core/src/lib.rs +++ b/talpid-core/src/lib.rs @@ -35,9 +35,6 @@ pub mod dns; /// State machine to handle tunnel configuration. pub mod tunnel_state_machine; -/// Future utilities -pub mod future_retry; - /// Misc utilities for the Linux platform. #[cfg(target_os = "linux")] mod linux; diff --git a/talpid-future/Cargo.toml b/talpid-future/Cargo.toml new file mode 100644 index 0000000000..1245fe1e2a --- /dev/null +++ b/talpid-future/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "talpid-future" +description = "Utilities for working with futures" +authors.workspace = true +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +rand = "0.8.5" +talpid-time = { path = "../talpid-time" } + +[dev-dependencies] +proptest = "1.4" +tokio = { workspace = true, features = [ "test-util", "macros" ] } diff --git a/talpid-future/src/lib.rs b/talpid-future/src/lib.rs new file mode 100644 index 0000000000..7cc721df60 --- /dev/null +++ b/talpid-future/src/lib.rs @@ -0,0 +1,5 @@ +//! This library provides utility functions and types for working with futures. +#![deny(missing_docs)] + +/// Retry futures +pub mod retry; diff --git a/talpid-core/src/future_retry.rs b/talpid-future/src/retry.rs index ee23de312f..5d9b4fb574 100644 --- a/talpid-core/src/future_retry.rs +++ b/talpid-future/src/retry.rs @@ -1,3 +1,4 @@ +//! This library provides utility functions and types for retrying futures. use rand::{distributions::OpenClosed01, Rng}; use std::{future::Future, ops::Deref, time::Duration}; use talpid_time::sleep; diff --git a/talpid-types/src/net/proxy.rs b/talpid-types/src/net/proxy.rs index d88afdf6c2..f873d26c84 100644 --- a/talpid-types/src/net/proxy.rs +++ b/talpid-types/src/net/proxy.rs @@ -65,6 +65,24 @@ impl CustomProxy { } } +impl From<Socks5Remote> for CustomProxy { + fn from(value: Socks5Remote) -> Self { + CustomProxy::Socks5Remote(value) + } +} + +impl From<Socks5Local> for CustomProxy { + fn from(value: Socks5Local) -> Self { + CustomProxy::Socks5Local(value) + } +} + +impl From<Shadowsocks> for CustomProxy { + fn from(value: Shadowsocks) -> Self { + CustomProxy::Shadowsocks(value) + } +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct Shadowsocks { pub endpoint: SocketAddr, diff --git a/test/Cargo.lock b/test/Cargo.lock index 41fc286059..9be666c178 100644 --- a/test/Cargo.lock +++ b/test/Cargo.lock @@ -1522,9 +1522,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libdbus-sys" @@ -1784,6 +1784,25 @@ dependencies = [ ] [[package]] +name = "mullvad-relay-selector" +version = "0.0.0" +dependencies = [ + "chrono", + "err-derive", + "futures", + "ipnetwork 0.16.0", + "log", + "mullvad-api", + "mullvad-types", + "parking_lot 0.12.1", + "rand 0.8.5", + "serde_json", + "talpid-future", + "talpid-types", + "tokio", +] + +[[package]] name = "mullvad-types" version = "0.0.0" dependencies = [ @@ -3028,6 +3047,14 @@ dependencies = [ ] [[package]] +name = "talpid-future" +version = "0.0.0" +dependencies = [ + "rand 0.8.5", + "talpid-time", +] + +[[package]] name = "talpid-platform-metadata" version = "0.0.0" dependencies = [ @@ -3148,6 +3175,7 @@ dependencies = [ "log", "mullvad-api", "mullvad-management-interface", + "mullvad-relay-selector", "mullvad-types", "nix 0.25.1", "once_cell", diff --git a/test/test-manager/Cargo.toml b/test/test-manager/Cargo.toml index 72230a2edd..086d937e45 100644 --- a/test/test-manager/Cargo.toml +++ b/test/test-manager/Cargo.toml @@ -50,10 +50,11 @@ tonic = { workspace = true } tower = { workspace = true } colored = { workspace = true } +mullvad-api = { path = "../../mullvad-api", features = ["api-override"] } mullvad-management-interface = { path = "../../mullvad-management-interface" } -talpid-types = { path = "../../talpid-types" } +mullvad-relay-selector = { path = "../../mullvad-relay-selector" } mullvad-types = { path = "../../mullvad-types" } -mullvad-api = { path = "../../mullvad-api", features = ["api-override"] } +talpid-types = { path = "../../talpid-types" } ssh2 = "0.9.4" diff --git a/test/test-manager/src/package.rs b/test/test-manager/src/package.rs index 3f35163e5e..7e69ae867f 100644 --- a/test/test-manager/src/package.rs +++ b/test/test-manager/src/package.rs @@ -69,7 +69,7 @@ async fn find_app( .join("mullvad-test") .join("packages"); fs::create_dir_all(&packages_dir).await?; - let mut dir = fs::read_dir(packages_dir) + let mut dir = fs::read_dir(packages_dir.clone()) .await .context("Failed to list packages")?; @@ -130,8 +130,9 @@ async fn find_app( matches.into_iter().next().context(if e2e_bin { format!( "Could not find UI/e2e test for package: {app}.\n\ - Expecting a binary named like `app-e2e-tests-{app}_ARCH` to exist in packages/\n\ - Example ARCH: `amd64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu`" + Expecting a binary named like `app-e2e-tests-{app}_ARCH` to exist in {package_dir}/\n\ + Example ARCH: `amd64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu`", + package_dir = packages_dir.display() ) } else { format!("Could not find package for app: {app}") diff --git a/test/test-manager/src/tests/access_methods.rs b/test/test-manager/src/tests/access_methods.rs new file mode 100644 index 0000000000..1d5660f4b0 --- /dev/null +++ b/test/test-manager/src/tests/access_methods.rs @@ -0,0 +1,74 @@ +//! Integration tests for API access methods. +use super::{Error, TestContext}; +use mullvad_management_interface::MullvadProxyClient; +use test_macro::test_function; +use test_rpc::ServiceClient; + +/// Assert that custom access methods may be used to access the Mullvad API. +/// +/// The tested access methods are: +/// * Shadowsocks +/// * Socks5 in remote mode +/// +/// # Note +/// +/// This tests assume that there exists working proxies *somewhere* for all +/// tested protocols. If the proxies themselves are bad/not running, this test +/// will fail due to issues that are out of the test manager's control. +#[test_function] +pub async fn test_custom_access_methods( + _: TestContext, + _rpc: ServiceClient, + mullvad_client: MullvadProxyClient, +) -> Result<(), Error> { + log::info!("Testing Shadowsocks access method"); + test_shadowsocks(mullvad_client.clone()).await?; + log::info!("Testing SOCKS5 (Remote) access method"); + test_socks_remote(mullvad_client.clone()).await?; + Ok(()) +} + +macro_rules! assert_access_method_works { + ($mullvad_client:expr, $access_method:expr) => { + let successful = $mullvad_client + .test_custom_api_access_method($access_method.clone().into()) + .await + .expect("Failed to test custom API access method"); + + assert!( + successful, + "Failed while testing access method - {:?}", + $access_method + ); + }; +} + +async fn test_shadowsocks(mut mullvad_client: MullvadProxyClient) -> Result<(), Error> { + use mullvad_relay_selector::{RelaySelector, SelectorConfig}; + // Set up all the parameters needed to create a custom Shadowsocks access method. + // + // Since Mullvad's bridge servers host Shadowsocks relays, we can simply + // select a bridge server to derive all the needed parameters. + let relay_list = mullvad_client.get_relay_locations().await.unwrap(); + let relay_selector = RelaySelector::from_list(SelectorConfig::default(), relay_list); + let access_method = relay_selector + .get_bridge_forced() + .expect("`test_shadowsocks` needs at least one shadowsocks relay to execute. Found none in relay list."); + assert_access_method_works!(mullvad_client, access_method); + Ok(()) +} + +async fn test_socks_remote(mut mullvad_client: MullvadProxyClient) -> Result<(), Error> { + use crate::vm::network::{NON_TUN_GATEWAY, SOCKS5_PORT}; + use std::net::SocketAddr; + use talpid_types::net::proxy::{CustomProxy, Socks5Remote}; + // Set up all the parameters needed to create a custom SOCKS5 access method. + // + // The remote SOCKS5 proxy is assumed to be running on the test manager. On + // which port it listens to is defined as a constant in the `test-manager` + // crate. + let endpoint = SocketAddr::from((NON_TUN_GATEWAY, SOCKS5_PORT)); + let access_method = CustomProxy::from(Socks5Remote::new(endpoint)); + assert_access_method_works!(mullvad_client, access_method); + Ok(()) +} diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs index 32e7c33b7a..c3e0e59481 100644 --- a/test/test-manager/src/tests/helpers.rs +++ b/test/test-manager/src/tests/helpers.rs @@ -14,6 +14,7 @@ use mullvad_types::{ }; use pnet_packet::ip::IpNextHeaderProtocols; use std::{ + collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr}, path::Path, time::Duration, @@ -397,6 +398,23 @@ pub fn unreachable_wireguard_tunnel() -> talpid_types::net::wireguard::Connectio } } +pub fn get_app_env() -> HashMap<String, String> { + use mullvad_api::env; + use std::net::ToSocketAddrs; + + let api_host = format!("api.{}", TEST_CONFIG.mullvad_host); + let api_addr = format!("{api_host}:443") + .to_socket_addrs() + .expect("failed to resolve API host") + .next() + .unwrap(); + + let api_host_env = (env::API_HOST_VAR.to_string(), api_host); + let api_addr_env = (env::API_ADDR_VAR.to_string(), api_addr.to_string()); + + [api_host_env, api_addr_env].into_iter().collect() +} + /// Return a filtered version of the daemon's relay list. /// /// * `mullvad_client` - An interface to the Mullvad daemon. diff --git a/test/test-manager/src/tests/install.rs b/test/test-manager/src/tests/install.rs index 8f2f2cdff2..6d6dd204b2 100644 --- a/test/test-manager/src/tests/install.rs +++ b/test/test-manager/src/tests/install.rs @@ -1,5 +1,7 @@ use super::config::TEST_CONFIG; -use super::helpers::{connect_and_wait, get_package_desc, wait_for_tunnel_state, Pinger}; +use super::helpers::{ + connect_and_wait, get_app_env, get_package_desc, wait_for_tunnel_state, Pinger, +}; use super::{Error, TestContext}; use mullvad_management_interface::MullvadProxyClient; @@ -8,7 +10,7 @@ use test_macro::test_function; use test_rpc::meta::Os; use test_rpc::{mullvad_daemon::ServiceStatus, ServiceClient}; -use std::{collections::HashMap, net::ToSocketAddrs, time::Duration}; +use std::time::Duration; /// Install the last stable version of the app and verify that it is running. #[test_function(priority = -200)] @@ -333,22 +335,6 @@ pub async fn test_installation_idempotency( Ok(()) } -fn get_app_env() -> HashMap<String, String> { - let mut map = HashMap::new(); - - let api_host = format!("api.{}", TEST_CONFIG.mullvad_host); - let api_addr = format!("{api_host}:443") - .to_socket_addrs() - .expect("failed to resolve API host") - .next() - .unwrap(); - - map.insert("MULLVAD_API_HOST".to_string(), api_host); - map.insert("MULLVAD_API_ADDR".to_string(), api_addr.to_string()); - - map -} - async fn replace_openvpn_cert(rpc: &ServiceClient) -> Result<(), Error> { use std::path::Path; diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs index d1a0ab216d..7847bc2424 100644 --- a/test/test-manager/src/tests/mod.rs +++ b/test/test-manager/src/tests/mod.rs @@ -1,3 +1,4 @@ +mod access_methods; mod account; pub mod config; mod dns; diff --git a/test/test-manager/src/tests/ui.rs b/test/test-manager/src/tests/ui.rs index 6ddb6b5a7f..bb14d7d7da 100644 --- a/test/test-manager/src/tests/ui.rs +++ b/test/test-manager/src/tests/ui.rs @@ -138,3 +138,85 @@ pub async fn test_ui_login(_: TestContext, rpc: ServiceClient) -> Result<(), Err Ok(()) } + +#[test_function(priority = 1000, must_succeed = true)] +async fn test_custom_access_methods_gui( + _: TestContext, + rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> Result<(), Error> { + use mullvad_api::env; + use mullvad_relay_selector::{RelaySelector, SelectorConfig}; + use talpid_types::net::proxy::CustomProxy; + // For this test to work, we need to supply the following env-variables: + // + // * SHADOWSOCKS_SERVER_IP + // * SHADOWSOCKS_SERVER_PORT + // * SHADOWSOCKS_SERVER_CIPHER + // * SHADOWSOCKS_SERVER_PASSWORD + // + // See `gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts` + // for details. The setup should be the same as in + // `test_manager::tests::access_methods::test_shadowsocks`. + // + // # Note + // + // API overrides have to be nullified before proceeding with this test. This + // is accomplished by setting the env variable + // `MULLVAD_API_FORCE_DIRECT=false` and restarting the daemon. + + let mut env = helpers::get_app_env(); + env.insert(env::API_FORCE_DIRECT_VAR.to_string(), "0".to_string()); + + tokio::time::timeout( + std::time::Duration::from_secs(60), + rpc.set_daemon_environment(env), + ) + .await + .map_err(|_| Error::DaemonNotRunning)??; + + let gui_test = "api-access-methods.spec"; + let relay_list = mullvad_client.get_relay_locations().await.unwrap(); + let relay_selector = RelaySelector::from_list(SelectorConfig::default(), relay_list); + let access_method = relay_selector + .get_bridge_forced() + .and_then(|proxy| match proxy { + CustomProxy::Shadowsocks(s) => Some(s), + _ => None + }) + .expect("`test_shadowsocks` needs at least one shadowsocks relay to execute. Found none in relay list."); + + let ui_result = run_test_env( + &rpc, + &[gui_test], + [ + ( + "SHADOWSOCKS_SERVER_IP", + access_method.endpoint.ip().to_string().as_ref(), + ), + ( + "SHADOWSOCKS_SERVER_PORT", + access_method.endpoint.port().to_string().as_ref(), + ), + ("SHADOWSOCKS_SERVER_CIPHER", access_method.cipher.as_ref()), + ( + "SHADOWSOCKS_SERVER_PASSWORD", + access_method.password.as_ref(), + ), + ], + ) + .await + .unwrap(); + + assert!(ui_result.success()); + + // Reset the `api-override` feature. + tokio::time::timeout( + std::time::Duration::from_secs(60), + rpc.set_daemon_environment(helpers::get_app_env()), + ) + .await + .map_err(|_| Error::DaemonNotRunning)??; + + Ok(()) +} diff --git a/test/test-rpc/src/client.rs b/test/test-rpc/src/client.rs index 4d103ed44e..6b3c2c4a0c 100644 --- a/test/test-rpc/src/client.rs +++ b/test/test-rpc/src/client.rs @@ -284,9 +284,15 @@ impl ServiceClient { Ok(()) } - pub async fn set_daemon_environment(&self, env: HashMap<String, String>) -> Result<(), Error> { + pub async fn set_daemon_environment<Env, K, V>(&self, env: Env) -> Result<(), Error> + where + Env: IntoIterator<Item = (K, V)>, + K: Into<String>, + V: Into<String>, + { let mut ctx = tarpc::context::current(); ctx.deadline = SystemTime::now().checked_add(LOG_LEVEL_TIMEOUT).unwrap(); + let env = env.into_iter().map(|(k, v)| (k.into(), v.into())).collect(); self.client.set_daemon_environment(ctx, env).await??; self.mullvad_daemon_wait_for_state(|state| state == ServiceStatus::Running) |
