diff --git a/Cargo.lock b/Cargo.lock index 7d5854a2..973807c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,7 +280,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -788,7 +788,7 @@ dependencies = [ "libc", "socket2", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -799,9 +799,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506" dependencies = [ "once_cell", "wasm-bindgen", @@ -1113,7 +1113,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1952,9 +1952,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee" dependencies = [ "cfg-if", "once_cell", @@ -1965,9 +1965,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a" dependencies = [ "cfg-if", "futures-util", @@ -1979,9 +1979,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1989,9 +1989,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571" dependencies = [ "bumpalo", "proc-macro2", @@ -2002,18 +2002,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.58" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45649196a53b0b7a15101d845d44d2dda7374fc1b5b5e2bbf58b7577ff4b346d" +checksum = "12430eab93df2be01b6575bf8e05700945dafa62d6fa40faa07b0ea9afd8add1" dependencies = [ "async-trait", "cast", @@ -2033,9 +2033,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.58" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f579cdd0123ac74b94e1a4a72bd963cf30ebac343f2df347da0b8df24cdebed2" +checksum = "ce7d6debc1772c3502c727c8c47180c040c8741f7fcf6e731d6ef57818d59ae2" dependencies = [ "proc-macro2", "quote", @@ -2044,9 +2044,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-shared" -version = "0.2.108" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8145dd1593bf0fb137dbfa85b8be79ec560a447298955877804640e40c2d6ea" +checksum = "c4f79c547a8daa04318dac7646f579a016f819452c34bcb14e8dda0e77a4386c" [[package]] name = "wasm-tracing" @@ -2061,9 +2061,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962" dependencies = [ "js-sys", "wasm-bindgen", @@ -2101,7 +2101,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] diff --git a/netwatch/src/interfaces.rs b/netwatch/src/interfaces.rs index 8d6448df..44a210c1 100644 --- a/netwatch/src/interfaces.rs +++ b/netwatch/src/interfaces.rs @@ -292,6 +292,16 @@ impl State { } } + // Check for new interesting interfaces not present in old state + for (iname, i) in &self.interfaces { + if !is_interesting_interface(i.name()) { + continue; + } + if !old.interfaces.contains_key(iname) { + return true; + } + } + false } } @@ -417,16 +427,16 @@ fn prefixes_major_equal(a: impl Iterator, b: impl Iterator return true, + (Some(a), Some(b)) if a == b => continue, + _ => return false, } } - - true } #[cfg(test)] @@ -435,6 +445,33 @@ mod tests { use super::*; + #[test] + fn test_is_major_change_identical() { + let a = State::fake(); + let b = State::fake(); + assert!(!a.is_major_change(&b)); + } + + #[test] + fn test_is_major_change_new_interface_added() { + let old = State::fake(); + let mut new = State::fake(); + // Add a new interesting interface to new state + let mut iface = Interface::fake(); + iface.iface.index = 10; + iface.iface.name = "eth1".to_string(); + new.interfaces.insert("eth1".to_string(), iface); + assert!(new.is_major_change(&old)); + } + + #[test] + fn test_is_major_change_interface_removed() { + let old = State::fake(); + let mut new = State::fake(); + new.interfaces.clear(); + assert!(new.is_major_change(&old)); + } + #[tokio::test] async fn test_default_route() { let default_route = DefaultRouteDetails::new() @@ -449,6 +486,42 @@ mod tests { println!("home router: {home_router:#?}"); } + #[test] + fn test_prefixes_major_equal() { + use std::net::Ipv4Addr; + + let a1 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap()); + let a2 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(10, 0, 0, 1), 8).unwrap()); + let a3 = IpNet::V4(Ipv4Net::new(Ipv4Addr::new(172, 16, 0, 1), 16).unwrap()); + + // equal lists + assert!(prefixes_major_equal( + vec![a1.clone(), a2.clone()].into_iter(), + vec![a1.clone(), a2.clone()].into_iter(), + )); + + // both empty + assert!(prefixes_major_equal(std::iter::empty(), std::iter::empty(),)); + + // different prefixes + assert!(!prefixes_major_equal( + vec![a1.clone()].into_iter(), + vec![a2.clone()].into_iter(), + )); + + // a has extra prefix + assert!(!prefixes_major_equal( + vec![a1.clone(), a2.clone(), a3.clone()].into_iter(), + vec![a1.clone(), a2.clone()].into_iter(), + )); + + // b has extra prefix + assert!(!prefixes_major_equal( + vec![a1.clone(), a2.clone()].into_iter(), + vec![a1.clone(), a2.clone(), a3.clone()].into_iter(), + )); + } + #[test] fn test_is_usable_v6() { let loopback = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0x1); diff --git a/netwatch/src/interfaces/bsd.rs b/netwatch/src/interfaces/bsd.rs index 3db2b6f3..bed3cbed 100644 --- a/netwatch/src/interfaces/bsd.rs +++ b/netwatch/src/interfaces/bsd.rs @@ -461,7 +461,8 @@ pub fn parse_rib(typ: RIBType, data: &[u8]) -> Result, RouteErr ensure!(l != 0, RouteError::InvalidMessage); ensure!(b.len() >= l as usize, RouteError::MessageTooShort); if b[2] as i32 != ROUTING_STACK.rtm_version { - // b = b[l:]; + b = &b[l as usize..]; + nskips += 1; continue; } match ROUTING_STACK.wire_formats.get(&(b[3] as i32)) { @@ -1015,6 +1016,29 @@ fn parse_default_addr(b: &[u8]) -> Result { mod tests { use super::*; + #[test] + fn test_parse_rib_skips_version_mismatch() { + let wrong_version = (ROUTING_STACK.rtm_version as u8).wrapping_add(1); + let msg_len: u16 = 8; + let mut buf = vec![0u8; msg_len as usize]; + buf[..2].copy_from_slice(&msg_len.to_ne_bytes()); + buf[2] = wrong_version; + buf[3] = 0; // arbitrary type + + #[cfg(any(target_os = "macos", target_os = "ios"))] + let rib_type = libc::NET_RT_IFLIST2; + #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] + let rib_type = libc::NET_RT_IFLIST; + #[cfg(target_os = "openbsd")] + let rib_type = libc::NET_RT_IFLIST; + + let msgs = parse_rib(rib_type, &buf).unwrap(); + assert!( + msgs.is_empty(), + "version-mismatched message should be skipped" + ); + } + #[test] fn test_fetch_parse_routing_table() { let rib_raw = fetch_routing_table().unwrap(); diff --git a/netwatch/src/netmon/actor.rs b/netwatch/src/netmon/actor.rs index c2e55530..e4dc0000 100644 --- a/netwatch/src/netmon/actor.rs +++ b/netwatch/src/netmon/actor.rs @@ -90,33 +90,32 @@ impl Actor { pub(super) async fn run(mut self) { const DEBOUNCE: Duration = Duration::from_millis(250); - let mut last_event = None; - let mut debounce_interval = time::interval(DEBOUNCE); + let mut pending_change = false; + let mut pending_time_jump = false; + let debounce = time::sleep(DEBOUNCE); + tokio::pin!(debounce); let mut wall_time_interval = time::interval(POLL_WALL_TIME_INTERVAL); loop { tokio::select! { - biased; - - _ = debounce_interval.tick() => { - if let Some(time_jumped) = last_event.take() { - self.handle_potential_change(time_jumped).await; - } + _ = &mut debounce, if pending_change || pending_time_jump => { + self.handle_potential_change(pending_time_jump).await; + pending_change = false; + pending_time_jump = false; } _ = wall_time_interval.tick() => { trace!("tick: wall_time_interval"); if self.check_wall_time_advance() { - // Trigger potential change - last_event.replace(true); - debounce_interval.reset_immediately(); + pending_time_jump = true; + debounce.as_mut().reset(Instant::now() + DEBOUNCE); } } event = self.mon_receiver.recv() => { match event { Some(NetworkMessage::Change) => { trace!("network activity detected"); - last_event.replace(false); - debounce_interval.reset_immediately(); + pending_change = true; + debounce.as_mut().reset(Instant::now() + DEBOUNCE); } None => { debug!("shutting down, network monitor receiver gone"); @@ -128,8 +127,8 @@ impl Actor { match msg { Some(ActorMessage::NetworkChange) => { trace!("external network activity detected"); - last_event.replace(false); - debounce_interval.reset_immediately(); + pending_change = true; + debounce.as_mut().reset(Instant::now() + DEBOUNCE); } None => { debug!("shutting down, actor receiver gone"); diff --git a/netwatch/src/netmon/windows.rs b/netwatch/src/netmon/windows.rs index d97b6b50..87fd575f 100644 --- a/netwatch/src/netmon/windows.rs +++ b/netwatch/src/netmon/windows.rs @@ -129,15 +129,15 @@ impl CallbackHandler { handle: UnicastCallbackHandle, ) -> Result<(), Error> { trace!("unregistering unicast callback"); - if self - .unicast_callbacks - .remove(&(handle.0.0 as isize)) - .is_some() - { + let key = handle.0.0 as isize; + if self.unicast_callbacks.contains_key(&key) { + // Cancel first to ensure no in-flight callbacks reference the Arc, + // then remove the Arc from the map. unsafe { windows::Win32::NetworkManagement::IpHelper::CancelMibChangeNotify2(handle.0) .ok()?; } + self.unicast_callbacks.remove(&key); } Ok(()) @@ -171,15 +171,15 @@ impl CallbackHandler { handle: RouteCallbackHandle, ) -> Result<(), Error> { trace!("unregistering route callback"); - if self - .route_callbacks - .remove(&(handle.0.0 as isize)) - .is_some() - { + let key = handle.0.0 as isize; + if self.route_callbacks.contains_key(&key) { + // Cancel first to ensure no in-flight callbacks reference the Arc, + // then remove the Arc from the map. unsafe { windows::Win32::NetworkManagement::IpHelper::CancelMibChangeNotify2(handle.0) .ok()?; } + self.route_callbacks.remove(&key); } Ok(())