Skip to content

test(wire): assert one attribute per type in debug encoders - #2981

Merged
lance0 merged 3 commits into
mainfrom
fix/no-duplicate-synthesized-attributes
Oct 8, 2026
Merged

lance0 merged 3 commits into
mainfrom
fix/no-duplicate-synthesized-attributes

Conversation

@lance0

@lance0 lance0 commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Workspace test builds now panic when one encoded attribute list would carry the same path-attribute type twice (RFC 4271 §5). Default, release and bench builds, and embedders of rustbgpd-wire, are unchanged.

Why

#2977 fixed a duplicate NEXT_HOP in MRT dumps, warm checkpoints and BMP Loc-RIB. Each encoder copied the route's stored attributes and also synthesized an attribute of a type already in storage. No test caught it, because the encoder tests built routes by hand.

An audit of every emitter that copies stored attributes and also adds one found no other reachable duplicate on current main. This PR adds a guard so the next one fails in tests instead of reaching the wire.

Change

  • rustbgpd-wire encoder (encode_path_attributes_with_scratch): a [bool; 256] records each emitted type, including the synthesized AS4_PATH/AS4_AGGREGATOR compatibility attributes. This one path covers export UPDATEs (per-session and shared update-group), Adj-RIB-Out BMP (the same bytes) and BMP Loc-RIB synthesis.
    • The check compiles only under cfg(all(debug_assertions, any(test, feature = "strict-encode-invariants"))). The feature is off by default, so embedders never panic on caller-supplied input.
    • The transport, rib and mrt crates enable it through [dev-dependencies] only. Under resolver 2 a release cargo build -p rustbgpd compiles wire with no features (checked against the rustc command line), and cargo test --workspace turns it on for every package, root tests included.
    • The root package gets it through workspace feature unification rather than its own dev-dependency, because the architecture contract allows only cli among root dev-dependencies.
    • Benches build with the feature, but debug_assertions is off in the bench profile, so measurements are unaffected.
  • MRT (publish = false): MRT encodes one attribute per wire-encoder call, so the wire check cannot see across them. A debug-only walk of each encoded RIB entry runs in the unicast, EVPN and generic entry writers.
  • Unit tests pin both guards, including the Extended Length parse.

Evidence

  • Reintroducing the fix(mrt): encode one next hop for received IPv4 routes #2977 bug, in the MRT encoder and BMP Loc-RIB, made all four IPv4 mrt_next_hop regressions panic in the new guards (duplicate path attribute type 3 ...). The IPv6 regression still passed.
  • Removing the export NEXT_HOP presence check in prepare_unicast_attributes made 58 existing transport tests fail, each panicking in the wire guard.
  • Making the MRT walker ignore Extended Length turns unique_attribute_guard_walks_extended_length_attributes red.
  • Both red proofs were re-run after the feature gating and still trip the guards: 4 IPv4 mrt_next_hop failures and 58 transport failures.
  • cargo fmt --check, just gate-rib and just gate all pass (10,511 passed, 0 failed); rustc/clippy 1.99.0.

No daemon behavior change, so there is no root changelog fragment. The wire crate changelog records the new non-default feature.

lance0 added 2 commits October 7, 2026 20:14
Debug builds now panic when the shared path-attribute encoder or an MRT
RIB entry would emit the same type code twice (RFC 4271 section 5). This
catches an emitter that copies a stored attribute and also synthesizes
one of the same type, the class behind the duplicate NEXT_HOP in MRT,
warm checkpoints and BMP Loc-RIB. Release builds skip the check.
The encoder is public, and a debug assertion there would panic on
caller-supplied attributes in an embedder's debug build. The check now
compiles only with the non-default strict-encode-invariants feature,
which the workspace enables through dev-dependencies for its own tests.
Default, release and bench builds are unchanged.

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.

🟡 Changes recommended

The MRT guard can falsely panic on oversized attributes before existing length-error handling runs.

1 open finding
What changed in this PR

Adds duplicate path-attribute checks to catch encoder regressions like #2977, while preserving default wire-crate and release behavior.

Changes:

  • Adds an opt-in wire encoder invariant enabled by workspace test dependencies.
  • Adds debug-only MRT entry checks.
  • Adds regression tests and documents the feature.
File Description
crates/​wire/​src/​attribute.rs Checks emitted attribute types and tests duplicate rejection.
crates/​wire/​CHANGELOG.md Documents the opt-in invariant.
crates/​wire/​Cargo.toml Declares the non-default feature.
crates/​transport/​Cargo.toml Enables checks for transport tests.
crates/​rib/​Cargo.toml Enables checks for RIB tests.
crates/​mrt/​src/​codec.rs Checks complete MRT entries and tests extended-length parsing.
crates/​mrt/​Cargo.toml Enables wire checks for MRT tests.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread crates/mrt/src/codec.rs Outdated
An attribute payload over u16::MAX has its length narrowed by the wire
encoder, so the debug walk misread the payload as further headers and
panicked on a false duplicate before the caller could return
FieldTooLarge. The walk now skips any block it cannot read faithfully.
@lance0
lance0 marked this pull request as ready for review October 8, 2026 03:12
@lance0
lance0 merged commit c14e3fb into main Oct 8, 2026
19 checks passed
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.

2 participants