xref: /freebsd/tests/sys/netpfil/pf/nat64.sh (revision 656f7f43f204ad1e6956f8257f66b50e032a6c61)
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	# Interfaces of the state are reversed when doing inbound NAT64!
217	# FIXME: Packets counters seem wrong!
218	states=$(mktemp) || exit 1
219	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
220	for state_regexp in \
221		"${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\]\) .* 9:9 pkts.* rule 3 .* origif: ${epair}b" \
222	; do
223		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
224	done
225	[ $(cat $states | grep tcp | wc -l) -eq 1 ] || atf_fail "Not exactly 1 state found!"
226}
227
228tcp_in_if_bound_cleanup()
229{
230	pft_cleanup
231}
232
233atf_test_case "tcp_out_if_bound" "cleanup"
234tcp_out_if_bound_head()
235{
236	atf_set descr 'TCP NAT64 test on outbound interface, if-bound states'
237	atf_set require.user root
238}
239
240tcp_out_if_bound_body()
241{
242	nat64_setup_out
243
244	echo "foo" | jexec dst nc -l 1234 &
245
246	# Sanity check & delay for nc startup
247	atf_check -s exit:0 -o ignore \
248	    ping6 -c 3 64:ff9b::192.0.2.2
249
250	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
251	if [ "${rcv}" != "foo" ];
252	then
253		echo "rcv=${rcv}"
254		atf_fail "Failed to connect to TCP server"
255	fi
256
257	# Origif is not printed when identical as if.
258	states=$(mktemp) || exit 1
259	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
260	for state_regexp in \
261		"${epair}b tcp 64:ff9b::c000:202\[1234\] <- 2001:db8::2\[[0-9]+\] .* 5:4 pkts.* rule 3 .*creatorid" \
262		"${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" \
263	; do
264		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
265	done
266	[ $(cat $states | grep tcp | wc -l) -eq 2 ] || atf_fail "Not exactly 2 states found!"
267}
268
269tcp_out_if_bound_cleanup()
270{
271	pft_cleanup
272}
273
274atf_test_case "tcp_in_floating" "cleanup"
275tcp_in_floating_head()
276{
277	atf_set descr 'TCP NAT64 test on inbound interface, floating states'
278	atf_set require.user root
279}
280
281tcp_in_floating_body()
282{
283	nat64_setup_in "floating"
284
285	echo "foo" | jexec dst nc -l 1234 &
286
287	# Sanity check & delay for nc startup
288	atf_check -s exit:0 -o ignore \
289	    ping6 -c 3 64:ff9b::192.0.2.2
290
291	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
292	if [ "${rcv}" != "foo" ];
293	then
294		echo "rcv=${rcv}"
295		atf_fail "Failed to connect to TCP server"
296	fi
297
298	# Interfaces of the state are reversed when doing inbound NAT64!
299	# FIXME: Packets counters seem wrong!
300	states=$(mktemp) || exit 1
301	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
302	for state_regexp in \
303		"all tcp 192.0.2.1:[0-9]+ \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:1234 \(64:ff9b::c000:202\[1234\]\).* 9:9 pkts.* rule 3 .* origif: ${epair}b" \
304	; do
305		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
306	done
307	[ $(cat $states | grep tcp | wc -l) -eq 1 ] || atf_fail "Not exactly 1 state found!"
308}
309
310tcp_in_floating_cleanup()
311{
312	pft_cleanup
313}
314
315atf_test_case "tcp_out_floating" "cleanup"
316tcp_out_floating_head()
317{
318	atf_set descr 'TCP NAT64 test on outbound interface, floating states'
319	atf_set require.user root
320}
321
322tcp_out_floating_body()
323{
324	nat64_setup_out "floating"
325
326	echo "foo" | jexec dst nc -l 1234 &
327
328	# Sanity check & delay for nc startup
329	atf_check -s exit:0 -o ignore \
330	    ping6 -c 3 64:ff9b::192.0.2.2
331
332	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
333	if [ "${rcv}" != "foo" ];
334	then
335		echo "rcv=${rcv}"
336		atf_fail "Failed to connect to TCP server"
337	fi
338
339	# Origif is not printed when identical as if.
340	states=$(mktemp) || exit 1
341	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
342	for state_regexp in \
343		"all tcp 64:ff9b::c000:202\[1234\] <- 2001:db8::2\[[0-9]+\] .* 5:4 pkts,.* rule 3 .*creatorid"\
344		"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"\
345	; do
346		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
347	done
348	[ $(cat $states | grep tcp | wc -l) -eq 2 ] || atf_fail "Not exactly 2 states found!"
349}
350
351tcp_out_floating_cleanup()
352{
353	pft_cleanup
354}
355
356atf_test_case "udp_in" "cleanup"
357udp_in_head()
358{
359	atf_set descr 'UDP NAT64 test on inbound interface'
360	atf_set require.user root
361}
362
363udp_in_body()
364{
365	nat64_setup_in
366
367	echo "foo" | jexec dst nc -u -l 1234 &
368
369	# Sanity check & delay for nc startup
370	atf_check -s exit:0 -o ignore \
371	    ping6 -c 3 64:ff9b::192.0.2.2
372
373	rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234)
374	if [ "${rcv}" != "foo" ];
375	then
376		echo "rcv=${rcv}"
377		atf_fail "Failed to connect to UDP server"
378	fi
379}
380
381udp_in_cleanup()
382{
383	pft_cleanup
384}
385
386atf_test_case "udp_out" "cleanup"
387udp_out_head()
388{
389	atf_set descr 'UDP NAT64 test on outbound interface'
390	atf_set require.user root
391}
392
393udp_out_body()
394{
395	nat64_setup_out
396
397	echo "foo" | jexec dst nc -u -l 1234 &
398
399	# Sanity check & delay for nc startup
400	atf_check -s exit:0 -o ignore \
401	    ping6 -c 3 64:ff9b::192.0.2.2
402
403	rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234)
404	if [ "${rcv}" != "foo" ];
405	then
406		echo "rcv=${rcv}"
407		atf_fail "Failed to connect to UDP server"
408	fi
409}
410
411udp_out_cleanup()
412{
413	pft_cleanup
414}
415
416atf_test_case "sctp_in" "cleanup"
417sctp_in_head()
418{
419	atf_set descr 'SCTP NAT64 test on inbound interface'
420	atf_set require.user root
421}
422
423sctp_in_body()
424{
425	nat64_setup_in
426	if ! kldstat -q -m sctp; then
427		atf_skip "This test requires SCTP"
428	fi
429
430	echo "foo" | jexec dst nc --sctp -N -l 1234 &
431
432	# Sanity check & delay for nc startup
433	atf_check -s exit:0 -o ignore \
434	    ping6 -c 3 64:ff9b::192.0.2.2
435
436	rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234)
437	if [ "${rcv}" != "foo" ];
438	then
439		echo "rcv=${rcv}"
440		atf_fail "Failed to connect to SCTP server"
441	fi
442}
443
444sctp_in_cleanup()
445{
446	pft_cleanup
447}
448
449atf_test_case "sctp_out" "cleanup"
450sctp_out_head()
451{
452	atf_set descr 'SCTP NAT64 test on outbound interface'
453	atf_set require.user root
454}
455
456sctp_out_body()
457{
458	nat64_setup_out
459	if ! kldstat -q -m sctp; then
460		atf_skip "This test requires SCTP"
461	fi
462
463	echo "foo" | jexec dst nc --sctp -N -l 1234 &
464
465	# Sanity check & delay for nc startup
466	atf_check -s exit:0 -o ignore \
467	    ping6 -c 3 64:ff9b::192.0.2.2
468
469	rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234)
470	if [ "${rcv}" != "foo" ];
471	then
472		echo "rcv=${rcv}"
473		atf_fail "Failed to connect to SCTP server"
474	fi
475}
476
477sctp_out_cleanup()
478{
479	pft_cleanup
480}
481
482atf_test_case "tos" "cleanup"
483tos_head()
484{
485	atf_set descr 'ToS translation test'
486	atf_set require.user root
487}
488
489tos_body()
490{
491	nat64_setup_in
492
493	# Ensure we can distinguish ToS on the destination
494	jexec dst pfctl -e
495	pft_set_rules dst \
496	    "pass" \
497	    "block in inet tos 8"
498
499	atf_check -s exit:0 -o ignore \
500	    ping6 -c 1 -z 4 64:ff9b::192.0.2.2
501	atf_check -s exit:2 -o ignore \
502	    ping6 -c 1 -z 8 64:ff9b::192.0.2.2
503	atf_check -s exit:0 -o ignore \
504	    ping6 -c 1 -z 16 64:ff9b::192.0.2.2
505
506	jexec dst pfctl -sr -vv
507}
508
509tos_cleanup()
510{
511	pft_cleanup
512}
513
514atf_test_case "no_v4" "cleanup"
515no_v4_head()
516{
517	atf_set descr 'Test error handling when there is no IPv4 address to translate to'
518	atf_set require.user root
519}
520
521no_v4_body()
522{
523	pft_init
524
525	epair_link=$(vnet_mkepair)
526	epair=$(vnet_mkepair)
527
528	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
529	route -6 add default 2001:db8::1
530
531	vnet_mkjail rtr ${epair}b ${epair_link}a
532	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
533
534	vnet_mkjail dst ${epair_link}b
535	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
536	jexec dst route add default 192.0.2.1
537
538	# Sanity check
539	atf_check -s exit:0 -o ignore \
540	    ping6 -c 1 2001:db8::1
541
542	jexec rtr pfctl -e
543	pft_set_rules rtr \
544	    "block" \
545	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
546	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)" \
547
548	atf_check -s exit:2 -o ignore \
549	    ping6 -c 3 64:ff9b::192.0.2.2
550}
551
552no_v4_cleanup()
553{
554	pft_cleanup
555}
556
557atf_test_case "range" "cleanup"
558range_head()
559{
560	atf_set descr 'Test using an address range for the IPv4 side'
561	atf_set require.user root
562}
563
564range_body()
565{
566	pft_init
567
568	epair_link=$(vnet_mkepair)
569	epair=$(vnet_mkepair)
570
571	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
572	route -6 add default 2001:db8::1
573
574	vnet_mkjail rtr ${epair}b ${epair_link}a
575	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
576	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
577	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
578
579	vnet_mkjail dst ${epair_link}b
580	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
581	jexec dst route add default 192.0.2.2
582
583	# Sanity checks
584	atf_check -s exit:0 -o ignore \
585	    jexec rtr ping -c 1 192.0.2.254
586	atf_check -s exit:0 -o ignore \
587	    ping6 -c 1 2001:db8::1
588	atf_check -s exit:0 -o ignore \
589	    jexec dst ping -c 1 192.0.2.2
590	atf_check -s exit:0 -o ignore \
591	    jexec dst ping -c 1 192.0.2.3
592
593	jexec rtr pfctl -e
594	pft_set_rules rtr \
595	    "set reassemble yes" \
596	    "set state-policy if-bound" \
597	    "block" \
598	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
599	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.2/31 round-robin" \
600
601	# Use pf to count sources
602	jexec dst pfctl -e
603	pft_set_rules dst \
604	    "pass"
605
606	atf_check -s exit:0 -o ignore \
607	    ping6 -c 1 64:ff9b::192.0.2.254
608	atf_check -s exit:0 -o ignore \
609	    ping6 -c 1 64:ff9b::192.0.2.254
610
611	# Verify on dst that we saw different source addresses
612	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
613	    jexec dst pfctl -ss
614	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
615	    jexec dst pfctl -ss
616}
617
618range_cleanup()
619{
620	pft_cleanup
621}
622
623atf_test_case "pool" "cleanup"
624pool_head()
625{
626	atf_set descr 'Use a pool of IPv4 addresses'
627	atf_set require.user root
628}
629
630pool_body()
631{
632	pft_init
633
634	epair_link=$(vnet_mkepair)
635	epair=$(vnet_mkepair)
636
637	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
638	route -6 add default 2001:db8::1
639
640	vnet_mkjail rtr ${epair}b ${epair_link}a
641	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
642	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
643	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
644	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
645
646	vnet_mkjail dst ${epair_link}b
647	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
648	jexec dst route add default 192.0.2.1
649
650	# Sanity checks
651	atf_check -s exit:0 -o ignore \
652	    ping6 -c 1 2001:db8::1
653	atf_check -s exit:0 -o ignore \
654	    jexec dst ping -c 1 192.0.2.1
655
656	jexec rtr pfctl -e
657	pft_set_rules rtr \
658	    "set reassemble yes" \
659	    "set state-policy if-bound" \
660	    "block" \
661	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
662	    "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"
663
664	# Use pf to count sources
665	jexec dst pfctl -e
666	pft_set_rules dst \
667	    "pass"
668
669	atf_check -s exit:0 -o ignore \
670	    ping6 -c 1 64:ff9b::192.0.2.2
671	atf_check -s exit:0 -o ignore \
672	    ping6 -c 1 64:ff9b::192.0.2.2
673	atf_check -s exit:0 -o ignore \
674	    ping6 -c 1 64:ff9b::192.0.2.2
675
676	# Verify on dst that we saw different source addresses
677	atf_check -s exit:0 -o match:".*192.0.2.1.*" \
678	    jexec dst pfctl -ss
679	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
680	    jexec dst pfctl -ss
681	atf_check -s exit:0 -o match:".*192.0.2.4.*" \
682	    jexec dst pfctl -ss
683}
684
685pool_cleanup()
686{
687	pft_cleanup
688}
689
690
691atf_test_case "table"
692table_head()
693{
694	atf_set descr 'Check table restrictions'
695	atf_set require.user root
696}
697
698table_body()
699{
700	pft_init
701
702	# Round-robin and random are allowed
703	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> round-robin" | \
704	    atf_check -s exit:0 \
705	    pfctl -f -
706	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> random" | \
707	    atf_check -s exit:0 \
708	    pfctl -f -
709
710	# bitmask is not
711	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr> bitmask" | \
712	    atf_check -s exit:1 \
713	    -e match:"tables are not supported by pool type" \
714	    pfctl -f -
715}
716
717table_cleanup()
718{
719	pft_cleanup
720}
721
722atf_test_case "table_range" "cleanup"
723table_range_head()
724{
725	atf_set descr 'Test using an address range within a table for the IPv4 side'
726	atf_set require.user root
727}
728
729table_range_body()
730{
731	pft_init
732
733	epair_link=$(vnet_mkepair)
734	epair=$(vnet_mkepair)
735
736	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
737	route -6 add default 2001:db8::1
738
739	vnet_mkjail rtr ${epair}b ${epair_link}a
740	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
741	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
742	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
743
744	vnet_mkjail dst ${epair_link}b
745	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
746	jexec dst route add default 192.0.2.2
747
748	# Sanity checks
749	atf_check -s exit:0 -o ignore \
750	    ping6 -c 1 2001:db8::1
751	atf_check -s exit:0 -o ignore \
752	    jexec dst ping -c 1 192.0.2.2
753
754	jexec rtr pfctl -e
755	pft_set_rules rtr \
756	    "set reassemble yes" \
757	    "set state-policy if-bound" \
758	    "table <wanaddrs> { 192.0.2.2/31 }" \
759	    "block" \
760	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
761	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin"
762
763	# Use pf to count sources
764	jexec dst pfctl -e
765	pft_set_rules dst \
766	    "pass"
767
768	atf_check -s exit:0 -o ignore \
769	    ping6 -c 1 64:ff9b::192.0.2.254
770	atf_check -s exit:0 -o ignore \
771	    ping6 -c 1 64:ff9b::192.0.2.254
772
773	# Verify on dst that we saw different source addresses
774	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
775	    jexec dst pfctl -ss
776	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
777	    jexec dst pfctl -ss
778}
779
780table_range_cleanup()
781{
782	pft_cleanup
783}
784
785table_common_body()
786{
787	pool_type=$1
788
789	pft_init
790
791	epair_link=$(vnet_mkepair)
792	epair=$(vnet_mkepair)
793
794	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
795	route -6 add default 2001:db8::1
796
797	vnet_mkjail rtr ${epair}b ${epair_link}a
798	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
799	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
800	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
801	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
802
803	vnet_mkjail dst ${epair_link}b
804	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
805	jexec dst route add default 192.0.2.1
806
807	# Sanity checks
808	atf_check -s exit:0 -o ignore \
809	    ping6 -c 1 2001:db8::1
810	atf_check -s exit:0 -o ignore \
811	    jexec dst ping -c 1 192.0.2.1
812
813	jexec rtr pfctl -e
814	pft_set_rules rtr \
815	    "set reassemble yes" \
816	    "set state-policy if-bound" \
817	    "table <wanaddrs> { 192.0.2.1, 192.0.2.3, 192.0.2.4 }" \
818	    "block" \
819	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
820	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> ${pool_type}"
821
822	# Use pf to count sources
823	jexec dst pfctl -e
824	pft_set_rules dst \
825	    "pass"
826
827	atf_check -s exit:0 -o ignore \
828	    ping6 -c 1 64:ff9b::192.0.2.2
829	atf_check -s exit:0 -o ignore \
830	    ping6 -c 1 64:ff9b::192.0.2.2
831	atf_check -s exit:0 -o ignore \
832	    ping6 -c 1 64:ff9b::192.0.2.2
833
834	# XXX We can't reasonably check pool type random because it's random. It may end
835	# up choosing the same source IP for all three connections.
836	if [ "${pool_type}" == "round-robin" ];
837	then
838		# Verify on dst that we saw different source addresses
839		atf_check -s exit:0 -o match:".*192.0.2.1.*" \
840		    jexec dst pfctl -ss
841		atf_check -s exit:0 -o match:".*192.0.2.3.*" \
842		    jexec dst pfctl -ss
843		atf_check -s exit:0 -o match:".*192.0.2.4.*" \
844		    jexec dst pfctl -ss
845	fi
846}
847
848atf_test_case "table_round_robin" "cleanup"
849table_round_robin_head()
850{
851	atf_set descr 'Use a table of IPv4 addresses in round-robin mode'
852	atf_set require.user root
853}
854
855table_round_robin_body()
856{
857	table_common_body round-robin
858}
859
860table_round_robin_cleanup()
861{
862	pft_cleanup
863}
864
865atf_test_case "table_random" "cleanup"
866table_random_head()
867{
868	atf_set descr 'Use a table of IPv4 addresses in random mode'
869	atf_set require.user root
870}
871
872table_random_body()
873{
874	table_common_body random
875}
876
877table_random_cleanup()
878{
879	pft_cleanup
880}
881
882atf_test_case "dummynet" "cleanup"
883dummynet_head()
884{
885	atf_set descr 'Test dummynet on af-to rules'
886	atf_set require.user root
887}
888
889dummynet_body()
890{
891	pft_init
892	dummynet_init
893
894	epair_link=$(vnet_mkepair)
895	epair=$(vnet_mkepair)
896
897	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
898	route -6 add default 2001:db8::1
899
900	vnet_mkjail rtr ${epair}b ${epair_link}a
901	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
902	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
903
904	vnet_mkjail dst ${epair_link}b
905	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
906	jexec dst route add default 192.0.2.1
907
908	# Sanity checks
909	atf_check -s exit:0 -o ignore \
910	    ping6 -c 1 2001:db8::1
911	atf_check -s exit:0 -o ignore \
912	    jexec dst ping -c 1 192.0.2.1
913
914	jexec rtr pfctl -e
915	jexec rtr dnctl pipe 1 config delay 600
916	pft_set_rules rtr \
917	    "set reassemble yes" \
918	    "set state-policy if-bound" \
919	    "block" \
920	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
921	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 dnpipe 1 af-to inet from (${epair_link}a)"
922
923	# The ping request will pass, but take 1.2 seconds (.6 in, .6 out)
924	# So this works:
925	atf_check -s exit:0 -o ignore \
926	    ping6 -c 1 -t 2 64:ff9b::192.0.2.2
927
928	# But this times out:
929	atf_check -s exit:2 -o ignore \
930	    ping6 -c 1 -t 1 64:ff9b::192.0.2.2
931}
932
933dummynet_cleanup()
934{
935	pft_cleanup
936}
937
938atf_test_case "gateway6" "cleanup"
939gateway6_head()
940{
941	atf_set descr 'NAT64 with a routing hop on the v6 side'
942	atf_set require.user root
943}
944
945gateway6_body()
946{
947	pft_init
948
949	epair_lan_link=$(vnet_mkepair)
950	epair_link=$(vnet_mkepair)
951	epair=$(vnet_mkepair)
952
953	ifconfig ${epair}a inet6 2001:db8:1::2/64 up no_dad
954	route -6 add default 2001:db8:1::1
955
956	vnet_mkjail lan_rtr ${epair}b ${epair_lan_link}a
957	jexec lan_rtr ifconfig ${epair}b inet6 2001:db8:1::1/64 up no_dad
958	jexec lan_rtr ifconfig ${epair_lan_link}a inet6 2001:db8::2/64 up no_dad
959	jexec lan_rtr route -6 add default 2001:db8::1
960	jexec lan_rtr sysctl net.inet6.ip6.forwarding=1
961
962	vnet_mkjail rtr ${epair_lan_link}b ${epair_link}a
963	jexec rtr ifconfig ${epair_lan_link}b inet6 2001:db8::1/64 up no_dad
964	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
965	jexec rtr route -6 add default 2001:db8::2
966
967	vnet_mkjail dst ${epair_link}b
968	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
969	jexec dst route add default 192.0.2.1
970
971	# Sanity checks
972	atf_check -s exit:0 -o ignore \
973	    ping6 -c 1 2001:db8:1::1
974	atf_check -s exit:0 -o ignore \
975	    ping6 -c 1 2001:db8::1
976	atf_check -s exit:0 -o ignore \
977	    jexec dst ping -c 1 192.0.2.1
978
979	jexec rtr pfctl -e
980	pft_set_rules rtr \
981	    "set reassemble yes" \
982	    "set state-policy if-bound" \
983	    "block" \
984	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
985	    "pass in on ${epair_lan_link}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
986
987	# One ping
988	atf_check -s exit:0 -o ignore \
989	    ping6 -c 1 64:ff9b::192.0.2.2
990
991	# Make sure packets make it even when state is established
992	atf_check -s exit:0 \
993	    -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \
994	    ping6 -c 5 64:ff9b::192.0.2.2
995}
996
997gateway6_cleanup()
998{
999	pft_cleanup
1000}
1001
1002atf_test_case "route_to" "cleanup"
1003route_to_head()
1004{
1005	atf_set descr 'Test route-to on af-to rules'
1006	atf_set require.user root
1007}
1008
1009route_to_body()
1010{
1011	pft_init
1012
1013	epair_link=$(vnet_mkepair)
1014	epair_null=$(vnet_mkepair)
1015	epair=$(vnet_mkepair)
1016
1017	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
1018	route -6 add default 2001:db8::1
1019
1020	vnet_mkjail rtr ${epair}b ${epair_link}a ${epair_null}a
1021	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
1022	jexec rtr ifconfig ${epair_null}a 192.0.2.3/24 up
1023	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
1024
1025	vnet_mkjail dst ${epair_link}b
1026	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
1027	jexec dst route add default 192.0.2.1
1028
1029	# Sanity checks
1030	atf_check -s exit:0 -o ignore \
1031	    ping6 -c 1 2001:db8::1
1032
1033	jexec rtr pfctl -e
1034	pft_set_rules rtr \
1035	    "set reassemble yes" \
1036	    "set state-policy if-bound" \
1037	    "block" \
1038	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1039	    "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)"
1040
1041	atf_check -s exit:0 -o ignore \
1042	    ping6 -c 3 64:ff9b::192.0.2.2
1043
1044	states=$(mktemp) || exit 1
1045	jexec rtr pfctl -qvvss | normalize_pfctl_s > $states
1046
1047	for state_regexp in \
1048		"${epair}b ipv6-icmp 192.0.2.1:.* \(2001:db8::2\[[0-9]+\]\) -> 192.0.2.2:8 \(64:ff9b::c000:202\[[0-9]+\]\).*4:2 pkts.*route-to: 192.0.2.2@${epair_link}a" \
1049	; do
1050		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
1051	done
1052}
1053
1054route_to_cleanup()
1055{
1056	pft_cleanup
1057}
1058
1059atf_test_case "reply_to" "cleanup"
1060reply_to_head()
1061{
1062	atf_set descr 'Test reply-to on af-to rules'
1063	atf_set require.user root
1064}
1065
1066reply_to_body()
1067{
1068	pft_init
1069
1070	epair_link=$(vnet_mkepair)
1071	epair=$(vnet_mkepair)
1072
1073	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
1074	route -6 add default 2001:db8::1
1075
1076	vnet_mkjail rtr ${epair}b ${epair_link}a
1077	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
1078	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
1079
1080	vnet_mkjail dst ${epair_link}b
1081	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
1082	jexec dst route add default 192.0.2.1
1083
1084	# Sanity checks
1085	atf_check -s exit:0 -o ignore \
1086	    ping6 -c 1 2001:db8::1
1087
1088	jexec rtr pfctl -e
1089	pft_set_rules rtr \
1090	    "set reassemble yes" \
1091	    "set state-policy if-bound" \
1092	    "block" \
1093	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1094	    "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"
1095
1096	atf_check -s exit:0 -o ignore \
1097	    ping6 -c 3 64:ff9b::192.0.2.2
1098}
1099
1100reply_to_cleanup()
1101{
1102	pft_cleanup
1103}
1104
1105atf_test_case "v6_gateway" "cleanup"
1106v6_gateway_head()
1107{
1108	atf_set descr 'nat64 when the IPv4 gateway is given by an IPv6 address'
1109	atf_set require.user root
1110}
1111
1112v6_gateway_body()
1113{
1114	pft_init
1115
1116	epair_wan_two=$(vnet_mkepair)
1117	epair_wan_one=$(vnet_mkepair)
1118	epair_lan=$(vnet_mkepair)
1119
1120	ifconfig ${epair_lan}a inet6 2001:db8::2/64 up no_dad
1121	route -6 add default 2001:db8::1
1122
1123	vnet_mkjail rtr ${epair_lan}b ${epair_wan_one}a
1124	jexec rtr ifconfig ${epair_lan}b inet6 2001:db8::1/64 up no_dad
1125	jexec rtr ifconfig ${epair_wan_one}a 192.0.2.1/24 up
1126	jexec rtr ifconfig ${epair_wan_one}a inet6 -ifdisabled
1127	jexec rtr route add default -inet6 fe80::1%${epair_wan_one}a
1128	#jexec rtr route add default 192.0.2.2
1129
1130	vnet_mkjail wan_one ${epair_wan_one}b ${epair_wan_two}a
1131	jexec wan_one ifconfig ${epair_wan_one}b 192.0.2.2/24 up
1132	jexec wan_one ifconfig ${epair_wan_one}b inet6 fe80::1/64
1133	jexec wan_one ifconfig ${epair_wan_two}a 198.51.100.2/24 up
1134	jexec wan_one route add default 192.0.2.1
1135	jexec wan_one sysctl net.inet.ip.forwarding=1
1136
1137	vnet_mkjail wan_two ${epair_wan_two}b
1138	jexec wan_two ifconfig ${epair_wan_two}b 198.51.100.1/24 up
1139	jexec wan_two route add default 198.51.100.2
1140
1141	# Sanity checks
1142	atf_check -s exit:0 -o ignore \
1143	    ping6 -c 1 2001:db8::1
1144	atf_check -s exit:0 -o ignore \
1145	    jexec rtr ping -c 1 192.0.2.2
1146	atf_check -s exit:0 -o ignore \
1147	    jexec rtr ping -c 1 198.51.100.1
1148
1149	jexec rtr pfctl -e
1150	pft_set_rules rtr \
1151	    "set reassemble yes" \
1152	    "set state-policy if-bound" \
1153	    "block" \
1154	    "pass inet6 proto icmp6 icmp6-type { neighbrsol, neighbradv }" \
1155	    "pass in on ${epair_lan}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_wan_one}a)"
1156
1157	atf_check -s exit:0 -o ignore \
1158	    ping6 -c 3 64:ff9b::192.0.2.2
1159	atf_check -s exit:0 -o ignore \
1160	    ping6 -c 3 64:ff9b::198.51.100.1
1161}
1162
1163v6_gateway_cleanup()
1164{
1165	pft_cleanup
1166}
1167
1168atf_init_test_cases()
1169{
1170	atf_add_test_case "icmp_echo_in"
1171	atf_add_test_case "icmp_echo_out"
1172	atf_add_test_case "fragmentation_in"
1173	atf_add_test_case "fragmentation_out"
1174	atf_add_test_case "tcp_in_if_bound"
1175	atf_add_test_case "tcp_out_if_bound"
1176	atf_add_test_case "tcp_in_floating"
1177	atf_add_test_case "tcp_out_floating"
1178	atf_add_test_case "udp_in"
1179	atf_add_test_case "udp_out"
1180	atf_add_test_case "sctp_in"
1181	atf_add_test_case "sctp_out"
1182	atf_add_test_case "tos"
1183	atf_add_test_case "no_v4"
1184	atf_add_test_case "range"
1185	atf_add_test_case "pool"
1186	atf_add_test_case "table"
1187	atf_add_test_case "table_range"
1188	atf_add_test_case "table_round_robin"
1189	atf_add_test_case "table_random"
1190	atf_add_test_case "dummynet"
1191	atf_add_test_case "gateway6"
1192	atf_add_test_case "route_to"
1193	atf_add_test_case "reply_to"
1194	atf_add_test_case "v6_gateway"
1195}
1196