Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Apr 2026 22:07:51 +0000
From:      Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 23b8d16c6641 - main - tests/netlink: Add nexthop group tests for multipath
Message-ID:  <69f3d2b7.1d184.1ddb9670@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=23b8d16c6641362833a8decdcb98b643006c3f5c

commit 23b8d16c6641362833a8decdcb98b643006c3f5c
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-04-19 11:04:01 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-04-30 22:06:20 +0000

    tests/netlink: Add nexthop group tests for multipath
    
    Added tests:
    * Test for creating multiple routes.
    * Test for merge multiple nexthops into a single nexthop group.
    * Test for nexthop expirations from a nexthop group.
    
    Reviewed by: glebius
    Differential Revision: https://reviews.freebsd.org/D56190
---
 tests/sys/netlink/Makefile          |   1 +
 tests/sys/netlink/test_rtnl_route.c | 321 ++++++++++++++++++++++++++++++++++++
 2 files changed, 322 insertions(+)

diff --git a/tests/sys/netlink/Makefile b/tests/sys/netlink/Makefile
index 43b9db80ee63..89dba16c823c 100644
--- a/tests/sys/netlink/Makefile
+++ b/tests/sys/netlink/Makefile
@@ -6,6 +6,7 @@ TESTSDIR=       ${TESTSBASE}/sys/netlink
 ATF_TESTS_C+=	netlink_socket
 ATF_TESTS_C+=	test_snl test_snl_generic
 ATF_TESTS_C+=	test_rtnl_gre
+ATF_TESTS_C+=	test_rtnl_route
 ATF_TESTS_PYTEST +=	test_nl_core.py
 ATF_TESTS_PYTEST +=	test_rtnl_iface.py
 ATF_TESTS_PYTEST +=	test_rtnl_ifaddr.py
