From c3931064e79e1d2e5f0e71e4804e91f54a2d4864 Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Thu, 24 Sep 2026 23:25:28 +0700 Subject: [PATCH] fix(shaper): distinguish interface loss and reset counters on recovery --- src/platform/freebsd.rs | 4 ++++ src/platform/linux/netlink.rs | 18 ++++++++++-------- src/platform/macos.rs | 4 ++++ src/platform/mod.rs | 4 ++++ src/ratecontroller.rs | 28 ++++++++++++++++++++-------- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/src/platform/freebsd.rs b/src/platform/freebsd.rs index fee7e81..ac0955a 100644 --- a/src/platform/freebsd.rs +++ b/src/platform/freebsd.rs @@ -105,6 +105,10 @@ impl InterfaceStatsProvider for FreeBsdInterfaceStats { tx_bytes: ifmd.ifmd_data.ifi_obytes, }) } + + fn is_interface_missing(error: &Self::Error) -> bool { + matches!(error, FreeBsdInterfaceStatsError::InterfaceNotFound { .. }) + } } pub(crate) type PlatformInterfaceStats = FreeBsdInterfaceStats; diff --git a/src/platform/linux/netlink.rs b/src/platform/linux/netlink.rs index 31af174..8dd456d 100644 --- a/src/platform/linux/netlink.rs +++ b/src/platform/linux/netlink.rs @@ -18,9 +18,6 @@ use std::sync::atomic::Ordering; #[derive(Debug, Error)] pub(crate) enum NetlinkError { - #[error("Couldn't find interface `{0}`")] - InterfaceNotFound(String), - #[error("Netlink error: {0}")] Netlink(#[from] io::Error), @@ -48,9 +45,8 @@ pub(crate) struct Qdisc { } impl NetlinkError { - fn is_qdisc_loss(&self) -> bool { + fn is_interface_missing(&self) -> bool { match self { - Self::InterfaceNotFound(_) | Self::NoQdiscFound(_) => true, Self::Netlink(error) => { matches!(error.raw_os_error(), Some(libc::ENODEV | libc::ENOENT)) } @@ -61,6 +57,10 @@ impl NetlinkError { _ => false, } } + + fn is_qdisc_loss(&self) -> bool { + self.is_interface_missing() || matches!(self, Self::NoQdiscFound(_)) + } } #[derive(Debug, Default)] @@ -74,9 +74,7 @@ impl Netlink { request.encode().push_ifname_bytes(ifname.as_bytes()); let mut iter = socket.request(&request)?; - let (header, _) = iter - .recv_one() - .map_err(|_| NetlinkError::InterfaceNotFound(ifname.to_string()))?; + let (header, _) = iter.recv_one()?; Ok(header.ifi_index) } @@ -173,6 +171,10 @@ impl InterfaceStatsProvider for Netlink { let (rx_bytes, tx_bytes) = Self::get_interface_stats(interface)?; Ok(InterfaceStats { rx_bytes, tx_bytes }) } + + fn is_interface_missing(error: &Self::Error) -> bool { + error.is_interface_missing() + } } impl TrafficControlBackend for Netlink { diff --git a/src/platform/macos.rs b/src/platform/macos.rs index c4bf5e8..ed2cc75 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -63,6 +63,10 @@ impl InterfaceStatsProvider for MacOsInterfaceStats { let response = route_interface_list(interface_index)?; parse_route_messages(&response, interface_index) } + + fn is_interface_missing(error: &Self::Error) -> bool { + matches!(error, MacOsInterfaceStatsError::InterfaceNotFound { .. }) + } } fn route_interface_list( diff --git a/src/platform/mod.rs b/src/platform/mod.rs index b5e959a..5294613 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -14,6 +14,10 @@ pub(crate) trait InterfaceStatsProvider: Send { type Error: Error + Send + Sync + 'static; fn read_stats(&mut self, interface: &str) -> Result; + + fn is_interface_missing(_error: &Self::Error) -> bool { + false + } } pub(crate) trait TrafficControlBackend: Send { diff --git a/src/ratecontroller.rs b/src/ratecontroller.rs index a7ef573..f1cb156 100644 --- a/src/ratecontroller.rs +++ b/src/ratecontroller.rs @@ -367,14 +367,8 @@ impl Ratecontroller { &mut self.stats_provider, &self.settings.network.upload_interface, ) { - Ok(stats) => { - if stats_missing { - info!("Interface statistics available again"); - stats_missing = false; - } - stats - } - Err(error) => { + Ok(stats) => stats, + Err(error) if S::is_interface_missing(&error) => { if !stats_missing { warn!( "Interface statistics unavailable: {error}; waiting for interface recovery" @@ -383,6 +377,7 @@ impl Ratecontroller { } continue; } + Err(error) => return Err(error.into()), }; if SHUTDOWN.load(Ordering::Relaxed) { return Ok(()); @@ -404,6 +399,23 @@ impl Ratecontroller { )?; last_reassert_t = now_t; } + if stats_missing + || self.state_dl.current_bytes < self.state_dl.previous_bytes + || self.state_ul.current_bytes < self.state_ul.previous_bytes + { + if stats_missing { + info!("Interface statistics available again"); + stats_missing = false; + } else { + warn!("Interface byte counters reset"); + } + self.state_dl.previous_bytes = self.state_dl.current_bytes; + self.state_ul.previous_bytes = self.state_ul.current_bytes; + self.state_dl.prev_t = now_t; + self.state_ul.prev_t = now_t; + lastchg_t = now_t; + continue; + } self.update_deltas()?; if self.state_dl.deltas.is_empty() || self.state_ul.deltas.is_empty() {