diff options
Diffstat (limited to 'test/test-manager/src/tests')
| -rw-r--r-- | test/test-manager/src/tests/helpers.rs | 87 | ||||
| -rw-r--r-- | test/test-manager/src/tests/relay_ip_overrides.rs | 8 | ||||
| -rw-r--r-- | test/test-manager/src/tests/tunnel.rs | 121 |
3 files changed, 182 insertions, 34 deletions
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs index 55b7b2384e..ac3a16fc9a 100644 --- a/test/test-manager/src/tests/helpers.rs +++ b/test/test-manager/src/tests/helpers.rs @@ -1025,7 +1025,7 @@ pub struct ConnCheckerHandle<'a> { pub struct ConnectionStatus { /// True if <https://am.i.mullvad.net/> reported we are connected. - am_i_mullvad: bool, + am_i_mullvad: anyhow::Result<bool>, /// True if we sniffed TCP packets going outside the tunnel. leaked_tcp: bool, @@ -1041,7 +1041,7 @@ impl ConnChecker { pub fn new( rpc: ServiceClient, mullvad_client: MullvadProxyClient, - leak_destination: SocketAddr, + leak_destination: impl Into<SocketAddr>, ) -> Self { let artifacts_dir = &TEST_CONFIG.artifacts_dir; let executable_path = match TEST_CONFIG.os { @@ -1052,7 +1052,7 @@ impl ConnChecker { Self { rpc, mullvad_client, - leak_destination, + leak_destination: leak_destination.into(), split: false, executable_path, payload: None, @@ -1072,6 +1072,11 @@ impl ConnChecker { log::debug!("spawning connection checker"); let opts = { + let ipvx = match self.leak_destination { + SocketAddr::V4(..) => "ipv4", + SocketAddr::V6(..) => "ipv6", + }; + let mut args = [ "--interactive", "--timeout", @@ -1085,7 +1090,7 @@ impl ConnChecker { "--leak-udp", "--leak-icmp", "--url", - &format!("https://am.i.{}/json", TEST_CONFIG.mullvad_host), + &format!("https://{ipvx}.am.i.{}/json", TEST_CONFIG.mullvad_host), ] .map(String::from) .to_vec(); @@ -1178,28 +1183,64 @@ impl ConnCheckerHandle<'_> { self.checker.unsplit().await } + /// Assert that traffic is blocked and that no packets are leaked. + pub async fn assert_blocked(&mut self) -> anyhow::Result<()> { + log::info!("checking that connection is blocked"); + async { + let status = self.check_connection().await?; + ensure!(status.am_i_mullvad.is_err()); + ensure!(!status.leaked_tcp); + ensure!(!status.leaked_udp); + ensure!(!status.leaked_icmp); + Ok(()) + } + .await + .with_context(|| { + anyhow!( + "assert_secure failed (leak_destination={})", + self.checker.leak_destination, + ) + }) + } + /// Assert that traffic is flowing through the Mullvad tunnel and that no packets are leaked. pub async fn assert_secure(&mut self) -> anyhow::Result<()> { log::info!("checking that connection is secure"); - let status = self.check_connection().await?; - ensure!(status.am_i_mullvad); - ensure!(!status.leaked_tcp); - ensure!(!status.leaked_udp); - ensure!(!status.leaked_icmp); - - Ok(()) + async { + let status = self.check_connection().await?; + ensure!(status.am_i_mullvad?); + ensure!(!status.leaked_tcp); + ensure!(!status.leaked_udp); + ensure!(!status.leaked_icmp); + Ok(()) + } + .await + .with_context(|| { + anyhow!( + "assert_secure failed (leak_destination={})", + self.checker.leak_destination, + ) + }) } /// Assert that traffic is NOT flowing through the Mullvad tunnel and that packets ARE leaked. pub async fn assert_insecure(&mut self) -> anyhow::Result<()> { log::info!("checking that connection is not secure"); - let status = self.check_connection().await?; - ensure!(!status.am_i_mullvad); - ensure!(status.leaked_tcp); - ensure!(status.leaked_udp); - ensure!(status.leaked_icmp); - - Ok(()) + async { + let status = self.check_connection().await?; + ensure!(!status.am_i_mullvad?); + ensure!(status.leaked_tcp); + ensure!(status.leaked_udp); + ensure!(status.leaked_icmp); + Ok(()) + } + .await + .with_context(|| { + anyhow!( + "assert_secure failed (leak_destination={})", + self.checker.leak_destination, + ) + }) } pub async fn check_connection(&mut self) -> anyhow::Result<ConnectionStatus> { @@ -1228,8 +1269,10 @@ impl ConnCheckerHandle<'_> { .await .map_err(|_e| anyhow!("Packet monitor unexpectedly stopped"))?; + let leak_destination = self.checker.leak_destination; + Ok(ConnectionStatus { - am_i_mullvad: parse_am_i_mullvad(line)?, + am_i_mullvad: parse_am_i_mullvad(line), leaked_tcp: (monitor_result.packets.iter()) .any(|pkt| pkt.protocol == IpNextHeaderProtocols::Tcp), @@ -1237,8 +1280,10 @@ impl ConnCheckerHandle<'_> { leaked_udp: (monitor_result.packets.iter()) .any(|pkt| pkt.protocol == IpNextHeaderProtocols::Udp), - leaked_icmp: (monitor_result.packets.iter()) - .any(|pkt| pkt.protocol == IpNextHeaderProtocols::Icmp), + leaked_icmp: (monitor_result.packets.iter()).any(|pkt| match leak_destination { + SocketAddr::V4(..) => pkt.protocol == IpNextHeaderProtocols::Icmp, + SocketAddr::V6(..) => pkt.protocol == IpNextHeaderProtocols::Icmpv6, + }), }) } diff --git a/test/test-manager/src/tests/relay_ip_overrides.rs b/test/test-manager/src/tests/relay_ip_overrides.rs index 36a9c828f8..786060bf23 100644 --- a/test/test-manager/src/tests/relay_ip_overrides.rs +++ b/test/test-manager/src/tests/relay_ip_overrides.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::{ tests::config::TEST_CONFIG, - vm::{self, network::linux::TEST_SUBNET}, + vm::{self, network::linux::TEST_SUBNET_IPV4}, }; use anyhow::{Context, anyhow, bail, ensure}; use futures::FutureExt; @@ -330,9 +330,10 @@ async fn pick_a_relay( /// Spawn a TCP socket that forwards packets between `destination` and anyone that connects to it. /// +/// The proxy socket will be bound to [TEST_SUBNET_V4]. /// Returns a handle that will stop the proxy when dropped. async fn spawn_tcp_proxy(destination: SocketAddr, port: u16) -> anyhow::Result<AbortOnDrop<()>> { - let socket = TcpListener::bind((TEST_SUBNET.ip(), port)).await?; + let socket = TcpListener::bind((TEST_SUBNET_IPV4.ip(), port)).await?; log::info!("started TCP proxy to {destination} on port {port}"); async fn client_task(destination: SocketAddr, mut client: TcpStream) -> anyhow::Result<()> { @@ -383,9 +384,10 @@ async fn spawn_tcp_proxy(destination: SocketAddr, port: u16) -> anyhow::Result<A /// /// NOTE: Doesn't work with multiple concurrent clients. /// +/// The proxy socket will be bound to [TEST_SUBNET_V4]. /// Returns a handle that will stop the proxy when dropped. async fn spawn_udp_proxy(destination: SocketAddr, port: u16) -> anyhow::Result<AbortOnDrop<()>> { - let socket = UdpSocket::bind((TEST_SUBNET.ip(), port)).await?; + let socket = UdpSocket::bind((TEST_SUBNET_IPV4.ip(), port)).await?; log::info!("started UDP proxy to {destination} on port {port}"); async fn proxy_task(destination: SocketAddr, socket: UdpSocket) -> anyhow::Result<()> { diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index 5e7e1eb383..828ced73a8 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -5,10 +5,13 @@ use super::{ }; use crate::{ network_monitor::{MonitorOptions, start_packet_monitor}, - tests::helpers::{geoip_lookup_with_retries, login_with_retries, update_relay_constraints}, + tests::helpers::{ + ConnChecker, geoip_lookup_with_retries, login_with_retries, update_relay_constraints, + }, }; use anyhow::{Context, ensure}; +use duplicate::duplicate_item; use mullvad_management_interface::MullvadProxyClient; use mullvad_relay_selector::query::builder::RelayQueryBuilder; use mullvad_types::{ @@ -18,9 +21,12 @@ use mullvad_types::{ }, wireguard, }; -use std::net::SocketAddr; +use std::{ + net::{Ipv4Addr, Ipv6Addr, SocketAddr}, + str::FromStr, +}; use talpid_types::net::{ - TransportProtocol, TunnelType, + IpVersion, TransportProtocol, TunnelType, proxy::{CustomProxy, Socks5Local, Socks5Remote}, }; use test_macro::test_function; @@ -82,21 +88,29 @@ pub async fn test_openvpn_tunnel( /// Set up a WireGuard tunnel. /// This test fails if a working tunnel cannot be set up. /// WARNING: This test will fail if host has something bound to port 53 such as a connected Mullvad +#[duplicate_item( + VX test_wireguard_tunnel_ipvx; + [ V4 ] [ test_wireguard_tunnel_ipv4 ]; + [ V6 ] [ test_wireguard_tunnel_ipv6 ]; +)] #[test_function] -pub async fn test_wireguard_tunnel( +pub async fn test_wireguard_tunnel_ipvx( _: TestContext, rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> Result<(), Error> { // TODO: observe UDP traffic on the expected destination/port (only) - // TODO: IPv6 + let ip_version = IpVersion::VX; const PORTS: [(u16, bool); 3] = [(53, true), (51820, true), (1, false)]; for (port, should_succeed) in PORTS { log::info!("Connect to WireGuard endpoint on port {port}"); - let query = RelayQueryBuilder::wireguard().port(port).build(); + let query = RelayQueryBuilder::wireguard() + .port(port) + .ip_version(ip_version) + .build(); apply_settings_from_relay_query(&mut mullvad_client, query) .await @@ -122,6 +136,67 @@ pub async fn test_wireguard_tunnel( Ok(()) } +/// Set up a WireGuard tunnel and check whether in-tunnel IPv6 works. +/// WARNING: This test will fail if host has something bound to port 53 such as a connected Mullvad +#[duplicate_item( + VX test_wireguard_ipv6_in_ipvx; + [ V4 ] [ test_wireguard_ipv6_in_ipv4 ]; + [ V6 ] [ test_wireguard_ipv6_in_ipv6 ]; +)] +#[test_function] +pub async fn test_wireguard_ipv6_in_ipvx( + _: TestContext, + rpc: ServiceClient, + mut mullvad_client: MullvadProxyClient, +) -> Result<(), Error> { + let ip_version = IpVersion::VX; + + let mut conn_checker_v4 = ConnChecker::new( + rpc.clone(), + mullvad_client.clone(), + (Ipv4Addr::new(1, 1, 1, 1), 53), + ); + + let mut conn_checker_v6 = ConnChecker::new( + rpc.clone(), + mullvad_client.clone(), + (Ipv6Addr::from_str("2606:4700:4700::1111").unwrap(), 53), + ); + + let mut conn_checker_v4 = conn_checker_v4.spawn().await?; + let mut conn_checker_v6 = conn_checker_v6.spawn().await?; + + conn_checker_v4.assert_insecure().await?; + conn_checker_v6.assert_insecure().await?; + + log::info!("Connect to WireGuard endpoint"); + + let query = RelayQueryBuilder::wireguard() + .ip_version(ip_version) + .build(); + apply_settings_from_relay_query(&mut mullvad_client, query) + .await + .unwrap(); + + // Test with in-tunnel IPv6 enabled + mullvad_client.set_enable_ipv6(true).await?; + let connection_result = connect_and_wait(&mut mullvad_client).await; + assert!(connection_result.is_ok()); + conn_checker_v4.assert_secure().await?; + conn_checker_v6.assert_secure().await?; + + // Test with in-tunnel IPv6 disabled + mullvad_client.set_enable_ipv6(false).await?; + let connection_result = connect_and_wait(&mut mullvad_client).await; + assert!(connection_result.is_ok()); + conn_checker_v4.assert_secure().await?; + conn_checker_v6.assert_blocked().await?; // ipv6 mustnt leak + + disconnect_and_wait(&mut mullvad_client).await?; + + Ok(()) +} + /// Use udp2tcp obfuscation. This test connects to a WireGuard relay over TCP. It fails if no /// outgoing TCP traffic to the relay is observed on the expected port. #[test_function] @@ -202,14 +277,24 @@ pub async fn test_wireguard_over_shadowsocks( /// Use QUIC obfuscation. This tests whether the daemon can establish a QUIC connection. /// Note that this doesn't verify that the outgoing traffic looks like http traffic (even though it /// doesn't sound too difficult to do?). +#[duplicate_item( + VX test_wireguard_over_quic_ipvx; + [ V4 ] [ test_wireguard_over_quic_ipv4 ]; + [ V6 ] [ test_wireguard_over_quic_ipv6 ]; +)] #[test_function] -pub async fn test_wireguard_over_quic( +pub async fn test_wireguard_over_quic_ipvx( _: TestContext, rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { + let ip_version = IpVersion::VX; + log::info!("Enable QUIC as obfuscation method"); - let query = RelayQueryBuilder::wireguard().quic().build(); + let query = RelayQueryBuilder::wireguard() + .ip_version(ip_version) + .quic() + .build(); apply_settings_from_relay_query(&mut mullvad_client, query).await?; log::info!("Connect to WireGuard via QUIC endpoint"); @@ -553,18 +638,26 @@ pub async fn test_quantum_resistant_multihop_udp2tcp_tunnel( /// /// This is not testing any of the individual components, just whether the daemon can connect when /// all of these features are combined. +#[duplicate_item( + VX test_quantum_resistant_multihop_shadowsocks_tunnel_ipvx; + [ V4 ] [ test_quantum_resistant_multihop_shadowsocks_tunnel_ipv4 ]; + [ V6 ] [ test_quantum_resistant_multihop_shadowsocks_tunnel_ipv6 ]; +)] #[test_function] -pub async fn test_quantum_resistant_multihop_shadowsocks_tunnel( +pub async fn test_quantum_resistant_multihop_shadowsocks_tunnel_ipvx( _: TestContext, rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { + let ip_version = IpVersion::VX; + mullvad_client .set_quantum_resistant_tunnel(wireguard::QuantumResistantState::On) .await .context("Failed to enable PQ tunnels")?; let query = RelayQueryBuilder::wireguard() + .ip_version(ip_version) .multihop() .shadowsocks() .build(); @@ -587,12 +680,19 @@ pub async fn test_quantum_resistant_multihop_shadowsocks_tunnel( /// /// This is not testing any of the individual components, just whether the daemon can connect when /// all of these features are combined. +#[duplicate_item( + VX test_quantum_resistant_multihop_quic_tunnel_ipvx; + [ V4 ] [ test_quantum_resistant_multihop_quic_tunnel_ipv4 ]; + [ V6 ] [ test_quantum_resistant_multihop_quic_tunnel_ipv6 ]; +)] #[test_function] -pub async fn test_quantum_resistant_multihop_quic_tunnel( +pub async fn test_quantum_resistant_multihop_quic_tunnel_ipvx( _: TestContext, rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { + let ip_version = IpVersion::VX; + mullvad_client // TODO: Why is this needed, exactly? .set_quantum_resistant_tunnel(wireguard::QuantumResistantState::On) @@ -600,6 +700,7 @@ pub async fn test_quantum_resistant_multihop_quic_tunnel( .context("Failed to enable PQ tunnels")?; let query = RelayQueryBuilder::wireguard() + .ip_version(ip_version) .quantum_resistant() .multihop() .quic() |
