diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-09-15 11:20:53 +0200 |
|---|---|---|
| committer | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2025-09-15 11:20:53 +0200 |
| commit | 23fccfb8f7fc901405bbc141d9ea0472a1f08ce2 (patch) | |
| tree | fedf61e730dd31a3b768a5a8af47960541c80ba6 /test/test-manager/src | |
| parent | 12bb6552e49e19510b3272f8d3d1f0c6277236ff (diff) | |
| parent | 2aedc93c417ee8af682a8f5bae09ee42552fbfcb (diff) | |
| download | mullvadvpn-23fccfb8f7fc901405bbc141d9ea0472a1f08ce2.tar.xz mullvadvpn-23fccfb8f7fc901405bbc141d9ea0472a1f08ce2.zip | |
Merge branch 'add-test-that-ipv6-in-the-tunnel-works-as-expected-on-des-1088'
Diffstat (limited to 'test/test-manager/src')
| -rw-r--r-- | test/test-manager/src/container.rs | 10 | ||||
| -rw-r--r-- | test/test-manager/src/network_monitor.rs | 37 | ||||
| -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 | ||||
| -rw-r--r-- | test/test-manager/src/vm/network/linux.rs | 88 |
6 files changed, 274 insertions, 77 deletions
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(()) } |
