diff --git a/leak-checker/src/lib.rs b/leak-checker/src/lib.rs index 1927385bc175..7b3caa57bf40 100644 --- a/leak-checker/src/lib.rs +++ b/leak-checker/src/lib.rs @@ -1,4 +1,4 @@ -use std::net::IpAddr; +use std::{fmt, net::IpAddr}; pub mod am_i_mullvad; pub mod traceroute; @@ -16,9 +16,35 @@ pub enum LeakInfo { /// Managed to reach another network node on the physical interface, bypassing firewall rules. NodeReachableOnInterface { reachable_nodes: Vec, - interface: String, + interface: Interface, }, /// Queried a , and was not mullvad. AmIMullvad { ip: IpAddr }, } + +#[derive(Clone)] +pub enum Interface { + Name(String), + + #[cfg(target_os = "windows")] + Luid(windows_sys::Win32::NetworkManagement::Ndis::NET_LUID_LH), +} + +impl From for Interface { + fn from(name: String) -> Self { + Interface::Name(name) + } +} + +impl fmt::Debug for Interface { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Name(arg0) => f.debug_tuple("Name").field(arg0).finish(), + + // TODO: + #[cfg(target_os = "windows")] + Self::Luid(arg0) => f.debug_tuple("Luid").field(unsafe { &arg0.Value }).finish(), + } + } +} diff --git a/leak-checker/src/traceroute.rs b/leak-checker/src/traceroute.rs index 7e2ce25a7ea1..ca298cf1a144 100644 --- a/leak-checker/src/traceroute.rs +++ b/leak-checker/src/traceroute.rs @@ -22,7 +22,7 @@ use pnet_packet::{ use socket2::{Domain, Protocol, Socket, Type}; use tokio::time::{sleep, timeout}; -use crate::LeakStatus; +use crate::{Interface, LeakStatus}; mod platform; @@ -34,7 +34,7 @@ use platform::{ pub struct TracerouteOpt { /// Try to bind to a specific interface #[clap(short, long)] - pub interface: String, + pub interface: Interface, /// Destination IP of the probe packets #[clap(short, long)] diff --git a/leak-checker/src/traceroute/platform/android.rs b/leak-checker/src/traceroute/platform/android.rs index 8b81b09f6684..e214d447a6e6 100644 --- a/leak-checker/src/traceroute/platform/android.rs +++ b/leak-checker/src/traceroute/platform/android.rs @@ -2,7 +2,7 @@ use std::net::IpAddr; use socket2::Socket; -use crate::traceroute::TracerouteOpt; +use crate::{traceroute::TracerouteOpt, Interface}; use super::{linux, linux::TracerouteLinux, unix, Traceroute}; @@ -12,12 +12,12 @@ impl Traceroute for TracerouteAndroid { type AsyncIcmpSocket = linux::AsyncIcmpSocketImpl; type AsyncUdpSocket = unix::AsyncUdpSocketUnix; - fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { + fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { // can't use the same method as desktop-linux here beacuse reasons super::common::bind_socket_to_interface(socket, interface) } - fn get_interface_ip(interface: &str) -> anyhow::Result { + fn get_interface_ip(interface: &Interface) -> anyhow::Result { super::unix::get_interface_ip(interface) } diff --git a/leak-checker/src/traceroute/platform/common.rs b/leak-checker/src/traceroute/platform/common.rs index 3b9e8cc0784e..0fc9ff5c9b6d 100644 --- a/leak-checker/src/traceroute/platform/common.rs +++ b/leak-checker/src/traceroute/platform/common.rs @@ -14,12 +14,12 @@ use tokio::{ use crate::{ traceroute::{parse_icmp_time_exceeded, parse_ipv4, RECV_TIMEOUT}, - LeakInfo, LeakStatus, + Interface, LeakInfo, LeakStatus, }; use super::{AsyncIcmpSocket, Impl, Traceroute}; -pub fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { +pub fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { let interface_ip = Impl::get_interface_ip(interface)?; log::info!("Binding socket to {interface_ip} ({interface:?})"); @@ -33,7 +33,7 @@ pub fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Res pub async fn recv_ttl_responses( socket: &impl AsyncIcmpSocket, - interface: &str, + interface: &Interface, ) -> anyhow::Result { // the list of node IP addresses from which we received a response to our probe packets. let mut reachable_nodes = vec![]; @@ -68,7 +68,7 @@ pub async fn recv_ttl_responses( _timeout = timer => { return Ok(LeakStatus::LeakDetected(LeakInfo::NodeReachableOnInterface { reachable_nodes, - interface: interface.to_string(), + interface: interface.clone(), })); } }; diff --git a/leak-checker/src/traceroute/platform/linux.rs b/leak-checker/src/traceroute/platform/linux.rs index 464dfbf8777b..4243499a105d 100644 --- a/leak-checker/src/traceroute/platform/linux.rs +++ b/leak-checker/src/traceroute/platform/linux.rs @@ -14,7 +14,7 @@ use socket2::Socket; use tokio::time::{sleep, Instant}; use crate::traceroute::{parse_icmp_echo_raw, TracerouteOpt, RECV_TIMEOUT}; -use crate::{LeakInfo, LeakStatus}; +use crate::{Interface, LeakInfo, LeakStatus}; use super::{unix, AsyncIcmpSocket, Traceroute}; @@ -26,11 +26,11 @@ impl Traceroute for TracerouteLinux { type AsyncIcmpSocket = AsyncIcmpSocketImpl; type AsyncUdpSocket = unix::AsyncUdpSocketUnix; - fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { + fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { bind_socket_to_interface(socket, interface) } - fn get_interface_ip(interface: &str) -> anyhow::Result { + fn get_interface_ip(interface: &Interface) -> anyhow::Result { super::unix::get_interface_ip(interface) } @@ -70,9 +70,11 @@ impl AsyncIcmpSocket for AsyncIcmpSocketImpl { } } -fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { +fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { log::info!("Binding socket to {interface:?}"); + let Interface::Name(interface) = interface; + socket .bind_device(Some(interface.as_bytes())) .context("Failed to bind socket to interface")?; @@ -86,7 +88,7 @@ fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result< // TODO: double check if this works on MacOS async fn recv_ttl_responses( destination: IpAddr, - interface: &str, + interface: &Interface, socket: &impl AsRawFd, ) -> anyhow::Result { // the list of node IP addresses from which we received a response to our probe packets. @@ -209,7 +211,7 @@ async fn recv_ttl_responses( Ok(LeakStatus::LeakDetected( LeakInfo::NodeReachableOnInterface { reachable_nodes, - interface: interface.to_string(), + interface: interface.clone(), }, )) } diff --git a/leak-checker/src/traceroute/platform/macos.rs b/leak-checker/src/traceroute/platform/macos.rs index 92dff4aa92be..6cdb79b5548c 100644 --- a/leak-checker/src/traceroute/platform/macos.rs +++ b/leak-checker/src/traceroute/platform/macos.rs @@ -1,12 +1,14 @@ use std::io; use std::net::IpAddr; +use std::num::NonZero; use std::os::fd::{FromRawFd, IntoRawFd}; -use anyhow::Context; +use anyhow::{anyhow, Context}; +use nix::net::if_::if_nametoindex; use socket2::Socket; use crate::traceroute::TracerouteOpt; -use crate::LeakStatus; +use crate::{Interface, LeakStatus}; use super::{common, unix, AsyncIcmpSocket, Traceroute}; @@ -18,12 +20,12 @@ impl Traceroute for TracerouteMacos { type AsyncIcmpSocket = AsyncIcmpSocketImpl; type AsyncUdpSocket = unix::AsyncUdpSocketUnix; - fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { + fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { // can't use the same method as desktop-linux here beacuse reasons bind_socket_to_interface(socket, interface) } - fn get_interface_ip(interface: &str) -> anyhow::Result { + fn get_interface_ip(interface: &Interface) -> anyhow::Result { super::unix::get_interface_ip(interface) } @@ -66,15 +68,14 @@ impl AsyncIcmpSocket for AsyncIcmpSocketImpl { } } -pub fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { - use nix::net::if_::if_nametoindex; - use std::num::NonZero; +pub fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { + let Interface::Name(interface) = interface; log::info!("Binding socket to {interface:?}"); - let interface_index = if_nametoindex(interface) - .map_err(anyhow::Report::from) - .and_then(|code| NonZero::new(code).ok_or_anyhow("Non-zero error code")) + let interface_index = if_nametoindex(interface.as_str()) + .map_err(anyhow::Error::from) + .and_then(|code| NonZero::new(code).ok_or(anyhow!("Non-zero error code"))) .context("Failed to get interface index")?; socket.bind_device_by_index_v4(Some(interface_index))?; diff --git a/leak-checker/src/traceroute/platform/mod.rs b/leak-checker/src/traceroute/platform/mod.rs index d14f63ad883e..201198bdc984 100644 --- a/leak-checker/src/traceroute/platform/mod.rs +++ b/leak-checker/src/traceroute/platform/mod.rs @@ -3,7 +3,7 @@ use std::{ net::{IpAddr, SocketAddr}, }; -use crate::LeakStatus; +use crate::{Interface, LeakStatus}; use super::TracerouteOpt; @@ -32,9 +32,12 @@ pub trait Traceroute { type AsyncIcmpSocket: AsyncIcmpSocket; type AsyncUdpSocket: AsyncUdpSocket; - fn get_interface_ip(interface: &str) -> anyhow::Result; + fn get_interface_ip(interface: &Interface) -> anyhow::Result; - fn bind_socket_to_interface(socket: &socket2::Socket, interface: &str) -> anyhow::Result<()>; + fn bind_socket_to_interface( + socket: &socket2::Socket, + interface: &Interface, + ) -> anyhow::Result<()>; /// Configure an ICMP socket to allow reception of ICMP/TimeExceeded errors. // TODO: consider moving into AsyncIcmpSocket constructor diff --git a/leak-checker/src/traceroute/platform/unix.rs b/leak-checker/src/traceroute/platform/unix.rs index 8a356a5f6e8a..d6c7d06da8a9 100644 --- a/leak-checker/src/traceroute/platform/unix.rs +++ b/leak-checker/src/traceroute/platform/unix.rs @@ -3,11 +3,15 @@ use std::os::fd::{FromRawFd, IntoRawFd}; use anyhow::Context; +use crate::Interface; + use super::AsyncUdpSocket; -pub fn get_interface_ip(interface: &str) -> anyhow::Result { +pub fn get_interface_ip(interface: &Interface) -> anyhow::Result { + let Interface::Name(interface) = interface; + for interface_address in nix::ifaddrs::getifaddrs()? { - if interface_address.interface_name != interface { + if &interface_address.interface_name != interface { continue; }; let Some(address) = interface_address.address else { diff --git a/leak-checker/src/traceroute/platform/windows.rs b/leak-checker/src/traceroute/platform/windows.rs index 382080f36bdb..7f7c1dc2a7c2 100644 --- a/leak-checker/src/traceroute/platform/windows.rs +++ b/leak-checker/src/traceroute/platform/windows.rs @@ -6,7 +6,7 @@ use std::{ ptr::null_mut, }; -use anyhow::{bail, anyhow, Context}; +use anyhow::{anyhow, bail, Context}; use socket2::Socket; use talpid_windows::net::{get_ip_address_for_interface, luid_from_alias, AddressFamily}; @@ -14,7 +14,7 @@ use windows_sys::Win32::Networking::WinSock::{ WSAGetLastError, WSAIoctl, SIO_RCVALL, SOCKET, SOCKET_ERROR, }; -use crate::{traceroute::TracerouteOpt, LeakStatus}; +use crate::{traceroute::TracerouteOpt, Interface, LeakStatus}; use super::{common, AsyncIcmpSocket, AsyncUdpSocket, Traceroute}; @@ -28,11 +28,11 @@ impl Traceroute for TracerouteWindows { type AsyncIcmpSocket = AsyncIcmpSocketImpl; type AsyncUdpSocket = AsyncUdpSocketWindows; - fn bind_socket_to_interface(socket: &Socket, interface: &str) -> anyhow::Result<()> { + fn bind_socket_to_interface(socket: &Socket, interface: &Interface) -> anyhow::Result<()> { common::bind_socket_to_interface(socket, interface) } - fn get_interface_ip(interface: &str) -> anyhow::Result { + fn get_interface_ip(interface: &Interface) -> anyhow::Result { get_interface_ip(interface) } @@ -93,8 +93,11 @@ impl AsyncUdpSocket for AsyncUdpSocketWindows { } } -pub fn get_interface_ip(interface: &str) -> anyhow::Result { - let interface_luid = luid_from_alias(interface)?; +pub fn get_interface_ip(interface: &Interface) -> anyhow::Result { + let interface_luid = match interface { + Interface::Name(name) => luid_from_alias(name)?, + Interface::Luid(luid) => *luid, + }; // TODO: ipv6 let interface_ip = get_ip_address_for_interface(AddressFamily::Ipv4, interface_luid)? diff --git a/mullvad-daemon/src/leak_checker/mod.rs b/mullvad-daemon/src/leak_checker/mod.rs index e06e10525e81..9eadd682d8ad 100644 --- a/mullvad-daemon/src/leak_checker/mod.rs +++ b/mullvad-daemon/src/leak_checker/mod.rs @@ -2,10 +2,9 @@ use anyhow::anyhow; use futures::{select, FutureExt}; use leak_checker::traceroute::TracerouteOpt; pub use leak_checker::LeakInfo; -use std::net::IpAddr; use std::time::Duration; use talpid_routing::RouteManagerHandle; -use talpid_types::tunnel::TunnelStateTransition; +use talpid_types::{net::Endpoint, tunnel::TunnelStateTransition}; use tokio::sync::mpsc; /// An actor that tries to leak traffic outside the tunnel while we are connected. @@ -96,13 +95,13 @@ impl Task { break 'leak_test; }; - let ping_destination = tunnel.endpoint.address.ip(); + let ping_destination = tunnel.endpoint; let route_manager = self.route_manager.clone(); let leak_test = async { // Give the connection a little time to settle before starting the test. // TODO: is this necessary? is there some better way? // TODO: ether remove this or add some concrete motivation. - tokio::time::sleep(Duration::from_millis(500)).await; + tokio::time::sleep(Duration::from_millis(5000)).await; check_for_leaks(&route_manager, ping_destination).await }; @@ -161,7 +160,7 @@ impl Task { async fn check_for_leaks( route_manager: &RouteManagerHandle, - destination: IpAddr, + destination: Endpoint, ) -> anyhow::Result> { // TODO (linux): // Use get_destination_route(ip, Some(fwmark)) to figure out default interface. @@ -169,7 +168,7 @@ async fn check_for_leaks( #[cfg(target_os = "linux")] let interface = { let Ok(Some(route)) = route_manager - .get_destination_route(destination, Some(mullvad_types::TUNNEL_FWMARK)) + .get_destination_route(destination.address.ip(), Some(mullvad_types::TUNNEL_FWMARK)) .await else { todo!("no route to relay?"); @@ -180,6 +179,7 @@ async fn check_for_leaks( .get_device() .expect("no device for default route??") .to_string() + .into() }; // TODO (android): @@ -187,29 +187,44 @@ async fn check_for_leaks( // It should be possible somehow. `ifconfig` can print interfaces. // needs further investigation #[cfg(target_os = "android")] - let interface: &str = todo!("get default interface"); + let interface = todo!("get default interface"); // TODO (macos): // get_default_route in route manager #[cfg(target_os = "macos")] - let interface: &str = todo!("get default interface"); + let interface = todo!("get default interface"); // TODO (windows): // Use default route monitor thingy. It should contain interfaces. // Can maybe use callback to subscribe for updates // get_best_route #[cfg(target_os = "windows")] - let interface: &str = todo!("get default interface"); + let interface = { + use std::net::IpAddr; + use talpid_windows::net::AddressFamily; + + let family = match destination.address.ip() { + IpAddr::V4(..) => AddressFamily::Ipv4, + IpAddr::V6(..) => AddressFamily::Ipv6, + }; + + let Ok(Some(route)) = talpid_routing::get_best_default_route(family) else { + todo!("no best default route"); + }; + + leak_checker::Interface::Luid(route.iface) + }; log::debug!("attempting to leak traffic on interface {interface:?} to {destination}"); // TODO: use UDP on windows leak_checker::traceroute::try_run_leak_test(&TracerouteOpt { - interface: interface.to_string(), - destination, - exclude_port: None, + interface, + destination: destination.address.ip(), port: None, - icmp: true, + + exclude_port: cfg!(target_os = "windows").then_some(destination.address.port()), + icmp: cfg!(not(target_os = "windows")), }) .await .map_err(|e| anyhow!("{e:#}")) diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs index 4bd405a4f2b9..11aa87080ef3 100644 --- a/talpid-core/src/firewall/macos.rs +++ b/talpid-core/src/firewall/macos.rs @@ -326,7 +326,6 @@ impl Firewall { let no_nat_to_vpn_server = pfctl::NatRuleBuilder::default() .action(pfctl::NatRuleAction::NoNat) .to(peer_endpoint.endpoint.address) - .user(Uid::from(0)) .build()?; rules.push(no_nat_to_vpn_server); @@ -597,10 +596,7 @@ impl Firewall { } /// Block traffic to relay_endpoint ip. Should come after [Self::get_allow_relay_rule]. - fn get_block_relay_rule( - &self, - relay_endpoint: &net::AllowedEndpoint, - ) -> Result { + fn get_block_relay_rule(&self, relay_endpoint: &AllowedEndpoint) -> Result { let mut builder = self.create_rule_builder(FilterRuleAction::Drop(DropAction::Return)); builder .direction(pfctl::Direction::Out) diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 21f8c3797da0..37d694769709 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -291,7 +291,7 @@ impl TunnelStateMachine { #[cfg(target_os = "macos")] let split_tunnel = - split_tunnel::SplitTunnel::spawn(args.command_tx.clone(), route_manager.clone()); + split_tunnel::SplitTunnel::spawn(args.command_tx.clone(), args.route_manager.clone()); let fw_args = FirewallArguments { #[cfg(not(target_os = "android"))]