Commit 740d67f9bf for frr
commit 740d67f9bff30d02131fefff943f253fe6d9936f
Author: Chenlang Li <lichenlang@catalystovo.com>
Date: Fri Nov 6 23:26:36 2026 +0800
tests: ospf6d: check that deleting a neighbor cancels its pending events
Queue seqnumber_mismatch() and bad_lsreq() the way ospf6_message.c does,
with the neighbor's tracking handles, then delete the neighbor the way
ospf6_neighbor_remove() does and run the event loop. Count the events
that are dispatched with the deleted neighbor as argument, and keep a
second neighbor's event and one carrying the interface as controls.
Without the fix both handlers run on freed memory; with it neither event
is left behind and the control events still run. NEIGHBOR_TEST_HANDLER
selects which handlers are queued.
Signed-off-by: Chenlang Li <lichenlang@catalystovo.com>
diff --git a/tests/ospf6d/subdir.am b/tests/ospf6d/subdir.am
index 0bbd3a51d4..a46b108ae9 100644
--- a/tests/ospf6d/subdir.am
+++ b/tests/ospf6d/subdir.am
@@ -21,9 +21,18 @@ tests_ospf6d_test_grace_lsa_CPPFLAGS = $(TESTS_CPPFLAGS)
tests_ospf6d_test_grace_lsa_LDADD = $(OSPF6_TEST_LDADD)
tests_ospf6d_test_grace_lsa_SOURCES = tests/ospf6d/test_grace_lsa.c
+if OSPF6D
+check_PROGRAMS += tests/ospf6d/test_neighbor_lifecycle
+endif
+tests_ospf6d_test_neighbor_lifecycle_CFLAGS = $(TESTS_CFLAGS)
+tests_ospf6d_test_neighbor_lifecycle_CPPFLAGS = $(TESTS_CPPFLAGS)
+tests_ospf6d_test_neighbor_lifecycle_LDADD = $(OSPF6_TEST_LDADD)
+tests_ospf6d_test_neighbor_lifecycle_SOURCES = tests/ospf6d/test_neighbor_lifecycle.c
+
EXTRA_DIST += \
tests/ospf6d/test_grace_lsa.py \
tests/ospf6d/test_lsdb.py \
tests/ospf6d/test_lsdb.in \
tests/ospf6d/test_lsdb.refout \
+ tests/ospf6d/test_neighbor_lifecycle.py \
# end
diff --git a/tests/ospf6d/test_neighbor_lifecycle.c b/tests/ospf6d/test_neighbor_lifecycle.c
new file mode 100644
index 0000000000..9ad58aa42d
--- /dev/null
+++ b/tests/ospf6d/test_neighbor_lifecycle.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A neighbor must not leave events behind that carry its own address.
+ *
+ * seqnumber_mismatch() and bad_lsreq() are queued by ospf6_message.c with the
+ * neighbor as the event argument; the neighbor keeps a handle for each of them
+ * (thread_seqnumber_mismatch, thread_bad_lsreq), so ospf6_neighbor_delete()
+ * cancels both before the memory goes away, without touching events of other
+ * neighbors or events that carry the interface pointer.
+ *
+ * The test queues the real handlers, deletes the neighbor the way
+ * ospf6_neighbor_remove() does and runs the event loop. The event loop counts
+ * how many events are dispatched with the deleted neighbor as argument before
+ * running them; without the fix those callbacks run on freed memory.
+ *
+ * NEIGHBOR_TEST_HANDLER=bad_lsreq|seqnumber_mismatch|both selects which
+ * handlers are queued (default: both).
+ */
+
+#include <zebra.h>
+
+#include "memory.h"
+#include "frrevent.h"
+#include "libospf.h"
+#include "ospf6d/ospf6_proto.h"
+#include "ospf6d/ospf6_lsa.h"
+#include "ospf6d/ospf6_lsdb.h"
+#include "ospf6d/ospf6_interface.h"
+#include "ospf6d/ospf6_top.h"
+#include "ospf6d/ospf6_neighbor.h"
+#include "ospf6d/ospf6d.h"
+
+#define TEST_PENDING "neighbor: pending events cancelled on delete"
+#define TEST_OTHER "neighbor: other neighbor's events survive"
+#define TEST_IFACE "neighbor: interface events survive"
+
+struct event_loop *master;
+struct zebra_privs_t ospf6d_privs;
+
+static int dispatched_pending;
+static int dispatched_other;
+static int dispatched_iface;
+/* Keeps the control callbacks' reads alive. */
+static unsigned int seen;
+
+static void other_cb(struct event *event)
+{
+ struct ospf6_neighbor *on = EVENT_ARG(event);
+
+ seen += on->state;
+ dispatched_other++;
+}
+
+static void iface_cb(struct event *event)
+{
+ struct ospf6_interface *oi = EVENT_ARG(event);
+
+ seen += oi->interface ? 1 : 0;
+ dispatched_iface++;
+}
+
+/* The real constructor, so the neighbor owns the memory types and lsdb lists
+ * that ospf6_neighbor_delete() expects to free.
+ */
+static struct ospf6_neighbor *neighbor_new(struct ospf6_interface *oi)
+{
+ struct ospf6_neighbor *on = ospf6_neighbor_create(htonl(0x02020202), oi);
+
+ on->state = OSPF6_NEIGHBOR_FULL;
+ return on;
+}
+
+static void failed(const char *test, const char *why)
+{
+ printf("%s\n%s\nfailed!\n", test, why);
+ exit(1);
+}
+
+/* Count events that were queued for the deleted neighbor, then run them. */
+static void run_loop(struct ospf6_neighbor *gone)
+{
+ struct event event;
+
+ while (event_fetch(master, &event)) {
+ if (event.arg == gone && (event.func == seqnumber_mismatch ||
+ event.func == bad_lsreq))
+ dispatched_pending++;
+ event_call(&event);
+ }
+}
+
+int main(void)
+{
+ struct ospf6_interface *oi;
+ struct ospf6_neighbor *on, *other;
+ const char *which = getenv("NEIGHBOR_TEST_HANDLER");
+ bool want_bad, want_seq;
+
+ if (which == NULL || !strcmp(which, "both")) {
+ want_bad = want_seq = true;
+ } else if (!strcmp(which, "bad_lsreq")) {
+ want_bad = true;
+ want_seq = false;
+ } else if (!strcmp(which, "seqnumber_mismatch")) {
+ want_bad = false;
+ want_seq = true;
+ } else {
+ printf("unknown NEIGHBOR_TEST_HANDLER=%s\n", which);
+ return 2;
+ }
+
+ master = event_master_create(NULL);
+
+ oi = XCALLOC(MTYPE_TMP, sizeof(*oi));
+ oi->neighbor_list = list_new();
+ oi->interface = XCALLOC(MTYPE_TMP, sizeof(struct interface));
+
+ on = neighbor_new(oi);
+ other = neighbor_new(oi);
+
+ /* The two handlers, queued exactly like ospf6_message.c does it. */
+ if (want_seq)
+ event_add_event(master, seqnumber_mismatch, on, 0,
+ &on->thread_seqnumber_mismatch);
+ if (want_bad)
+ event_add_event(master, bad_lsreq, on, 0,
+ &on->thread_bad_lsreq);
+ /* Controls: another neighbor, and the interface itself. */
+ event_add_event(master, other_cb, other, 0, NULL);
+ event_add_event(master, iface_cb, oi, 0, NULL);
+
+ /* ospf6_neighbor_remove(): unlink first, then free. */
+ listnode_delete(oi->neighbor_list, on);
+ ospf6_neighbor_delete(on);
+
+ run_loop(on);
+
+ printf("queued: %s%s%s dispatched=%d other=%d iface=%d\n",
+ want_seq ? "seqnumber_mismatch" : "", want_seq && want_bad ? "+" : "",
+ want_bad ? "bad_lsreq" : "", dispatched_pending, dispatched_other,
+ dispatched_iface);
+
+ if (dispatched_pending)
+ failed(TEST_PENDING,
+ dispatched_pending == 1
+ ? "handler ran with the deleted neighbor as argument"
+ : "handlers ran with the deleted neighbor as argument");
+ printf("%s\nOK\n", TEST_PENDING);
+
+ if (dispatched_other != 1)
+ failed(TEST_OTHER, "event of another neighbor was cancelled");
+ printf("%s\nOK\n", TEST_OTHER);
+
+ if (dispatched_iface != 1)
+ failed(TEST_IFACE, "event with the interface was cancelled");
+ printf("%s\nOK\n", TEST_IFACE);
+
+ ospf6_neighbor_delete(other);
+ list_delete(&oi->neighbor_list);
+ XFREE(MTYPE_TMP, oi->interface);
+ XFREE(MTYPE_TMP, oi);
+ event_master_free(master);
+ return 0;
+}
diff --git a/tests/ospf6d/test_neighbor_lifecycle.py b/tests/ospf6d/test_neighbor_lifecycle.py
new file mode 100644
index 0000000000..9cb2f97084
--- /dev/null
+++ b/tests/ospf6d/test_neighbor_lifecycle.py
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+import frrtest
+
+
+class TestNeighborLifecycle(frrtest.TestMultiOut):
+ program = "./test_neighbor_lifecycle"
+
+
+TestNeighborLifecycle.okfail("neighbor: pending events cancelled on delete")
+TestNeighborLifecycle.okfail("neighbor: other neighbor's events survive")
+TestNeighborLifecycle.okfail("neighbor: interface events survive")