diff options
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | talpid-core/Cargo.toml | 2 | ||||
| -rw-r--r-- | talpid-wireguard/Cargo.toml | 1 | ||||
| -rw-r--r-- | talpid-wireguard/src/boringtun/mod.rs | 9 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/check.rs | 5 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/mock.rs | 3 | ||||
| -rw-r--r-- | talpid-wireguard/src/connectivity/monitor.rs | 1 | ||||
| -rw-r--r-- | talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap | 10 | ||||
| -rw-r--r-- | talpid-wireguard/src/stats.rs | 64 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_go/mod.rs | 33 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_kernel/stats.rs | 25 | ||||
| -rw-r--r-- | talpid-wireguard/src/wireguard_nt/mod.rs | 20 |
12 files changed, 171 insertions, 3 deletions
diff --git a/Cargo.lock b/Cargo.lock index b92081e112..13214926ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5858,6 +5858,7 @@ dependencies = [ "chrono", "futures", "hex", + "insta", "internet-checksum", "ipnetwork", "libc", diff --git a/talpid-core/Cargo.toml b/talpid-core/Cargo.toml index d771bb23a6..5712135f9d 100644 --- a/talpid-core/Cargo.toml +++ b/talpid-core/Cargo.toml @@ -113,4 +113,4 @@ tonic-build = { workspace = true, default-features = false, features = ["transpo [dev-dependencies] test-log = "0.2.17" tokio = { workspace = true, features = ["io-util", "test-util", "time"] } -insta = "1.42" +insta = { workspace = true } diff --git a/talpid-wireguard/Cargo.toml b/talpid-wireguard/Cargo.toml index 2bfea215a7..02a37c4215 100644 --- a/talpid-wireguard/Cargo.toml +++ b/talpid-wireguard/Cargo.toml @@ -97,3 +97,4 @@ features = [ [dev-dependencies] proptest = { workspace = true } tokio = { workspace = true, features = ["test-util"] } +insta = { workspace = true } diff --git a/talpid-wireguard/src/boringtun/mod.rs b/talpid-wireguard/src/boringtun/mod.rs index f4ba01f089..c854213d71 100644 --- a/talpid-wireguard/src/boringtun/mod.rs +++ b/talpid-wireguard/src/boringtun/mod.rs @@ -25,6 +25,7 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, ops::Deref, sync::{Arc, Mutex}, + time::{Duration, SystemTime}, }; use talpid_tunnel::tun_provider::{self, Tun, TunProvider}; use talpid_tunnel_config_client::DaitaSettings; @@ -376,11 +377,19 @@ impl Tunnel for BoringTun { }; for peer in response.peers { + let last_handshake = || -> Option<SystemTime> { + let handshake_sec = peer.last_handshake_time_sec?; + let handshake_nsec = peer.last_handshake_time_nsec?; + // TODO: Boringtun should probably return a Unix timestamp (like wg-go) + Some(SystemTime::now() - Duration::new(handshake_sec, handshake_nsec)) + }; + 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(), }, ); } diff --git a/talpid-wireguard/src/connectivity/check.rs b/talpid-wireguard/src/connectivity/check.rs index 3690356ff8..89043fb54c 100644 --- a/talpid-wireguard/src/connectivity/check.rs +++ b/talpid-wireguard/src/connectivity/check.rs @@ -515,6 +515,7 @@ mod test { Stats { rx_bytes: 1, tx_bytes: 0, + last_handshake_time: None, }, ); conn_state.update(Instant::now(), stats); @@ -540,6 +541,7 @@ mod test { Stats { rx_bytes: 1, tx_bytes: 0, + last_handshake_time: None, }, ); conn_state.update(connect_time, stats); @@ -564,6 +566,7 @@ mod test { Stats { rx_bytes: 1, tx_bytes: 0, + last_handshake_time: None, }, ); conn_state.update(start, stats); @@ -575,6 +578,7 @@ mod test { Stats { rx_bytes: 1, tx_bytes: 1, + last_handshake_time: None, }, ); conn_state.update(update_time, stats); @@ -671,6 +675,7 @@ mod test { Stats { tx_bytes: 0, rx_bytes: 0, + last_handshake_time: None, }, ); 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 a7a1851e31..ef8bf4103f 100644 --- a/talpid-wireguard/src/connectivity/mock.rs +++ b/talpid-wireguard/src/connectivity/mock.rs @@ -35,6 +35,7 @@ pub fn connected_state(timestamp: Instant) -> ConnState { Stats { tx_bytes: 0, rx_bytes: 0, + last_handshake_time: None, }, ); ConnState::Connected { @@ -65,6 +66,7 @@ impl MockTunnel { Stats { tx_bytes: 0, rx_bytes: 0, + last_handshake_time: None, }, ); let peers = std::sync::Mutex::new(map); @@ -89,6 +91,7 @@ impl MockTunnel { Stats { tx_bytes: 0, rx_bytes: 0, + last_handshake_time: None, }, ); Ok(map) diff --git a/talpid-wireguard/src/connectivity/monitor.rs b/talpid-wireguard/src/connectivity/monitor.rs index 7b2eb5e6a6..644f2c9d80 100644 --- a/talpid-wireguard/src/connectivity/monitor.rs +++ b/talpid-wireguard/src/connectivity/monitor.rs @@ -131,6 +131,7 @@ mod test { Stats { tx_bytes: 0, rx_bytes: 0, + last_handshake_time: None, }, ); let tunnel_stats = std::sync::Mutex::new(map); 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 new file mode 100644 index 0000000000..a3dc8c3b6a --- /dev/null +++ b/talpid-wireguard/src/snapshots/talpid_wireguard__stats__test__stats_debug.snap @@ -0,0 +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", +} diff --git a/talpid-wireguard/src/stats.rs b/talpid-wireguard/src/stats.rs index cdbe8318cf..4707339f75 100644 --- a/talpid-wireguard/src/stats.rs +++ b/talpid-wireguard/src/stats.rs @@ -1,9 +1,71 @@ +use std::fmt; +use std::time::{Duration, SystemTime}; + /// Contains bytes sent and received through a tunnel -#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Default, PartialEq, Eq, Clone, Copy)] pub struct Stats { pub tx_bytes: u64, pub rx_bytes: u64, + pub last_handshake_time: Option<SystemTime>, +} + +impl fmt::Debug for Stats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let stats = StatsDebug { + now: SystemTime::now(), + stats: self, + }; + fmt::Debug::fmt(&stats, f) + } +} + +struct StatsDebug<'a> { + pub now: SystemTime, + pub stats: &'a Stats, +} + +impl fmt::Debug for StatsDebug<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut dbg = f.debug_struct("Stats"); + + dbg.field("tx_bytes", &self.stats.tx_bytes) + .field("rx_bytes", &self.stats.rx_bytes); + + if let Some(last_handshake) = self.stats.last_handshake_time { + let time_since_handshake = self + .now + .duration_since(last_handshake) + .unwrap_or(Duration::ZERO); + + dbg.field( + "last_handshake", + &format_args!("\"{} ms ago\"", time_since_handshake.as_millis()), + ); + } else { + dbg.field("last_handshake", &"no handshake"); + } + + dbg.finish() + } } /// A map from peer pubkeys to peer stats. pub type StatsMap = std::collections::HashMap<[u8; 32], Stats>; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_stats_debug() { + let now = SystemTime::UNIX_EPOCH + Duration::from_secs(60); + + let stats = Stats { + tx_bytes: 100, + rx_bytes: 100, + last_handshake_time: Some(SystemTime::UNIX_EPOCH), + }; + + 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 6e277926c3..b12afe40b4 100644 --- a/talpid-wireguard/src/wireguard_go/mod.rs +++ b/talpid-wireguard/src/wireguard_go/mod.rs @@ -758,6 +758,7 @@ impl Tunnel for WgGoTunnel { mod stats { use super::{Stats, StatsMap}; + use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { @@ -773,8 +774,11 @@ mod stats { let mut map = StatsMap::new(); let mut peer = None; + let mut tx_bytes = None; let mut rx_bytes = None; + let mut last_handshake_time_sec = None; + let mut last_handshake_time_nsec = None; // parts iterates over keys and values let parts = config.split('\n').filter_map(|line| { @@ -793,6 +797,8 @@ mod stats { peer = Some(buffer); tx_bytes = None; rx_bytes = None; + last_handshake_time_sec = None; + last_handshake_time_nsec = None; } "rx_bytes" => { rx_bytes = Some( @@ -810,6 +816,22 @@ mod stats { .map_err(|err| Error::IntParse(value.to_string(), err))?, ); } + "last_handshake_time_sec" => { + last_handshake_time_sec = Some( + value + .trim() + .parse() + .map_err(|err| Error::IntParse(value.to_string(), err))?, + ); + } + "last_handshake_time_nsec" => { + last_handshake_time_nsec = Some( + value + .trim() + .parse() + .map_err(|err| Error::IntParse(value.to_string(), err))?, + ); + } _ => continue, } @@ -817,16 +839,27 @@ mod stats { if let (Some(peer_val), Some(tx_bytes_val), Some(rx_bytes_val)) = (peer, tx_bytes, rx_bytes) { + let last_handshake_time = || -> Option<SystemTime> { + let handshake_sec = last_handshake_time_sec?; + let handshake_nsec = last_handshake_time_nsec?; + // handshake_{sec,nsec} are relative to UNIX_EPOCH + // https://www.wireguard.com/xplatform/ + Some(UNIX_EPOCH + Duration::new(handshake_sec, handshake_nsec)) + }; + map.insert( peer_val, Self { tx_bytes: tx_bytes_val, rx_bytes: rx_bytes_val, + last_handshake_time: last_handshake_time(), }, ); peer = None; tx_bytes = None; rx_bytes = None; + last_handshake_time_sec = None; + last_handshake_time_nsec = None; } } Ok(map) diff --git a/talpid-wireguard/src/wireguard_kernel/stats.rs b/talpid-wireguard/src/wireguard_kernel/stats.rs index 8604e8243f..a055a35d10 100644 --- a/talpid-wireguard/src/wireguard_kernel/stats.rs +++ b/talpid-wireguard/src/wireguard_kernel/stats.rs @@ -1,3 +1,5 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + use super::wg_message::{DeviceMessage, DeviceNla, PeerNla}; use crate::stats::{Stats, StatsMap}; @@ -10,18 +12,39 @@ impl Stats { for msg in peers { let mut tx_bytes = 0; let mut rx_bytes = 0; + let mut last_handshake_time = None; let mut pub_key = None; for nla in &msg.0 { match nla { PeerNla::TxBytes(bytes) => tx_bytes = *bytes, PeerNla::RxBytes(bytes) => rx_bytes = *bytes, + PeerNla::LastHandshakeTime(time) => { + last_handshake_time = || -> Option<SystemTime> { + // handshake_{sec,nsec} are relative to UNIX_EPOCH + // https://www.wireguard.com/xplatform/ + Some( + UNIX_EPOCH + + Duration::new( + time.tv_sec().try_into().ok()?, + time.tv_nsec().try_into().ok()?, + ), + ) + }(); + } PeerNla::PublicKey(key) => pub_key = Some(*key), _ => continue, } } if let Some(key) = pub_key { - map.insert(key, Stats { tx_bytes, rx_bytes }); + map.insert( + key, + Stats { + tx_bytes, + rx_bytes, + last_handshake_time, + }, + ); } } } diff --git a/talpid-wireguard/src/wireguard_nt/mod.rs b/talpid-wireguard/src/wireguard_nt/mod.rs index 7fe748ec5c..0f650d3866 100644 --- a/talpid-wireguard/src/wireguard_nt/mod.rs +++ b/talpid-wireguard/src/wireguard_nt/mod.rs @@ -22,6 +22,7 @@ use std::{ pin::Pin, ptr, sync::{Arc, LazyLock, Mutex}, + time::{Duration, SystemTime, UNIX_EPOCH}, }; #[cfg(daita)] use std::{ffi::c_uchar, path::PathBuf}; @@ -1072,11 +1073,17 @@ impl Tunnel for WgNtTunnel { super::TunnelError::GetConfigError })?; for (peer, _allowed_ips) in &peers { + // last_handshake is in 100s of ns relative to 1601-01-01 UTC + // https://git.zx2c4.com/wireguard-nt/tree/api/wireguard.h?id=30a2817d913460ed8a23388d3da485cf9347afa3#n246 + let last_handshake_time = + (peer.last_handshake > 0).then(|| filetime_to_systemtime(peer.last_handshake)); + map.insert( peer.public_key, Stats { tx_bytes: peer.tx_bytes, rx_bytes: peer.rx_bytes, + last_handshake_time, }, ); } @@ -1134,6 +1141,19 @@ pub fn as_uninit_byte_slice<T: Copy + Sized>(value: &T) -> &[mem::MaybeUninit<u8 unsafe { std::slice::from_raw_parts(value as *const _ as *const _, mem::size_of::<T>()) } } +/// wireguard-nt uses the `FILETIME` timestamp (100ns intervals since 1601-01-01). +/// This function converts this to [SystemTime]. +fn filetime_to_systemtime(filetime: u64) -> SystemTime { + // Difference between 1601-01-01 and 1970-01-01 in 100ns intervals + const WINDOWS_TO_UNIX_EPOCH_DIFF: u64 = 11644473600u64; + const HUNDRED_NANOSECONDS: u64 = 10_000_000; + + let seconds = filetime / HUNDRED_NANOSECONDS; + let nanos = (filetime % HUNDRED_NANOSECONDS) * 100; + + UNIX_EPOCH + Duration::new(seconds - WINDOWS_TO_UNIX_EPOCH_DIFF, nanos as u32) +} + #[cfg(test)] mod tests { use super::*; |
