From 31637ab8b58a7e1ab00acdf32d5ee5ddbd08c458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20L=C3=B6nnhager?= Date: Mon, 26 Aug 2024 21:40:30 +0200 Subject: [PATCH] Fix test_split_tunnel Raw ICMP isn't allowed on all platforms for unprivileged users. Instead: * Use SOCK_DGRAM, where possible * Use 'ping' command, since SOCK_DGRAM isn't allowed on some older Linux distros. --- test/connection-checker/src/cli.rs | 8 ++-- test/connection-checker/src/main.rs | 2 +- test/connection-checker/src/net.rs | 58 ++++++++++++++++++++++++-- test/scripts/ssh-setup.sh | 3 ++ test/test-manager/src/tests/helpers.rs | 18 ++++---- 5 files changed, 72 insertions(+), 17 deletions(-) diff --git a/test/connection-checker/src/cli.rs b/test/connection-checker/src/cli.rs index ecc33dd7e9db..97240cf974c6 100644 --- a/test/connection-checker/src/cli.rs +++ b/test/connection-checker/src/cli.rs @@ -10,8 +10,8 @@ pub struct Opt { #[clap(short, long)] pub interactive: bool, - /// Timeout for network connection to am.i.mullvad (in millis). - #[clap(short, long, default_value = "3000")] + /// Timeout for network connection to am.i.mullvad (in seconds). + #[clap(short, long, default_value = "3")] pub timeout: u64, /// Try to send some junk data over TCP to . @@ -30,8 +30,8 @@ pub struct Opt { #[clap(long)] pub leak: Option, - /// Timeout for leak check network connections (in millis). - #[clap(long, default_value = "1000")] + /// Timeout for leak check network connections (in seconds). + #[clap(long, default_value = "1")] pub leak_timeout: u64, /// Junk data for each UDP and TCP packet diff --git a/test/connection-checker/src/main.rs b/test/connection-checker/src/main.rs index 16050f322ea2..c56d8b31da15 100644 --- a/test/connection-checker/src/main.rs +++ b/test/connection-checker/src/main.rs @@ -54,7 +54,7 @@ fn am_i_mullvad(opt: &Opt) -> eyre::Result { let client = Client::new(); let response: Response = client .get(url) - .timeout(Duration::from_millis(opt.timeout)) + .timeout(Duration::from_secs(opt.timeout)) .send() .and_then(|r| r.json()) .wrap_err_with(|| eyre!("Failed to GET {url}"))?; diff --git a/test/connection-checker/src/net.rs b/test/connection-checker/src/net.rs index 40db99e8b509..0ac19f3ca540 100644 --- a/test/connection-checker/src/net.rs +++ b/test/connection-checker/src/net.rs @@ -22,7 +22,7 @@ pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> eyre::Result<()> { sock.bind(&socket2::SockAddr::from(bind_addr)) .wrap_err(eyre!("Failed to bind TCP socket to {bind_addr}"))?; - let timeout = Duration::from_millis(opt.leak_timeout); + let timeout = Duration::from_secs(opt.leak_timeout); sock.set_write_timeout(Some(timeout))?; sock.set_read_timeout(Some(timeout))?; @@ -60,12 +60,13 @@ pub fn send_udp(opt: &Opt, destination: SocketAddr) -> Result<(), eyre::Error> { Ok(()) } +#[cfg(target_os = "windows")] pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> { - eprintln!("Leaking IMCP packets to {destination}"); + eprintln!("Leaking ICMP packets to {destination}"); ping::ping( destination, - Some(Duration::from_millis(opt.leak_timeout)), + Some(Duration::from_secs(opt.leak_timeout)), None, None, None, @@ -74,3 +75,54 @@ pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> { Ok(()) } + +#[cfg(target_os = "macos")] +pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> { + eprintln!("Leaking ICMP packets to {destination}"); + + // On macOS, use dgramsock (SOCK_DGRAM) instead of the default sock type (SOCK_RAW), + // so that we don't need root privileges. Naturally, this does not work for Windows. + ping::dgramsock::ping( + destination, + Some(Duration::from_secs(opt.leak_timeout)), + None, + None, + None, + None, + )?; + + Ok(()) +} + +// Older Linux distributions don't allow unprivileged users to send ICMP packets, even for +// SOCK_DGRAM sockets. We use the ping command (which has capabilities/setuid set) to get around +// that. +#[cfg(target_os = "linux")] +pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> { + eprintln!("Leaking ICMP packets to {destination}"); + + let mut cmd = std::process::Command::new("ping"); + + let timeout_sec = opt.leak_timeout.to_string(); + + cmd.args(["-c", "1", "-W", &timeout_sec, &destination.to_string()]); + + let output = cmd.output().wrap_err(eyre!( + "Failed to execute ping for destination {destination}" + ))?; + + if !output.status.success() { + eprintln!( + "ping stdout:\n\n{}", + std::str::from_utf8(&output.stdout).unwrap_or("invalid utf8") + ); + eprintln!( + "ping stderr:\n\n{}", + std::str::from_utf8(&output.stderr).unwrap_or("invalid utf8") + ); + + return Err(eyre!("ping for destination {destination} failed")); + } + + Ok(()) +} diff --git a/test/scripts/ssh-setup.sh b/test/scripts/ssh-setup.sh index 5ac5dea15efa..0bbea4f0b922 100644 --- a/test/scripts/ssh-setup.sh +++ b/test/scripts/ssh-setup.sh @@ -128,6 +128,9 @@ function install_packages_apt { echo "Installing required apt packages" apt update apt install -yf xvfb wireguard-tools curl + if ! which ping &>/dev/null; then + apt install -yf iputils-ping + fi curl -fsSL https://get.docker.com | sh } diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs index 4c9da00a168e..542cf0c8a492 100644 --- a/test/test-manager/src/tests/helpers.rs +++ b/test/test-manager/src/tests/helpers.rs @@ -44,15 +44,15 @@ pub const THROTTLE_RETRY_DELAY: Duration = Duration::from_secs(120); const CHECKER_FILENAME_WINDOWS: &str = "connection-checker.exe"; const CHECKER_FILENAME_UNIX: &str = "connection-checker"; -const AM_I_MULLVAD_TIMEOUT_MS: u64 = 10000; -const LEAK_TIMEOUT_MS: u64 = 500; +const AM_I_MULLVAD_TIMEOUT_S: u64 = 10; +const LEAK_TIMEOUT_S: u64 = 1; /// Timeout of [ConnCheckerHandle::check_connection]. -const CONN_CHECKER_TIMEOUT: Duration = Duration::from_millis( - AM_I_MULLVAD_TIMEOUT_MS // https://am.i.mullvad.net timeout - + LEAK_TIMEOUT_MS // leak-tcp timeout - + LEAK_TIMEOUT_MS // leak-icmp timeout - + 1000, // plus some extra grace time +const CONN_CHECKER_TIMEOUT: Duration = Duration::from_secs( + AM_I_MULLVAD_TIMEOUT_S // https://am.i.mullvad.net timeout + + LEAK_TIMEOUT_S // leak-tcp timeout + + LEAK_TIMEOUT_S // leak-icmp timeout + + 1, // plus some extra grace time ); #[macro_export] @@ -957,12 +957,12 @@ impl ConnChecker { let mut args = [ "--interactive", "--timeout", - &AM_I_MULLVAD_TIMEOUT_MS.to_string(), + &AM_I_MULLVAD_TIMEOUT_S.to_string(), // try to leak traffic to LEAK_DESTINATION "--leak", &self.leak_destination.to_string(), "--leak-timeout", - &LEAK_TIMEOUT_MS.to_string(), + &LEAK_TIMEOUT_S.to_string(), "--leak-tcp", "--leak-udp", "--leak-icmp",