Skip to content

bgpd: PMSI tunnel attribute compatibility#21507

Merged
Jafaral merged 4 commits intoFRRouting:masterfrom
mjstapp:bgp_pmsi_tun_v6
Apr 14, 2026
Merged

bgpd: PMSI tunnel attribute compatibility#21507
Jafaral merged 4 commits intoFRRouting:masterfrom
mjstapp:bgp_pmsi_tun_v6

Conversation

@mjstapp
Copy link
Copy Markdown
Contributor

@mjstapp mjstapp commented Apr 13, 2026

Enhance support of the PMSI tunnel attribute, include both v4 and v6 endpoints. This isn't MVPN support (!) - just extended compatibility with the attribute for interop purposes.
This is an updated version of #19962 - I'll update this with comments from that original PR and see if we can converge.

The code here has 3 functional parts:
1> Accept V6 PMSI tunnel length i.e 21
2> Send side: We encode the Tunnel ID with V4 or V6 Tunnel ID.
this is based on retrieved tunnel Id or for local route tunnel Id fetched based on nexthop.
3> Receive side: Decode the tunnel Id to attribute tunnel_id new field.

In addition, show output is updated to display Tunnel ID in vty and json
Topotest added to validate V4 and V4 Tunnel ID for Ingress replication tunnel type

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 13, 2026

Greptile Summary

This PR extends PMSI tunnel attribute handling to support IPv6 tunnel identifiers (length 21), covering parsing, encoding, and show output. The previous stream-corruption bug for non-INGR_REPL tunnel types has been addressed — the tunnel-ID read is now correctly gated on PMSI_TNLTYPE_INGR_REPL, and the stream_forward_getp arithmetic is sound for all tunnel types.

Confidence Score: 5/5

  • Safe to merge; all remaining findings are P2 style/robustness suggestions in test helpers that do not affect production code paths.
  • The core PMSI parsing, encoding, and show-output changes are correct. The prior P0 concern (stream corruption from unconditional tunnel-ID reads) has been resolved. The two remaining comments are test-helper quality issues (missing error guard, bare dict access) with no impact on the daemon.
  • tests/topotests/lib/evpn.py and tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py have minor test-quality issues worth addressing.

Important Files Changed

Filename Overview
bgpd/bgp_attr.c Parsing (bgp_attr_pmsi_tunnel) now correctly gates IPv6 tunnel-ID reads on PMSI_TNLTYPE_INGR_REPL and validates length as exactly 9 (IPv4) or 21 (IPv6), fixing the previous underflow in stream_forward_getp. Encoding correctly uses IS_MAPPED_IPV6 to select IPv4 vs IPv6 length and writes the right number of bytes.
bgpd/bgp_attr.h Adds three PMSI length constants (LBL_ONLY=5, V4=9, V6=21) and a new in6_addr tunn_id field to struct attr; both changes are clean and consistent with the parsing/encoding logic.
bgpd/bgp_evpn.c IMET route origination now sets tunn_id from the nexthop (IPv4-mapped for IPv4, raw IPv6 for IPv6), which drives the correct wire format on the send side.
bgpd/bgp_route.c Show output now includes PMSI tunnel ID for INGR_REPL in both VTY (pI4/pI6 format strings) and JSON ("id" field); IS_MAPPED_IPV6 correctly distinguishes IPv4 from IPv6 for display.
tests/topotests/lib/evpn.py New evpn_check_bgp_imet helper correctly navigates FRR's nested-array paths structure (paths[0][0]), but uses bare dict access for "pmsi" which raises KeyError instead of returning a clean error string.
tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py New test_imet exercises the IPv6 PMSI tunnel ID path, but is missing the routers_have_failure() guard present in the sibling test file.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Receive PMSI Tunnel attribute] --> B{length < 5?}
    B -- Yes --> ERR1[ATTR_LENG_ERR / malformed]
    B -- No --> C[Read Flags + Tunnel Type + Label]
    C --> D{tnl_type == INGR_REPL?}
    D -- No --> E[skip remaining bytes\nstream_forward_getp\nlength - 5]
    D -- Yes --> F{length == 9?}
    F -- No, not 21 either --> ERR2[ATTR_LENG_ERR / malformed]
    F -- Yes IPv4 --> G[stream_get_ipv4\nipv4_to_ipv4_mapped_ipv6\nattr_parse_len += 4]
    F -- No, length == 21 --> H[stream_get IPv6\n16 bytes\nattr_parse_len += 16]
    G --> I[stream_forward_getp 0\nno-op]
    H --> I

    subgraph Encode [Send PMSI Tunnel attribute]
        J{tnl_type == INGR_REPL?} -- No --> K[write length=5\nFlags+Type+Label only]
        J -- Yes --> L{IS_MAPPED_IPV6\ntunn_id?}
        L -- Yes IPv4-mapped --> M[write length=9\nIPv4 Tunnel ID]
        L -- No pure IPv6 --> N[write length=21\nIPv6 Tunnel ID]
    end
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: tests/topotests/lib/evpn.py
Line: 1579-1583

Comment:
**Bare dict access raises `KeyError` instead of returning an error string**

