git: bc301fee4cb2 - main - routing: Add tests for metric

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Wed, 20 May 2026 20:58:55 UTC
The branch main has been updated by pouria:

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

commit bc301fee4cb2c3e9ce220dc3e0cbb9d7d5a83d6f
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-05-15 14:03:37 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-05-20 20:55:11 +0000

    routing: Add tests for metric
    
    Add tests to make sure:
    * Default metric is enforced.
    * Lowest metric wins.
    * Deleting routes by specifying gateway/metric works.
    
    Reviewed by:    markj
    Differential Revision: https://reviews.freebsd.org/D57016
---
 tests/sys/net/routing/Makefile        |   3 +
 tests/sys/net/routing/test_routing.sh | 231 ++++++++++++++++++++++++++++++++++
 2 files changed, 234 insertions(+)

diff --git a/tests/sys/net/routing/Makefile b/tests/sys/net/routing/Makefile
index c725d23f15d1..cbb00a236871 100644
--- a/tests/sys/net/routing/Makefile
+++ b/tests/sys/net/routing/Makefile
@@ -8,6 +8,7 @@ ATF_TESTS_C +=	test_rtsock_lladdr
 ATF_TESTS_C +=	test_rtsock_ops
 ATF_TESTS_PYTEST +=	test_routing_l3.py
 ATF_TESTS_PYTEST +=	test_rtsock_multipath.py
+ATF_TESTS_SH+=	test_routing
 
 ${PACKAGE}FILES+=	generic_cleanup.sh
 ${PACKAGE}FILESMODE_generic_cleanup.sh=0555
@@ -15,6 +16,8 @@ ${PACKAGE}FILESMODE_generic_cleanup.sh=0555
 # Most of the tests operates on a common IPv4/IPv6 prefix,
 # so running them in parallel will lead to weird results.
 TEST_METADATA+=	is_exclusive=true
+TEST_METADATA.test_routing+= execenv="jail" \
+			execenv_jail_params="vnet"
 
 CFLAGS+=	-I${.CURDIR:H:H:H}
 
