100 lines
2.9 KiB
Rust
100 lines
2.9 KiB
Rust
//! 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());
|
||
|
|
}
|
||
|
|
}
|