fix(shaper): distinguish interface loss and reset counters on recovery
Build / build (pull_request) Failing after 4s
DevSkim / DevSkim (pull_request) Failing after 1m21s
rust-clippy analyze / Run rust-clippy analyzing (pull_request) Failing after 1m42s

This commit is contained in:
Nils Andreas Svee committed 2026-09-24 23:25:28 +07:00
1 parent 708d88fc57
commit c3931064e7
5 files changed
+42 -16

No files matched your search

+4
View File
@@ -105,6 +105,10 @@ impl InterfaceStatsProvider for FreeBsdInterfaceStats {
tx_bytes: ifmd.ifmd_data.ifi_obytes, tx_bytes: ifmd.ifmd_data.ifi_obytes,
}) })
} }
fn is_interface_missing(error: &Self::Error) -> bool {
matches!(error, FreeBsdInterfaceStatsError::InterfaceNotFound { .. })
}
} }
pub(crate) type PlatformInterfaceStats = FreeBsdInterfaceStats; pub(crate) type PlatformInterfaceStats = FreeBsdInterfaceStats;
+10 -8
View File
@@ -18,9 +18,6 @@ use std::sync::atomic::Ordering;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub(crate) enum NetlinkError { pub(crate) enum NetlinkError {
#[error("Couldn't find interface `{0}`")]
InterfaceNotFound(String),
#[error("Netlink error: {0}")] #[error("Netlink error: {0}")]
Netlink(#[from] io::Error), Netlink(#[from] io::Error),
@@ -48,9 +45,8 @@ pub(crate) struct Qdisc {
} }
impl NetlinkError { impl NetlinkError {
fn is_qdisc_loss(&self) -> bool { fn is_interface_missing(&self) -> bool {
match self { match self {
Self::InterfaceNotFound(_) | Self::NoQdiscFound(_) => true,
Self::Netlink(error) => { Self::Netlink(error) => {
matches!(error.raw_os_error(), Some(libc::ENODEV | libc::ENOENT)) matches!(error.raw_os_error(), Some(libc::ENODEV | libc::ENOENT))
} }
@@ -61,6 +57,10 @@ impl NetlinkError {
_ => false, _ => false,
} }
} }
fn is_qdisc_loss(&self) -> bool {
self.is_interface_missing() || matches!(self, Self::NoQdiscFound(_))
}
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@@ -74,9 +74,7 @@ impl Netlink {
request.encode().push_ifname_bytes(ifname.as_bytes()); request.encode().push_ifname_bytes(ifname.as_bytes());
let mut iter = socket.request(&request)?; let mut iter = socket.request(&request)?;
let (header, _) = iter let (header, _) = iter.recv_one()?;
.recv_one()
.map_err(|_| NetlinkError::InterfaceNotFound(ifname.to_string()))?;
Ok(header.ifi_index) Ok(header.ifi_index)
} }
@@ -173,6 +171,10 @@ impl InterfaceStatsProvider for Netlink {
let (rx_bytes, tx_bytes) = Self::get_interface_stats(interface)?; let (rx_bytes, tx_bytes) = Self::get_interface_stats(interface)?;
Ok(InterfaceStats { rx_bytes, tx_bytes }) Ok(InterfaceStats { rx_bytes, tx_bytes })
} }
fn is_interface_missing(error: &Self::Error) -> bool {
error.is_interface_missing()
}
} }
impl TrafficControlBackend for Netlink { impl TrafficControlBackend for Netlink {
+4
View File
@@ -63,6 +63,10 @@ impl InterfaceStatsProvider for MacOsInterfaceStats {
let response = route_interface_list(interface_index)?; let response = route_interface_list(interface_index)?;
parse_route_messages(&response, interface_index) parse_route_messages(&response, interface_index)
} }
fn is_interface_missing(error: &Self::Error) -> bool {
matches!(error, MacOsInterfaceStatsError::InterfaceNotFound { .. })
}
} }
fn route_interface_list( fn route_interface_list(
+4
View File
@@ -14,6 +14,10 @@ pub(crate) trait InterfaceStatsProvider: Send {
type Error: Error + Send + Sync + 'static; type Error: Error + Send + Sync + 'static;
fn read_stats(&mut self, interface: &str) -> Result<InterfaceStats, Self::Error>; fn read_stats(&mut self, interface: &str) -> Result<InterfaceStats, Self::Error>;
fn is_interface_missing(_error: &Self::Error) -> bool {
false
}
} }
pub(crate) trait TrafficControlBackend: Send { pub(crate) trait TrafficControlBackend: Send {
+20 -8
View File
@@ -367,14 +367,8 @@ impl<S: InterfaceStatsProvider, T: TrafficControlBackend> Ratecontroller<S, T> {
&mut self.stats_provider, &mut self.stats_provider,
&self.settings.network.upload_interface, &self.settings.network.upload_interface,
) { ) {
Ok(stats) => { Ok(stats) => stats,
if stats_missing { Err(error) if S::is_interface_missing(&error) => {
info!("Interface statistics available again");
stats_missing = false;
}
stats
}
Err(error) => {
if !stats_missing { if !stats_missing {
warn!( warn!(
"Interface statistics unavailable: {error}; waiting for interface recovery" "Interface statistics unavailable: {error}; waiting for interface recovery"
@@ -383,6 +377,7 @@ impl<S: InterfaceStatsProvider, T: TrafficControlBackend> Ratecontroller<S, T> {
} }
continue; continue;
} }
Err(error) => return Err(error.into()),
}; };
if SHUTDOWN.load(Ordering::Relaxed) { if SHUTDOWN.load(Ordering::Relaxed) {
return Ok(()); return Ok(());
@@ -404,6 +399,23 @@ impl<S: InterfaceStatsProvider, T: TrafficControlBackend> Ratecontroller<S, T> {
)?; )?;
last_reassert_t = now_t; 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()?; self.update_deltas()?;
if self.state_dl.deltas.is_empty() || self.state_ul.deltas.is_empty() { if self.state_dl.deltas.is_empty() || self.state_ul.deltas.is_empty() {