Repository navigation
fix(transport): keep stored next hop aligned with import rewrite - #2978
Merged
Merged
Conversation
Import next-hop self and an IPv6 import next hop changed only the route's next hop, leaving the received NEXT_HOP among its stored attributes. Passthrough exports (iBGP, route-server clients) send that stored attribute, so they advertised the received address instead of the one the RIB selected and installed. Inbound now rewrites (or, for an IPv6 next hop, drops) the stored attribute when a next-hop action applies, shared across the NLRI of one UPDATE.
There was a problem hiding this comment.
🟡 Changes recommended
The single-entry alignment cache defeats bounded attribute sharing for interleaved policy results.
1 open finding
What changed in this PR
Aligns stored IPv4 route attributes with import-policy next-hop rewrites, ensuring consistent passthrough exports.
Changes:
- Rewrites or removes stale stored
NEXT_HOPattributes. - Adds passthrough export regression tests.
- Documents the operator-visible fix.
| File | Description |
|---|---|
crates/transport/src/session/inbound.rs |
Aligns and memoizes imported next-hop attributes. |
crates/transport/src/session/tests/next_hop.rs |
Tests IPv4 and IPv6 rewritten next-hop exports. |
changelog.d/fixed-import-next-hop-self-passthrough.md |
Records the behavioral change. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The stored next-hop alignment cached one result, so prefix-dependent import outcomes interleaved within one UPDATE cloned an attribute set per NLRI. Keep a bounded set of aligned results keyed by source set and next hop, with the import memo's bound.
There was a problem hiding this comment.
🟢 Approval recommended
The implementation and regression coverage are coherent; only minor documentation wording needs correction.
1 open finding
1 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Comment on lines
+3
to
7
| //! Inbound keeps a `NEXT_HOP` among an IPv4 unicast route's stored | ||
| //! attributes, aligned with `Route::next_hop`, the effective (post-import- | ||
| //! policy) next hop. The MRT dump, the warm checkpoint and the BMP Loc-RIB | ||
| //! synthesizer emit the next hop from `Route::next_hop` and must not also | ||
| //! emit the stored attribute. |
lance0
marked this pull request as ready for review
October 8, 2026 00:32
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Problem
An IPv4 unicast route learned in a body UPDATE keeps the received
NEXT_HOPamong its stored attributes. Import policy writes a specific IPv4 next hop into that attribute.next-hop self, and an IPv6 next hop on an IPv4 route, are resolved by the session and change onlyRoute::next_hop, so the stored attribute keeps the received address.Export to passthrough peers (iBGP and route-server clients, with no export-policy rewrite) copies the stored attribute (
prepare_unicast_attributes). Such peers were told the received address, while the RIB, the FIB, export policymatch next-hop, gRPC and explain all used the import-chosen one.The effect depends on the peer:
MP_REACH_NLRI. That is the form the exporter otherwise avoids because OpenBGPD resets the session on it.Reader audit
Every non-test match on
PathAttribute::NextHop, plus the readers that take a next hop from routes:transport/session/export.rs:529prepare_unicast_attributespassthrough of storedNEXT_HOPexport.rs:1017Extended Next Hop body-vs-MP choice compares storedNEXT_HOPto the chosen next hopMP_REACHexport.rs:579–608synthesizeNEXT_HOPwhen absent (override, self, eBGP, elseroute.next_hop)export.rs:669MP export dropsNEXT_HOP;:930without-next-hop variant;:1045body form requires onerib/bmp_sync.rs:256,mrt/codec.rs:603/814/823(BMP Loc-RIB, MRT, warm checkpoint)route.next_hopmrt/reader.rs:346,cli/ribsnap.rs:437,cli/ribsnap_bmp.rs:971transport/session/inbound.rs:372(malformed-UPDATE log),:2008(received next hop, input to import)policy/engine.rs:1916apply_modificationsapi/injection_service.rs:237,src/evpn_imet.rs:366,src/evpn_segment.rs:2047,src/evpn_originator/rib_write.rs:280wire/validate.rs,wire/attribute.rsroute_to_proto(api/rib_service.rs:1834, ignores the attribute), explain (api/policy_service.rs:1821), export policyRouteContext(rib/manager/distribution/unicast.rs:727,1077,1222,1822,2045,2399;rib/manager/queries.rs:2324), FIB (src/fib_runtime.rs), Loc-RIB/best path, ORR, BLACKHOLE, RPKI/ASPARoute::next_hopor no next hop; correctOther readers of
route.attributesdo not matchNEXT_HOP, so they cannot read a next hop from it:adj_rib_in,srv6,rs_control,export_memo,flowspec_validation, the EVPN Linux dataplane, and the CLI neighbor view.Change
When an import next-hop action applies to a body IPv4 route, inbound makes the stored
NEXT_HOPagree with the resolved next hop:Routes without a next-hop action skip the check, because their stored attribute is the received one. The aligned set is memoized, so every NLRI of one UPDATE still shares one attribute
Arc. Storage keeps the attribute; stripping it would touch every producer and attribute interning on the hot path.Validation
process_updatewith a real import policy, then build the export candidate for passthrough peers:next-hop self: iBGP, iBGP with Extended Next Hop, and a route-server client must each send body NLRI with oneNEXT_HOPequal to the import-resolved address.MP_REACHwith the IPv6 next hop with it.NextHop(10.0.0.2)where10.0.0.1was expected, and the IPv6 case exported body NLRI instead of refusing.Arcsharing assertion fails.cargo test -p rustbgpd-transport --lib: 801 passed.just gateexited 0 (10499 passed, 0 failed, 19 ignored across 155 test suites, plus links, contracts, strict Clippy and rustdoc).just gate-ribexited 0.Note
The MRT/BMP fix currently in review adds
crates/transport/src/session/tests/mrt_next_hop.rs. One of its assertions pins the stale stored value undernext-hop self. Whichever of the two changes lands second must update that assertion: the stored attribute will then equalRoute::next_hop.