From cf51715e8a221207add650e0d35474c3edbdf6a0 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Thu, 26 Feb 2026 13:14:02 +0100 Subject: [PATCH 1/3] fix(netwatch): add last_updated to State so time jumps always notify watchers --- netwatch/src/interfaces.rs | 15 +++++++++++++++ netwatch/src/interfaces/wasm_browser.rs | 16 ++++++++++++++++ netwatch/src/netmon/actor.rs | 2 +- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/netwatch/src/interfaces.rs b/netwatch/src/interfaces.rs index 9fbbe304..983feaed 100644 --- a/netwatch/src/interfaces.rs +++ b/netwatch/src/interfaces.rs @@ -188,6 +188,9 @@ pub struct State { /// /// When set, its value is the map key into `interface` and `interface_ips`. pub default_route_interface: Option, + + /// Monotonic timestamp used to force [`n0_watcher::Watchable`] notifications. + pub last_updated: n0_future::time::Instant, } impl fmt::Display for State { @@ -257,6 +260,7 @@ impl State { have_v6, is_expensive: false, default_route_interface, + last_updated: n0_future::time::Instant::now(), } } @@ -273,9 +277,20 @@ impl State { have_v4: true, is_expensive: false, default_route_interface: Some(ifname), + last_updated: n0_future::time::Instant::now(), } } + /// Compares network state ignoring `last_updated`. + pub fn eq_ignoring_timestamp(&self, other: &State) -> bool { + self.interfaces == other.interfaces + && self.local_addresses == other.local_addresses + && self.have_v6 == other.have_v6 + && self.have_v4 == other.have_v4 + && self.is_expensive == other.is_expensive + && self.default_route_interface == other.default_route_interface + } + /// Is this a major change compared to the `old` one?. pub fn is_major_change(&self, old: &State) -> bool { if self.have_v6 != old.have_v6 diff --git a/netwatch/src/interfaces/wasm_browser.rs b/netwatch/src/interfaces/wasm_browser.rs index 7db072f5..8abe3275 100644 --- a/netwatch/src/interfaces/wasm_browser.rs +++ b/netwatch/src/interfaces/wasm_browser.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, fmt}; use js_sys::{JsString, Reflect}; +use n0_future::time::Instant; pub const BROWSER_INTERFACE: &str = "browserif"; @@ -77,6 +78,9 @@ pub struct State { /// The URL to the Proxy Autoconfig URL, if applicable. pub(crate) pac: Option, + + /// Monotonic timestamp used to force [`n0_watcher::Watchable`] notifications. + pub last_updated: Instant, } impl fmt::Display for State { @@ -117,9 +121,21 @@ impl State { default_route_interface: Some(BROWSER_INTERFACE.to_string()), http_proxy: None, pac: None, + last_updated: Instant::now(), } } + /// Compares network state ignoring `last_updated`. + pub fn eq_ignoring_timestamp(&self, other: &State) -> bool { + self.interfaces == other.interfaces + && self.have_v6 == other.have_v6 + && self.have_v4 == other.have_v4 + && self.is_expensive == other.is_expensive + && self.default_route_interface == other.default_route_interface + && self.http_proxy == other.http_proxy + && self.pac == other.pac + } + /// Is this a major change compared to the `old` one?. pub fn is_major_change(&self, old: &State) -> bool { // All changes are major. diff --git a/netwatch/src/netmon/actor.rs b/netwatch/src/netmon/actor.rs index e4dc0000..ed58c959 100644 --- a/netwatch/src/netmon/actor.rs +++ b/netwatch/src/netmon/actor.rs @@ -147,7 +147,7 @@ impl Actor { let old_state = &self.interface_state.get(); // No major changes, continue on - if !time_jumped && old_state == &new_state { + if !time_jumped && old_state.eq_ignoring_timestamp(&new_state) { debug!("no changes detected"); return; } From ae976c4ecdfb07642f636e08cb8cd26b5c86e594 Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2026 10:13:31 +0100 Subject: [PATCH 2/3] switch to last_unsuspend --- netwatch/src/interfaces.rs | 18 ++++-------------- netwatch/src/interfaces/wasm_browser.rs | 17 +++-------------- netwatch/src/netmon/actor.rs | 8 +++++--- 3 files changed, 12 insertions(+), 31 deletions(-) diff --git a/netwatch/src/interfaces.rs b/netwatch/src/interfaces.rs index 983feaed..59618128 100644 --- a/netwatch/src/interfaces.rs +++ b/netwatch/src/interfaces.rs @@ -189,8 +189,8 @@ pub struct State { /// When set, its value is the map key into `interface` and `interface_ips`. pub default_route_interface: Option, - /// Monotonic timestamp used to force [`n0_watcher::Watchable`] notifications. - pub last_updated: n0_future::time::Instant, + /// Monotonic timestamp, when an unsuspend was detected. + pub last_unsuspend: Option, } impl fmt::Display for State { @@ -260,7 +260,7 @@ impl State { have_v6, is_expensive: false, default_route_interface, - last_updated: n0_future::time::Instant::now(), + last_unsuspend: None, } } @@ -277,20 +277,10 @@ impl State { have_v4: true, is_expensive: false, default_route_interface: Some(ifname), - last_updated: n0_future::time::Instant::now(), + last_unsuspend: None, } } - /// Compares network state ignoring `last_updated`. - pub fn eq_ignoring_timestamp(&self, other: &State) -> bool { - self.interfaces == other.interfaces - && self.local_addresses == other.local_addresses - && self.have_v6 == other.have_v6 - && self.have_v4 == other.have_v4 - && self.is_expensive == other.is_expensive - && self.default_route_interface == other.default_route_interface - } - /// Is this a major change compared to the `old` one?. pub fn is_major_change(&self, old: &State) -> bool { if self.have_v6 != old.have_v6 diff --git a/netwatch/src/interfaces/wasm_browser.rs b/netwatch/src/interfaces/wasm_browser.rs index 8abe3275..2a882c76 100644 --- a/netwatch/src/interfaces/wasm_browser.rs +++ b/netwatch/src/interfaces/wasm_browser.rs @@ -79,8 +79,8 @@ pub struct State { /// The URL to the Proxy Autoconfig URL, if applicable. pub(crate) pac: Option, - /// Monotonic timestamp used to force [`n0_watcher::Watchable`] notifications. - pub last_updated: Instant, + /// Monotonic timestamp, when an unsuspend was detected. + pub last_unsuspend: Option, } impl fmt::Display for State { @@ -121,21 +121,10 @@ impl State { default_route_interface: Some(BROWSER_INTERFACE.to_string()), http_proxy: None, pac: None, - last_updated: Instant::now(), + last_unsuspend: None, } } - /// Compares network state ignoring `last_updated`. - pub fn eq_ignoring_timestamp(&self, other: &State) -> bool { - self.interfaces == other.interfaces - && self.have_v6 == other.have_v6 - && self.have_v4 == other.have_v4 - && self.is_expensive == other.is_expensive - && self.default_route_interface == other.default_route_interface - && self.http_proxy == other.http_proxy - && self.pac == other.pac - } - /// Is this a major change compared to the `old` one?. pub fn is_major_change(&self, old: &State) -> bool { // All changes are major. diff --git a/netwatch/src/netmon/actor.rs b/netwatch/src/netmon/actor.rs index ed58c959..3ce5bf55 100644 --- a/netwatch/src/netmon/actor.rs +++ b/netwatch/src/netmon/actor.rs @@ -143,11 +143,13 @@ impl Actor { async fn handle_potential_change(&mut self, time_jumped: bool) { trace!("potential change"); - let new_state = State::new().await; + let mut new_state = State::new().await; let old_state = &self.interface_state.get(); - // No major changes, continue on - if !time_jumped && old_state.eq_ignoring_timestamp(&new_state) { + if time_jumped { + new_state.last_unsuspend.replace(Instant::now()); + } else if old_state == &new_state { + // No major changes, continue on debug!("no changes detected"); return; } From df573f3c0c2aa26691aa3ea8928999411bdc6f6f Mon Sep 17 00:00:00 2001 From: dignifiedquire Date: Wed, 4 Mar 2026 10:14:28 +0100 Subject: [PATCH 3/3] fixup imports --- netwatch/src/interfaces.rs | 4 +++- netwatch/src/interfaces/wasm_browser.rs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/netwatch/src/interfaces.rs b/netwatch/src/interfaces.rs index 59618128..2077946b 100644 --- a/netwatch/src/interfaces.rs +++ b/netwatch/src/interfaces.rs @@ -2,6 +2,8 @@ use std::{collections::HashMap, fmt, net::IpAddr}; +use n0_future::time::Instant; + #[cfg(any( target_os = "freebsd", target_os = "openbsd", @@ -190,7 +192,7 @@ pub struct State { pub default_route_interface: Option, /// Monotonic timestamp, when an unsuspend was detected. - pub last_unsuspend: Option, + pub last_unsuspend: Option, } impl fmt::Display for State { diff --git a/netwatch/src/interfaces/wasm_browser.rs b/netwatch/src/interfaces/wasm_browser.rs index 2a882c76..782bb651 100644 --- a/netwatch/src/interfaces/wasm_browser.rs +++ b/netwatch/src/interfaces/wasm_browser.rs @@ -80,7 +80,7 @@ pub struct State { pub(crate) pac: Option, /// Monotonic timestamp, when an unsuspend was detected. - pub last_unsuspend: Option, + pub last_unsuspend: Option, } impl fmt::Display for State {