diff options
| author | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-07-11 10:15:48 +0200 |
|---|---|---|
| committer | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-07-11 10:15:48 +0200 |
| commit | 04e07b831e66e8461d9d6dcd6217a90f6db1d3e2 (patch) | |
| tree | 45566123976ffba5101d68a1e003c93e9bfe0ac6 /test/test-manager/src | |
| parent | 62ad63a99a450a31c484316d0ddd424d9ff7192d (diff) | |
| parent | 2985cf50fc64a715767e4d3e3617933804e42af0 (diff) | |
| download | mullvadvpn-04e07b831e66e8461d9d6dcd6217a90f6db1d3e2.tar.xz mullvadvpn-04e07b831e66e8461d9d6dcd6217a90f6db1d3e2.zip | |
Merge branch 'e2e-failed-upgrades-only-softlock'
Diffstat (limited to 'test/test-manager/src')
| -rw-r--r-- | test/test-manager/src/tests/mod.rs | 58 | ||||
| -rw-r--r-- | test/test-manager/src/tests/windows.rs | 141 |
2 files changed, 173 insertions, 26 deletions
diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs index 39f4f12e90..76d3a4bb48 100644 --- a/test/test-manager/src/tests/mod.rs +++ b/test/test-manager/src/tests/mod.rs @@ -15,6 +15,7 @@ mod test_metadata; mod tunnel; mod tunnel_state; mod ui; +mod windows; use itertools::Itertools; use mullvad_types::relay_constraints::{GeographicLocationConstraint, LocationConstraint}; @@ -213,19 +214,35 @@ async fn ensure_daemon_version( ) -> anyhow::Result<MullvadProxyClient> { let app_package_filename = &TEST_CONFIG.app_package_filename; - let mullvad_client = if correct_daemon_version_is_running(rpc_provider.new_client().await).await - { - ensure_daemon_environment(rpc) - .await - .context("Failed to reset daemon environment")?; - rpc_provider.new_client().await - } else { + let must_reinstall_app = + match correct_daemon_version_is_running(rpc_provider.new_client().await).await { + Ok(correct_version) => !correct_version, + // Failing to reach the daemon is a sign that it is not installed + Err(mullvad_management_interface::Error::Rpc(..)) => { + log::debug!("Daemon is not running, attempting to start it"); + + let failed_starting_daemon = rpc.enable_mullvad_daemon().await.is_err() + || rpc.start_mullvad_daemon().await.is_err(); + if failed_starting_daemon { + log::warn!("Failed to start the daemon service"); + } + failed_starting_daemon + } + Err(e) => panic!("Failed to get app version: {e}"), + }; + + if must_reinstall_app { // NOTE: Reinstalling the app resets the daemon environment install_app(rpc, app_package_filename, rpc_provider) .await - .with_context(|| format!("Failed to install app '{app_package_filename}'"))? - }; - Ok(mullvad_client) + .with_context(|| format!("Failed to install app '{app_package_filename}'")) + } else { + ensure_daemon_environment(rpc) + .await + .context("Failed to reset daemon environment")?; + + Ok(rpc_provider.new_client().await) + } } /// Conditionally restart the running daemon @@ -253,23 +270,12 @@ pub async fn ensure_daemon_environment(rpc: &ServiceClient) -> Result<(), anyhow } /// Checks if daemon is installed with the version specified by `TEST_CONFIG.app_package_filename` -async fn correct_daemon_version_is_running(mut mullvad_client: MullvadProxyClient) -> bool { +async fn correct_daemon_version_is_running( + mut mullvad_client: MullvadProxyClient, +) -> Result<bool, mullvad_management_interface::Error> { let app_package_filename = &TEST_CONFIG.app_package_filename; let expected_version = get_version_from_path(std::path::Path::new(app_package_filename)) .unwrap_or_else(|_| panic!("Invalid app version: {app_package_filename}")); - - use mullvad_management_interface::Error::*; - match mullvad_client.get_current_version().await { - // Failing to reach the daemon is a sign that it is not installed - Err(Rpc(..)) => { - log::debug!("Could not reach active daemon before test, it is not running"); - false - } - Err(e) => panic!("Failed to get app version: {e}"), - Ok(version) if version == expected_version => true, - _ => { - log::debug!("Daemon version mismatch"); - false - } - } + let version = mullvad_client.get_current_version().await?; + Ok(version == expected_version) } diff --git a/test/test-manager/src/tests/windows.rs b/test/test-manager/src/tests/windows.rs new file mode 100644 index 0000000000..e857b6901f --- /dev/null +++ b/test/test-manager/src/tests/windows.rs @@ -0,0 +1,141 @@ +//! Windows-specific tests. + +use anyhow::{Context, ensure}; +use mullvad_management_interface::MullvadProxyClient; +use mullvad_types::states::TunnelState; +use test_macro::test_function; +use test_rpc::ServiceClient; + +use crate::tests::helpers::{geoip_lookup_with_retries, wait_for_tunnel_state}; + +use super::TestContext; + +/// Test that, on a failed upgrade, blocking firewall rules are cleared on a reboot. +#[test_function(target_os = "windows")] +async fn test_clearing_blocked_state_on_failed_upgrade( + _: TestContext, + mut rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> anyhow::Result<()> { + // Assert that the below settings are disabled. If they are not, + // then blocking firewall rules *will* persist after a reboot. + { + let settings = mullvad_client.get_settings().await?; + ensure!( + !settings.block_when_disconnected, + "Block when disconnected should be disabled" + ); + ensure!(!settings.auto_connect, "Auto connect should be disabled"); + } + + log::info!("Connecting to tunnel to enter secured state"); + // This is necessary to ensure that the firewall rules are applied + // Note that we do not need to wait for the tunnel to be fully connected + mullvad_client + .connect_tunnel() + .await + .context("failed to begin connecting")?; + log::info!("Waiting for tunnel state to be Connected or Error"); + wait_for_tunnel_state(mullvad_client.clone(), |state| { + matches!( + state, + TunnelState::Connected { .. } | TunnelState::Error(..) + ) + }) + .await?; + log::info!("Preparing daemon for restart (simulate failed upgrade)"); + mullvad_client + .prepare_restart_v2(false) + .await + .context("Failed to prepare restart")?; + + // Simulate that the daemon has been uninstalled, by disabling the system service. + // We cannot actually uninstall the daemon here, because it would remove the blocking firewall rules, + // regardless of having called `prepare_restart_v2`. + log::info!("Disabling Mullvad daemon system service"); + rpc.disable_mullvad_daemon().await?; + rpc.stop_mullvad_daemon().await?; + + // Make sure that blocking firewall rules are active - there should be no leaks (yet) 💦❌ + log::info!("Checking that blocking firewall rules are active..."); + let geoip = geoip_lookup_with_retries(&rpc).await; + ensure!( + geoip.is_err(), + "Device is leaking with geo IP '{:?}'- blocking rules have not applied properly", + geoip.unwrap() + ); + // Reboot - we expect desperate users to take this measure + log::info!("Rebooting device..."); + rpc.reboot().await?; + // The conn check should now fail - the firewall filters should have been removed at this point 💦💦💦 + log::info!("Checking connectivity after reboot (should be online, but not secured)"); + let mullvad_exit_ip = geoip_lookup_with_retries(&rpc) + .await + .context("Device is offline after reboot")? + .mullvad_exit_ip; + ensure!(!mullvad_exit_ip, "Should *not* be a Mullvad Exit IP"); + + Ok(()) +} + +/// Test that, on a failed upgrade when `Auto-connect` is enabled, blocking firewall rules are *not* cleared on a reboot. +#[test_function(target_os = "windows")] +async fn test_not_clearing_blocked_state_on_failed_upgrade_with_lockdown_mode( + _: TestContext, + mut rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> anyhow::Result<()> { + // Make sure that lockdown mode is enabled. + // If it is not, then blocking firewall rules *will not* persist after a reboot. + { + mullvad_client.set_block_when_disconnected(true).await?; + let settings = mullvad_client.get_settings().await?; + ensure!( + settings.block_when_disconnected, + "Block when disconnected should be enabled" + ); + ensure!(!settings.auto_connect, "Auto connect should be disabled"); + } + + log::info!("Waiting for tunnel state to be Disconnected with lockdown enabled"); + wait_for_tunnel_state(mullvad_client.clone(), |state| { + matches!( + state, + TunnelState::Disconnected { locked_down, .. } if *locked_down + ) + }) + .await?; + log::info!("Preparing daemon for restart (simulate failed upgrade)"); + mullvad_client + .prepare_restart_v2(false) + .await + .context("Failed to prepare restart")?; + + // Simulate that the daemon has been uninstalled, by disabling the system service. + // We cannot actually uninstall the daemon here, because it would remove the blocking firewall rules, + // regardless of having called `prepare_restart_v2`. + log::info!("Disabling Mullvad daemon system service"); + rpc.disable_mullvad_daemon().await?; + rpc.stop_mullvad_daemon().await?; + + // Make sure that blocking firewall rules are active - there should be no leaks 💦❌ + log::info!("Checking that blocking firewall rules are active..."); + let blocked = geoip_lookup_with_retries(&rpc).await.is_err(); + ensure!( + blocked, + "Device is leaking - blocking rules have not applied properly" + ); + // Reboot - we expect desperate users to take this measure + log::info!("Rebooting device..."); + rpc.reboot().await?; + + // The conn check should now fail - the firewall filters should *not* have been removed at this point 💦❌ + log::info!("Checking connectivity after reboot (should be blocked)"); + let blocked = geoip_lookup_with_retries(&rpc).await.is_err(); + ensure!( + blocked, + "Device is leaking - blocking rules have not applied properly" + ); + + Ok(()) +} |
