xref: /freebsd/tests/sys/netpfil/pf/nat64.sh (revision f1ddb6fb8c4d051a205dae3a848776c9d56f86ff)
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()
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	pft_set_rules rtr \
55	    "set reassemble yes" \
56	    "set state-policy if-bound" \
57	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
58}
59
60atf_test_case "icmp_echo" "cleanup"
61icmp_echo_head()
62{
63	atf_set descr 'Basic NAT64 ICMP echo test'
64	atf_set require.user root
65}
66
67icmp_echo_body()
68{
69	nat64_setup
70
71	# One ping
72	atf_check -s exit:0 -o ignore \
73	    ping6 -c 1 64:ff9b::192.0.2.2
74
75	# Make sure packets make it even when state is established
76	atf_check -s exit:0 \
77	    -o match:'5 packets transmitted, 5 packets received, 0.0% packet loss' \
78	    ping6 -c 5 64:ff9b::192.0.2.2
79}
80
81icmp_echo_cleanup()
82{
83	pft_cleanup
84}
85
86atf_test_case "fragmentation" "cleanup"
87fragmentation_head()
88{
89	atf_set descr 'Test fragmented packets'
90	atf_set require.user root
91}
92
93fragmentation_body()
94{
95	nat64_setup
96
97	atf_check -s exit:0 -o ignore \
98	    ping6 -c 1 -s 1280 64:ff9b::192.0.2.2
99
100	atf_check -s exit:0 \
101	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
102	    ping6 -c 3 -s 2000 64:ff9b::192.0.2.2
103	atf_check -s exit:0 \
104	    -o match:'3 packets transmitted, 3 packets received, 0.0% packet loss' \
105	    ping6 -c 3 -s 10000 -b 20000 64:ff9b::192.0.2.2
106}
107
108fragmentation_cleanup()
109{
110	pft_cleanup
111}
112
113atf_test_case "tcp" "cleanup"
114tcp_head()
115{
116	atf_set descr 'TCP NAT64 test'
117	atf_set require.user root
118}
119
120tcp_body()
121{
122	nat64_setup
123
124	echo "foo" | jexec dst nc -l 1234 &
125
126	# Sanity check & delay for nc startup
127	atf_check -s exit:0 -o ignore \
128	    ping6 -c 1 64:ff9b::192.0.2.2
129
130	rcv=$(nc -w 3 -6 64:ff9b::c000:202 1234)
131	if [ "${rcv}" != "foo" ];
132	then
133		echo "rcv=${rcv}"
134		atf_fail "Failed to connect to TCP server"
135	fi
136}
137
138tcp_cleanup()
139{
140	pft_cleanup
141}
142
143atf_test_case "udp" "cleanup"
144udp_head()
145{
146	atf_set descr 'UDP NAT64 test'
147	atf_set require.user root
148}
149
150udp_body()
151{
152	nat64_setup
153
154	echo "foo" | jexec dst nc -u -l 1234 &
155
156	# Sanity check & delay for nc startup
157	atf_check -s exit:0 -o ignore \
158	    ping6 -c 1 64:ff9b::192.0.2.2
159
160	rcv=$(echo bar | nc -w 3 -6 -u 64:ff9b::c000:202 1234)
161	if [ "${rcv}" != "foo" ];
162	then
163		echo "rcv=${rcv}"
164		atf_fail "Failed to connect to UDP server"
165	fi
166}
167
168udp_cleanup()
169{
170	pft_cleanup
171}
172
173atf_test_case "sctp" "cleanup"
174sctp_head()
175{
176	atf_set descr 'SCTP NAT64 test'
177	atf_set require.user root
178}
179
180sctp_body()
181{
182	nat64_setup
183	if ! kldstat -q -m sctp; then
184		atf_skip "This test requires SCTP"
185	fi
186
187	echo "foo" | jexec dst nc --sctp -N -l 1234 &
188
189	# Sanity check & delay for nc startup
190	atf_check -s exit:0 -o ignore \
191	    ping6 -c 1 64:ff9b::192.0.2.2
192
193	rcv=$(echo bar | nc --sctp -w 3 -6 64:ff9b::c000:202 1234)
194	if [ "${rcv}" != "foo" ];
195	then
196		echo "rcv=${rcv}"
197		atf_fail "Failed to connect to SCTP server"
198	fi
199}
200
201sctp_cleanup()
202{
203	pft_cleanup
204}
205
206atf_test_case "tos" "cleanup"
207tos_head()
208{
209	atf_set descr 'ToS translation test'
210	atf_set require.user root
211}
212
213tos_body()
214{
215	nat64_setup
216
217	# Ensure we can distinguish ToS on the destination
218	jexec dst pfctl -e
219	pft_set_rules dst \
220	    "pass" \
221	    "block in inet tos 8"
222
223	atf_check -s exit:0 -o ignore \
224	    ping6 -c 1 -z 4 64:ff9b::192.0.2.2
225	atf_check -s exit:2 -o ignore \
226	    ping6 -c 1 -z 8 64:ff9b::192.0.2.2
227	atf_check -s exit:0 -o ignore \
228	    ping6 -c 1 -z 16 64:ff9b::192.0.2.2
229
230	jexec dst pfctl -sr -vv
231}
232
233tos_cleanup()
234{
235	pft_cleanup
236}
237
238atf_test_case "no_v4" "cleanup"
239no_v4_head()
240{
241	atf_set descr 'Test error handling when there is no IPv4 address to translate to'
242	atf_set require.user root
243}
244
245no_v4_body()
246{
247	pft_init
248
249	epair_link=$(vnet_mkepair)
250	epair=$(vnet_mkepair)
251
252	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
253	route -6 add default 2001:db8::1
254
255	vnet_mkjail rtr ${epair}b ${epair_link}a
256	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
257
258	vnet_mkjail dst ${epair_link}b
259	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
260	jexec dst route add default 192.0.2.1
261
262	# Sanity check
263	atf_check -s exit:0 -o ignore \
264	    ping6 -c 1 2001:db8::1
265
266	jexec rtr pfctl -e
267	pft_set_rules rtr \
268	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from (${epair_link}a)"
269
270	atf_check -s exit:2 -o ignore \
271	    ping6 -c 3 64:ff9b::192.0.2.2
272}
273
274no_v4_cleanup()
275{
276	pft_cleanup
277}
278
279atf_test_case "range" "cleanup"
280range_head()
281{
282	atf_set descr 'Test using an address range for the IPv4 side'
283	atf_set require.user root
284}
285
286range_body()
287{
288	pft_init
289
290	epair_link=$(vnet_mkepair)
291	epair=$(vnet_mkepair)
292
293	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
294	route -6 add default 2001:db8::1
295
296	vnet_mkjail rtr ${epair}b ${epair_link}a
297	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
298	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
299	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
300
301	vnet_mkjail dst ${epair_link}b
302	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
303	jexec dst route add default 192.0.2.2
304
305	# Sanity checks
306	atf_check -s exit:0 -o ignore \
307	    jexec rtr ping -c 1 192.0.2.254
308	atf_check -s exit:0 -o ignore \
309	    ping6 -c 1 2001:db8::1
310	atf_check -s exit:0 -o ignore \
311	    jexec dst ping -c 1 192.0.2.2
312	atf_check -s exit:0 -o ignore \
313	    jexec dst ping -c 1 192.0.2.3
314
315	jexec rtr pfctl -e
316	pft_set_rules rtr \
317	    "set reassemble yes" \
318	    "set state-policy if-bound" \
319	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from 192.0.2.2/31 round-robin"
320
321	# Use pf to count sources
322	jexec dst pfctl -e
323	pft_set_rules dst \
324	    "pass"
325
326	atf_check -s exit:0 -o ignore \
327	    ping6 -c 1 64:ff9b::192.0.2.254
328	atf_check -s exit:0 -o ignore \
329	    ping6 -c 1 64:ff9b::192.0.2.254
330
331	# Verify on dst that we saw different source addresses
332	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
333	    jexec dst pfctl -ss
334	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
335	    jexec dst pfctl -ss
336}
337
338range_cleanup()
339{
340	pft_cleanup
341}
342
343atf_test_case "pool" "cleanup"
344pool_head()
345{
346	atf_set descr 'Use a pool of IPv4 addresses'
347	atf_set require.user root
348}
349
350pool_body()
351{
352	pft_init
353
354	epair_link=$(vnet_mkepair)
355	epair=$(vnet_mkepair)
356
357	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
358	route -6 add default 2001:db8::1
359
360	vnet_mkjail rtr ${epair}b ${epair_link}a
361	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
362	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
363	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
364	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
365
366	vnet_mkjail dst ${epair_link}b
367	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
368	jexec dst route add default 192.0.2.1
369
370	# Sanity checks
371	atf_check -s exit:0 -o ignore \
372	    ping6 -c 1 2001:db8::1
373	atf_check -s exit:0 -o ignore \
374	    jexec dst ping -c 1 192.0.2.1
375
376	jexec rtr pfctl -e
377	pft_set_rules rtr \
378	    "set reassemble yes" \
379	    "set state-policy if-bound" \
380	    "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"
381
382	# Use pf to count sources
383	jexec dst pfctl -e
384	pft_set_rules dst \
385	    "pass"
386
387	atf_check -s exit:0 -o ignore \
388	    ping6 -c 1 64:ff9b::192.0.2.2
389	atf_check -s exit:0 -o ignore \
390	    ping6 -c 1 64:ff9b::192.0.2.2
391	atf_check -s exit:0 -o ignore \
392	    ping6 -c 1 64:ff9b::192.0.2.2
393
394	# Verify on dst that we saw different source addresses
395	atf_check -s exit:0 -o match:".*192.0.2.1.*" \
396	    jexec dst pfctl -ss
397	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
398	    jexec dst pfctl -ss
399	atf_check -s exit:0 -o match:".*192.0.2.4.*" \
400	    jexec dst pfctl -ss
401}
402
403pool_cleanup()
404{
405	pft_cleanup
406}
407
408
409atf_test_case "table"
410table_head()
411{
412	atf_set descr 'Tables require round-robin'
413	atf_set require.user root
414}
415
416table_body()
417{
418	pft_init
419
420	echo "pass in on epair inet6 from any to 64:ff9b::/96 af-to inet from <wanaddr>" | \
421	    atf_check -s exit:1 \
422	    -e match:"tables are only supported in round-robin pools" \
423	    pfctl -f -
424}
425
426table_cleanup()
427{
428	pft_cleanup
429}
430
431atf_test_case "table_range" "cleanup"
432table_range_head()
433{
434	atf_set descr 'Test using an address range within a table for the IPv4 side'
435	atf_set require.user root
436}
437
438table_range_body()
439{
440	pft_init
441
442	epair_link=$(vnet_mkepair)
443	epair=$(vnet_mkepair)
444
445	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
446	route -6 add default 2001:db8::1
447
448	vnet_mkjail rtr ${epair}b ${epair_link}a
449	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
450	jexec rtr ifconfig ${epair_link}a 192.0.2.2/24 up
451	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
452
453	vnet_mkjail dst ${epair_link}b
454	jexec dst ifconfig ${epair_link}b 192.0.2.254/24 up
455	jexec dst route add default 192.0.2.2
456
457	# Sanity checks
458	atf_check -s exit:0 -o ignore \
459	    ping6 -c 1 2001:db8::1
460	atf_check -s exit:0 -o ignore \
461	    jexec dst ping -c 1 192.0.2.2
462
463	jexec rtr pfctl -e
464	pft_set_rules rtr \
465	    "set reassemble yes" \
466	    "set state-policy if-bound" \
467	    "table <wanaddrs> { 192.0.2.2/31 }" \
468	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin"
469
470	# Use pf to count sources
471	jexec dst pfctl -e
472	pft_set_rules dst \
473	    "pass"
474
475	atf_check -s exit:0 -o ignore \
476	    ping6 -c 1 64:ff9b::192.0.2.254
477	atf_check -s exit:0 -o ignore \
478	    ping6 -c 1 64:ff9b::192.0.2.254
479
480	# Verify on dst that we saw different source addresses
481	atf_check -s exit:0 -o match:".*192.0.2.2.*" \
482	    jexec dst pfctl -ss
483	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
484	    jexec dst pfctl -ss
485}
486
487table_range_cleanup()
488{
489	pft_cleanup
490}
491
492atf_test_case "table_round_robin" "cleanup"
493table_round_robin_head()
494{
495	atf_set descr 'Use a table of IPv4 addresses in round-robin mode'
496	atf_set require.user root
497}
498
499table_round_robin_body()
500{
501	pft_init
502
503	epair_link=$(vnet_mkepair)
504	epair=$(vnet_mkepair)
505
506	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
507	route -6 add default 2001:db8::1
508
509	vnet_mkjail rtr ${epair}b ${epair_link}a
510	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
511	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
512	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.3/24 up
513	jexec rtr ifconfig ${epair_link}a inet alias 192.0.2.4/24 up
514
515	vnet_mkjail dst ${epair_link}b
516	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
517	jexec dst route add default 192.0.2.1
518
519	# Sanity checks
520	atf_check -s exit:0 -o ignore \
521	    ping6 -c 1 2001:db8::1
522	atf_check -s exit:0 -o ignore \
523	    jexec dst ping -c 1 192.0.2.1
524
525	jexec rtr pfctl -e
526	pft_set_rules rtr \
527	    "set reassemble yes" \
528	    "set state-policy if-bound" \
529	    "table <wanaddrs> { 192.0.2.1, 192.0.2.3, 192.0.2.4 }" \
530	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 af-to inet from <wanaddrs> round-robin"
531
532	# Use pf to count sources
533	jexec dst pfctl -e
534	pft_set_rules dst \
535	    "pass"
536
537	atf_check -s exit:0 -o ignore \
538	    ping6 -c 1 64:ff9b::192.0.2.2
539	atf_check -s exit:0 -o ignore \
540	    ping6 -c 1 64:ff9b::192.0.2.2
541	atf_check -s exit:0 -o ignore \
542	    ping6 -c 1 64:ff9b::192.0.2.2
543
544	# Verify on dst that we saw different source addresses
545	atf_check -s exit:0 -o match:".*192.0.2.1.*" \
546	    jexec dst pfctl -ss
547	atf_check -s exit:0 -o match:".*192.0.2.3.*" \
548	    jexec dst pfctl -ss
549	atf_check -s exit:0 -o match:".*192.0.2.4.*" \
550	    jexec dst pfctl -ss
551}
552
553table_round_robin_cleanup()
554{
555	pft_cleanup
556}
557
558atf_test_case "dummynet" "cleanup"
559dummynet_head()
560{
561	atf_set descr 'Test dummynet on af-to rules'
562	atf_set require.user root
563}
564
565dummynet_body()
566{
567	pft_init
568	dummynet_init
569
570	epair_link=$(vnet_mkepair)
571	epair=$(vnet_mkepair)
572
573	ifconfig ${epair}a inet6 2001:db8::2/64 up no_dad
574	route -6 add default 2001:db8::1
575
576	vnet_mkjail rtr ${epair}b ${epair_link}a
577	jexec rtr ifconfig ${epair}b inet6 2001:db8::1/64 up no_dad
578	jexec rtr ifconfig ${epair_link}a 192.0.2.1/24 up
579
580	vnet_mkjail dst ${epair_link}b
581	jexec dst ifconfig ${epair_link}b 192.0.2.2/24 up
582	jexec dst route add default 192.0.2.1
583
584	# Sanity checks
585	atf_check -s exit:0 -o ignore \
586	    ping6 -c 1 2001:db8::1
587	atf_check -s exit:0 -o ignore \
588	    jexec dst ping -c 1 192.0.2.1
589
590	jexec rtr pfctl -e
591	jexec rtr dnctl pipe 1 config delay 600
592	pft_set_rules rtr \
593	    "set reassemble yes" \
594	    "set state-policy if-bound" \
595	    "pass in on ${epair}b inet6 from any to 64:ff9b::/96 dnpipe 1 af-to inet from (${epair_link}a)"
596
597	# The ping request will pass, but take 1.2 seconds (.6 in, .6 out)
598	# So this works:
599	atf_check -s exit:0 -o ignore \
600	    ping6 -c 1 -t 2 64:ff9b::192.0.2.2
601
602	# But this times out:
603	atf_check -s exit:2 -o ignore \
604	    ping6 -c 1 -t 1 64:ff9b::192.0.2.2
605}
606
607dummynet_cleanup()
608{
609	pft_cleanup
610}
611
612atf_init_test_cases()
613{
614	atf_add_test_case "icmp_echo"
615	atf_add_test_case "fragmentation"
616	atf_add_test_case "tcp"
617	atf_add_test_case "udp"
618	atf_add_test_case "sctp"
619	atf_add_test_case "tos"
620	atf_add_test_case "no_v4"
621	atf_add_test_case "range"
622	atf_add_test_case "pool"
623	atf_add_test_case "table"
624	atf_add_test_case "table_range"
625	atf_add_test_case "table_round_robin"
626	atf_add_test_case "dummynet"
627}
628