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")