From aeb0b8f8b49fce6c755f0b9ca33932d9f5903c8e Mon Sep 17 00:00:00 2001 From: nessi Date: Wed, 18 Mar 2026 08:27:36 +0100 Subject: [PATCH] feat: add fallback to wg show transfer parsing when dump command fails on Windows Add read_windows_metrics_from_show function that parses human-readable transfer output from wg show command when wg show dump fails. Add parse_human_wireguard_bytes helper to convert human-readable byte values (B, KiB, MiB, GiB, TiB) to u64. Update direct_windows_metrics to fall back to transfer parsing instead of returning zero metrics when dump command fails. --- .../src-tauri/src/tunnel_manager.rs | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/desktop-client/src-tauri/src/tunnel_manager.rs b/desktop-client/src-tauri/src/tunnel_manager.rs index bef6133..3a9c7ac 100644 --- a/desktop-client/src-tauri/src/tunnel_manager.rs +++ b/desktop-client/src-tauri/src/tunnel_manager.rs @@ -136,11 +136,7 @@ fn direct_windows_metrics(profile_path: &Path) -> Result .map_err(|err| format!("Unable to query WireGuard counters: {err}"))?; if !wg_output.status.success() { - return Ok(TunnelMetrics { - active: true, - rx_bytes: 0, - tx_bytes: 0, - }); + return read_windows_metrics_from_show(tunnel_name); } let stdout = String::from_utf8_lossy(&wg_output.stdout); @@ -163,6 +159,77 @@ fn direct_windows_metrics(profile_path: &Path) -> Result }) } +#[cfg(target_os = "windows")] +fn read_windows_metrics_from_show(tunnel_name: &str) -> Result { + let wg = find_windows_wg()?; + let mut command = Command::new(wg); + command + .arg("show") + .arg(tunnel_name) + .creation_flags(CREATE_NO_WINDOW); + let output = command + .output() + .map_err(|err| format!("Unable to query WireGuard transfer text: {err}"))?; + + if !output.status.success() { + return Ok(TunnelMetrics { + active: true, + rx_bytes: 0, + tx_bytes: 0, + }); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + let mut rx_bytes = 0_u64; + let mut tx_bytes = 0_u64; + + for line in stdout.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("transfer:") { + let parts: Vec<&str> = rest.split(',').collect(); + if let Some(received) = parts.first() { + rx_bytes = parse_human_wireguard_bytes(received); + } + if let Some(sent) = parts.get(1) { + tx_bytes = parse_human_wireguard_bytes(sent); + } + } + } + + Ok(TunnelMetrics { + active: true, + rx_bytes, + tx_bytes, + }) +} + +#[cfg(target_os = "windows")] +fn parse_human_wireguard_bytes(value: &str) -> u64 { + let cleaned = value + .replace("received", "") + .replace("sent", "") + .trim() + .to_string(); + let mut parts = cleaned.split_whitespace(); + let amount = parts + .next() + .map(|raw| raw.replace(',', ".")) + .and_then(|raw| raw.parse::().ok()) + .unwrap_or(0.0); + let unit = parts.next().unwrap_or("B"); + + let multiplier = match unit { + "B" => 1.0, + "KiB" => 1024.0, + "MiB" => 1024.0 * 1024.0, + "GiB" => 1024.0 * 1024.0 * 1024.0, + "TiB" => 1024.0 * 1024.0 * 1024.0 * 1024.0, + _ => 1.0, + }; + + (amount * multiplier) as u64 +} + #[cfg(target_os = "windows")] fn find_windows_wg() -> Result { let candidates = [