Skip to content

Fix stale BMP peer retention with automatic snapshot reconciliation - #14

Draft
nuclearcat wants to merge 4 commits into
mainfrom
fix/bmp-automatic-reconciliation
Draft

nuclearcat wants to merge 4 commits into
mainfrom
fix/bmp-automatic-reconciliation

Conversation

@nuclearcat

Copy link
Copy Markdown
Collaborator

Fixes #11.

A misaddressed or missing BMP Peer Down can leave the old peer connected with its routes indefinitely. Recover automatically by renewing the affected speaker’s BMP connection and rebuilding from its current snapshot, rather than guessing which same-router-ID peer to delete. Includes periodic renewal for silent disappearance, startup replay protection, coalesced anomaly deadlines, and a peer-state capacity limit. No opt-in is required.

Reproduction and coverage: our synthetic BMP exporter repeatedly replaces a link-local peer while retaining the speaker connection. Before the fix, 20 replacements with missing or :: Peer Down retained 21 peers and route records after GC. The replay-aware simulation now verifies reclamation, legitimate parallel peers, silent disappearance, partial-frame timeouts, and capacity recovery. Rust regressions cover scheduling, identity scopes, policy/ADD-PATH cleanup, and active reconnects.

Validation on this main-based branch: cargo test --lib --offline (354 passed, 31 ignored), daemon build, and all seven live scenarios with 20 replacements per churn scenario.

Caveat: reconciliation withdraws the speaker’s monitored routes until reconnect/replay restores them, causing a monitoring gap and full-table replay cost; it does not reset the router’s BGP sessions. It requires an exporter that reconnects and supplies current state. Startup grace and the peer-state cap are bounded heuristics, not a total memory limit. This follows RFC 7854 §3.2, §3.3, and §5; defaults and operational limitations are documented in docs/bmp-tcp-in.md.

Assisted-by: OpenAI Codex

Copilot AI balanced review requested due to automatic review settings October 2, 2026 18:31
Comment thread scripts/bmp-peer-churn.py
data = self.socket.recv(1)
if data:
raise RuntimeError("collector unexpectedly sent BMP data")
except ConnectionResetError:
Comment thread scripts/bmp-peer-churn.py
self.socket.recv(1)
except socket.timeout:
return
except ConnectionResetError:

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

It introduces an always-on, non-disableable behavior change to core BMP processing that periodically forces full-table replay for every exporter, with broad operational impact that warrants human judgment despite the clean, well-tested implementation.

Review effort: Balanced
Findings: None

What changed in this PR

This PR addresses issue #11, where a misaddressed or missing BMP Peer Down (e.g. FRR reporting :: after an unnumbered peer loses its link-local address) leaves a stale peer and its Adj-RIB-In connected indefinitely. Rather than guessing which same-router-ID peer to delete, it introduces an always-on "reconciliation" mechanism: when an anomaly is detected (unmatched Peer Down, possible unnumbered-peer replacement, peer-state capacity reached, or a periodic/idle timer), the speaker's BMP transport is closed so the exporter replays an authoritative snapshot on reconnect. The logic lives in a new reconciliation module wired into the per-connection RouterHandler read loop and the BMP state machine.

Changes:

  • New reconciliation module: validated Config (bounds enforced via TryFrom, cannot be disabled), jittered periodic deadline, and anomaly/capacity deadline coalescing.
  • State-machine reconciliation_reason classifies Peer Up/Down against logical peer identity (type/distinguisher/ASN/BGP-id, excluding policy flags) and exposes peer_state_count; the read loop times out on the deadline and closes the transport for a fresh snapshot.
  • Config plumbing through BmpTcpIn/BmpTcpInRunner/RouterHandler (new connections only), plus extensive Rust tests, a Python churn simulator, and documentation.
File Description
src/​units/​bmp_tcp_in/​reconciliation.rs New module: config validation, jitter, deadline/observe logic, unit tests.
src/​units/​bmp_tcp_in/​state_machine/​machine.rs Adds peer_states/peer_state_count/reconciliation_reason for anomaly detection.
src/​units/​bmp_tcp_in/​router_handler.rs Read loop enforces reconciliation deadline/timeout, observes per-message, drops transport.
src/​units/​bmp_tcp_in/​unit.rs Threads reconciliation config through construction/reconfigure; adds active redial test.
src/​units/​bmp_tcp_in/​mod.rs Exposes the new reconciliation module.
src/​units/​bmp_tcp_in/​state_machine/​tests.rs Tests for identity scoping and non-destructive detection of parallel peers.
scripts/​bmp-peer-churn.py New stdlib-only simulator exercising seven reconciliation scenarios.
docs/​bmp-tcp-in.md Documents triggers, defaults, RFC 7854 basis, and the monitoring-gap/replay caveat.
TESTING.md Describes the simulator and Rust regression suite.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@nuclearcat
nuclearcat marked this pull request as draft October 4, 2026 11:16

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unnumbered peer replacement leaves the old link-local Adj-RIB-In connected - implicit Peer Down feature?

2 participants