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"
377 .parse()
378 .expect("test: valid IP address literal should parse")],
379 interface_type: InterfaceType::WiFi,
380 is_active: true,
381 },
382 );
383
384 interfaces.insert(
386 "wwan0".to_string(),
387 NetworkInterface {
388 name: "wwan0".to_string(),
389 addresses: vec!["10.0.0.100"
390 .parse()
391 .expect("test: valid IP address literal should parse")],
392 interface_type: InterfaceType::Cellular,
393 is_active: true,
394 },
395 );
396
397 let primary = monitor.select_primary_interface(&interfaces);
398 assert!(primary.is_some());
399 assert_eq!(
401 primary
402 .expect("test: primary interface should be Some for active interfaces")
403 .interface_type,
404 InterfaceType::WiFi
405 );
406 }
407
408 #[test]
409 fn test_ethernet_preferred_over_wifi() {
410 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
411 let mut interfaces = HashMap::new();
412
413 interfaces.insert(
414 "eth0".to_string(),
415 NetworkInterface {
416 name: "eth0".to_string(),
417 addresses: vec!["192.168.1.50"
418 .parse()
419 .expect("test: valid IP address literal should parse")],
420 interface_type: InterfaceType::Ethernet,
421 is_active: true,
422 },
423 );
424
425 interfaces.insert(
426 "wlan0".to_string(),
427 NetworkInterface {
428 name: "wlan0".to_string(),
429 addresses: vec!["192.168.1.100"
430 .parse()
431 .expect("test: valid IP address literal should parse")],
432 interface_type: InterfaceType::WiFi,
433 is_active: true,
434 },
435 );
436
437 let primary = monitor.select_primary_interface(&interfaces);
438 assert!(primary.is_some());
439 assert_eq!(
440 primary
441 .expect("test: primary interface should be Some when ethernet is active")
442 .interface_type,
443 InterfaceType::Ethernet
444 );
445 }
446
447 #[test]
448 fn test_inactive_interface_not_selected() {
449 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
450 let mut interfaces = HashMap::new();
451
452 interfaces.insert(
453 "wlan0".to_string(),
454 NetworkInterface {
455 name: "wlan0".to_string(),
456 addresses: vec!["192.168.1.100"
457 .parse()
458 .expect("test: valid IP address literal should parse")],
459 interface_type: InterfaceType::WiFi,
460 is_active: false, },
462 );
463
464 let primary = monitor.select_primary_interface(&interfaces);
465 assert!(primary.is_none());
466 }
467
468 #[test]
469 fn test_interface_without_addresses_not_selected() {
470 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
471 let mut interfaces = HashMap::new();
472
473 interfaces.insert(
474 "wlan0".to_string(),
475 NetworkInterface {
476 name: "wlan0".to_string(),
477 addresses: vec![], interface_type: InterfaceType::WiFi,
479 is_active: true,
480 },
481 );
482
483 let primary = monitor.select_primary_interface(&interfaces);
484 assert!(primary.is_none());
485 }
486
487 #[test]
488 fn test_loopback_filtering() {
489 let config = NetworkMonitorConfig {
490 monitor_loopback: false,
491 ..Default::default()
492 };
493 let monitor = NetworkMonitor::new(config);
494 let mut interfaces = HashMap::new();
495
496 interfaces.insert(
497 "lo".to_string(),
498 NetworkInterface {
499 name: "lo".to_string(),
500 addresses: vec!["127.0.0.1"
501 .parse()
502 .expect("test: valid loopback IP literal should parse")],
503 interface_type: InterfaceType::Loopback,
504 is_active: true,
505 },
506 );
507
508 let primary = monitor.select_primary_interface(&interfaces);
509 assert!(primary.is_none()); }
511
512 #[test]
513 fn test_stats_initialization() {
514 let monitor = NetworkMonitor::new(NetworkMonitorConfig::default());
515 let stats = monitor.stats();
516 assert_eq!(stats.interfaces_added, 0);
517 assert_eq!(stats.interfaces_removed, 0);
518 assert_eq!(stats.primary_changes, 0);
519 assert_eq!(stats.address_changes, 0);
520 assert_eq!(stats.total_changes, 0);
521 }
522
523 #[test]
524 fn test_mobile_config() {
525 let config = NetworkMonitorConfig::mobile();
526 assert_eq!(config.poll_interval, Duration::from_secs(2));
527 assert!(!config.monitor_loopback);
528 }
529
530 #[test]
531 fn test_server_config() {
532 let config = NetworkMonitorConfig::server();
533 assert_eq!(config.poll_interval, Duration::from_secs(30));
534 assert!(!config.monitor_loopback);
535 }
536}