summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2024-02-15 17:05:21 +0100
committerMarkus Pettersson <markus.pettersson@mullvad.net>2024-02-15 17:05:21 +0100
commitbf4fc6d570e6ef468c040dadeaabb1b943124f41 (patch)
treeec55e9c17f429851b7e1236fc672eb2b7acb0eb9
parent110e037e84e233aac0796e1d18502640bc241ca3 (diff)
parent6980530b51fe1e17a5a7e5b36d56b40c66d5f484 (diff)
downloadmullvadvpn-bf4fc6d570e6ef468c040dadeaabb1b943124f41.tar.xz
mullvadvpn-bf4fc6d570e6ef468c040dadeaabb1b943124f41.zip
Merge branch 'add-api-access-method-daemon-tests-des-607'
-rw-r--r--.github/workflows/cargo-audit.yml3
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml17
-rw-r--r--gui/test/e2e/installed/state-dependent/api-access-methods.spec.ts2
-rw-r--r--mullvad-api/src/lib.rs67
-rw-r--r--mullvad-daemon/Cargo.toml3
-rw-r--r--mullvad-daemon/src/api.rs9
-rw-r--r--mullvad-daemon/src/device/service.rs2
-rw-r--r--mullvad-daemon/src/geoip.rs6
-rw-r--r--mullvad-daemon/src/version_check.rs7
-rw-r--r--mullvad-relay-selector/Cargo.toml2
-rw-r--r--mullvad-relay-selector/src/updater.rs2
-rw-r--r--mullvad-setup/Cargo.toml1
-rw-r--r--mullvad-setup/src/main.rs11
-rw-r--r--talpid-core/Cargo.toml24
-rw-r--r--talpid-core/src/lib.rs3
-rw-r--r--talpid-future/Cargo.toml19
-rw-r--r--talpid-future/src/lib.rs5
-rw-r--r--talpid-future/src/retry.rs (renamed from talpid-core/src/future_retry.rs)1
-rw-r--r--talpid-types/src/net/proxy.rs18
-rw-r--r--test/Cargo.lock32
-rw-r--r--test/test-manager/Cargo.toml5
-rw-r--r--test/test-manager/src/package.rs7
-rw-r--r--test/test-manager/src/tests/access_methods.rs74
-rw-r--r--test/test-manager/src/tests/helpers.rs18
-rw-r--r--test/test-manager/src/tests/install.rs22
-rw-r--r--test/test-manager/src/tests/mod.rs1
-rw-r--r--test/test-manager/src/tests/ui.rs82
-rw-r--r--test/test-rpc/src/client.rs8
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)