Files
sabia/crates/sabia-core/src/iface.rs
T
Lucas RayanandClaude Sonnet 5 55e19a8e5b
CI / Format and clippy (push) Successful in 1m13s
CI / Dependency audit (push) Successful in 13s
CI / Tests (Linux) (push) Successful in 1m35s
Release / Create release (push) Successful in 7s
CI / Check desktop app (Linux) (push) Successful in 3m19s
Release / CLI (linux-aarch64) (push) Successful in 1m3s
Release / CLI (linux-x86_64) (push) Successful in 1m28s
Release / Desktop app (linux-aarch64) (push) Successful in 7m16s
Release / Desktop app (linux-x86_64) (push) Successful in 10m17s
Release / Desktop app (windows) (push) Successful in 4m54s
chore(i18n): finish translating code comments and scripts to English
Completes the English-only standardization for code, comments, scripts
and CI templates (UI/CLI-facing strings stay bilingual pt-BR/en,
untouched). Covers the remaining crates/, apps/gui/ui/ and
apps/gui/src-tauri/ comments, .gitea/ workflow and template comments,
.githooks/ hooks, and the scripts/ source comments for the files
already renamed in a prior commit (check.sh, generate-logo.py,
install-hooks.sh).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD
2026-10-30 20:44:39 -03:00

120 lines
3.3 KiB
Rust

//! Network interface enumeration and 802.11 wireless phy identification.
//!
//! Reads directly from `/sys/class/net` on Linux, falling back to `iw` for
//! detailed interface mode inspection when available.
use crate::error::{Result, SabiaError};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, serde::Serialize)]
pub struct Interface {
pub name: String,
pub mac: String,
/// Wireless `wiphy` name (e.g. `"phy0"`), present only on 802.11 interfaces.
pub phy: Option<String>,
pub mode: Mode,
pub up: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
pub enum Mode {
Managed,
Monitor,
Unknown,
}
fn net_root() -> &'static Path {
Path::new("/sys/class/net")
}
/// Lists all network interfaces on the system (wireless interfaces have `phy.is_some()`).
pub fn list_all() -> Result<Vec<Interface>> {
let mut out = Vec::new();
for entry in fs::read_dir(net_root())? {
let entry = entry?;
let name = entry.file_name().to_string_lossy().into_owned();
if name == "lo" {
continue;
}
out.push(read_interface(&name)?);
}
out.sort_by(|a, b| a.name.cmp(&b.name));
Ok(out)
}
/// Lists only 802.11 wireless interfaces (`phy80211` present).
pub fn list_wireless() -> Result<Vec<Interface>> {
Ok(list_all()?
.into_iter()
.filter(|i| i.phy.is_some())
.collect())
}
pub fn get(name: &str) -> Result<Interface> {
let iface = read_interface(name)?;
if iface.phy.is_none() {
return Err(SabiaError::NotWireless(name.to_string()));
}
Ok(iface)
}
fn read_interface(name: &str) -> Result<Interface> {
let base: PathBuf = net_root().join(name);
if !base.exists() {
return Err(SabiaError::InterfaceNotFound(name.to_string()));
}
let mac = fs::read_to_string(base.join("address"))
.map(|s| s.trim().to_string())
.unwrap_or_default();
let up = fs::read_to_string(base.join("operstate"))
.map(|s| s.trim() == "up")
.unwrap_or(false);
let phy80211 = base.join("phy80211");
let phy = if phy80211.is_dir() {
fs::read_link(&phy80211)
.ok()
.and_then(|p| p.file_name().map(|f| f.to_string_lossy().into_owned()))
.or_else(|| {
// On some kernels `phy80211` is a directory rather than a symlink;
// read the `"phyNN"` subdirectory inside it.
fs::read_dir(&phy80211)
.ok()
.and_then(|mut rd| rd.next())
.and_then(|e| e.ok())
.map(|e| e.file_name().to_string_lossy().into_owned())
})
} else {
None
};
let mode = if phy.is_some() {
read_wireless_mode(name)
} else {
Mode::Unknown
};
Ok(Interface {
name: name.to_string(),
mac,
phy,
mode,
up,
})
}
/// Reads the interface operating mode (`managed`/`monitor`) via `/sys/class/net/<if>/type`
/// and `iw dev <if> info` when available.
///
///
fn read_wireless_mode(name: &str) -> Mode {
match crate::netlink::iw_dev_type(name) {
Some(Mode::Monitor) => Mode::Monitor,
Some(Mode::Managed) => Mode::Managed,
_ => Mode::Unknown,
}
}