Files
varde/varde-daemon/src/metered.rs
T

100 lines
2.9 KiB
Rust
Raw Normal View History

//! Metered-connection awareness.
//!
//! When the active connection is metered, varde stops moving bytes:
//! no background fetching, no serving. Detection queries
//! NetworkManager's `Metered` property over D-Bus and is feature-gated
//! (`metered`, on by default) so the daemon still builds and runs on
//! systems without D-Bus — those simply always count as unmetered.
//!
//! `VARDE_FORCE_METERED=true` forces the metered state regardless of
//! NetworkManager; useful for testing and as a manual kill switch.
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
/// Shared metered state, cheap to read on every transfer decision.
#[derive(Debug, Clone, Default)]
pub struct MeteredState(Arc<AtomicBool>);
impl MeteredState {
pub fn is_metered(&self) -> bool {
self.0.load(Ordering::Relaxed)
}
fn set(&self, metered: bool) {
let was = self.0.swap(metered, Ordering::Relaxed);
if was != metered {
tracing::info!(metered, "metered state changed");
}
}
}
/// Start metered detection and return the shared state.
pub fn start() -> MeteredState {
let state = MeteredState::default();
if std::env::var("VARDE_FORCE_METERED").as_deref() == Ok("true") {
tracing::warn!("VARDE_FORCE_METERED=true: all transfers disabled");
state.set(true);
return state;
}
#[cfg(feature = "metered")]
{
let state = state.clone();
tokio::spawn(async move { poll_network_manager(state).await });
}
state
}
/// NetworkManager's NM_METERED enum: 1 = yes, 3 = guess-yes.
#[cfg(feature = "metered")]
async fn poll_network_manager(state: MeteredState) {
use std::time::Duration;
let connection = match zbus::Connection::system().await {
Ok(c) => c,
Err(e) => {
tracing::debug!(error = %e, "no D-Bus system bus; assuming unmetered");
return;
}
};
let proxy = match zbus::Proxy::new(
&connection,
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager",
"org.freedesktop.NetworkManager",
)
.await
{
Ok(p) => p,
Err(e) => {
tracing::debug!(error = %e, "NetworkManager proxy failed; assuming unmetered");
return;
}
};
loop {
match proxy.get_property::<u32>("Metered").await {
Ok(value) => state.set(value == 1 || value == 3),
Err(e) => {
tracing::debug!(error = %e, "reading Metered property");
}
}
tokio::time::sleep(Duration::from_secs(30)).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_unmetered_and_flips() {
let state = MeteredState::default();
assert!(!state.is_metered());
state.set(true);
assert!(state.is_metered());
state.set(false);
assert!(!state.is_metered());
}
}