1#!/bin/bash
2#
3# This test is for basic NAT functionality: snat, dnat, redirect, masquerade.
4#
5
6# Kselftest framework requirement - SKIP code is 4.
7ksft_skip=4
8ret=0
9test_inet_nat=true
10
11sfx=$(mktemp -u "XXXXXXXX")
12ns0="ns0-$sfx"
13ns1="ns1-$sfx"
14ns2="ns2-$sfx"
15
16cleanup()
17{
18	for i in 0 1 2; do ip netns del ns$i-"$sfx";done
19}
20
21nft --version > /dev/null 2>&1
22if [ $? -ne 0 ];then
23	echo "SKIP: Could not run test without nft tool"
24	exit $ksft_skip
25fi
26
27ip -Version > /dev/null 2>&1
28if [ $? -ne 0 ];then
29	echo "SKIP: Could not run test without ip tool"
30	exit $ksft_skip
31fi
32
33ip netns add "$ns0"
34if [ $? -ne 0 ];then
35	echo "SKIP: Could not create net namespace $ns0"
36	exit $ksft_skip
37fi
38
39trap cleanup EXIT
40
41ip netns add "$ns1"
42if [ $? -ne 0 ];then
43	echo "SKIP: Could not create net namespace $ns1"
44	exit $ksft_skip
45fi
46
47ip netns add "$ns2"
48if [ $? -ne 0 ];then
49	echo "SKIP: Could not create net namespace $ns2"
50	exit $ksft_skip
51fi
52
53ip link add veth0 netns "$ns0" type veth peer name eth0 netns "$ns1" > /dev/null 2>&1
54if [ $? -ne 0 ];then
55    echo "SKIP: No virtual ethernet pair device support in kernel"
56    exit $ksft_skip
57fi
58ip link add veth1 netns "$ns0" type veth peer name eth0 netns "$ns2"
59
60ip -net "$ns0" link set lo up
61ip -net "$ns0" link set veth0 up
62ip -net "$ns0" addr add 10.0.1.1/24 dev veth0
63ip -net "$ns0" addr add dead:1::1/64 dev veth0
64
65ip -net "$ns0" link set veth1 up
66ip -net "$ns0" addr add 10.0.2.1/24 dev veth1
67ip -net "$ns0" addr add dead:2::1/64 dev veth1
68
69for i in 1 2; do
70  ip -net ns$i-$sfx link set lo up
71  ip -net ns$i-$sfx link set eth0 up
72  ip -net ns$i-$sfx addr add 10.0.$i.99/24 dev eth0
73  ip -net ns$i-$sfx route add default via 10.0.$i.1
74  ip -net ns$i-$sfx addr add dead:$i::99/64 dev eth0
75  ip -net ns$i-$sfx route add default via dead:$i::1
76done
77
78bad_counter()
79{
80	local ns=$1
81	local counter=$2
82	local expect=$3
83	local tag=$4
84
85	echo "ERROR: $counter counter in $ns has unexpected value (expected $expect) at $tag" 1>&2
86	ip netns exec $ns nft list counter inet filter $counter 1>&2
87}
88
89check_counters()
90{
91	ns=$1
92	local lret=0
93
94	cnt=$(ip netns exec $ns nft list counter inet filter ns0in | grep -q "packets 1 bytes 84")
95	if [ $? -ne 0 ]; then
96		bad_counter $ns ns0in "packets 1 bytes 84" "check_counters 1"
97		lret=1
98	fi
99	cnt=$(ip netns exec $ns nft list counter inet filter ns0out | grep -q "packets 1 bytes 84")
100	if [ $? -ne 0 ]; then
101		bad_counter $ns ns0out "packets 1 bytes 84" "check_counters 2"
102		lret=1
103	fi
104
105	expect="packets 1 bytes 104"
106	cnt=$(ip netns exec $ns nft list counter inet filter ns0in6 | grep -q "$expect")
107	if [ $? -ne 0 ]; then
108		bad_counter $ns ns0in6 "$expect" "check_counters 3"
109		lret=1
110	fi
111	cnt=$(ip netns exec $ns nft list counter inet filter ns0out6 | grep -q "$expect")
112	if [ $? -ne 0 ]; then
113		bad_counter $ns ns0out6 "$expect" "check_counters 4"
114		lret=1
115	fi
116
117	return $lret
118}
119
120check_ns0_counters()
121{
122	local ns=$1
123	local lret=0
124
125	cnt=$(ip netns exec "$ns0" nft list counter inet filter ns0in | grep -q "packets 0 bytes 0")
126	if [ $? -ne 0 ]; then
127		bad_counter "$ns0" ns0in "packets 0 bytes 0" "check_ns0_counters 1"
128		lret=1
129	fi
130
131	cnt=$(ip netns exec "$ns0" nft list counter inet filter ns0in6 | grep -q "packets 0 bytes 0")
132	if [ $? -ne 0 ]; then
133		bad_counter "$ns0" ns0in6 "packets 0 bytes 0"
134		lret=1
135	fi
136
137	cnt=$(ip netns exec "$ns0" nft list counter inet filter ns0out | grep -q "packets 0 bytes 0")
138	if [ $? -ne 0 ]; then
139		bad_counter "$ns0" ns0out "packets 0 bytes 0" "check_ns0_counters 2"
140		lret=1
141	fi
142	cnt=$(ip netns exec "$ns0" nft list counter inet filter ns0out6 | grep -q "packets 0 bytes 0")
143	if [ $? -ne 0 ]; then
144		bad_counter "$ns0" ns0out6 "packets 0 bytes 0" "check_ns0_counters3 "
145		lret=1
146	fi
147
148	for dir in "in" "out" ; do
149		expect="packets 1 bytes 84"
150		cnt=$(ip netns exec "$ns0" nft list counter inet filter ${ns}${dir} | grep -q "$expect")
151		if [ $? -ne 0 ]; then
152			bad_counter "$ns0" $ns$dir "$expect" "check_ns0_counters 4"
153			lret=1
154		fi
155
156		expect="packets 1 bytes 104"
157		cnt=$(ip netns exec "$ns0" nft list counter inet filter ${ns}${dir}6 | grep -q "$expect")
158		if [ $? -ne 0 ]; then
159			bad_counter "$ns0" $ns$dir6 "$expect" "check_ns0_counters 5"
160			lret=1
161		fi
162	done
163
164	return $lret
165}
166
167reset_counters()
168{
169	for i in 0 1 2;do
170		ip netns exec ns$i-$sfx nft reset counters inet > /dev/null
171	done
172}
173
174test_local_dnat6()
175{
176	local family=$1
177	local lret=0
178	local IPF=""
179
180	if [ $family = "inet" ];then
181		IPF="ip6"
182	fi
183
184ip netns exec "$ns0" nft -f /dev/stdin <<EOF
185table $family nat {
186	chain output {
187		type nat hook output priority 0; policy accept;
188		ip6 daddr dead:1::99 dnat $IPF to dead:2::99
189	}
190}
191EOF
192	if [ $? -ne 0 ]; then
193		echo "SKIP: Could not add add $family dnat hook"
194		return $ksft_skip
195	fi
196
197	# ping netns1, expect rewrite to netns2
198	ip netns exec "$ns0" ping -q -c 1 dead:1::99 > /dev/null
199	if [ $? -ne 0 ]; then
200		lret=1
201		echo "ERROR: ping6 failed"
202		return $lret
203	fi
204
205	expect="packets 0 bytes 0"
206	for dir in "in6" "out6" ; do
207		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
208		if [ $? -ne 0 ]; then
209			bad_counter "$ns0" ns1$dir "$expect" "test_local_dnat6 1"
210			lret=1
211		fi
212	done
213
214	expect="packets 1 bytes 104"
215	for dir in "in6" "out6" ; do
216		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns2${dir} | grep -q "$expect")
217		if [ $? -ne 0 ]; then
218			bad_counter "$ns0" ns2$dir "$expect" "test_local_dnat6 2"
219			lret=1
220		fi
221	done
222
223	# expect 0 count in ns1
224	expect="packets 0 bytes 0"
225	for dir in "in6" "out6" ; do
226		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0${dir} | grep -q "$expect")
227		if [ $? -ne 0 ]; then
228			bad_counter "$ns1" ns0$dir "$expect" "test_local_dnat6 3"
229			lret=1
230		fi
231	done
232
233	# expect 1 packet in ns2
234	expect="packets 1 bytes 104"
235	for dir in "in6" "out6" ; do
236		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns0${dir} | grep -q "$expect")
237		if [ $? -ne 0 ]; then
238			bad_counter "$ns2" ns0$dir "$expect" "test_local_dnat6 4"
239			lret=1
240		fi
241	done
242
243	test $lret -eq 0 && echo "PASS: ipv6 ping to $ns1 was $family NATted to $ns2"
244	ip netns exec "$ns0" nft flush chain ip6 nat output
245
246	return $lret
247}
248
249test_local_dnat()
250{
251	local family=$1
252	local lret=0
253	local IPF=""
254
255	if [ $family = "inet" ];then
256		IPF="ip"
257	fi
258
259ip netns exec "$ns0" nft -f /dev/stdin <<EOF 2>/dev/null
260table $family nat {
261	chain output {
262		type nat hook output priority 0; policy accept;
263		ip daddr 10.0.1.99 dnat $IPF to 10.0.2.99
264	}
265}
266EOF
267	if [ $? -ne 0 ]; then
268		if [ $family = "inet" ];then
269			echo "SKIP: inet nat tests"
270			test_inet_nat=false
271			return $ksft_skip
272		fi
273		echo "SKIP: Could not add add $family dnat hook"
274		return $ksft_skip
275	fi
276
277	# ping netns1, expect rewrite to netns2
278	ip netns exec "$ns0" ping -q -c 1 10.0.1.99 > /dev/null
279	if [ $? -ne 0 ]; then
280		lret=1
281		echo "ERROR: ping failed"
282		return $lret
283	fi
284
285	expect="packets 0 bytes 0"
286	for dir in "in" "out" ; do
287		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
288		if [ $? -ne 0 ]; then
289			bad_counter "$ns0" ns1$dir "$expect" "test_local_dnat 1"
290			lret=1
291		fi
292	done
293
294	expect="packets 1 bytes 84"
295	for dir in "in" "out" ; do
296		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns2${dir} | grep -q "$expect")
297		if [ $? -ne 0 ]; then
298			bad_counter "$ns0" ns2$dir "$expect" "test_local_dnat 2"
299			lret=1
300		fi
301	done
302
303	# expect 0 count in ns1
304	expect="packets 0 bytes 0"
305	for dir in "in" "out" ; do
306		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0${dir} | grep -q "$expect")
307		if [ $? -ne 0 ]; then
308			bad_counter "$ns1" ns0$dir "$expect" "test_local_dnat 3"
309			lret=1
310		fi
311	done
312
313	# expect 1 packet in ns2
314	expect="packets 1 bytes 84"
315	for dir in "in" "out" ; do
316		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns0${dir} | grep -q "$expect")
317		if [ $? -ne 0 ]; then
318			bad_counter "$ns2" ns0$dir "$expect" "test_local_dnat 4"
319			lret=1
320		fi
321	done
322
323	test $lret -eq 0 && echo "PASS: ping to $ns1 was $family NATted to $ns2"
324
325	ip netns exec "$ns0" nft flush chain $family nat output
326
327	reset_counters
328	ip netns exec "$ns0" ping -q -c 1 10.0.1.99 > /dev/null
329	if [ $? -ne 0 ]; then
330		lret=1
331		echo "ERROR: ping failed"
332		return $lret
333	fi
334
335	expect="packets 1 bytes 84"
336	for dir in "in" "out" ; do
337		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
338		if [ $? -ne 0 ]; then
339			bad_counter "$ns1" ns1$dir "$expect" "test_local_dnat 5"
340			lret=1
341		fi
342	done
343	expect="packets 0 bytes 0"
344	for dir in "in" "out" ; do
345		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns2${dir} | grep -q "$expect")
346		if [ $? -ne 0 ]; then
347			bad_counter "$ns0" ns2$dir "$expect" "test_local_dnat 6"
348			lret=1
349		fi
350	done
351
352	# expect 1 count in ns1
353	expect="packets 1 bytes 84"
354	for dir in "in" "out" ; do
355		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0${dir} | grep -q "$expect")
356		if [ $? -ne 0 ]; then
357			bad_counter "$ns0" ns0$dir "$expect" "test_local_dnat 7"
358			lret=1
359		fi
360	done
361
362	# expect 0 packet in ns2
363	expect="packets 0 bytes 0"
364	for dir in "in" "out" ; do
365		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns0${dir} | grep -q "$expect")
366		if [ $? -ne 0 ]; then
367			bad_counter "$ns2" ns0$dir "$expect" "test_local_dnat 8"
368			lret=1
369		fi
370	done
371
372	test $lret -eq 0 && echo "PASS: ping to $ns1 OK after $family nat output chain flush"
373
374	return $lret
375}
376
377
378test_masquerade6()
379{
380	local family=$1
381	local natflags=$2
382	local lret=0
383
384	ip netns exec "$ns0" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
385
386	ip netns exec "$ns2" ping -q -c 1 dead:1::99 > /dev/null # ping ns2->ns1
387	if [ $? -ne 0 ] ; then
388		echo "ERROR: cannot ping $ns1 from $ns2 via ipv6"
389		return 1
390		lret=1
391	fi
392
393	expect="packets 1 bytes 104"
394	for dir in "in6" "out6" ; do
395		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
396		if [ $? -ne 0 ]; then
397			bad_counter "$ns1" ns2$dir "$expect" "test_masquerade6 1"
398			lret=1
399		fi
400
401		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
402		if [ $? -ne 0 ]; then
403			bad_counter "$ns2" ns1$dir "$expect" "test_masquerade6 2"
404			lret=1
405		fi
406	done
407
408	reset_counters
409
410# add masquerading rule
411ip netns exec "$ns0" nft -f /dev/stdin <<EOF
412table $family nat {
413	chain postrouting {
414		type nat hook postrouting priority 0; policy accept;
415		meta oif veth0 masquerade $natflags
416	}
417}
418EOF
419	if [ $? -ne 0 ]; then
420		echo "SKIP: Could not add add $family masquerade hook"
421		return $ksft_skip
422	fi
423
424	ip netns exec "$ns2" ping -q -c 1 dead:1::99 > /dev/null # ping ns2->ns1
425	if [ $? -ne 0 ] ; then
426		echo "ERROR: cannot ping $ns1 from $ns2 with active $family masquerade $natflags"
427		lret=1
428	fi
429
430	# ns1 should have seen packets from ns0, due to masquerade
431	expect="packets 1 bytes 104"
432	for dir in "in6" "out6" ; do
433		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0${dir} | grep -q "$expect")
434		if [ $? -ne 0 ]; then
435			bad_counter "$ns1" ns0$dir "$expect" "test_masquerade6 3"
436			lret=1
437		fi
438
439		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
440		if [ $? -ne 0 ]; then
441			bad_counter "$ns2" ns1$dir "$expect" "test_masquerade6 4"
442			lret=1
443		fi
444	done
445
446	# ns1 should not have seen packets from ns2, due to masquerade
447	expect="packets 0 bytes 0"
448	for dir in "in6" "out6" ; do
449		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
450		if [ $? -ne 0 ]; then
451			bad_counter "$ns1" ns0$dir "$expect" "test_masquerade6 5"
452			lret=1
453		fi
454
455		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
456		if [ $? -ne 0 ]; then
457			bad_counter "$ns0" ns1$dir "$expect" "test_masquerade6 6"
458			lret=1
459		fi
460	done
461
462	ip netns exec "$ns2" ping -q -c 1 dead:1::99 > /dev/null # ping ns2->ns1
463	if [ $? -ne 0 ] ; then
464		echo "ERROR: cannot ping $ns1 from $ns2 with active ipv6 masquerade $natflags (attempt 2)"
465		lret=1
466	fi
467
468	ip netns exec "$ns0" nft flush chain $family nat postrouting
469	if [ $? -ne 0 ]; then
470		echo "ERROR: Could not flush $family nat postrouting" 1>&2
471		lret=1
472	fi
473
474	test $lret -eq 0 && echo "PASS: $family IPv6 masquerade $natflags for $ns2"
475
476	return $lret
477}
478
479test_masquerade()
480{
481	local family=$1
482	local natflags=$2
483	local lret=0
484
485	ip netns exec "$ns0" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
486	ip netns exec "$ns0" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
487
488	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
489	if [ $? -ne 0 ] ; then
490		echo "ERROR: cannot ping $ns1 from "$ns2" $natflags"
491		lret=1
492	fi
493
494	expect="packets 1 bytes 84"
495	for dir in "in" "out" ; do
496		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
497		if [ $? -ne 0 ]; then
498			bad_counter "$ns1" ns2$dir "$expect" "test_masquerade 1"
499			lret=1
500		fi
501
502		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
503		if [ $? -ne 0 ]; then
504			bad_counter "$ns2" ns1$dir "$expect" "test_masquerade 2"
505			lret=1
506		fi
507	done
508
509	reset_counters
510
511# add masquerading rule
512ip netns exec "$ns0" nft -f /dev/stdin <<EOF
513table $family nat {
514	chain postrouting {
515		type nat hook postrouting priority 0; policy accept;
516		meta oif veth0 masquerade $natflags
517	}
518}
519EOF
520	if [ $? -ne 0 ]; then
521		echo "SKIP: Could not add add $family masquerade hook"
522		return $ksft_skip
523	fi
524
525	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
526	if [ $? -ne 0 ] ; then
527		echo "ERROR: cannot ping $ns1 from $ns2 with active $family masquerade $natflags"
528		lret=1
529	fi
530
531	# ns1 should have seen packets from ns0, due to masquerade
532	expect="packets 1 bytes 84"
533	for dir in "in" "out" ; do
534		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0${dir} | grep -q "$expect")
535		if [ $? -ne 0 ]; then
536			bad_counter "$ns1" ns0$dir "$expect" "test_masquerade 3"
537			lret=1
538		fi
539
540		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
541		if [ $? -ne 0 ]; then
542			bad_counter "$ns2" ns1$dir "$expect" "test_masquerade 4"
543			lret=1
544		fi
545	done
546
547	# ns1 should not have seen packets from ns2, due to masquerade
548	expect="packets 0 bytes 0"
549	for dir in "in" "out" ; do
550		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
551		if [ $? -ne 0 ]; then
552			bad_counter "$ns1" ns0$dir "$expect" "test_masquerade 5"
553			lret=1
554		fi
555
556		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
557		if [ $? -ne 0 ]; then
558			bad_counter "$ns0" ns1$dir "$expect" "test_masquerade 6"
559			lret=1
560		fi
561	done
562
563	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
564	if [ $? -ne 0 ] ; then
565		echo "ERROR: cannot ping $ns1 from $ns2 with active ip masquerade $natflags (attempt 2)"
566		lret=1
567	fi
568
569	ip netns exec "$ns0" nft flush chain $family nat postrouting
570	if [ $? -ne 0 ]; then
571		echo "ERROR: Could not flush $family nat postrouting" 1>&2
572		lret=1
573	fi
574
575	test $lret -eq 0 && echo "PASS: $family IP masquerade $natflags for $ns2"
576
577	return $lret
578}
579
580test_redirect6()
581{
582	local family=$1
583	local lret=0
584
585	ip netns exec "$ns0" sysctl net.ipv6.conf.all.forwarding=1 > /dev/null
586
587	ip netns exec "$ns2" ping -q -c 1 dead:1::99 > /dev/null # ping ns2->ns1
588	if [ $? -ne 0 ] ; then
589		echo "ERROR: cannnot ping $ns1 from $ns2 via ipv6"
590		lret=1
591	fi
592
593	expect="packets 1 bytes 104"
594	for dir in "in6" "out6" ; do
595		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
596		if [ $? -ne 0 ]; then
597			bad_counter "$ns1" ns2$dir "$expect" "test_redirect6 1"
598			lret=1
599		fi
600
601		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
602		if [ $? -ne 0 ]; then
603			bad_counter "$ns2" ns1$dir "$expect" "test_redirect6 2"
604			lret=1
605		fi
606	done
607
608	reset_counters
609
610# add redirect rule
611ip netns exec "$ns0" nft -f /dev/stdin <<EOF
612table $family nat {
613	chain prerouting {
614		type nat hook prerouting priority 0; policy accept;
615		meta iif veth1 meta l4proto icmpv6 ip6 saddr dead:2::99 ip6 daddr dead:1::99 redirect
616	}
617}
618EOF
619	if [ $? -ne 0 ]; then
620		echo "SKIP: Could not add add $family redirect hook"
621		return $ksft_skip
622	fi
623
624	ip netns exec "$ns2" ping -q -c 1 dead:1::99 > /dev/null # ping ns2->ns1
625	if [ $? -ne 0 ] ; then
626		echo "ERROR: cannot ping $ns1 from $ns2 via ipv6 with active $family redirect"
627		lret=1
628	fi
629
630	# ns1 should have seen no packets from ns2, due to redirection
631	expect="packets 0 bytes 0"
632	for dir in "in6" "out6" ; do
633		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
634		if [ $? -ne 0 ]; then
635			bad_counter "$ns1" ns0$dir "$expect" "test_redirect6 3"
636			lret=1
637		fi
638	done
639
640	# ns0 should have seen packets from ns2, due to masquerade
641	expect="packets 1 bytes 104"
642	for dir in "in6" "out6" ; do
643		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns2${dir} | grep -q "$expect")
644		if [ $? -ne 0 ]; then
645			bad_counter "$ns1" ns0$dir "$expect" "test_redirect6 4"
646			lret=1
647		fi
648	done
649
650	ip netns exec "$ns0" nft delete table $family nat
651	if [ $? -ne 0 ]; then
652		echo "ERROR: Could not delete $family nat table" 1>&2
653		lret=1
654	fi
655
656	test $lret -eq 0 && echo "PASS: $family IPv6 redirection for $ns2"
657
658	return $lret
659}
660
661test_redirect()
662{
663	local family=$1
664	local lret=0
665
666	ip netns exec "$ns0" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
667	ip netns exec "$ns0" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
668
669	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
670	if [ $? -ne 0 ] ; then
671		echo "ERROR: cannot ping $ns1 from $ns2"
672		lret=1
673	fi
674
675	expect="packets 1 bytes 84"
676	for dir in "in" "out" ; do
677		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
678		if [ $? -ne 0 ]; then
679			bad_counter "$ns1" $ns2$dir "$expect" "test_redirect 1"
680			lret=1
681		fi
682
683		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
684		if [ $? -ne 0 ]; then
685			bad_counter "$ns2" ns1$dir "$expect" "test_redirect 2"
686			lret=1
687		fi
688	done
689
690	reset_counters
691
692# add redirect rule
693ip netns exec "$ns0" nft -f /dev/stdin <<EOF
694table $family nat {
695	chain prerouting {
696		type nat hook prerouting priority 0; policy accept;
697		meta iif veth1 ip protocol icmp ip saddr 10.0.2.99 ip daddr 10.0.1.99 redirect
698	}
699}
700EOF
701	if [ $? -ne 0 ]; then
702		echo "SKIP: Could not add add $family redirect hook"
703		return $ksft_skip
704	fi
705
706	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
707	if [ $? -ne 0 ] ; then
708		echo "ERROR: cannot ping $ns1 from $ns2 with active $family ip redirect"
709		lret=1
710	fi
711
712	# ns1 should have seen no packets from ns2, due to redirection
713	expect="packets 0 bytes 0"
714	for dir in "in" "out" ; do
715
716		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
717		if [ $? -ne 0 ]; then
718			bad_counter "$ns1" ns0$dir "$expect" "test_redirect 3"
719			lret=1
720		fi
721	done
722
723	# ns0 should have seen packets from ns2, due to masquerade
724	expect="packets 1 bytes 84"
725	for dir in "in" "out" ; do
726		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns2${dir} | grep -q "$expect")
727		if [ $? -ne 0 ]; then
728			bad_counter "$ns0" ns0$dir "$expect" "test_redirect 4"
729			lret=1
730		fi
731	done
732
733	ip netns exec "$ns0" nft delete table $family nat
734	if [ $? -ne 0 ]; then
735		echo "ERROR: Could not delete $family nat table" 1>&2
736		lret=1
737	fi
738
739	test $lret -eq 0 && echo "PASS: $family IP redirection for $ns2"
740
741	return $lret
742}
743
744# test port shadowing.
745# create two listening services, one on router (ns0), one
746# on client (ns2), which is masqueraded from ns1 point of view.
747# ns2 sends udp packet coming from service port to ns1, on a highport.
748# Later, if n1 uses same highport to connect to ns0:service, packet
749# might be port-forwarded to ns2 instead.
750
751# second argument tells if we expect the 'fake-entry' to take effect
752# (CLIENT) or not (ROUTER).
753test_port_shadow()
754{
755	local test=$1
756	local expect=$2
757	local daddrc="10.0.1.99"
758	local daddrs="10.0.1.1"
759	local result=""
760	local logmsg=""
761
762	# make shadow entry, from client (ns2), going to (ns1), port 41404, sport 1405.
763	echo "fake-entry" | ip netns exec "$ns2" timeout 1 socat -u STDIN UDP:"$daddrc":41404,sourceport=1405
764
765	echo ROUTER | ip netns exec "$ns0" timeout 5 socat -u STDIN UDP4-LISTEN:1405 &
766	sc_r=$!
767
768	echo CLIENT | ip netns exec "$ns2" timeout 5 socat -u STDIN UDP4-LISTEN:1405,reuseport &
769	sc_c=$!
770
771	sleep 0.3
772
773	# ns1 tries to connect to ns0:1405.  With default settings this should connect
774	# to client, it matches the conntrack entry created above.
775
776	result=$(echo "data" | ip netns exec "$ns1" timeout 1 socat - UDP:"$daddrs":1405,sourceport=41404)
777
778	if [ "$result" = "$expect" ] ;then
779		echo "PASS: portshadow test $test: got reply from ${expect}${logmsg}"
780	else
781		echo "ERROR: portshadow test $test: got reply from \"$result\", not $expect as intended"
782		ret=1
783	fi
784
785	kill $sc_r $sc_c 2>/dev/null
786
787	# flush udp entries for next test round, if any
788	ip netns exec "$ns0" conntrack -F >/dev/null 2>&1
789}
790
791# This prevents port shadow of router service via packet filter,
792# packets claiming to originate from service port from internal
793# network are dropped.
794test_port_shadow_filter()
795{
796	local family=$1
797
798ip netns exec "$ns0" nft -f /dev/stdin <<EOF
799table $family filter {
800	chain forward {
801		type filter hook forward priority 0; policy accept;
802		meta iif veth1 udp sport 1405 drop
803	}
804}
805EOF
806	test_port_shadow "port-filter" "ROUTER"
807
808	ip netns exec "$ns0" nft delete table $family filter
809}
810
811# This prevents port shadow of router service via notrack.
812test_port_shadow_notrack()
813{
814	local family=$1
815
816ip netns exec "$ns0" nft -f /dev/stdin <<EOF
817table $family raw {
818	chain prerouting {
819		type filter hook prerouting priority -300; policy accept;
820		meta iif veth0 udp dport 1405 notrack
821	}
822	chain output {
823		type filter hook output priority -300; policy accept;
824		meta oif veth0 udp sport 1405 notrack
825	}
826}
827EOF
828	test_port_shadow "port-notrack" "ROUTER"
829
830	ip netns exec "$ns0" nft delete table $family raw
831}
832
833# This prevents port shadow of router service via sport remap.
834test_port_shadow_pat()
835{
836	local family=$1
837
838ip netns exec "$ns0" nft -f /dev/stdin <<EOF
839table $family pat {
840	chain postrouting {
841		type nat hook postrouting priority -1; policy accept;
842		meta iif veth1 udp sport <= 1405 masquerade to : 1406-65535 random
843	}
844}
845EOF
846	test_port_shadow "pat" "ROUTER"
847
848	ip netns exec "$ns0" nft delete table $family pat
849}
850
851test_port_shadowing()
852{
853	local family="ip"
854
855	conntrack -h >/dev/null 2>&1
856	if [ $? -ne 0 ];then
857		echo "SKIP: Could not run nat port shadowing test without conntrack tool"
858		return
859	fi
860
861	socat -h > /dev/null 2>&1
862	if [ $? -ne 0 ];then
863		echo "SKIP: Could not run nat port shadowing test without socat tool"
864		return
865	fi
866
867	ip netns exec "$ns0" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
868	ip netns exec "$ns0" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
869
870	ip netns exec "$ns0" nft -f /dev/stdin <<EOF
871table $family nat {
872	chain postrouting {
873		type nat hook postrouting priority 0; policy accept;
874		meta oif veth0 masquerade
875	}
876}
877EOF
878	if [ $? -ne 0 ]; then
879		echo "SKIP: Could not add add $family masquerade hook"
880		return $ksft_skip
881	fi
882
883	# test default behaviour. Packet from ns1 to ns0 is redirected to ns2.
884	test_port_shadow "default" "CLIENT"
885
886	# test packet filter based mitigation: prevent forwarding of
887	# packets claiming to come from the service port.
888	test_port_shadow_filter "$family"
889
890	# test conntrack based mitigation: connections going or coming
891	# from router:service bypass connection tracking.
892	test_port_shadow_notrack "$family"
893
894	# test nat based mitigation: fowarded packets coming from service port
895	# are masqueraded with random highport.
896	test_port_shadow_pat "$family"
897
898	ip netns exec "$ns0" nft delete table $family nat
899}
900
901test_stateless_nat_ip()
902{
903	local lret=0
904
905	ip netns exec "$ns0" sysctl net.ipv4.conf.veth0.forwarding=1 > /dev/null
906	ip netns exec "$ns0" sysctl net.ipv4.conf.veth1.forwarding=1 > /dev/null
907
908	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
909	if [ $? -ne 0 ] ; then
910		echo "ERROR: cannot ping $ns1 from $ns2 before loading stateless rules"
911		return 1
912	fi
913
914ip netns exec "$ns0" nft -f /dev/stdin <<EOF
915table ip stateless {
916	map xlate_in {
917		typeof meta iifname . ip saddr . ip daddr : ip daddr
918		elements = {
919			"veth1" . 10.0.2.99 . 10.0.1.99 : 10.0.2.2,
920		}
921	}
922	map xlate_out {
923		typeof meta iifname . ip saddr . ip daddr : ip daddr
924		elements = {
925			"veth0" . 10.0.1.99 . 10.0.2.2 : 10.0.2.99
926		}
927	}
928
929	chain prerouting {
930		type filter hook prerouting priority -400; policy accept;
931		ip saddr set meta iifname . ip saddr . ip daddr map @xlate_in
932		ip daddr set meta iifname . ip saddr . ip daddr map @xlate_out
933	}
934}
935EOF
936	if [ $? -ne 0 ]; then
937		echo "SKIP: Could not add ip statless rules"
938		return $ksft_skip
939	fi
940
941	reset_counters
942
943	ip netns exec "$ns2" ping -q -c 1 10.0.1.99 > /dev/null # ping ns2->ns1
944	if [ $? -ne 0 ] ; then
945		echo "ERROR: cannot ping $ns1 from $ns2 with stateless rules"
946		lret=1
947	fi
948
949	# ns1 should have seen packets from .2.2, due to stateless rewrite.
950	expect="packets 1 bytes 84"
951	cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0insl | grep -q "$expect")
952	if [ $? -ne 0 ]; then
953		bad_counter "$ns1" ns0insl "$expect" "test_stateless 1"
954		lret=1
955	fi
956
957	for dir in "in" "out" ; do
958		cnt=$(ip netns exec "$ns2" nft list counter inet filter ns1${dir} | grep -q "$expect")
959		if [ $? -ne 0 ]; then
960			bad_counter "$ns2" ns1$dir "$expect" "test_stateless 2"
961			lret=1
962		fi
963	done
964
965	# ns1 should not have seen packets from ns2, due to masquerade
966	expect="packets 0 bytes 0"
967	for dir in "in" "out" ; do
968		cnt=$(ip netns exec "$ns1" nft list counter inet filter ns2${dir} | grep -q "$expect")
969		if [ $? -ne 0 ]; then
970			bad_counter "$ns1" ns0$dir "$expect" "test_stateless 3"
971			lret=1
972		fi
973
974		cnt=$(ip netns exec "$ns0" nft list counter inet filter ns1${dir} | grep -q "$expect")
975		if [ $? -ne 0 ]; then
976			bad_counter "$ns0" ns1$dir "$expect" "test_stateless 4"
977			lret=1
978		fi
979	done
980
981	reset_counters
982
983	socat -h > /dev/null 2>&1
984	if [ $? -ne 0 ];then
985		echo "SKIP: Could not run stateless nat frag test without socat tool"
986		if [ $lret -eq 0 ]; then
987			return $ksft_skip
988		fi
989
990		ip netns exec "$ns0" nft delete table ip stateless
991		return $lret
992	fi
993
994	local tmpfile=$(mktemp)
995	dd if=/dev/urandom of=$tmpfile bs=4096 count=1 2>/dev/null
996
997	local outfile=$(mktemp)
998	ip netns exec "$ns1" timeout 3 socat -u UDP4-RECV:4233 OPEN:$outfile < /dev/null &
999	sc_r=$!
1000
1001	sleep 1
1002	# re-do with large ping -> ip fragmentation
1003	ip netns exec "$ns2" timeout 3 socat - UDP4-SENDTO:"10.0.1.99:4233" < "$tmpfile" > /dev/null
1004	if [ $? -ne 0 ] ; then
1005		echo "ERROR: failed to test udp $ns1 to $ns2 with stateless ip nat" 1>&2
1006		lret=1
1007	fi
1008
1009	wait
1010
1011	cmp "$tmpfile" "$outfile"
1012	if [ $? -ne 0 ]; then
1013		ls -l "$tmpfile" "$outfile"
1014		echo "ERROR: in and output file mismatch when checking udp with stateless nat" 1>&2
1015		lret=1
1016	fi
1017
1018	rm -f "$tmpfile" "$outfile"
1019
1020	# ns1 should have seen packets from 2.2, due to stateless rewrite.
1021	expect="packets 3 bytes 4164"
1022	cnt=$(ip netns exec "$ns1" nft list counter inet filter ns0insl | grep -q "$expect")
1023	if [ $? -ne 0 ]; then
1024		bad_counter "$ns1" ns0insl "$expect" "test_stateless 5"
1025		lret=1
1026	fi
1027
1028	ip netns exec "$ns0" nft delete table ip stateless
1029	if [ $? -ne 0 ]; then
1030		echo "ERROR: Could not delete table ip stateless" 1>&2
1031		lret=1
1032	fi
1033
1034	test $lret -eq 0 && echo "PASS: IP statless for $ns2"
1035
1036	return $lret
1037}
1038
1039# ip netns exec "$ns0" ping -c 1 -q 10.0.$i.99
1040for i in 0 1 2; do
1041ip netns exec ns$i-$sfx nft -f /dev/stdin <<EOF
1042table inet filter {
1043	counter ns0in {}
1044	counter ns1in {}
1045	counter ns2in {}
1046
1047	counter ns0out {}
1048	counter ns1out {}
1049	counter ns2out {}
1050
1051	counter ns0in6 {}
1052	counter ns1in6 {}
1053	counter ns2in6 {}
1054
1055	counter ns0out6 {}
1056	counter ns1out6 {}
1057	counter ns2out6 {}
1058
1059	map nsincounter {
1060		type ipv4_addr : counter
1061		elements = { 10.0.1.1 : "ns0in",
1062			     10.0.2.1 : "ns0in",
1063			     10.0.1.99 : "ns1in",
1064			     10.0.2.99 : "ns2in" }
1065	}
1066
1067	map nsincounter6 {
1068		type ipv6_addr : counter
1069		elements = { dead:1::1 : "ns0in6",
1070			     dead:2::1 : "ns0in6",
1071			     dead:1::99 : "ns1in6",
1072			     dead:2::99 : "ns2in6" }
1073	}
1074
1075	map nsoutcounter {
1076		type ipv4_addr : counter
1077		elements = { 10.0.1.1 : "ns0out",
1078			     10.0.2.1 : "ns0out",
1079			     10.0.1.99: "ns1out",
1080			     10.0.2.99: "ns2out" }
1081	}
1082
1083	map nsoutcounter6 {
1084		type ipv6_addr : counter
1085		elements = { dead:1::1 : "ns0out6",
1086			     dead:2::1 : "ns0out6",
1087			     dead:1::99 : "ns1out6",
1088			     dead:2::99 : "ns2out6" }
1089	}
1090
1091	chain input {
1092		type filter hook input priority 0; policy accept;
1093		counter name ip saddr map @nsincounter
1094		icmpv6 type { "echo-request", "echo-reply" } counter name ip6 saddr map @nsincounter6
1095	}
1096	chain output {
1097		type filter hook output priority 0; policy accept;
1098		counter name ip daddr map @nsoutcounter
1099		icmpv6 type { "echo-request", "echo-reply" } counter name ip6 daddr map @nsoutcounter6
1100	}
1101}
1102EOF
1103done
1104
1105# special case for stateless nat check, counter needs to
1106# be done before (input) ip defragmentation
1107ip netns exec ns1-$sfx nft -f /dev/stdin <<EOF
1108table inet filter {
1109	counter ns0insl {}
1110
1111	chain pre {
1112		type filter hook prerouting priority -400; policy accept;
1113		ip saddr 10.0.2.2 counter name "ns0insl"
1114	}
1115}
1116EOF
1117
1118sleep 3
1119# test basic connectivity
1120for i in 1 2; do
1121  ip netns exec "$ns0" ping -c 1 -q 10.0.$i.99 > /dev/null
1122  if [ $? -ne 0 ];then
1123  	echo "ERROR: Could not reach other namespace(s)" 1>&2
1124	ret=1
1125  fi
1126
1127  ip netns exec "$ns0" ping -c 1 -q dead:$i::99 > /dev/null
1128  if [ $? -ne 0 ];then
1129	echo "ERROR: Could not reach other namespace(s) via ipv6" 1>&2
1130	ret=1
1131  fi
1132  check_counters ns$i-$sfx
1133  if [ $? -ne 0 ]; then
1134	ret=1
1135  fi
1136
1137  check_ns0_counters ns$i
1138  if [ $? -ne 0 ]; then
1139	ret=1
1140  fi
1141  reset_counters
1142done
1143
1144if [ $ret -eq 0 ];then
1145	echo "PASS: netns routing/connectivity: $ns0 can reach $ns1 and $ns2"
1146fi
1147
1148reset_counters
1149test_local_dnat ip
1150test_local_dnat6 ip6
1151reset_counters
1152$test_inet_nat && test_local_dnat inet
1153$test_inet_nat && test_local_dnat6 inet
1154
1155for flags in "" "fully-random"; do
1156reset_counters
1157test_masquerade ip $flags
1158test_masquerade6 ip6 $flags
1159reset_counters
1160$test_inet_nat && test_masquerade inet $flags
1161$test_inet_nat && test_masquerade6 inet $flags
1162done
1163
1164reset_counters
1165test_redirect ip
1166test_redirect6 ip6
1167reset_counters
1168$test_inet_nat && test_redirect inet
1169$test_inet_nat && test_redirect6 inet
1170
1171test_port_shadowing
1172test_stateless_nat_ip
1173
1174if [ $ret -ne 0 ];then
1175	echo -n "FAIL: "
1176	nft --version
1177fi
1178
1179exit $ret
1180