use std::sync::Arc;
use super::dbus_util::parse_dbus_uint32;
use crate::NetworkStatus;
const NM_STATE_CONNECTED_GLOBAL: u32 = 70;
pub(crate) fn query_network_status() -> NetworkStatus {
if let Some(status) = query_network_manager_state() {
return status;
}
network_status_from_sysfs()
}
fn query_network_manager_state() -> Option<NetworkStatus> {
let output = std::process::Command::new("dbus-send")
.args([
"--system",
"--dest=org.freedesktop.NetworkManager",
"--type=method_call",
"--print-reply",
"/org/freedesktop/NetworkManager",
"org.freedesktop.NetworkManager.state",
])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let state = parse_dbus_uint32(&output.stdout)?;
Some(nm_state_to_network_status(state))
}
fn nm_state_to_network_status(state: u32) -> NetworkStatus {
if state >= NM_STATE_CONNECTED_GLOBAL {
NetworkStatus::Online
} else {
NetworkStatus::Offline
}
}
pub(crate) fn start_network_monitor(
tx: calloop::channel::Sender<NetworkStatus>,
) -> Option<NetworkMonitorHandle> {
#[allow(clippy::disallowed_methods)]
let child = std::process::Command::new("dbus-monitor")
.args([
"--system",
"type='signal',interface='org.freedesktop.NetworkManager',member='StateChanged'",
])
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.stdin(std::process::Stdio::null())
.spawn()
.ok()?;
let pid = child.id();
let stdout = child.stdout;
let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
let stop_clone = stop.clone();
std::thread::Builder::new()
.name("network-monitor".into())
.spawn(move || {
use std::io::{BufRead, BufReader};
let Some(stdout) = stdout else { return };
let reader = BufReader::new(stdout);
for line in reader.lines() {
if stop_clone.load(std::sync::atomic::Ordering::Relaxed) {
break;
}
let Ok(line) = line else { break };
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("uint32") {
if let Ok(state) = rest.trim().parse::<u32>() {
let status = nm_state_to_network_status(state);
let _ = tx.send(status);
}
}
}
})
.ok()?;
Some(NetworkMonitorHandle { pid, stop })
}
pub(crate) struct NetworkMonitorHandle {
pid: u32,
stop: Arc<std::sync::atomic::AtomicBool>,
}
impl Drop for NetworkMonitorHandle {
fn drop(&mut self) {
self.stop.store(true, std::sync::atomic::Ordering::Relaxed);
unsafe {
libc::kill(self.pid as i32, libc::SIGTERM);
}
}
}
fn network_status_from_sysfs() -> NetworkStatus {
if let Ok(entries) = std::fs::read_dir("/sys/class/net") {
for entry in entries.flatten() {
let name = entry.file_name();
if name == "lo" {
continue;
}
if let Ok(state) = std::fs::read_to_string(entry.path().join("operstate")) {
if state.trim() == "up" {
return NetworkStatus::Online;
}
}
}
}
NetworkStatus::Offline
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_nm_state_to_network_status() {
assert_eq!(nm_state_to_network_status(0), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(10), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(20), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(30), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(40), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(50), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(60), NetworkStatus::Offline);
assert_eq!(nm_state_to_network_status(70), NetworkStatus::Online);
assert_eq!(nm_state_to_network_status(80), NetworkStatus::Online);
}
#[test]
fn test_parse_dbus_uint32() {
let output = b"method return time=1234.567 sender=:1.2 -> destination=:1.3\n uint32 70\n";
assert_eq!(parse_dbus_uint32(output), Some(70));
let output = b"method return time=1234.567\n uint32 20\n";
assert_eq!(parse_dbus_uint32(output), Some(20));
let output = b"no uint32 here\n";
assert_eq!(parse_dbus_uint32(output), None);
let output = b"";
assert_eq!(parse_dbus_uint32(output), None);
}
#[test]
fn test_sysfs_fallback_does_not_panic() {
let _status = network_status_from_sysfs();
}
}