Skip to content

fix(parser): decode an NLRI block whole so a leading default route survives - #361

Merged
digizeph merged 3 commits into
mainfrom
fix/nlri-default-route
Nov 6, 2026
Merged

digizeph merged 3 commits into
mainfrom
fix/nlri-default-route

Conversation

@digizeph

@digizeph digizeph commented Nov 6, 2026

Copy link
Copy Markdown
Member

Problem

parse_nlri_list reads a leading 0x00 byte as an ADD-PATH path identifier when the record says
non-ADD-PATH, keeps that framing for the rest of the block, and retries at the failure position
instead of the block start. 0x00 is also the RFC 4271 §4.3 encoding of the default route, so a
withdrawn list that begins with 0.0.0.0/0 or ::/0 loses prefixes from the middle of the list, or
loses every withdrawal in the message when both framings fail and read_nlri turns the error into an
empty list plus a MalformedNlri warning.

Change

The block is decoded whole under one framing, and a failed ADD-PATH guess re-runs over the whole
block. The guess itself stays: it exists for producers that write ADD-PATH data under a
non-ADD-PATH subtype (issue #31).

A non-ADD-PATH block whose bytes also frame exactly as ADD-PATH is still read as ADD-PATH. Nothing in
the bytes decides between the two readings, and on the data that motivated the guess the ADD-PATH
reading is the correct one. A short block of that shape, 0.0.0.0/0 followed by one prefix, keeps the
old reading; that residual is named in the CHANGELOG entry and pinned by
test_parse_nlri_list_short_ambiguous_block_is_read_as_add_path.

Evidence

An independent MRT decoder and released 0.23.0 decode the three RIPE RIS files in #360 (1,039,666
records). Exactly three (timestamp, peer) keys differ, and they are the three reported messages:

  • rrc25 2026-02-26 10:20, peer AS15397: 1020 withdrawals, every one dropped
  • rrc25 2026-02-12 16:25, peer AS212271: 166 withdrawals, every one dropped
  • rrc24 2026-02-20 13:10, peer AS270014: 627 withdrawals, element count preserved while 11 real
    prefixes were lost and 7 invented (aa00::/1, ::/2, ...)

With this change all three files match the raw decoder on every prefix, and the three messages
decode to 1020, 166 and 627 withdrawals.

route-views.amsix 2020-06-17 18:30 UTC, the ADD-PATH-under-subtype-4 data from issue #31, keeps
3,679,857 elements. Deleting the guess instead takes that file to 955,417 elements (-74%) with no
warning, which is why the guess is kept and only its failure path changes.

Tests

New cases pin the bug rather than the implementation: a crate outside this repository running them
against origin/main fails the IPv4 and IPv6 default-route-at-head cases (the IPv4 one returns
0.0.0.0/0, 1.0.5.0/24, 1.0.6.0/24), while the lone-default-route and wrapped-ADD-PATH cases pass on
both trees. The tie-break test asserts that the plain framing also consumes the ambiguous block, so
the decision it locks is executable rather than implied.

Refs #360.

…rvives

parse_nlri_list read a leading 0x00 byte as an ADD-PATH path identifier and kept
that reading for the rest of the block. A default route (RFC 4271 §4.3) is a
valid non-ADD-PATH encoding of exactly that byte, so the four bytes after it were
eaten as a path id, and the retry the memory optimization added in b3f52ad
resumed at the failure position rather than the block start. Prefixes already
decoded out of path-id bytes stayed in the list and the real ones were dropped.
When both readings failed, the message became a MalformedNlri warning with an
empty withdrawal list, so every withdrawal in it disappeared without an error.

Decode the block whole under one reading, and fall back to the whole block under
the other. ADD-PATH data wrapped in a non-ADD-PATH subtype still decodes with its
path ids, which is the case the guess exists for; a plain block whose bytes also
frame exactly as ADD-PATH still takes the guess, because nothing in the bytes
decides between them.

Verified against the three RIPE RIS files named in #360: an independent MRT
decoder and this build now agree on all 1,039,666 records and on every prefix of
the 1020-, 166- and 627-withdrawal messages. route-views.amsix 2020-06-17 18:30,
the ADD-PATH-under-subtype-4 data from #31, is unchanged at 3,679,857 elements.
The Fixed entry said withdrawals are no longer lost when a block starts with the
default route. A short block that frames exactly as ADD-PATH is still decoded
that way, so 0.0.0.0/0 followed by one /24 keeps losing the /24. Scope the
headline to a guess that cannot frame the block, name the limitation in the entry
and in the doc comment, and pin it with a test so the behaviour cannot drift
silently.

Also restore a debug trace when the guess is taken and when it fails, which the
old heuristic emitted, and correct the "two thirds" figure to the measured 74%
of the amsix file.
A block that fails under both framings does not always yield a MalformedNlri
finding: that is the classic IPv4 withdrawn/announced field path, while a failing
MP_UNREACH_NLRI attribute is stored raw and raises OptionalAttributeError. Say
which field produces which, and assert in the tie-break test that the plain
framing also consumes the block, so the premise the decision rests on is
executable.
Copilot AI balanced review requested due to automatic review settings November 6, 2026 16:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues are recorded for the NLRI decoding fix.

No open issues 🎉

What changed in this PR

Fixes NLRI decoding so a failed ADD-PATH guess retries the entire block, preserving default-route withdrawals affected by #360 while retaining compatibility with mislabeled ADD-PATH records from #31.

Changes:

  • Decodes each NLRI block under one consistent framing and restarts from the beginning when the guess fails.
  • Adds regression tests for IPv4/IPv6 default routes, withdrawal messages, and ADD-PATH compatibility.
  • Documents the remaining ambiguity: blocks valid under both framings still prefer ADD-PATH.
File Description
src/​parser/​utils.rs Implements whole-block decoding and fallback, with framing regression tests.
src/​parser/​bgp/​messages.rs Adds a message-level test preserving withdrawals that start with a default route.
CHANGELOG.md Records the fix, observed impact, and remaining ambiguous-block limitation.

Review effort: Balanced

@codecov

codecov Bot commented Nov 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.09%. Comparing base (f032da2) to head (9e85b3b).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #361      +/-   ##
==========================================
+ Coverage   92.02%   92.09%   +0.06%     
==========================================
  Files          97       97              
  Lines       24759    24868     +109     
==========================================
+ Hits        22785    22901     +116     
+ Misses       1974     1967       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@digizeph
digizeph requested a balanced review from Copilot and removed request for Copilot November 6, 2026 17:16
@digizeph
digizeph merged commit 03b98e8 into main Nov 6, 2026
11 checks passed
@digizeph
digizeph deleted the fix/nlri-default-route branch November 6, 2026 17:24
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