Commit 508d967765 for frr
commit 508d9677655cdc70c9d5ed211ef7884b5a157947
Author: Rajasekar Raja <rajasekarr@nvidia.com>
Date: Mon Oct 19 13:18:11 2026 -0700
tests: Add test for SRv6 static SIDs sharing an address
Configure two SIDs that share an address, one on a missing locator, and
check the real one is installed and staticd survives removing the other.
Signed-off-by: Rajasekar Raja <rajasekarr@nvidia.com>
diff --git a/tests/topotests/static_srv6_sids/test_static_srv6_sids.py b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py
index 086711f0f6..c42b14ae70 100755
--- a/tests/topotests/static_srv6_sids/test_static_srv6_sids.py
+++ b/tests/topotests/static_srv6_sids/test_static_srv6_sids.py
@@ -15,6 +15,7 @@ Test for SRv6 static route on zebra
"""
import os
+import re
import sys
import json
import pytest
@@ -619,6 +620,161 @@ def test_srv6_static_sids_locator_removal_and_readd():
check_srv6_static_sids(router, "expected_srv6_sids.json")
+SAME_ADDR_SID = "fcbb:bbbb:1::"
+UN_SID_PREFIX = "fcbb:bbbb:1::/48"
+MISSING_LOCATOR_SID_PREFIX = "fcbb:bbbb:1::/64"
+
+EXPECTED_SAME_ADDR_SID_ALLOCATED = {
+ SAME_ADDR_SID: {"locator": "MAIN", "clients": [{"protocol": "static"}]}
+}
+
+EXPECTED_UN_SID_INSTALLED = {
+ UN_SID_PREFIX: [
+ {
+ "installed": True,
+ "nexthops": [
+ {"interfaceName": "sr0", "fib": True, "seg6local": {"action": "uN"}}
+ ],
+ }
+ ],
+ MISSING_LOCATOR_SID_PREFIX: None,
+}
+
+
+def _kernel_sid_route(router, prefix):
+ output = router.run("ip -6 route show {}".format(prefix))
+ # The seg6local encap is on the nexthop object when zebra uses nhid
+ nhid = re.search(r"nhid (\d+)", output)
+ if nhid:
+ output += router.run("ip nexthop show id {}".format(nhid.group(1)))
+ return output
+
+
+def _check_same_address_sids(router, un_sid_present):
+ status = router.check_router_running()
+ if status:
+ return status
+
+ output = json.loads(router.vtysh_cmd("show segment-routing srv6 sid json"))
+ if un_sid_present:
+ expected = EXPECTED_SAME_ADDR_SID_ALLOCATED
+ else:
+ expected = {SAME_ADDR_SID: None}
+ result = topotest.json_cmp(output, expected)
+ if result is not None:
+ return "show segment-routing srv6 sid: {}".format(result)
+
+ output = json.loads(router.vtysh_cmd("show ipv6 route static json"))
+ if un_sid_present:
+ expected = EXPECTED_UN_SID_INSTALLED
+ else:
+ expected = {UN_SID_PREFIX: None, MISSING_LOCATOR_SID_PREFIX: None}
+ result = topotest.json_cmp(output, expected)
+ if result is not None:
+ return "show ipv6 route static: {}".format(result)
+
+ output = _kernel_sid_route(router, UN_SID_PREFIX)
+ if un_sid_present and "seg6local action End" not in output:
+ return "kernel has no seg6local End route for {}: {}".format(
+ UN_SID_PREFIX, output
+ )
+ if not un_sid_present and output.strip():
+ return "kernel still has {}: {}".format(UN_SID_PREFIX, output)
+
+ output = _kernel_sid_route(router, MISSING_LOCATOR_SID_PREFIX)
+ if output.strip():
+ return "kernel has {}: {}".format(MISSING_LOCATOR_SID_PREFIX, output)
+
+ return None
+
+
+def wait_same_address_sids(router, un_sid_present):
+ func = functools.partial(_check_same_address_sids, router, un_sid_present)
+ _, result = topotest.run_and_expect(func, None, count=15, wait=1)
+ return result
+
+
+def staticd_missing_locator_errors(router):
+ log = router.net.getLog("log", "staticd")
+ return [
+ message
+ for message in (
+ "Failed to install SID {}".format(MISSING_LOCATOR_SID_PREFIX),
+ "Failed to release SID {}".format(MISSING_LOCATOR_SID_PREFIX),
+ )
+ if message in log
+ ]
+
+
+def test_srv6_static_sids_same_address_missing_locator():
+ """
+ Configure two SIDs that share an address and differ only in prefix
+ length: a /64 on a locator that does not exist, then the /48 on MAIN.
+ Then remove the /64.
+
+ zebra's SID notification carries the address but not the prefix length.
+ staticd used to mark the /64 as allocated instead of the /48. zebra
+ listed the SID as allocated while the /48 was missing from the RIB and
+ the kernel, staticd logged "Failed to install SID fcbb:bbbb:1::/64", and
+ removing the /64 crashed staticd in static_zebra_release_srv6_sid().
+ """
+ tgen = get_topogen()
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+ router = tgen.gears["r1"]
+
+ logger.info("Replace the uN SID with a /64 on missing locator LOC_X")
+ router.vtysh_cmd(
+ """
+ configure terminal
+ segment-routing
+ srv6
+ static-sids
+ no sid fcbb:bbbb:1::/48
+ sid fcbb:bbbb:1::/64 locator LOC_X behavior uN
+ """
+ )
+ result = wait_same_address_sids(router, un_sid_present=False)
+ assert result is None, result
+
+ logger.info("Re-add the uN SID on locator MAIN")
+ router.vtysh_cmd(
+ """
+ configure terminal
+ segment-routing
+ srv6
+ static-sids
+ sid fcbb:bbbb:1::/48 locator MAIN behavior uN
+ """
+ )
+ # Asserted below: removing the /64 is what crashes an unfixed staticd
+ readd_result = wait_same_address_sids(router, un_sid_present=True)
+
+ logger.info("Remove the /64 SID on missing locator LOC_X")
+ router.vtysh_cmd(
+ """
+ configure terminal
+ segment-routing
+ srv6
+ static-sids
+ no sid fcbb:bbbb:1::/64
+ """
+ )
+ removal_result = wait_same_address_sids(router, un_sid_present=True)
+
+ errors = []
+ if readd_result is not None:
+ errors.append("after re-adding the /48: {}".format(readd_result))
+ if removal_result is not None:
+ errors.append("after removing the /64: {}".format(removal_result))
+ for message in staticd_missing_locator_errors(router):
+ errors.append("staticd logged '{}'".format(message))
+ assert not errors, "\n".join(errors)
+
+ logger.info("Verify the other SIDs are unaffected")
+ check_srv6_static_sids(router, "expected_srv6_sids.json")
+
+
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))