diff --git a/tests/sys/net/routing/test_routing.sh b/tests/sys/net/routing/test_routing.sh
new file mode 100755
index 000000000000..296bd7ebaffd
--- /dev/null
+++ b/tests/sys/net/routing/test_routing.sh
@@ -0,0 +1,231 @@
+#
+# Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+
+. $(atf_get_srcdir)/../../common/vnet.subr
+
+jq_rtentry()
+{
+	local route="$1"
+
+	jq -r '.statistics."route-information"."route-table"."rt-family".[]."rt-entry".[] |
+	    select(.destination == "'${route}'")'
+}
+
+jq_nhop_filter()
+{
+	local nhop="$1"
+	local weight="$2"
+	local metric="$3"
+
+	jq -r 'select(.gateway == "'${nhop}'") |
+	    select(.weight == '${weight}') |
+	    select(.metric == '${metric}') |
+	    .gateway'
+}
+
+
+atf_test_case "add_lowest_metric" "cleanup"
+add_lowest_metric_head()
+{
+	atf_set descr 'Create 4 routes to same dst and verify the lowest metric wins'
+	atf_set require.user root
+	atf_set require.progs jq
+}
+
+add_lowest_metric_body()
+{
+	local epair laddr route nhop1 nhop2 nhop3
+
+	laddr="3fff::1"
+	route="3fff:a::"
+	nhop1="3fff::1"
+	nhop2="3fff::2"
+	nhop3="3fff::3"
+
+	vnet_init
+	epair=$(vnet_mkepair)
+
+	atf_check -o ignore \
+	    ifconfig ${epair}a inet6 ${laddr} up
+
+	# Create an ECMP route with metric 2
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop2} -weight 10 -metric 2
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop3} -weight 10 -metric 2
+
+	# Validate routes
+	atf_check -o save:netstat \
+	    netstat -rn6 --libxo json
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 10 2)
+	atf_check_equal "$output" "$nhop2"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop3} 10 2)
+	atf_check_equal "$output" "$nhop3"
+
+	# Create a route with metric 3
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop1} -metric 3
+	# Verify that nhop1 is not the best route
+	atf_check -o not-match:".*gateway: ${nhop1}.*" \
+	    route -n6 get -net ${route}/64
+
+	# Create a route to the same nhop with same metric 3 and verify it fails
+	atf_check -s exit:1 -o ignore -e match:".*exists.*" \
+	    route -6 add -net ${route}/64 -gateway ${nhop1} -metric 3
+
+	# Create a route to an existing nhop with lower metric
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop1} -metric 1
+	# Verify that nhop1 is now the best route
+	atf_check -o match:".*gateway: ${nhop1}.*" \
+	    route -n6 get -net ${route}/64
+}
+
+add_lowest_metric_cleanup()
+{
+	vnet_cleanup
+}
+
+atf_test_case "add_default_metric" "cleanup"
+add_default_metric_head()
+{
+	atf_set descr 'Create a route and verify the default metric is set'
+	atf_set require.user root
+	atf_set require.progs jq
+}
+
+add_default_metric_body()
+{
+	local epair laddr route nhop1
+
+	laddr="3fff::1"
+	route="3fff:a::"
+	nhop1="3fff::1"
+
+	vnet_init
+	epair=$(vnet_mkepair)
+
+	atf_check -o ignore \
+	    ifconfig ${epair}a inet6 ${laddr} up
+
+	# Create a route without specifying its metric
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop1}
+
+	# Verify the route has the default metric of 1
+	atf_check -o save:netstat \
+	    netstat -rn6 --libxo json
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop1} 1 1)
+	atf_check_equal "$output" "$nhop1"
+}
+
+add_default_metric_cleanup()
+{
+	vnet_cleanup
+}
+
+atf_test_case "delete_route_with_metric" "cleanup"
+delete_route_with_metric_head()
+{
+	atf_set descr 'Create multiple routes to same dst and delete routes with specific metric'
+	atf_set require.user root
+	atf_set require.progs jq
+}
+
+delete_route_with_metric_body()
+{
+	local epair laddr route nhop1 nhop2
+
+	laddr="3fff::1"
+	route="3fff:a::"
+	nhop1="3fff::1"
+	nhop2="3fff::2"
+
+	vnet_init
+	epair=$(vnet_mkepair)
+
+	atf_check -o ignore \
+	    ifconfig ${epair}a inet6 ${laddr} up
+
+	# Create two groups of ECMP routes with metric 2 and 3, and
+	# another route with metric 4.
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop1} -metric 3
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop1} -weight 10 -metric 2
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop2} -weight 10 -metric 2
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop2} -metric 3
+	atf_check -o ignore \
+	    route -6 add -net ${route}/64 -gateway ${nhop2} -metric 4
+
+	# Validate we have 5 routes
+	atf_check -o save:netstat \
+	    netstat -rn6 --libxo json
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop1} 1 3)
+	atf_check_equal "$output" "$nhop1"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop1} 10 2)
+	atf_check_equal "$output" "$nhop1"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 10 2)
+	atf_check_equal "$output" "$nhop2"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 1 3)
+	atf_check_equal "$output" "$nhop2"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 1 4)
+	atf_check_equal "$output" "$nhop2"
+
+	# Delete one of the nexthops of them best ECMP route
+	# Test that deleting a route by specifying gateway + metric works.
+	atf_check -o ignore \
+	    route -n6 delete -net ${route}/64 -gateway ${nhop2} -metric 2
+
+	# Verify that nhop1 is the best route now
+	atf_check -o match:".*gateway: ${nhop1}.*" \
+	    route -n6 get -net ${route}/64
+
+	# But other route with nhops2 should exists.
+	atf_check -o save:netstat \
+	    netstat -rn6 --libxo json
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 1 3)
+	atf_check_equal "$output" "$nhop2"
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 1 4)
+	atf_check_equal "$output" "$nhop2"
+
+	# Delete routes with nhop1 as nexthop without specifying metric.
+	# Test that deleting a route by gateway removes all routes with
+	# that gateway, regardless of metric value.
+	atf_check -o ignore \
+	    route -n6 delete -net ${route}/64 -gateway ${nhop1}
+
+	# Verify that nhop2 is the best route now
+	atf_check -o match:".*gateway: ${nhop2}.*" \
+	    route -n6 get -net ${route}/64
+
+	# Delete routes with metric 3 without specifying their gateway.
+	# Test that deleting a route by metric removes all routes with
+	# that metric, regardless of gateway value.
+	atf_check -o ignore \
+	    route -n6 delete -net ${route}/64 -metric 3
+
+	# Verify that nhop2 is still the best route with metric of 4
+	atf_check -o match:".*gateway: ${nhop2}.*" \
+	    route -n6 get -net ${route}/64
+	output=$(cat netstat | jq_rtentry ${route}/64 | jq_nhop_filter ${nhop2} 1 4)
+	atf_check_equal "$output" "$nhop2"
+}
+
+delete_route_with_metric_cleanup()
+{
+	vnet_cleanup
+}
+
+
+atf_init_test_cases()
+{
+	atf_add_test_case "add_lowest_metric"
+	atf_add_test_case "add_default_metric"
+	atf_add_test_case "delete_route_with_metric"
+}