From 88ef98179956317730fbf5396c9f7816622512d0 Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Wed, 8 Jan 2025 11:58:46 +0100 Subject: [PATCH 1/3] Fix bridge interface IP on macOS The IP of the bridge interface was incorrectly assumed to be constant, but has at some point changed. This broke the algorithm for finding the bridge interface. Replace the algorithm with one based on the guest IP, which is given by `tart`. As the bridge IP is no longer constant, we also expose it through the `TEST_CONFIG`. --- test/test-manager/src/main.rs | 16 +++++++- test/test-manager/src/tests/access_methods.rs | 6 ++- .../src/tests/audits/cve_2019_14899.rs | 6 +-- .../src/tests/audits/mllvd_cr_24_03.rs | 35 ++++++++-------- test/test-manager/src/tests/config.rs | 7 ++-- test/test-manager/src/tests/dns.rs | 20 ++++++--- .../src/tests/relay_ip_overrides.rs | 10 ++--- test/test-manager/src/tests/tunnel.rs | 8 ++-- test/test-manager/src/vm/network/linux.rs | 5 +-- test/test-manager/src/vm/network/macos.rs | 41 ++++++++++--------- test/test-manager/src/vm/network/mod.rs | 14 ++++--- 11 files changed, 96 insertions(+), 72 deletions(-) diff --git a/test/test-manager/src/main.rs b/test/test-manager/src/main.rs index 973a1aec06f2..95274e9a7a8e 100644 --- a/test/test-manager/src/main.rs +++ b/test/test-manager/src/main.rs @@ -9,6 +9,8 @@ mod summary; mod tests; mod vm; +#[cfg(target_os = "macos")] +use std::net::IpAddr; use std::{net::SocketAddr, path::PathBuf}; use anyhow::{Context, Result}; @@ -295,6 +297,15 @@ async fn main() -> Result<()> { .await .context("Failed to run provisioning for VM")?; + #[cfg(target_os = "macos")] + let IpAddr::V4(guest_ip) = instance.get_ip().to_owned() else { + panic!("Expected bridge IP to be version 4, but was version 6.") + }; + let (bridge_name, bridge_ip) = vm::network::bridge( + #[cfg(target_os = "macos")] + &guest_ip, + )?; + TEST_CONFIG.init(tests::config::TestConfig::new( account, artifacts_dir, @@ -311,7 +322,8 @@ async fn main() -> Result<()> { .gui_package_path .map(|path| path.file_name().unwrap().to_string_lossy().into_owned()), mullvad_host, - vm::network::bridge()?, + bridge_name, + bridge_ip, test_rpc::meta::Os::from(vm_config.os_type), openvpn_certificate, )); @@ -319,7 +331,7 @@ async fn main() -> Result<()> { // For convenience, spawn a SOCKS5 server that is reachable for tests that need it let socks = socks_server::spawn(SocketAddr::new( - crate::vm::network::NON_TUN_GATEWAY.into(), + TEST_CONFIG.host_bridge_ip.into(), crate::vm::network::SOCKS5_PORT, )) .await?; diff --git a/test/test-manager/src/tests/access_methods.rs b/test/test-manager/src/tests/access_methods.rs index 747ebd23e590..bead7767f27f 100644 --- a/test/test-manager/src/tests/access_methods.rs +++ b/test/test-manager/src/tests/access_methods.rs @@ -14,6 +14,8 @@ use talpid_types::net::proxy::CustomProxy; use test_macro::test_function; use test_rpc::ServiceClient; +use crate::tests::config::TEST_CONFIG; + use super::TestContext; /// Assert that API traffic can be proxied via a custom Shadowsocks proxy. @@ -48,7 +50,7 @@ async fn test_access_method_socks5_remote( _rpc: ServiceClient, mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { - use crate::vm::network::{NON_TUN_GATEWAY, SOCKS5_PORT}; + use crate::vm::network::SOCKS5_PORT; use std::net::SocketAddr; use talpid_types::net::proxy::Socks5Remote; log::info!("Testing SOCKS5 (Remote) access method"); @@ -57,7 +59,7 @@ async fn test_access_method_socks5_remote( // The remote SOCKS5 proxy is assumed to be running on the test manager. On // which port it listens to is defined as a constant in the `test-manager` // crate. - let endpoint = SocketAddr::from((NON_TUN_GATEWAY, SOCKS5_PORT)); + let endpoint = SocketAddr::from((TEST_CONFIG.host_bridge_ip, SOCKS5_PORT)); let access_method = Socks5Remote::new(endpoint); log::info!("Testing SOCKS5-proxy: {access_method:?}"); assert_access_method_works(mullvad_client, access_method.clone()) diff --git a/test/test-manager/src/tests/audits/cve_2019_14899.rs b/test/test-manager/src/tests/audits/cve_2019_14899.rs index 71872c071686..d723b84dd11b 100644 --- a/test/test-manager/src/tests/audits/cve_2019_14899.rs +++ b/test/test-manager/src/tests/audits/cve_2019_14899.rs @@ -42,8 +42,8 @@ use test_rpc::ServiceClient; use tokio::{task::yield_now, time::sleep}; use crate::{ - tests::{helpers, TestContext}, - vm::network::{linux::TAP_NAME, NON_TUN_GATEWAY}, + tests::{config::TEST_CONFIG, helpers, TestContext}, + vm::network::linux::TAP_NAME, }; /// The port number we set in the malicious packet. @@ -67,7 +67,7 @@ pub async fn test_cve_2019_14899_mitigation( let victim_tunnel_interface = helpers::get_tunnel_interface(&mut mullvad_client) .await .context("Failed to find tunnel interface")?; - let victim_gateway_ip = NON_TUN_GATEWAY; + let victim_gateway_ip = TEST_CONFIG.host_bridge_ip; // Create a raw socket which let's us send custom ethernet packets log::info!("Creating raw socket"); diff --git a/test/test-manager/src/tests/audits/mllvd_cr_24_03.rs b/test/test-manager/src/tests/audits/mllvd_cr_24_03.rs index bf743bcc676d..d82945d809c9 100644 --- a/test/test-manager/src/tests/audits/mllvd_cr_24_03.rs +++ b/test/test-manager/src/tests/audits/mllvd_cr_24_03.rs @@ -1,9 +1,10 @@ #![cfg(target_os = "linux")] //! Test mitigation for mllvd_cr_24_03 //! -//! By sending an ARP request for the in-tunnel IP address to any network interface on the device running Mullvad, it -//! will respond and confirm that it owns this address. This means someone on the LAN or similar can figure out the -//! device's in-tunnel IP, and potentially also make an educated guess that they are using Mullvad at all. +//! By sending an ARP request for the in-tunnel IP address to any network interface on the device +//! running Mullvad, it will respond and confirm that it owns this address. This means someone on +//! the LAN or similar can figure out the device's in-tunnel IP, and potentially also make an +//! educated guess that they are using Mullvad at all. //! //! # Setup //! @@ -12,26 +13,23 @@ //! Network adjacent attacker: test-manager //! //! # Procedure -//! Have test-runner connect to relay. Let test-manager know about the test-runner's private in-tunnel IP (such that -//! we don't have to enumerate all possible private IPs). +//! Have test-runner connect to relay. Let test-manager know about the test-runner's private +//! in-tunnel IP (such that we don't have to enumerate all possible private IPs). //! -//! Have test-manager invoke the `arping` command targeting the bridge network between test-manager <-> test-runner. -//! If `arping` times out without a reply, it will exit with a non-0 exit code. If it got a reply from test-runner, it -//! will exit with code 0. +//! Have test-manager invoke the `arping` command targeting the bridge network between test-manager +//! <-> test-runner. If `arping` times out without a reply, it will exit with a non-0 exit code. If +//! it got a reply from test-runner, it will exit with code 0. //! //! Note that only linux was susceptible to this vulnerability. -use std::ffi::OsStr; -use std::process::Output; +use std::{ffi::OsStr, process::Output}; use anyhow::bail; use mullvad_management_interface::MullvadProxyClient; use test_macro::test_function; use test_rpc::ServiceClient; -use crate::tests::helpers::*; -use crate::tests::TestContext; -use crate::vm::network::bridge; +use crate::tests::{config::TEST_CONFIG, helpers::*, TestContext}; #[test_function(target_os = "linux")] pub async fn test_mllvd_cr_24_03( @@ -40,8 +38,9 @@ pub async fn test_mllvd_cr_24_03( mut mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { // Get the bridge network between manager and runner. This will be used when invoking `arping`. - let bridge = bridge()?; - // Connect runner to a relay. After this point we will be able to acquire the runner's private in-tunnel IP. + let bridge = &TEST_CONFIG.host_bridge_name; + // Connect runner to a relay. After this point we will be able to acquire the runner's private + // in-tunnel IP. connect_and_wait(&mut mullvad_client).await?; // Get the private ip address let in_tunnel_ip = { @@ -55,12 +54,12 @@ pub async fn test_mllvd_cr_24_03( "-i", "1", "-I", - &bridge, + bridge, &in_tunnel_ip.to_string(), ]) .await?; - // If arping exited with code 0, it means the runner replied to the ARP request, implying the runner leaked its - // private in-tunnel IP! + // If arping exited with code 0, it means the runner replied to the ARP request, implying the + // runner leaked its private in-tunnel IP! if let Some(0) = malicious_arping.status.code() { log::error!("{}", String::from_utf8(malicious_arping.stdout)?); bail!("ARP leak detected") diff --git a/test/test-manager/src/tests/config.rs b/test/test-manager/src/tests/config.rs index 04b2e01e71b1..b7b7b9f20701 100644 --- a/test/test-manager/src/tests/config.rs +++ b/test/test-manager/src/tests/config.rs @@ -1,5 +1,4 @@ -use std::sync::OnceLock; -use std::{ops::Deref, path::Path}; +use std::{net::Ipv4Addr, ops::Deref, path::Path, sync::OnceLock}; use test_rpc::meta::Os; pub static TEST_CONFIG: TestConfigContainer = TestConfigContainer::new(); @@ -35,7 +34,7 @@ pub struct TestConfig { pub mullvad_host: String, pub host_bridge_name: String, - + pub host_bridge_ip: Ipv4Addr, pub os: Os, /// The OpenVPN CA certificate to use with the the installed Mullvad App. pub openvpn_certificate: OpenVPNCertificate, @@ -52,6 +51,7 @@ impl TestConfig { ui_e2e_tests_filename: Option, mullvad_host: String, host_bridge_name: String, + host_bridge_ip: Ipv4Addr, os: Os, openvpn_certificate: OpenVPNCertificate, ) -> Self { @@ -63,6 +63,7 @@ impl TestConfig { ui_e2e_tests_filename, mullvad_host, host_bridge_name, + host_bridge_ip, os, openvpn_certificate, } diff --git a/test/test-manager/src/tests/dns.rs b/test/test-manager/src/tests/dns.rs index 69f98450caf1..28db345cf7c4 100644 --- a/test/test-manager/src/tests/dns.rs +++ b/test/test-manager/src/tests/dns.rs @@ -28,9 +28,9 @@ use crate::{ }, vm::network::{ CUSTOM_TUN_GATEWAY, CUSTOM_TUN_LOCAL_PRIVKEY, CUSTOM_TUN_LOCAL_TUN_ADDR, - CUSTOM_TUN_REMOTE_PUBKEY, CUSTOM_TUN_REMOTE_REAL_ADDR, CUSTOM_TUN_REMOTE_REAL_PORT, - CUSTOM_TUN_REMOTE_TUN_ADDR, NON_TUN_GATEWAY, + CUSTOM_TUN_REMOTE_PUBKEY, CUSTOM_TUN_REMOTE_REAL_PORT, CUSTOM_TUN_REMOTE_TUN_ADDR, }, + TEST_CONFIG, }; /// How long to wait for expected "DNS queries" to appear @@ -358,20 +358,28 @@ pub async fn test_dns_config_custom_private( rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> anyhow::Result<()> { - log::debug!("Setting custom DNS resolver to {NON_TUN_GATEWAY}"); + log::debug!( + "Setting custom DNS resolver to {}", + TEST_CONFIG.host_bridge_ip + ); mullvad_client .set_dns_options(settings::DnsOptions { default_options: settings::DefaultDnsOptions::default(), custom_options: settings::CustomDnsOptions { - addresses: vec![IpAddr::V4(NON_TUN_GATEWAY)], + addresses: vec![IpAddr::V4(TEST_CONFIG.host_bridge_ip)], }, state: settings::DnsState::Custom, }) .await .context("failed to configure DNS server")?; - run_dns_config_non_tunnel_test(&rpc, &mut mullvad_client, IpAddr::V4(NON_TUN_GATEWAY)).await + run_dns_config_non_tunnel_test( + &rpc, + &mut mullvad_client, + IpAddr::V4(TEST_CONFIG.host_bridge_ip), + ) + .await } /// Test whether the expected custom DNS works for public IPs. @@ -646,7 +654,7 @@ async fn connect_local_wg_relay(mullvad_client: &mut MullvadProxyClient) -> Resu .await?; let peer_addr: SocketAddr = SocketAddr::new( - IpAddr::V4(CUSTOM_TUN_REMOTE_REAL_ADDR), + IpAddr::V4(TEST_CONFIG.host_bridge_ip), CUSTOM_TUN_REMOTE_REAL_PORT, ); diff --git a/test/test-manager/src/tests/relay_ip_overrides.rs b/test/test-manager/src/tests/relay_ip_overrides.rs index 3805412ee92a..eb2e36d2f857 100644 --- a/test/test-manager/src/tests/relay_ip_overrides.rs +++ b/test/test-manager/src/tests/relay_ip_overrides.rs @@ -5,8 +5,8 @@ use super::{ TestContext, }; use crate::{ - vm, - vm::network::linux::{NON_TUN_GATEWAY, TEST_SUBNET}, + tests::config::TEST_CONFIG, + vm::{self, network::linux::TEST_SUBNET}, }; use anyhow::{anyhow, bail, ensure, Context}; use futures::FutureExt; @@ -88,7 +88,7 @@ pub async fn test_wireguard_ip_override( mullvad_client .set_relay_override(RelayOverride { hostname, - ipv4_addr_in: Some(NON_TUN_GATEWAY), + ipv4_addr_in: Some(TEST_CONFIG.host_bridge_ip), ipv6_addr_in: None, }) .await?; @@ -144,7 +144,7 @@ pub async fn test_openvpn_ip_override( mullvad_client .set_relay_override(RelayOverride { hostname, - ipv4_addr_in: Some(NON_TUN_GATEWAY), + ipv4_addr_in: Some(TEST_CONFIG.host_bridge_ip), ipv6_addr_in: None, }) .await?; @@ -229,7 +229,7 @@ pub async fn test_bridge_ip_override( mullvad_client .set_relay_override(RelayOverride { hostname, - ipv4_addr_in: Some(NON_TUN_GATEWAY), + ipv4_addr_in: Some(TEST_CONFIG.host_bridge_ip), ipv6_addr_in: None, }) .await?; diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs index 162de63f615d..bc15ccb8a3bb 100644 --- a/test/test-manager/src/tests/tunnel.rs +++ b/test/test-manager/src/tests/tunnel.rs @@ -625,7 +625,7 @@ pub async fn test_remote_socks_bridge( bridge_type: BridgeType::Custom, normal: BridgeConstraints::default(), custom: Some(CustomProxy::Socks5Remote(Socks5Remote::new(( - crate::vm::network::NON_TUN_GATEWAY, + TEST_CONFIG.host_bridge_ip, crate::vm::network::SOCKS5_PORT, )))), }) @@ -703,10 +703,8 @@ pub async fn test_local_socks_bridge( rpc: ServiceClient, mut mullvad_client: MullvadProxyClient, ) -> Result<(), Error> { - let remote_addr = SocketAddr::from(( - crate::vm::network::NON_TUN_GATEWAY, - crate::vm::network::SOCKS5_PORT, - )); + let remote_addr = + SocketAddr::from((TEST_CONFIG.host_bridge_ip, crate::vm::network::SOCKS5_PORT)); let socks_server = rpc .start_tcp_forward("127.0.0.1:0".parse().unwrap(), remote_addr) .await diff --git a/test/test-manager/src/vm/network/linux.rs b/test/test-manager/src/vm/network/linux.rs index f81131119cea..f5ed5236557f 100644 --- a/test/test-manager/src/vm/network/linux.rs +++ b/test/test-manager/src/vm/network/linux.rs @@ -38,9 +38,6 @@ data_encoding_macro::base64_array!( "pub const CUSTOM_TUN_LOCAL_PRIVKEY" = "mPue6Xt0pdz4NRAhfQSp/SLKo7kV7DW+2zvBq0N9iUI=" ); -/// "Real" (non-tunnel) IP of the wireguard remote peer on the host -#[allow(dead_code)] -pub const CUSTOM_TUN_REMOTE_REAL_ADDR: Ipv4Addr = Ipv4Addr::new(172, 29, 1, 1); /// Port of the wireguard remote peer as defined in `setup-network.sh`. #[allow(dead_code)] pub const CUSTOM_TUN_REMOTE_REAL_PORT: u16 = 51820; @@ -53,7 +50,7 @@ pub const CUSTOM_TUN_REMOTE_TUN_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 15, 1); pub const CUSTOM_TUN_GATEWAY: Ipv4Addr = CUSTOM_TUN_REMOTE_TUN_ADDR; /// Gateway of the non-tunnel interface. #[allow(dead_code)] -pub const NON_TUN_GATEWAY: Ipv4Addr = Ipv4Addr::new(172, 29, 1, 1); +pub(super) const NON_TUN_GATEWAY: Ipv4Addr = Ipv4Addr::new(172, 29, 1, 1); /// Name of the wireguard interface on the host pub const CUSTOM_TUN_INTERFACE_NAME: &str = "wg-relay0"; diff --git a/test/test-manager/src/vm/network/macos.rs b/test/test-manager/src/vm/network/macos.rs index 35467144ec1c..d0e48fbd7d7f 100644 --- a/test/test-manager/src/vm/network/macos.rs +++ b/test/test-manager/src/vm/network/macos.rs @@ -1,7 +1,7 @@ -use std::net::{Ipv4Addr, SocketAddrV4}; - use anyhow::{anyhow, Context, Result}; use futures::future::{self, Either}; +use nix::sys::socket::SockaddrStorage; +use std::net::{Ipv4Addr, SocketAddrV4}; use tokio::{io::AsyncWriteExt, process::Command}; // Private key of the wireguard remote peer on host. @@ -16,9 +16,6 @@ const CUSTOM_TUN_LOCAL_PUBKEY: &str = "h6elqt3dfamtS/p9jxJ8bIYs8UW9YHfTFhvx0fabT data_encoding_macro::base64_array!( "pub const CUSTOM_TUN_LOCAL_PRIVKEY" = "mPue6Xt0pdz4NRAhfQSp/SLKo7kV7DW+2zvBq0N9iUI=" ); -/// "Real" (non-tunnel) IP of the wireguard remote peer as defined in `setup-network.sh`. -/// TODO: This should not be hardcoded. Set by tart. -pub const CUSTOM_TUN_REMOTE_REAL_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 64, 1); /// Port of the wireguard remote peer as defined in `setup-network.sh`. pub const CUSTOM_TUN_REMOTE_REAL_PORT: u16 = 51820; /// Tunnel address of the wireguard local peer as defined in `setup-network.sh`. @@ -27,9 +24,6 @@ pub const CUSTOM_TUN_LOCAL_TUN_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 15, 2); pub const CUSTOM_TUN_REMOTE_TUN_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 15, 1); /// Gateway (and default DNS resolver) of the wireguard tunnel. pub const CUSTOM_TUN_GATEWAY: Ipv4Addr = CUSTOM_TUN_REMOTE_TUN_ADDR; -/// Gateway of the non-tunnel interface. -/// TODO: This should not be hardcoded. Set by tart. -pub const NON_TUN_GATEWAY: Ipv4Addr = Ipv4Addr::new(192, 168, 64, 1); /// Name of the wireguard interface on the host pub const CUSTOM_TUN_INTERFACE_NAME: &str = "utun123"; @@ -53,17 +47,26 @@ pub async fn setup_test_network() -> Result<()> { /// A hack to find the Tart bridge interface using `NON_TUN_GATEWAY`. /// It should be possible to retrieve this using the virtualization framework instead, /// but that requires an entitlement. -pub(crate) fn find_vm_bridge() -> Result { - for addr in nix::ifaddrs::getifaddrs().unwrap() { - if !addr.interface_name.starts_with("bridge") { - continue; - } - if let Some(address) = addr.address.as_ref().and_then(|addr| addr.as_sockaddr_in()) { - let interface_ip = *SocketAddrV4::from(*address).ip(); - if interface_ip == NON_TUN_GATEWAY { - return Ok(addr.interface_name.to_owned()); - } - } +pub(crate) fn find_vm_bridge(guest_ip: &Ipv4Addr) -> Result<(String, Ipv4Addr)> { + for addr in nix::ifaddrs::getifaddrs() + .unwrap() + .filter(|addr| addr.interface_name.starts_with("bridge")) + { + let to_sock_addr = |addr: Option| { + addr.as_ref() + .and_then(|addr| addr.as_sockaddr_in()) + .map(|addr| *SocketAddrV4::from(*addr).ip()) + }; + + if let (Some(address), Some(netmask)) = + (to_sock_addr(addr.address), to_sock_addr(addr.netmask)) + { + if let Ok(ip_v4_network) = ipnetwork::Ipv4Network::with_netmask(address, netmask) { + if ip_v4_network.contains(*guest_ip) { + return Ok((addr.interface_name.to_owned(), address)); + } + }; + }; } // This is probably either due to IP mismatch or Tart not running diff --git a/test/test-manager/src/vm/network/mod.rs b/test/test-manager/src/vm/network/mod.rs index a06227027b9d..c9a314726529 100644 --- a/test/test-manager/src/vm/network/mod.rs +++ b/test/test-manager/src/vm/network/mod.rs @@ -1,5 +1,7 @@ // #[cfg(target_os = "linux")] pub mod linux; +use std::net::Ipv4Addr; + #[cfg(target_os = "linux")] pub use linux as platform; @@ -11,19 +13,21 @@ pub use macos as platform; // Import shared constants and functions pub use platform::{ CUSTOM_TUN_GATEWAY, CUSTOM_TUN_INTERFACE_NAME, CUSTOM_TUN_LOCAL_PRIVKEY, - CUSTOM_TUN_LOCAL_TUN_ADDR, CUSTOM_TUN_REMOTE_PUBKEY, CUSTOM_TUN_REMOTE_REAL_ADDR, - CUSTOM_TUN_REMOTE_REAL_PORT, CUSTOM_TUN_REMOTE_TUN_ADDR, NON_TUN_GATEWAY, + CUSTOM_TUN_LOCAL_TUN_ADDR, CUSTOM_TUN_REMOTE_PUBKEY, CUSTOM_TUN_REMOTE_REAL_PORT, + CUSTOM_TUN_REMOTE_TUN_ADDR, }; /// Port on NON_TUN_GATEWAY that hosts a SOCKS5 server pub const SOCKS5_PORT: u16 = 54321; /// Get the name of the bridge interface between the test-manager and the test-runner. -pub fn bridge() -> anyhow::Result { +pub fn bridge( + #[cfg(target_os = "macos")] bridge_ip: &Ipv4Addr, +) -> anyhow::Result<(String, Ipv4Addr)> { #[cfg(target_os = "macos")] { - crate::vm::network::macos::find_vm_bridge() + crate::vm::network::macos::find_vm_bridge(bridge_ip) } #[cfg(not(target_os = "macos"))] - Ok(platform::BRIDGE_NAME.to_owned()) + Ok((platform::BRIDGE_NAME.to_owned(), platform::NON_TUN_GATEWAY)) } From b23da797007f5f4617b3fa484be2f39ebe67ffc4 Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Wed, 8 Jan 2025 15:54:01 +0100 Subject: [PATCH 2/3] Update docstring --- test/test-manager/src/vm/network/macos.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test-manager/src/vm/network/macos.rs b/test/test-manager/src/vm/network/macos.rs index d0e48fbd7d7f..3d8bc0fd2f2f 100644 --- a/test/test-manager/src/vm/network/macos.rs +++ b/test/test-manager/src/vm/network/macos.rs @@ -44,9 +44,8 @@ pub async fn setup_test_network() -> Result<()> { Ok(()) } -/// A hack to find the Tart bridge interface using `NON_TUN_GATEWAY`. -/// It should be possible to retrieve this using the virtualization framework instead, -/// but that requires an entitlement. +/// Returns the interface name and IP address of the bridge gateway, which is the (first) bridge +/// network that the given `guest_ip` belongs to. pub(crate) fn find_vm_bridge(guest_ip: &Ipv4Addr) -> Result<(String, Ipv4Addr)> { for addr in nix::ifaddrs::getifaddrs() .unwrap() From aab384fcffff864308467397b2444080ef24e2f2 Mon Sep 17 00:00:00 2001 From: Sebastian Holmin Date: Wed, 8 Jan 2025 16:01:40 +0100 Subject: [PATCH 3/3] Rewrite in functional style --- test/test-manager/src/vm/network/macos.rs | 44 +++++++++++------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/test/test-manager/src/vm/network/macos.rs b/test/test-manager/src/vm/network/macos.rs index 3d8bc0fd2f2f..fab648c33838 100644 --- a/test/test-manager/src/vm/network/macos.rs +++ b/test/test-manager/src/vm/network/macos.rs @@ -47,31 +47,29 @@ pub async fn setup_test_network() -> Result<()> { /// Returns the interface name and IP address of the bridge gateway, which is the (first) bridge /// network that the given `guest_ip` belongs to. pub(crate) fn find_vm_bridge(guest_ip: &Ipv4Addr) -> Result<(String, Ipv4Addr)> { - for addr in nix::ifaddrs::getifaddrs() + let to_sock_addr = |addr: Option| { + addr.as_ref() + .and_then(|addr| addr.as_sockaddr_in()) + .map(|addr| *SocketAddrV4::from(*addr).ip()) + }; + + nix::ifaddrs::getifaddrs() .unwrap() .filter(|addr| addr.interface_name.starts_with("bridge")) - { - let to_sock_addr = |addr: Option| { - addr.as_ref() - .and_then(|addr| addr.as_sockaddr_in()) - .map(|addr| *SocketAddrV4::from(*addr).ip()) - }; - - if let (Some(address), Some(netmask)) = - (to_sock_addr(addr.address), to_sock_addr(addr.netmask)) - { - if let Ok(ip_v4_network) = ipnetwork::Ipv4Network::with_netmask(address, netmask) { - if ip_v4_network.contains(*guest_ip) { - return Ok((addr.interface_name.to_owned(), address)); - } - }; - }; - } - - // This is probably either due to IP mismatch or Tart not running - Err(anyhow!( - "Failed to identify bridge used by tart -- not running?" - )) + .filter_map(|addr| { + let address = to_sock_addr(addr.address); + let netmask = to_sock_addr(addr.netmask); + address + .zip(netmask) + .map(|(address, netmask)| (addr.interface_name, address, netmask)) + }) + .find_map(|(interface_name, address, netmask)| { + ipnetwork::Ipv4Network::with_netmask(address, netmask) + .ok() + .filter(|ip_v4_network| ip_v4_network.contains(*guest_ip)) + .map(|_| (interface_name.to_owned(), address)) + }) + .ok_or_else(|| anyhow!("Failed to identify bridge used by tart -- not running?")) } async fn enable_forwarding() -> Result<()> {