diff options
| -rw-r--r-- | Cargo.lock | 65 | ||||
| -rw-r--r-- | talpid-core/src/tunnel/mod.rs | 4 | ||||
| -rw-r--r-- | talpid-routing/src/lib.rs | 2 | ||||
| -rw-r--r-- | talpid-routing/src/unix/mod.rs | 12 | ||||
| -rw-r--r-- | talpid-tunnel/src/lib.rs | 4 | ||||
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 4 | ||||
| -rw-r--r-- | talpid-wireguard/src/boringtun/mod.rs | 301 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/check.rs | 20 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/mock.rs | 37 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/monitor.rs | 9 | ||||
| -rw-r--r-- | talpid-wireguard/src/ephemeral.rs | 27 | ||||
| -rw-r--r-- | talpid-wireguard/src/lib.rs | 131 | ||||
| -rw-r--r-- | talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap | 2 | ||||
| -rw-r--r-- | talpid-wireguard/src/stats.rs | 23 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_go/mod.rs | 45 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_kernel/netlink_tunnel.rs | 11 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_kernel/nm_tunnel.rs | 11 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_kernel/stats.rs | 1 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_nt/mod.rs | 6 | ||||
| -rw-r--r-- | wireguard-go-rs/Cargo.toml | 2 |
20 files changed, 433 insertions, 284 deletions
diff --git a/Cargo.lock b/Cargo.lock index 7e30a542e3..c16e83be5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,7 +388,7 @@ dependencies = [ "arrayvec", "cc", "cfg-if", - "constant_time_eq", + "constant_time_eq 0.3.0", ] [[package]] @@ -440,7 +440,7 @@ dependencies = [ [[package]] name = "boringtun" version = "0.6.0" -source = "git+https://github.com/mullvad/boringtun?rev=83b3b5bb1bf3ec8b9849cc0d250d96d623b6181f#83b3b5bb1bf3ec8b9849cc0d250d96d623b6181f" +source = "git+https://github.com/mullvad/gotatun?rev=d8ddd4232c9636aa98ab4bd14093e256f7d09abb#d8ddd4232c9636aa98ab4bd14093e256f7d09abb" dependencies = [ "aead", "async-trait", @@ -449,29 +449,34 @@ dependencies = [ "blake2", "bytes", "chacha20poly1305", + "constant_time_eq 0.4.2", "duplicate", "either", "eyre", + "futures", "hex", "hmac", "ip_network", "ip_network_table", "libc", "log", + "maybenot", "nix 0.30.1", "parking_lot", "pcap-file", "pnet_packet 0.35.0", "rand 0.9.2", + "rand_chacha 0.9.0", "rand_core 0.6.4", "ring", - "socket2 0.4.10", + "socket2 0.6.0", "thiserror 1.0.59", "tokio", "tracing", "tun 0.7.13", "typed-builder 0.21.0", "untrusted", + "windows-sys 0.61.1", "x25519-dalek", "zerocopy", ] @@ -811,6 +816,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1500,9 +1511,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.34" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -2815,16 +2826,16 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "maybenot" -version = "2.0.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fd2cdf418470d9b87dd88e7fd57c479d7d84d22e765691ee814d51e0cf6782" +checksum = "44731ed644f441efeb5ca66a440a84555a40883e2873e20c9afde89b5b4836c8" dependencies = [ "base64 0.22.1", "bincode", "enum-map", "flate2", - "rand 0.8.5", - "rand_core 0.6.4", + "rand 0.9.2", + "rand_core 0.9.3", "rand_distr", "serde", "sha256", @@ -2832,13 +2843,13 @@ dependencies = [ [[package]] name = "maybenot-ffi" -version = "2.0.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c0111f55c83db74f51cdb1d8ffdad16149200d936d0a227fdfe5d579318ef78" +checksum = "6b2682b6da446844fad3c7ced24be8c66853efefa4674a48072cfd6b45e4fb0d" dependencies = [ "maybenot", - "rand 0.8.5", - "rand_chacha 0.3.1", + "rand 0.9.2", + "rand_chacha 0.9.0", ] [[package]] @@ -2883,9 +2894,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -4635,12 +4646,12 @@ dependencies = [ [[package]] name = "rand_distr" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +checksum = "6a8615d50dcf34fa31f7ab52692afec947c4dd0ab803cc87cb3b0b4570ff7463" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.9.2", ] [[package]] @@ -5307,16 +5318,6 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" @@ -7331,18 +7332,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", 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/Cargo.toml b/talpid-wireguard/Cargo.toml index 5da612f577..ec3f7aee86 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -45,8 +45,8 @@ tokio-stream = { version = "0.1", features = ["io-util"] } [dependencies.boringtun] optional = true features = ["device", "tun"] -git = "https://github.com/mullvad/boringtun" -rev = "83b3b5bb1bf3ec8b9849cc0d250d96d623b6181f" +git = "https://github.com/mullvad/gotatun" +rev = "d8ddd4232c9636aa98ab4bd14093e256f7d09abb" [target.'cfg(unix)'.dependencies] nix = { workspace = true, features = ["fs"] } diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs index f687458f0b..d82c19ac92 100644 --- a/talpid-wireguard/src/boringtun/mod.rs +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -3,7 +3,7 @@ use crate::config::patch_allowed_ips; use crate::{ Tunnel, TunnelError, config::Config, - stats::{Stats, StatsMap}, + stats::{DaitaStats, Stats, StatsMap}, }; #[cfg(target_os = "android")] use boringtun::udp::UdpTransportFactory; @@ -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), @@ -134,6 +145,15 @@ impl Devices { } } +/// Errors that can happen when setting up / restarting / reconfiguring GotaTun devices. +#[derive(thiserror::Error, Debug)] +pub enum ConfigureGotaTunDeviceError { + #[error("Multihop devices were provided with a single config")] + ExpectedSinglehopDevice, + #[error("Single devices were provided with a multihop config")] + ExpectedMultihopDevice, +} + #[cfg(target_os = "android")] struct AndroidUdpSocketFactory { pub tun: Arc<Tun>, @@ -169,7 +189,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)?; @@ -208,16 +228,19 @@ pub async fn open_boringtun_tunnel( let mut tun_config = tun07::Configuration::default(); tun_config.raw_fd(fd); - let device = tun07::Device::new(&tun_config).unwrap(); + let mut device = tun07::Device::new(&tun_config).unwrap(); + + // HACK: the `tun` crate does not implement AbstractDevice::(set_)mtu on Android, instead + // they are stubbed. `mtu()` will simply return the value set by `set_mtu()`, or 1500. + // + // GotaTun will try to read the MTU from this, so call set_mtu here with the correct value. + device.set_mtu(config.mtu).unwrap(); (Arc::new(tun), tun07::AsyncDevice::new(device).unwrap()) }; 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 +249,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")] @@ -237,32 +261,40 @@ pub async fn open_boringtun_tunnel( .inspect_err(|e| log::error!("Failed to open BoringTun: {e:?}"))?; log::info!( - "This tunnel was brought to you by... - ......................................................... - ..*...*.. .--. .---. ..*....*. - ...*..... | ) o | ......*.. - .*..*..*. |--: .-. .--.. .--. .-..|. . .--. ...*..... - ...*..... | )( )| | | |( ||| | | | .*.....*. - *.....*.. '--' `-' ' -' `-' `-`-`|'`--`-' `- .....*... - ......... ._.' ..*...*.. - ..*...*.............................................*...." + r#"This tunnel was brought to you by... + _______ _ __ ______ + / ____(_)_(_) /_____ /_ __/_ ______ + / / __/ __ \/ __/ __ `// / / / / / __ \ + / /_/ / /_/ / /_/ /_/ // / / /_/ / / / / + \____/\____/\__/\__,_//_/ \__,_/_/ /_/"# ); 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 @@ -284,32 +316,92 @@ 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 entry_device = EntryDevice::new(factory, tun_tx, tun_rx, boringtun_entry_config).await; + let device = SinglehopDevice::new( + udp_factory, + tun_dev.clone(), + tun_dev, + boringtun_entry_config, + ) + .await; + + Devices::Singlehop { + device, + api: entry_api, + } + }; + + configure_devices(&mut devices, config, daita).await?; + + Ok(devices) +} + +/// (Re)Configure boringtun devices. +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 { + return Err(TunnelError::ConfigureGotaTunDevice( + ConfigureGotaTunDeviceError::ExpectedMultihopDevice, + )); + }; let private_key = &config.tunnel.private_key; let peer = &config.entry_peer; @@ -318,6 +410,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:#}"); @@ -329,34 +422,24 @@ 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 { + return Err(TunnelError::ConfigureGotaTunDevice( + ConfigureGotaTunDeviceError::ExpectedSinglehopDevice, + )); + }; - log::info!("configuring boringtun device"); let private_key = &config.tunnel.private_key; let peer = &config.entry_peer; let set_cmd = create_set_command( @@ -364,18 +447,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] @@ -385,7 +465,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()); @@ -423,12 +502,22 @@ impl Tunnel for BoringTun { Some(SystemTime::now() - Duration::new(handshake_sec, handshake_nsec)) }; + let daita = || -> Option<DaitaStats> { + Some(DaitaStats { + tx_padding_bytes: peer.tx_padding_bytes?, + tx_padding_packet_bytes: peer.tx_padding_packet_bytes?, + rx_padding_bytes: peer.rx_padding_bytes?, + rx_padding_packet_bytes: peer.rx_padding_packet_bytes?, + }) + }; + stats.insert( peer.peer.public_key.0, Stats { tx_bytes: peer.tx_bytes.unwrap_or_default(), rx_bytes: peer.rx_bytes.unwrap_or_default(), last_handshake_time: last_handshake(), + daita: daita(), }, ); } @@ -440,38 +529,55 @@ 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>> { 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 + // TODO: Debug configure_devices. Currently we need to tear down the old devices + // after having exchanged tunnel params with the ephemeral peer. Empirically this + // is true for both DAITA & PQ. + // let recreate_devices = old_config.is_multihop() != self.config.is_multihop(); + let recreate_devices = true; + + 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()) @@ -502,9 +608,20 @@ 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 { + set_peer.daita_settings = Some(boringtun::device::daita::api::DaitaSettings { + maybenot_machines: daita.client_machines.clone(), + max_padding_frac: daita.max_padding_frac, + max_blocking_frac: daita.max_blocking_frac, + // TODO: tweak to sane values + max_blocked_packets: 1024, + min_blocking_capacity: 50, + }); + } + + set_cmd.peers.push(set_peer); set_cmd } diff --git a/talpid-wireguard/src/connectivity/check.rs b/talpid-wireguard/src/connectivity/check.rs index 89043fb54c..c7182f57df 100644 --- a/talpid-wireguard/src/connectivity/check.rs +++ b/talpid-wireguard/src/connectivity/check.rs @@ -514,8 +514,7 @@ mod test { [0u8; 32], Stats { rx_bytes: 1, - tx_bytes: 0, - last_handshake_time: None, + ..Default::default() }, ); conn_state.update(Instant::now(), stats); @@ -540,8 +539,7 @@ mod test { [0u8; 32], Stats { rx_bytes: 1, - tx_bytes: 0, - last_handshake_time: None, + ..Default::default() }, ); conn_state.update(connect_time, stats); @@ -565,8 +563,7 @@ mod test { [0u8; 32], Stats { rx_bytes: 1, - tx_bytes: 0, - last_handshake_time: None, + ..Default::default() }, ); conn_state.update(start, stats); @@ -578,7 +575,7 @@ mod test { Stats { rx_bytes: 1, tx_bytes: 1, - last_handshake_time: None, + ..Default::default() }, ); conn_state.update(update_time, stats); @@ -670,14 +667,7 @@ mod test { let tunnel = { let mut tunnel_stats = StatsMap::new(); - tunnel_stats.insert( - [0u8; 32], - Stats { - tx_bytes: 0, - rx_bytes: 0, - last_handshake_time: None, - }, - ); + tunnel_stats.insert([0u8; 32], Stats::default()); MockTunnel::new(move || Ok(tunnel_stats.clone())).boxed() }; diff --git a/talpid-wireguard/src/connectivity/mock.rs b/talpid-wireguard/src/connectivity/mock.rs index ef8bf4103f..103c691c9a 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; @@ -30,14 +31,7 @@ pub fn mock_checker(now: Instant, pinger: Box<dyn Pinger>) -> (Check, CancelToke pub fn connected_state(timestamp: Instant) -> ConnState { const PEER: [u8; 32] = [0u8; 32]; let mut stats = StatsMap::new(); - stats.insert( - PEER, - Stats { - tx_bytes: 0, - rx_bytes: 0, - last_handshake_time: None, - }, - ); + stats.insert(PEER, Stats::default()); ConnState::Connected { rx_timestamp: timestamp, tx_timestamp: timestamp, @@ -61,14 +55,7 @@ impl MockTunnel { pub fn always_incrementing() -> Self { let mut map = StatsMap::new(); - map.insert( - Self::PEER, - Stats { - tx_bytes: 0, - rx_bytes: 0, - last_handshake_time: None, - }, - ); + map.insert(Self::PEER, Stats::default()); let peers = std::sync::Mutex::new(map); Self { on_get_stats: Box::new(move || { @@ -86,14 +73,7 @@ impl MockTunnel { Self { on_get_stats: Box::new(|| { let mut map = StatsMap::new(); - map.insert( - Self::PEER, - Stats { - tx_bytes: 0, - rx_bytes: 0, - last_handshake_time: None, - }, - ); + map.insert(Self::PEER, Stats::default()); Ok(map) }), } @@ -117,17 +97,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/connectivity/monitor.rs b/talpid-wireguard/src/connectivity/monitor.rs index 644f2c9d80..6d5bc569cf 100644 --- a/talpid-wireguard/src/connectivity/monitor.rs +++ b/talpid-wireguard/src/connectivity/monitor.rs @@ -126,14 +126,7 @@ mod test { let stop_bytes_rx_inner = stop_bytes_rx.clone(); let mut map = StatsMap::new(); - map.insert( - [0u8; 32], - Stats { - tx_bytes: 0, - rx_bytes: 0, - last_handshake_time: None, - }, - ); + map.insert([0u8; 32], Stats::default()); let tunnel_stats = std::sync::Mutex::new(map); let pinger = MockPinger::default(); 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..d95e38d9b8 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)?; @@ -834,8 +832,10 @@ impl WireguardMonitor { self.pinger_stop_sender.close(); - self.runtime - .block_on(self.event_hook.on_event(TunnelEvent::Down)); + self.runtime.block_on(async { + self.event_hook.on_event(TunnelEvent::Down).await; + log_daita_overhead(&self.tunnel).await; + }); self.stop_tunnel(); @@ -984,6 +984,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 +992,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) } } @@ -1040,6 +1039,36 @@ async fn log_tunnel_data_usage(config: &Config, tunnel: &Arc<AsyncMutex<Option<T } } +async fn log_daita_overhead(tunnel: &Arc<AsyncMutex<Option<TunnelType>>>) { + let tunnel = tunnel.lock().await; + let Some(tunnel) = &*tunnel else { return }; + let Ok(tunnel_stats) = tunnel.get_tunnel_stats().await else { + return; + }; + if let Some(stats) = tunnel_stats.values().find(|stats| stats.daita.is_some()) { + let daita = stats.daita.as_ref().unwrap(); + log::info!("DAITA overhead stats:"); + log::info!("Total (outgoing) {} MiB", stats.tx_bytes / 1024 / 1024); + log::info!("Total (incoming) {} MiB", stats.rx_bytes / 1024 / 1024); + log::info!( + "Padding packet overhead (outgoing) {} MiB", + daita.tx_padding_packet_bytes / 1024 / 1024 + ); + log::info!( + "Padding packet overhead (incoming) {} MiB", + daita.rx_padding_packet_bytes / 1024 / 1024 + ); + log::info!( + "Constant packet size overhead (outgoing) {} MiB", + daita.tx_padding_bytes / 1024 / 1024 + ); + log::info!( + "Constant packet size overhead (incoming) {} MiB", + daita.rx_padding_bytes / 1024 / 1024 + ); + } +} + #[derive(Debug)] enum CloseMsg { Stop, @@ -1059,10 +1088,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 @@ -1146,6 +1173,11 @@ pub enum TunnelError { #[cfg(feature = "boringtun")] #[error("Boringtun: {0:?}")] BoringTunDevice(::boringtun::device::Error), + + /// Failed to configure GotaTun device. + #[cfg(feature = "boringtun")] + #[error("Failed to configure the GotaTun device")] + ConfigureGotaTunDevice(#[source] boringtun::ConfigureGotaTunDeviceError), } #[cfg(target_os = "linux")] @@ -1173,13 +1205,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 +1228,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) = ¶ms.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 +1267,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/snapshots/talpid_wireguard__stats__test__stats_debug.snap b/talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap index a3dc8c3b6a..59f59bdd32 100644 --- a/talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap +++ b/talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap @@ -1,10 +1,10 @@ --- source: talpid-wireguard/src/stats.rs expression: "StatsDebug { now, stats: &stats }" -snapshot_kind: text --- Stats { tx_bytes: 100, rx_bytes: 100, last_handshake: "60000 ms ago", + daita: None, } diff --git a/talpid-wireguard/src/stats.rs b/talpid-wireguard/src/stats.rs index 4707339f75..7adacb5338 100644 --- a/talpid-wireguard/src/stats.rs +++ b/talpid-wireguard/src/stats.rs @@ -2,11 +2,29 @@ use std::fmt; use std::time::{Duration, SystemTime}; /// Contains bytes sent and received through a tunnel -#[derive(Default, PartialEq, Eq, Clone, Copy)] +#[derive(Default, PartialEq, Eq, Clone)] pub struct Stats { pub tx_bytes: u64, pub rx_bytes: u64, pub last_handshake_time: Option<SystemTime>, + // Optional DAITA stats + // Currently only available for GotaTun + pub daita: Option<DaitaStats>, +} + +#[derive(Debug, Default, PartialEq, Eq, Clone)] +pub struct DaitaStats { + /// Extra bytes added due to constant-size padding of data packets + pub tx_padding_bytes: u64, + + /// Bytes of standalone padding packets transmitted + pub tx_padding_packet_bytes: u64, + + /// Total extra bytes removed due to constant-size padding of data packets + pub rx_padding_bytes: u64, + + /// Bytes of standalone padding packets received + pub rx_padding_packet_bytes: u64, } impl fmt::Debug for Stats { @@ -45,6 +63,8 @@ impl fmt::Debug for StatsDebug<'_> { dbg.field("last_handshake", &"no handshake"); } + dbg.field("daita", &self.stats.daita); + dbg.finish() } } @@ -64,6 +84,7 @@ mod test { tx_bytes: 100, rx_bytes: 100, last_handshake_time: Some(SystemTime::UNIX_EPOCH), + ..Default::default() }; insta::assert_debug_snapshot!(StatsDebug { now, stats: &stats }); diff --git a/talpid-wireguard/src/wireguard_go/mod.rs b/talpid-wireguard/src/wireguard_go/mod.rs index 55058f2a0c..343d777ddb 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(()) + }) } } @@ -816,6 +818,7 @@ mod stats { tx_bytes: tx_bytes_val, rx_bytes: rx_bytes_val, last_handshake_time: last_handshake_time(), + ..Default::default() }, ); peer = None; 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_kernel/stats.rs b/talpid-wireguard/src/wireguard_kernel/stats.rs index a055a35d10..c74aebc140 100644 --- a/talpid-wireguard/src/wireguard_kernel/stats.rs +++ b/talpid-wireguard/src/wireguard_kernel/stats.rs @@ -43,6 +43,7 @@ impl Stats { tx_bytes, rx_bytes, last_handshake_time, + ..Default::default() }, ); } diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index a1472bb2ff..d7a6b1e5f7 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -951,6 +951,7 @@ impl Tunnel for WgNtTunnel { tx_bytes: peer.tx_bytes, rx_bytes: peer.rx_bytes, last_handshake_time, + ..Default::default() }, ); } @@ -968,6 +969,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 +990,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>] { diff --git a/wireguard-go-rs/Cargo.toml b/wireguard-go-rs/Cargo.toml index 40d70623a5..7efbac12ab 100644 --- a/wireguard-go-rs/Cargo.toml +++ b/wireguard-go-rs/Cargo.toml @@ -22,7 +22,7 @@ talpid-types.path = "../talpid-types" # NOTE: for other platforms, maybenot-ffi is NOT declared here, but instead built directly from # wireguard-go-rs/libwg/wireguard-go/maybenot-ffi [target.'cfg(any(target_os = "linux", target_os = "macos"))'.dependencies] -maybenot-ffi = "2.0.1" +maybenot-ffi = "2.2.2" [target.'cfg(target_os = "windows")'.dependencies] windows-sys = { workspace = true, features = [ |
