xref: /freebsd/tests/sys/netpfil/pf/nat64.sh (revision df21a004be237a1dccd03c7b47254625eea62fa9)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2024 Rubicon Communications, LLC (Netgate)
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9# 1. Redistributions of source code must retain the above copyright
10#    notice, this list of conditions and the following disclaimer.
11# 2. Redistributions in binary form must reproduce the above copyright
12#    notice, this list of conditions and the following disclaimer in the
13#    documentation and/or other materials provided with the distribution.
14#
15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25# SUCH DAMAGE.
26
27. $(atf_get_srcdir)/utils.subr
28
29nat64_setup_base()
30{
31	pft_init
32
33	epair_link=$(vnet_mkepair)
34	epair=$(vnet_mkepair)
35
36	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
37	route -6 add default 2001:db8::1
38
39	vnet_mkjail rtr ${epair}b ${epair_link}a
40	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
41	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
42
43	vnet_mkjail dst ${epair_link}b
44	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
45	jexec dst route add default 192.0.2.1
46
47	# Sanity checks
48	atf_check -s exit:0 -o ignore \
49	    ping6 -c 1 2001:db8::1
50	atf_check -s exit:0 -o ignore \
51	    jexec dst ping -c 1 192.0.2.1
52
53	jexec rtr pfctl -e
54}
55
56nat64_setup_in()
57{
58	state_policy="${1:-if-bound}"
59	nat64_setup_base
60	pft_set_rules rtr \
61	    "set reassemble yes" \
62	    "set state-policy ${state_policy}" \
63	    "block" \
64	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
65	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
66}
67
68nat64_setup_out()
69{
70	state_policy="${1:-if-bound}"
71	nat64_setup_base
72	jexec rtr sysctl net.inet6.ip6.forwarding=1
73	# AF translation happens post-routing, traffic must be directed
74	# towards the outbound interface using routes for the original AF.
75	# jexec rtr ifconfig ${epair_link}a inet6 2001:db8:2::1/64 up no_dad
76	jexec rtr route add -inet6 64:ff9b::/96 -iface ${epair_link}a;
77	pft_set_rules rtr \
78	    "set reassemble yes" \
79	    "set state-policy ${state_policy}" \
80	    "block" \
81	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
82	    "pass in  on ${epair}b from any to 64:ff9b::/96" \
83	    "pass out on ${epair_link}a from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
84}
85
86atf_test_case "icmp_echo_in" "cleanup"
87icmp_echo_in_head()
88{
89	atf_set descr 'Basic NAT64 ICMP echo test on inbound interface'
90	atf_set require.user root
91}
92
93icmp_echo_in_body()
94{
95	nat64_setup_in
96
97	# One ping
98	atf_check -s exit:0 -o ignore \
99	    ping6 -c 1 64:ff9b::192.0.2.2
100
101	# Make sure packets make it even when state is established
102	atf_check -s exit:0 \
103	    -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \
104	    ping6 -c 5 64:ff9b::192.0.2.2
105}
106
107icmp_echo_in_cleanup()
108{
109	pft_cleanup
110}
111
112atf_test_case "icmp_echo_out" "cleanup"
113icmp_echo_out_head()
114{
115	atf_set descr 'Basic NAT64 ICMP echo test on outbound interface'
116	atf_set require.user root
117}
118
119icmp_echo_out_body()
120{
121	nat64_setup_out
122
123	# One ping
124	atf_check -s exit:0 -o ignore \
125	    ping6 -c 1 64:ff9b::192.0.2.2
126
127	# Make sure packets make it even when state is established
128	atf_check -s exit:0 \
129	    -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \
130	    ping6 -c 5 64:ff9b::192.0.2.2
131}
132
133icmp_echo_out_cleanup()
134{
135	pft_cleanup
136}
137
138atf_test_case "fragmentation_in" "cleanup"
139fragmentation_in_head()
140{
141	atf_set descr 'Test fragmented packets on inbound interface'
142	atf_set require.user root
143}
144
145fragmentation_in_body()
146{
147	nat64_setup_in
148
149	atf_check -s exit:0 -o ignore \
150	    ping6 -c 1 -s 1280 64:ff9b::192.0.2.2
151
152	atf_check -s exit:0 \
153	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
154	    ping6 -c 3 -s 2000 64:ff9b::192.0.2.2
155	atf_check -s exit:0 \
156	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
157	    ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2
158}
159
160fragmentation_in_cleanup()
161{
162	pft_cleanup
163}
164
165atf_test_case "fragmentation_out" "cleanup"
166fragmentation_out_head()
167{
168	atf_set descr 'Test fragmented packets on outbound interface'
169	atf_set require.user root
170}
171
172fragmentation_out_body()
173{
174	nat64_setup_out
175
176	atf_check -s exit:0 -o ignore \
177	    ping6 -c 1 -s 1280 64:ff9b::192.0.2.2
178
179	atf_check -s exit:0 \
180	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
181	    ping6 -c 3 -s 2000 64:ff9b::192.0.2.2
182	atf_check -s exit:0 \
183	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
184	    ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2
185}
186
187fragmentation_out_cleanup()
188{
189	pft_cleanup
190}
191
192atf_test_case "tcp_in_if_bound" "cleanup"
193tcp_in_if_bound_head()
194{
195	atf_set descr 'TCP NAT64 test on inbound interface, if-bound states'
196	atf_set require.user root
197}
198
199tcp_in_if_bound_body()
200{
201	nat64_setup_in
202
203	echo "foo" | jexec dst nc -l 1234 &
204
205	# Sanity check & delay for nc startup
206	atf_check -s exit:0 -o ignore \
207	    ping6 -c 3 64:ff9b::192.0.2.2
208
209	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
210	if [ "${rcv}" != "foo" ];
211	then
212		echo "rcv=${rcv}"
213		atf_fail "Failed to connect to TCP server"
214	fi
215
216	sleep 1
217
218	# Interfaces of the state are reversed when doing inbound NAT64!
219	# FIXME: Packets from both directions are counted only on the inbound direction!
220	states=$(mktemp) || exit 1
221	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
222	for state_regexp in \
223		"${epair_link}a tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\) .* 5:4 pkts.* rule 3 .* origif: ${epair}b" \
224	; do
225		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
226	done
227	[ $(cat $states | grep tcp | wc -l) -eq 1 ] || atf_fail "Not exactly 1 state found!"
228}
229
230tcp_in_if_bound_cleanup()
231{
232	pft_cleanup
233}
234
235atf_test_case "tcp_out_if_bound" "cleanup"
236tcp_out_if_bound_head()
237{
238	atf_set descr 'TCP NAT64 test on outbound interface, if-bound states'
239	atf_set require.user root
240}
241
242tcp_out_if_bound_body()
243{
244	nat64_setup_out
245
246	echo "foo" | jexec dst nc -l 1234 &
247
248	# Sanity check & delay for nc startup
249	atf_check -s exit:0 -o ignore \
250	    ping6 -c 3 64:ff9b::192.0.2.2
251
252	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
253	if [ "${rcv}" != "foo" ];
254	then
255		echo "rcv=${rcv}"
256		atf_fail "Failed to connect to TCP server"
257	fi
258
259	sleep 1
260
261	# Origif is not printed when identical as if.
262	states=$(mktemp) || exit 1
263	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
264	for state_regexp in \
265		"${epair}b tcp 64:ff9b::c000:202\[1234\] <- 2001:db8::2\[[0-9]+\] .* 5:4 pkts.* rule 3 .*creatorid" \
266		"${epair_link}a tcp 192.0.2.1:[0-9]+ \(64:ff9b::c000:202\[1234\]\) -> 192.0.2.2:1234 \(2001:db8::2\[[0-9]+\]\).* 5:4 pkts.* rule 4 .*creatorid" \
267	; do
268		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
269	done
270	[ $(cat $states | grep tcp | wc -l) -eq 2 ] || atf_fail "Not exactly 2 states found!"
271}
272
273tcp_out_if_bound_cleanup()
274{
275	pft_cleanup
276}
277
278atf_test_case "tcp_in_floating" "cleanup"
279tcp_in_floating_head()
280{
281	atf_set descr 'TCP NAT64 test on inbound interface, floating states'
282	atf_set require.user root
283}
284
285tcp_in_floating_body()
286{
287	nat64_setup_in "floating"
288
289	echo "foo" | jexec dst nc -l 1234 &
290
291	# Sanity check & delay for nc startup
292	atf_check -s exit:0 -o ignore \
293	    ping6 -c 3 64:ff9b::192.0.2.2
294
295	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
296	if [ "${rcv}" != "foo" ];
297	then
298		echo "rcv=${rcv}"
299		atf_fail "Failed to connect to TCP server"
300	fi
301
302	sleep 1
303
304	# Interfaces of the state are reversed when doing inbound NAT64!
305	# FIXME: Packets from both directions are counted only on the inbound direction!
306	states=$(mktemp) || exit 1
307	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
308	for state_regexp in \
309		"all tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\).* 5:4 pkts.* rule 3 .* origif: ${epair}b" \
310	; do
311		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
312	done
313	[ $(cat $states | grep tcp | wc -l) -eq 1 ] || atf_fail "Not exactly 1 state found!"
314}
315
316tcp_in_floating_cleanup()
317{
318	pft_cleanup
319}
320
321atf_test_case "tcp_out_floating" "cleanup"
322tcp_out_floating_head()
323{
324	atf_set descr 'TCP NAT64 test on outbound interface, floating states'
325	atf_set require.user root
326}
327
328tcp_out_floating_body()
329{
330	nat64_setup_out "floating"
331
332	echo "foo" | jexec dst nc -l 1234 &
333
334	# Sanity check & delay for nc startup
335	atf_check -s exit:0 -o ignore \
336	    ping6 -c 3 64:ff9b::192.0.2.2
337
338	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
339	if [ "${rcv}" != "foo" ];
340	then
341		echo "rcv=${rcv}"
342		atf_fail "Failed to connect to TCP server"
343	fi
344
345	sleep 1
346
347	# Origif is not printed when identical as if.
348	states=$(mktemp) || exit 1
349	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
350	for state_regexp in \
351		"all tcp 64:ff9b::c000:202\[1234\] <- 2001:db8::2\[[0-9]+\] .* 5:4 pkts,.* rule 3 .*creatorid"\
352		"all tcp 192.0.2.1:[0-9]+ \(64:ff9b::c000:202\[1234\]\) -> 192.0.2.2:1234 \(2001:db8::2\[[0-9]+\]\) .* 5:4 pkts,.* rule 4 .*creatorid"\
353	; do
354		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
355	done
356	[ $(cat $states | grep tcp | wc -l) -eq 2 ] || atf_fail "Not exactly 2 states found!"
357}
358
359tcp_out_floating_cleanup()
360{
361	pft_cleanup
362}
363
364atf_test_case "udp_in" "cleanup"
365udp_in_head()
366{
367	atf_set descr 'UDP NAT64 test on inbound interface'
368	atf_set require.user root
369}
370
371udp_in_body()
372{
373	nat64_setup_in
374
375	echo "foo" | jexec dst nc -u -l 1234 &
376
377	# Sanity check & delay for nc startup
378	atf_check -s exit:0 -o ignore \
379	    ping6 -c 3 64:ff9b::192.0.2.2
380
381	rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234)
382	if [ "${rcv}" != "foo" ];
383	then
384		echo "rcv=${rcv}"
385		atf_fail "Failed to connect to UDP server"
386	fi
387}
388
389udp_in_cleanup()
390{
391	pft_cleanup
392}
393
394atf_test_case "udp_out" "cleanup"
395udp_out_head()
396{
397	atf_set descr 'UDP NAT64 test on outbound interface'
398	atf_set require.user root
399}
400
401udp_out_body()
402{
403	nat64_setup_out
404
405	echo "foo" | jexec dst nc -u -l 1234 &
406
407	# Sanity check & delay for nc startup
408	atf_check -s exit:0 -o ignore \
409	    ping6 -c 3 64:ff9b::192.0.2.2
410
411	rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234)
412	if [ "${rcv}" != "foo" ];
413	then
414		echo "rcv=${rcv}"
415		atf_fail "Failed to connect to UDP server"
416	fi
417}
418
419udp_out_cleanup()
420{
421	pft_cleanup
422}
423
424atf_test_case "sctp_in" "cleanup"
425sctp_in_head()
426{
427	atf_set descr 'SCTP NAT64 test on inbound interface'
428	atf_set require.user root
429}
430
431sctp_in_body()
432{
433	nat64_setup_in
434	if ! kldstat -q -m sctp; then
435		atf_skip "This test requires SCTP"
436	fi
437
438	echo "foo" | jexec dst nc --sctp -N -l 1234 &
439
440	# Sanity check & delay for nc startup
441	atf_check -s exit:0 -o ignore \
442	    ping6 -c 3 64:ff9b::192.0.2.2
443
444	rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234)
445	if [ "${rcv}" != "foo" ];
446	then
447		echo "rcv=${rcv}"
448		atf_fail "Failed to connect to SCTP server"
449	fi
450}
451
452sctp_in_cleanup()
453{
454	pft_cleanup
455}
456
457atf_test_case "sctp_out" "cleanup"
458sctp_out_head()
459{
460	atf_set descr 'SCTP NAT64 test on outbound interface'
461	atf_set require.user root
462}
463
464sctp_out_body()
465{
466	nat64_setup_out
467	if ! kldstat -q -m sctp; then
468		atf_skip "This test requires SCTP"
469	fi
470
471	echo "foo" | jexec dst nc --sctp -N -l 1234 &
472
473	# Sanity check & delay for nc startup
474	atf_check -s exit:0 -o ignore \
475	    ping6 -c 3 64:ff9b::192.0.2.2
476
477	rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234)
478	if [ "${rcv}" != "foo" ];
479	then
480		echo "rcv=${rcv}"
481		atf_fail "Failed to connect to SCTP server"
482	fi
483}
484
485sctp_out_cleanup()
486{
487	pft_cleanup
488}
489
490atf_test_case "tos" "cleanup"
491tos_head()
492{
493	atf_set descr 'ToS translation test'
494	atf_set require.user root
495}
496
497tos_body()
498{
499	nat64_setup_in
500
501	# Ensure we can distinguish ToS on the destination
502	jexec dst pfctl -e
503	pft_set_rules dst \
504	    "pass" \
505	    "block in inet tos 8"
506
507	atf_check -s exit:0 -o ignore \
508	    ping6 -c 1 -z 4 64:ff9b::192.0.2.2
509	atf_check -s exit:2 -o ignore \
510	    ping6 -c 1 -z 8 64:ff9b::192.0.2.2
511	atf_check -s exit:0 -o ignore \
512	    ping6 -c 1 -z 16 64:ff9b::192.0.2.2
513
514	jexec dst pfctl -sr -vv
515}
516
517tos_cleanup()
518{
519	pft_cleanup
520}
521
522atf_test_case "no_v4" "cleanup"
523no_v4_head()
524{
525	atf_set descr 'Test error handling when there is no IPv4 address to translate to'
526	atf_set require.user root
527}
528
529no_v4_body()
530{
531	pft_init
532
533	epair_link=$(vnet_mkepair)
534	epair=$(vnet_mkepair)
535
536	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
537	route -6 add default 2001:db8::1
538
539	vnet_mkjail rtr ${epair}b ${epair_link}a
540	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
541
542	vnet_mkjail dst ${epair_link}b
543	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
544	jexec dst route add default 192.0.2.1
545
546	# Sanity check
547	atf_check -s exit:0 -o ignore \
548	    ping6 -c 1 2001:db8::1
549
550	jexec rtr pfctl -e
551	pft_set_rules rtr \
552	    "block" \
553	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
554	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" \
555
556	atf_check -s exit:2 -o ignore \
557	    ping6 -c 3 64:ff9b::192.0.2.2
558}
559
560no_v4_cleanup()
561{
562	pft_cleanup
563}
564
565atf_test_case "range" "cleanup"
566range_head()
567{
568	atf_set descr 'Test using an address range for the IPv4 side'
569	atf_set require.user root
570}
571
572range_body()
573{
574	pft_init
575
576	epair_link=$(vnet_mkepair)
577	epair=$(vnet_mkepair)
578
579	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
580	route -6 add default 2001:db8::1
581
582	vnet_mkjail rtr ${epair}b ${epair_link}a
583	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
584	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
585	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
586
587	vnet_mkjail dst ${epair_link}b
588	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
589	jexec dst route add default 192.0.2.2
590
591	# Sanity checks
592	atf_check -s exit:0 -o ignore \
593	    jexec rtr ping -c 1 192.0.2.254
594	atf_check -s exit:0 -o ignore \
595	    ping6 -c 1 2001:db8::1
596	atf_check -s exit:0 -o ignore \
597	    jexec dst ping -c 1 192.0.2.2
598	atf_check -s exit:0 -o ignore \
599	    jexec dst ping -c 1 192.0.2.3
600
601	jexec rtr pfctl -e
602	pft_set_rules rtr \
603	    "set reassemble yes" \
604	    "set state-policy if-bound" \
605	    "block" \
606	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
607	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.2/31 round-robin" \
608
609	# Use pf to count sources
610	jexec dst pfctl -e
611	pft_set_rules dst \
612	    "pass"
613
614	atf_check -s exit:0 -o ignore \
615	    ping6 -c 1 64:ff9b::192.0.2.254
616	atf_check -s exit:0 -o ignore \
617	    ping6 -c 1 64:ff9b::192.0.2.254
618
619	# Verify on dst that we saw different source addresses
620	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
621	    jexec dst pfctl -ss
622	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
623	    jexec dst pfctl -ss
624}
625
626range_cleanup()
627{
628	pft_cleanup
629}
630
631atf_test_case "pool" "cleanup"
632pool_head()
633{
634	atf_set descr 'Use a pool of IPv4 addresses'
635	atf_set require.user root
636}
637
638pool_body()
639{
640	pft_init
641
642	epair_link=$(vnet_mkepair)
643	epair=$(vnet_mkepair)
644
645	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
646	route -6 add default 2001:db8::1
647
648	vnet_mkjail rtr ${epair}b ${epair_link}a
649	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
650	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
651	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
652	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
653
654	vnet_mkjail dst ${epair_link}b
655	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
656	jexec dst route add default 192.0.2.1
657
658	# Sanity checks
659	atf_check -s exit:0 -o ignore \
660	    ping6 -c 1 2001:db8::1
661	atf_check -s exit:0 -o ignore \
662	    jexec dst ping -c 1 192.0.2.1
663
664	jexec rtr pfctl -e
665	pft_set_rules rtr \
666	    "set reassemble yes" \
667	    "set state-policy if-bound" \
668	    "block" \
669	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
670	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from { 192.0.2.1, 192.0.2.3, 192.0.2.4 } round-robin"
671
672	# Use pf to count sources
673	jexec dst pfctl -e
674	pft_set_rules dst \
675	    "pass"
676
677	atf_check -s exit:0 -o ignore \
678	    ping6 -c 1 64:ff9b::192.0.2.2
679	atf_check -s exit:0 -o ignore \
680	    ping6 -c 1 64:ff9b::192.0.2.2
681	atf_check -s exit:0 -o ignore \
682	    ping6 -c 1 64:ff9b::192.0.2.2
683
684	# Verify on dst that we saw different source addresses
685	atf_check -s exit:0 -o match:".*192.0.2.1.*" \
686	    jexec dst pfctl -ss
687	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
688	    jexec dst pfctl -ss
689	atf_check -s exit:0 -o match:".*192.0.2.4.*" \
690	    jexec dst pfctl -ss
691}
692
693pool_cleanup()
694{
695	pft_cleanup
696}
697
698
699atf_test_case "table"
700table_head()
701{
702	atf_set descr 'Check table restrictions'
703	atf_set require.user root
704}
705
706table_body()
707{
708	pft_init
709
710	# Round-robin and random are allowed
711	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> round-robin" | \
712	    atf_check -s exit:0 \
713	    pfctl -f -
714	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> random" | \
715	    atf_check -s exit:0 \
716	    pfctl -f -
717
718	# bitmask is not
719	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> bitmask" | \
720	    atf_check -s exit:1 \
721	    -e match:"tables are not supported by pool type" \
722	    pfctl -f -
723}
724
725table_cleanup()
726{
727	pft_cleanup
728}
729
730atf_test_case "table_range" "cleanup"
731table_range_head()
732{
733	atf_set descr 'Test using an address range within a table for the IPv4 side'
734	atf_set require.user root
735}
736
737table_range_body()
738{
739	pft_init
740
741	epair_link=$(vnet_mkepair)
742	epair=$(vnet_mkepair)
743
744	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
745	route -6 add default 2001:db8::1
746
747	vnet_mkjail rtr ${epair}b ${epair_link}a
748	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
749	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
750	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
751
752	vnet_mkjail dst ${epair_link}b
753	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
754	jexec dst route add default 192.0.2.2
755
756	# Sanity checks
757	atf_check -s exit:0 -o ignore \
758	    ping6 -c 1 2001:db8::1
759	atf_check -s exit:0 -o ignore \
760	    jexec dst ping -c 1 192.0.2.2
761
762	jexec rtr pfctl -e
763	pft_set_rules rtr \
764	    "set reassemble yes" \
765	    "set state-policy if-bound" \
766	    "table <wanaddrs> { 192.0.2.2/31 }" \
767	    "block" \
768	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
769	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin"
770
771	# Use pf to count sources
772	jexec dst pfctl -e
773	pft_set_rules dst \
774	    "pass"
775
776	atf_check -s exit:0 -o ignore \
777	    ping6 -c 1 64:ff9b::192.0.2.254
778	atf_check -s exit:0 -o ignore \
779	    ping6 -c 1 64:ff9b::192.0.2.254
780
781	# Verify on dst that we saw different source addresses
782	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
783	    jexec dst pfctl -ss
784	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
785	    jexec dst pfctl -ss
786}
787
788table_range_cleanup()
789{
790	pft_cleanup
791}
792
793table_common_body()
794{
795	pool_type=$1
796
797	pft_init
798
799	epair_link=$(vnet_mkepair)
800	epair=$(vnet_mkepair)
801
802	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
803	route -6 add default 2001:db8::1
804
805	vnet_mkjail rtr ${epair}b ${epair_link}a
806	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
807	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
808	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
809	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
810
811	vnet_mkjail dst ${epair_link}b
812	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
813	jexec dst route add default 192.0.2.1
814
815	# Sanity checks
816	atf_check -s exit:0 -o ignore \
817	    ping6 -c 1 2001:db8::1
818	atf_check -s exit:0 -o ignore \
819	    jexec dst ping -c 1 192.0.2.1
820
821	jexec rtr pfctl -e
822	pft_set_rules rtr \
823	    "set reassemble yes" \
824	    "set state-policy if-bound" \
825	    "table <wanaddrs> { 192.0.2.1, 192.0.2.3, 192.0.2.4 }" \
826	    "block" \
827	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
828	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> ${pool_type}"
829
830	# Use pf to count sources
831	jexec dst pfctl -e
832	pft_set_rules dst \
833	    "pass"
834
835	atf_check -s exit:0 -o ignore \
836	    ping6 -c 1 64:ff9b::192.0.2.2
837	atf_check -s exit:0 -o ignore \
838	    ping6 -c 1 64:ff9b::192.0.2.2
839	atf_check -s exit:0 -o ignore \
840	    ping6 -c 1 64:ff9b::192.0.2.2
841
842	# XXX We can't reasonably check pool type random because it's random. It may end
843	# up choosing the same source IP for all three connections.
844	if [ "${pool_type}" == "round-robin" ];
845	then
846		# Verify on dst that we saw different source addresses
847		atf_check -s exit:0 -o match:".*192.0.2.1.*" \
848		    jexec dst pfctl -ss
849		atf_check -s exit:0 -o match:".*192.0.2.3.*" \
850		    jexec dst pfctl -ss
851		atf_check -s exit:0 -o match:".*192.0.2.4.*" \
852		    jexec dst pfctl -ss
853	fi
854}
855
856atf_test_case "table_round_robin" "cleanup"
857table_round_robin_head()
858{
859	atf_set descr 'Use a table of IPv4 addresses in round-robin mode'
860	atf_set require.user root
861}
862
863table_round_robin_body()
864{
865	table_common_body round-robin
866}
867
868table_round_robin_cleanup()
869{
870	pft_cleanup
871}
872
873atf_test_case "table_random" "cleanup"
874table_random_head()
875{
876	atf_set descr 'Use a table of IPv4 addresses in random mode'
877	atf_set require.user root
878}
879
880table_random_body()
881{
882	table_common_body random
883}
884
885table_random_cleanup()
886{
887	pft_cleanup
888}
889
890atf_test_case "dummynet" "cleanup"
891dummynet_head()
892{
893	atf_set descr 'Test dummynet on af-to rules'
894	atf_set require.user root
895}
896
897dummynet_body()
898{
899	pft_init
900	dummynet_init
901
902	epair_link=$(vnet_mkepair)
903	epair=$(vnet_mkepair)
904
905	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
906	route -6 add default 2001:db8::1
907
908	vnet_mkjail rtr ${epair}b ${epair_link}a
909	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
910	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
911
912	vnet_mkjail dst ${epair_link}b
913	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
914	jexec dst route add default 192.0.2.1
915
916	# Sanity checks
917	atf_check -s exit:0 -o ignore \
918	    ping6 -c 1 2001:db8::1
919	atf_check -s exit:0 -o ignore \
920	    jexec dst ping -c 1 192.0.2.1
921
922	jexec rtr pfctl -e
923	jexec rtr dnctl pipe 1 config delay 600
924	pft_set_rules rtr \
925	    "set reassemble yes" \
926	    "set state-policy if-bound" \
927	    "block" \
928	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
929	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 dnpipe 1 af-to inet from (${epair_link}a)"
930
931	# The ping request will pass, but take 1.2 seconds (.6 in, .6 out)
932	# So this works:
933	atf_check -s exit:0 -o ignore \
934	    ping6 -c 1 -t 2 64:ff9b::192.0.2.2
935
936	# But this times out:
937	atf_check -s exit:2 -o ignore \
938	    ping6 -c 1 -t 1 64:ff9b::192.0.2.2
939}
940
941dummynet_cleanup()
942{
943	pft_cleanup
944}
945
946atf_test_case "gateway6" "cleanup"
947gateway6_head()
948{
949	atf_set descr 'NAT64 with a routing hop on the v6 side'
950	atf_set require.user root
951}
952
953gateway6_body()
954{
955	pft_init
956
957	epair_lan_link=$(vnet_mkepair)
958	epair_link=$(vnet_mkepair)
959	epair=$(vnet_mkepair)
960
961	ifconfig ${epair}a inet6 2001:db8:1::2/64 up no_dad
962	route -6 add default 2001:db8:1::1
963
964	vnet_mkjail lan_rtr ${epair}b ${epair_lan_link}a
965	jexec lan_rtr ifconfig ${epair}b inet6 2001:db8:1::1/64 up no_dad
966	jexec lan_rtr ifconfig ${epair_lan_link}a inet6 2001:db8::2/64 up no_dad
967	jexec lan_rtr route -6 add default 2001:db8::1
968	jexec lan_rtr sysctl net.inet6.ip6.forwarding=1
969
970	vnet_mkjail rtr ${epair_lan_link}b ${epair_link}a
971	jexec rtr ifconfig ${epair_lan_link}b inet6 2001:db8::1/64 up no_dad
972	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
973	jexec rtr route -6 add default 2001:db8::2
974
975	vnet_mkjail dst ${epair_link}b
976	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
977	jexec dst route add default 192.0.2.1
978
979	# Sanity checks
980	atf_check -s exit:0 -o ignore \
981	    ping6 -c 1 2001:db8:1::1
982	atf_check -s exit:0 -o ignore \
983	    ping6 -c 1 2001:db8::1
984	atf_check -s exit:0 -o ignore \
985	    jexec dst ping -c 1 192.0.2.1
986
987	jexec rtr pfctl -e
988	pft_set_rules rtr \
989	    "set reassemble yes" \
990	    "set state-policy if-bound" \
991	    "block" \
992	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
993	    "pass in on ${epair_lan_link}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
994
995	# One ping
996	atf_check -s exit:0 -o ignore \
997	    ping6 -c 1 64:ff9b::192.0.2.2
998
999	# Make sure packets make it even when state is established
1000	atf_check -s exit:0 \
1001	    -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \
1002	    ping6 -c 5 64:ff9b::192.0.2.2
1003}
1004
1005gateway6_cleanup()
1006{
1007	pft_cleanup
1008}
1009
1010atf_test_case "route_to" "cleanup"
1011route_to_head()
1012{
1013	atf_set descr 'Test route-to on af-to rules'
1014	atf_set require.user root
1015}
1016
1017route_to_body()
1018{
1019	pft_init
1020
1021	epair_link=$(vnet_mkepair)
1022	epair=$(vnet_mkepair)
1023
1024	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
1025	route -6 add default 2001:db8::1
1026
1027	vnet_mkjail rtr ${epair}b ${epair_link}a
1028	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
1029	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
1030
1031	vnet_mkjail dst ${epair_link}b
1032	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
1033	jexec dst ifconfig lo0 203.0.113.1/32 alias
1034	jexec dst route add default 192.0.2.2
1035
1036	# Sanity checks
1037	atf_check -s exit:0 -o ignore \
1038	    ping6 -c 1 2001:db8::1
1039
1040	jexec rtr pfctl -e
1041	pft_set_rules rtr \
1042	    "set reassemble yes" \
1043	    "set debug loud" \
1044	    "set state-policy if-bound" \
1045	    "block log (all)" \
1046	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1047	    "pass in on ${epair}b route-to (${epair_link}a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
1048
1049	atf_check -s exit:0 -o ignore \
1050	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
1051	    ping6 -c 3 64:ff9b::192.0.2.2
1052
1053	states=$(mktemp) || exit 1
1054	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
1055	cat $states
1056
1057	# Interfaces of the state are reversed when doing inbound NAT64!
1058	for state_regexp in \
1059		"${epair_link}a ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).* 3:3 pkts.*route-to: 192.0.2.2@${epair_link}a origif: ${epair}b" \
1060	; do
1061		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
1062	done
1063}
1064
1065route_to_cleanup()
1066{
1067	pft_cleanup
1068}
1069
1070atf_test_case "reply_to" "cleanup"
1071reply_to_head()
1072{
1073	atf_set descr 'Test reply-to on af-to rules'
1074	atf_set require.user root
1075}
1076
1077reply_to_body()
1078{
1079	pft_init
1080
1081	epair_link=$(vnet_mkepair)
1082	epair=$(vnet_mkepair)
1083
1084	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
1085	route -6 add default 2001:db8::1
1086
1087	vnet_mkjail rtr ${epair}b ${epair_link}a
1088	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
1089	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
1090
1091	vnet_mkjail dst ${epair_link}b
1092	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
1093	jexec dst route add default 192.0.2.1
1094
1095	# Sanity checks
1096	atf_check -s exit:0 -o ignore \
1097	    ping6 -c 1 2001:db8::1
1098
1099	jexec rtr pfctl -e
1100	pft_set_rules rtr \
1101	    "set reassemble yes" \
1102	    "set state-policy if-bound" \
1103	    "block" \
1104	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1105	    "pass in on ${epair}b reply-to (${epair}b 2001:db8::2) inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.1"
1106
1107	atf_check -s exit:0 -o ignore \
1108	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
1109	    ping6 -c 3 64:ff9b::192.0.2.2
1110}
1111
1112reply_to_cleanup()
1113{
1114	pft_cleanup
1115}
1116
1117atf_test_case "v6_gateway" "cleanup"
1118v6_gateway_head()
1119{
1120	atf_set descr 'nat64 when the IPv4 gateway is given by an IPv6 address'
1121	atf_set require.user root
1122}
1123
1124v6_gateway_body()
1125{
1126	pft_init
1127
1128	epair_wan_two=$(vnet_mkepair)
1129	epair_wan_one=$(vnet_mkepair)
1130	epair_lan=$(vnet_mkepair)
1131
1132	ifconfig ${epair_lan}a inet6 2001:db8::2/64 up no_dad
1133	route -6 add default 2001:db8::1
1134
1135	vnet_mkjail rtr ${epair_lan}b ${epair_wan_one}a
1136	jexec rtr ifconfig ${epair_lan}b inet6 2001:db8::1/64 up no_dad
1137	jexec rtr ifconfig ${epair_wan_one}a 192.0.2.1/24 up
1138	jexec rtr ifconfig ${epair_wan_one}a inet6 -ifdisabled
1139	jexec rtr route add default -inet6 fe80::1%${epair_wan_one}a
1140	#jexec rtr route add default 192.0.2.2
1141
1142	vnet_mkjail wan_one ${epair_wan_one}b ${epair_wan_two}a
1143	jexec wan_one ifconfig ${epair_wan_one}b 192.0.2.2/24 up
1144	jexec wan_one ifconfig ${epair_wan_one}b inet6 fe80::1/64
1145	jexec wan_one ifconfig ${epair_wan_two}a 198.51.100.2/24 up
1146	jexec wan_one route add default 192.0.2.1
1147	jexec wan_one sysctl net.inet.ip.forwarding=1
1148
1149	vnet_mkjail wan_two ${epair_wan_two}b
1150	jexec wan_two ifconfig ${epair_wan_two}b 198.51.100.1/24 up
1151	jexec wan_two route add default 198.51.100.2
1152
1153	# Sanity checks
1154	atf_check -s exit:0 -o ignore \
1155	    ping6 -c 1 2001:db8::1
1156	atf_check -s exit:0 -o ignore \
1157	    jexec rtr ping -c 1 192.0.2.2
1158	atf_check -s exit:0 -o ignore \
1159	    jexec rtr ping -c 1 198.51.100.1
1160
1161	jexec rtr pfctl -e
1162	pft_set_rules rtr \
1163	    "set reassemble yes" \
1164	    "set state-policy if-bound" \
1165	    "block" \
1166	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1167	    "pass in on ${epair_lan}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_wan_one}a)"
1168
1169	atf_check -s exit:0 -o ignore \
1170	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
1171	    ping6 -c 3 64:ff9b::192.0.2.2
1172	atf_check -s exit:0 -o ignore \
1173	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
1174	    ping6 -c 3 64:ff9b::198.51.100.1
1175}
1176
1177v6_gateway_cleanup()
1178{
1179	pft_cleanup
1180}
1181
1182atf_init_test_cases()
1183{
1184	atf_add_test_case "icmp_echo_in"
1185	atf_add_test_case "icmp_echo_out"
1186	atf_add_test_case "fragmentation_in"
1187	atf_add_test_case "fragmentation_out"
1188	atf_add_test_case "tcp_in_if_bound"
1189	atf_add_test_case "tcp_out_if_bound"
1190	atf_add_test_case "tcp_in_floating"
1191	atf_add_test_case "tcp_out_floating"
1192	atf_add_test_case "udp_in"
1193	atf_add_test_case "udp_out"
1194	atf_add_test_case "sctp_in"
1195	atf_add_test_case "sctp_out"
1196	atf_add_test_case "tos"
1197	atf_add_test_case "no_v4"
1198	atf_add_test_case "range"
1199	atf_add_test_case "pool"
1200	atf_add_test_case "table"
1201	atf_add_test_case "table_range"
1202	atf_add_test_case "table_round_robin"
1203	atf_add_test_case "table_random"
1204	atf_add_test_case "dummynet"
1205	atf_add_test_case "gateway6"
1206	atf_add_test_case "route_to"
1207	atf_add_test_case "reply_to"
1208	atf_add_test_case "v6_gateway"
1209}
1210