summaryrefslogtreecommitdiffhomepage
path: root/talpid-wireguard
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim.hulthe@mullvad.net>2024-11-29 10:48:15 +0100
committerSebastian Holmin <sebastian.holmin@mullvad.net>2025-05-26 15:53:07 +0200
commit8f3900bb993c3a3859564fd97f052fd1cecbb37e (patch)
treea5f1644b4999086a644a8e27c3c5627a5738c2e8 /talpid-wireguard
parent9dfafb3e5031c991db0b6117c8cd6a71d86deb40 (diff)
downloadmullvadvpn-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.toml54
-rw-r--r--talpid-wireguard/build.rs3
-rw-r--r--talpid-wireguard/src/boringtun/mod.rs314
-rw-r--r--talpid-wireguard/src/connectivity/check.rs51
-rw-r--r--talpid-wireguard/src/connectivity/mod.rs2
-rw-r--r--talpid-wireguard/src/connectivity/monitor.rs31
-rw-r--r--talpid-wireguard/src/ephemeral.rs10
-rw-r--r--talpid-wireguard/src/lib.rs360
-rw-r--r--talpid-wireguard/src/logging.rs7
-rw-r--r--talpid-wireguard/src/wireguard_go/mod.rs291
-rw-r--r--talpid-wireguard/src/wireguard_nt/mod.rs4
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)),
}
})