From 184192e1c23610e46e44eaaf083e9fd1b91d2879 Mon Sep 17 00:00:00 2001 From: nessi Date: Wed, 18 Mar 2026 09:59:44 +0100 Subject: [PATCH] feat: add fallback to wg show command for transfer metrics when dump command fails Add read_transfer_totals_from_show function to parse transfer statistics from wg show output as fallback when wg show dump command fails. Add parse_human_wireguard_bytes helper to convert human-readable byte values (B, KiB, MiB, GiB, TiB) to u64. Update read_transfer_totals to call fallback instead of returning error when dump command fails. --- desktop-client/tunnel-helper/src/main.rs | 62 +++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/desktop-client/tunnel-helper/src/main.rs b/desktop-client/tunnel-helper/src/main.rs index 87ce51a..8a8d84b 100644 --- a/desktop-client/tunnel-helper/src/main.rs +++ b/desktop-client/tunnel-helper/src/main.rs @@ -712,7 +712,7 @@ fn read_transfer_totals(profile: &Path) -> Result<(u64, u64), String> { .map_err(|err| format!("Unable to query tunnel transfer counters: {err}"))?; if !output.status.success() { - return Err("Unable to read WireGuard transfer counters.".into()); + return read_transfer_totals_from_show(tunnel_name); } let stdout = String::from_utf8_lossy(&output.stdout); @@ -732,6 +732,66 @@ fn read_transfer_totals(profile: &Path) -> Result<(u64, u64), String> { Ok((rx_bytes, tx_bytes)) } +fn read_transfer_totals_from_show(tunnel_name: &str) -> Result<(u64, u64), String> { + let mut command = Command::new(find_wg_cli()?); + command.arg("show").arg(tunnel_name); + #[cfg(target_os = "windows")] + command.creation_flags(CREATE_NO_WINDOW); + + let output = command + .output() + .map_err(|err| format!("Unable to query tunnel transfer text: {err}"))?; + + if !output.status.success() { + return Err("Unable to read WireGuard transfer counters.".into()); + } + + 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 = rx_bytes.saturating_add(parse_human_wireguard_bytes(received)); + } + if let Some(sent) = parts.get(1) { + tx_bytes = tx_bytes.saturating_add(parse_human_wireguard_bytes(sent)); + } + } + } + + Ok((rx_bytes, tx_bytes)) +} + +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_wg_cli() -> Result { let candidates = [