summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2025-07-03 14:55:58 +0200
committerSebastian Holmin <sebastian.holmin@mullvad.net>2025-07-11 10:15:29 +0200
commit1391bf030931ee44964702f5112e0bec5f18fea9 (patch)
tree2ebded458f8f4e0346e66b3c67809b87f3e9debd
parent62ad63a99a450a31c484316d0ddd424d9ff7192d (diff)
downloadmullvadvpn-1391bf030931ee44964702f5112e0bec5f18fea9.tar.xz
mullvadvpn-1391bf030931ee44964702f5112e0bec5f18fea9.zip
Add e2e test for clearing firewall rules on reboot
In some conditions, you can be left with blocking firewall rules if an upgrade fails to install the new app version. Add e2e test to assert that these firewall rules are cleared on reboot for Windows.
-rw-r--r--test/test-manager/src/tests/mod.rs1
-rw-r--r--test/test-manager/src/tests/windows.rs83
-rw-r--r--test/test-rpc/src/client.rs14
-rw-r--r--test/test-rpc/src/lib.rs3
-rw-r--r--test/test-runner/src/main.rs16
-rw-r--r--test/test-runner/src/sys.rs6
6 files changed, 123 insertions, 0 deletions
diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs
index 39f4f12e90..2194841b34 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};
diff --git a/test/test-manager/src/tests/windows.rs b/test/test-manager/src/tests/windows.rs
new file mode 100644
index 0000000000..08c88d87d5
--- /dev/null
+++ b/test/test-manager/src/tests/windows.rs
@@ -0,0 +1,83 @@
+//! 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;
+
+/// TODO: Explain me
+#[test_function(target_os = "windows")]
+async fn test_clearing_blocked_state_on_failed_upgrade(
+ _: TestContext,
+ mut rpc: ServiceClient,
+ mut mullvad_client: MullvadProxyClient,
+) -> anyhow::Result<()> {
+ let settings = mullvad_client.get_settings().await?;
+ assert!(
+ !settings.block_when_disconnected,
+ "Block when dissconnected should be disabled"
+ );
+ assert!(!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::Connecting { .. } | TunnelState::Error(..)
+ )
+ })
+ .await?;
+ // Simulate a failed upgrade
+ log::info!("Preparing daemon for restart (simulate failed upgrade)");
+ // Prepare the daemon for restarting
+ mullvad_client
+ .prepare_restart_v2(false)
+ .await
+ .context("Failed to prepare restart")?;
+ // Simulate that the daemon has been removed
+ log::info!("Disabling Mullvad daemon system service");
+ // Do this by disabling the system service (the important part is that it does not restart
+ // automatically on reboot)
+ 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");
+ // Make sure system service is enabled in the test runner.
+
+ // Do the same thing again, but with lockdown mode enabled?
+ // assert that the firewall rules are still applied
+
+ // TODO: Move this to clean up routine
+ log::info!("Re-enabling Mullvad daemon system service");
+ rpc.enable_mullvad_daemon().await?;
+ Ok(())
+}
diff --git a/test/test-rpc/src/client.rs b/test/test-rpc/src/client.rs
index ad3d39dce9..c6593c162b 100644
--- a/test/test-rpc/src/client.rs
+++ b/test/test-rpc/src/client.rs
@@ -298,6 +298,18 @@ impl ServiceClient {
Ok(())
}
+ /// Enable the daemon system service.
+ ///
+ /// Does *not* start a stopped app. See [start_mullvad_daemon].
+ pub async fn enable_mullvad_daemon_service(&self) -> Result<(), Error> {
+ let mut ctx = tarpc::context::current();
+ ctx.deadline = SystemTime::now()
+ .checked_add(DAEMON_RESTART_TIMEOUT)
+ .unwrap();
+ let _ = todo!("self.client.enable_mullvad_daemon(ctx).await?;");
+ Ok(())
+ }
+
pub async fn set_daemon_log_level(
&self,
verbosity_level: mullvad_daemon::Verbosity,
@@ -372,6 +384,8 @@ impl ServiceClient {
.await?
}
+ /// Reboot the testing VM. The VM should be completely rebooted and responsive when this
+ /// future completes.
pub async fn reboot(&mut self) -> Result<(), Error> {
log::debug!("Rebooting server");
diff --git a/test/test-rpc/src/lib.rs b/test/test-rpc/src/lib.rs
index d92e6ff3af..1bb8da6636 100644
--- a/test/test-rpc/src/lib.rs
+++ b/test/test-rpc/src/lib.rs
@@ -230,6 +230,9 @@ mod service {
/// Start the Mullvad VPN application.
async fn start_mullvad_daemon() -> Result<(), Error>;
+ /// Disable the Mullvad VPN system service.
+ async fn disable_mullvad_daemon() -> Result<(), Error>;
+
/// Sets the log level of the daemon service, the verbosity level represents the number of
/// `-v`s passed on the command line. This will restart the daemon system service.
async fn set_daemon_log_level(
diff --git a/test/test-runner/src/main.rs b/test/test-runner/src/main.rs
index 95392dc93f..fec18255d7 100644
--- a/test/test-runner/src/main.rs
+++ b/test/test-runner/src/main.rs
@@ -318,6 +318,22 @@ impl Service for TestServer {
sys::start_app().await
}
+ /// Disable the Mullvad VPN system service.
+ async fn disable_mullvad_daemon_service(
+ self,
+ _: context::Context,
+ ) -> Result<(), test_rpc::Error> {
+ sys::disable_system_service_startup().await
+ }
+
+ /// Enable the Mullvad VPN system service.
+ async fn enable_mullvad_daemon_service(
+ self,
+ _: context::Context,
+ ) -> Result<(), test_rpc::Error> {
+ sys::enable_system_service_startup().await
+ }
+
async fn set_daemon_log_level(
self,
_: context::Context,
diff --git a/test/test-runner/src/sys.rs b/test/test-runner/src/sys.rs
index a20a84cbaa..44ac2803ce 100644
--- a/test/test-runner/src/sys.rs
+++ b/test/test-runner/src/sys.rs
@@ -284,6 +284,12 @@ pub async fn start_app() -> Result<(), test_rpc::Error> {
Ok(())
}
+/// Disable the Mullvad VPN system service.
+#[cfg(target_os = "windows")]
+pub async fn disable_system_service() -> Result<(), test_rpc::Error> {
+ todo!("sc.exe disable?")
+}
+
/// Restart the Mullvad VPN application.
///
/// This function waits for the app to successfully start again.