Commit b02a991a89 for frr

commit b02a991a8936186eb82e4d211c9072610587353f
Author: Donald Sharp <sharpd@nvidia.com>
Date:   Thu Nov 5 10:20:23 2026 -0500

    tests: Modify ospf_hello_neighbor_identity test to wait until sniffer is ready

    Currently the test starts a packet sniffer, does a sleep and then captures
    a packet or two for a short time and then declares victory.  Unless of
    course you are running on a obscenely busy ci system.  Modify the
    sniffer to code to wait until the sniffer is ready and then wait
    until a decent amount of time to see the packet.

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

diff --git a/tests/topotests/ospf_hello_neighbor_identity/send_hello.py b/tests/topotests/ospf_hello_neighbor_identity/send_hello.py
index 06669a6803..1d3783677d 100644
--- a/tests/topotests/ospf_hello_neighbor_identity/send_hello.py
+++ b/tests/topotests/ospf_hello_neighbor_identity/send_hello.py
@@ -23,7 +23,7 @@ visible from the host (use the test's logdir).
 import argparse
 import json
 import sys
-import time
+import threading

 from scapy.all import AsyncSniffer, Ether, IP, Raw, conf, sendp, wrpcap
 from scapy.contrib.ospf import OSPF_Hdr, OSPF_Hello
@@ -33,6 +33,8 @@ conf.verb = 0
 ALL_SPF_ROUTERS = "224.0.0.5"
 ALL_SPF_ROUTERS_MAC = "01:00:5e:00:00:05"
 OSPF_PROTO = 89
+SNIFFER_START_TIMEOUT = 15
+CAPTURE_TIMEOUT = 15


 def main():
@@ -86,17 +88,39 @@ def main():
         / Raw(payload)
     )

+    # AsyncSniffer.start() only launches a thread; the capture socket and
+    # BPF filter are not in place until the thread calls started_callback.
+    # Do not send until then, or the frame can leave before the sniffer is
+    # listening (seen on loaded CI hosts).
+    def is_ours(packet):
+        return packet.haslayer(IP) and bytes(packet[IP].payload) == payload
+
+    sniffer_ready = threading.Event()
     sniffer = AsyncSniffer(
         iface=args.iface,
         filter="ip proto %d and src host %s and dst host %s"
         % (OSPF_PROTO, args.src_ip, args.dst_ip),
         store=True,
+        started_callback=sniffer_ready.set,
+        # Stop as soon as the exact frame is seen on the wire.
+        stop_filter=is_ours,
     )
     sniffer.start()
-    time.sleep(0.2)
+    if not sniffer_ready.wait(SNIFFER_START_TIMEOUT):
+        sys.exit(
+            "sniffer did not start within %ds (exception: %s)"
+            % (SNIFFER_START_TIMEOUT, sniffer.exception)
+        )
     sendp(frame, iface=args.iface, count=1, verbose=False)
-    time.sleep(0.3)
-    packets = list(sniffer.stop())
+
+    # Wait for the matching frame instead of sleeping a fixed time: this
+    # returns immediately on a healthy host and still tolerates a starved
+    # sniffer thread on a loaded one.
+    sniffer.join(timeout=CAPTURE_TIMEOUT)
+    if sniffer.running:
+        # No match within the window; stop the sniffer and keep what it has.
+        sniffer.stop()
+    packets = list(sniffer.results or [])

     matching = [
         packet