summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim.hulthe@mullvad.net>2025-09-15 11:20:53 +0200
committerJoakim Hulthe <joakim.hulthe@mullvad.net>2025-09-15 11:20:53 +0200
commit23fccfb8f7fc901405bbc141d9ea0472a1f08ce2 (patch)
treefedf61e730dd31a3b768a5a8af47960541c80ba6
parent12bb6552e49e19510b3272f8d3d1f0c6277236ff (diff)
parent2aedc93c417ee8af682a8f5bae09ee42552fbfcb (diff)
downloadmullvadvpn-23fccfb8f7fc901405bbc141d9ea0472a1f08ce2.tar.xz
mullvadvpn-23fccfb8f7fc901405bbc141d9ea0472a1f08ce2.zip
Merge branch 'add-test-that-ipv6-in-the-tunnel-works-as-expected-on-des-1088'
-rw-r--r--Cargo.lock8
-rw-r--r--Cargo.toml2
-rw-r--r--talpid-core/Cargo.toml2
-rw-r--r--talpid-tunnel/src/tun_provider/android/ipnetwork_sub.rs4
-rw-r--r--talpid-types/Cargo.toml2
-rw-r--r--test/Cargo.lock83
-rw-r--r--test/connection-checker/Cargo.toml3
-rw-r--r--test/connection-checker/src/main.rs54
-rw-r--r--test/connection-checker/src/net.rs58
-rw-r--r--test/test-manager/Cargo.toml5
-rw-r--r--test/test-manager/src/container.rs10
-rw-r--r--test/test-manager/src/network_monitor.rs37
-rw-r--r--test/test-manager/src/tests/helpers.rs87
-rw-r--r--test/test-manager/src/tests/relay_ip_overrides.rs8
-rw-r--r--test/test-manager/src/tests/tunnel.rs121
-rw-r--r--test/test-manager/src/vm/network/linux.rs88
-rw-r--r--test/test-runner/src/net.rs2
17 files changed, 366 insertions, 208 deletions
diff --git a/Cargo.lock b/Cargo.lock
index fed1ebf2a6..6e651f17c6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2494,9 +2494,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "ipnetwork"
-version = "0.20.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
+checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
dependencies = [
"serde",
]
@@ -4124,9 +4124,9 @@ dependencies = [
[[package]]
name = "pfctl"
-version = "0.6.1"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44e65c0d3523afa79a600a3964c3ac0fabdabe2d7c68da624b2bb0b441b9d61"
+checksum = "944d2c073758b6bda57f517cff54cf69d74eae3593fe1e9aa9918666543456a9"
dependencies = [
"derive_builder",
"ioctl-sys 0.8.0",
diff --git a/Cargo.toml b/Cargo.toml
index 5ade871d11..e33522942f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -100,7 +100,7 @@ strum = { version = "0.27" }
# Networking
pnet_packet = "0.35.0"
-ipnetwork = "0.20"
+ipnetwork = "0.21.1"
tun = { version = "0.5.5", features = ["async"] }
socket2 = "0.5.7"
reqwest = { version = "0.12.23", default-features = false, features = ["rustls-tls"] }
diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml
index f7a22ee346..d7303d9d7c 100644
--- a/talpid-core/Cargo.toml
+++ b/talpid-core/Cargo.toml
@@ -51,7 +51,7 @@ duct = "0.13"
[target.'cfg(target_os = "macos")'.dependencies]
async-trait = "0.1"
-pfctl = "0.6.1"
+pfctl = "0.7.0"
system-configuration = "0.5.1"
hickory-proto = { workspace = true }
hickory-server = { workspace = true, features = ["resolver"] }
diff --git a/talpid-tunnel/src/tun_provider/android/ipnetwork_sub.rs b/talpid-tunnel/src/tun_provider/android/ipnetwork_sub.rs
index 44f9160ef1..272a28a911 100644
--- a/talpid-tunnel/src/tun_provider/android/ipnetwork_sub.rs
+++ b/talpid-tunnel/src/tun_provider/android/ipnetwork_sub.rs
@@ -40,11 +40,11 @@ impl AbstractIpNetwork for Ipv4Network {
}
fn mask(self) -> Self::Representation {
- Ipv4Network::mask(self).into()
+ Ipv4Network::mask(&self).into()
}
fn network(self) -> Self::Representation {
- Ipv4Network::network(self).into()
+ Ipv4Network::network(&self).into()
}
fn prefix(self) -> u8 {
diff --git a/talpid-types/Cargo.toml b/talpid-types/Cargo.toml
index 3933e9ecf0..188ea92237 100644
--- a/talpid-types/Cargo.toml
+++ b/talpid-types/Cargo.toml
@@ -12,7 +12,7 @@ workspace = true
[dependencies]
serde = { workspace = true, features = ["derive"] }
-ipnetwork = { workspace = true }
+ipnetwork = { workspace = true, features = ["serde"] }
base64 = "0.22.0"
x25519-dalek = { version = "2.0.1", features = ["static_secrets", "zeroize", "getrandom"] }
thiserror = { workspace = true }
diff --git a/test/Cargo.lock b/test/Cargo.lock
index fe46d24b8c..263e29b763 100644
--- a/test/Cargo.lock
+++ b/test/Cargo.lock
@@ -494,33 +494,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
-name = "color-eyre"
-version = "0.6.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d"
-dependencies = [
- "backtrace",
- "color-spantrace",
- "eyre",
- "indenter",
- "once_cell",
- "owo-colors",
- "tracing-error",
-]
-
-[[package]]
-name = "color-spantrace"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427"
-dependencies = [
- "once_cell",
- "owo-colors",
- "tracing-core",
- "tracing-error",
-]
-
-[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -550,9 +523,8 @@ dependencies = [
name = "connection-checker"
version = "0.0.0"
dependencies = [
+ "anyhow",
"clap",
- "color-eyre",
- "eyre",
"ping",
"reqwest",
"serde",
@@ -749,6 +721,12 @@ dependencies = [
]
[[package]]
+name = "duplicate"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97af9b5f014e228b33e77d75ee0e6e87960124f0f4b16337b586a6bec91867b1"
+
+[[package]]
name = "ecdsa"
version = "0.16.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -906,16 +884,6 @@ dependencies = [
]
[[package]]
-name = "eyre"
-version = "0.6.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
-dependencies = [
- "indenter",
- "once_cell",
-]
-
-[[package]]
name = "fast-socks5"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1625,12 +1593,6 @@ dependencies = [
]
[[package]]
-name = "indenter"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
-
-[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1730,9 +1692,9 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "ipnetwork"
-version = "0.20.0"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e"
+checksum = "cf370abdafd54d13e54a620e8c3e1145f28e46cc9d704bc6d94414559df41763"
dependencies = [
"serde",
]
@@ -2379,6 +2341,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
+name = "openssl-src"
+version = "300.5.2+3.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
name = "openssl-sys"
version = "0.9.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2386,6 +2357,7 @@ checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
dependencies = [
"cc",
"libc",
+ "openssl-src",
"pkg-config",
"vcpkg",
]
@@ -2416,12 +2388,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
-name = "owo-colors"
-version = "4.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e"
-
-[[package]]
name = "p256"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3759,6 +3725,7 @@ dependencies = [
"colored",
"data-encoding-macro",
"dirs",
+ "duplicate",
"env_logger",
"futures",
"glob",
@@ -4284,16 +4251,6 @@ dependencies = [
]
[[package]]
-name = "tracing-error"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
-dependencies = [
- "tracing",
- "tracing-subscriber",
-]
-
-[[package]]
name = "tracing-opentelemetry"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/test/connection-checker/Cargo.toml b/test/connection-checker/Cargo.toml
index a0b5e7fbae..16ecdc3059 100644
--- a/test/connection-checker/Cargo.toml
+++ b/test/connection-checker/Cargo.toml
@@ -11,9 +11,8 @@ rust-version.workspace = true
workspace = true
[dependencies]
+anyhow = "1.0.99"
clap = { workspace = true, features = ["derive"] }
-color-eyre = "0.6.2"
-eyre = "0.6.12"
ping = "0.5.2"
reqwest = { version = "0.12.23", default-features = false, features = ["blocking", "rustls-tls", "json"] }
serde = { workspace = true, features = ["derive"] }
diff --git a/test/connection-checker/src/main.rs b/test/connection-checker/src/main.rs
index 3c8a3c1c0c..19e2eddbec 100644
--- a/test/connection-checker/src/main.rs
+++ b/test/connection-checker/src/main.rs
@@ -1,5 +1,5 @@
+use anyhow::{Context, anyhow};
use clap::Parser;
-use eyre::{Context, eyre};
use reqwest::blocking::Client;
use serde::Deserialize;
use std::{io::stdin, time::Duration};
@@ -9,24 +9,23 @@ use connection_checker::{
net::{send_ping, send_tcp, send_udp},
};
-fn main() -> eyre::Result<()> {
+fn main() {
let opt = Opt::parse();
- color_eyre::install()?;
if opt.interactive {
let stdin = stdin();
for line in stdin.lines() {
- let _ = line.wrap_err("Failed to read from stdin")?;
- test_connection(&opt)?;
+ if line.is_err() {
+ break;
+ };
+ test_connection(&opt);
}
} else {
- test_connection(&opt)?;
+ test_connection(&opt);
}
-
- Ok(())
}
-fn test_connection(opt: &Opt) -> eyre::Result<bool> {
+fn test_connection(opt: &Opt) {
if let Some(destination) = opt.leak {
if opt.leak_tcp {
let _ = send_tcp(opt, destination);
@@ -38,11 +37,11 @@ fn test_connection(opt: &Opt) -> eyre::Result<bool> {
let _ = send_ping(opt, destination.ip());
}
}
- am_i_mullvad(opt)
+ am_i_mullvad(opt);
}
/// Check if connected to Mullvad and print the result to stdout
-fn am_i_mullvad(opt: &Opt) -> eyre::Result<bool> {
+fn am_i_mullvad(opt: &Opt) {
#[derive(Debug, Deserialize)]
struct Response {
ip: String,
@@ -52,24 +51,29 @@ fn am_i_mullvad(opt: &Opt) -> eyre::Result<bool> {
let url = &opt.url;
let client = Client::new();
- let response: Response = client
+ let result: Result<Response, _> = client
.get(url)
.timeout(Duration::from_secs(opt.timeout))
.send()
.and_then(|r| r.json())
- .wrap_err_with(|| eyre!("Failed to GET {url}"))?;
+ .with_context(|| anyhow!("Failed to GET {url}"));
- if let Some(server) = &response.mullvad_exit_ip_hostname {
- println!(
- "You are connected to Mullvad (server {}). Your IP address is {}",
- server, response.ip
- );
- Ok(true)
- } else {
- println!(
- "You are not connected to Mullvad. Your IP address is {}",
- response.ip
- );
- Ok(false)
+ match result {
+ Ok(response) => {
+ if let Some(server) = &response.mullvad_exit_ip_hostname {
+ println!(
+ "You are connected to Mullvad (server {}). Your IP address is {}",
+ server, response.ip
+ );
+ } else {
+ println!(
+ "You are not connected to Mullvad. Your IP address is {}",
+ response.ip
+ );
+ }
+ }
+ Err(e) => {
+ println!("Error: {e}");
+ }
}
}
diff --git a/test/connection-checker/src/net.rs b/test/connection-checker/src/net.rs
index 8b8136846f..e087ad50b5 100644
--- a/test/connection-checker/src/net.rs
+++ b/test/connection-checker/src/net.rs
@@ -1,67 +1,67 @@
-use eyre::{Context, eyre};
+use anyhow::{Context, anyhow};
use std::{
io::Write,
- net::{IpAddr, Ipv4Addr, SocketAddr},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
time::Duration,
};
use crate::cli::Opt;
-pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> eyre::Result<()> {
- let bind_addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0);
+pub fn send_tcp(opt: &Opt, destination: SocketAddr) -> anyhow::Result<()> {
+ eprintln!("Leaking TCP packets to {destination}");
- let family = match &destination {
- SocketAddr::V4(_) => socket2::Domain::IPV4,
- SocketAddr::V6(_) => socket2::Domain::IPV6,
+ let (family, bind_address) = match &destination {
+ SocketAddr::V4(_) => (socket2::Domain::IPV4, IpAddr::from(Ipv4Addr::UNSPECIFIED)),
+ SocketAddr::V6(_) => (socket2::Domain::IPV6, IpAddr::from(Ipv6Addr::UNSPECIFIED)),
};
- let sock = socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP))
- .wrap_err(eyre!("Failed to create TCP socket"))?;
+ let bind_address: SocketAddr = SocketAddr::new(bind_address, 0);
- eprintln!("Leaking TCP packets to {destination}");
+ let sock = socket2::Socket::new(family, socket2::Type::STREAM, Some(socket2::Protocol::TCP))
+ .context(anyhow!("Failed to create TCP socket"))?;
- sock.bind(&socket2::SockAddr::from(bind_addr))
- .wrap_err(eyre!("Failed to bind TCP socket to {bind_addr}"))?;
+ sock.bind(&socket2::SockAddr::from(bind_address))
+ .context(anyhow!("Failed to bind TCP socket to {bind_address}"))?;
let timeout = Duration::from_secs(opt.leak_timeout);
sock.set_write_timeout(Some(timeout))?;
sock.set_read_timeout(Some(timeout))?;
sock.connect_timeout(&socket2::SockAddr::from(destination), timeout)
- .wrap_err(eyre!("Failed to connect to {destination}"))?;
+ .context(anyhow!("Failed to connect to {destination}"))?;
let mut stream = std::net::TcpStream::from(sock);
stream
.write_all(opt.payload.as_bytes())
- .wrap_err(eyre!("Failed to send message to {destination}"))?;
+ .context(anyhow!("Failed to send message to {destination}"))?;
Ok(())
}
-pub fn send_udp(opt: &Opt, destination: SocketAddr) -> Result<(), eyre::Error> {
- let bind_addr: SocketAddr = SocketAddr::new(Ipv4Addr::new(0, 0, 0, 0).into(), 0);
-
+pub fn send_udp(opt: &Opt, destination: SocketAddr) -> Result<(), anyhow::Error> {
eprintln!("Leaking UDP packets to {destination}");
- let family = match &destination {
- SocketAddr::V4(_) => socket2::Domain::IPV4,
- SocketAddr::V6(_) => socket2::Domain::IPV6,
+ let (family, bind_address) = match &destination {
+ SocketAddr::V4(_) => (socket2::Domain::IPV4, IpAddr::from(Ipv4Addr::UNSPECIFIED)),
+ SocketAddr::V6(_) => (socket2::Domain::IPV6, IpAddr::from(Ipv6Addr::UNSPECIFIED)),
};
+ let bind_address: SocketAddr = SocketAddr::new(bind_address, 0);
+
let sock = socket2::Socket::new(family, socket2::Type::DGRAM, Some(socket2::Protocol::UDP))
- .wrap_err("Failed to create UDP socket")?;
+ .context("Failed to create UDP socket")?;
- sock.bind(&socket2::SockAddr::from(bind_addr))
- .wrap_err(eyre!("Failed to bind UDP socket to {bind_addr}"))?;
+ sock.bind(&socket2::SockAddr::from(bind_address))
+ .context(anyhow!("Failed to bind UDP socket to {bind_address}"))?;
let std_socket = std::net::UdpSocket::from(sock);
std_socket
.send_to(opt.payload.as_bytes(), destination)
- .wrap_err(eyre!("Failed to send message to {destination}"))?;
+ .context(anyhow!("Failed to send message to {destination}"))?;
Ok(())
}
#[cfg(target_os = "windows")]
-pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
+pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> {
eprintln!("Leaking ICMP packets to {destination}");
ping::ping(
@@ -77,7 +77,7 @@ pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
}
#[cfg(target_os = "macos")]
-pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
+pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> {
eprintln!("Leaking ICMP packets to {destination}");
// On macOS, use dgramsock (SOCK_DGRAM) instead of the default sock type (SOCK_RAW),
@@ -98,7 +98,7 @@ pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
// 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<()> {
+pub fn send_ping(opt: &Opt, destination: IpAddr) -> anyhow::Result<()> {
eprintln!("Leaking ICMP packets to {destination}");
let mut cmd = std::process::Command::new("ping");
@@ -107,7 +107,7 @@ pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
cmd.args(["-c", "1", "-W", &timeout_sec, &destination.to_string()]);
- let output = cmd.output().wrap_err(eyre!(
+ let output = cmd.output().context(anyhow!(
"Failed to execute ping for destination {destination}"
))?;
@@ -121,7 +121,7 @@ pub fn send_ping(opt: &Opt, destination: IpAddr) -> eyre::Result<()> {
std::str::from_utf8(&output.stderr).unwrap_or("invalid utf8")
);
- return Err(eyre!("ping for destination {destination} failed"));
+ return Err(anyhow!("ping for destination {destination} failed"));
}
Ok(())
diff --git a/test/test-manager/Cargo.toml b/test/test-manager/Cargo.toml
index 2225f46ad9..bc7ab9515d 100644
--- a/test/test-manager/Cargo.toml
+++ b/test/test-manager/Cargo.toml
@@ -21,7 +21,7 @@ tokio-serial = { workspace = true }
thiserror = { workspace = true }
bytes = { workspace = true }
test_macro = { path = "./test_macro" }
-ipnetwork = "0.20"
+ipnetwork = "0.21.1"
inventory = "0.3"
data-encoding-macro = "0.1.12"
itertools = "0.10.5"
@@ -61,10 +61,11 @@ mullvad-types = { path = "../../mullvad-types" }
mullvad-version = { path = "../../mullvad-version" }
talpid-types = { path = "../../talpid-types" }
-ssh2 = "0.9.5"
+ssh2 = { version = "0.9.5", features = ["vendored-openssl"] }
nix = { workspace = true }
socket2 = { workspace = true }
+duplicate = { version = "2.0.0", default-features = false }
[target.'cfg(target_os = "macos")'.dependencies]
tun = "0.5.1"
diff --git a/test/test-manager/src/container.rs b/test/test-manager/src/container.rs
index 88ba7d9a6b..9560f48b3e 100644
--- a/test/test-manager/src/container.rs
+++ b/test/test-manager/src/container.rs
@@ -11,7 +11,15 @@ pub async fn relaunch_with_rootlesskit(vnc_port: Option<u16>) {
}
let mut cmd = Command::new("rootlesskit");
- cmd.args(["--net", "slirp4netns", "--copy-up=/etc"]);
+ cmd.args([
+ "--net",
+ "slirp4netns",
+ "--ipv6",
+ // A higher MTU breaks IPv6
+ "--mtu",
+ "1500",
+ "--copy-up=/etc",
+ ]);
if let Some(port) = vnc_port {
log::debug!("VNC port: {port} -> 5901/tcp");
diff --git a/test/test-manager/src/network_monitor.rs b/test/test-manager/src/network_monitor.rs
index e89c33fb34..64e1414b5c 100644
--- a/test/test-manager/src/network_monitor.rs
+++ b/test/test-manager/src/network_monitor.rs
@@ -284,29 +284,26 @@ async fn start_packet_monitor_for_interface(
break Ok(monitor_result);
}
maybe_next_packet = next_packet => {
- match maybe_next_packet {
- Some(Ok(packet))=> {
- if let Some(packet) = packet {
- if !filter_fn(&packet) {
- log::trace!("{interface} \"{packet:?}\" does not match closure conditions");
- monitor_result.discarded_packets =
- monitor_result.discarded_packets.saturating_add(1);
- } else {
- log::trace!("{interface} \"{packet:?}\" matches closure conditions");
+ let Some(Ok(packet)) = maybe_next_packet else {
+ log::error!("lost packet stream");
+ break Err(MonitorUnexpectedlyStopped);
+ };
- let should_continue = should_continue_fn(&packet);
+ let Some(packet) = packet else { continue };
- monitor_result.packets.push(packet);
+ if !filter_fn(&packet) {
+ log::trace!("{interface} \"{packet:?}\" does not match closure conditions");
+ monitor_result.discarded_packets =
+ monitor_result.discarded_packets.saturating_add(1);
+ } else {
+ log::trace!("{interface} \"{packet:?}\" matches closure conditions");
- if !should_continue {
- break Ok(monitor_result);
- }
- }
- }
- }
- _ => {
- log::error!("lost packet stream");
- break Err(MonitorUnexpectedlyStopped);
+ let should_continue = should_continue_fn(&packet);
+
+ monitor_result.packets.push(packet);
+
+ if !should_continue {
+ break Ok(monitor_result);
}
}
}
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()
diff --git a/test/test-manager/src/vm/network/linux.rs b/test/test-manager/src/vm/network/linux.rs
index ef7eebeb2a..b6df187dcc 100644
--- a/test/test-manager/src/vm/network/linux.rs
+++ b/test/test-manager/src/vm/network/linux.rs
@@ -1,24 +1,31 @@
-use ipnetwork::Ipv4Network;
+use ipnetwork::{Ipv4Network, Ipv6Network};
use std::{
ffi::OsStr,
io,
- net::{IpAddr, Ipv4Addr},
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
+ ops::RangeInclusive,
process::Stdio,
str::FromStr,
- sync::LazyLock,
};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::{Child, Command},
};
-/// (Contained) test subnet for the test runner: 172.29.1.1/24
-pub static TEST_SUBNET: LazyLock<Ipv4Network> =
- LazyLock::new(|| Ipv4Network::new(Ipv4Addr::new(172, 29, 1, 1), 24).unwrap());
-/// Range of IPs returned by the DNS server: TEST_SUBNET_DHCP_FIRST to TEST_SUBNET_DHCP_LAST
-pub const TEST_SUBNET_DHCP_FIRST: Ipv4Addr = Ipv4Addr::new(172, 29, 1, 2);
-/// Range of IPs returned by the DNS server: TEST_SUBNET_DHCP_FIRST to TEST_SUBNET_DHCP_LAST
-pub const TEST_SUBNET_DHCP_LAST: Ipv4Addr = Ipv4Addr::new(172, 29, 1, 128);
+/// (Contained) IPv4 subnet for the test runner: 172.29.1.1/24
+pub const TEST_SUBNET_IPV4: Ipv4Network =
+ Ipv4Network::new_checked(Ipv4Addr::new(172, 29, 1, 1), 24).unwrap();
+
+/// IPv4 range returned by the DHCP server.
+pub const TEST_SUBNET_IPV4_DHCP: RangeInclusive<Ipv4Addr> =
+ Ipv4Addr::new(172, 29, 1, 2)..=Ipv4Addr::new(172, 29, 1, 128);
+
+/// IPv6 subnet for the test runner. "0xfd multest"
+pub const TEST_SUBNET_IPV6: Ipv6Network = Ipv6Network::new_checked(
+ Ipv6Addr::new(0xfd6d, 0x756c, 0x7465, 0x7374, 0, 0, 0, 1),
+ 64,
+)
+.unwrap();
/// Bridge interface on the host
pub(crate) const BRIDGE_NAME: &str = "br-mullvadtest";
@@ -97,26 +104,44 @@ struct DhcpProcHandle {
_pid_file: async_tempfile::TempFile,
}
+/// IPv6-support in `rootlesskit` is experimental, and addresses are not automatically assigned.
+/// This function will assigned an IPv6 address to the TAP interface, and set up routes.
+async fn fix_ipv6() -> Result<()> {
+ let tap = "tap0"; // TAP-device that connects to slirp2netns
+ let addr = "fd00::1337/64"; // our address within the slirp2netns subnet
+ let gateway = "fd00::2"; // slirp2netns gateway
+ let _dns = "fd00::3"; // slirp2netns dns
+
+ run_ip_cmd(["-6", "addr", "add", addr, "dev", tap]).await?;
+ run_ip_cmd(["-6", "route", "add", "default", "via", gateway, "dev", tap]).await?;
+ Ok(())
+}
+
/// Create a bridge network and hosts
pub async fn setup_test_network() -> Result<NetworkHandle> {
+ fix_ipv6().await?;
+
enable_forwarding().await?;
- let test_subnet = TEST_SUBNET.to_string();
+ let test_subnet_v4 = TEST_SUBNET_IPV4.to_string();
+ let test_subnet_v6 = TEST_SUBNET_IPV6.to_string();
- log::debug!("Create bridge network: dev {BRIDGE_NAME}, net {test_subnet}");
+ log::debug!("Create bridge network: dev {BRIDGE_NAME}, net {test_subnet_v4}");
run_ip_cmd(["link", "add", BRIDGE_NAME, "type", "bridge"]).await?;
- run_ip_cmd(["addr", "add", "dev", BRIDGE_NAME, &test_subnet]).await?;
+ run_ip_cmd(["addr", "add", "dev", BRIDGE_NAME, &test_subnet_v4]).await?;
+ run_ip_cmd(["addr", "add", "dev", BRIDGE_NAME, &test_subnet_v6]).await?;
run_ip_cmd(["link", "set", "dev", BRIDGE_NAME, "up"]).await?;
log::debug!("Masquerade traffic from bridge to internet");
run_nft(&format!(
"
-table ip mullvad_test_nat {{
+table inet mullvad_test_nat {{
chain POSTROUTING {{
type nat hook postrouting priority srcnat; policy accept;
- ip saddr {test_subnet} ip daddr != {test_subnet} counter masquerade
+ ip saddr {test_subnet_v4} ip daddr != {test_subnet_v4} counter masquerade
+ ip6 saddr {test_subnet_v6} ip6 daddr != {test_subnet_v6} counter masquerade
}}
}}"
))
@@ -189,8 +214,11 @@ impl NetworkHandle {
}
}
+/// Run dnsmasq as a DHCP server.
+///
+/// dnsmasq will serve IPv4 addresses within the range [TEST_SUBNET_IPV4_DHCP] using regular DHCP.
+/// It will also advertise SLAAC for IPv6 within [TEST_SUBNET_IPV6].
async fn start_dnsmasq() -> Result<DhcpProcHandle> {
- // dnsmasq -i BRIDGE_NAME -F TEST_SUBNET_DHCP_FIRST,TEST_SUBNET_DHCP_LAST ...
let mut cmd = Command::new("dnsmasq");
cmd.kill_on_drop(true);
@@ -198,13 +226,21 @@ async fn start_dnsmasq() -> Result<DhcpProcHandle> {
cmd.stderr(Stdio::piped());
cmd.args([
+ "--conf-file=/dev/null",
"--bind-interfaces",
- "-C",
- "/dev/null",
- "-i",
- BRIDGE_NAME,
- "-F",
- &format!("{TEST_SUBNET_DHCP_FIRST},{TEST_SUBNET_DHCP_LAST}"),
+ &format!("--interface={BRIDGE_NAME}"),
+ // IPv4
+ &format!(
+ "--dhcp-range={},{}",
+ TEST_SUBNET_IPV4_DHCP.start(),
+ TEST_SUBNET_IPV4_DHCP.end(),
+ ),
+ // IPv6
+ &format!(
+ "--dhcp-range={prefix},slaac,{prefix_len}",
+ prefix = TEST_SUBNET_IPV6.ip(),
+ prefix_len = TEST_SUBNET_IPV6.prefix()
+ ),
"--no-hosts",
"--keep-in-foreground",
"--log-facility=-",
@@ -343,5 +379,13 @@ async fn enable_forwarding() -> Result<()> {
if !output.status.success() {
return Err(Error::SysctlFailed(output.status.code().unwrap()));
}
+
+ let mut cmd = Command::new("sysctl");
+ cmd.arg("net.ipv6.conf.all.forwarding=1");
+ let output = cmd.output().await.map_err(Error::SysctlStart)?;
+ if !output.status.success() {
+ return Err(Error::SysctlFailed(output.status.code().unwrap()));
+ }
+
Ok(())
}
diff --git a/test/test-runner/src/net.rs b/test/test-runner/src/net.rs
index 967d3c8c32..fe100f22c7 100644
--- a/test/test-runner/src/net.rs
+++ b/test/test-runner/src/net.rs
@@ -308,7 +308,7 @@ pub fn get_interface_mtu(interface_name: &str) -> Result<u16, test_rpc::Error> {
// 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 {
+ if unsafe { libc::ioctl(sock.as_raw_fd(), libc::SIOCGIFMTU as libc::Ioctl, &mut ifr) } < 0 {
let e = std::io::Error::last_os_error();
log::error!("{}", e);