zond_engine/system/interface/
os.rs1use pnet::datalink::NetworkInterface;
13
14#[cfg(target_os = "linux")]
15#[doc(inline)]
16pub use linux_impl::{is_physical, is_wireless};
17#[cfg(target_os = "macos")]
18#[doc(inline)]
19pub use macos_impl::{is_physical, is_wireless};
20#[cfg(target_os = "windows")]
21#[doc(inline)]
22pub use windows_impl::{is_physical, is_wireless};
23
24#[cfg(target_os = "linux")]
26pub mod linux_impl {
27 use super::*;
28 use std::path::Path;
29
30 pub fn is_physical(interface: &NetworkInterface) -> bool {
31 Path::new(&format!("/sys/class/net/{}/device", interface.name)).exists()
32 }
33
34 pub fn is_wireless(interface: &NetworkInterface) -> bool {
35 Path::new(&format!("sys/class/net/{}/wireless", interface.name)).exists()
36 }
37}
38
39#[cfg(target_os = "macos")]
40pub mod macos_impl {
41 use super::*;
42 use std::collections::HashSet;
43 use std::process::Command;
44 use std::sync::OnceLock;
45
46 struct HardwareInfo {
48 physical_devices: HashSet<String>,
49 wireless_devices: HashSet<String>,
50 }
51
52 fn get_hardware_info() -> &'static HardwareInfo {
54 static HARDWARE_INFO: OnceLock<HardwareInfo> = OnceLock::new();
55
56 HARDWARE_INFO.get_or_init(|| {
57 let mut physical = HashSet::new();
58 let mut wireless = HashSet::new();
59
60 if let Ok(output) = Command::new("networksetup")
62 .arg("-listallhardwareports")
63 .output()
64 {
65 let stdout = String::from_utf8_lossy(&output.stdout);
66 for line in stdout.lines() {
67 if let Some(device) = line.strip_prefix("Device: ") {
68 physical.insert(device.trim().to_string());
69 }
70 }
71 }
72
73 for device in &physical {
75 let is_wifi = Command::new("networksetup")
76 .arg("-getairportnetwork")
77 .arg(device)
78 .output()
79 .map(|out| out.status.success())
80 .unwrap_or(false);
81
82 if is_wifi {
83 wireless.insert(device.clone());
84 }
85 }
86
87 HardwareInfo {
88 physical_devices: physical,
89 wireless_devices: wireless,
90 }
91 })
92 }
93
94 pub fn is_physical(interface: &NetworkInterface) -> bool {
95 get_hardware_info()
96 .physical_devices
97 .contains(&interface.name)
98 }
99
100 pub fn is_wireless(interface: &NetworkInterface) -> bool {
101 get_hardware_info()
102 .wireless_devices
103 .contains(&interface.name)
104 }
105}
106
107#[cfg(target_os = "windows")]
108pub mod windows_impl {
109 use super::*;
110 use std::cell::RefCell;
111 use std::collections::HashSet;
112 use std::time::{Duration, Instant};
113
114 use windows_sys::Win32::NetworkManagement::IpHelper::{
115 FreeMibTable, GetIfTable2, MIB_IF_ROW2, MIB_IF_TABLE2,
116 };
117
118 const IF_TYPE_SOFTWARE_LOOPBACK: u32 = 24;
119 const IF_TYPE_TUNNEL: u32 = 131;
120 const IF_TYPE_IEEE80211: u32 = 71;
121 const IF_TYPE_PPP: u32 = 23;
122
123 struct WindowsInterfaceInfo {
124 physical_devices: HashSet<String>,
125 wireless_devices: HashSet<String>,
126 }
127
128 thread_local! {
131 static INTERFACE_CACHE: RefCell<Option<(Instant, WindowsInterfaceInfo)>> = RefCell::new(None);
132 }
133
134 fn fetch_windows_interface_info() -> WindowsInterfaceInfo {
135 let mut physical = HashSet::new();
136 let mut wireless = HashSet::new();
137
138 unsafe {
139 let mut table_ptr: *mut MIB_IF_TABLE2 = std::ptr::null_mut();
140 if GetIfTable2(&mut table_ptr) == 0 && !table_ptr.is_null() {
141 let table = &*table_ptr;
142 let rows = std::slice::from_raw_parts(
143 &table.Table[0] as *const MIB_IF_ROW2,
144 table.NumEntries as usize,
145 );
146
147 for row in rows {
148 let is_software = row.Type == IF_TYPE_SOFTWARE_LOOPBACK
149 || row.Type == IF_TYPE_TUNNEL
150 || row.Type == IF_TYPE_PPP;
151
152 let bitfield = row.InterfaceAndOperStatusFlags._bitfield;
153 let is_hardware_bit = (bitfield & 1) != 0;
154 let has_connector_bit = (bitfield & (1 << 2)) != 0;
155
156 let guid = row.InterfaceGuid;
157 let guid_str = format!(
158 "{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
159 guid.data1,
160 guid.data2,
161 guid.data3,
162 guid.data4[0],
163 guid.data4[1],
164 guid.data4[2],
165 guid.data4[3],
166 guid.data4[4],
167 guid.data4[5],
168 guid.data4[6],
169 guid.data4[7]
170 );
171
172 if !is_software && (is_hardware_bit || has_connector_bit) {
174 physical.insert(guid_str.clone());
175 }
176
177 if row.Type == IF_TYPE_IEEE80211 {
178 wireless.insert(guid_str);
179 }
180 }
181 FreeMibTable(table_ptr as *mut std::ffi::c_void);
182 }
183 }
184
185 WindowsInterfaceInfo {
186 physical_devices: physical,
187 wireless_devices: wireless,
188 }
189 }
190
191 fn with_cache<F, R>(f: F) -> R
193 where
194 F: FnOnce(&WindowsInterfaceInfo) -> R,
195 {
196 INTERFACE_CACHE.with(|cache| {
197 let mut cache_ref = cache.borrow_mut();
198 let now = Instant::now();
199
200 let needs_update = match cache_ref.as_ref() {
201 Some((timestamp, _)) => now.duration_since(*timestamp) > Duration::from_secs(2),
202 None => true,
203 };
204
205 if needs_update {
206 *cache_ref = Some((now, fetch_windows_interface_info()));
207 }
208
209 f(&cache_ref.as_ref().unwrap().1)
211 })
212 }
213
214 pub fn is_physical(interface: &NetworkInterface) -> bool {
215 with_cache(|info| {
216 let name = interface
217 .name
218 .strip_prefix(r"\Device\NPF_")
219 .unwrap_or(&interface.name);
220 info.physical_devices.contains(name)
221 })
222 }
223
224 pub fn is_wireless(interface: &NetworkInterface) -> bool {
225 with_cache(|info| {
226 let name = interface
227 .name
228 .strip_prefix(r"\Device\NPF_")
229 .unwrap_or(&interface.name);
230 info.wireless_devices.contains(name)
231 })
232 }
233}