//! 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); 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::("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()); } }