1#!/bin/bash
2#
3# This tests nf_queue:
4# 1. can process packets from all hooks
5# 2. support running nfqueue from more than one base chain
6#
7# Kselftest framework requirement - SKIP code is 4.
8ksft_skip=4
9ret=0
10
11sfx=$(mktemp -u "XXXXXXXX")
12ns1="ns1-$sfx"
13ns2="ns2-$sfx"
14nsrouter="nsrouter-$sfx"
15timeout=4
16
17cleanup()
18{
19	ip netns pids ${ns1} | xargs kill 2>/dev/null
20	ip netns pids ${ns2} | xargs kill 2>/dev/null
21	ip netns pids ${nsrouter} | xargs kill 2>/dev/null
22
23	ip netns del ${ns1}
24	ip netns del ${ns2}
25	ip netns del ${nsrouter}
26	rm -f "$TMPFILE0"
27	rm -f "$TMPFILE1"
28	rm -f "$TMPFILE2" "$TMPFILE3"
29}
30
31nft --version > /dev/null 2>&1
32if [ $? -ne 0 ];then
33	echo "SKIP: Could not run test without nft tool"
34	exit $ksft_skip
35fi
36
37ip -Version > /dev/null 2>&1
38if [ $? -ne 0 ];then
39	echo "SKIP: Could not run test without ip tool"
40	exit $ksft_skip
41fi
42
43ip netns add ${nsrouter}
44if [ $? -ne 0 ];then
45	echo "SKIP: Could not create net namespace"
46	exit $ksft_skip
47fi
48
49TMPFILE0=$(mktemp)
50TMPFILE1=$(mktemp)
51TMPFILE2=$(mktemp)
52TMPFILE3=$(mktemp)
53trap cleanup EXIT
54
55ip netns add ${ns1}
56ip netns add ${ns2}
57
58ip link add veth0 netns ${nsrouter} type veth peer name eth0 netns ${ns1} > /dev/null 2>&1
59if [ $? -ne 0 ];then
60    echo "SKIP: No virtual ethernet pair device support in kernel"
61    exit $ksft_skip
62fi
63ip link add veth1 netns ${nsrouter} type veth peer name eth0 netns ${ns2}
64
65ip -net ${nsrouter} link set lo up
66ip -net ${nsrouter} link set veth0 up
67ip -net ${nsrouter} addr add 10.0.1.1/24 dev veth0
68ip -net ${nsrouter} addr add dead:1::1/64 dev veth0
69
70ip -net ${nsrouter} link set veth1 up
71ip -net ${nsrouter} addr add 10.0.2.1/24 dev veth1
72ip -net ${nsrouter} addr add dead:2::1/64 dev veth1
73
74ip -net ${ns1} link set lo up
75ip -net ${ns1} link set eth0 up
76
77ip -net ${ns2} link set lo up
78ip -net ${ns2} link set eth0 up
79
80ip -net ${ns1} addr add 10.0.1.99/24 dev eth0
81ip -net ${ns1} addr add dead:1::99/64 dev eth0
82ip -net ${ns1} route add default via 10.0.1.1
83ip -net ${ns1} route add default via dead:1::1
84
85ip -net ${ns2} addr add 10.0.2.99/24 dev eth0
86ip -net ${ns2} addr add dead:2::99/64 dev eth0
87ip -net ${ns2} route add default via 10.0.2.1
88ip -net ${ns2} route add default via dead:2::1
89
90load_ruleset() {
91	local name=$1
92	local prio=$2
93
94ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
95table inet $name {
96	chain nfq {
97		ip protocol icmp queue bypass
98		icmpv6 type { "echo-request", "echo-reply" } queue num 1 bypass
99	}
100	chain pre {
101		type filter hook prerouting priority $prio; policy accept;
102		jump nfq
103	}
104	chain input {
105		type filter hook input priority $prio; policy accept;
106		jump nfq
107	}
108	chain forward {
109		type filter hook forward priority $prio; policy accept;
110		tcp dport 12345 queue num 2
111		jump nfq
112	}
113	chain output {
114		type filter hook output priority $prio; policy accept;
115		tcp dport 12345 queue num 3
116		jump nfq
117	}
118	chain post {
119		type filter hook postrouting priority $prio; policy accept;
120		jump nfq
121	}
122}
123EOF
124}
125
126load_counter_ruleset() {
127	local prio=$1
128
129ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
130table inet countrules {
131	chain pre {
132		type filter hook prerouting priority $prio; policy accept;
133		counter
134	}
135	chain input {
136		type filter hook input priority $prio; policy accept;
137		counter
138	}
139	chain forward {
140		type filter hook forward priority $prio; policy accept;
141		counter
142	}
143	chain output {
144		type filter hook output priority $prio; policy accept;
145		counter
146	}
147	chain post {
148		type filter hook postrouting priority $prio; policy accept;
149		counter
150	}
151}
152EOF
153}
154
155test_ping() {
156  ip netns exec ${ns1} ping -c 1 -q 10.0.2.99 > /dev/null
157  if [ $? -ne 0 ];then
158	return 1
159  fi
160
161  ip netns exec ${ns1} ping -c 1 -q dead:2::99 > /dev/null
162  if [ $? -ne 0 ];then
163	return 1
164  fi
165
166  return 0
167}
168
169test_ping_router() {
170  ip netns exec ${ns1} ping -c 1 -q 10.0.2.1 > /dev/null
171  if [ $? -ne 0 ];then
172	return 1
173  fi
174
175  ip netns exec ${ns1} ping -c 1 -q dead:2::1 > /dev/null
176  if [ $? -ne 0 ];then
177	return 1
178  fi
179
180  return 0
181}
182
183test_queue_blackhole() {
184	local proto=$1
185
186ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
187table $proto blackh {
188	chain forward {
189	type filter hook forward priority 0; policy accept;
190		queue num 600
191	}
192}
193EOF
194	if [ $proto = "ip" ] ;then
195		ip netns exec ${ns1} ping -W 2 -c 1 -q 10.0.2.99 > /dev/null
196		lret=$?
197	elif [ $proto = "ip6" ]; then
198		ip netns exec ${ns1} ping -W 2 -c 1 -q dead:2::99 > /dev/null
199		lret=$?
200	else
201		lret=111
202	fi
203
204	# queue without bypass keyword should drop traffic if no listener exists.
205	if [ $lret -eq 0 ];then
206		echo "FAIL: $proto expected failure, got $lret" 1>&2
207		exit 1
208	fi
209
210	ip netns exec ${nsrouter} nft delete table $proto blackh
211	if [ $? -ne 0 ] ;then
212	        echo "FAIL: $proto: Could not delete blackh table"
213	        exit 1
214	fi
215
216        echo "PASS: $proto: statement with no listener results in packet drop"
217}
218
219test_queue()
220{
221	local expected=$1
222	local last=""
223
224	# spawn nf-queue listeners
225	ip netns exec ${nsrouter} ./nf-queue -c -q 0 -t $timeout > "$TMPFILE0" &
226	ip netns exec ${nsrouter} ./nf-queue -c -q 1 -t $timeout > "$TMPFILE1" &
227	sleep 1
228	test_ping
229	ret=$?
230	if [ $ret -ne 0 ];then
231		echo "FAIL: netns routing/connectivity with active listener on queue $queue: $ret" 1>&2
232		exit $ret
233	fi
234
235	test_ping_router
236	ret=$?
237	if [ $ret -ne 0 ];then
238		echo "FAIL: netns router unreachable listener on queue $queue: $ret" 1>&2
239		exit $ret
240	fi
241
242	wait
243	ret=$?
244
245	for file in $TMPFILE0 $TMPFILE1; do
246		last=$(tail -n1 "$file")
247		if [ x"$last" != x"$expected packets total" ]; then
248			echo "FAIL: Expected $expected packets total, but got $last" 1>&2
249			cat "$file" 1>&2
250
251			ip netns exec ${nsrouter} nft list ruleset
252			exit 1
253		fi
254	done
255
256	echo "PASS: Expected and received $last"
257}
258
259test_tcp_forward()
260{
261	ip netns exec ${nsrouter} ./nf-queue -q 2 -t $timeout &
262	local nfqpid=$!
263
264	tmpfile=$(mktemp) || exit 1
265	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
266	ip netns exec ${ns2} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
267	local rpid=$!
268
269	sleep 1
270	ip netns exec ${ns1} nc -w 5 10.0.2.99 12345 <"$tmpfile" >/dev/null &
271
272	rm -f "$tmpfile"
273
274	wait $rpid
275	wait $lpid
276	[ $? -eq 0 ] && echo "PASS: tcp and nfqueue in forward chain"
277}
278
279test_tcp_localhost()
280{
281	tmpfile=$(mktemp) || exit 1
282
283	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
284	ip netns exec ${nsrouter} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
285	local rpid=$!
286
287	ip netns exec ${nsrouter} ./nf-queue -q 3 -t $timeout &
288	local nfqpid=$!
289
290	sleep 1
291	ip netns exec ${nsrouter} nc -w 5 127.0.0.1 12345 <"$tmpfile" > /dev/null
292	rm -f "$tmpfile"
293
294	wait $rpid
295	[ $? -eq 0 ] && echo "PASS: tcp via loopback"
296	wait 2>/dev/null
297}
298
299test_tcp_localhost_requeue()
300{
301ip netns exec ${nsrouter} nft -f /dev/stdin <<EOF
302flush ruleset
303table inet filter {
304	chain output {
305		type filter hook output priority 0; policy accept;
306		tcp dport 12345 limit rate 1/second burst 1 packets counter queue num 0
307	}
308	chain post {
309		type filter hook postrouting priority 0; policy accept;
310		tcp dport 12345 limit rate 1/second burst 1 packets counter queue num 0
311	}
312}
313EOF
314	tmpfile=$(mktemp) || exit 1
315	dd conv=sparse status=none if=/dev/zero bs=1M count=200 of=$tmpfile
316	ip netns exec ${nsrouter} nc -w 5 -l -p 12345 <"$tmpfile" >/dev/null &
317	local rpid=$!
318
319	ip netns exec ${nsrouter} ./nf-queue -c -q 1 -t $timeout > "$TMPFILE2" &
320
321	# nfqueue 1 will be called via output hook.  But this time,
322        # re-queue the packet to nfqueue program on queue 2.
323	ip netns exec ${nsrouter} ./nf-queue -G -d 150 -c -q 0 -Q 1 -t $timeout > "$TMPFILE3" &
324
325	sleep 1
326	ip netns exec ${nsrouter} nc -w 5 127.0.0.1 12345 <"$tmpfile" > /dev/null
327	rm -f "$tmpfile"
328
329	wait
330
331	if ! diff -u "$TMPFILE2" "$TMPFILE3" ; then
332		echo "FAIL: lost packets during requeue?!" 1>&2
333		return
334	fi
335
336	echo "PASS: tcp via loopback and re-queueing"
337}
338
339test_icmp_vrf() {
340	ip -net $ns1 link add tvrf type vrf table 9876
341	if [ $? -ne 0 ];then
342		echo "SKIP: Could not add vrf device"
343		return
344	fi
345
346	ip -net $ns1 li set eth0 master tvrf
347	ip -net $ns1 li set tvrf up
348
349	ip -net $ns1 route add 10.0.2.0/24 via 10.0.1.1 dev eth0 table 9876
350ip netns exec ${ns1} nft -f /dev/stdin <<EOF
351flush ruleset
352table inet filter {
353	chain output {
354		type filter hook output priority 0; policy accept;
355		meta oifname "tvrf" icmp type echo-request counter queue num 1
356		meta oifname "eth0" icmp type echo-request counter queue num 1
357	}
358	chain post {
359		type filter hook postrouting priority 0; policy accept;
360		meta oifname "tvrf" icmp type echo-request counter queue num 1
361		meta oifname "eth0" icmp type echo-request counter queue num 1
362	}
363}
364EOF
365	ip netns exec ${ns1} ./nf-queue -q 1 -t $timeout &
366	local nfqpid=$!
367
368	sleep 1
369	ip netns exec ${ns1} ip vrf exec tvrf ping -c 1 10.0.2.99 > /dev/null
370
371	for n in output post; do
372		for d in tvrf eth0; do
373			ip netns exec ${ns1} nft list chain inet filter $n | grep -q "oifname \"$d\" icmp type echo-request counter packets 1"
374			if [ $? -ne 0 ] ; then
375				echo "FAIL: chain $n: icmp packet counter mismatch for device $d" 1>&2
376				ip netns exec ${ns1} nft list ruleset
377				ret=1
378				return
379			fi
380		done
381	done
382
383	wait $nfqpid
384	[ $? -eq 0 ] && echo "PASS: icmp+nfqueue via vrf"
385	wait 2>/dev/null
386}
387
388ip netns exec ${nsrouter} sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
389ip netns exec ${nsrouter} sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
390ip netns exec ${nsrouter} sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
391
392load_ruleset "filter" 0
393
394sleep 3
395
396test_ping
397ret=$?
398if [ $ret -eq 0 ];then
399	# queue bypass works (rules were skipped, no listener)
400	echo "PASS: ${ns1} can reach ${ns2}"
401else
402	echo "FAIL: ${ns1} cannot reach ${ns2}: $ret" 1>&2
403	exit $ret
404fi
405
406test_queue_blackhole ip
407test_queue_blackhole ip6
408
409# dummy ruleset to add base chains between the
410# queueing rules.  We don't want the second reinject
411# to re-execute the old hooks.
412load_counter_ruleset 10
413
414# we are hooking all: prerouting/input/forward/output/postrouting.
415# we ping ${ns2} from ${ns1} via ${nsrouter} using ipv4 and ipv6, so:
416# 1x icmp prerouting,forward,postrouting -> 3 queue events (6 incl. reply).
417# 1x icmp prerouting,input,output postrouting -> 4 queue events incl. reply.
418# so we expect that userspace program receives 10 packets.
419test_queue 10
420
421# same.  We queue to a second program as well.
422load_ruleset "filter2" 20
423test_queue 20
424
425test_tcp_forward
426test_tcp_localhost
427test_tcp_localhost_requeue
428test_icmp_vrf
429
430exit $ret
431