mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 08:14:42 +00:00
node: re-push TreeAnnounce when a peer advertises a worse root
A node with a single tree peer has its periodic parent re-evaluation disabled (it needs at least two peers for a meaningful comparison), so it depends entirely on its peer pushing a TreeAnnounce for it to attach. That push happens once at promotion time plus on the parent's slow periodic no-change re-broadcast. If the one-shot attaching announce is lost, the single-uplink node falls back to self-root and cannot recover until the next periodic re-broadcast (reeval_interval_secs later), stranding it out of the tree and unreachable end-to-end in the interim. Make tree-position exchange self-healing on the receive path: when an accepted TreeAnnounce advertises a root strictly worse (higher NodeAddr, since election is smallest-wins) than our own, echo our current declaration back to that peer. A stranded self-root node's announce now provokes its better-rooted peer to re-push its real position immediately, so the node re-attaches within a round-trip instead of waiting for the periodic cadence. Echo only in that one direction. If the peer's root is lower (better) than ours, we are the stale side: the peer would ignore our worse root anyway and we converge via the parent re-evaluation that follows, so echoing back is pure waste and would double announce traffic in the learning direction during a root change or partition merge. Equal roots are already converged. The echo is bounded by the existing per-peer 500 ms tree-announce rate limiter and is a no-op once the peer adopts our root, so it adds no traffic in a converged mesh. Add a spanning-tree unit test that drives a converged child back to self-root with its peer-ancestry view of the root cleared (modelling the lost attaching announce), and asserts the root re-pushes on the resulting root disagreement and the child re-attaches.
This commit is contained in:
@@ -216,6 +216,41 @@ impl Node {
|
||||
"Processed TreeAnnounce"
|
||||
);
|
||||
|
||||
// Re-push our current position when the announcing peer advertises a
|
||||
// strictly worse (higher NodeAddr) root than ours. Root election is
|
||||
// smallest-NodeAddr-wins, so a peer on a higher root has a stale or
|
||||
// pre-attachment view and can attach to (or re-attach through) us;
|
||||
// reply with our current declaration so it does so without waiting
|
||||
// for the next periodic re-broadcast cadence.
|
||||
//
|
||||
// Only the better-rooted side echoes. If the peer's root is lower
|
||||
// (better) than ours, WE are the stale side: the peer would ignore
|
||||
// our worse root anyway, and we converge via the parent re-evaluation
|
||||
// below, so echoing back is pure waste — and during a root change or
|
||||
// partition merge it would double announce traffic in the learning
|
||||
// direction. Equal roots are already converged. Restricting to `>`
|
||||
// keeps the echo to the one direction that helps.
|
||||
//
|
||||
// This closes a convergence wedge on a single-uplink node: its only
|
||||
// peer pushes the attaching announce once at promotion time, and if
|
||||
// that datagram is lost the single-uplink node cannot self-correct
|
||||
// (its own periodic parent re-evaluation is disabled below two peers)
|
||||
// and is stranded as a self-root until the parent's next periodic
|
||||
// re-broadcast (~reeval_interval_secs later). Echoing on root
|
||||
// disagreement makes tree-position exchange self-healing on the
|
||||
// receive path and is naturally bounded by the per-peer 500 ms
|
||||
// tree-announce rate limiter, so it does not storm during normal
|
||||
// convergence (it stops as soon as the peer adopts our root).
|
||||
if *announce.ancestry.root_id() > *self.tree_state.root()
|
||||
&& let Err(e) = self.send_tree_announce_to_peer(from).await
|
||||
{
|
||||
debug!(
|
||||
peer = %self.peer_display_name(from),
|
||||
error = %e,
|
||||
"Failed to re-push TreeAnnounce on root disagreement"
|
||||
);
|
||||
}
|
||||
|
||||
// Bloom filter exchange initiation is handled at handshake completion
|
||||
// ([handshake.rs] mark_update_needed on the new peer) and on actual
|
||||
// content changes via [bloom.rs::handle_filter_announce]'s
|
||||
|
||||
Reference in New Issue
Block a user