diff options
| author | Joakim Hulthe <joakim.hulthe@mullvad.net> | 2024-11-29 10:48:15 +0100 |
|---|---|---|
| committer | Sebastian Holmin <sebastian.holmin@mullvad.net> | 2025-05-26 15:53:07 +0200 |
| commit | 8f3900bb993c3a3859564fd97f052fd1cecbb37e (patch) | |
| tree | a5f1644b4999086a644a8e27c3c5627a5738c2e8 /talpid-wireguard | |
| parent | 9dfafb3e5031c991db0b6117c8cd6a71d86deb40 (diff) | |
| download | mullvadvpn-8f3900bb993c3a3859564fd97f052fd1cecbb37e.tar.xz mullvadvpn-8f3900bb993c3a3859564fd97f052fd1cecbb37e.zip | |
Add Boringtun
Co-authored-by: Joakim Hulthe <joakim.hulthe@mullvad.net>
Co-authored-by: Sebastian Holmin <sebastian.holmin@mullvad.net>
Co-authored-by: David Göransson <david.goransson@mullvad.net>
Co-authored-by: Markus Pettersson <markus.pettersson@mullvad.net>
Co-authored-by: David Lönnhager <david.l@mullvad.net>
Diffstat (limited to 'talpid-wireguard')
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 54 | ||||
| -rw-r--r-- | talpid-wireguard/build.rs | 3 | ||||
| -rw-r--r-- | talpid-wireguard/src/boringtun/mod.rs | 314 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/check.rs | 51 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/mod.rs | 2 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/monitor.rs | 31 | ||||
| -rw-r--r-- | talpid-wireguard/src/ephemeral.rs | 10 | ||||
| -rw-r--r-- | talpid-wireguard/src/lib.rs | 360 | ||||
| -rw-r--r-- | talpid-wireguard/src/logging.rs | 7 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_go/mod.rs | 291 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_nt/mod.rs | 4 |
11 files changed, 776 insertions, 351 deletions
diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 9cac2c2970..b79ec503cd 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -10,6 +10,9 @@ rust-version.workspace = true [lints] workspace = true +[features] +boringtun = ["dep:boringtun", "dep:tun07", "talpid-tunnel/boringtun"] + [dependencies] async-trait = "0.1" thiserror = { workspace = true } @@ -30,12 +33,21 @@ tunnel-obfuscation = { path = "../tunnel-obfuscation" } rand = "0.8.5" surge-ping = "0.8.0" rand_chacha = "0.3.1" -wireguard-go-rs = { path = "../wireguard-go-rs"} +wireguard-go-rs = { path = "../wireguard-go-rs" } +tun07 = { package = "tun", version = "0.7.11", features = [ + "async", +], optional = true } byteorder = "1" internet-checksum = "0.2" socket2 = { workspace = true, features = ["all"] } tokio-stream = { version = "0.1", features = ["io-util"] } +[dependencies.boringtun] +optional = true +features = ["device"] +git = "https://github.com/mullvad/boringtun" +rev = "a7e11fb46d4a" + [target.'cfg(unix)'.dependencies] nix = "0.23" libc = "0.2.150" @@ -61,27 +73,27 @@ maybenot = "2.0.0" [target.'cfg(windows)'.dependencies.windows-sys] workspace = true features = [ - "Win32_Foundation", - "Win32_Globalization", - "Win32_Security", - "Win32_System_Com", - "Win32_System_Diagnostics_ToolHelp", - "Win32_System_Ioctl", - "Win32_System_IO", - "Win32_System_LibraryLoader", - "Win32_System_ProcessStatus", - "Win32_System_Registry", - "Win32_System_Services", - "Win32_System_SystemServices", - "Win32_System_Threading", - "Win32_System_WindowsProgramming", - "Win32_Networking_WinSock", - "Win32_NetworkManagement_IpHelper", - "Win32_NetworkManagement_Ndis", - "Win32_UI_Shell", - "Win32_UI_WindowsAndMessaging", + "Win32_Foundation", + "Win32_Globalization", + "Win32_Security", + "Win32_System_Com", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Ioctl", + "Win32_System_IO", + "Win32_System_LibraryLoader", + "Win32_System_ProcessStatus", + "Win32_System_Registry", + "Win32_System_Services", + "Win32_System_SystemServices", + "Win32_System_Threading", + "Win32_System_WindowsProgramming", + "Win32_Networking_WinSock", + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] [dev-dependencies] proptest = { workspace = true } -tokio = { workspace = true, features = [ "test-util" ] } +tokio = { workspace = true, features = ["test-util"] } diff --git a/talpid-wireguard/build.rs b/talpid-wireguard/build.rs index 23c2f3bb67..deba05655e 100644 --- a/talpid-wireguard/build.rs +++ b/talpid-wireguard/build.rs @@ -6,9 +6,6 @@ fn main() { if target_os == "windows" { declare_libs_dir("../dist-assets/binaries"); } - // Wireguard-Go can be used on all platforms - println!("cargo::rustc-check-cfg=cfg(wireguard_go)"); - println!("cargo::rustc-cfg=wireguard_go"); // Enable DAITA by default on desktop and android println!("cargo::rustc-check-cfg=cfg(daita)"); diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs new file mode 100644 index 0000000000..7482a26301 --- /dev/null +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -0,0 +1,314 @@ +use crate::{ + config::Config, + stats::{Stats, StatsMap}, + Tunnel, TunnelError, +}; +use boringtun::device::{ + api::{command::*, ApiClient, ApiServer}, + peer::AllowedIP, + DeviceConfig, DeviceHandle, +}; + +#[cfg(not(target_os = "android"))] +use ipnetwork::IpNetwork; +#[cfg(target_os = "android")] +use std::os::fd::AsRawFd; +use std::{ + future::Future, + ops::Deref, + sync::{Arc, Mutex}, +}; +use talpid_tunnel::tun_provider::{self, Tun, TunProvider}; +use talpid_tunnel_config_client::DaitaSettings; +use tun07::AbstractDevice; + +pub struct BoringTun { + device_handle: DeviceHandle, + config_tx: ApiClient, + config: Config, + + /// Name of the tun interface. + interface_name: String, +} + +/// Configure and start a boringtun tunnel. +pub async fn open_boringtun_tunnel( + config: &Config, + tun_provider: Arc<Mutex<tun_provider::TunProvider>>, + #[cfg(target_os = "android")] route_manager_handle: talpid_routing::RouteManagerHandle, +) -> super::Result<BoringTun> { + log::info!("BoringTun::start_tunnel"); + let routes = config.get_tunnel_destinations(); + + log::info!("calling get_tunnel_for_userspace"); + #[cfg(not(target_os = "android"))] + let async_tun = { + let tun = get_tunnel_for_userspace(tun_provider, config, routes)?; + + #[cfg(unix)] + { + tun.into_inner().into_inner() + } + #[cfg(windows)] + { + tun.into_inner() + } + }; + + let (mut config_tx, config_rx) = ApiServer::new(); + + let boringtun_config = DeviceConfig { + n_threads: 4, + api: Some(config_rx), + on_bind: None, + }; + + #[cfg(target_os = "android")] + let mut boringtun_config = boringtun_config; + + #[cfg(target_os = "android")] + let async_tun = { + let _ = routes; // TODO: do we need this? + let (mut tun, fd) = get_tunnel_for_userspace(Arc::clone(&tun_provider), config)?; + let is_new_tunnel = tun.is_new; + + // TODO We should also wait for routes before sending any ping / connectivity check + + // There is a brief period of time between setting up a Wireguard-go tunnel and the tunnel being ready to serve + // traffic. This function blocks until the tunnel starts to serve traffic or until [connectivity::Check] times out. + if is_new_tunnel { + let expected_routes = tun_provider.lock().unwrap().real_routes(); + + route_manager_handle + .clone() + .wait_for_routes(expected_routes) + .await + .map_err(crate::Error::SetupRoutingError) + .map_err(|e| TunnelError::RecoverableStartWireguardError(Box::new(e)))?; + } + + let mut config = tun07::Configuration::default(); + config.raw_fd(fd); + + boringtun_config.on_bind = Some(Box::new(move |socket| { + tun.bypass(socket.as_raw_fd()).unwrap() + })); + + let device = tun07::Device::new(&config).unwrap(); + tun07::AsyncDevice::new(device).unwrap() + }; + + let interface_name = async_tun.deref().tun_name().unwrap(); + + log::info!("passing tunnel dev to boringtun"); + let device_handle: DeviceHandle = DeviceHandle::new(async_tun, boringtun_config) + .await + .map_err(TunnelError::BoringTunDevice)?; + + set_boringtun_config(&mut config_tx, config).await?; + + log::info!( + "This tunnel was brought to you by... +......................................................... +..*...*.. .--. .---. ..*....*. +...*..... | ) o | ......*.. +.*..*..*. |--: .-. .--.. .--. .-..|. . .--. ...*..... +...*..... | )( )| | | |( ||| | | | .*.....*. +*.....*.. '--' `-' ' -' `-' `-`-`|'`--`-' `- .....*... +......... ._.' ..*...*.. +..*...*.............................................*...." + ); + + Ok(BoringTun { + device_handle, + config: config.clone(), + config_tx, + interface_name, + }) +} + +#[async_trait::async_trait] +impl Tunnel for BoringTun { + fn get_interface_name(&self) -> String { + self.interface_name.clone() + } + + fn stop(self: Box<Self>) -> Result<(), TunnelError> { + log::info!("BoringTun::stop"); // remove me + tokio::runtime::Handle::current().block_on(self.device_handle.stop()); + Ok(()) + } + + async fn get_tunnel_stats(&self) -> Result<StatsMap, TunnelError> { + let response = self + .config_tx + .send(Get::default()) + .await + .expect("Failed to get peers"); + + let Response::Get(response) = response else { + return Err(TunnelError::GetConfigError); + }; + Ok(StatsMap::from_iter(response.peers.into_iter().map( + |peer| { + ( + peer.peer.public_key.0, + Stats { + tx_bytes: peer.tx_bytes.unwrap_or_default(), + rx_bytes: peer.rx_bytes.unwrap_or_default(), + }, + ) + }, + ))) + } + + fn set_config<'a>( + &'a mut self, + config: Config, + ) -> std::pin::Pin<Box<dyn Future<Output = Result<(), TunnelError>> + Send + 'a>> { + Box::pin(async move { + self.config = config; + set_boringtun_config(&mut self.config_tx, &self.config).await?; + Ok(()) + }) + } + + fn start_daita(&mut self, _settings: DaitaSettings) -> Result<(), TunnelError> { + log::info!("Haha no"); + Ok(()) + } +} + +async fn set_boringtun_config( + tx: &mut ApiClient, + config: &Config, +) -> Result<(), crate::TunnelError> { + log::info!("configuring boringtun device"); + let mut set_cmd = Set::builder() + .private_key(config.tunnel.private_key.to_bytes()) + .listen_port(0u16) + .replace_peers() + .build(); + + #[cfg(target_os = "linux")] + { + set_cmd.fwmark = config.fwmark; + } + + for peer in config.peers() { + let mut boring_peer = Peer::builder() + .public_key(*peer.public_key.as_bytes()) + .endpoint(peer.endpoint) + .allowed_ip( + peer.allowed_ips + .iter() + .map(|net| AllowedIP { + addr: net.ip(), + cidr: net.prefix(), + }) + .collect(), + ) + .build(); + + if let Some(psk) = &peer.psk { + boring_peer.preshared_key = Some(SetUnset::Set((*psk.as_bytes()).into())); + } + + let boring_peer = SetPeer::builder().peer(boring_peer).build(); + + set_cmd.peers.push(boring_peer); + } + + tx.send(set_cmd).await.map_err(|err| { + log::error!("Failed to set boringtun config: {err:#}"); + TunnelError::SetConfigError + })?; + Ok(()) +} + +#[cfg(target_os = "windows")] +fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, + routes: impl Iterator<Item = IpNetwork>, +) -> Result<Tun, crate::TunnelError> { + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.mtu = config.mtu; + + let _ = routes; + + #[cfg(windows)] + tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice) +} + +#[cfg(all(not(target_os = "android"), unix))] +fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, + routes: impl Iterator<Item = IpNetwork>, +) -> Result<Tun, crate::TunnelError> { + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + #[cfg(target_os = "linux")] + { + tun_config.name = Some(crate::config::MULLVAD_INTERFACE_NAME.to_string()); + tun_config.packet_information = false; + } + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.routes = routes.collect(); + tun_config.mtu = config.mtu; + + tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice) +} + +#[cfg(target_os = "android")] +pub fn get_tunnel_for_userspace( + tun_provider: Arc<Mutex<TunProvider>>, + config: &Config, +) -> Result<(Tun, std::os::fd::RawFd), TunnelError> { + let mut last_error = None; + let mut tun_provider = tun_provider.lock().unwrap(); + + let tun_config = tun_provider.config_mut(); + tun_config.addresses = config.tunnel.addresses.clone(); + tun_config.ipv4_gateway = config.ipv4_gateway; + tun_config.ipv6_gateway = config.ipv6_gateway; + tun_config.mtu = config.mtu; + + // Route everything into the tunnel and have wireguard-go act as a firewall when + // blocking. These will not necessarily be the actual routes used by android. Those will + // be generated at a later stage e.g. if Local Network Sharing is enabled. + tun_config.routes = vec!["0.0.0.0/0".parse().unwrap(), "::/0".parse().unwrap()]; + + const MAX_PREPARE_TUN_ATTEMPTS: usize = 4; + + for _ in 1..=MAX_PREPARE_TUN_ATTEMPTS { + let tunnel_device = tun_provider + .open_tun() + .map_err(TunnelError::SetupTunnelDevice)?; + + match nix::unistd::dup(tunnel_device.as_raw_fd()) { + Ok(fd) => return Ok((tunnel_device, fd)), + #[cfg(not(target_os = "macos"))] + Err(error @ nix::errno::Errno::EBADFD) => last_error = Some(error), + Err(error @ nix::errno::Errno::EBADF) => last_error = Some(error), + Err(error) => return Err(TunnelError::FdDuplicationError(error)), + } + } + + Err(TunnelError::FdDuplicationError( + last_error.expect("Should be collected in loop"), + )) +} diff --git a/talpid-wireguard/src/connectivity/check.rs b/talpid-wireguard/src/connectivity/check.rs index 9c029948bf..b67c5da497 100644 --- a/talpid-wireguard/src/connectivity/check.rs +++ b/talpid-wireguard/src/connectivity/check.rs @@ -1,18 +1,16 @@ -use std::net::Ipv4Addr; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::broadcast; -use tokio::time::Instant; +use std::{ + net::Ipv4Addr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::{sync::broadcast, time::Instant}; -use super::constants::*; -use super::error::Error; -use super::pinger; +use super::{constants::*, error::Error, pinger}; -use crate::stats::StatsMap; -#[cfg(target_os = "android")] -use crate::Tunnel; -use crate::{TunnelError, TunnelType}; +use crate::{stats::StatsMap, Tunnel, TunnelError}; use pinger::Pinger; /// Verifies if a connection to a tunnel is working. @@ -132,7 +130,7 @@ impl Check { // successful at the start of a connection. pub async fn establish_connectivity( &mut self, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { // Send initial ping to prod WireGuard into connecting. self.ping_state @@ -161,7 +159,7 @@ impl Check { timeout_initial: Duration, timeout_multiplier: u32, max_timeout: Duration, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { if self.conn_state.connected() { return Ok(true); @@ -226,7 +224,7 @@ impl Check { pub(crate) async fn check_connectivity( &mut self, now: Instant, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { Self::check_connectivity_interval( &mut self.conn_state, @@ -244,7 +242,7 @@ impl Check { ping_state: &mut PingState, now: Instant, timeout: Duration, - tunnel_handle: &TunnelType, + tunnel_handle: &dyn Tunnel, ) -> Result<bool, Error> { match Self::get_stats(tunnel_handle) .await @@ -265,7 +263,7 @@ impl Check { /// If None is returned, then the underlying tunnel has already been closed and all subsequent /// calls will also return None. - async fn get_stats(tunnel_handle: &TunnelType) -> Result<Option<StatsMap>, TunnelError> { + async fn get_stats(tunnel_handle: &dyn Tunnel) -> Result<Option<StatsMap>, TunnelError> { let stats = tunnel_handle.get_tunnel_stats().await?; if stats.is_empty() { log::error!("Tunnel unexpectedly shut down"); @@ -604,7 +602,10 @@ mod test { Check::maybe_send_ping(&mut checker.conn_state, &mut checker.ping_state, start) .await .unwrap(); - assert!(!checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(!checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test] @@ -617,7 +618,10 @@ mod test { let start = now.checked_sub(Duration::from_secs(1)).unwrap(); let (mut checker, _cancel_token) = mock_checker(start, Box::new(pinger)); - assert!(!checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(!checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test] @@ -633,7 +637,10 @@ mod test { // Mock the state - connectivity has been established checker.conn_state = connected_state(start); - assert!(checker.check_connectivity(now, &tunnel).await.unwrap()) + assert!(checker + .check_connectivity(now, tunnel.as_ref()) + .await + .unwrap()) } #[tokio::test(start_paused = true)] @@ -671,7 +678,7 @@ mod test { ESTABLISH_TIMEOUT, ESTABLISH_TIMEOUT_MULTIPLIER, MAX_ESTABLISH_TIMEOUT, - &tunnel, + tunnel.as_ref(), ) .await, ) diff --git a/talpid-wireguard/src/connectivity/mod.rs b/talpid-wireguard/src/connectivity/mod.rs index 2da555ad45..709749abd6 100644 --- a/talpid-wireguard/src/connectivity/mod.rs +++ b/talpid-wireguard/src/connectivity/mod.rs @@ -6,7 +6,7 @@ mod mock; mod monitor; mod pinger; -#[cfg(target_os = "android")] +#[cfg(all(target_os = "android", not(feature = "boringtun")))] pub use check::CancelReceiver; pub use check::{CancelToken, Check}; pub use error::Error; diff --git a/talpid-wireguard/src/connectivity/monitor.rs b/talpid-wireguard/src/connectivity/monitor.rs index 1272b43f4d..87c9ffacd0 100644 --- a/talpid-wireguard/src/connectivity/monitor.rs +++ b/talpid-wireguard/src/connectivity/monitor.rs @@ -1,12 +1,13 @@ use std::{sync::Weak, time::Duration}; -use tokio::sync::Mutex; -use tokio::time::{Instant, MissedTickBehavior}; +use tokio::{ + sync::Mutex, + time::{Instant, MissedTickBehavior}, +}; use crate::TunnelType; -use super::check::Check; -use super::error::Error; +use super::{check::Check, error::Error}; /// Sleep time used when checking if an established connection is still working. const REGULAR_LOOP_SLEEP: Duration = Duration::from_secs(1); @@ -66,7 +67,7 @@ impl Monitor { }; self.connectivity_check - .check_connectivity(Instant::now(), tunnel) + .check_connectivity(Instant::now(), tunnel.as_ref()) .await } } @@ -75,15 +76,17 @@ impl Monitor { mod test { use super::*; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - use std::time::Duration; + use std::{ + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::Duration, + }; - use tokio::sync::mpsc; - use tokio::sync::Mutex; + use tokio::sync::{mpsc, Mutex}; - use crate::connectivity::constants::*; - use crate::connectivity::mock::*; + use crate::connectivity::{constants::*, mock::*}; #[tokio::test(start_paused = true)] /// Verify that the connectivity monitor doesn't fail if the tunnel constantly sends traffic, @@ -99,7 +102,7 @@ mod test { }; tokio::spawn(async move { - let start_result = checker.establish_connectivity(&tunnel).await; + let start_result = checker.establish_connectivity(tunnel.as_ref()).await; result_tx.send(start_result).await.unwrap(); // Pointer dance let tunnel = Arc::new(Mutex::new(Some(tunnel))); @@ -155,7 +158,7 @@ mod test { let start = now.checked_sub(Duration::from_secs(1)).unwrap(); mock_checker(start, Box::new(pinger)) }; - let start_result = checker.establish_connectivity(&tunnel).await; + let start_result = checker.establish_connectivity(tunnel.as_ref()).await; result_tx.send(start_result).await.unwrap(); // Pointer dance let _tunnel = Arc::new(Mutex::new(Some(tunnel))); diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs index 1d7f4f3955..b0431b94fd 100644 --- a/talpid-wireguard/src/ephemeral.rs +++ b/talpid-wireguard/src/ephemeral.rs @@ -1,8 +1,6 @@ //! This module takes care of obtaining ephemeral peers, updating the WireGuard configuration and //! restarting obfuscation and WG tunnels when necessary. -#[cfg(target_os = "android")] // On Android, the Tunnel trait is not imported by default. -use super::Tunnel; use super::{config::Config, obfuscation::ObfuscatorHandle, CloseMsg, Error, TunnelType}; #[cfg(target_os = "android")] @@ -207,15 +205,15 @@ async fn reconfigure_tunnel( } { let mut shared_tunnel = tunnel.lock().await; - let tunnel = shared_tunnel.take().expect("tunnel was None"); + let mut tunnel = shared_tunnel.take().expect("tunnel was None"); - let updated_tunnel = tunnel - .set_config(&config) + tunnel + .set_config(config.clone()) .await .map_err(Error::TunnelError) .map_err(CloseMsg::SetupError)?; - *shared_tunnel = Some(updated_tunnel); + *shared_tunnel = Some(tunnel); } Ok(config) } diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index 308d8c335b..461a18eda4 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -7,29 +7,20 @@ use self::config::Config; use futures::channel::mpsc; use futures::future::Future; use obfuscation::ObfuscatorHandle; -#[cfg(target_os = "android")] -use std::borrow::Cow; #[cfg(windows)] use std::io; use std::{ convert::Infallible, - net::IpAddr, path::Path, pin::Pin, - sync::{mpsc as sync_mpsc, Arc, Mutex}, + sync::{mpsc as sync_mpsc, Arc}, }; -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(not(target_os = "android"))] use std::{env, sync::LazyLock}; #[cfg(not(target_os = "android"))] use talpid_routing::{self, RequiredRoute}; -#[cfg(not(windows))] -use talpid_tunnel::tun_provider; -use talpid_tunnel::{ - tun_provider::TunProvider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata, -}; +use talpid_tunnel::{tun_provider, EventHook, TunnelArgs, TunnelEvent, TunnelMetadata}; -#[cfg(target_os = "android")] -use talpid_routing::RouteManagerHandle; #[cfg(daita)] use talpid_tunnel_config_client::DaitaSettings; use talpid_types::{ @@ -38,6 +29,12 @@ use talpid_types::{ }; use tokio::sync::Mutex as AsyncMutex; +#[cfg(feature = "boringtun")] +mod boringtun; + +#[cfg(not(feature = "boringtun"))] +mod wireguard_go; + /// WireGuard config data-types pub mod config; mod connectivity; @@ -45,8 +42,6 @@ mod ephemeral; mod logging; mod obfuscation; mod stats; -#[cfg(wireguard_go)] -mod wireguard_go; #[cfg(target_os = "linux")] pub(crate) mod wireguard_kernel; #[cfg(windows)] @@ -55,14 +50,7 @@ mod wireguard_nt; #[cfg(not(target_os = "android"))] mod mtu_detection; -#[cfg(wireguard_go)] -use self::wireguard_go::WgGoTunnel; - -// On android we only have Wireguard Go tunnel -#[cfg(not(target_os = "android"))] type TunnelType = Box<dyn Tunnel>; -#[cfg(target_os = "android")] -type TunnelType = WgGoTunnel; type Result<T> = std::result::Result<T, Error>; @@ -83,7 +71,7 @@ pub enum Error { /// An interaction with a tunnel failed #[error("Tunnel failed")] - TunnelError(#[source] TunnelError), + TunnelError(#[from] TunnelError), /// Failed to run tunnel obfuscation #[error("Tunnel obfuscation failed")] @@ -122,9 +110,8 @@ impl Error { Error::TunnelError(TunnelError::BypassError(_)) => true, #[cfg(windows)] - _ => self.get_tunnel_device_error().is_some(), + Error::TunnelError(TunnelError::SetupTunnelDevice(_)) => true, - #[cfg(not(windows))] _ => false, } } @@ -133,7 +120,9 @@ impl Error { #[cfg(windows)] pub fn get_tunnel_device_error(&self) -> Option<&io::Error> { match self { - Error::TunnelError(TunnelError::SetupTunnelDevice(error)) => Some(error), + Error::TunnelError(TunnelError::SetupTunnelDevice(tun_provider::Error::Io(error))) => { + Some(error) + } _ => None, } } @@ -151,7 +140,7 @@ pub struct WireguardMonitor { obfuscator: Arc<AsyncMutex<Option<ObfuscatorHandle>>>, } -#[cfg(any(target_os = "linux", target_os = "windows"))] +#[cfg(not(target_os = "android"))] /// Overrides the preference for the kernel module for WireGuard. static FORCE_USERSPACE_WIREGUARD: LazyLock<bool> = LazyLock::new(|| { env::var("TALPID_FORCE_USERSPACE_WIREGUARD") @@ -164,8 +153,8 @@ impl WireguardMonitor { #[cfg(not(target_os = "android"))] pub fn start( params: &TunnelParameters, - log_path: Option<&Path>, args: TunnelArgs<'_>, + _log_path: Option<&Path>, ) -> Result<WireguardMonitor> { #[cfg(any(target_os = "windows", target_os = "linux"))] let desired_mtu = args @@ -194,19 +183,27 @@ impl WireguardMonitor { config.mtu = clamp_mtu(params, config.mtu); } + // NOTE: We force userspace WireGuard while boringtun is enabled to more easily test + // the implementation, as DAITA is not currently supported by boringtun. + // TODO: Remove `cfg!(feature = "boringtun")`. + let userspace_wireguard = + *FORCE_USERSPACE_WIREGUARD || config.daita || cfg!(feature = "boringtun"); + #[cfg(target_os = "windows")] let (setup_done_tx, setup_done_rx) = mpsc::channel(0); let tunnel = Self::open_tunnel( args.runtime.clone(), &config, - log_path, #[cfg(target_os = "windows")] args.resource_dir, + #[cfg(not(all(target_os = "windows", not(feature = "boringtun"))))] args.tun_provider.clone(), - #[cfg(target_os = "windows")] + #[cfg(all(windows, not(feature = "boringtun")))] args.route_manager.clone(), #[cfg(target_os = "windows")] setup_done_tx, + userspace_wireguard, + _log_path, )?; let iface_name = tunnel.get_interface_name(); @@ -242,8 +239,15 @@ impl WireguardMonitor { let close_obfs_sender: sync_mpsc::Sender<CloseMsg> = moved_close_obfs_sender; let obfuscator = moved_obfuscator; #[cfg(windows)] - Self::add_device_ip_addresses(&iface_name, &config.tunnel.addresses, setup_done_rx) - .await?; + if cfg!(feature = "boringtun") && userspace_wireguard { + // NOTE: For boringtun, we use the `tun` crate to create our tunnel interface. + // It will automatically configure the IP address and DNS servers using `netsh`. + // This is quite slow, so we need to wait for the interface to be created. + Self::wait_for_ip_addresses(&config, &iface_name).await?; + } else { + Self::add_device_ip_addresses(&iface_name, &config.tunnel.addresses, setup_done_rx) + .await?; + } let metadata = Self::tunnel_metadata(&iface_name, &config); let allowed_traffic = Self::allowed_traffic_during_tunnel_config(&config); @@ -330,7 +334,7 @@ impl WireguardMonitor { let lock = tunnel.lock().await; let borrowed_tun = lock.as_ref().expect("The tunnel was dropped unexpectedly"); match connectivity_monitor - .establish_connectivity(borrowed_tun) + .establish_connectivity(borrowed_tun.as_ref()) .await { Ok(true) => Ok(()), @@ -399,8 +403,8 @@ impl WireguardMonitor { #[cfg(target_os = "android")] pub fn start( params: &TunnelParameters, - log_path: Option<&Path>, args: TunnelArgs<'_>, + #[allow(unused_variables)] log_path: Option<&Path>, ) -> Result<WireguardMonitor> { let desired_mtu = get_desired_mtu(params); let mut config = @@ -425,24 +429,39 @@ impl WireguardMonitor { let should_negotiate_ephemeral_peer = config.quantum_resistant || config.daita; let (cancel_token, cancel_receiver) = connectivity::CancelToken::new(); - let connectivity_check = connectivity::Check::new( + #[allow(unused_mut)] + let mut connectivity_monitor = connectivity::Check::new( config.ipv4_gateway, args.retry_attempt, cancel_receiver.clone(), ) .map_err(Error::ConnectivityMonitorError)?; - let tunnel = args.runtime.block_on(Self::open_wireguard_go_tunnel( - &config, - log_path, - args.tun_provider.clone(), - args.route_manager, - // In case we should negotiate an ephemeral peer, we should specify via AllowedIPs - // that we only allows traffic to/from the gateway. This is only needed on Android - // since we lack a firewall there. - should_negotiate_ephemeral_peer, - cancel_receiver, - ))?; + #[cfg(feature = "boringtun")] + let tunnel = args + .runtime + .block_on(boringtun::open_boringtun_tunnel( + &config, + args.tun_provider.clone(), + args.route_manager, + )) + .map(Box::new)? as Box<dyn Tunnel>; + + #[cfg(not(feature = "boringtun"))] + let tunnel = args + .runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( + &config, + log_path, + args.tun_provider.clone(), + args.route_manager, + // In case we should negotiate an ephemeral peer, we should specify via AllowedIPs + // that we only allows traffic to/from the gateway. This is only needed on Android + // since we lack a firewall there. + should_negotiate_ephemeral_peer, + cancel_receiver, + )) + .map(Box::new)? as Box<dyn Tunnel>; let iface_name = tunnel.get_interface_name(); let tunnel = Arc::new(AsyncMutex::new(Some(tunnel))); @@ -468,6 +487,29 @@ impl WireguardMonitor { .on_event(TunnelEvent::InterfaceUp(metadata.clone(), allowed_traffic)) .await; + #[cfg(feature = "boringtun")] + { + let lock = tunnel.lock().await; + let borrowed_tun = lock.as_ref().expect("The tunnel was dropped unexpectedly"); + match connectivity_monitor + .establish_connectivity(borrowed_tun.as_ref()) + .await + { + Ok(true) => Ok(()), + Ok(false) => { + log::warn!("Timeout while checking tunnel connection"); + Err(CloseMsg::PingErr) + } + Err(error) => { + log::error!( + "{}", + error.display_chain_with_msg("Failed to check tunnel connection") + ); + Err(CloseMsg::PingErr) + } + }?; + } + if should_negotiate_ephemeral_peer { let ephemeral_obfs_sender = close_obfs_sender.clone(); @@ -501,7 +543,7 @@ impl WireguardMonitor { let metadata = Self::tunnel_metadata(&iface_name, &config); event_hook.on_event(TunnelEvent::Up(metadata)).await; - if let Err(error) = connectivity::Monitor::init(connectivity_check) + if let Err(error) = connectivity::Monitor::init(connectivity_monitor) .run(Arc::downgrade(&tunnel)) .await { @@ -561,45 +603,36 @@ impl WireguardMonitor { AllowedTunnelTraffic::All } - /// Replace `0.0.0.0/0`/`::/0` with the gateway IPs when `gateway_only` is true. - /// Used to block traffic to other destinations while connecting on Android. - #[cfg(target_os = "android")] - fn patch_allowed_ips(config: &Config, gateway_only: bool) -> Cow<'_, Config> { - if gateway_only { - let mut patched_config = config.clone(); - let gateway_net_v4 = ipnetwork::IpNetwork::from(IpAddr::from(config.ipv4_gateway)); - let gateway_net_v6 = config - .ipv6_gateway - .map(|net| ipnetwork::IpNetwork::from(IpAddr::from(net))); - for peer in patched_config.peers_mut() { - peer.allowed_ips = peer - .allowed_ips - .iter() - .cloned() - .filter_map(|mut allowed_ip| { - if allowed_ip.prefix() == 0 { - if allowed_ip.is_ipv4() { - allowed_ip = gateway_net_v4; - } else if let Some(net) = gateway_net_v6 { - allowed_ip = net; - } else { - return None; - } - } - Some(allowed_ip) - }) - .collect(); - } - Cow::Owned(patched_config) - } else { - Cow::Borrowed(config) - } + #[cfg(windows)] + async fn wait_for_ip_addresses( + config: &Config, + iface_name: &String, + ) -> std::result::Result<(), CloseMsg> { + log::debug!("Waiting for tunnel IP interfaces to arrive"); + let luid = talpid_windows::net::luid_from_alias(iface_name).map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + talpid_windows::net::wait_for_interfaces(luid, true, config.ipv6_gateway.is_some()) + .await + .map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + talpid_windows::net::wait_for_addresses(luid) + .await + .map_err(|error| { + log::error!("Failed to obtain tunnel interface LUID: {}", error); + CloseMsg::SetupError(Error::IpInterfacesError) + })?; + log::debug!("Done waiting for tunnel IP interfaces to arrive"); + Ok(()) } #[cfg(windows)] async fn add_device_ip_addresses( iface_name: &str, - addresses: &[IpAddr], + addresses: &[std::net::IpAddr], mut setup_done_rx: mpsc::Receiver<std::result::Result<(), BoxedError>>, ) -> std::result::Result<(), CloseMsg> { use futures::StreamExt; @@ -631,27 +664,35 @@ impl WireguardMonitor { Ok(()) } + #[allow(clippy::too_many_arguments)] #[cfg(target_os = "windows")] fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, resource_dir: &Path, - _tun_provider: Arc<Mutex<TunProvider>>, - route_manager: talpid_routing::RouteManagerHandle, + #[cfg(feature = "boringtun")] tun_provider: Arc< + std::sync::Mutex<tun_provider::TunProvider>, + >, + #[cfg(not(feature = "boringtun"))] route_manager: talpid_routing::RouteManagerHandle, setup_done_tx: mpsc::Sender<std::result::Result<(), BoxedError>>, + userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); - let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || config.daita; - if userspace_wireguard { log::debug!("Using userspace WireGuard implementation"); + #[cfg(feature = "boringtun")] let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?; + + #[cfg(not(feature = "boringtun"))] + let tunnel = runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( config, - log_path, + _log_path, setup_done_tx, route_manager, )) @@ -660,7 +701,7 @@ impl WireguardMonitor { } else { log::debug!("Using kernel WireGuard implementation"); - wireguard_nt::WgNtTunnel::start_tunnel(config, log_path, resource_dir, setup_done_tx) + wireguard_nt::WgNtTunnel::start_tunnel(config, _log_path, resource_dir, setup_done_tx) .map(|tun| Box::new(tun) as Box<dyn Tunnel + 'static>) .map_err(Error::TunnelError) } @@ -670,20 +711,27 @@ impl WireguardMonitor { fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, - tun_provider: Arc<Mutex<TunProvider>>, + tun_provider: Arc<std::sync::Mutex<tun_provider::TunProvider>>, + _userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); log::debug!("Using userspace WireGuard implementation"); + #[cfg(not(feature = "boringtun"))] let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( + .block_on(wireguard_go::open_wireguard_go_tunnel( config, - log_path, + _log_path, tun_provider, )) .map(Box::new)?; + + #[cfg(feature = "boringtun")] + let tunnel = runtime + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?; Ok(tunnel) } @@ -691,22 +739,22 @@ impl WireguardMonitor { fn open_tunnel( runtime: tokio::runtime::Handle, config: &Config, - log_path: Option<&Path>, - tun_provider: Arc<Mutex<TunProvider>>, + tun_provider: Arc<std::sync::Mutex<tun_provider::TunProvider>>, + userspace_wireguard: bool, + _log_path: Option<&Path>, ) -> Result<TunnelType> { log::debug!("Tunnel MTU: {}", config.mtu); - let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || config.daita; if userspace_wireguard { log::debug!("Using userspace WireGuard implementation"); - let tunnel = runtime - .block_on(Self::open_wireguard_go_tunnel( - config, - log_path, - tun_provider, - )) - .map(Box::new)?; + #[cfg(not(feature = "boringtun"))] + let f = wireguard_go::open_wireguard_go_tunnel(config, _log_path, tun_provider); + + #[cfg(feature = "boringtun")] + let f = boringtun::open_boringtun_tunnel(config, tun_provider); + + let tunnel = runtime.block_on(f).map(Box::new)?; Ok(tunnel) } else { let res = if will_nm_manage_dns() { @@ -721,81 +769,27 @@ impl WireguardMonitor { res.or_else(|err| { log::warn!("Failed to initialize kernel WireGuard tunnel, falling back to userspace WireGuard implementation:\n{}",err.display_chain() ); - Ok(runtime - .block_on(Self::open_wireguard_go_tunnel( - config, - log_path, - tun_provider, - )) - .map(Box::new)?) + + #[cfg(not(feature = "boringtun"))] + { + Ok(runtime + .block_on(wireguard_go::open_wireguard_go_tunnel( + config, + _log_path, + tun_provider, + )) + .map(Box::new)?) + } + #[cfg(feature = "boringtun")] + { + Ok(runtime + .block_on(boringtun::open_boringtun_tunnel(config, tun_provider)) + .map(Box::new)?) + } }) } } - /// Configure and start a Wireguard-go tunnel. - #[cfg(wireguard_go)] - #[allow(clippy::unused_async)] - async fn open_wireguard_go_tunnel( - config: &Config, - log_path: Option<&Path>, - #[cfg(unix)] tun_provider: Arc<Mutex<TunProvider>>, - #[cfg(target_os = "android")] route_manager: RouteManagerHandle, - #[cfg(windows)] setup_done_tx: mpsc::Sender<std::result::Result<(), BoxedError>>, - #[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle, - #[cfg(target_os = "android")] gateway_only: bool, - #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, - ) -> Result<WgGoTunnel> { - #[cfg(all(unix, not(target_os = "android")))] - let routes = config.get_tunnel_destinations(); - - #[cfg(all(unix, not(target_os = "android")))] - let tunnel = WgGoTunnel::start_tunnel(config, log_path, tun_provider, routes) - .map_err(Error::TunnelError)?; - - #[cfg(target_os = "windows")] - let tunnel = WgGoTunnel::start_tunnel(config, log_path, route_manager, setup_done_tx) - .await - .map_err(Error::TunnelError)?; - - // Android uses multihop implemented in Mullvad's wireguard-go fork. When negotiating - // with an ephemeral peer, this multihop strategy require us to restart the tunnel - // every time we want to reconfigure it. As such, we will actually start a multihop - // tunnel at a later stage, after we have negotiated with the first ephemeral peer. - // At this point, when the tunnel *is first started*, we establish a regular, singlehop - // tunnel to where the ephemeral peer resides. - // - // Refer to `docs/architecture.md` for details on how to use multihop + PQ. - #[cfg(target_os = "android")] - let config = Self::patch_allowed_ips(config, gateway_only); - - #[cfg(target_os = "android")] - let tunnel = if let Some(exit_peer) = &config.exit_peer { - WgGoTunnel::start_multihop_tunnel( - &config, - exit_peer, - log_path, - tun_provider, - route_manager, - cancel_receiver, - ) - .await - .map_err(Error::TunnelError)? - } else { - WgGoTunnel::start_tunnel( - #[allow(clippy::needless_borrow)] - &config, - log_path, - tun_provider, - route_manager, - cancel_receiver, - ) - .await - .map_err(Error::TunnelError)? - }; - - Ok(tunnel) - } - /// Blocks the current thread until tunnel disconnects pub fn wait(mut self) -> Result<()> { let wait_result = match self.close_msg_receiver.recv() { @@ -837,7 +831,9 @@ impl WireguardMonitor { /// Returns routes to the peer endpoints (through the physical interface). #[cfg_attr(target_os = "linux", allow(unused_variables))] #[cfg(not(target_os = "android"))] - fn get_endpoint_routes(endpoints: &[IpAddr]) -> impl Iterator<Item = RequiredRoute> + '_ { + fn get_endpoint_routes( + endpoints: &[std::net::IpAddr], + ) -> impl Iterator<Item = RequiredRoute> + '_ { #[cfg(target_os = "linux")] { // No need due to policy based routing. @@ -1065,15 +1061,9 @@ pub enum TunnelError { #[error("Failed to duplicate tunnel file descriptor for wireguard-go")] FdDuplicationError(#[source] nix::Error), - /// Failed to setup a tunnel device. - #[cfg(not(windows))] - #[error("Failed to create tunnel device")] - SetupTunnelDevice(#[source] tun_provider::Error), - /// Failed to set up a tunnel device - #[cfg(windows)] - #[error("Failed to create tunnel device")] - SetupTunnelDevice(#[source] io::Error), + #[error("Failed to setup a tunnel device")] + SetupTunnelDevice(#[source] tun_provider::Error), /// Failed to setup a tunnel device. #[cfg(windows)] @@ -1095,6 +1085,7 @@ pub enum TunnelError { InvalidAlias, /// Failure to set up logging + #[cfg(any(windows, not(feature = "boringtun")))] #[error("Failed to set up logging")] LoggingError(#[source] logging::Error), @@ -1107,6 +1098,11 @@ pub enum TunnelError { #[cfg(daita)] #[error("Failed to start DAITA - tunnel implemenation does not support DAITA")] DaitaNotSupported, + + /// BoringTun device error + #[cfg(feature = "boringtun")] + #[error("Boringtun: {0:?}")] + BoringTunDevice(::boringtun::device::Error), } #[cfg(target_os = "linux")] diff --git a/talpid-wireguard/src/logging.rs b/talpid-wireguard/src/logging.rs index dcb33b11e3..61000e7f37 100644 --- a/talpid-wireguard/src/logging.rs +++ b/talpid-wireguard/src/logging.rs @@ -1,3 +1,4 @@ +#![cfg(any(windows, not(feature = "boringtun")))] use parking_lot::Mutex; use std::{collections::HashMap, fmt, fs, io::Write, path::Path, sync::LazyLock}; @@ -44,12 +45,12 @@ pub fn clean_up_logging(ordinal: u64) { state.map.remove(&ordinal); } +#[allow(dead_code)] pub enum LogLevel { - #[cfg_attr(windows, allow(dead_code))] Verbose, - #[cfg_attr(wireguard_go, allow(dead_code))] + #[cfg_attr(not(feature = "boringtun"), allow(dead_code))] Info, - #[cfg_attr(wireguard_go, allow(dead_code))] + #[cfg_attr(not(feature = "boringtun"), allow(dead_code))] Warning, Error, } diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index cf99b8c50e..ced16ab9d3 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -13,6 +13,8 @@ use crate::connectivity; use crate::logging::{clean_up_logging, initialize_logging}; #[cfg(all(unix, not(target_os = "android")))] use ipnetwork::IpNetwork; +#[cfg(target_os = "android")] +use std::borrow::Cow; #[cfg(daita)] use std::ffi::CString; #[cfg(unix)] @@ -67,105 +69,191 @@ impl Drop for LoggingContext { } } -#[cfg(not(target_os = "android"))] -pub struct WgGoTunnel(WgGoTunnelState); +pub struct WgGoTunnel { + // This should never be [None] _unless_ we have just called [Self::stop] and + // we're restarting the tunnel. + inner: Option<WgGoTunnelState>, + #[cfg(target_os = "android")] + r#type: Circuit, +} #[cfg(target_os = "android")] -pub enum WgGoTunnel { - Multihop(WgGoTunnelState), - Singlehop(WgGoTunnelState), +#[derive(Clone, Copy, Debug)] +enum Circuit { + Singlehop, + Multihop, } -#[cfg(not(target_os = "android"))] -impl WgGoTunnel { - fn into_state(self) -> WgGoTunnelState { - self.0 - } +/// Configure and start a Wireguard-go tunnel. +#[allow(clippy::unused_async)] +pub(crate) async fn open_wireguard_go_tunnel( + config: &Config, + log_path: Option<&Path>, + #[cfg(unix)] tun_provider: Arc<std::sync::Mutex<talpid_tunnel::tun_provider::TunProvider>>, + #[cfg(target_os = "android")] route_manager: RouteManagerHandle, + #[cfg(windows)] setup_done_tx: futures::channel::mpsc::Sender< + std::result::Result<(), BoxedError>, + >, + #[cfg(windows)] route_manager: talpid_routing::RouteManagerHandle, + #[cfg(target_os = "android")] gateway_only: bool, + #[cfg(target_os = "android")] cancel_receiver: connectivity::CancelReceiver, +) -> Result<WgGoTunnel> { + #[cfg(all(unix, not(target_os = "android")))] + let routes = config.get_tunnel_destinations(); - fn as_state(&self) -> &WgGoTunnelState { - &self.0 - } + #[cfg(all(unix, not(target_os = "android")))] + let tunnel = WgGoTunnel::start_tunnel(config, log_path, tun_provider, routes)?; - fn as_state_mut(&mut self) -> &mut WgGoTunnelState { - &mut self.0 - } + #[cfg(target_os = "windows")] + let tunnel = WgGoTunnel::start_tunnel(config, log_path, route_manager, setup_done_tx).await?; + + // Android uses multihop implemented in Mullvad's wireguard-go fork. When negotiating + // with an ephemeral peer, this multihop strategy require us to restart the tunnel + // every time we want to reconfigure it. As such, we will actually start a multihop + // tunnel at a later stage, after we have negotiated with the first ephemeral peer. + // At this point, when the tunnel *is first started*, we establish a regular, singlehop + // tunnel to where the ephemeral peer resides. + // + // Refer to `docs/architecture.md` for details on how to use multihop + PQ. + #[cfg(target_os = "android")] + let config = patch_allowed_ips(config, gateway_only); + + #[cfg(target_os = "android")] + let tunnel = if let Some(exit_peer) = &config.exit_peer { + WgGoTunnel::start_multihop_tunnel( + &config, + exit_peer, + log_path, + tun_provider, + route_manager, + cancel_receiver, + ) + .await? + } else { + WgGoTunnel::start_tunnel( + #[allow(clippy::needless_borrow)] + &config, + log_path, + tun_provider, + route_manager, + cancel_receiver, + ) + .await? + }; + + Ok(tunnel) } +/// Replace `0.0.0.0/0`/`::/0` with the gateway IPs when `gateway_only` is true. +/// Used to block traffic to other destinations while connecting on Android. #[cfg(target_os = "android")] -impl WgGoTunnel { - fn into_state(self) -> WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, +fn patch_allowed_ips(config: &Config, gateway_only: bool) -> Cow<'_, Config> { + use std::net::IpAddr; + + if gateway_only { + let mut patched_config = config.clone(); + let gateway_net_v4 = + ipnetwork::IpNetwork::from(std::net::IpAddr::from(config.ipv4_gateway)); + let gateway_net_v6 = config + .ipv6_gateway + .map(|net| ipnetwork::IpNetwork::from(IpAddr::from(net))); + for peer in patched_config.peers_mut() { + peer.allowed_ips = peer + .allowed_ips + .iter() + .cloned() + .filter_map(|mut allowed_ip| { + if allowed_ip.prefix() == 0 { + if allowed_ip.is_ipv4() { + allowed_ip = gateway_net_v4; + } else if let Some(net) = gateway_net_v6 { + allowed_ip = net; + } else { + return None; + } + } + Some(allowed_ip) + }) + .collect(); } + Cow::Owned(patched_config) + } else { + Cow::Borrowed(config) } +} - fn as_state(&self) -> &WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, - } +impl WgGoTunnel { + fn handle(&self) -> &WgGoTunnelState { + debug_assert!(&self.inner.is_some()); + self.inner.as_ref().unwrap() + } + + fn handle_mut(&mut self) -> &mut WgGoTunnelState { + debug_assert!(&self.inner.is_some()); + self.inner.as_mut().unwrap() } - fn as_state_mut(&mut self) -> &mut WgGoTunnelState { - match self { - WgGoTunnel::Multihop(state) => state, - WgGoTunnel::Singlehop(state) => state, + fn stop(&mut self) -> Result<()> { + if let Some(tunnel) = self.inner.take() { + tunnel + .tunnel_handle + .turn_off() + .map_err(|e| TunnelError::StopWireguardError(Box::new(e)))?; } + Ok(()) + } + + #[cfg(not(target_os = "android"))] + #[allow(clippy::unused_async)] + async fn set_config(&mut self, config: Config) -> Result<()> { + self.handle_mut().set_config(config) } - pub async fn set_config(self, config: &Config) -> Result<Self> { - let state = self.as_state(); - let log_path = state._logging_context.path.clone(); - let cancel_receiver = state.cancel_receiver.clone(); - let tun_provider = Arc::clone(&state.tun_provider); - let route_manager = state.route_manager.clone(); + #[cfg(target_os = "android")] + pub async fn set_config(&mut self, config: Config) -> Result<()> { + let log_path = self.handle()._logging_context.path.clone(); + let cancel_receiver = self.handle().cancel_receiver.clone(); + let tun_provider = Arc::clone(&self.handle().tun_provider); + let route_manager = self.handle().route_manager.clone(); - match self { - WgGoTunnel::Multihop(state) if !config.is_multihop() => { - state.stop()?; - Self::start_tunnel( - config, + match self.r#type { + Circuit::Multihop if !config.is_multihop() => { + self.stop()?; + *self = Self::start_tunnel( + &config, log_path.as_deref(), tun_provider, route_manager, cancel_receiver, ) - .await + .await?; } - WgGoTunnel::Singlehop(state) if config.is_multihop() => { - state.stop()?; - Self::start_multihop_tunnel( - config, + Circuit::Singlehop if config.is_multihop() => { + self.stop()?; + *self = Self::start_multihop_tunnel( + &config, &config.exit_peer.clone().unwrap().clone(), log_path.as_deref(), tun_provider, route_manager, cancel_receiver, ) - .await + .await?; } - WgGoTunnel::Singlehop(mut state) => { - state.set_config(config.clone())?; - let new_state = WgGoTunnel::Singlehop(state); + Circuit::Singlehop => { + self.handle_mut().set_config(config)?; // HACK: Check if the tunnel is working by sending a ping in the tunnel. // This check is needed for PQ connections to be established. - new_state.ensure_tunnel_is_running().await?; - Ok(new_state) + self.ensure_tunnel_is_running().await?; } - WgGoTunnel::Multihop(mut state) => { - state.set_config(config.clone())?; - let new_state = WgGoTunnel::Multihop(state); + Circuit::Multihop => { + self.handle_mut().set_config(config)?; // HACK: Check if the tunnel is working by sending a ping in the tunnel. // This check is needed for PQ connections to be established. - new_state.ensure_tunnel_is_running().await?; - Ok(new_state) + self.ensure_tunnel_is_running().await?; } - } - } - - pub fn stop(self) -> Result<()> { - self.into_state().stop() + }; + Ok(()) } } @@ -194,12 +282,6 @@ pub(crate) struct WgGoTunnelState { } impl WgGoTunnelState { - fn stop(self) -> Result<()> { - self.tunnel_handle - .turn_off() - .map_err(|e| TunnelError::StopWireguardError(Box::new(e))) - } - fn set_config(&mut self, config: Config) -> Result<()> { let wg_config_str = config.to_userspace_format(); @@ -231,7 +313,7 @@ impl WgGoTunnelState { impl WgGoTunnel { #[cfg(any(target_os = "linux", target_os = "macos"))] - pub fn start_tunnel( + fn start_tunnel( config: &Config, log_path: Option<&Path>, tun_provider: Arc<Mutex<TunProvider>>, @@ -258,14 +340,18 @@ impl WgGoTunnel { ) .map_err(|e| TunnelError::FatalStartWireguardError(Box::new(e)))?; - Ok(WgGoTunnel(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, _logging_context: logging_context, #[cfg(daita)] config: config.clone(), - })) + }; + + Ok(WgGoTunnel { + inner: Some(tunnel), + }) } #[cfg(target_os = "windows")] @@ -329,14 +415,16 @@ impl WgGoTunnel { let interface_name = handle.name(); - Ok(WgGoTunnel(WgGoTunnelState { - interface_name: interface_name.to_owned(), - tunnel_handle: handle, - _logging_context: logging_context, - _socket_update_cb: socket_update_cb, - #[cfg(daita)] - config: config.clone(), - })) + Ok(WgGoTunnel { + inner: Some(WgGoTunnelState { + interface_name: interface_name.to_owned(), + tunnel_handle: handle, + _logging_context: logging_context, + _socket_update_cb: socket_update_cb, + #[cfg(daita)] + config: config.clone(), + }), + }) } // Callback to be used to rebind the tunnel sockets when the default route changes @@ -367,6 +455,7 @@ impl WgGoTunnel { #[cfg(target_os = "linux")] { tun_config.name = Some(MULLVAD_INTERFACE_NAME.to_string()); + tun_config.packet_information = true; } tun_config.addresses = config.tunnel.addresses.clone(); tun_config.ipv4_gateway = config.ipv4_gateway; @@ -449,7 +538,7 @@ impl WgGoTunnel { Self::bypass_tunnel_sockets(&handle, &mut tunnel_device) .map_err(TunnelError::BypassError)?; - let tunnel = WgGoTunnel::Singlehop(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, @@ -459,7 +548,11 @@ impl WgGoTunnel { #[cfg(daita)] config: config.clone(), cancel_receiver, - }); + }; + let tunnel = Self { + inner: Some(tunnel), + r#type: Circuit::Singlehop, + }; if is_new_tunnel { tunnel.wait_for_routes().await?; @@ -527,7 +620,7 @@ impl WgGoTunnel { Self::bypass_tunnel_sockets(&handle, &mut tunnel_device) .map_err(TunnelError::BypassError)?; - let tunnel = WgGoTunnel::Multihop(WgGoTunnelState { + let tunnel = WgGoTunnelState { interface_name, tunnel_handle: handle, _tunnel_device: tunnel_device, @@ -537,7 +630,12 @@ impl WgGoTunnel { #[cfg(daita)] config: config.clone(), cancel_receiver: cancel_receiver.clone(), - }); + }; + + let tunnel = Self { + inner: Some(tunnel), + r#type: Circuit::Multihop, + }; if is_new_tunnel { tunnel.wait_for_routes().await?; @@ -569,12 +667,10 @@ impl WgGoTunnel { /// There is a brief period of time between setting up a Wireguard-go tunnel and the tunnel being ready to serve /// traffic. This function blocks until the tunnel starts to serve traffic or until [connectivity::Check] times out. async fn wait_for_routes(&self) -> Result<()> { - let state = self.as_state(); - - let expected_routes = state.tun_provider.lock().unwrap().real_routes(); + let expected_routes = self.handle().tun_provider.lock().unwrap().real_routes(); // Wait for routes to come up - state + self.handle() .route_manager .clone() .wait_for_routes(expected_routes) @@ -585,9 +681,8 @@ impl WgGoTunnel { Ok(()) } async fn ensure_tunnel_is_running(&self) -> Result<()> { - let state = self.as_state(); - let addr = state.config.ipv4_gateway; - let cancel_receiver = state.cancel_receiver.clone(); + let addr = self.handle().config.ipv4_gateway; + let cancel_receiver = self.handle().cancel_receiver.clone(); let mut check = connectivity::Check::new(addr, 0, cancel_receiver) .map_err(|err| TunnelError::RecoverableStartWireguardError(Box::new(err)))?; @@ -612,16 +707,17 @@ impl WgGoTunnel { #[async_trait::async_trait] impl Tunnel for WgGoTunnel { fn get_interface_name(&self) -> String { - self.as_state().interface_name.clone() + self.handle().interface_name.clone() } - fn stop(self: Box<Self>) -> Result<()> { - self.into_state().stop() + fn stop(mut self: Box<Self>) -> Result<()> { + WgGoTunnel::stop(&mut self)?; + Ok(()) } async fn get_tunnel_stats(&self) -> Result<StatsMap> { // NOTE: wireguard-go might perform blocking I/O, but it's most likely not a problem - self.as_state() + self.handle() .tunnel_handle .get_config(|cstr| { Stats::parse_config_str(cstr.to_str().expect("Go strings are always UTF-8")) @@ -634,20 +730,19 @@ impl Tunnel for WgGoTunnel { &mut self, config: Config, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> { - Box::pin(async move { self.as_state_mut().set_config(config) }) + Box::pin(async move { self.set_config(config).await }) } #[cfg(daita)] fn start_daita(&mut self, settings: DaitaSettings) -> Result<()> { log::info!("Initializing DAITA for wireguard device"); - let config = &self.as_state().config; - let peer_public_key = &config.entry_peer.public_key; + let peer_public_key = self.handle().config.entry_peer.public_key.clone(); let machines = settings.client_machines.join("\n"); let machines = CString::new(machines).map_err(|err| TunnelError::StartDaita(Box::new(err)))?; - self.as_state() + self.handle() .tunnel_handle .activate_daita( peer_public_key.as_bytes(), diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index baac2ddd69..43eb548491 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -440,7 +440,9 @@ impl WgNtTunnel { ); match error { - Error::CreateTunnelDevice(error) => super::TunnelError::SetupTunnelDevice(error), + Error::CreateTunnelDevice(error) => super::TunnelError::SetupTunnelDevice( + talpid_tunnel::tun_provider::Error::Io(error), + ), _ => super::TunnelError::FatalStartWireguardError(Box::new(error)), } }) |
