xref: /freebsd/tests/sys/netpfil/pf/divert-to.sh (revision 069ac184)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2023 Igor Ostapenko <pm@igoro.pro>
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#
28# pf divert-to action test cases
29#
30#  -----------|           |--     |----|     ----|            |-----------
31# ( ) inbound |pf_check_in|  ) -> |host| -> ( )  |pf_check_out| outbound  )
32#  -----------|     |     |--     |----|     ----|     |      |-----------
33#                   |                                  |
34#                  \|/                                \|/
35#                |------|                           |------|
36#                |divapp|                           |divapp|
37#                |------|                           |------|
38#
39# The basic cases:
40#   - inbound > diverted               | divapp terminated
41#   - inbound > diverted > inbound     | host terminated
42#   - inbound > diverted > outbound    | network terminated
43#   - outbound > diverted              | divapp terminated
44#   - outbound > diverted > outbound   | network terminated
45#   - outbound > diverted > inbound    | e.g. host terminated
46#
47# When a packet is diverted, forwarded, and possibly diverted again:
48#   - inbound > diverted > inbound > forwarded
49#         > outbound                       | network terminated
50#   - inbound > diverted > inbound > forwarded
51#         > outbound > diverted > outbound | network terminated
52#
53# Test case naming legend:
54# ipfwon - with ipfw enabled
55# ipfwoff - with ipfw disabled
56# in - inbound
57# div - diverted
58# out - outbound
59# fwd - forwarded
60# dn - delayed by dummynet
61#
62
63. $(atf_get_srcdir)/utils.subr
64
65divert_init()
66{
67	if ! kldstat -q -m ipdivert; then
68		atf_skip "This test requires ipdivert"
69	fi
70}
71
72dummynet_init()
73{
74	if ! kldstat -q -m dummynet; then
75		atf_skip "This test requires dummynet"
76	fi
77}
78
79ipfw_init()
80{
81	if ! kldstat -q -m ipfw; then
82		atf_skip "This test requires ipfw"
83	fi
84}
85
86assert_ipfw_is_off()
87{
88	if kldstat -q -m ipfw; then
89		atf_skip "This test is for the case when ipfw is not loaded"
90	fi
91}
92
93atf_test_case "ipfwoff_in_div" "cleanup"
94ipfwoff_in_div_head()
95{
96	atf_set descr 'Test inbound > diverted | divapp terminated'
97	atf_set require.user root
98}
99ipfwoff_in_div_body()
100{
101	local ipfwon
102
103	pft_init
104	divert_init
105	test "$1" == "ipfwon" && ipfwon="yes"
106	test $ipfwon && ipfw_init || assert_ipfw_is_off
107
108	epair=$(vnet_mkepair)
109	vnet_mkjail div ${epair}b
110	ifconfig ${epair}a 192.0.2.1/24 up
111	jexec div ifconfig ${epair}b 192.0.2.2/24 up
112	test $ipfwon && jexec div ipfw add 65534 allow all from any to any
113
114	# Sanity check
115	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
116
117	jexec div pfctl -e
118	pft_set_rules div \
119		"pass all" \
120		"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000"
121
122	jexec div $(atf_get_srcdir)/divapp 2000 &
123	divapp_pid=$!
124	# Wait for the divapp to be ready
125	sleep 1
126
127	# divapp is expected to "eat" the packet
128	atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2
129
130	wait $divapp_pid
131}
132ipfwoff_in_div_cleanup()
133{
134	pft_cleanup
135}
136
137atf_test_case "ipfwon_in_div" "cleanup"
138ipfwon_in_div_head()
139{
140	atf_set descr 'Test inbound > diverted | divapp terminated, with ipfw enabled'
141	atf_set require.user root
142}
143ipfwon_in_div_body()
144{
145	ipfwoff_in_div_body "ipfwon"
146}
147ipfwon_in_div_cleanup()
148{
149	pft_cleanup
150}
151
152atf_test_case "ipfwoff_in_div_in" "cleanup"
153ipfwoff_in_div_in_head()
154{
155	atf_set descr 'Test inbound > diverted > inbound | host terminated'
156	atf_set require.user root
157}
158ipfwoff_in_div_in_body()
159{
160	local ipfwon
161
162	pft_init
163	divert_init
164	test "$1" == "ipfwon" && ipfwon="yes"
165	test $ipfwon && ipfw_init || assert_ipfw_is_off
166
167	epair=$(vnet_mkepair)
168	vnet_mkjail div ${epair}b
169	ifconfig ${epair}a 192.0.2.1/24 up
170	jexec div ifconfig ${epair}b 192.0.2.2/24 up
171	test $ipfwon && jexec div ipfw add 65534 allow all from any to any
172
173	# Sanity check
174	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
175
176	jexec div pfctl -e
177	pft_set_rules div \
178		"pass all" \
179		"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000 no state"
180
181	jexec div $(atf_get_srcdir)/divapp 2000 divert-back &
182	divapp_pid=$!
183	# Wait for the divapp to be ready
184	sleep 1
185
186	# divapp is NOT expected to "eat" the packet
187	atf_check -s exit:0 -o ignore ping -c1 192.0.2.2
188
189	wait $divapp_pid
190}
191ipfwoff_in_div_in_cleanup()
192{
193	pft_cleanup
194}
195
196atf_test_case "ipfwon_in_div_in" "cleanup"
197ipfwon_in_div_in_head()
198{
199	atf_set descr 'Test inbound > diverted > inbound | host terminated, with ipfw enabled'
200	atf_set require.user root
201}
202ipfwon_in_div_in_body()
203{
204	ipfwoff_in_div_in_body "ipfwon"
205}
206ipfwon_in_div_in_cleanup()
207{
208	pft_cleanup
209}
210
211atf_test_case "ipfwoff_out_div" "cleanup"
212ipfwoff_out_div_head()
213{
214	atf_set descr 'Test outbound > diverted | divapp terminated'
215	atf_set require.user root
216}
217ipfwoff_out_div_body()
218{
219	local ipfwon
220
221	pft_init
222	divert_init
223	test "$1" == "ipfwon" && ipfwon="yes"
224	test $ipfwon && ipfw_init || assert_ipfw_is_off
225
226	epair=$(vnet_mkepair)
227	vnet_mkjail div ${epair}b
228	ifconfig ${epair}a 192.0.2.1/24 up
229	jexec div ifconfig ${epair}b 192.0.2.2/24 up
230	test $ipfwon && jexec div ipfw add 65534 allow all from any to any
231
232	# Sanity check
233	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
234
235	jexec div pfctl -e
236	pft_set_rules div \
237		"pass all" \
238		"pass in inet proto icmp icmp-type echoreq no state" \
239		"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state"
240
241	jexec div $(atf_get_srcdir)/divapp 2000 &
242	divapp_pid=$!
243	# Wait for the divapp to be ready
244	sleep 1
245
246	# divapp is expected to "eat" the packet
247	atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2
248
249	wait $divapp_pid
250}
251ipfwoff_out_div_cleanup()
252{
253	pft_cleanup
254}
255
256atf_test_case "ipfwon_out_div" "cleanup"
257ipfwon_out_div_head()
258{
259	atf_set descr 'Test outbound > diverted | divapp terminated, with ipfw enabled'
260	atf_set require.user root
261}
262ipfwon_out_div_body()
263{
264	ipfwoff_out_div_body "ipfwon"
265}
266ipfwon_out_div_cleanup()
267{
268	pft_cleanup
269}
270
271atf_test_case "ipfwoff_out_div_out" "cleanup"
272ipfwoff_out_div_out_head()
273{
274	atf_set descr 'Test outbound > diverted > outbound | network terminated'
275	atf_set require.user root
276}
277ipfwoff_out_div_out_body()
278{
279	local ipfwon
280
281	pft_init
282	divert_init
283	test "$1" == "ipfwon" && ipfwon="yes"
284	test $ipfwon && ipfw_init || assert_ipfw_is_off
285
286	epair=$(vnet_mkepair)
287	vnet_mkjail div ${epair}b
288	ifconfig ${epair}a 192.0.2.1/24 up
289	jexec div ifconfig ${epair}b 192.0.2.2/24 up
290	test $ipfwon && jexec div ipfw add 65534 allow all from any to any
291
292	# Sanity check
293	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
294
295	jexec div pfctl -e
296	pft_set_rules div \
297		"pass all" \
298		"pass in inet proto icmp icmp-type echoreq no state" \
299		"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state"
300
301	jexec div $(atf_get_srcdir)/divapp 2000 divert-back &
302	divapp_pid=$!
303	# Wait for the divapp to be ready
304	sleep 1
305
306	# divapp is NOT expected to "eat" the packet
307	atf_check -s exit:0 -o ignore ping -c1 192.0.2.2
308
309	wait $divapp_pid
310}
311ipfwoff_out_div_out_cleanup()
312{
313	pft_cleanup
314}
315
316atf_test_case "ipfwon_out_div_out" "cleanup"
317ipfwon_out_div_out_head()
318{
319	atf_set descr 'Test outbound > diverted > outbound | network terminated, with ipfw enabled'
320	atf_set require.user root
321}
322ipfwon_out_div_out_body()
323{
324	ipfwoff_out_div_out_body "ipfwon"
325}
326ipfwon_out_div_out_cleanup()
327{
328	pft_cleanup
329}
330
331atf_test_case "ipfwoff_in_div_in_fwd_out_div_out" "cleanup"
332ipfwoff_in_div_in_fwd_out_div_out_head()
333{
334	atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated'
335	atf_set require.user root
336}
337ipfwoff_in_div_in_fwd_out_div_out_body()
338{
339	local ipfwon
340
341	pft_init
342	divert_init
343	test "$1" == "ipfwon" && ipfwon="yes"
344	test $ipfwon && ipfw_init || assert_ipfw_is_off
345
346	# host <a--epair0--b> router <a--epair1--b> site
347	epair0=$(vnet_mkepair)
348	epair1=$(vnet_mkepair)
349
350	vnet_mkjail router ${epair0}b ${epair1}a
351	ifconfig ${epair0}a 192.0.2.1/24 up
352	jexec router sysctl net.inet.ip.forwarding=1
353	jexec router ifconfig ${epair0}b 192.0.2.2/24 up
354	jexec router ifconfig ${epair1}a 198.51.100.1/24 up
355	test $ipfwon && jexec router ipfw add 65534 allow all from any to any
356
357	vnet_mkjail site ${epair1}b
358	jexec site ifconfig ${epair1}b 198.51.100.2/24 up
359	jexec site route add default 198.51.100.1
360	test $ipfwon && jexec site ipfw add 65534 allow all from any to any
361
362	route add -net 198.51.100.0/24 192.0.2.2
363
364	# Sanity check
365	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
366
367	# Should be routed without pf
368	atf_check -s exit:0 -o ignore ping -c3 198.51.100.2
369
370	jexec router pfctl -e
371	pft_set_rules router \
372		"pass all" \
373		"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \
374		"pass out inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2002 no state"
375
376	jexec router $(atf_get_srcdir)/divapp 2001 divert-back &
377	indivapp_pid=$!
378	jexec router $(atf_get_srcdir)/divapp 2002 divert-back &
379	outdivapp_pid=$!
380	# Wait for the divappS to be ready
381	sleep 1
382
383	# Both divappS are NOT expected to "eat" the packet
384	atf_check -s exit:0 -o ignore ping -c1 198.51.100.2
385
386	wait $indivapp_pid && wait $outdivapp_pid
387}
388ipfwoff_in_div_in_fwd_out_div_out_cleanup()
389{
390	pft_cleanup
391}
392
393atf_test_case "ipfwon_in_div_in_fwd_out_div_out" "cleanup"
394ipfwon_in_div_in_fwd_out_div_out_head()
395{
396	atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated, with ipfw enabled'
397	atf_set require.user root
398}
399ipfwon_in_div_in_fwd_out_div_out_body()
400{
401	ipfwoff_in_div_in_fwd_out_div_out_body "ipfwon"
402}
403ipfwon_in_div_in_fwd_out_div_out_cleanup()
404{
405	pft_cleanup
406}
407
408atf_test_case "ipfwoff_in_dn_in_div_in_out_div_out_dn_out" "cleanup"
409ipfwoff_in_dn_in_div_in_out_div_out_dn_out_head()
410{
411	atf_set descr 'Test inbound > delayed+diverted > outbound > diverted+delayed > outbound | network terminated'
412	atf_set require.user root
413}
414ipfwoff_in_dn_in_div_in_out_div_out_dn_out_body()
415{
416	local ipfwon
417
418	pft_init
419	divert_init
420	dummynet_init
421	test "$1" == "ipfwon" && ipfwon="yes"
422	test $ipfwon && ipfw_init || assert_ipfw_is_off
423
424	epair=$(vnet_mkepair)
425	vnet_mkjail alcatraz ${epair}b
426	ifconfig ${epair}a 192.0.2.1/24 up
427	ifconfig ${epair}a ether 02:00:00:00:00:01
428	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
429	test $ipfwon && jexec alcatraz ipfw add 65534 allow all from any to any
430
431	# Sanity check
432	atf_check -s exit:0 -o ignore ping -c3 192.0.2.2
433
434	# a) ping should time out due to very narrow dummynet pipes {
435
436	jexec alcatraz dnctl pipe 1001 config bw 1Byte/s
437	jexec alcatraz dnctl pipe 1002 config bw 1Byte/s
438
439	jexec alcatraz pfctl -e
440	pft_set_rules alcatraz \
441		"ether pass in from 02:00:00:00:00:01 l3 all dnpipe 1001" \
442		"ether pass out to 02:00:00:00:00:01 l3 all dnpipe 1002 " \
443		"pass all" \
444		"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 1001 no state" \
445		"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 1002 no state"
446
447	jexec alcatraz $(atf_get_srcdir)/divapp 1001 divert-back &
448	indivapp_pid=$!
449	jexec alcatraz $(atf_get_srcdir)/divapp 1002 divert-back &
450	outdivapp_pid=$!
451	# Wait for the divappS to be ready
452	sleep 1
453
454	atf_check -s not-exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2
455
456	wait $indivapp_pid
457	atf_check_not_equal 0 $?
458	wait $outdivapp_pid
459	atf_check_not_equal 0 $?
460
461	# }
462
463	# b) ping should NOT time out due to wide enough dummynet pipes {
464
465	jexec alcatraz dnctl pipe 2001 config bw 100KByte/s
466	jexec alcatraz dnctl pipe 2002 config bw 100KByte/s
467
468	jexec alcatraz pfctl -e
469	pft_set_rules alcatraz \
470		"ether pass in from 02:00:00:00:00:01 l3 all dnpipe 2001" \
471		"ether pass out to 02:00:00:00:00:01 l3 all dnpipe 2002 " \
472		"pass all" \
473		"pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \
474		"pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2002 no state"
475
476	jexec alcatraz $(atf_get_srcdir)/divapp 2001 divert-back &
477	indivapp_pid=$!
478	jexec alcatraz $(atf_get_srcdir)/divapp 2002 divert-back &
479	outdivapp_pid=$!
480	# Wait for the divappS to be ready
481	sleep 1
482
483	atf_check -s exit:0 -o ignore ping -c1 -s56 -t1 192.0.2.2
484
485	wait $indivapp_pid
486	atf_check_equal 0 $?
487	wait $outdivapp_pid
488	atf_check_equal 0 $?
489
490	# }
491}
492ipfwoff_in_dn_in_div_in_out_div_out_dn_out_cleanup()
493{
494	pft_cleanup
495}
496
497atf_test_case "ipfwon_in_dn_in_div_in_out_div_out_dn_out" "cleanup"
498ipfwon_in_dn_in_div_in_out_div_out_dn_out_head()
499{
500	atf_set descr 'Test inbound > delayed+diverted > outbound > diverted+delayed > outbound | network terminated, with ipfw enabled'
501	atf_set require.user root
502}
503ipfwon_in_dn_in_div_in_out_div_out_dn_out_body()
504{
505	ipfwoff_in_dn_in_div_in_out_div_out_dn_out_body "ipfwon"
506}
507ipfwon_in_dn_in_div_in_out_div_out_dn_out_cleanup()
508{
509	pft_cleanup
510}
511
512atf_init_test_cases()
513{
514	atf_add_test_case "ipfwoff_in_div"
515	atf_add_test_case "ipfwoff_in_div_in"
516	atf_add_test_case "ipfwon_in_div"
517	atf_add_test_case "ipfwon_in_div_in"
518
519	atf_add_test_case "ipfwoff_out_div"
520	atf_add_test_case "ipfwoff_out_div_out"
521	atf_add_test_case "ipfwon_out_div"
522	atf_add_test_case "ipfwon_out_div_out"
523
524	atf_add_test_case "ipfwoff_in_div_in_fwd_out_div_out"
525	atf_add_test_case "ipfwon_in_div_in_fwd_out_div_out"
526
527	atf_add_test_case "ipfwoff_in_dn_in_div_in_out_div_out_dn_out"
528	atf_add_test_case "ipfwon_in_dn_in_div_in_out_div_out_dn_out"
529}
530