1#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3#
4# Test topology:
5#    - - - - - - - - - - - - - - - - - - -
6#    | veth1         veth2         veth3 |  ns0
7#     - -| - - - - - - | - - - - - - | - -
8#    ---------     ---------     ---------
9#    | veth0 |     | veth0 |     | veth0 |
10#    ---------     ---------     ---------
11#       ns1           ns2           ns3
12#
13# Test modules:
14# XDP modes: generic, native, native + egress_prog
15#
16# Test cases:
17#   ARP: Testing BPF_F_BROADCAST, the ingress interface also should receive
18#   the redirects.
19#      ns1 -> gw: ns1, ns2, ns3, should receive the arp request
20#   IPv4: Testing BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS, the ingress
21#   interface should not receive the redirects.
22#      ns1 -> gw: ns1 should not receive, ns2, ns3 should receive redirects.
23#   IPv6: Testing none flag, all the pkts should be redirected back
24#      ping test: ns1 -> ns2 (block), echo requests will be redirect back
25#   egress_prog:
26#      all src mac should be egress interface's mac
27
28# netns numbers
29NUM=3
30IFACES=""
31DRV_MODE="xdpgeneric xdpdrv xdpegress"
32PASS=0
33FAIL=0
34LOG_DIR=$(mktemp -d)
35
36test_pass()
37{
38	echo "Pass: $@"
39	PASS=$((PASS + 1))
40}
41
42test_fail()
43{
44	echo "fail: $@"
45	FAIL=$((FAIL + 1))
46}
47
48clean_up()
49{
50	for i in $(seq $NUM); do
51		ip link del veth$i 2> /dev/null
52		ip netns del ns$i 2> /dev/null
53	done
54	ip netns del ns0 2> /dev/null
55}
56
57# Kselftest framework requirement - SKIP code is 4.
58check_env()
59{
60	ip link set dev lo xdpgeneric off &>/dev/null
61	if [ $? -ne 0 ];then
62		echo "selftests: [SKIP] Could not run test without the ip xdpgeneric support"
63		exit 4
64	fi
65
66	which tcpdump &>/dev/null
67	if [ $? -ne 0 ];then
68		echo "selftests: [SKIP] Could not run test without tcpdump"
69		exit 4
70	fi
71}
72
73setup_ns()
74{
75	local mode=$1
76	IFACES=""
77
78	if [ "$mode" = "xdpegress" ]; then
79		mode="xdpdrv"
80	fi
81
82	ip netns add ns0
83	for i in $(seq $NUM); do
84	        ip netns add ns$i
85		ip -n ns$i link add veth0 index 2 type veth \
86			peer name veth$i netns ns0 index $((1 + $i))
87		ip -n ns0 link set veth$i up
88		ip -n ns$i link set veth0 up
89
90		ip -n ns$i addr add 192.0.2.$i/24 dev veth0
91		ip -n ns$i addr add 2001:db8::$i/64 dev veth0
92		# Add a neigh entry for IPv4 ping test
93		ip -n ns$i neigh add 192.0.2.253 lladdr 00:00:00:00:00:01 dev veth0
94		ip -n ns$i link set veth0 $mode obj \
95			xdp_dummy.o sec xdp &> /dev/null || \
96			{ test_fail "Unable to load dummy xdp" && exit 1; }
97		IFACES="$IFACES veth$i"
98		veth_mac[$i]=$(ip -n ns0 link show veth$i | awk '/link\/ether/ {print $2}')
99	done
100}
101
102do_egress_tests()
103{
104	local mode=$1
105
106	# mac test
107	ip netns exec ns2 tcpdump -e -i veth0 -nn -l -e &> ${LOG_DIR}/mac_ns1-2_${mode}.log &
108	ip netns exec ns3 tcpdump -e -i veth0 -nn -l -e &> ${LOG_DIR}/mac_ns1-3_${mode}.log &
109	sleep 0.5
110	ip netns exec ns1 ping 192.0.2.254 -i 0.1 -c 4 &> /dev/null
111	sleep 0.5
112	pkill tcpdump
113
114	# mac check
115	grep -q "${veth_mac[2]} > ff:ff:ff:ff:ff:ff" ${LOG_DIR}/mac_ns1-2_${mode}.log && \
116	       test_pass "$mode mac ns1-2" || test_fail "$mode mac ns1-2"
117	grep -q "${veth_mac[3]} > ff:ff:ff:ff:ff:ff" ${LOG_DIR}/mac_ns1-3_${mode}.log && \
118		test_pass "$mode mac ns1-3" || test_fail "$mode mac ns1-3"
119}
120
121do_ping_tests()
122{
123	local mode=$1
124
125	# ping6 test: echo request should be redirect back to itself, not others
126	ip netns exec ns1 ip neigh add 2001:db8::2 dev veth0 lladdr 00:00:00:00:00:02
127
128	ip netns exec ns1 tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-1_${mode}.log &
129	ip netns exec ns2 tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-2_${mode}.log &
130	ip netns exec ns3 tcpdump -i veth0 -nn -l -e &> ${LOG_DIR}/ns1-3_${mode}.log &
131	sleep 0.5
132	# ARP test
133	ip netns exec ns1 arping -q -c 2 -I veth0 192.0.2.254
134	# IPv4 test
135	ip netns exec ns1 ping 192.0.2.253 -i 0.1 -c 4 &> /dev/null
136	# IPv6 test
137	ip netns exec ns1 ping6 2001:db8::2 -i 0.1 -c 2 &> /dev/null
138	sleep 0.5
139	pkill tcpdump
140
141	# All netns should receive the redirect arp requests
142	[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
143		test_pass "$mode arp(F_BROADCAST) ns1-1" || \
144		test_fail "$mode arp(F_BROADCAST) ns1-1"
145	[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-2_${mode}.log) -eq 2 ] && \
146		test_pass "$mode arp(F_BROADCAST) ns1-2" || \
147		test_fail "$mode arp(F_BROADCAST) ns1-2"
148	[ $(grep -cF "who-has 192.0.2.254" ${LOG_DIR}/ns1-3_${mode}.log) -eq 2 ] && \
149		test_pass "$mode arp(F_BROADCAST) ns1-3" || \
150		test_fail "$mode arp(F_BROADCAST) ns1-3"
151
152	# ns1 should not receive the redirect echo request, others should
153	[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
154		test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-1" || \
155		test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-1"
156	[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-2_${mode}.log) -eq 4 ] && \
157		test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-2" || \
158		test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-2"
159	[ $(grep -c "ICMP echo request" ${LOG_DIR}/ns1-3_${mode}.log) -eq 4 ] && \
160		test_pass "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-3" || \
161		test_fail "$mode IPv4 (F_BROADCAST|F_EXCLUDE_INGRESS) ns1-3"
162
163	# ns1 should receive the echo request, ns2 should not
164	[ $(grep -c "ICMP6, echo request" ${LOG_DIR}/ns1-1_${mode}.log) -eq 4 ] && \
165		test_pass "$mode IPv6 (no flags) ns1-1" || \
166		test_fail "$mode IPv6 (no flags) ns1-1"
167	[ $(grep -c "ICMP6, echo request" ${LOG_DIR}/ns1-2_${mode}.log) -eq 0 ] && \
168		test_pass "$mode IPv6 (no flags) ns1-2" || \
169		test_fail "$mode IPv6 (no flags) ns1-2"
170}
171
172do_tests()
173{
174	local mode=$1
175	local drv_p
176
177	case ${mode} in
178		xdpdrv)  drv_p="-N";;
179		xdpegress) drv_p="-X";;
180		xdpgeneric) drv_p="-S";;
181	esac
182
183	ip netns exec ns0 ./xdp_redirect_multi $drv_p $IFACES &> ${LOG_DIR}/xdp_redirect_${mode}.log &
184	xdp_pid=$!
185	sleep 1
186	if ! ps -p $xdp_pid > /dev/null; then
187		test_fail "$mode xdp_redirect_multi start failed"
188		return 1
189	fi
190
191	if [ "$mode" = "xdpegress" ]; then
192		do_egress_tests $mode
193	else
194		do_ping_tests $mode
195	fi
196
197	kill $xdp_pid
198}
199
200trap clean_up EXIT
201
202check_env
203
204for mode in ${DRV_MODE}; do
205	setup_ns $mode
206	do_tests $mode
207	clean_up
208done
209rm -rf ${LOG_DIR}
210
211echo "Summary: PASS $PASS, FAIL $FAIL"
212[ $FAIL -eq 0 ] && exit 0 || exit 1
213