xref: /freebsd/tests/sys/netpfil/pf/nat.sh (revision e0fe26691fc98a16cdda9d4f4beea9c5698ac64a)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2018 Kristof Provost <kp@FreeBSD.org>
5# Copyright (c) 2025 Kajetan Staszkiewicz <ks@FreeBSD.org>
6# Copyright (c) 2021 KUROSAWA Takahiro <takahiro.kurosawa@gmail.com>
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions
10# are met:
11# 1. Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13# 2. Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in the
15#    documentation and/or other materials provided with the distribution.
16#
17# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27# SUCH DAMAGE.
28
29. $(atf_get_srcdir)/utils.subr
30
31atf_test_case "exhaust" "cleanup"
32exhaust_head()
33{
34	atf_set descr 'Test exhausting the NAT pool'
35	atf_set require.user root
36}
37
38exhaust_body()
39{
40	pft_init
41
42	epair_nat=$(vnet_mkepair)
43	epair_echo=$(vnet_mkepair)
44
45	vnet_mkjail nat ${epair_nat}b ${epair_echo}a
46	vnet_mkjail echo ${epair_echo}b
47
48	ifconfig ${epair_nat}a 192.0.2.2/24 up
49	route add -net 198.51.100.0/24 192.0.2.1
50
51	jexec nat ifconfig ${epair_nat}b 192.0.2.1/24 up
52	jexec nat ifconfig ${epair_echo}a 198.51.100.1/24 up
53	jexec nat sysctl net.inet.ip.forwarding=1
54
55	jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
56	jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
57
58	# Enable pf!
59	jexec nat pfctl -e
60	pft_set_rules nat \
61		"nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) port 30000:30001 sticky-address"
62
63	# Sanity check
64	atf_check -s exit:0 -o ignore ping -c 3 198.51.100.2
65
66	atf_check -s exit:0 -o match:foo* echo "foo" | nc -N 198.51.100.2 7
67	atf_check -s exit:0 -o match:foo* echo "foo" | nc -N 198.51.100.2 7
68
69	# This one will fail, but that's expected
70	echo "foo" | nc -N 198.51.100.2 7 &
71
72	sleep 1
73
74	# If the kernel is stuck in pf_get_sport() this will not succeed either.
75	timeout 2 jexec nat pfctl -sa
76	if [ $? -eq 124 ]; then
77		# Timed out
78		atf_fail "pfctl timeout"
79	fi
80}
81
82exhaust_cleanup()
83{
84	pft_cleanup
85}
86
87atf_test_case "nested_anchor" "cleanup"
88nested_anchor_head()
89{
90	atf_set descr 'Test setting and retrieving nested nat anchors'
91	atf_set require.user root
92}
93
94nested_anchor_body()
95{
96	pft_init
97
98	epair=$(vnet_mkepair)
99
100	vnet_mkjail nat ${epair}a
101
102	pft_set_rules nat \
103		"nat-anchor \"foo\""
104
105	echo "nat-anchor \"bar\"" | jexec nat pfctl -g -a foo -f -
106	echo "nat on ${epair}a from any to any -> (${epair}a)" | jexec nat pfctl -g -a "foo/bar" -f -
107
108	atf_check -s exit:0 -o inline:"nat-anchor \"foo\" all {
109  nat-anchor \"bar\" all {
110    nat on ${epair}a all -> (${epair}a) round-robin
111  }
112}
113" jexec nat pfctl -sn -a "*"
114
115}
116
117endpoint_independent_setup()
118{
119	pft_init
120	filter="udp and dst port 1234"	# only capture udp pings
121
122	epair_client=$(vnet_mkepair)
123	epair_nat=$(vnet_mkepair)
124	epair_server1=$(vnet_mkepair)
125	epair_server2=$(vnet_mkepair)
126	bridge=$(vnet_mkbridge)
127
128	vnet_mkjail nat ${epair_client}b ${epair_nat}a
129	vnet_mkjail client ${epair_client}a
130	vnet_mkjail server1 ${epair_server1}a
131	vnet_mkjail server2 ${epair_server2}a
132
133	ifconfig ${epair_server1}b up
134	ifconfig ${epair_server2}b up
135	ifconfig ${epair_nat}b up
136	ifconfig ${bridge} \
137		addm ${epair_server1}b \
138		addm ${epair_server2}b \
139		addm ${epair_nat}b \
140		up
141
142	jexec nat ifconfig ${epair_client}b 192.0.2.1/24 up
143	jexec nat ifconfig ${epair_nat}a 198.51.100.42/24 up
144	jexec nat sysctl net.inet.ip.forwarding=1
145
146	jexec client ifconfig ${epair_client}a 192.0.2.2/24 up
147	jexec client route add default 192.0.2.1
148
149	jexec server1 ifconfig ${epair_server1}a 198.51.100.32/24 up
150	jexec server2 ifconfig ${epair_server2}a 198.51.100.22/24 up
151}
152
153endpoint_independent_common()
154{
155	# Enable pf!
156	jexec nat pfctl -e
157
158	# validate non-endpoint independent nat rule behaviour
159	pft_set_rules nat "${1}"
160
161	jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
162		--immediate-mode $filter &
163	server1tcppid="$!"
164	jexec server2 tcpdump -i ${epair_server2}a -w ${PWD}/server2.pcap \
165		--immediate-mode $filter &
166	server2tcppid="$!"
167
168	# send out multiple packets
169	for i in $(seq 1 10); do
170		echo "ping" | jexec client nc -u 198.51.100.32 1234 -p 4242 -w 0
171		echo "ping" | jexec client nc -u 198.51.100.22 1234 -p 4242 -w 0
172	done
173
174	kill $server1tcppid
175	kill $server2tcppid
176
177	tuple_server1=$(tcpdump -r ${PWD}/server1.pcap | awk '{addr=$3} END {print addr}')
178	tuple_server2=$(tcpdump -r ${PWD}/server2.pcap | awk '{addr=$3} END {print addr}')
179
180	if [ -z $tuple_server1 ]
181	then
182		atf_fail "server1 did not receive connection from client (default)"
183	fi
184
185	if [ -z $tuple_server2 ]
186	then
187		atf_fail "server2 did not receive connection from client (default)"
188	fi
189
190	if [ "$tuple_server1" = "$tuple_server2" ]
191	then
192		echo "server1 tcpdump: $tuple_server1"
193		echo "server2 tcpdump: $tuple_server2"
194		atf_fail "Received same IP:port on server1 and server2 (default)"
195	fi
196
197	# validate endpoint independent nat rule behaviour
198	pft_set_rules nat "${2}"
199
200	jexec server1 tcpdump -i ${epair_server1}a -w ${PWD}/server1.pcap \
201		--immediate-mode $filter &
202	server1tcppid="$!"
203	jexec server2 tcpdump -i ${epair_server2}a -w ${PWD}/server2.pcap \
204		--immediate-mode $filter &
205	server2tcppid="$!"
206
207	# send out multiple packets,  sometimes one fails to go through
208	for i in $(seq 1 10); do
209		echo "ping" | jexec client nc -u 198.51.100.32 1234 -p 4242 -w 0
210		echo "ping" | jexec client nc -u 198.51.100.22 1234 -p 4242 -w 0
211	done
212
213	kill $server1tcppid
214	kill $server2tcppid
215
216	tuple_server1=$(tcpdump -r ${PWD}/server1.pcap | awk '{addr=$3} END {print addr}')
217	tuple_server2=$(tcpdump -r ${PWD}/server2.pcap | awk '{addr=$3} END {print addr}')
218
219	if [ -z $tuple_server1 ]
220	then
221		atf_fail "server1 did not receive connection from client (endpoint-independent)"
222	fi
223
224	if [ -z $tuple_server2 ]
225	then
226		atf_fail "server2 did not receive connection from client (endpoint-independent)"
227	fi
228
229	if [ ! "$tuple_server1" = "$tuple_server2" ]
230	then
231		echo "server1 tcpdump: $tuple_server1"
232		echo "server2 tcpdump: $tuple_server2"
233		atf_fail "Received different IP:port on server1 than server2 (endpoint-independent)"
234	fi
235}
236
237atf_test_case "endpoint_independent_compat" "cleanup"
238endpoint_independent_compat_head()
239{
240	atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
241	atf_set require.user root
242}
243
244endpoint_independent_compat_body()
245{
246	endpoint_independent_setup # Sets ${epair_…} variables
247
248	endpoint_independent_common \
249		"nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a)" \
250		"nat on ${epair_nat}a inet from ! (${epair_nat}a) to any -> (${epair_nat}a) endpoint-independent"
251}
252
253endpoint_independent_compat_cleanup()
254{
255	pft_cleanup
256	rm -f server1.out
257	rm -f server2.out
258}
259
260atf_test_case "endpoint_independent_pass" "cleanup"
261endpoint_independent_pass_head()
262{
263	atf_set descr 'Test that a client behind NAT gets the same external IP:port for different servers'
264	atf_set require.user root
265}
266
267endpoint_independent_pass_body()
268{
269	endpoint_independent_setup # Sets ${epair_…} variables
270
271	endpoint_independent_common \
272		"pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) keep state" \
273		"pass out on ${epair_nat}a inet from ! (${epair_nat}a) to any nat-to (${epair_nat}a) endpoint-independent keep state"
274
275}
276
277endpoint_independent_pass_cleanup()
278{
279	pft_cleanup
280	rm -f server1.out
281	rm -f server2.out
282}
283
284nested_anchor_cleanup()
285{
286	pft_cleanup
287}
288
289atf_test_case "nat6_nolinklocal" "cleanup"
290nat6_nolinklocal_head()
291{
292	atf_set descr 'Ensure we do not use link-local addresses'
293	atf_set require.user root
294}
295
296nat6_nolinklocal_body()
297{
298	pft_init
299
300	epair_nat=$(vnet_mkepair)
301	epair_echo=$(vnet_mkepair)
302
303	vnet_mkjail nat ${epair_nat}b ${epair_echo}a
304	vnet_mkjail echo ${epair_echo}b
305
306	ifconfig ${epair_nat}a inet6 2001:db8::2/64 no_dad up
307	route add -6 -net 2001:db8:1::/64 2001:db8::1
308
309	jexec nat ifconfig ${epair_nat}b inet6 2001:db8::1/64 no_dad up
310	jexec nat ifconfig ${epair_echo}a inet6 2001:db8:1::1/64 no_dad up
311	jexec nat sysctl net.inet6.ip6.forwarding=1
312
313	jexec echo ifconfig ${epair_echo}b inet6 2001:db8:1::2/64 no_dad up
314	# Ensure we can't reply to link-local pings
315	jexec echo pfctl -e
316	pft_set_rules echo \
317	    "pass" \
318	    "block in inet6 proto icmp6 from fe80::/10 to any icmp6-type echoreq"
319
320	jexec nat pfctl -e
321	pft_set_rules nat \
322	    "nat pass on ${epair_echo}a inet6 from 2001:db8::/64 to any -> (${epair_echo}a)" \
323	    "pass"
324
325	# Sanity check
326	atf_check -s exit:0 -o ignore \
327	    ping -6 -c 1 2001:db8::1
328	for i in `seq 0 10`
329	do
330		atf_check -s exit:0 -o ignore \
331		    ping -6 -c 1 2001:db8:1::2
332	done
333}
334
335nat6_nolinklocal_cleanup()
336{
337	pft_cleanup
338}
339
340empty_table_common()
341{
342	option=$1
343
344	pft_init
345
346	epair_wan=$(vnet_mkepair)
347	epair_lan=$(vnet_mkepair)
348
349	vnet_mkjail srv ${epair_wan}a
350	jexec srv ifconfig ${epair_wan}a 192.0.2.2/24 up
351
352	vnet_mkjail rtr ${epair_wan}b ${epair_lan}a
353	jexec rtr ifconfig ${epair_wan}b 192.0.2.1/24 up
354	jexec rtr ifconfig ${epair_lan}a 198.51.100.1/24 up
355	jexec rtr sysctl net.inet.ip.forwarding=1
356
357	ifconfig ${epair_lan}b 198.51.100.2/24 up
358	route add default 198.51.100.1
359
360	jexec rtr pfctl -e
361	pft_set_rules rtr \
362	    "table <empty>" \
363	    "nat on ${epair_wan}b inet from 198.51.100.0/24 -> <empty> ${option}" \
364	    "pass"
365
366	# Sanity checks
367	atf_check -s exit:0 -o ignore \
368	    jexec rtr ping -c 1 192.0.2.2
369	atf_check -s exit:0 -o ignore \
370	    ping -c 1 198.51.100.1
371	atf_check -s exit:0 -o ignore \
372	    ping -c 1 192.0.2.1
373
374	# Provoke divide by zero
375	ping -c 1 192.0.2.2
376	true
377}
378
379atf_test_case "empty_table_source_hash" "cleanup"
380empty_table_source_hash_head()
381{
382	atf_set descr 'Test source-hash on an emtpy table'
383	atf_set require.user root
384}
385
386empty_table_source_hash_body()
387{
388	empty_table_common "source-hash"
389}
390
391empty_table_source_hash_cleanup()
392{
393	pft_cleanup
394}
395
396atf_test_case "empty_table_random" "cleanup"
397empty_table_random_head()
398{
399	atf_set descr 'Test random on an emtpy table'
400	atf_set require.user root
401}
402
403empty_table_random_body()
404{
405	empty_table_common "random"
406}
407
408empty_table_random_cleanup()
409{
410	pft_cleanup
411}
412
413no_addrs_common()
414{
415	option=$1
416
417	pft_init
418
419	epair_wan=$(vnet_mkepair)
420	epair_lan=$(vnet_mkepair)
421
422	vnet_mkjail srv ${epair_wan}a
423	jexec srv ifconfig ${epair_wan}a 192.0.2.2/24 up
424
425	vnet_mkjail rtr ${epair_wan}b ${epair_lan}a
426	jexec rtr route add -net 192.0.2.0/24 -iface ${epair_wan}b
427	jexec rtr ifconfig ${epair_lan}a 198.51.100.1/24 up
428	jexec rtr sysctl net.inet.ip.forwarding=1
429
430	ifconfig ${epair_lan}b 198.51.100.2/24 up
431	route add default 198.51.100.1
432
433	jexec rtr pfctl -e
434	pft_set_rules rtr \
435	    "nat on ${epair_wan}b inet from 198.51.100.0/24 -> (${epair_wan}b) ${option}" \
436	    "pass"
437
438	# Provoke divide by zero
439	ping -c 1 192.0.2.2
440	true
441}
442
443atf_test_case "no_addrs_source_hash" "cleanup"
444no_addrs_source_hash_head()
445{
446	atf_set descr 'Test source-hash on an interface with no addresses'
447	atf_set require.user root
448}
449
450no_addrs_source_hash_body()
451{
452	no_addrs_common "source-hash"
453}
454
455no_addrs_source_hash_cleanup()
456{
457	pft_cleanup
458}
459
460atf_test_case "no_addrs_random" "cleanup"
461no_addrs_random_head()
462{
463	atf_set descr 'Test random on an interface with no addresses'
464	atf_set require.user root
465}
466
467no_addrs_random_body()
468{
469	no_addrs_common "random"
470}
471
472no_addrs_random_cleanup()
473{
474	pft_cleanup
475}
476
477nat_pass_head()
478{
479	atf_set descr 'IPv4 NAT on pass rule'
480	atf_set require.user root
481	atf_set require.progs scapy
482}
483
484nat_pass_body()
485{
486	setup_router_server_ipv4
487	# Delete the route back to make sure that the traffic has been NAT-ed
488	jexec server route del -net ${net_tester} ${net_server_host_router}
489
490	pft_set_rules router \
491		"block" \
492		"pass in  on ${epair_tester}b inet proto tcp keep state" \
493		"pass out on ${epair_server}a inet proto tcp nat-to ${epair_server}a keep state"
494
495	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
496
497	jexec router pfctl -qvvsr
498	jexec router pfctl -qvvss
499	jexec router ifconfig
500	jexec router netstat -rn
501}
502
503nat_pass_cleanup()
504{
505	pft_cleanup
506}
507
508nat_match_head()
509{
510	atf_set descr 'IPv4 NAT on match rule'
511	atf_set require.user root
512	atf_set require.progs scapy
513}
514
515nat_match_body()
516{
517	setup_router_server_ipv4
518	# Delete the route back to make sure that the traffic has been NAT-ed
519	jexec server route del -net ${net_tester} ${net_server_host_router}
520
521	# NAT is applied during ruleset evaluation:
522	# rules after "match" match on NAT-ed address
523	pft_set_rules router \
524		"block" \
525		"pass in  on ${epair_tester}b inet proto tcp keep state" \
526		"match out on ${epair_server}a inet proto tcp nat-to ${epair_server}a" \
527		"pass out on ${epair_server}a inet proto tcp from ${epair_server}a keep state"
528
529	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
530
531	jexec router pfctl -qvvsr
532	jexec router pfctl -qvvss
533	jexec router ifconfig
534	jexec router netstat -rn
535}
536
537nat_match_cleanup()
538{
539	pft_cleanup
540}
541
542map_e_common()
543{
544	NC_TRY_COUNT=12
545
546	pft_init
547
548	epair_map_e=$(vnet_mkepair)
549	epair_echo=$(vnet_mkepair)
550
551	vnet_mkjail map_e ${epair_map_e}b ${epair_echo}a
552	vnet_mkjail echo ${epair_echo}b
553
554	ifconfig ${epair_map_e}a 192.0.2.2/24 up
555	route add -net 198.51.100.0/24 192.0.2.1
556
557	jexec map_e ifconfig ${epair_map_e}b 192.0.2.1/24 up
558	jexec map_e ifconfig ${epair_echo}a 198.51.100.1/24 up
559	jexec map_e sysctl net.inet.ip.forwarding=1
560
561	jexec echo ifconfig ${epair_echo}b 198.51.100.2/24 up
562	jexec echo /usr/sbin/inetd -p ${PWD}/inetd-echo.pid $(atf_get_srcdir)/echo_inetd.conf
563
564	# Enable pf!
565	jexec map_e pfctl -e
566}
567
568atf_test_case "map_e_compat" "cleanup"
569map_e_compat_head()
570{
571	atf_set descr 'map-e-portset test'
572	atf_set require.user root
573}
574
575map_e_compat_body()
576{
577	map_e_common
578
579	pft_set_rules map_e \
580		"nat pass on ${epair_echo}a inet from 192.0.2.0/24 to any -> (${epair_echo}a) map-e-portset 2/12/0x342"
581
582	# Only allow specified ports.
583	jexec echo pfctl -e
584	pft_set_rules echo "block return all" \
585		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
586		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
587		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
588		"set skip on lo"
589
590	i=0
591	while [ ${i} -lt ${NC_TRY_COUNT} ]
592	do
593		echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
594		if [ $? -ne 0 ]; then
595			atf_fail "nc failed (${i})"
596		fi
597		i=$((${i}+1))
598	done
599}
600
601map_e_compat_cleanup()
602{
603	pft_cleanup
604}
605
606
607atf_test_case "map_e_pass" "cleanup"
608map_e_pass_head()
609{
610	atf_set descr 'map-e-portset test'
611	atf_set require.user root
612}
613
614map_e_pass_body()
615{
616	map_e_common
617
618	pft_set_rules map_e \
619		"pass out on ${epair_echo}a inet from 192.0.2.0/24 to any nat-to (${epair_echo}a) map-e-portset 2/12/0x342 keep state"
620
621	jexec map_e pfctl -qvvsr
622
623	# Only allow specified ports.
624	jexec echo pfctl -e
625	pft_set_rules echo "block return all" \
626		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 19720:19723 to (${epair_echo}b) port 7" \
627		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 36104:36107 to (${epair_echo}b) port 7" \
628		"pass in on ${epair_echo}b inet proto tcp from 198.51.100.1 port 52488:52491 to (${epair_echo}b) port 7" \
629		"set skip on lo"
630
631	i=0
632	while [ ${i} -lt ${NC_TRY_COUNT} ]
633	do
634		echo "foo ${i}" | timeout 2 nc -N 198.51.100.2 7
635		if [ $? -ne 0 ]; then
636			atf_fail "nc failed (${i})"
637		fi
638		i=$((${i}+1))
639	done
640}
641
642map_e_pass_cleanup()
643{
644	pft_cleanup
645}
646
647binat_compat_head()
648{
649	atf_set descr 'IPv4 BINAT with nat ruleset'
650	atf_set require.user root
651	atf_set require.progs scapy
652}
653
654binat_compat_body()
655{
656	setup_router_server_ipv4
657	# Delete the route back to make sure that the traffic has been NAT-ed
658	jexec server route del -net ${net_tester} ${net_server_host_router}
659
660	pft_set_rules router \
661		"set state-policy if-bound" \
662		"set ruleset-optimization none" \
663		"binat on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag -> ${epair_server}a" \
664		"block" \
665		"pass in  on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
666		"pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
667		"pass in  on ${epair_server}a inet proto tcp tagged sometag keep state" \
668		"pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
669
670	# Test the outbound NAT part of BINAT.
671	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
672
673	states=$(mktemp) || exit 1
674	jexec router pfctl -qvss | normalize_pfctl_s > $states
675
676	for state_regexp in \
677		"${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 1" \
678		"${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 2" \
679	; do
680		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
681	done
682
683	# Test the inbound RDR part of BINAT.
684	# The "tester" becomes "server" and vice versa.
685	inetd_conf=$(mktemp)
686	echo "discard stream tcp nowait root internal" > $inetd_conf
687	inetd -p ${PWD}/inetd_tester.pid $inetd_conf
688
689	atf_check -s exit:0 \
690	jexec server ${common_dir}/pft_ping.py \
691	    --ping-type=tcp3way --send-sport=4202 \
692	    --sendif ${epair_server}b \
693	    --to ${net_server_host_router} \
694	    --replyif ${epair_server}b
695
696	states=$(mktemp) || exit 1
697	jexec router pfctl -qvss | normalize_pfctl_s > $states
698
699	for state_regexp in \
700		"${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 3" \
701		"${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 4" \
702	; do
703		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
704	done
705}
706
707binat_compat_cleanup()
708{
709	pft_cleanup
710	kill $(cat ${PWD}/inetd_tester.pid)
711}
712
713binat_match_head()
714{
715	atf_set descr 'IPv4 BINAT with nat ruleset'
716	atf_set require.user root
717	atf_set require.progs scapy
718}
719
720binat_match_body()
721{
722	setup_router_server_ipv4
723	# Delete the route back to make sure that the traffic has been NAT-ed
724	jexec server route del -net ${net_tester} ${net_server_host_router}
725
726	# The "binat-to" rule expands to 2 rules so the ""pass" rules start at 3!
727	pft_set_rules router \
728		"set state-policy if-bound" \
729		"set ruleset-optimization none" \
730		"block" \
731		"match on ${epair_server}a inet proto tcp from ${net_tester_host_tester} to any tag sometag binat-to ${epair_server}a" \
732		"pass in  on ${epair_tester}b inet proto tcp !tagged sometag keep state" \
733		"pass out on ${epair_server}a inet proto tcp tagged sometag keep state" \
734		"pass in  on ${epair_server}a inet proto tcp tagged sometag keep state" \
735		"pass out on ${epair_tester}b inet proto tcp tagged sometag keep state"
736
737	# Test the outbound NAT part of BINAT.
738	ping_server_check_reply exit:0 --ping-type=tcp3way --send-sport=4201
739
740	states=$(mktemp) || exit 1
741	jexec router pfctl -qvss | normalize_pfctl_s > $states
742
743	for state_regexp in \
744		"${epair_tester}b tcp ${net_server_host_server}:9 <- ${net_tester_host_tester}:4201 .* 3:2 pkts,.* rule 3" \
745		"${epair_server}a tcp ${net_server_host_router}:4201 \(${net_tester_host_tester}:4201\) -> ${net_server_host_server}:9 .* 3:2 pkts,.* rule 4" \
746	; do
747		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
748	done
749
750	# Test the inbound RDR part of BINAT.
751	# The "tester" becomes "server" and vice versa.
752	inetd_conf=$(mktemp)
753	echo "discard stream tcp nowait root internal" > $inetd_conf
754	inetd -p ${PWD}/inetd_tester.pid $inetd_conf
755
756	atf_check -s exit:0 \
757	jexec server ${common_dir}/pft_ping.py \
758	    --ping-type=tcp3way --send-sport=4202 \
759	    --sendif ${epair_server}b \
760	    --to ${net_server_host_router} \
761	    --replyif ${epair_server}b
762
763	states=$(mktemp) || exit 1
764	jexec router pfctl -qvss | normalize_pfctl_s > $states
765
766	for state_regexp in \
767		"${epair_server}a tcp ${net_tester_host_tester}:9 \(${net_server_host_router}:9\) <- ${net_server_host_server}:4202 .* 3:2 pkts,.* rule 5" \
768		"${epair_tester}b tcp ${net_server_host_server}:4202 -> ${net_tester_host_tester}:9 .* 3:2 pkts,.* rule 6" \
769	; do
770		grep -qE "${state_regexp}" $states || atf_fail "State not found for '${state_regexp}'"
771	done
772}
773
774binat_match_cleanup()
775{
776	pft_cleanup
777	kill $(cat ${PWD}/inetd_tester.pid)
778}
779
780atf_init_test_cases()
781{
782	atf_add_test_case "exhaust"
783	atf_add_test_case "nested_anchor"
784	atf_add_test_case "endpoint_independent_compat"
785	atf_add_test_case "endpoint_independent_pass"
786	atf_add_test_case "nat6_nolinklocal"
787	atf_add_test_case "empty_table_source_hash"
788	atf_add_test_case "no_addrs_source_hash"
789	atf_add_test_case "empty_table_random"
790	atf_add_test_case "no_addrs_random"
791	atf_add_test_case "map_e_compat"
792	atf_add_test_case "map_e_pass"
793	atf_add_test_case "nat_pass"
794	atf_add_test_case "nat_match"
795	atf_add_test_case "binat_compat"
796	atf_add_test_case "binat_match"
797}
798