summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2025-10-24 13:47:14 +0200
committerMarkus Pettersson <markus.pettersson@mullvad.net>2025-10-24 14:11:11 +0200
commit2351c1c5e827004e28db637ebcb27f7fa1448826 (patch)
tree4a79451988a04b6d2a584549708d6538efe786e2
parent4b6c2abefdf8639d138aac61da9675a5d6ce4ebf (diff)
downloadmullvadvpn-2351c1c5e827004e28db637ebcb27f7fa1448826.tar.xz
mullvadvpn-2351c1c5e827004e28db637ebcb27f7fa1448826.zip
Integrate GotaTun cleanly
Refactor `trait Tunnel` and factor `start_daita` into `set_config`. Co-authored-by: Joakim Hulthe <joakim.hulthe@mullvad.net>
-rw-r--r--talpid-core/src/tunnel/mod.rs4
-rw-r--r--talpid-routing/src/lib.rs2
-rw-r--r--talpid-routing/src/unix/mod.rs12
-rw-r--r--talpid-tunnel/src/lib.rs4
-rw-r--r--talpid-wireguard/src/boringtun/mod.rs254
-rw-r--r--talpid-wireguard/src/connectivity/mock.rs10
-rw-r--r--talpid-wireguard/src/ephemeral.rs27
-rw-r--r--talpid-wireguard/src/lib.rs90
-rw-r--r--talpid-wireguard/src/wireguard_go/mod.rs44
-rw-r--r--talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs11
-rw-r--r--talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs11
-rw-r--r--talpid-wireguard/src/wireguard_nt/mod.rs5
12 files changed, 287 insertions, 187 deletions
diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs
index a23f45e097..96bd504412 100644
--- a/talpid-core/src/tunnel/mod.rs
+++ b/talpid-core/src/tunnel/mod.rs
@@ -46,10 +46,6 @@ pub enum Error {
/// There was an error listening for events from the Wireguard tunnel
#[error("Failed while listening for events from the Wireguard tunnel")]
WireguardTunnelMonitoringError(#[from] talpid_wireguard::Error),
-
- /// Could not detect and assign the correct mtu
- #[error("Could not detect and assign a correct MTU for the Wireguard tunnel")]
- AssignMtuError,
}
impl From<Error> for ErrorStateCause {
diff --git a/talpid-routing/src/lib.rs b/talpid-routing/src/lib.rs
index 22ba95fadf..5a63d1d20d 100644
--- a/talpid-routing/src/lib.rs
+++ b/talpid-routing/src/lib.rs
@@ -190,7 +190,7 @@ impl RequiredRoute {
/// Set route MTU to the given value.
#[cfg(any(target_os = "linux", target_os = "macos"))]
- pub fn mtu(mut self, mtu: u16) -> Self {
+ pub fn with_mtu(mut self, mtu: u16) -> Self {
self.mtu = Some(mtu);
self
}
diff --git a/talpid-routing/src/unix/mod.rs b/talpid-routing/src/unix/mod.rs
index a3c669db4c..55068ccf57 100644
--- a/talpid-routing/src/unix/mod.rs
+++ b/talpid-routing/src/unix/mod.rs
@@ -377,7 +377,17 @@ impl RouteManagerHandle {
.map_err(Error::PlatformError)
}
- /// Listen for route changes.
+ /// Get the link-MTU of the route to `ip`.
+ ///
+ /// 1. Get the route to `ip`
+ /// 2. Get the link associated with that route
+ /// 3. Get the MTU of that link
+ ///
+ /// Or, expressed in sh:
+ /// ```sh
+ /// ip route get 127.0.0.1 | grep -o 'dev [a-z]*' # outputs "dev lo"
+ /// ip link show dev lo | grep -o 'mtu [0-9]*' # outputs "mtu 65536"
+ /// ```
#[cfg(target_os = "linux")]
pub async fn get_mtu_for_route(&self, ip: IpAddr) -> Result<u16, Error> {
let (response_tx, response_rx) = oneshot::channel();
diff --git a/talpid-tunnel/src/lib.rs b/talpid-tunnel/src/lib.rs
index ab5c291e56..5eca4c9dbd 100644
--- a/talpid-tunnel/src/lib.rs
+++ b/talpid-tunnel/src/lib.rs
@@ -24,8 +24,8 @@ use tun_provider::TunProvider;
pub const IPV4_HEADER_SIZE: u16 = 20;
/// Size of IPv6 header in bytes
pub const IPV6_HEADER_SIZE: u16 = 40;
-/// Size of wireguard header in bytes
-pub const WIREGUARD_HEADER_SIZE: u16 = 40;
+/// WireGuard overhead. Size of UDP header, plus header and footer of a WireGuard data packet.
+pub const WIREGUARD_HEADER_SIZE: u16 = 8 + 32;
/// Size of ICMP header in bytes
pub const ICMP_HEADER_SIZE: u16 = 8;
/// Smallest allowed MTU for IPv4 in bytes
diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs
index 48794cfc49..ef3ce5116d 100644
--- a/talpid-wireguard/src/boringtun/mod.rs
+++ b/talpid-wireguard/src/boringtun/mod.rs
@@ -13,9 +13,15 @@ use boringtun::{
api::{ApiClient, ApiServer, command::*},
peer::AllowedIP,
},
+ packet::{Ipv4Header, Ipv6Header, UdpHeader, WgData},
+ tun::{
+ IpRecv,
+ channel::{TunChannelRx, TunChannelTx},
+ tun_async_device::TunDevice as GotaTunDevice,
+ },
udp::{
- UdpSocketFactory,
- channel::{PacketChannelUdp, TunChannelRx, TunChannelTx, get_packet_channels},
+ channel::{UdpChannelFactory, new_udp_tun_channel},
+ socket::UdpSocketFactory,
},
};
#[cfg(not(target_os = "android"))]
@@ -35,7 +41,7 @@ use tun07::{AbstractDevice, AsyncDevice};
#[cfg(all(feature = "multihop-pcap", target_os = "linux"))]
use boringtun::tun::{
- IpRecv, IpSend,
+ IpSend,
pcap::{PcapSniffer, PcapStream},
};
@@ -45,8 +51,8 @@ type UdpFactory = AndroidUdpSocketFactory;
#[cfg(not(target_os = "android"))]
type UdpFactory = UdpSocketFactory;
-type SinglehopDevice = DeviceHandle<(UdpFactory, Arc<tun07::AsyncDevice>, Arc<tun07::AsyncDevice>)>;
-type ExitDevice = DeviceHandle<(PacketChannelUdp, Arc<AsyncDevice>, Arc<AsyncDevice>)>;
+type SinglehopDevice = DeviceHandle<(UdpFactory, GotaTunDevice)>;
+type ExitDevice = DeviceHandle<(UdpChannelFactory, GotaTunDevice)>;
#[cfg(not(all(feature = "multihop-pcap", target_os = "linux")))]
type EntryDevice = DeviceHandle<(UdpFactory, TunChannelTx, TunChannelRx)>;
@@ -64,7 +70,7 @@ pub struct BoringTun {
// TODO: Can we not store this in an option?
devices: Option<Devices>,
- tun: Arc<AsyncDevice>,
+ tun_dev: GotaTunDevice,
#[cfg(target_os = "android")]
android_tun: Arc<Tun>,
@@ -78,22 +84,27 @@ pub struct BoringTun {
impl BoringTun {
async fn new(
- tun: Arc<AsyncDevice>,
+ tun_dev: AsyncDevice,
#[cfg(target_os = "android")] android_tun: Arc<Tun>,
config: Config,
interface_name: String,
) -> Result<Self, TunnelError> {
+ let tun_dev = GotaTunDevice::from_tun_device(tun_dev)
+ .map_err(|e| TunnelError::RecoverableStartWireguardError(Box::new(e)))?;
+
let devices = create_devices(
&config,
- tun.clone(),
+ None,
+ tun_dev.clone(),
#[cfg(target_os = "android")]
android_tun.clone(),
)
.await?;
+
Ok(Self {
config,
interface_name,
- tun,
+ tun_dev,
#[cfg(target_os = "android")]
android_tun,
devices: Some(devices),
@@ -169,7 +180,7 @@ pub async fn open_boringtun_tunnel(
log::info!("BoringTun::start_tunnel");
let routes = config.get_tunnel_destinations();
- log::info!("calling get_tunnel_for_userspace");
+ log::trace!("calling get_tunnel_for_userspace");
#[cfg(not(target_os = "android"))]
let async_tun = {
let tun = get_tunnel_for_userspace(tun_provider, config, routes)?;
@@ -215,9 +226,6 @@ pub async fn open_boringtun_tunnel(
let interface_name = async_tun.deref().tun_name().unwrap();
- log::info!("passing tunnel dev to boringtun");
- let async_tun = Arc::new(async_tun);
-
let config = config.clone();
#[cfg(target_os = "android")]
let config = match gateway_only {
@@ -226,6 +234,7 @@ pub async fn open_boringtun_tunnel(
false => config,
};
+ log::trace!("passing tunnel dev to boringtun");
let boringtun = BoringTun::new(
async_tun,
#[cfg(target_os = "android")]
@@ -248,18 +257,29 @@ pub async fn open_boringtun_tunnel(
Ok(boringtun)
}
+/// Create and configure boringtun devices.
+///
+/// Will create an [EntryDevice] and an [ExitDevice] if `config` is a multihop config,
+/// and a [SinglehopDevice] otherwise.
async fn create_devices(
- config: &Config,
- async_tun: Arc<AsyncDevice>,
- #[cfg(target_os = "android")] tun: Arc<Tun>,
+ config: &Config, // TODO: do not include config to reduce confusion
+ daita: Option<&DaitaSettings>,
+ tun_dev: GotaTunDevice,
+ #[cfg(target_os = "android")] android_tun: Arc<Tun>,
) -> Result<Devices, TunnelError> {
let (entry_api, entry_api_server) = ApiServer::new();
let boringtun_entry_config = DeviceConfig {
api: Some(entry_api_server),
};
- if let Some(exit_peer) = &config.exit_peer {
- // multihop
+ #[cfg(target_os = "android")]
+ let udp_factory = AndroidUdpSocketFactory { tun: android_tun };
+
+ #[cfg(not(target_os = "android"))]
+ let udp_factory = UdpSocketFactory;
+
+ let mut devices = if let Some(exit_peer) = &config.exit_peer {
+ // Multihop setup
let source_v4 = config
.tunnel
@@ -281,32 +301,95 @@ async fn create_devices(
})
.unwrap_or(Ipv6Addr::UNSPECIFIED);
- let (tun_tx, tun_rx, udp_channels) =
- get_packet_channels(PACKET_CHANNEL_CAPACITY, source_v4, source_v6);
+ // Calculate length of extra headers, assuming no optional header fields (i.e. IP options)
+ let multihop_overhead = match exit_peer.endpoint.ip() {
+ IpAddr::V4(..) => Ipv4Header::LEN + UdpHeader::LEN + WgData::OVERHEAD,
+ IpAddr::V6(..) => Ipv6Header::LEN + UdpHeader::LEN + WgData::OVERHEAD,
+ };
+
+ let exit_mtu = tun_dev.mtu();
+ let entry_mtu = exit_mtu.increase(multihop_overhead as u16).unwrap(/* TODO: this can happen if tun mtu is max i think*/);
+
+ let (tun_channel_tx, tun_channel_rx, udp_channels) =
+ new_udp_tun_channel(PACKET_CHANNEL_CAPACITY, source_v4, source_v6, entry_mtu);
let (exit_api, exit_api_server) = ApiServer::new();
let exit_device = ExitDevice::new(
udp_channels,
- async_tun.clone(),
- async_tun,
+ tun_dev.clone(),
+ tun_dev,
DeviceConfig {
api: Some(exit_api_server),
},
)
.await;
- #[cfg(target_os = "android")]
- let factory = AndroidUdpSocketFactory { tun };
-
- #[cfg(not(target_os = "android"))]
- let factory = UdpSocketFactory;
-
// Hacky way of dumping entry<->exit traffic to a unix socket which wireshark can read.
// See docs on wrap_in_pcap_sniffer for an explanation.
#[cfg(all(feature = "multihop-pcap", target_os = "linux"))]
- let (tun_tx, tun_rx) = wrap_in_pcap_sniffer(tun_tx, tun_rx);
+ let (tun_channel_tx, tun_channel_rx) = wrap_in_pcap_sniffer(tun_channel_tx, tun_channel_rx);
+
+ let entry_device = EntryDevice::new(
+ udp_factory,
+ tun_channel_tx,
+ tun_channel_rx,
+ boringtun_entry_config,
+ )
+ .await;
+
+ Devices::Multihop {
+ entry_device,
+ entry_api,
+ exit_device,
+ exit_api,
+ }
+ } else {
+ // Singlehop setup
+
+ let device = SinglehopDevice::new(
+ udp_factory,
+ tun_dev.clone(),
+ tun_dev,
+ boringtun_entry_config,
+ )
+ .await;
- let entry_device = EntryDevice::new(factory, tun_tx, tun_rx, boringtun_entry_config).await;
+ Devices::Singlehop {
+ device,
+ api: entry_api,
+ }
+ };
+
+ configure_devices(&mut devices, config, daita).await?;
+
+ Ok(devices)
+}
+
+/// (Re)Configure boringtun devices.
+///
+/// # Panic
+/// Panics if `config` is a multihop config and `devices` is [Devices::Singlehop].
+/// Panics if `config` is a singlehop config and `devices` is [Devices::Multihop].
+// TODO: don't panic
+async fn configure_devices(
+ devices: &mut Devices,
+ config: &Config,
+ daita: Option<&DaitaSettings>,
+) -> Result<(), TunnelError> {
+ if let Some(exit_peer) = &config.exit_peer {
+ log::trace!(
+ "configuring boringtun multihop device (daita={})",
+ daita.is_some()
+ );
+
+ let Devices::Multihop {
+ entry_api,
+ exit_api,
+ ..
+ } = devices
+ else {
+ panic!("Single devices were provided with a multihop config");
+ };
let private_key = &config.tunnel.private_key;
let peer = &config.entry_peer;
@@ -315,6 +398,7 @@ async fn create_devices(
config.fwmark,
private_key,
peer,
+ daita,
);
entry_api.send(set_cmd).await.map_err(|err| {
log::error!("Failed to set boringtun config: {err:#}");
@@ -326,34 +410,22 @@ async fn create_devices(
config.fwmark,
private_key,
exit_peer,
+ None, // exit peer never has daita
);
exit_api.send(set_cmd).await.map_err(|err| {
log::error!("Failed to set boringtun config: {err:#}");
TunnelError::SetConfigError
})?;
-
- Ok(Devices::Multihop {
- entry_device,
- entry_api,
- exit_device,
- exit_api,
- })
} else {
- #[cfg(target_os = "android")]
- let factory = AndroidUdpSocketFactory { tun };
-
- #[cfg(not(target_os = "android"))]
- let factory = UdpSocketFactory;
+ log::trace!(
+ "configuring boringtun singlehop device (daita={})",
+ daita.is_some()
+ );
- let device = SinglehopDevice::new(
- factory,
- async_tun.clone(),
- async_tun,
- boringtun_entry_config,
- )
- .await;
+ let Devices::Singlehop { api, .. } = devices else {
+ panic!("Multihop devices were provided with a single config");
+ };
- log::info!("configuring boringtun device");
let private_key = &config.tunnel.private_key;
let peer = &config.entry_peer;
let set_cmd = create_set_command(
@@ -361,18 +433,15 @@ async fn create_devices(
config.fwmark,
private_key,
peer,
+ daita,
);
-
- entry_api.send(set_cmd).await.map_err(|err| {
+ api.send(set_cmd).await.map_err(|err| {
log::error!("Failed to set boringtun config: {err:#}");
TunnelError::SetConfigError
})?;
-
- Ok(Devices::Singlehop {
- device,
- api: entry_api,
- })
}
+
+ Ok(())
}
#[async_trait::async_trait]
@@ -382,7 +451,6 @@ impl Tunnel for BoringTun {
}
fn stop(mut self: Box<Self>) -> Result<(), TunnelError> {
- log::info!("BoringTun::stop"); // remove me
tokio::runtime::Handle::current().block_on(async {
// TODO: devices should never be None while this BoringTun instance is running.
debug_assert!(self.devices.is_some());
@@ -437,38 +505,52 @@ impl Tunnel for BoringTun {
fn set_config<'a>(
&'a mut self,
config: Config,
+ daita: Option<DaitaSettings>,
) -> std::pin::Pin<Box<dyn Future<Output = Result<(), TunnelError>> + Send + 'a>> {
+ dbg!(&config);
Box::pin(async move {
- let _old_config = std::mem::replace(&mut self.config, config);
- // TODO: diff with _old_config to see if devices need to be recreated.
- // TODO: devices should never be None while this BoringTun instance is running.
- debug_assert!(self.devices.is_some());
- if let Some(devices) = self.devices.take() {
- devices.stop().await;
+ self.config = config;
+
+ // if we're switching to/from multihop, we'll need to tear down the old device(s)
+ // and set them up with the new DeviceTransports
+ let recreate_devices = old_config.is_multihop() != self.config.is_multihop();
+
+ if recreate_devices {
+ // TODO: devices should never be None while this BoringTun instance is running.
+ debug_assert!(self.devices.is_some());
+ if let Some(devices) = self.devices.take() {
+ devices.stop().await;
+ }
}
- self.devices = Some(
- create_devices(
- &self.config,
- self.tun.clone(),
- #[cfg(target_os = "android")]
- self.android_tun.clone(),
- )
- .await?,
- );
+
+ match &mut self.devices {
+ Some(devices) => {
+ configure_devices(devices, &self.config, daita.as_ref()).await?;
+ }
+ None => {
+ self.devices = Some(
+ create_devices(
+ &self.config,
+ daita.as_ref(),
+ self.tun_dev.clone(),
+ #[cfg(target_os = "android")]
+ self.android_tun.clone(),
+ )
+ .await?,
+ )
+ }
+ };
+
Ok(())
})
}
-
- fn start_daita(&mut self, _settings: DaitaSettings) -> Result<(), TunnelError> {
- log::info!("Haha no");
- Ok(())
- }
}
fn create_set_command(
#[cfg(target_os = "linux")] fwmark: Option<u32>,
private_key: &talpid_types::net::wireguard::PrivateKey,
peer: &talpid_types::net::wireguard::PeerConfig,
+ daita: Option<&DaitaSettings>,
) -> Set {
let mut set_cmd = Set::builder()
.private_key(private_key.to_bytes())
@@ -499,9 +581,19 @@ fn create_set_command(
boring_peer.preshared_key = Some(SetUnset::Set((*psk.as_bytes()).into()));
}
- set_cmd
- .peers
- .push(SetPeer::builder().peer(boring_peer).build());
+ let mut set_peer = SetPeer::builder().peer(boring_peer).build();
+
+ if let Some(daita) = daita {
+ let DaitaSettings {
+ client_machines,
+ max_padding_frac,
+ max_blocking_frac,
+ } = daita;
+
+ set_peer.maybenot_machines = Some(client_machines.clone());
+ }
+
+ set_cmd.peers.push(set_peer);
set_cmd
}
diff --git a/talpid-wireguard/src/connectivity/mock.rs b/talpid-wireguard/src/connectivity/mock.rs
index ef8bf4103f..5b84dc8db4 100644
--- a/talpid-wireguard/src/connectivity/mock.rs
+++ b/talpid-wireguard/src/connectivity/mock.rs
@@ -1,5 +1,6 @@
use std::future::Future;
use std::pin::Pin;
+use talpid_tunnel_config_client::DaitaSettings;
use tokio::time::Instant;
use super::Check;
@@ -117,17 +118,10 @@ impl Tunnel for MockTunnel {
fn set_config(
&mut self,
_config: Config,
+ _daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), TunnelError>> + Send>> {
Box::pin(async { Ok(()) })
}
-
- #[cfg(daita)]
- fn start_daita(
- &mut self,
- _: talpid_tunnel_config_client::DaitaSettings,
- ) -> std::result::Result<(), TunnelError> {
- Ok(())
- }
}
#[async_trait::async_trait]
diff --git a/talpid-wireguard/src/ephemeral.rs b/talpid-wireguard/src/ephemeral.rs
index 501de4fdb6..57a1fe9598 100644
--- a/talpid-wireguard/src/ephemeral.rs
+++ b/talpid-wireguard/src/ephemeral.rs
@@ -14,7 +14,7 @@ use std::{
use talpid_tunnel::tun_provider::TunProvider;
use ipnetwork::IpNetwork;
-use talpid_tunnel_config_client::EphemeralPeer;
+use talpid_tunnel_config_client::{DaitaSettings, EphemeralPeer};
use talpid_types::net::wireguard::{PrivateKey, PublicKey};
use tokio::sync::Mutex as AsyncMutex;
@@ -136,6 +136,7 @@ async fn config_ephemeral_peers_inner(
let entry_config = reconfigure_tunnel(
tunnel,
entry_tun_config,
+ None,
obfuscation_mtu,
obfuscator.clone(),
close_obfs_sender,
@@ -143,6 +144,7 @@ async fn config_ephemeral_peers_inner(
&tun_provider,
)
.await?;
+
let entry_ephemeral_peer = request_ephemeral_peer(
retry_attempt,
&entry_config,
@@ -159,6 +161,7 @@ async fn config_ephemeral_peers_inner(
config.exit_peer_mut().psk = exit_ephemeral_peer.psk;
if config.daita {
+ // NOTE: this option does nothing for GotaTun, and should be removed in future.
log::trace!("Enabling constant packet size for entry peer");
config.entry_peer.constant_packet_size = true;
}
@@ -168,6 +171,7 @@ async fn config_ephemeral_peers_inner(
*config = reconfigure_tunnel(
tunnel,
config.clone(),
+ daita,
obfuscation_mtu,
obfuscator,
close_obfs_sender,
@@ -176,21 +180,6 @@ async fn config_ephemeral_peers_inner(
)
.await?;
- if config.daita {
- let Some(daita) = daita else {
- unreachable!("missing DAITA settings");
- };
-
- // Start local DAITA machines
- let mut tunnel = tunnel.lock().await;
- if let Some(tunnel) = tunnel.as_mut() {
- tunnel
- .start_daita(daita)
- .map_err(Error::TunnelError)
- .map_err(CloseMsg::SetupError)?;
- }
- }
-
Ok(())
}
@@ -200,6 +189,7 @@ async fn config_ephemeral_peers_inner(
async fn reconfigure_tunnel(
tunnel: &Arc<AsyncMutex<Option<TunnelType>>>,
mut config: Config,
+ daita: Option<DaitaSettings>,
obfuscation_mtu: u16,
obfuscator: Arc<AsyncMutex<Option<ObfuscatorHandle>>>,
close_obfs_sender: sync_mpsc::Sender<CloseMsg>,
@@ -223,7 +213,7 @@ async fn reconfigure_tunnel(
let mut tunnel = shared_tunnel.take().expect("tunnel was None");
tunnel
- .set_config(config.clone())
+ .set_config(config.clone(), daita)
.await
.map_err(Error::TunnelError)
.map_err(CloseMsg::SetupError)?;
@@ -239,6 +229,7 @@ async fn reconfigure_tunnel(
async fn reconfigure_tunnel(
tunnel: &Arc<AsyncMutex<Option<TunnelType>>>,
mut config: Config,
+ daita: Option<DaitaSettings>,
obfuscation_mtu: u16,
obfuscator: Arc<AsyncMutex<Option<ObfuscatorHandle>>>,
close_obfs_sender: sync_mpsc::Sender<CloseMsg>,
@@ -260,7 +251,7 @@ async fn reconfigure_tunnel(
let set_config_future = tunnel
.as_mut()
- .map(|tunnel| tunnel.set_config(config.clone()));
+ .map(|tunnel| tunnel.set_config(config.clone(), daita));
if let Some(f) = set_config_future {
f.await
diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs
index 9531a1066f..3e218ebdc5 100644
--- a/talpid-wireguard/src/lib.rs
+++ b/talpid-wireguard/src/lib.rs
@@ -9,10 +9,9 @@ use futures::future::Future;
use obfuscation::ObfuscatorHandle;
#[cfg(windows)]
use std::io;
-#[cfg(not(target_os = "android"))]
-use std::net::IpAddr;
use std::{
convert::Infallible,
+ net::IpAddr,
path::Path,
pin::Pin,
sync::{Arc, mpsc as sync_mpsc},
@@ -22,6 +21,7 @@ use std::{env, sync::LazyLock};
#[cfg(not(target_os = "android"))]
use talpid_routing::{self, RequiredRoute};
use talpid_tunnel::{EventHook, TunnelArgs, TunnelEvent, TunnelMetadata, tun_provider};
+use talpid_tunnel::{IPV4_HEADER_SIZE, IPV6_HEADER_SIZE, WIREGUARD_HEADER_SIZE};
#[cfg(daita)]
use talpid_tunnel_config_client::DaitaSettings;
@@ -158,13 +158,16 @@ impl WireguardMonitor {
args: TunnelArgs<'_>,
_log_path: Option<&Path>,
) -> Result<WireguardMonitor> {
+ // NOTE: We force userspace WireGuard while boringtun is enabled to more easily test it
+ // TODO: Consider removing `cfg!(feature = "boringtun")`
+ let userspace_wireguard =
+ *FORCE_USERSPACE_WIREGUARD || params.options.daita || cfg!(feature = "boringtun");
+ let userspace_multihop = userspace_wireguard && cfg!(feature = "boringtun");
+
let route_mtu = args
.runtime
.block_on(get_route_mtu(params, &args.route_manager));
-
- let tunnel_mtu = params.options.mtu.unwrap_or_else(|| {
- clamp_tunnel_mtu(params, route_mtu.saturating_sub(wireguard_overhead(params)))
- });
+ let tunnel_mtu = calculate_tunnel_mtu(route_mtu, params, userspace_multihop);
let mut config = crate::config::Config::from_parameters(params, tunnel_mtu)
.map_err(Error::WireguardConfigError)?;
@@ -195,12 +198,6 @@ impl WireguardMonitor {
);
}
- // 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(
@@ -426,10 +423,11 @@ impl WireguardMonitor {
.runtime
.block_on(get_route_mtu(params, &args.route_manager));
- let tunnel_mtu = params.options.mtu.unwrap_or_else(|| {
- clamp_tunnel_mtu(params, route_mtu.saturating_sub(wireguard_overhead(params)))
- });
+ // TODO: previously, we didn't account for userspace multihop on android.
+ // but it seems correct to do so.
+ let userspace_multihop = true;
+ let tunnel_mtu = calculate_tunnel_mtu(route_mtu, params, userspace_multihop);
let mut config = crate::config::Config::from_parameters(params, tunnel_mtu)
.map_err(Error::WireguardConfigError)?;
@@ -984,6 +982,7 @@ impl WireguardMonitor {
config: &Config,
userspace_wireguard: bool,
) -> RequiredRoute {
+ // TODO: surely this applies to all kinds of userspace multihop, not just gotatun?
// For userspace multihop, per-route MTU is unnecessary. Packets are not sent back to
// the tunnel interface, so we're not constrained by its MTU.
let using_boringtun = userspace_wireguard && cfg!(feature = "boringtun");
@@ -991,18 +990,16 @@ impl WireguardMonitor {
if !config.is_multihop() || using_boringtun {
route
} else {
- use talpid_tunnel::{IPV4_HEADER_SIZE, IPV6_HEADER_SIZE, WIREGUARD_HEADER_SIZE};
-
+ // FIXME: this presumably refers to the fact that wireguard can pad data packet
+ // payload lengths to a multiple of 16 bytes, but that number must not
+ // according to wg spec, exceed the MTU anyway, so why do we subtract 15 here?
+ //
// Set route MTU by subtracting the WireGuard overhead from the tunnel MTU. Plus
// some margin to make room for padding bytes.
- let ip_overhead = match route.prefix.is_ipv4() {
- true => IPV4_HEADER_SIZE,
- false => IPV6_HEADER_SIZE,
- };
const PADDING_BYTES_MARGIN: u16 = 15;
- let mtu = config.mtu - ip_overhead - WIREGUARD_HEADER_SIZE - PADDING_BYTES_MARGIN;
+ let mtu = config.mtu - wireguard_overhead(route.prefix.ip()) - PADDING_BYTES_MARGIN;
- route.mtu(mtu)
+ route.with_mtu(mtu)
}
}
@@ -1059,10 +1056,8 @@ pub(crate) trait Tunnel: Send + Sync {
fn set_config<'a>(
&'a mut self,
_config: Config,
+ _daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), TunnelError>> + Send + 'a>>;
- #[cfg(daita)]
- /// A [`Tunnel`] capable of using DAITA.
- fn start_daita(&mut self, settings: DaitaSettings) -> std::result::Result<(), TunnelError>;
}
/// Errors to be returned from WireGuard implementations, namely implementers of the Tunnel trait
@@ -1173,13 +1168,12 @@ const DEFAULT_MTU: u16 = if cfg!(target_os = "android") {
1380
};
-/// Get MTU based on the physical interface route
+/// Get the link-MTU of the route to the (entry) peer.
#[cfg(any(target_os = "linux", target_os = "windows"))]
async fn get_route_mtu(
params: &TunnelParameters,
route_manager: &talpid_routing::RouteManagerHandle,
) -> u16 {
- // Get the MTU of the device/route
route_manager
.get_mtu_for_route(params.connection.peer.endpoint.ip())
.await
@@ -1197,6 +1191,28 @@ async fn get_route_mtu(
DEFAULT_MTU
}
+/// Calculate what the MTU on the tunnel link should be.
+fn calculate_tunnel_mtu(
+ link_mtu_for_peer: u16,
+ params: &TunnelParameters,
+ userspace_multihop: bool,
+) -> u16 {
+ if let Some(mtu) = params.options.mtu {
+ return mtu;
+ }
+
+ let mut overhead = wireguard_overhead(params.connection.peer.endpoint.ip());
+
+ // only reduce tunnel_mtu for *userspace* multihop.
+ // For kernel-multihop, traffic to the exit peer is routed back through the tunnel link,
+ // so the MTU on that link must be larger to account for multihop overhead
+ if userspace_multihop && let Some(exit_peer) = &params.connection.exit_peer {
+ overhead += wireguard_overhead(exit_peer.endpoint.ip());
+ }
+
+ clamp_tunnel_mtu(params, link_mtu_for_peer.saturating_sub(overhead))
+}
+
/// Clamp WireGuard tunnel MTU to reasonable values
fn clamp_tunnel_mtu(params: &TunnelParameters, mtu: u16) -> u16 {
use talpid_tunnel::{MIN_IPV4_MTU, MIN_IPV6_MTU};
@@ -1214,17 +1230,17 @@ fn clamp_tunnel_mtu(params: &TunnelParameters, mtu: u16) -> u16 {
const MTU_SAFETY_MARGIN: u16 = 60;
// The largest peer MTU that we allow
- let max_peer_mtu: u16 = 1500 - MTU_SAFETY_MARGIN - wireguard_overhead(params);
+ // TODO: userspace multihop?
+ let max_peer_mtu: u16 =
+ 1500 - MTU_SAFETY_MARGIN - wireguard_overhead(params.connection.peer.endpoint.ip());
mtu.clamp(min_mtu, max_peer_mtu)
}
-/// Calculates total overhead due to WireGuard
-const fn wireguard_overhead(params: &TunnelParameters) -> u16 {
- use talpid_tunnel::{IPV4_HEADER_SIZE, IPV6_HEADER_SIZE, WIREGUARD_HEADER_SIZE};
- WIREGUARD_HEADER_SIZE
- + match params.connection.peer.endpoint.is_ipv6() {
- false => IPV4_HEADER_SIZE,
- true => IPV6_HEADER_SIZE,
- }
+/// Calculates WireGuard per-packet overhead
+const fn wireguard_overhead(ip_version: IpAddr) -> u16 {
+ match ip_version {
+ IpAddr::V4(..) => IPV4_HEADER_SIZE + WIREGUARD_HEADER_SIZE,
+ IpAddr::V6(..) => IPV6_HEADER_SIZE + WIREGUARD_HEADER_SIZE,
+ }
}
diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs
index 55058f2a0c..3c07fce01d 100644
--- a/talpid-wireguard/src/wireguard_go/mod.rs
+++ b/talpid-wireguard/src/wireguard_go/mod.rs
@@ -690,32 +690,34 @@ impl Tunnel for WgGoTunnel {
fn set_config(
&mut self,
config: Config,
+ daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
- Box::pin(async move { self.set_config(config).await })
- }
+ 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 peer_public_key = self.handle().config.entry_peer.public_key.clone();
+ if let Some(daita) = daita {
+ log::info!("Initializing DAITA for wireguard device");
+ 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)))?;
+ let machines = daita.client_machines.join("\n");
+ let machines =
+ CString::new(machines).map_err(|err| TunnelError::StartDaita(Box::new(err)))?;
- self.handle()
- .tunnel_handle
- .activate_daita(
- peer_public_key.as_bytes(),
- &machines,
- settings.max_padding_frac,
- settings.max_blocking_frac,
- DAITA_EVENTS_CAPACITY,
- DAITA_ACTIONS_CAPACITY,
- )
- .map_err(|e| TunnelError::StartDaita(Box::new(e)))?;
+ self.handle()
+ .tunnel_handle
+ .activate_daita(
+ peer_public_key.as_bytes(),
+ &machines,
+ daita.max_padding_frac,
+ daita.max_blocking_frac,
+ DAITA_EVENTS_CAPACITY,
+ DAITA_ACTIONS_CAPACITY,
+ )
+ .map_err(|e| TunnelError::StartDaita(Box::new(e)))?;
+ }
- Ok(())
+ Ok(())
+ })
}
}
diff --git a/talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs b/talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs
index b7735b68b1..407b4cdd25 100644
--- a/talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs
+++ b/talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs
@@ -120,10 +120,16 @@ impl Tunnel for NetlinkTunnel {
fn set_config(
&mut self,
config: Config,
+ daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), TunnelError>> + Send + 'static>> {
let mut wg = self.netlink_connections.wg_handle.clone();
let interface_index = self.interface_index;
Box::pin(async move {
+ if daita.is_some() {
+ // Outright fail to start - this tunnel type does not support DAITA.
+ return Err(TunnelError::DaitaNotSupported);
+ }
+
wg.set_config(interface_index, &config)
.await
.map_err(|err| {
@@ -132,9 +138,4 @@ impl Tunnel for NetlinkTunnel {
})
})
}
-
- /// Outright fail to start - this tunnel type does not support DAITA.
- fn start_daita(&mut self, _: DaitaSettings) -> std::result::Result<(), TunnelError> {
- Err(TunnelError::DaitaNotSupported)
- }
}
diff --git a/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs b/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs
index 46a394a59d..c35503fa82 100644
--- a/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs
+++ b/talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs
@@ -97,10 +97,16 @@ impl Tunnel for NetworkManagerTunnel {
fn set_config(
&mut self,
config: Config,
+ daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), TunnelError>> + Send>> {
let interface_name = self.interface_name.clone();
let mut wg = self.netlink_connections.wg_handle.clone();
Box::pin(async move {
+ if daita.is_some() {
+ // Outright fail to start - this tunnel type does not support DAITA.
+ return Err(TunnelError::DaitaNotSupported);
+ }
+
let index = iface_index(&interface_name).map_err(|err| {
log::error!("Failed to fetch WireGuard device index: {}", err);
TunnelError::SetConfigError
@@ -111,11 +117,6 @@ impl Tunnel for NetworkManagerTunnel {
})
})
}
-
- /// Outright fail to start - this tunnel type does not support DAITA.
- fn start_daita(&mut self, _: DaitaSettings) -> std::result::Result<(), TunnelError> {
- Err(TunnelError::DaitaNotSupported)
- }
}
fn convert_config_to_dbus(config: &Config) -> DeviceConfig {
diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs
index a1472bb2ff..68e7a00fcf 100644
--- a/talpid-wireguard/src/wireguard_nt/mod.rs
+++ b/talpid-wireguard/src/wireguard_nt/mod.rs
@@ -968,6 +968,7 @@ impl Tunnel for WgNtTunnel {
fn set_config(
&mut self,
config: Config,
+ _daita: Option<DaitaSettings>,
) -> Pin<Box<dyn Future<Output = std::result::Result<(), super::TunnelError>> + Send>> {
let device = self.device.clone();
let current_config = self.config.clone();
@@ -988,10 +989,6 @@ impl Tunnel for WgNtTunnel {
})
})
}
-
- fn start_daita(&mut self, _settings: DaitaSettings) -> std::result::Result<(), TunnelError> {
- unimplemented!("DAITA is not supported on wireguard-nt")
- }
}
pub fn as_uninit_byte_slice<T: Copy + Sized>(value: &T) -> &[mem::MaybeUninit<u8>] {