db6fc65ee1
- get_local_ip: construit d'abord la liste des IPs physiques (getifaddrs avec IFF_POINTOPOINT exclu + type=1 ARPHRD_ETHER requis), puis vérifie que l'IP choisie par le UDP-connect-trick en fait partie → évite de retourner une IP VPN même quand le trafic y est routé - is_physical: remplace le filtrage par préfixe de nom par type kernel /sys/class/net/<iface>/type == 1 (Ethernet/WiFi) ; exclut WireGuard (type 65534), tunnels et autres interfaces virtuelles nommées librement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.7 KiB
Rust
106 lines
3.7 KiB
Rust
use std::mem::MaybeUninit;
|
|
|
|
fn local_hhmm() -> (u32, u32) {
|
|
let now = std::time::SystemTime::now()
|
|
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs() as i64;
|
|
let mut tm = MaybeUninit::<libc::tm>::uninit();
|
|
unsafe { libc::localtime_r(&now, tm.as_mut_ptr()) };
|
|
let tm = unsafe { tm.assume_init() };
|
|
(tm.tm_hour as u32, tm.tm_min as u32)
|
|
}
|
|
|
|
pub fn current_hhmm() -> (u32, u32) {
|
|
local_hhmm()
|
|
}
|
|
|
|
pub fn current_yday() -> u32 {
|
|
let now = std::time::SystemTime::now()
|
|
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
|
.unwrap_or_default()
|
|
.as_secs() as i64;
|
|
let mut tm = MaybeUninit::<libc::tm>::uninit();
|
|
unsafe { libc::localtime_r(&now, tm.as_mut_ptr()) };
|
|
unsafe { tm.assume_init() }.tm_yday as u32
|
|
}
|
|
|
|
fn is_physical(name: &str) -> bool {
|
|
// Type 1 = ARPHRD_ETHER (Ethernet + WiFi). WireGuard = 65534, tunnels = autres.
|
|
let itype: u32 = std::fs::read_to_string(format!("/sys/class/net/{}/type", name))
|
|
.ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
|
|
if itype != 1 { return false; }
|
|
// Exclut bridges et interfaces Docker par nom (type 1 aussi)
|
|
!name.starts_with("br-") && !name.starts_with("docker")
|
|
&& !name.starts_with("virbr") && !name.starts_with("veth")
|
|
}
|
|
|
|
fn read_sysfs(iface: &str, file: &str) -> Option<String> {
|
|
std::fs::read_to_string(format!("/sys/class/net/{}/{}", iface, file))
|
|
.ok()
|
|
.map(|s| s.trim().to_string())
|
|
}
|
|
|
|
fn is_wifi(name: &str) -> bool {
|
|
std::path::Path::new(&format!("/sys/class/net/{}/wireless", name)).exists()
|
|
}
|
|
|
|
fn wol_status(name: &str) -> Option<bool> {
|
|
let out = std::process::Command::new("ethtool").arg(name).output().ok()?;
|
|
let text = String::from_utf8_lossy(&out.stdout);
|
|
for line in text.lines() {
|
|
let t = line.trim();
|
|
if t.starts_with("Wake-on:") {
|
|
let val = t.split(':').nth(1)?.trim().to_string();
|
|
return Some(val != "d" && !val.is_empty());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn iperf_mbps(server_ip: &str, port: u16) -> Option<f64> {
|
|
std::process::Command::new("which").arg("iperf3")
|
|
.output().ok()
|
|
.filter(|o| o.status.success())?;
|
|
let port_str = port.to_string();
|
|
let out = std::process::Command::new("iperf3")
|
|
.args(["-c", server_ip, "-p", &port_str, "-J", "-t", "5", "-P", "1"])
|
|
.output().ok()?;
|
|
let json = String::from_utf8_lossy(&out.stdout);
|
|
let v: serde_json::Value = serde_json::from_str(&json).ok()?;
|
|
let bps = v["end"]["sum_received"]["bits_per_second"].as_f64()?;
|
|
Some((bps / 1_000_000.0 * 10.0).round() / 10.0)
|
|
}
|
|
|
|
pub fn collect(server_ip: &str, iperf3_port: u16) -> Vec<crate::payload::NetworkInterface> {
|
|
let entries = match std::fs::read_dir("/sys/class/net") {
|
|
Ok(e) => e,
|
|
Err(_) => return vec![],
|
|
};
|
|
let mut ifaces: Vec<String> = entries
|
|
.flatten()
|
|
.map(|e| e.file_name().into_string().unwrap_or_default())
|
|
.filter(|n| is_physical(n))
|
|
.collect();
|
|
ifaces.sort();
|
|
if ifaces.is_empty() { return vec![]; }
|
|
|
|
let iperf = iperf_mbps(server_ip, iperf3_port);
|
|
|
|
ifaces.iter().map(|name| {
|
|
let speed = read_sysfs(name, "speed")
|
|
.and_then(|s| s.parse::<i64>().ok())
|
|
.filter(|&v| v > 0);
|
|
let mac = read_sysfs(name, "address").unwrap_or_default();
|
|
let wifi = is_wifi(name);
|
|
crate::payload::NetworkInterface {
|
|
name: name.clone(),
|
|
if_type: if wifi { "wifi".to_string() } else { "ethernet".to_string() },
|
|
speed_mbps: speed,
|
|
mac,
|
|
wol: if wifi { None } else { wol_status(name) },
|
|
iperf_mbps: iperf,
|
|
}
|
|
}).collect()
|
|
}
|