The function's contract is to return an error string on failure (so `run_and_expect` can retry), but `paths[0][0]["pmsi"]` raises `KeyError` if the `pmsi` key is absent rather than returning a message. The exception propagates through `run_and_expect`, eventually becoming the opaque `result` string instead of a clean diagnostic.

```suggestion
    pmsi = paths[0][0].get("pmsi")
    if pmsi is None:
        return f"Imet PMSI attribute not found in path for rd {rd} and {prefix}"
    out_label = pmsi.get("label", 0)
    if out_label != pmsi_label:
        return f"Imet PMSI Label mismatch Expected {pmsi_label} Got {out_label}"

    out_id = pmsi.get("id", "")
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: tests/topotests/bgp_evpn_vxlan_topo1/test_bgp_evpn_vxlan_v6_vtep.py
Line: 302-305

Comment:
**Missing `routers_have_failure()` guard**

The equivalent `test_imet` in `test_bgp_evpn_vxlan.py` skips early when routers are in a failed state, but this version does not. If a prior test left a router failed, this test runs against broken state and produces misleading failures.

```suggestion
def test_imet():
    tgen = get_topogen()
    if tgen.routers_have_failure():
        pytest.skip(tgen.errors)

    dut_name = "PE1"
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (3): Last reviewed commit: "tests: Add a v6 pmsi tunnel only test." | Re-trigger Greptile

