1use parking_lot::RwLock;
8use std::collections::HashMap;
9use std::net::IpAddr;
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12use thiserror::Error;
13use tracing::{debug, info, warn};
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct NetworkInterface {
18 pub name: String,
20 pub addresses: Vec<IpAddr>,
22 pub interface_type: InterfaceType,
24 pub is_active: bool,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub enum InterfaceType {
31 Ethernet,
33 WiFi,
35 Cellular,
37 Loopback,
39 Other,
41}
42
43impl InterfaceType {
44 pub fn from_name(name: &str) -> Self {
46 let lower = name.to_lowercase();
47 if lower.contains("eth") || lower.contains("en") && lower.contains("p") {
48 InterfaceType::Ethernet
49 } else if lower.contains("wlan") || lower.contains("wifi") || lower.contains("wl") {
50 InterfaceType::WiFi
51 } else if lower.contains("cellular") || lower.contains("wwan") || lower.contains("ppp") {
52 InterfaceType::Cellular
53 } else if lower.contains("lo") {
54 InterfaceType::Loopback
55 } else {
56 InterfaceType::Other
57 }
58 }
59
60 pub fn priority(&self) -> u8 {
62 match self {
63 InterfaceType::Ethernet => 3,
64 InterfaceType::WiFi => 2,
65 InterfaceType::Cellular => 1,
66 InterfaceType::Loopback => 0,
67 InterfaceType::Other => 0,
68 }
69 }
70}
71
72#[derive(Debug, Clone)]
74pub enum NetworkChange {
75 InterfaceAdded(NetworkInterface),
77 InterfaceRemoved(NetworkInterface),
79 PrimaryInterfaceChanged {
81 old: Option<NetworkInterface>,
82 new: NetworkInterface,
83 },
84 AddressChanged {
86 interface: String,
87 old_addresses: Vec<IpAddr>,
88 new_addresses: Vec<IpAddr>,
89 },
90 InterfaceUp(NetworkInterface),
92 InterfaceDown(NetworkInterface),
94}
95
96#[derive(Debug, Clone)]
98pub struct NetworkMonitorConfig {
99 pub poll_interval: Duration,
101 pub debounce_duration: Duration,
103 pub monitor_loopback: bool,
105}
106
107impl Default for NetworkMonitorConfig {
108 fn default() -> Self {
109 Self {
110 poll_interval: Duration::from_secs(5),
111 debounce_duration: Duration::from_millis(500),
112 monitor_loopback: false,
113 }
114 }
115}
116
117impl NetworkMonitorConfig {
118 pub fn mobile() -> Self {
120 Self {
121 poll_interval: Duration::from_secs(2), debounce_duration: Duration::from_millis(1000), monitor_loopback: false,
124 }
125 }
126
127 pub fn server() -> Self {
129 Self {
130 poll_interval: Duration::from_secs(30), debounce_duration: Duration::from_millis(100),
132 monitor_loopback: false,
133 }
134 }
135}
136
137pub struct NetworkMonitor {
139 config: NetworkMonitorConfig,
140 interfaces: Arc<RwLock<HashMap<String, NetworkInterface>>>,
142 primary_interface: Arc<RwLock<Option<NetworkInterface>>>,
144 last_change: Arc<RwLock<Instant>>,
146 stats: Arc<RwLock<NetworkMonitorStats>>,
148}
149
150#[derive(Debug, Clone, Default)]
152pub struct NetworkMonitorStats {
153 pub interfaces_added: usize,
155 pub interfaces_removed: usize,
157 pub primary_changes: usize,
159 pub address_changes: usize,
161 pub total_changes: usize,
163}
164
165#[derive(Debug, Error)]
167pub enum NetworkMonitorError {
168 #[error("Failed to get network interfaces: {0}")]
169 InterfaceQueryFailed(String),
170
171 #[error("No active network interfaces found")]
172 NoActiveInterfaces,
173}
174
175impl NetworkMonitor {
176 pub fn new(config: NetworkMonitorConfig) -> Self {
178 Self {
179 config,
180 interfaces: Arc::new(RwLock::new(HashMap::new())),
181 primary_interface: Arc::new(RwLock::new(None)),
182 last_change: Arc::new(RwLock::new(Instant::now())),
183 stats: Arc::new(RwLock::new(NetworkMonitorStats::default())),
184 }
185 }
186
187 pub fn get_interfaces(&self) -> HashMap<String, NetworkInterface> {
189 self.interfaces.read().clone()
190 }
191
192 pub fn get_primary_interface(&self) -> Option<NetworkInterface> {
194 self.primary_interface.read().clone()
195 }
196
197 pub fn check_for_changes(&self) -> Result<Vec<NetworkChange>, NetworkMonitorError> {
201 let current_interfaces = self.query_system_interfaces()?;
203
204 {
206 let last_change = *self.last_change.read();
207 if last_change.elapsed() < self.config.debounce_duration {
208 return Ok(Vec::new());
209 }
210 }
211
212 let mut changes = Vec::new();
213 let mut interfaces_lock = self.interfaces.write();
214
215 for (name, new_iface) in ¤t_interfaces {
217 if let Some(old_iface) = interfaces_lock.get(name) {
218 if old_iface.addresses != new_iface.addresses {
220 debug!(
221 "Address changed on interface {}: {:?} -> {:?}",
222 name, old_iface.addresses, new_iface.addresses
223 );
224 changes.push(NetworkChange::AddressChanged {
225 interface: name.clone(),
226 old_addresses: old_iface.addresses.clone(),
227 new_addresses: new_iface.addresses.clone(),
228 });
229 self.stats.write().address_changes += 1;
230 }
231
232 if old_iface.is_active != new_iface.is_active {
234 if new_iface.is_active {
235 info!("Interface {} is now active", name);
236 changes.push(NetworkChange::InterfaceUp(new_iface.clone()));
237 } else {
238 info!("Interface {} is now inactive", name);
239 changes.push(NetworkChange::InterfaceDown(new_iface.clone()));
240 }
241 }
242 } else {
243 info!("New interface detected: {}", name);
245 changes.push(NetworkChange::InterfaceAdded(new_iface.clone()));
246 self.stats.write().interfaces_added += 1;
247 }
248 }
249
250 for (name, old_iface) in interfaces_lock.iter() {
252 if !current_interfaces.contains_key(name) {
253 info!("Interface removed: {}", name);
254 changes.push(NetworkChange::InterfaceRemoved(old_iface.clone()));
255 self.stats.write().interfaces_removed += 1;
256 }
257 }
258
259 *interfaces_lock = current_interfaces.clone();
261 drop(interfaces_lock);
262
263 let old_primary = self.primary_interface.read().clone();
265 let new_primary = self.select_primary_interface(¤t_interfaces);
266
267 if old_primary != new_primary {
268 if let Some(new) = &new_primary {
269 info!(
270 "Primary interface changed: {:?} -> {}",
271 old_primary.as_ref().map(|i| i.name.as_str()),
272 new.name
273 );
274 changes.push(NetworkChange::PrimaryInterfaceChanged {
275 old: old_primary,
276 new: new.clone(),
277 });
278 self.stats.write().primary_changes += 1;
279 }
280 *self.primary_interface.write() = new_primary;
281 }
282
283 if !changes.is_empty() {
284 *self.last_change.write() = Instant::now();
285 self.stats.write().total_changes += changes.len();
286 }
287
288 Ok(changes)
289 }
290
291 fn select_primary_interface(
293 &self,
294 interfaces: &HashMap<String, NetworkInterface>,
295 ) -> Option<NetworkInterface> {
296 interfaces
297 .values()
298 .filter(|iface| {
299 iface.is_active
300 && !iface.addresses.is_empty()
301 && (self.config.monitor_loopback
302 || iface.interface_type != InterfaceType::Loopback)
303 })
304 .max_by_key(|iface| iface.interface_type.priority())
305 .cloned()
306 }
307
308 fn query_system_interfaces(
314 &self,
315 ) -> Result<HashMap<String, NetworkInterface>, NetworkMonitorError> {
316 warn!("Using mock network interface detection - implement platform-specific querying for production use");
320
321 let interfaces = HashMap::new();
322
323 Ok(interfaces)
327 }
328
329 pub fn stats(&self) -> NetworkMonitorStats {
331 self.stats.read().clone()
332 }
333
334 pub fn reset_stats(&self) {
336 *self.stats.write() = NetworkMonitorStats::default();
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn test_interface_type_from_name() {
346 assert_eq!(InterfaceType::from_name("eth0"), InterfaceType::Ethernet);
347 assert_eq!(InterfaceType::from_name("wlan0"), InterfaceType::WiFi);
348 assert_eq!(InterfaceType::from_name("wwan0"), InterfaceType::Cellular);
349 assert_eq!(InterfaceType::from_name("lo"), InterfaceType::Loopback);
350 }
351
352 #[test]
353 fn test_interface_priority() {
354 assert!(InterfaceType::Ethernet.priority() > InterfaceType::WiFi.priority());
355 assert!(InterfaceType::WiFi.priority() > InterfaceType::Cellular.priority());
356 assert!(InterfaceType::Cellular.priority() > InterfaceType::Loopback.priority());
357 }
358
359 #[test]
360 fn test_network_monitor_creation() {
361 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
362 assert!(monitor.get_interfaces().is_empty());
363 assert!(monitor.get_primary_interface().is_none());
364 }
365
366 #[test]
367 fn test_select_primary_interface() {
368 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
369 let mut interfaces = HashMap::new();
370
371 interfaces.insert(
373 "wlan0".to_string(),
374 NetworkInterface {
375 name: "wlan0".to_string(),
376 addresses: vec!["192.168.1.100".parse().unwrap()],
377 interface_type: InterfaceType::WiFi,
378 is_active: true,
379 },
380 );
381
382 interfaces.insert(
384 "wwan0".to_string(),
385 NetworkInterface {
386 name: "wwan0".to_string(),
387 addresses: vec!["10.0.0.100".parse().unwrap()],
388 interface_type: InterfaceType::Cellular,
389 is_active: true,
390 },
391 );
392
393 let primary = monitor.select_primary_interface(&interfaces);
394 assert!(primary.is_some());
395 assert_eq!(primary.unwrap().interface_type, InterfaceType::WiFi);
397 }
398
399 #[test]
400 fn test_ethernet_preferred_over_wifi() {
401 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
402 let mut interfaces = HashMap::new();
403
404 interfaces.insert(
405 "eth0".to_string(),
406 NetworkInterface {
407 name: "eth0".to_string(),
408 addresses: vec!["192.168.1.50".parse().unwrap()],
409 interface_type: InterfaceType::Ethernet,
410 is_active: true,
411 },
412 );
413
414 interfaces.insert(
415 "wlan0".to_string(),
416 NetworkInterface {
417 name: "wlan0".to_string(),
418 addresses: vec!["192.168.1.100".parse().unwrap()],
419 interface_type: InterfaceType::WiFi,
420 is_active: true,
421 },
422 );
423
424 let primary = monitor.select_primary_interface(&interfaces);
425 assert!(primary.is_some());
426 assert_eq!(primary.unwrap().interface_type, InterfaceType::Ethernet);
427 }
428
429 #[test]
430 fn test_inactive_interface_not_selected() {
431 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
432 let mut interfaces = HashMap::new();
433
434 interfaces.insert(
435 "wlan0".to_string(),
436 NetworkInterface {
437 name: "wlan0".to_string(),
438 addresses: vec!["192.168.1.100".parse().unwrap()],
439 interface_type: InterfaceType::WiFi,
440 is_active: false, },
442 );
443
444 let primary = monitor.select_primary_interface(&interfaces);
445 assert!(primary.is_none());
446 }
447
448 #[test]
449 fn test_interface_without_addresses_not_selected() {
450 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
451 let mut interfaces = HashMap::new();
452
453 interfaces.insert(
454 "wlan0".to_string(),
455 NetworkInterface {
456 name: "wlan0".to_string(),
457 addresses: vec![], interface_type: InterfaceType::WiFi,
459 is_active: true,
460 },
461 );
462
463 let primary = monitor.select_primary_interface(&interfaces);
464 assert!(primary.is_none());
465 }
466
467 #[test]
468 fn test_loopback_filtering() {
469 let config = NetworkMonitorConfig {
470 monitor_loopback: false,
471 ..Default::default()
472 };
473 let monitor = NetworkMonitor::new(config);
474 let mut interfaces = HashMap::new();
475
476 interfaces.insert(
477 "lo".to_string(),
478 NetworkInterface {
479 name: "lo".to_string(),
480 addresses: vec!["127.0.0.1".parse().unwrap()],
481 interface_type: InterfaceType::Loopback,
482 is_active: true,
483 },
484 );
485
486 let primary = monitor.select_primary_interface(&interfaces);
487 assert!(primary.is_none()); }
489
490 #[test]
491 fn test_stats_initialization() {
492 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
493 let stats = monitor.stats();
494 assert_eq!(stats.interfaces_added, 0);
495 assert_eq!(stats.interfaces_removed, 0);
496 assert_eq!(stats.primary_changes, 0);
497 assert_eq!(stats.address_changes, 0);
498 assert_eq!(stats.total_changes, 0);
499 }
500
501 #[test]
502 fn test_mobile_config() {
503 let config = NetworkMonitorConfig::mobile();
504 assert_eq!(config.poll_interval, Duration::from_secs(2));
505 assert!(!config.monitor_loopback);
506 }
507
508 #[test]
509 fn test_server_config() {
510 let config = NetworkMonitorConfig::server();
511 assert_eq!(config.poll_interval, Duration::from_secs(30));
512 assert!(!config.monitor_loopback);
513 }
514}