1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4# This test is for the accept_untracked_na feature to
5# enable RFC9131 behaviour. The following is the test-matrix.
6# drop   accept  fwding                   behaviour
7# ----   ------  ------  ----------------------------------------------
8#    1        X       X  Don't update NC
9#    0        0       X  Don't update NC
10#    0        1       0  Don't update NC
11#    0        1       1  Add a STALE NC entry
12
13ret=0
14# Kselftest framework requirement - SKIP code is 4.
15ksft_skip=4
16
17PAUSE_ON_FAIL=no
18PAUSE=no
19
20HOST_NS="ns-host"
21ROUTER_NS="ns-router"
22
23HOST_INTF="veth-host"
24ROUTER_INTF="veth-router"
25
26ROUTER_ADDR="2000:20::1"
27HOST_ADDR="2000:20::2"
28SUBNET_WIDTH=64
29ROUTER_ADDR_WITH_MASK="${ROUTER_ADDR}/${SUBNET_WIDTH}"
30HOST_ADDR_WITH_MASK="${HOST_ADDR}/${SUBNET_WIDTH}"
31
32IP_HOST="ip -6 -netns ${HOST_NS}"
33IP_HOST_EXEC="ip netns exec ${HOST_NS}"
34IP_ROUTER="ip -6 -netns ${ROUTER_NS}"
35IP_ROUTER_EXEC="ip netns exec ${ROUTER_NS}"
36
37tcpdump_stdout=
38tcpdump_stderr=
39
40log_test()
41{
42	local rc=$1
43	local expected=$2
44	local msg="$3"
45
46	if [ ${rc} -eq ${expected} ]; then
47		printf "    TEST: %-60s  [ OK ]\n" "${msg}"
48		nsuccess=$((nsuccess+1))
49	else
50		ret=1
51		nfail=$((nfail+1))
52		printf "    TEST: %-60s  [FAIL]\n" "${msg}"
53		if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
54		echo
55			echo "hit enter to continue, 'q' to quit"
56			read a
57			[ "$a" = "q" ] && exit 1
58		fi
59	fi
60
61	if [ "${PAUSE}" = "yes" ]; then
62		echo
63		echo "hit enter to continue, 'q' to quit"
64		read a
65		[ "$a" = "q" ] && exit 1
66	fi
67}
68
69setup()
70{
71	set -e
72
73	local drop_unsolicited_na=$1
74	local accept_untracked_na=$2
75	local forwarding=$3
76
77	# Setup two namespaces and a veth tunnel across them.
78	# On end of the tunnel is a router and the other end is a host.
79	ip netns add ${HOST_NS}
80	ip netns add ${ROUTER_NS}
81	${IP_ROUTER} link add ${ROUTER_INTF} type veth \
82                peer name ${HOST_INTF} netns ${HOST_NS}
83
84	# Enable IPv6 on both router and host, and configure static addresses.
85	# The router here is the DUT
86	# Setup router configuration as specified by the arguments.
87	# forwarding=0 case is to check that a non-router
88	# doesn't add neighbour entries.
89        ROUTER_CONF=net.ipv6.conf.${ROUTER_INTF}
90	${IP_ROUTER_EXEC} sysctl -qw \
91                ${ROUTER_CONF}.forwarding=${forwarding}
92	${IP_ROUTER_EXEC} sysctl -qw \
93                ${ROUTER_CONF}.drop_unsolicited_na=${drop_unsolicited_na}
94	${IP_ROUTER_EXEC} sysctl -qw \
95                ${ROUTER_CONF}.accept_untracked_na=${accept_untracked_na}
96	${IP_ROUTER_EXEC} sysctl -qw ${ROUTER_CONF}.disable_ipv6=0
97	${IP_ROUTER} addr add ${ROUTER_ADDR_WITH_MASK} dev ${ROUTER_INTF}
98
99	# Turn on ndisc_notify on host interface so that
100	# the host sends unsolicited NAs.
101	HOST_CONF=net.ipv6.conf.${HOST_INTF}
102	${IP_HOST_EXEC} sysctl -qw ${HOST_CONF}.ndisc_notify=1
103	${IP_HOST_EXEC} sysctl -qw ${HOST_CONF}.disable_ipv6=0
104	${IP_HOST} addr add ${HOST_ADDR_WITH_MASK} dev ${HOST_INTF}
105
106	set +e
107}
108
109start_tcpdump() {
110	set -e
111	tcpdump_stdout=`mktemp`
112	tcpdump_stderr=`mktemp`
113	${IP_ROUTER_EXEC} timeout 15s \
114                tcpdump --immediate-mode -tpni ${ROUTER_INTF} -c 1 \
115                "icmp6 && icmp6[0] == 136 && src ${HOST_ADDR}" \
116                > ${tcpdump_stdout} 2> /dev/null
117	set +e
118}
119
120cleanup_tcpdump()
121{
122	set -e
123	[[ ! -z  ${tcpdump_stdout} ]] && rm -f ${tcpdump_stdout}
124	[[ ! -z  ${tcpdump_stderr} ]] && rm -f ${tcpdump_stderr}
125	tcpdump_stdout=
126	tcpdump_stderr=
127	set +e
128}
129
130cleanup()
131{
132	cleanup_tcpdump
133	ip netns del ${HOST_NS}
134	ip netns del ${ROUTER_NS}
135}
136
137link_up() {
138	set -e
139	${IP_ROUTER} link set dev ${ROUTER_INTF} up
140	${IP_HOST} link set dev ${HOST_INTF} up
141	set +e
142}
143
144verify_ndisc() {
145	local drop_unsolicited_na=$1
146	local accept_untracked_na=$2
147	local forwarding=$3
148
149	neigh_show_output=$(${IP_ROUTER} neigh show \
150                to ${HOST_ADDR} dev ${ROUTER_INTF} nud stale)
151	if [ ${drop_unsolicited_na} -eq 0 ] && \
152			[ ${accept_untracked_na} -eq 1 ] && \
153			[ ${forwarding} -eq 1 ]; then
154		# Neighbour entry expected to be present for 011 case
155		[[ ${neigh_show_output} ]]
156	else
157		# Neighbour entry expected to be absent for all other cases
158		[[ -z ${neigh_show_output} ]]
159	fi
160}
161
162test_unsolicited_na_common()
163{
164	# Setup the test bed, but keep links down
165	setup $1 $2 $3
166
167	# Bring the link up, wait for the NA,
168	# and add a delay to ensure neighbour processing is done.
169	link_up
170	start_tcpdump
171
172	# Verify the neighbour table
173	verify_ndisc $1 $2 $3
174
175}
176
177test_unsolicited_na_combination() {
178	test_unsolicited_na_common $1 $2 $3
179	test_msg=("test_unsolicited_na: "
180		"drop_unsolicited_na=$1 "
181		"accept_untracked_na=$2 "
182		"forwarding=$3")
183	log_test $? 0 "${test_msg[*]}"
184	cleanup
185}
186
187test_unsolicited_na_combinations() {
188	# Args: drop_unsolicited_na accept_untracked_na forwarding
189
190	# Expect entry
191	test_unsolicited_na_combination 0 1 1
192
193	# Expect no entry
194	test_unsolicited_na_combination 0 0 0
195	test_unsolicited_na_combination 0 0 1
196	test_unsolicited_na_combination 0 1 0
197	test_unsolicited_na_combination 1 0 0
198	test_unsolicited_na_combination 1 0 1
199	test_unsolicited_na_combination 1 1 0
200	test_unsolicited_na_combination 1 1 1
201}
202
203###############################################################################
204# usage
205
206usage()
207{
208	cat <<EOF
209usage: ${0##*/} OPTS
210        -p          Pause on fail
211        -P          Pause after each test before cleanup
212EOF
213}
214
215###############################################################################
216# main
217
218while getopts :pPh o
219do
220	case $o in
221		p) PAUSE_ON_FAIL=yes;;
222		P) PAUSE=yes;;
223		h) usage; exit 0;;
224		*) usage; exit 1;;
225	esac
226done
227
228# make sure we don't pause twice
229[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no
230
231if [ "$(id -u)" -ne 0 ];then
232	echo "SKIP: Need root privileges"
233	exit $ksft_skip;
234fi
235
236if [ ! -x "$(command -v ip)" ]; then
237	echo "SKIP: Could not run test without ip tool"
238	exit $ksft_skip
239fi
240
241if [ ! -x "$(command -v tcpdump)" ]; then
242	echo "SKIP: Could not run test without tcpdump tool"
243	exit $ksft_skip
244fi
245
246# start clean
247cleanup &> /dev/null
248
249test_unsolicited_na_combinations
250
251printf "\nTests passed: %3d\n" ${nsuccess}
252printf "Tests failed: %3d\n"   ${nfail}
253
254exit $ret
255