bgpd/bgp_attr.c Outdated
Comment on lines +3798 to +3807
if (length == 9) {
struct in_addr tunn_id;

tunn_id.s_addr = stream_get_ipv4(connection->curr);
ipv4_to_ipv4_mapped_ipv6(&attr->tunn_id, tunn_id);
attr_parse_len += IPV4_MAX_BYTELEN;
} else {
stream_get(&attr->tunn_id, connection->curr, IPV6_MAX_BYTELEN);
attr_parse_len += IPV6_MAX_BYTELEN;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Tunnel ID parsing not guarded by PMSI_TNLTYPE_INGR_REPL

The tunnel ID parsing block runs unconditionally for all tnl_type values, but the length-vs-address-family contract (length == 9 → IPv4, else → IPv6) is only valid for PMSI_TNLTYPE_INGR_REPL. For any other tunnel type with length != 9 (e.g., PMSI_TNLTYPE_NO_INFO with the minimum length of 5), the else branch calls stream_get(..., IPV6_MAX_BYTELEN) when only 0 bytes remain; stream_get returns early and emits a warning, yet attr_parse_len is still incremented by 16. The subsequent stream_forward_getp(connection->curr, length - attr_parse_len) then receives a wrapped-around size_t value, fails bounds-checking, and leaves the stream's read pointer at the wrong position, corrupting the parse of every subsequent attribute in the UPDATE message.

Suggested change
if (length == 9) {
struct in_addr tunn_id;
tunn_id.s_addr = stream_get_ipv4(connection->curr);
ipv4_to_ipv4_mapped_ipv6(&attr->tunn_id, tunn_id);
attr_parse_len += IPV4_MAX_BYTELEN;
} else {
stream_get(&attr->tunn_id, connection->curr, IPV6_MAX_BYTELEN);
attr_parse_len += IPV6_MAX_BYTELEN;
}
stream_get(&attr->label, connection->curr, BGP_LABEL_BYTES);
if (tnl_type == PMSI_TNLTYPE_INGR_REPL) {
if (length == 9) {
struct in_addr tunn_id;
tunn_id.s_addr = stream_get_ipv4(connection->curr);
ipv4_to_ipv4_mapped_ipv6(&attr->tunn_id, tunn_id);
attr_parse_len += IPV4_MAX_BYTELEN;
} else {
stream_get(&attr->tunn_id, connection->curr, IPV6_MAX_BYTELEN);
attr_parse_len += IPV6_MAX_BYTELEN;
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: bgpd/bgp_attr.c
Line: 3798-3807

Comment:
**Tunnel ID parsing not guarded by `PMSI_TNLTYPE_INGR_REPL`**

The tunnel ID parsing block runs unconditionally for **all** `tnl_type` values, but the length-vs-address-family contract (`length == 9` → IPv4, else → IPv6) is only valid for `PMSI_TNLTYPE_INGR_REPL`. For any other tunnel type with `length != 9` (e.g., `PMSI_TNLTYPE_NO_INFO` with the minimum length of 5), the `else` branch calls `stream_get(..., IPV6_MAX_BYTELEN)` when only 0 bytes remain; `stream_get` returns early and emits a warning, yet `attr_parse_len` is still incremented by 16. The subsequent `stream_forward_getp(connection->curr, length - attr_parse_len)` then receives a wrapped-around `size_t` value, fails bounds-checking, and leaves the stream's read pointer at the wrong position, corrupting the parse of every subsequent attribute in the UPDATE message.

```suggestion
	stream_get(&attr->label, connection->curr, BGP_LABEL_BYTES);
	if (tnl_type == PMSI_TNLTYPE_INGR_REPL) {
		if (length == 9) {
			struct in_addr tunn_id;

			tunn_id.s_addr = stream_get_ipv4(connection->curr);
			ipv4_to_ipv4_mapped_ipv6(&attr->tunn_id, tunn_id);
			attr_parse_len += IPV4_MAX_BYTELEN;
		} else {
			stream_get(&attr->tunn_id, connection->curr, IPV6_MAX_BYTELEN);
			attr_parse_len += IPV6_MAX_BYTELEN;
		}
	}
```

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes, that's valid: I'll update the logic here

MIX(attr->encap_tunneltype);
MIX(bgp_attr_get_pmsi_tnl_type(attr));
if (bgp_attr_get_pmsi_tnl_type(attr) == PMSI_TNLTYPE_INGR_REPL)
key = jhash(attr->tunn_id.s6_addr, IPV6_MAX_BYTELEN, key);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Donald had a comment in the original PR:

so from my perspective anytime we've had a situation where we had unset values as part of this attribute hash key making/comparison we've ended up with bugs. I would prefer that FRR has a v4 and a v6 side of this where we explicitly only create a key for the v4 side if v4 and if v6 use the v6 data. The way I am reading this is that the v4 and v6 share the same space, correct?

@mjstapp
Copy link
Copy Markdown
Contributor Author

mjstapp commented Apr 13, 2026

Moved the dup topotest helper to the lib/evpn module.

mrin-g added 3 commits April 13, 2026 11:41
Presently EVPN IMET PMSI Tunnel attributes PMSI tunnel ID is not sent or
processed properly.
For sending side we try to send attr->nexthop which is not initialized
for V6 nexthop, while on the receiving side the Tunnel ID is not processed.
Here the fix has 3 functional parts:
1> Accept V6 PMSI tunnel length i.e 21
2> Send side: We encode the Tunnel ID with V4 or V6 Tunnel ID.
   this is based on retrieved tunnel Id or for local route tunnel Id
   fetched based on nexthop.
3> Receive side: Decode the tunnel Id to attribute tunnel_id new field.

Signed-off-by: Mrinmoy Ghosh <mrinmoy_g@hotmail.com>
Signed-off-by: Mark Stapp <mjs@cisco.com>
Display PMSI tunnel ID for V4 and V6 in vty and JSON

Signed-off-by: Mrinmoy Ghosh <mrinmoy_g@hotmail.com>
Signed-off-by: Mark Stapp <mjs@cisco.com>
Test 'test_imet' added for V4 and V4 to validate Tunnel ID PMSI

Signed-off-by: Mrinmoy Ghosh <mrinmoy_g@hotmail.com>
Signed-off-by: Mark Stapp <mjs@cisco.com>
@mjstapp
Copy link
Copy Markdown
Contributor Author

mjstapp commented Apr 13, 2026

I've replaced some magic numbers with defines, and updated the logic so that we only parse tunnel id addresses for the INGR_REPL tunnel type we think we understand. I also updated the show code so that the tunnel id address is only included for the known tunnel type(s).

@mjstapp
Copy link
Copy Markdown
Contributor Author

mjstapp commented Apr 13, 2026

@greptileai review

if (IS_MAPPED_IPV6(&attr->tunn_id)) {
stream_putc(s, BGP_ATTR_PMSI_TUNNEL_V4_LENGTH);
tunn_id_len = IPV4_MAX_BYTELEN;
ipv4_mapped_ipv6_to_ipv4(&attr->tunn_id, &tunn_id);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this seems wrong. We are supposed to be writing to the stream, yet we are setting the attr->tunn_id not the stream, right? The non mapped case writes the value to the stream. Also I am dubious about any pattern where we are setting attr values in a write function to a stream. This is the wrong place to do it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

shit I read this wrong. The ipv4_mapped_ipv6_to_ipv4 sets tunn_id.

@donaldsharp
Copy link
Copy Markdown
Member

I wrote a topotest that checks v6 to v6 only in my PR can you grab it and put it in this PR too?

@mjstapp
Copy link
Copy Markdown
Contributor Author

mjstapp commented Apr 14, 2026

oh, yes, sure, will do

I wrote a topotest that checks v6 to v6 only in my PR can you grab it and put it in this PR too?

Add a v6 only test that shows that v6 encoding actually
works.

Signed-off-by: Donald Sharp <sharpd@nvidia.com>
@frrbot frrbot bot added the tests Topotests, make check, etc label Apr 14, 2026
@github-actions github-actions bot added the rebase PR needs rebase label Apr 14, 2026
@mjstapp
Copy link
Copy Markdown
Contributor Author

mjstapp commented Apr 14, 2026

I've pushed an update with that added topotest

oh, yes, sure, will do

I wrote a topotest that checks v6 to v6 only in my PR can you grab it and put it in this PR too?

@donaldsharp
Copy link
Copy Markdown
Member

LGTM. I'll bug Jafar to get this one in because I can no longer do so since it has a commit from me.

@Jafaral
Copy link
Copy Markdown
Member

Jafaral commented Apr 14, 2026

@greptile review

@Jafaral Jafaral merged commit e66d35b into FRRouting:master Apr 14, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bgp bugfix master rebase PR needs rebase size/L tests Topotests, make check, etc

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants