From 46575d0899393926778308117f8c2071316d29f5 Mon Sep 17 00:00:00 2001 From: Aleksandr Bogomiakov Date: Thu, 1 Oct 2026 00:36:24 +0100 Subject: [PATCH] Prioritize stored agreed range over default range in reserve_range When an agent restarted with multiple networks (e.g. hexor and ultradesu), Agent::reserve_range() previously attempted to reserve the default configured range (10.13.37.0/24) for whichever network happened to be listed first in storage, even if that network had already agreed upon a different range (such as 10.79.175.0/24). As a result, the first network seized 10.13.37.0/24 in the route table, causing the second network (which genuinely used 10.13.37.0/24) to trigger a spurious RouteError::Overlap warning at startup until the first network asynchronously loaded its signed records and released the reservation. Key changes: - In crates/tsunagi/src/agent/mod.rs, make reserve_range asynchronous and inspect stored signed records first using StateSet::agreed_range(). - If an agreed range already exists in storage, reserve that specific range in routes immediately instead of preemptively claiming the default overlay_ipv4_range. Validation on Linux: - cargo fmt --all -- --check passed. - cargo clippy --locked --workspace --all-targets -- -D warnings passed. - cargo test --locked --workspace --all-targets --jobs 2 -- --test-threads=2 passed (349 tests passed). --- crates/tsunagi/src/agent/mod.rs | 45 +++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/crates/tsunagi/src/agent/mod.rs b/crates/tsunagi/src/agent/mod.rs index afc81a5..c1fe6bb 100644 --- a/crates/tsunagi/src/agent/mod.rs +++ b/crates/tsunagi/src/agent/mod.rs @@ -385,10 +385,7 @@ impl Agent { /// waits to adopt whatever it settles on. One agent has one interface, so /// proposing a range it could not route would be worse than having none: /// the lowest author's range wins, and the collision would spread. - fn reserve_range(&self, network: NetworkId) -> RangePlan { - let Some(configured) = self.inner.config.overlay_ipv4_range else { - return RangePlan::default(); - }; + async fn reserve_range(&self, network: NetworkId) -> RangePlan { let reserve = |wanted: Ipv4Range| { let reservation = crate::overlay::NetworkRoutes { broadcast: Default::default(), @@ -399,6 +396,44 @@ impl Agent { self.inner.routes.set_network(network, reservation).is_ok() }; + // If this network already has signed records in storage, check if its + // members have already agreed on a range. If so, preserve and reserve + // THAT range rather than preemptively seizing the default configured range. + let stored_records = self + .inner + .storage + .signed_records(network) + .await + .unwrap_or_default(); + let mut state_set = crate::state::StateSet::new(); + state_set.merge_all(network, stored_records); + if let Some(agreed) = state_set.agreed_range() { + if reserve(agreed) { + return RangePlan { + propose: if Some(agreed) == self.inner.config.overlay_ipv4_range { + Some(agreed) + } else { + None + }, + ..RangePlan::default() + }; + } else { + tracing::warn!( + %network, + range = %agreed, + "agreed range for this network overlaps another network already active on this interface" + ); + return RangePlan { + conflict: Some(agreed), + ..RangePlan::default() + }; + } + } + + let Some(configured) = self.inner.config.overlay_ipv4_range else { + return RangePlan::default(); + }; + // The configured range first, so a network a device has always had // keeps the addresses it has always had. if reserve(configured) { @@ -610,7 +645,7 @@ impl Agent { return Err(Error::NetworkAlreadyActive(network_id)); } - let range = self.reserve_range(network_id); + let range = self.reserve_range(network_id).await; let mut backends = Vec::new(); if let Some(discovery) = &self.inner.config.discovery { backends.push(discovery.clone());