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