summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--test/Cargo.lock77
-rw-r--r--test/Cargo.toml20
-rw-r--r--test/test-manager/Cargo.toml1
-rw-r--r--test/test-manager/src/tests/helpers.rs24
-rw-r--r--test/test-manager/src/tests/mod.rs4
-rw-r--r--test/test-manager/src/tests/tunnel_state.rs152
-rw-r--r--test/test-manager/src/vm/network/linux.rs2
-rw-r--r--test/test-rpc/src/client.rs20
-rw-r--r--test/test-rpc/src/lib.rs30
-rw-r--r--test/test-runner/Cargo.toml14
-rw-r--r--test/test-runner/src/main.rs20
-rw-r--r--test/test-runner/src/net.rs146
12 files changed, 367 insertions, 143 deletions
diff --git a/test/Cargo.lock b/test/Cargo.lock
index f05e2dc9dd..2579a6a005 100644
--- a/test/Cargo.lock
+++ b/test/Cargo.lock
@@ -1097,6 +1097,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
name = "hkdf"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2166,6 +2172,15 @@ dependencies = [
]
[[package]]
+name = "pnet_base"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872e46346144ebf35219ccaa64b1dffacd9c6f188cd7d012bd6977a2a838f42e"
+dependencies = [
+ "no-std-net",
+]
+
+[[package]]
name = "pnet_macros"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2178,12 +2193,33 @@ dependencies = [
]
[[package]]
+name = "pnet_macros"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a780e80005c2e463ec25a6e9f928630049a10b43945fea83207207d4a7606f4"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+]
+
+[[package]]
name = "pnet_macros_support"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89de095dc7739349559913aed1ef6a11e73ceade4897dadc77c5e09de6740750"
dependencies = [
- "pnet_base",
+ "pnet_base 0.31.0",
+]
+
+[[package]]
+name = "pnet_macros_support"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d932134f32efd7834eb8b16d42418dac87086347d1bc7d142370ef078582bc"
+dependencies = [
+ "pnet_base 0.33.0",
]
[[package]]
@@ -2193,9 +2229,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc3b5111e697c39c8b9795b9fdccbc301ab696699e88b9ea5a4e4628978f495f"
dependencies = [
"glob",
- "pnet_base",
- "pnet_macros",
- "pnet_macros_support",
+ "pnet_base 0.31.0",
+ "pnet_macros 0.31.0",
+ "pnet_macros_support 0.31.0",
+]
+
+[[package]]
+name = "pnet_packet"
+version = "0.33.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bde678bbd85cb1c2d99dc9fc596e57f03aa725f84f3168b0eaf33eeccb41706"
+dependencies = [
+ "glob",
+ "pnet_base 0.33.0",
+ "pnet_macros 0.33.0",
+ "pnet_macros_support 0.33.0",
]
[[package]]
@@ -2952,6 +3000,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
+name = "surge-ping"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af341b2be485d647b5dc4cfb2da99efac35b5c95748a08fb7233480fedc5ead3"
+dependencies = [
+ "hex",
+ "parking_lot 0.12.1",
+ "pnet_packet 0.33.0",
+ "rand 0.8.5",
+ "socket2 0.5.4",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3116,8 +3180,9 @@ dependencies = [
"nix 0.25.1",
"once_cell",
"pcap",
- "pnet_packet",
+ "pnet_packet 0.31.0",
"regex",
+ "scopeguard",
"serde",
"serde_json",
"socks-server",
@@ -3174,10 +3239,12 @@ dependencies = [
"once_cell",
"parity-tokio-ipc",
"plist",
+ "rand 0.8.5",
"rs-release",
"serde",
"serde_json",
"socket2 0.5.4",
+ "surge-ping",
"talpid-platform-metadata",
"talpid-windows",
"tarpc",
diff --git a/test/Cargo.toml b/test/Cargo.toml
index 44cc016d66..977f9082d8 100644
--- a/test/Cargo.toml
+++ b/test/Cargo.toml
@@ -7,12 +7,7 @@ rust-version = "1.75.0"
[workspace]
resolver = "2"
-members = [
- "test-manager",
- "test-runner",
- "test-rpc",
- "socks-server",
-]
+members = ["test-manager", "test-runner", "test-rpc", "socks-server"]
[workspace.lints.rust]
rust_2018_idioms = "deny"
@@ -22,7 +17,15 @@ unused_async = "deny"
[workspace.dependencies]
futures = "0.3"
-tokio = { version = "1.8", features = ["macros", "rt", "process", "time", "fs", "io-util", "rt-multi-thread"] }
+tokio = { version = "1.8", features = [
+ "macros",
+ "rt",
+ "process",
+ "time",
+ "fs",
+ "io-util",
+ "rt-multi-thread",
+] }
tokio-serial = "5.4.1"
# Serde and related crates
serde = "1.0"
@@ -46,8 +49,9 @@ shadowsocks-service = { version = "1.16" }
windows-sys = "0.48.0"
-chrono = { version = "0.4.26", default-features = false}
+chrono = { version = "0.4.26", default-features = false }
clap = { version = "4.2.7", features = ["cargo", "derive"] }
once_cell = "1.16.0"
bytes = "1.3.0"
async-trait = "0.1.58"
+surge-ping = "0.8"
diff --git a/test/test-manager/Cargo.toml b/test/test-manager/Cargo.toml
index 66ef6f0025..f0e6cb9235 100644
--- a/test/test-manager/Cargo.toml
+++ b/test/test-manager/Cargo.toml
@@ -32,6 +32,7 @@ async-tempfile = "0.2"
async-trait = { workspace = true }
uuid = "1.3"
dirs = "5.0.1"
+scopeguard = "1.2"
serde = { workspace = true }
serde_json = { workspace = true }
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs
index c3e0e59481..108da125f6 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, PING_TIMEOUT, WAIT_FOR_TUNNEL_STATE_TIMEOUT};
+use super::{config::TEST_CONFIG, Error, WAIT_FOR_TUNNEL_STATE_TIMEOUT};
use crate::network_monitor::{
self, start_packet_monitor, MonitorOptions, MonitorUnexpectedlyStopped, PacketMonitor,
};
@@ -21,7 +21,6 @@ use std::{
};
use talpid_types::net::wireguard::{PeerConfig, PrivateKey, TunnelConfig};
use test_rpc::{package::Package, AmIMullvad, ServiceClient};
-use tokio::time::timeout;
#[macro_export]
macro_rules! assert_tunnel_state {
@@ -182,9 +181,21 @@ pub async fn ping_with_timeout(
dest: IpAddr,
interface: Option<String>,
) -> Result<(), Error> {
- timeout(PING_TIMEOUT, rpc.send_ping(interface, dest))
+ const DEFAULT_PING_SIZE: usize = 64;
+
+ rpc.send_ping(dest, interface, DEFAULT_PING_SIZE)
+ .await
+ .map_err(Error::Rpc)
+}
+
+pub async fn ping_sized_with_timeout(
+ rpc: &ServiceClient,
+ dest: IpAddr,
+ interface: Option<String>,
+ size: usize,
+) -> Result<(), Error> {
+ rpc.send_ping(dest, interface, size)
.await
- .map_err(|_| Error::PingTimeout)?
.map_err(Error::Rpc)
}
@@ -506,8 +517,9 @@ impl Pinger {
log::debug!("Monitoring outgoing traffic");
let monitor = start_packet_monitor(
move |packet| {
- // NOTE: Many packets will likely be observed for API traffic. Rather than filtering all
- // of those specifically, simply fail if our probes are observed.
+ // NOTE: Many packets will likely be observed for API traffic. Rather than filtering
+ // all of those specifically, simply fail if our probes are
+ // observed.
packet.source.ip() == guest_ip
&& packet.destination.ip() == builder.destination.ip()
},
diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs
index 0e3e73a2f6..b104dbe9e8 100644
--- a/test/test-manager/src/tests/mod.rs
+++ b/test/test-manager/src/tests/mod.rs
@@ -21,7 +21,6 @@ use futures::future::BoxFuture;
use mullvad_management_interface::MullvadProxyClient;
use std::time::Duration;
-const PING_TIMEOUT: Duration = Duration::from_secs(3);
const WAIT_FOR_TUNNEL_STATE_TIMEOUT: Duration = Duration::from_secs(40);
#[derive(Clone)]
@@ -42,9 +41,6 @@ pub enum Error {
#[error("RPC call failed")]
Rpc(#[from] test_rpc::Error),
- #[error("Timeout waiting for ping")]
- PingTimeout,
-
#[error("geoip lookup failed")]
GeoipLookup(test_rpc::Error),
diff --git a/test/test-manager/src/tests/tunnel_state.rs b/test/test-manager/src/tests/tunnel_state.rs
index 7abc505939..407328e029 100644
--- a/test/test-manager/src/tests/tunnel_state.rs
+++ b/test/test-manager/src/tests/tunnel_state.rs
@@ -1,24 +1,138 @@
-use super::helpers::{
- self, connect_and_wait, send_guest_probes, set_relay_settings, unreachable_wireguard_tunnel,
- wait_for_tunnel_state,
+use super::{
+ helpers::{
+ self, connect_and_wait, send_guest_probes, set_relay_settings,
+ unreachable_wireguard_tunnel, wait_for_tunnel_state,
+ },
+ ui, Error, TestContext,
+};
+use crate::{
+ assert_tunnel_state, tests::helpers::ping_sized_with_timeout,
+ vm::network::DUMMY_LAN_INTERFACE_IP,
};
-use super::{ui, Error, TestContext};
-use crate::assert_tunnel_state;
-use crate::vm::network::DUMMY_LAN_INTERFACE_IP;
use mullvad_management_interface::MullvadProxyClient;
-use mullvad_types::relay_constraints::GeographicLocationConstraint;
-use mullvad_types::relay_list::{Relay, RelayEndpointData};
-use mullvad_types::CustomTunnelEndpoint;
use mullvad_types::{
- relay_constraints::{Constraint, LocationConstraint, RelayConstraints, RelaySettings},
+ relay_constraints::{
+ Constraint, GeographicLocationConstraint, LocationConstraint, RelayConstraints,
+ RelaySettings,
+ },
+ relay_list::{Relay, RelayEndpointData},
states::TunnelState,
+ CustomTunnelEndpoint,
+};
+use std::{
+ net::{IpAddr, SocketAddr},
+ time::Duration,
};
-use std::net::{IpAddr, SocketAddr};
use talpid_types::net::{Endpoint, TransportProtocol, TunnelEndpoint, TunnelType};
use test_macro::test_function;
use test_rpc::ServiceClient;
+#[cfg(target_os = "linux")]
+async fn setup_nftables_drop_pings_rule(
+ max_packet_size: u16,
+) -> scopeguard::ScopeGuard<(), impl FnOnce(())> {
+ fn log_ruleset() {
+ let output = std::process::Command::new("nft")
+ .args(["list", "ruleset"])
+ .output()
+ .unwrap();
+
+ log::debug!(
+ "Set NF-tables ruleset to:\n{}",
+ String::from_utf8(output.stdout).unwrap()
+ );
+
+ let exit_status = output.status;
+ assert_eq!(exit_status.code(), Some(0));
+ }
+ // Set nftables ruleset
+ crate::vm::network::linux::run_nft(
+ &(format!(
+ "table inet DropPings {{
+ chain postrouting {{
+ type filter hook postrouting priority 0; policy accept;
+ ip length > {max_packet_size} drop;
+ }}
+ }}"
+ )),
+ )
+ .await
+ .unwrap();
+ log_ruleset();
+
+ scopeguard::guard((), |()| {
+ let mut cmd = std::process::Command::new("nft");
+ cmd.args(["delete", "table", "inet", "DropPings"]);
+ let output = cmd.output().unwrap();
+ if !output.status.success() {
+ panic!("{}", std::str::from_utf8(&output.stderr).unwrap());
+ }
+ log_ruleset();
+ })
+}
+
+#[test_function(target_os = "windows")]
+pub async fn test_mtu_detection_windows(
+ _: TestContext,
+ rpc: ServiceClient,
+ mullvad_client: MullvadProxyClient,
+) -> Result<(), Error> {
+ test_mtu_detection(rpc, mullvad_client).await
+}
+
+#[test_function(target_os = "linux")]
+pub async fn test_mtu_detection_linux(
+ _: TestContext,
+ rpc: ServiceClient,
+ mullvad_client: MullvadProxyClient,
+) -> Result<(), Error> {
+ test_mtu_detection(rpc, mullvad_client).await
+}
+
+async fn test_mtu_detection(
+ rpc: ServiceClient,
+ mut mullvad_client: MullvadProxyClient,
+) -> Result<(), Error> {
+ const MAX_PACKET_SIZE: u16 = 800;
+ const MARGIN: u16 = 200;
+ let large_ping_size: usize = (MAX_PACKET_SIZE + MARGIN).into();
+
+ log::info!("Verify tunnel state: disconnected");
+ assert_tunnel_state!(&mut mullvad_client, TunnelState::Disconnected { .. });
+
+ // mullvad.net address
+ let inet_destination = "45.83.223.209".parse().unwrap();
+
+ log::info!("Setting up nftables firewall rules");
+ #[cfg(target_os = "linux")]
+ let _nft_guard = setup_nftables_drop_pings_rule(MAX_PACKET_SIZE).await;
+
+ // Test that the firewall rule works
+ log::info!("Sending large ping outside tunnel");
+ ping_sized_with_timeout(&rpc, inet_destination, None, large_ping_size)
+ .await
+ .expect_err("Ping larger than the filter should time out");
+
+ connect_and_wait(&mut mullvad_client).await.unwrap();
+ let tunnel_iface = helpers::get_tunnel_interface(&mut mullvad_client)
+ .await
+ .expect("failed to find tunnel interface");
+
+ log::info!("Waiting for MTU detection");
+ for _ in 0..10 {
+ let mtu = rpc.get_interface_mtu(tunnel_iface.clone()).await?;
+ if mtu < MAX_PACKET_SIZE {
+ println!(
+ "Tunnel MTU after dropping packets larger than {MAX_PACKET_SIZE} bytes: {mtu}"
+ );
+ return Ok(());
+ }
+ tokio::time::sleep(Duration::from_secs(1)).await;
+ }
+ panic!("MTU detection test failed")
+}
+
/// Verify that outgoing TCP, UDP, and ICMP packets can be observed
/// in the disconnected state. The purpose is mostly to rule prevent
/// false negatives in other tests.
@@ -51,7 +165,6 @@ pub async fn test_disconnected_state(
"did not see (all) outgoing packets to destination: {detected_probes:?}",
);
- //
// Test UI view
//
@@ -118,7 +231,6 @@ pub async fn test_connecting_state(
new_state
);
- //
// Leak test
//
@@ -197,7 +309,6 @@ pub async fn test_error_state(
let _ = connect_and_wait(&mut mullvad_client).await;
assert_tunnel_state!(&mut mullvad_client, TunnelState::Error { .. });
- //
// Leak test
//
@@ -235,12 +346,11 @@ pub async fn test_error_state(
}
/// Connect to a single relay and verify that:
-/// * Traffic can be sent and received in the tunnel.
-/// This is done by pinging a single public IP address
-/// and failing if there is no response.
+/// * Traffic can be sent and received in the tunnel. This is done by pinging a single public IP
+/// address and failing if there is no response.
/// * The correct relay is used.
-/// * Leaks outside the tunnel are blocked. Refer to the
-/// `test_connecting_state` documentation for details.
+/// * Leaks outside the tunnel are blocked. Refer to the `test_connecting_state` documentation for
+/// details.
#[test_function]
pub async fn test_connected_state(
_: TestContext,
@@ -249,7 +359,6 @@ pub async fn test_connected_state(
) -> Result<(), Error> {
let inet_destination = "1.1.1.1:1337".parse().unwrap();
- //
// Set relay to use
//
@@ -273,13 +382,11 @@ pub async fn test_connected_state(
.await
.expect("failed to update relay settings");
- //
// Connect
//
connect_and_wait(&mut mullvad_client).await?;
- //
// Verify that endpoint was selected
//
@@ -307,7 +414,6 @@ pub async fn test_connected_state(
actual => panic!("unexpected tunnel state: {:?}", actual),
}
- //
// Ping outside of tunnel while connected
//
diff --git a/test/test-manager/src/vm/network/linux.rs b/test/test-manager/src/vm/network/linux.rs
index d375fc2eb5..f54d218b2f 100644
--- a/test/test-manager/src/vm/network/linux.rs
+++ b/test/test-manager/src/vm/network/linux.rs
@@ -334,7 +334,7 @@ where
Ok(())
}
-async fn run_nft(input: &str) -> Result<()> {
+pub async fn run_nft(input: &str) -> Result<()> {
let mut cmd = Command::new("nft");
cmd.args(["-f", "-"]);
diff --git a/test/test-rpc/src/client.rs b/test/test-rpc/src/client.rs
index 6b3c2c4a0c..b4fb67f5c0 100644
--- a/test/test-rpc/src/client.rs
+++ b/test/test-rpc/src/client.rs
@@ -109,8 +109,8 @@ impl ServiceClient {
.map_err(Error::Tarpc)
}
- /// Wait for the Mullvad service to enter a specified state. The state is inferred from the presence
- /// of a named pipe or UDS, not the actual system service state.
+ /// Wait for the Mullvad service to enter a specified state. The state is inferred from the
+ /// presence of a named pipe or UDS, not the actual system service state.
pub async fn mullvad_daemon_wait_for_state(
&self,
accept_state_fn: impl Fn(ServiceStatus) -> bool,
@@ -178,11 +178,12 @@ impl ServiceClient {
/// Send ICMP
pub async fn send_ping(
&self,
- interface: Option<String>,
destination: IpAddr,
+ interface: Option<String>,
+ size: usize,
) -> Result<(), Error> {
self.client
- .send_ping(tarpc::context::current(), interface, destination)
+ .send_ping(tarpc::context::current(), destination, interface, size)
.await?
}
@@ -200,6 +201,13 @@ impl ServiceClient {
.await?
}
+ /// Returns the MTU of the given interface.
+ pub async fn get_interface_mtu(&self, interface: String) -> Result<u16, Error> {
+ self.client
+ .get_interface_mtu(tarpc::context::current(), interface)
+ .await?
+ }
+
/// Returns the name of the default non-tunnel interface
pub async fn get_default_interface(&self) -> Result<String, Error> {
self.client
@@ -213,8 +221,8 @@ impl ServiceClient {
.await?
}
- /// Start forwarding TCP from a server listening on `bind_addr` to the given address, and return a handle that closes the
- /// server when dropped
+ /// Start forwarding TCP from a server listening on `bind_addr` to the given address, and return
+ /// a handle that closes the server when dropped
pub async fn start_tcp_forward(
&self,
bind_addr: SocketAddr,
diff --git a/test/test-rpc/src/lib.rs b/test/test-rpc/src/lib.rs
index 166a00d9a7..d151520601 100644
--- a/test/test-rpc/src/lib.rs
+++ b/test/test-rpc/src/lib.rs
@@ -19,9 +19,11 @@ pub enum Error {
Tarpc(#[from] tarpc::client::RpcError),
#[error("Syscall failed")]
Syscall,
+ #[error("Internal IO error occurred: {0}")]
+ Io(String),
#[error("Interface not found")]
InterfaceNotFound,
- #[error("HTTP request failed")]
+ #[error("HTTP request failed: {0}")]
HttpRequest(String),
#[error("Failed to deserialize HTTP body")]
DeserializeBody,
@@ -37,17 +39,17 @@ pub enum Error {
SendUdp,
#[error("Failed to send TCP segment")]
SendTcp,
- #[error("Failed to send ping")]
- Ping,
- #[error("Failed to get or set registry value")]
+ #[error("Failed to send ping: {0}")]
+ Ping(String),
+ #[error("Failed to get or set registry value: {0}")]
Registry(String),
- #[error("Failed to change the service")]
+ #[error("Failed to change the service: {0}")]
Service(String),
- #[error("Could not read from or write to the file system")]
+ #[error("Could not read from or write to the file system: {0}")]
FileSystem(String),
- #[error("Could not serialize or deserialize file")]
+ #[error("Could not serialize or deserialize file: {0}")]
FileSerialization(String),
- #[error("User must be logged in but is not")]
+ #[error("User must be logged in but is not: {0}")]
UserNotLoggedIn(String),
#[error("Invalid URL")]
InvalidUrl,
@@ -136,7 +138,11 @@ mod service {
) -> Result<(), Error>;
/// Send ICMP
- async fn send_ping(interface: Option<String>, destination: IpAddr) -> Result<(), Error>;
+ async fn send_ping(
+ destination: IpAddr,
+ interface: Option<String>,
+ size: usize,
+ ) -> Result<(), Error>;
/// Fetch the current location.
async fn geoip_lookup(mullvad_host: String) -> Result<AmIMullvad, Error>;
@@ -144,6 +150,9 @@ mod service {
/// Returns the IP of the given interface.
async fn get_interface_ip(interface: String) -> Result<IpAddr, Error>;
+ /// Returns the MTU of the given interface.
+ async fn get_interface_mtu(interface: String) -> Result<u16, Error>;
+
/// Returns the name of the default interface.
async fn get_default_interface() -> Result<String, Error>;
@@ -175,7 +184,8 @@ mod service {
verbosity_level: mullvad_daemon::Verbosity,
) -> Result<(), Error>;
- /// Set environment variables for the daemon service. This will restart the daemon system service.
+ /// Set environment variables for the daemon service. This will restart the daemon system
+ /// service.
async fn set_daemon_environment(env: HashMap<String, String>) -> Result<(), Error>;
/// Copy a file from `src` to `dest` on the test runner.
diff --git a/test/test-runner/Cargo.toml b/test/test-runner/Cargo.toml
index dbe57c938e..8e2ae8cbf6 100644
--- a/test/test-runner/Cargo.toml
+++ b/test/test-runner/Cargo.toml
@@ -23,6 +23,8 @@ bytes = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio-serde = { workspace = true }
+surge-ping = { workspace = true }
+rand = "0.8"
libc = "0.2"
chrono = { workspace = true, features = ["serde"] }
@@ -42,12 +44,12 @@ winreg = "0.50"
[target.'cfg(windows)'.dependencies.windows-sys]
version = "0.45.0"
features = [
- "Win32_Foundation",
- "Win32_Security",
- "Win32_System_Shutdown",
- "Win32_System_SystemServices",
- "Win32_System_Threading",
- "Win32_UI_WindowsAndMessaging",
+ "Win32_Foundation",
+ "Win32_Security",
+ "Win32_System_Shutdown",
+ "Win32_System_SystemServices",
+ "Win32_System_Threading",
+ "Win32_UI_WindowsAndMessaging",
]
[dependencies.tokio-util]
diff --git a/test/test-runner/src/main.rs b/test/test-runner/src/main.rs
index befeeb61e0..3511d78cec 100644
--- a/test/test-runner/src/main.rs
+++ b/test/test-runner/src/main.rs
@@ -6,8 +6,7 @@ use std::{
path::{Path, PathBuf},
};
-use tarpc::context;
-use tarpc::server::Channel;
+use tarpc::{context, server::Channel};
use test_rpc::{
mullvad_daemon::{ServiceStatus, SOCKET_PATH},
net::SockHandleId,
@@ -15,10 +14,10 @@ use test_rpc::{
transport::GrpcForwarder,
AppTrace, Service,
};
-use tokio::sync::broadcast::error::TryRecvError;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
process::Command,
+ sync::broadcast::error::TryRecvError,
};
use tokio_util::codec::{Decoder, LengthDelimitedCodec};
@@ -141,10 +140,13 @@ impl Service for TestServer {
async fn send_ping(
self,
_: context::Context,
- interface: Option<String>,
destination: IpAddr,
+ interface: Option<String>,
+ size: usize,
) -> Result<(), test_rpc::Error> {
- net::send_ping(interface.as_deref(), destination).await
+ net::send_ping(destination, interface.as_deref(), size)
+ .await
+ .map_err(|e| test_rpc::Error::Ping(e.to_string()))
}
async fn geoip_lookup(
@@ -194,6 +196,14 @@ impl Service for TestServer {
net::get_interface_ip(&interface)
}
+ async fn get_interface_mtu(
+ self,
+ _: context::Context,
+ interface: String,
+ ) -> Result<u16, test_rpc::Error> {
+ net::get_interface_mtu(&interface)
+ }
+
async fn get_default_interface(self, _: context::Context) -> Result<String, test_rpc::Error> {
Ok(net::get_default_interface().to_owned())
}
diff --git a/test/test-runner/src/net.rs b/test/test-runner/src/net.rs
index 1de728a744..22de4da9c9 100644
--- a/test/test-runner/src/net.rs
+++ b/test/test-runner/src/net.rs
@@ -4,9 +4,7 @@ use std::{ffi::CString, num::NonZeroU32};
use std::{
io::Write,
net::{IpAddr, SocketAddr},
- process::Output,
};
-use tokio::process::Command;
pub async fn send_tcp(
bind_interface: Option<String>,
@@ -139,66 +137,37 @@ pub async fn send_udp(
}
pub async fn send_ping(
- interface: Option<&str>,
destination: IpAddr,
+ interface: Option<&str>,
+ size: usize,
) -> Result<(), test_rpc::Error> {
- #[cfg(target_os = "windows")]
- let mut source_ip = None;
- #[cfg(target_os = "windows")]
- if let Some(interface) = interface {
- let family = match destination {
- IpAddr::V4(_) => talpid_windows::net::AddressFamily::Ipv4,
- IpAddr::V6(_) => talpid_windows::net::AddressFamily::Ipv6,
- };
- source_ip = get_interface_ip_for_family(interface, family)
- .map_err(|_error| test_rpc::Error::Syscall)?;
- if source_ip.is_none() {
- log::error!("Failed to obtain interface IP");
- return Err(test_rpc::Error::Ping);
- }
- }
-
- let mut cmd = Command::new("ping");
- cmd.arg(destination.to_string());
-
- #[cfg(target_os = "windows")]
- cmd.args(["-n", "1"]);
-
- #[cfg(not(target_os = "windows"))]
- cmd.args(["-c", "1"]);
-
- match interface {
- Some(interface) => {
- log::info!("Pinging {destination} on interface {interface}");
-
- #[cfg(target_os = "windows")]
- if let Some(source_ip) = source_ip {
- cmd.args(["-S", &source_ip.to_string()]);
- }
-
- #[cfg(target_os = "linux")]
- cmd.args(["-I", interface]);
-
- #[cfg(target_os = "macos")]
- cmd.args(["-b", interface]);
- }
- None => log::info!("Pinging {destination}"),
- }
+ use surge_ping::{Client, Config, PingIdentifier, PingSequence, ICMP};
- cmd.kill_on_drop(true);
+ const IPV4_HEADER_SIZE: usize = 20;
+ const ICMP_HEADER_SIZE: usize = 8;
+ let payload_size = size - IPV4_HEADER_SIZE - ICMP_HEADER_SIZE;
+ let payload: &[u8] = &vec![0; payload_size];
- cmd.spawn()
- .map_err(|error| {
- log::error!("Failed to spawn ping process: {error}");
- test_rpc::Error::Ping
- })?
- .wait_with_output()
+ let config = match destination {
+ IpAddr::V4(_) => Config::builder(),
+ IpAddr::V6(_) => Config::builder().kind(ICMP::V6),
+ };
+ let config = if let Some(interface) = interface {
+ let interface_ip = get_interface_ip(interface)?;
+ config.interface(interface).bind((interface_ip, 0).into())
+ } else {
+ config
+ };
+ let client = Client::new(&config.build()).map_err(|e| test_rpc::Error::Ping(e.to_string()))?;
+ let mut pinger = client
+ .pinger(destination, PingIdentifier(rand::random()))
+ .await;
+ pinger
+ .ping(PingSequence(0), payload)
.await
- .map_err(|error| {
- log::error!("Failed to wait on ping: {error}");
- test_rpc::Error::Ping
- })
- .and_then(|output| result_from_output("ping", output, test_rpc::Error::Ping))
+ .map_err(|e| test_rpc::Error::Ping(e.to_string()))?;
+
+ Ok(())
}
#[cfg(unix)]
@@ -273,19 +242,58 @@ pub fn get_default_interface() -> &'static str {
"en0"
}
-fn result_from_output<E>(action: &'static str, output: Output, err: E) -> Result<(), E> {
- if output.status.success() {
- return Ok(());
+#[cfg(target_os = "macos")]
+pub fn get_interface_mtu(_interface_name: &str) -> Result<u16, test_rpc::Error> {
+ todo!("Implement setting MTU on macOS")
+}
+
+#[cfg(target_os = "linux")]
+pub fn get_interface_mtu(interface_name: &str) -> Result<u16, test_rpc::Error> {
+ use std::os::fd::AsRawFd;
+
+ let sock = socket2::Socket::new(
+ socket2::Domain::IPV4,
+ socket2::Type::STREAM,
+ Some(socket2::Protocol::TCP),
+ )
+ .map_err(|e| test_rpc::Error::Io(e.to_string()))?;
+
+ let mut ifr: libc::ifreq = unsafe { std::mem::zeroed() };
+ if interface_name.len() >= ifr.ifr_name.len() {
+ panic!("Interface '{interface_name}' name too long")
}
- let stdout_str = std::str::from_utf8(&output.stdout).unwrap_or("non-utf8 string");
- let stderr_str = std::str::from_utf8(&output.stderr).unwrap_or("non-utf8 string");
+ // SAFETY: interface_name is shorter than ifr.ifr_name
+ unsafe {
+ std::ptr::copy_nonoverlapping(
+ interface_name.as_ptr() as *const libc::c_char,
+ &mut ifr.ifr_name as *mut _,
+ interface_name.len(),
+ )
+ };
+
+ // TODO: define SIOCGIFMTU for macos
+ // SAFETY: SIOCGIFMTU expects an ifreq, and the socket is valid
+ if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFMTU, &mut ifr) } < 0 {
+ let e = std::io::Error::last_os_error();
- log::error!(
- "{action} failed:\n\ncode: {:?}\n\nstdout:\n\n{}\n\nstderr:\n\n{}",
- output.status.code(),
- stdout_str,
- stderr_str
- );
- Err(err)
+ log::error!("{}", e);
+ return Err(test_rpc::Error::Io(e.to_string()));
+ }
+
+ // SAFETY: ifru_mtu is set since SIOGCIFMTU succeeded
+ Ok(unsafe { ifr.ifr_ifru.ifru_mtu }
+ .try_into()
+ .expect("MTU should fit in u16"))
+}
+
+#[cfg(target_os = "windows")]
+pub fn get_interface_mtu(interface: &str) -> Result<u16, test_rpc::Error> {
+ let luid = talpid_windows::net::luid_from_alias(interface).map_err(|error| {
+ log::error!("Failed to obtain interface LUID: {error}");
+ test_rpc::Error::Syscall
+ })?;
+ talpid_windows::net::get_ip_interface_entry(talpid_windows::net::AddressFamily::Ipv4, &luid)
+ .map_err(|_error| test_rpc::Error::InterfaceNotFound)
+ .map(|row| row.NlMtu.try_into().unwrap())
}