Commit 937e1fc82d for frr
commit 937e1fc82d1fd60ac8895a12ad121137d12a1382
Author: Louis Scalbert <louis.scalbert@6wind.com>
Date: Thu Nov 5 12:11:44 2026 +0100
tests: check repeated SRv6 SID Information Sub-TLVs
RFC 9252 says that when multiple SID Information Sub-TLVs are present,
the SID from the first instance is used and the others are ignored.
Let bgp_injector.py encode several SID Information Sub-TLVs in the
same SRv6 Service TLV with a "sub_tlvs" list, and inject an EVPN type-5
route whose SRv6 L3 Service TLV carries two of them with different SID,
endpoint behavior and SID structure. Check that r2 and r3 use the
values of the first one.
Signed-off-by: Louis Scalbert <louis.scalbert@6wind.com>
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.cfg b/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.cfg
index a0151b19a6..55b826409d 100644
--- a/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.cfg
+++ b/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.cfg
@@ -433,6 +433,56 @@
"encapsulation:vxlan"
]
}
+ },
+ {
+ "nlri": {
+ "type": 5,
+ "rd": "192.0.2.1:203",
+ "esi": "0",
+ "eth_tag": 0,
+ "ip": "2001:5::/64",
+ "gw_ip": "::",
+ "label1": 32763
+ },
+ "path_attributes": {
+ "extended-communities": [
+ "target:65001:203"
+ ],
+ // RFC 9252: repeated SID Information Sub-TLVs, use the first one
+ "prefix-sid": [
+ {
+ "type": 5,
+ "sub_tlvs": [
+ {
+ "sid": "3333:3333:3333::",
+ "flags": "00",
+ "behavior": 18,
+ "sid_structure": {
+ "lbl": 32,
+ "lnl": 16,
+ "fl": 16,
+ "al": 0,
+ "tl": 16,
+ "to": 48
+ }
+ },
+ {
+ "sid": "4444:4444:4444::",
+ "flags": "00",
+ "behavior": 20,
+ "sid_structure": {
+ "lbl": 40,
+ "lnl": 24,
+ "fl": 16,
+ "al": 0,
+ "tl": 16,
+ "to": 64
+ }
+ }
+ ]
+ }
+ ]
+ }
}
]
}
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.py b/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.py
index 6c5359f797..9f7f380176 100755
--- a/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.py
+++ b/tests/topotests/bgp_prefix_sid_evpn/r1/bgp_injector.py
@@ -242,6 +242,47 @@ def encode_path_attribute_prefix_sid(data):
return hexstream
+def encode_srv6_sid_info_sub_tlv(val):
+ sid_str = val.get("sid", "::")
+ behavior = int(val.get("behavior", 0))
+
+ # Note: Some pre-RFC FRR implementations/drafts used a 1-byte Flags
+ # field just before the behavior. We expose it here just in case.
+ flags_hex = val.get("flags", "").replace("0x", "")
+ flags_bytes = bytes.fromhex(flags_hex) if flags_hex else b""
+
+ sstlv_data = val.get("sid_structure", "")
+ if isinstance(sstlv_data, str):
+ sub_sub_tlvs = bytes.fromhex(sstlv_data)
+ elif isinstance(sstlv_data, dict):
+ # Pack: Type(2) + Length(2) + LBL(1) + LNL(1) + FL(1) + AL(1) + TL(1) + TO(1)
+ sub_sub_tlvs = struct.pack(
+ "!HHBBBBBB",
+ 1, # SID Structure type
+ 6, # Fixed Length of 6 bytes for the payload
+ int(sstlv_data.get("lbl", 0)),
+ int(sstlv_data.get("lnl", 0)),
+ int(sstlv_data.get("fl", 0)),
+ int(sstlv_data.get("al", 0)),
+ int(sstlv_data.get("tl", 0)),
+ int(sstlv_data.get("to", 0)),
+ )
+ else:
+ sub_sub_tlvs = b""
+
+ # Parse the IPv6 string into 16 bytes
+ sid_bytes = socket.inet_pton(socket.AF_INET6, sid_str)
+ behavior_bytes = struct.pack("!H", behavior)
+
+ # Construct Sub-TLV 1 Payload: Reserved(1) + SID(16) + [Flags(1)] + Behavior(2) + Sub-Sub-TLVs
+ sub_tlv_payload = (
+ struct.pack("!B", 0) + sid_bytes + flags_bytes + behavior_bytes + sub_sub_tlvs
+ )
+
+ # Construct Sub-TLV: Type(1) + Length(2) + Payload
+ return struct.pack("!BH", 1, len(sub_tlv_payload)) + sub_tlv_payload
+
+
def encode_path_attribute(type, value):
path_attributes = {
"origin": [b"\x40", 1],
@@ -390,51 +431,17 @@ def encode_path_attribute(type, value):
for val in value:
# Dynamically encode the SRv6 Prefix-SID Attribute (RFC 9252)
srv6_type = int(val.get("type", 6)) # 5 = L3 Service, 6 = L2 Service
- sid_str = val.get("sid", "::")
- behavior = int(val.get("behavior", 0))
-
- # Note: Some pre-RFC FRR implementations/drafts used a 1-byte Flags
- # field just before the behavior. We expose it here just in case.
- flags_hex = val.get("flags", "").replace("0x", "")
- flags_bytes = bytes.fromhex(flags_hex) if flags_hex else b""
-
- sstlv_data = val.get("sid_structure", "")
- if isinstance(sstlv_data, str):
- sub_sub_tlvs = bytes.fromhex(sstlv_data)
- elif isinstance(sstlv_data, dict):
- # Pack: Type(2) + Length(2) + LBL(1) + LNL(1) + FL(1) + AL(1) + TL(1) + TO(1)
- sub_sub_tlvs = struct.pack(
- "!HHBBBBBB",
- 1, # SID Structure type
- 6, # Fixed Length of 6 bytes for the payload
- int(sstlv_data.get("lbl", 0)),
- int(sstlv_data.get("lnl", 0)),
- int(sstlv_data.get("fl", 0)),
- int(sstlv_data.get("al", 0)),
- int(sstlv_data.get("tl", 0)),
- int(sstlv_data.get("to", 0)),
- )
- else:
- sub_sub_tlvs = b""
-
- # Parse the IPv6 string into 16 bytes
- sid_bytes = socket.inet_pton(socket.AF_INET6, sid_str)
- behavior_bytes = struct.pack("!H", behavior)
-
- # Construct Sub-TLV 1 Payload: Reserved(1) + SID(16) + [Flags(1)] + Behavior(2) + Sub-Sub-TLVs
- sub_tlv_payload = (
- struct.pack("!B", 0)
- + sid_bytes
- + flags_bytes
- + behavior_bytes
- + sub_sub_tlvs
- )
- # Construct Sub-TLV: Type(1) + Length(2) + Payload
- sub_tlv = struct.pack("!BH", 1, len(sub_tlv_payload)) + sub_tlv_payload
+ # An optional "sub_tlvs" list encodes several SID Information
+ # Sub-TLVs in the same TLV. Otherwise, the entry itself is the
+ # only SID Information Sub-TLV.
+ sub_tlvs = b"".join(
+ encode_srv6_sid_info_sub_tlv(sub_tlv)
+ for sub_tlv in val.get("sub_tlvs", [val])
+ )
# Construct Main TLV Payload: Reserved(1) + Sub-TLVs
- tlv_payload = struct.pack("!B", 0) + sub_tlv
+ tlv_payload = struct.pack("!B", 0) + sub_tlvs
# Construct Final Attribute: Type(1) + Length(2) + Payload
attribute_value = attribute_value + (
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_l2vpn_evpn_route_detail.json b/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_l2vpn_evpn_route_detail.json
index 6ab3105280..ceb52bf9fa 100644
--- a/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_l2vpn_evpn_route_detail.json
+++ b/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_l2vpn_evpn_route_detail.json
@@ -665,6 +665,58 @@
},
"numRoutes": 5
},
- "numPrefix": 15,
- "numPaths": 15
+ "192.0.2.1:203": {
+ "rd": "192.0.2.1:203",
+ "[5]:[0]:[64]:[2001:5::]": {
+ "prefixLen": 352,
+ "rd": "192.0.2.1:203",
+ "routeType": 5,
+ "ethTag": 0,
+ "ipLen": 64,
+ "ip": "2001:5::",
+ "pathCount": 1,
+ "paths": [
+ [
+ {
+ "label": "32763",
+ "aspath": {
+ "string": "65001"
+ },
+ "origin": "IGP",
+ "valid": true,
+ "extendedCommunity": {
+ "string": "RT:65001:203"
+ },
+ "prefixSids": [
+ {
+ "remoteSid": "3333:3333:3333::",
+ "remoteTransposedSid": "3333:3333:3333:7ff::",
+ "endpointBehavior": "End.DT6",
+ "prefixSidType": "l3",
+ "remoteSidStructure": {
+ "locatorBlockLen": 32,
+ "locatorNodeLen": 16,
+ "functionLen": 16,
+ "argumentLen": 0,
+ "transpositionLen": 16,
+ "transpositionOffset": 48
+ }
+ }
+ ],
+ "nexthops": [
+ {
+ "ip": "fc00::1",
+ "afi": "ipv6",
+ "scope": "global",
+ "used": true
+ }
+ ]
+ }
+ ]
+ ]
+ },
+ "numRoutes": 1
+ },
+ "numPrefix": 16,
+ "numPaths": 16
}
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_summary.json b/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_summary.json
index 0332a22aa0..34eeac8aea 100644
--- a/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_summary.json
+++ b/tests/topotests/bgp_prefix_sid_evpn/r2/show_bgp_summary.json
@@ -2,12 +2,12 @@
"l2VpnEvpn": {
"peers": {
"fc00::1": {
- "pfxRcd": 15,
+ "pfxRcd": 16,
"peerState": "OK",
"connectionsEstablished": 1
},
"fc00::3": {
- "pfxSnt": 15,
+ "pfxSnt": 16,
"peerState": "OK",
"connectionsEstablished": 1
}
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_l2vpn_evpn_route_detail.json b/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_l2vpn_evpn_route_detail.json
index 60368b4049..3d8aa1c538 100644
--- a/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_l2vpn_evpn_route_detail.json
+++ b/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_l2vpn_evpn_route_detail.json
@@ -665,6 +665,58 @@
},
"numRoutes": 5
},
- "numPrefix": 15,
- "numPaths": 15
+ "192.0.2.1:203": {
+ "rd": "192.0.2.1:203",
+ "[5]:[0]:[64]:[2001:5::]": {
+ "prefixLen": 352,
+ "rd": "192.0.2.1:203",
+ "routeType": 5,
+ "ethTag": 0,
+ "ipLen": 64,
+ "ip": "2001:5::",
+ "pathCount": 1,
+ "paths": [
+ [
+ {
+ "label": "32763",
+ "aspath": {
+ "string": "65002 65001"
+ },
+ "origin": "IGP",
+ "valid": true,
+ "extendedCommunity": {
+ "string": "RT:65001:203"
+ },
+ "prefixSids": [
+ {
+ "remoteSid": "3333:3333:3333::",
+ "remoteTransposedSid": "3333:3333:3333:7ff::",
+ "endpointBehavior": "End.DT6",
+ "prefixSidType": "l3",
+ "remoteSidStructure": {
+ "locatorBlockLen": 32,
+ "locatorNodeLen": 16,
+ "functionLen": 16,
+ "argumentLen": 0,
+ "transpositionLen": 16,
+ "transpositionOffset": 48
+ }
+ }
+ ],
+ "nexthops": [
+ {
+ "ip": "fc00::1",
+ "afi": "ipv6",
+ "scope": "global",
+ "used": true
+ }
+ ]
+ }
+ ]
+ ]
+ },
+ "numRoutes": 1
+ },
+ "numPrefix": 16,
+ "numPaths": 16
}
diff --git a/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_summary.json b/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_summary.json
index 6b1eb38fe2..206bdf5a7d 100644
--- a/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_summary.json
+++ b/tests/topotests/bgp_prefix_sid_evpn/r3/show_bgp_summary.json
@@ -2,7 +2,7 @@
"l2VpnEvpn": {
"peers": {
"fc00::2": {
- "pfxRcd": 15,
+ "pfxRcd": 16,
"peerState": "OK",
"connectionsEstablished": 1
}