Commit 72734ae988 for frr

commit 72734ae988b1cf4a4c96a4eb597a5f6246a8cd43
Author: Donald Sharp <sharpd@nvidia.com>
Date:   Mon Oct 5 14:00:44 2026 -0400

    tests: Show that zebra recovers correctly with an unallocated sid

    Show that a request for a sid that has not been configured in zebra
    does not crash zebra.

    Signed-off-by: Donald Sharp <sharpd@nvidia.com>

diff --git a/tests/topotests/static_srv6_sid_before_prefix/r1/frr.conf b/tests/topotests/static_srv6_sid_before_prefix/r1/frr.conf
new file mode 100644
index 0000000000..c8fb5604ca
--- /dev/null
+++ b/tests/topotests/static_srv6_sid_before_prefix/r1/frr.conf
@@ -0,0 +1,6 @@
+!
+! staticd is started from this file. The test configures the locator
+! name first, then the static SID, and only afterwards the prefix.
+!
+hostname r1
+!
diff --git a/tests/topotests/static_srv6_sid_before_prefix/test_static_srv6_sid_before_prefix.py b/tests/topotests/static_srv6_sid_before_prefix/test_static_srv6_sid_before_prefix.py
new file mode 100644
index 0000000000..f8a520ba06
--- /dev/null
+++ b/tests/topotests/static_srv6_sid_before_prefix/test_static_srv6_sid_before_prefix.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+#
+# Copyright 2026, Nvidia Inc.
+#                 Donald Sharp
+#
+"""
+Request an explicit SRv6 static SID before the locator prefix exists.
+
+`locator NAME` creates the locator with prefix ::/0 and no SID block.
+prefix_match() treats that prefix as matching every address, so an explicit
+SID request used to walk a NULL SID-context list and SIGSEGV zebra in
+zebra_srv6_sid_ctx_lookup().
+
+This test applies that order on purpose. Zebra must stay up, and the SID
+must be installed only after the prefix is configured.
+"""
+
+import functools
+import json
+import os
+import sys
+import time
+
+import pytest
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+from lib.topolog import logger
+
+pytestmark = [pytest.mark.staticd]
+
+SID_PREFIX = "fcbb:bbbb:1::/48"
+# staticd's get-locator / get-sid exchange is asynchronous. Hold this long
+# after the SID is configured; zebra used to SIGSEGV in that window.
+ZEBRA_SURVIVE_SECS = 20
+
+EXPECTED_UNREADY_LOCATOR = {
+    "locators": [
+        {
+            "name": "MAIN",
+            "blockBitsLength": 0,
+            "nodeBitsLength": 0,
+            "functionBitsLength": 0,
+        }
+    ]
+}
+
+EXPECTED_UN_SID = {
+    SID_PREFIX: [
+        {
+            "prefix": SID_PREFIX,
+            "protocol": "static",
+            "installed": True,
+            "nexthops": [
+                {
+                    "interfaceName": "sr0",
+                    "fib": True,
+                    "seg6local": {"action": "uN"},
+                }
+            ],
+        }
+    ]
+}
+
+
+def setup_module(mod):
+    tgen = Topogen({"s1": ("r1",)}, mod.__name__)
+    tgen.start_topology()
+
+    router = tgen.gears["r1"]
+    # uN SIDs are installed on the default SRv6 interface.
+    router.run("ip link add sr0 type dummy")
+    router.run("ip link set sr0 up")
+    router.load_frr_config("frr.conf")
+
+    tgen.start_router()
+
+
+def teardown_module():
+    get_topogen().stop_topology()
+
+
+def _static_routes(router):
+    output = router.vtysh_cmd("show ipv6 route static json")
+    try:
+        return json.loads(output)
+    except json.JSONDecodeError as error:
+        return "show ipv6 route static json: {}: {}".format(error, output)
+
+
+def _zebra_still_running(router, started):
+    """Stay unmatched until ZEBRA_SURVIVE_SECS have elapsed.
+
+    run_and_expect() returns on the first match, so a plain "zebra is up"
+    check would succeed immediately. A dead daemon fails the test here
+    instead of being polled again.
+    """
+    status = router.check_router_running()
+    if status:
+        raise AssertionError(status)
+    if time.monotonic() - started < ZEBRA_SURVIVE_SECS:
+        return "waiting"
+    return None
+
+
+def _check_un_sid(router):
+    status = router.check_router_running()
+    if status:
+        raise AssertionError(status)
+
+    routes = _static_routes(router)
+    if isinstance(routes, str):
+        return routes
+    return topotest.json_cmp(routes, EXPECTED_UN_SID)
+
+
+def test_static_sid_before_locator_prefix():
+    tgen = get_topogen()
+    if tgen.routers_have_failure():
+        pytest.skip(tgen.errors)
+    router = tgen.gears["r1"]
+
+    logger.info("Create locator MAIN with no prefix")
+    router.vtysh_cmd(
+        """
+        configure terminal
+         segment-routing
+          srv6
+           locators
+            locator MAIN
+        """
+    )
+    locators = json.loads(router.vtysh_cmd("show segment-routing srv6 locator json"))
+    result = topotest.json_cmp(locators, EXPECTED_UNREADY_LOCATOR)
+    assert result is None, result
+
+    logger.info("Request explicit uN SID before the locator prefix exists")
+    router.vtysh_cmd(
+        """
+        configure terminal
+         segment-routing
+          srv6
+           static-sids
+            sid fcbb:bbbb:1::/48 locator MAIN behavior uN
+        """
+    )
+
+    # count=21 and wait=1: twenty mismatches, then one sample after 20s.
+    logger.info("Wait %ss for the SID request to reach zebra", ZEBRA_SURVIVE_SECS)
+    success, result = topotest.run_and_expect(
+        functools.partial(_zebra_still_running, router, time.monotonic()),
+        None,
+        count=ZEBRA_SURVIVE_SECS + 1,
+        wait=1,
+    )
+    assert success, result
+
+    routes = _static_routes(router)
+    assert not isinstance(routes, str), routes
+    assert SID_PREFIX not in routes, (
+        "SID was installed while locator MAIN had no prefix: {}".format(routes)
+    )
+
+    logger.info("Configure the locator prefix; the SID must be allocated now")
+    router.vtysh_cmd(
+        """
+        configure terminal
+         segment-routing
+          srv6
+           locators
+            locator MAIN
+             prefix fcbb:bbbb:1::/48 block-len 32 node-len 16 func-bits 16
+        """
+    )
+
+    _, result = topotest.run_and_expect(
+        functools.partial(_check_un_sid, router), None, count=5, wait=3
+    )
+    assert result is None, result