diff --git a/tests/sys/netlink/test_rtnl_route.c b/tests/sys/netlink/test_rtnl_route.c
new file mode 100644
index 000000000000..84d73db11cc7
--- /dev/null
+++ b/tests/sys/netlink/test_rtnl_route.c
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/param.h>
+#include <sys/module.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <netlink/netlink.h>
+#include <netlink/netlink_route.h>
+#include "netlink/netlink_snl.h"
+#include <netlink/netlink_snl_route.h>
+#include <netlink/netlink_snl_route_compat.h>
+#include <netlink/netlink_snl_route_parsers.h>
+
+#include <unistd.h>
+#include <time.h>
+
+#include <atf-c.h>
+
+static struct rtmsg *
+prepare_rtm_by_dst(struct snl_writer *nw, char *dst)
+{
+	struct rtmsg *rtm;
+	struct in_addr in_dst;
+
+	inet_pton(AF_INET, dst, &in_dst);
+	rtm = snl_reserve_msg_object(nw, struct rtmsg);
+	if (rtm == NULL)
+		return (NULL);
+
+	rtm->rtm_family = AF_INET;
+	rtm->rtm_protocol = RTPROT_STATIC;
+	rtm->rtm_type = RTN_UNICAST;
+	rtm->rtm_dst_len = 24;
+	rtm->rtm_flags = RTF_GATEWAY;
+	snl_add_msg_attr_ip4(nw, RTA_DST, &in_dst);
+
+	return (rtm);
+}
+
+static void
+cleanup_route_by_dst(struct snl_state *ss, struct snl_writer *nw, char *dst)
+{
+	struct nlmsghdr *hdr, *rx_hdr;
+	struct snl_errmsg_data e = {};
+
+	/* Delete route */
+	snl_init_writer(ss, nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(nw, RTM_DELROUTE)) != NULL);
+	ATF_REQUIRE(prepare_rtm_by_dst(nw, dst) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(ss, hdr->nlmsg_seq)) != NULL);
+}
+
+ATF_TC(rtnl_nhgrp);
+ATF_TC_HEAD(rtnl_nhgrp, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "test nexthop group using netlink");
+	atf_tc_set_md_var(tc, "require.user", "root");
+	atf_tc_set_md_var(tc, "require.kmods", "netlink");
+}
+
+ATF_TC_BODY(rtnl_nhgrp, tc)
+{
+	struct snl_state ss;
+	struct snl_writer nw;
+	struct nlmsghdr *hdr, *rx_hdr;
+	struct in_addr gw1, gw2;
+	struct snl_errmsg_data e = {};
+	struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT };
+	struct rtmsg *rtm;
+	struct rtnexthop *rtnh;
+	int off, off2;
+
+	ATF_REQUIRE_MSG(snl_init(&ss, NETLINK_ROUTE), "snl_init() failed");
+
+	inet_pton(AF_INET, "127.0.0.1", &gw1);
+	inet_pton(AF_INET, "127.0.0.2", &gw2);
+
+	/* Create new multipath route */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWROUTE)) != NULL);
+	hdr->nlmsg_flags |= NLM_F_CREATE;
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "192.0.2.0")) != NULL);
+
+	off = snl_add_msg_attr_nested(&nw, RTA_MULTIPATH);
+	/* first nexthop */
+	off2 = snl_get_msg_offset(&nw);
+	rtnh = snl_reserve_msg_object(&nw, struct rtnexthop);
+	rtnh->rtnh_flags = 0;
+	rtnh->rtnh_hops = 1;
+	rtnh->rtnh_ifindex = 0;
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw1);
+	rtnh = snl_restore_msg_offset(&nw, off2, struct rtnexthop);
+	rtnh->rtnh_len = snl_get_msg_offset(&nw) - off2;
+
+	/* second nexthop */
+	off2 = snl_get_msg_offset(&nw);
+	rtnh = snl_reserve_msg_object(&nw, struct rtnexthop);
+	rtnh->rtnh_flags = 0;
+	rtnh->rtnh_hops = 1;
+	rtnh->rtnh_ifindex = 0;
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw2);
+	rtnh = snl_restore_msg_offset(&nw, off2, struct rtnexthop);
+	rtnh->rtnh_len = snl_get_msg_offset(&nw) - off2;
+
+	snl_end_attr_nested(&nw, off);
+
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e));
+	ATF_REQUIRE_INTEQ(e.error, 0);
+
+	/* Get route and check for its nexthop group */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETROUTE)) != NULL);
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "192.0.2.0")) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
+	ATF_CHECK(r.rta_knh_id != 0);
+	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+
+	cleanup_route_by_dst(&ss, &nw, "192.0.2.0");
+}
+
+ATF_TC(rtnl_nhop_merge);
+ATF_TC_HEAD(rtnl_nhop_merge, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "test merge of two independent nexthop using netlink");
+	atf_tc_set_md_var(tc, "require.user", "root");
+	atf_tc_set_md_var(tc, "require.kmods", "netlink");
+}
+
+ATF_TC_BODY(rtnl_nhop_merge, tc)
+{
+	struct snl_state ss;
+	struct snl_writer nw;
+	struct nlmsghdr *hdr, *rx_hdr;
+	struct in_addr gw1, gw2;
+	struct snl_errmsg_data e = {};
+	struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT };
+	struct rtmsg *rtm;
+	struct rtnexthop *rtnh;
+
+	ATF_REQUIRE_MSG(snl_init(&ss, NETLINK_ROUTE), "snl_init() failed");
+
+	inet_pton(AF_INET, "127.0.1.1", &gw1);
+	inet_pton(AF_INET, "127.0.1.2", &gw2);
+
+	/* Create new route with single nhop */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWROUTE)) != NULL);
+	hdr->nlmsg_flags |= NLM_F_CREATE;
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "198.51.100.0")) != NULL);
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw1);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e));
+	ATF_REQUIRE_INTEQ(e.error, 0);
+
+	/* Get route and verify it's NOT a nexthop group */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETROUTE)) != NULL);
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "198.51.100.0")) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
+	ATF_CHECK(r.rta_knh_id != 0);
+	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 0);
+
+	/* Append anoher nhop */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWROUTE)) != NULL);
+	hdr->nlmsg_flags |= NLM_F_APPEND;
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "198.51.100.0")) != NULL);
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw2);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e));
+	ATF_REQUIRE_INTEQ(e.error, 0);
+
+	/* Get route and verify it became a nexthop group */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETROUTE)) != NULL);
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "198.51.100.0")) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
+	ATF_CHECK(r.rta_knh_id != 0);
+	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+
+	cleanup_route_by_dst(&ss, &nw, "198.51.100.0");
+}
+
+ATF_TC(rtnl_nhgrp_expire);
+ATF_TC_HEAD(rtnl_nhgrp_expire, tc)
+{
+	atf_tc_set_md_var(tc, "descr", "test nhop expiration of a member inside nhgrp using netlink");
+	atf_tc_set_md_var(tc, "require.user", "root");
+	atf_tc_set_md_var(tc, "require.kmods", "netlink");
+}
+
+ATF_TC_BODY(rtnl_nhgrp_expire, tc)
+{
+	struct snl_state ss;
+	struct snl_writer nw;
+	struct nlmsghdr *hdr, *rx_hdr;
+	struct in_addr gw1, gw2, gw3;
+	struct snl_errmsg_data e = {};
+	struct snl_parsed_route r = { .rtax_weight = RT_DEFAULT_WEIGHT };
+	struct rtmsg *rtm;
+	struct rtnexthop *rtnh;
+	struct timespec ts;
+	int off, off2;
+
+	ATF_REQUIRE_MSG(snl_init(&ss, NETLINK_ROUTE), "snl_init() failed");
+
+	inet_pton(AF_INET, "127.0.2.1", &gw1);
+	inet_pton(AF_INET, "127.0.2.2", &gw2);
+	inet_pton(AF_INET, "127.0.2.3", &gw3);
+
+	/* create new multipath route */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWROUTE)) != NULL);
+	hdr->nlmsg_flags |= NLM_F_CREATE;
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "203.0.113.0")) != NULL);
+
+	off = snl_add_msg_attr_nested(&nw, RTA_MULTIPATH);
+	/* first nexthop */
+	off2 = snl_get_msg_offset(&nw);
+	rtnh = snl_reserve_msg_object(&nw, struct rtnexthop);
+	rtnh->rtnh_flags = 0;
+	rtnh->rtnh_hops = 1;
+	rtnh->rtnh_ifindex = 0;
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw1);
+	rtnh = snl_restore_msg_offset(&nw, off2, struct rtnexthop);
+	rtnh->rtnh_len = snl_get_msg_offset(&nw) - off2;
+
+	/* second nexthop */
+	off2 = snl_get_msg_offset(&nw);
+	rtnh = snl_reserve_msg_object(&nw, struct rtnexthop);
+	rtnh->rtnh_flags = 0;
+	rtnh->rtnh_hops = 1;
+	rtnh->rtnh_ifindex = 0;
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw2);
+	rtnh = snl_restore_msg_offset(&nw, off2, struct rtnexthop);
+	rtnh->rtnh_len = snl_get_msg_offset(&nw) - off2;
+
+	snl_end_attr_nested(&nw, off);
+
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e));
+	ATF_REQUIRE_INTEQ(e.error, 0);
+
+	/* append anoher nhop with expiration time */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_NEWROUTE)) != NULL);
+	hdr->nlmsg_flags |= NLM_F_APPEND;
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "203.0.113.0")) != NULL);
+	snl_add_msg_attr_ip4(&nw, RTA_GATEWAY, &gw3);
+	/* expire after 1 seconds */
+	clock_gettime(CLOCK_REALTIME_FAST, &ts);
+	snl_add_msg_attr_u32(&nw, RTA_EXPIRES, ts.tv_sec + 1);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_REQUIRE(snl_parse_errmsg(&ss, rx_hdr, &e));
+	ATF_REQUIRE_INTEQ(e.error, 0);
+
+	/* get route and check for number of nhops */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETROUTE)) != NULL);
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "203.0.113.0")) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
+	ATF_CHECK(r.rta_knh_id != 0);
+	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 3);
+
+	/* wait for 2 seconds and try again */
+	sleep(2);
+
+	/* get route and check for number of nhops */
+	snl_init_writer(&ss, &nw);
+	ATF_REQUIRE((hdr = snl_create_msg_request(&nw, RTM_GETROUTE)) != NULL);
+	ATF_REQUIRE((rtm = prepare_rtm_by_dst(&nw, "203.0.113.0")) != NULL);
+	ATF_REQUIRE((hdr = snl_finalize_msg(&nw)) != NULL);
+	ATF_REQUIRE(snl_send_message(&ss, hdr));
+	ATF_REQUIRE((rx_hdr = snl_read_reply(&ss, hdr->nlmsg_seq)) != NULL);
+	ATF_CHECK(snl_parse_nlmsg(&ss, rx_hdr, &snl_rtm_route_parser, &r));
+	ATF_CHECK_INTEQ(r.rta_multipath.num_nhops, 2);
+
+	cleanup_route_by_dst(&ss, &nw, "203.0.113.0");
+}
+
+
+ATF_TP_ADD_TCS(tp)
+{
+	ATF_TP_ADD_TC(tp, rtnl_nhgrp);
+	ATF_TP_ADD_TC(tp, rtnl_nhgrp_expire);
+	ATF_TP_ADD_TC(tp, rtnl_nhop_merge);
+
+	return (atf_no_error());
+}


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69f3d2b7.1d184.1ddb9670>