diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-03-21 10:53:42 +0100 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2024-03-25 11:07:34 +0100 |
| commit | 1bb03d8b151b643f86e026ebf250e5d452f9b727 (patch) | |
| tree | e6c4ac7728725b546261cbc737af19f6abc4e7ed /test/test-manager/src | |
| parent | b2a81664962728e2a7e8b8eee27050bde1756e20 (diff) | |
| download | mullvadvpn-1bb03d8b151b643f86e026ebf250e5d452f9b727.tar.xz mullvadvpn-1bb03d8b151b643f86e026ebf250e5d452f9b727.zip | |
Make sure connecting works while API is unavailable
Diffstat (limited to 'test/test-manager/src')
| -rw-r--r-- | test/test-manager/src/tests/account.rs | 30 | ||||
| -rw-r--r-- | test/test-manager/src/tests/helpers.rs | 87 | ||||
| -rw-r--r-- | test/test-manager/src/tests/tunnel.rs | 32 |
3 files changed, 104 insertions, 45 deletions
diff --git a/test/test-manager/src/tests/account.rs b/test/test-manager/src/tests/account.rs index 60c61a78ff..80c6272d2a 100644 --- a/test/test-manager/src/tests/account.rs +++ b/test/test-manager/src/tests/account.rs @@ -1,3 +1,5 @@ +use crate::tests::helpers::{login_with_retries, THROTTLE_RETRY_DELAY}; + use super::config::TEST_CONFIG; use super::{helpers, ui, Error, TestContext}; use mullvad_api::DevicesProxy; @@ -10,8 +12,6 @@ use talpid_types::net::wireguard; use test_macro::test_function; use test_rpc::ServiceClient; -const THROTTLE_RETRY_DELAY: Duration = Duration::from_secs(120); - /// Log in and create a new device for the account. #[test_function(always_run = true, must_succeed = true, priority = -100)] pub async fn test_login( @@ -241,32 +241,6 @@ pub fn new_device_client() -> DevicesProxy { DevicesProxy::new(rest_handle) } -/// Log in and retry if it fails due to throttling -pub async fn login_with_retries( - mullvad_client: &mut MullvadProxyClient, -) -> Result<(), mullvad_management_interface::Error> { - loop { - match mullvad_client - .login_account(TEST_CONFIG.account_number.clone()) - .await - { - Err(mullvad_management_interface::Error::Rpc(status)) - if status.message().to_uppercase().contains("THROTTLED") => - { - // Work around throttling errors by sleeping - log::debug!( - "Login failed due to throttling. Sleeping for {} seconds", - THROTTLE_RETRY_DELAY.as_secs() - ); - - tokio::time::sleep(THROTTLE_RETRY_DELAY).await; - } - Err(err) => break Err(err), - Ok(_) => break Ok(()), - } - } -} - pub async fn list_devices_with_retries( device_client: &DevicesProxy, ) -> Result<Vec<Device>, mullvad_api::rest::Error> { diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs index 108da125f6..3c645268c4 100644 --- a/test/test-manager/src/tests/helpers.rs +++ b/test/test-manager/src/tests/helpers.rs @@ -1,4 +1,4 @@ -use super::{config::TEST_CONFIG, Error, WAIT_FOR_TUNNEL_STATE_TIMEOUT}; +use super::{config::TEST_CONFIG, Error, TestContext, WAIT_FOR_TUNNEL_STATE_TIMEOUT}; use crate::network_monitor::{ self, start_packet_monitor, MonitorOptions, MonitorUnexpectedlyStopped, PacketMonitor, }; @@ -22,6 +22,8 @@ use std::{ use talpid_types::net::wireguard::{PeerConfig, PrivateKey, TunnelConfig}; use test_rpc::{package::Package, AmIMullvad, ServiceClient}; +pub const THROTTLE_RETRY_DELAY: Duration = Duration::from_secs(120); + #[macro_export] macro_rules! assert_tunnel_state { ($mullvad_client:expr, $pattern:pat) => {{ @@ -199,19 +201,44 @@ pub async fn ping_sized_with_timeout( .map_err(Error::Rpc) } +/// Log in and retry if it fails due to throttling +pub async fn login_with_retries( + mullvad_client: &mut MullvadProxyClient, +) -> Result<(), mullvad_management_interface::Error> { + loop { + match mullvad_client + .login_account(TEST_CONFIG.account_number.clone()) + .await + { + Err(mullvad_management_interface::Error::Rpc(status)) + if status.message().to_uppercase().contains("THROTTLED") => + { + // Work around throttling errors by sleeping + log::debug!( + "Login failed due to throttling. Sleeping for {} seconds", + THROTTLE_RETRY_DELAY.as_secs() + ); + + tokio::time::sleep(THROTTLE_RETRY_DELAY).await; + } + Err(err) => break Err(err), + Ok(_) => break Ok(()), + } + } +} + /// Try to connect to a Mullvad Tunnel. /// -/// If that fails to begin to connect, [`Error::DaemonError`] is returned. If it fails to connect -/// after that, the daemon ends up in the [`TunnelState::Error`] state, and -/// [`Error::UnexpectedErrorState`] is returned. +/// # Returns +/// - `Result::Ok` if the daemon successfully connected to a tunnel +/// - `Result::Err` if: +/// - The daemon failed to even begin connecting. Then [`Error::Rpc`] is returned. +/// - The daemon started to connect but ended up in the [`TunnelState::Error`] state. +/// Then [`Error::UnexpectedErrorState`] is returned pub async fn connect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result<(), Error> { log::info!("Connecting"); - mullvad_client - .connect_tunnel() - .await - .map_err(|error| Error::Daemon(format!("failed to begin connecting: {}", error)))?; - + mullvad_client.connect_tunnel().await?; let new_state = wait_for_tunnel_state(mullvad_client.clone(), |state| { matches!( state, @@ -231,11 +258,8 @@ pub async fn connect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result pub async fn disconnect_and_wait(mullvad_client: &mut MullvadProxyClient) -> Result<(), Error> { log::info!("Disconnecting"); + mullvad_client.disconnect_tunnel().await?; - mullvad_client - .disconnect_tunnel() - .await - .map_err(|error| Error::Daemon(format!("failed to begin disconnecting: {}", error)))?; wait_for_tunnel_state(mullvad_client.clone(), |state| { matches!(state, TunnelState::Disconnected { .. }) }) @@ -308,6 +332,30 @@ where } } +/// Set environment variables specified by `env` and restart the Mullvad daemon. +/// Returns a new [rpc client][`MullvadProxyClient`], since the old client *probably* +/// can't communicate with the new daemon. +/// +/// # Note +/// This is just a thin wrapper around [`ServiceClient::set_daemon_environment`] which also +/// invalidates the old [`MullvadProxyClient`]. +pub async fn restart_daemon_with<K, V, Env>( + rpc: &ServiceClient, + test_context: &TestContext, + _: MullvadProxyClient, // Just consume the old proxy client + env: Env, +) -> Result<MullvadProxyClient, Error> +where + Env: IntoIterator<Item = (K, V)>, + K: Into<String>, + V: Into<String>, +{ + rpc.set_daemon_environment(env).await?; + // Need to create a new `mullvad_client` here after the restart + // otherwise we *probably* can't communicate with the daemon. + Ok(test_context.rpc_provider.new_client().await) +} + pub async fn geoip_lookup_with_retries(rpc: &ServiceClient) -> Result<AmIMullvad, Error> { const MAX_ATTEMPTS: usize = 5; const BEFORE_RETRY_DELAY: Duration = Duration::from_secs(2); @@ -409,6 +457,11 @@ pub fn unreachable_wireguard_tunnel() -> talpid_types::net::wireguard::Connectio } } +/// Return the current `MULLVAD_API_HOST` et al. +/// +/// # Note +/// This is independent of the running daemon's environment. +/// It is solely dependant on the current value of [`TEST_CONFIG`]. pub fn get_app_env() -> HashMap<String, String> { use mullvad_api::env; use std::net::ToSocketAddrs; @@ -420,10 +473,10 @@ pub fn get_app_env() -> HashMap<String, String> { .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() + HashMap::from_iter(vec![ + (env::API_HOST_VAR.to_string(), api_host), + (env::API_ADDR_VAR.to_string(), api_addr.to_string()), + ]) } /// Return a filtered version of the daemon's relay list. diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index 3bbb832443..e4802d20c5 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -3,6 +3,7 @@ use super::helpers::{ }; use super::{config::TEST_CONFIG, Error, TestContext}; use crate::network_monitor::{start_packet_monitor, MonitorOptions}; +use crate::tests::helpers::login_with_retries; use mullvad_management_interface::MullvadProxyClient; use mullvad_types::relay_constraints::{ @@ -770,3 +771,34 @@ pub async fn test_local_socks_bridge( Ok(()) } + +/// Verify that the app can connect to a VPN server and get working internet when the API is down. +/// As long as the user has managed to log in to the app, establishing a tunnel should work even if the API is down (This includes actually being down, not just censored). +/// +/// The test procedure is as follows: +/// 1. The app is logged in +/// 2. The app is killed +/// 3. The API is "removed" (override API IP/host to something bogus) +/// 4. The app is restarted +/// 5. Verify that it starts as intended and a tunnel can be established +#[test_function] +pub async fn test_establish_tunnel_without_api( + ctx: TestContext, + rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> anyhow::Result<()> { + // 1 + login_with_retries(&mut mullvad_client).await?; + // 2 + rpc.stop_mullvad_daemon().await?; + // 3 + let borked_env = [("MULLVAD_API_ADDR", "1.3.3.7:421")]; + // 4 + log::debug!("Restarting the daemon with the following overrides: {borked_env:?}"); + let mut mullvad_client = + helpers::restart_daemon_with(&rpc, &ctx, mullvad_client, borked_env).await?; + // 5 + connect_and_wait(&mut mullvad_client).await?; + // Profit + Ok(()) +} |
