xref: /freebsd/tests/sys/netpfil/pf/killstate.sh (revision 535af610)
1# $FreeBSD$
2#
3# SPDX-License-Identifier: BSD-2-Clause
4#
5# Copyright (c) 2021 Rubicon Communications, LLC (Netgate)
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26# SUCH DAMAGE.
27
28. $(atf_get_srcdir)/utils.subr
29
30common_dir=$(atf_get_srcdir)/../common
31
32find_state()
33{
34	jexec alcatraz pfctl -ss | grep icmp | grep 192.0.2.2
35}
36
37find_state_v6()
38{
39	jexec alcatraz pfctl -ss | grep icmp | grep 2001:db8::2
40}
41
42
43atf_test_case "v4" "cleanup"
44v4_head()
45{
46	atf_set descr 'Test killing states by IPv4 address'
47	atf_set require.user root
48	atf_set require.progs scapy
49}
50
51v4_body()
52{
53	pft_init
54
55	epair=$(vnet_mkepair)
56	ifconfig ${epair}a 192.0.2.1/24 up
57
58	vnet_mkjail alcatraz ${epair}b
59	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
60	jexec alcatraz pfctl -e
61
62	pft_set_rules alcatraz "block all" \
63		"pass in proto icmp" \
64		"set skip on lo"
65
66	# Sanity check & establish state
67	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
68		--sendif ${epair}a \
69		--to 192.0.2.2 \
70		--replyif ${epair}a
71
72	# Change rules to now deny the ICMP traffic
73	pft_set_rules noflush alcatraz "block all"
74	if ! find_state;
75	then
76		atf_fail "Setting new rules removed the state."
77	fi
78
79	# Killing with the wrong IP doesn't affect our state
80	jexec alcatraz pfctl -k 192.0.2.3
81	if ! find_state;
82	then
83		atf_fail "Killing with the wrong IP removed our state."
84	fi
85
86	# Killing with one correct address and one incorrect doesn't kill the state
87	jexec alcatraz pfctl -k 192.0.2.1 -k 192.0.2.3
88	if ! find_state;
89	then
90		atf_fail "Killing with one wrong IP removed our state."
91	fi
92
93	# Killing with correct address does remove the state
94	jexec alcatraz pfctl -k 192.0.2.1
95	if find_state;
96	then
97		atf_fail "Killing with the correct IP did not remove our state."
98	fi
99}
100
101v4_cleanup()
102{
103	pft_cleanup
104}
105
106atf_test_case "v6" "cleanup"
107v6_head()
108{
109	atf_set descr 'Test killing states by IPv6 address'
110	atf_set require.user root
111	atf_set require.progs scapy
112}
113
114v6_body()
115{
116	pft_init
117
118	if [ "$(atf_config_get ci false)" = "true" ]; then
119		atf_skip "https://bugs.freebsd.org/260458"
120	fi
121
122	epair=$(vnet_mkepair)
123	ifconfig ${epair}a inet6 2001:db8::1/64 up no_dad
124
125	vnet_mkjail alcatraz ${epair}b
126	jexec alcatraz ifconfig ${epair}b inet6 2001:db8::2/64 up no_dad
127	jexec alcatraz pfctl -e
128
129	pft_set_rules alcatraz "block all" \
130		"pass in proto icmp6" \
131		"set skip on lo"
132
133	# Sanity check & establish state
134	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
135		--sendif ${epair}a \
136		--to 2001:db8::2 \
137		--replyif ${epair}a
138
139	# Change rules to now deny the ICMP traffic
140	pft_set_rules noflush alcatraz "block all"
141	if ! find_state_v6;
142	then
143		atf_fail "Setting new rules removed the state."
144	fi
145
146	# Killing with the wrong IP doesn't affect our state
147	jexec alcatraz pfctl -k 2001:db8::3
148	if ! find_state_v6;
149	then
150		atf_fail "Killing with the wrong IP removed our state."
151	fi
152
153	# Killing with one correct address and one incorrect doesn't kill the state
154	jexec alcatraz pfctl -k 2001:db8::1 -k 2001:db8::3
155	if ! find_state_v6;
156	then
157		atf_fail "Killing with one wrong IP removed our state."
158	fi
159
160	# Killing with correct address does remove the state
161	jexec alcatraz pfctl -k 2001:db8::1
162	if find_state_v6;
163	then
164		atf_fail "Killing with the correct IP did not remove our state."
165	fi
166}
167
168v6_cleanup()
169{
170	pft_cleanup
171}
172
173atf_test_case "label" "cleanup"
174label_head()
175{
176	atf_set descr 'Test killing states by label'
177	atf_set require.user root
178	atf_set require.progs scapy
179}
180
181label_body()
182{
183	pft_init
184
185	epair=$(vnet_mkepair)
186	ifconfig ${epair}a 192.0.2.1/24 up
187
188	vnet_mkjail alcatraz ${epair}b
189	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
190	jexec alcatraz pfctl -e
191
192	pft_set_rules alcatraz "block all" \
193		"pass in proto tcp label bar" \
194		"pass in proto icmp label foo" \
195		"set skip on lo"
196
197	# Sanity check & establish state
198	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
199		--sendif ${epair}a \
200		--to 192.0.2.2 \
201		--replyif ${epair}a
202
203	# Change rules to now deny the ICMP traffic
204	pft_set_rules noflush alcatraz "block all"
205	if ! find_state;
206	then
207		atf_fail "Setting new rules removed the state."
208	fi
209
210	# Killing a label on a different rules keeps the state
211	jexec alcatraz pfctl -k label -k bar
212	if ! find_state;
213	then
214		atf_fail "Killing a different label removed the state."
215	fi
216
217	# Killing a non-existing label keeps the state
218	jexec alcatraz pfctl -k label -k baz
219	if ! find_state;
220	then
221		atf_fail "Killing a non-existing label removed the state."
222	fi
223
224	# Killing the correct label kills the state
225	jexec alcatraz pfctl -k label -k foo
226	if find_state;
227	then
228		atf_fail "Killing the state did not remove it."
229	fi
230}
231
232label_cleanup()
233{
234	pft_cleanup
235}
236
237atf_test_case "multilabel" "cleanup"
238multilabel_head()
239{
240	atf_set descr 'Test killing states with multiple labels by label'
241	atf_set require.user root
242	atf_set require.progs scapy
243}
244
245multilabel_body()
246{
247	pft_init
248
249	epair=$(vnet_mkepair)
250	ifconfig ${epair}a 192.0.2.1/24 up
251
252	vnet_mkjail alcatraz ${epair}b
253	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
254	jexec alcatraz pfctl -e
255
256	pft_set_rules alcatraz "block all" \
257		"pass in proto icmp label foo label bar" \
258		"set skip on lo"
259
260	# Sanity check & establish state
261	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
262		--sendif ${epair}a \
263		--to 192.0.2.2 \
264		--replyif ${epair}a
265
266	# Change rules to now deny the ICMP traffic
267	pft_set_rules noflush alcatraz "block all"
268	if ! find_state;
269	then
270		atf_fail "Setting new rules removed the state."
271	fi
272
273	# Killing a label on a different rules keeps the state
274	jexec alcatraz pfctl -k label -k baz
275	if ! find_state;
276	then
277		atf_fail "Killing a different label removed the state."
278	fi
279
280	# Killing the state with the last label works
281	jexec alcatraz pfctl -k label -k bar
282	if find_state;
283	then
284		atf_fail "Killing with the last label did not remove the state."
285	fi
286
287	pft_set_rules alcatraz "block all" \
288		"pass in proto icmp label foo label bar" \
289		"set skip on lo"
290
291	# Reestablish state
292	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
293		--sendif ${epair}a \
294		--to 192.0.2.2 \
295		--replyif ${epair}a
296
297	# Change rules to now deny the ICMP traffic
298	pft_set_rules noflush alcatraz "block all"
299	if ! find_state;
300	then
301		atf_fail "Setting new rules removed the state."
302	fi
303
304	# Killing with the first label works too
305	jexec alcatraz pfctl -k label -k foo
306	if find_state;
307	then
308		atf_fail "Killing with the first label did not remove the state."
309	fi
310}
311
312multilabel_cleanup()
313{
314	pft_cleanup
315}
316
317atf_test_case "gateway" "cleanup"
318gateway_head()
319{
320	atf_set descr 'Test killing states by route-to/reply-to address'
321	atf_set require.user root
322	atf_set require.progs scapy
323}
324
325gateway_body()
326{
327	pft_init
328
329	epair=$(vnet_mkepair)
330	ifconfig ${epair}a 192.0.2.1/24 up
331
332	vnet_mkjail alcatraz ${epair}b
333	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
334	jexec alcatraz pfctl -e
335
336	pft_set_rules alcatraz "block all" \
337		"pass in reply-to (${epair}b 192.0.2.1) proto icmp" \
338		"set skip on lo"
339
340	# Sanity check & establish state
341	# Note: use pft_ping so we always use the same ID, so pf considers all
342	# echo requests part of the same flow.
343	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
344		--sendif ${epair}a \
345		--to 192.0.2.2 \
346		--replyif ${epair}a
347
348	# Change rules to now deny the ICMP traffic
349	pft_set_rules noflush alcatraz "block all"
350	if ! find_state;
351	then
352		atf_fail "Setting new rules removed the state."
353	fi
354
355	# Killing with a different gateway does not affect our state
356	jexec alcatraz pfctl -k gateway -k 192.0.2.2
357	if ! find_state;
358	then
359		atf_fail "Killing with a different gateway removed the state."
360	fi
361
362	# Killing states with the relevant gateway does terminate our state
363	jexec alcatraz pfctl -k gateway -k 192.0.2.1
364	if find_state;
365	then
366		atf_fail "Killing with the gateway did not remove the state."
367	fi
368}
369
370gateway_cleanup()
371{
372	pft_cleanup
373}
374
375atf_test_case "match" "cleanup"
376match_head()
377{
378	atf_set descr 'Test killing matching states'
379	atf_set require.user root
380}
381
382wait_for_state()
383{
384	jail=$1
385	addr=$2
386
387	while ! jexec $jail pfctl -s s | grep $addr >/dev/null;
388	do
389		sleep .1
390	done
391}
392
393match_body()
394{
395	pft_init
396
397	epair_one=$(vnet_mkepair)
398	ifconfig ${epair_one}a 192.0.2.1/24 up
399
400	epair_two=$(vnet_mkepair)
401
402	vnet_mkjail alcatraz ${epair_one}b ${epair_two}a
403	jexec alcatraz ifconfig ${epair_one}b 192.0.2.2/24 up
404	jexec alcatraz ifconfig ${epair_two}a 198.51.100.1/24 up
405	jexec alcatraz sysctl net.inet.ip.forwarding=1
406	jexec alcatraz pfctl -e
407
408	vnet_mkjail singsing ${epair_two}b
409	jexec singsing ifconfig ${epair_two}b 198.51.100.2/24 up
410	jexec singsing route add default 198.51.100.1
411	jexec singsing /usr/sbin/inetd -p inetd-echo.pid \
412	    $(atf_get_srcdir)/echo_inetd.conf
413
414	route add 198.51.100.0/24 192.0.2.2
415
416	pft_set_rules alcatraz \
417		"nat on ${epair_two}a from 192.0.2.0/24 -> (${epair_two}a)" \
418		"pass all"
419
420	nc 198.51.100.2 7 &
421	wait_for_state alcatraz 192.0.2.1
422
423	# Expect two states
424	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
425	if [ $states -ne 2 ] ;
426	then
427		atf_fail "Expected two states, found $states"
428	fi
429
430	# If we don't kill the matching NAT state one should be left
431	jexec alcatraz pfctl -k 192.0.2.1
432	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
433	if [ $states -ne 1 ] ;
434	then
435		atf_fail "Expected one states, found $states"
436	fi
437
438	# Flush
439	jexec alcatraz pfctl -F states
440
441	nc 198.51.100.2 7 &
442	wait_for_state alcatraz 192.0.2.1
443
444	# Kill matching states, expect all of them to be gone
445	jexec alcatraz pfctl -M -k 192.0.2.1
446	states=$(jexec alcatraz pfctl -s s | grep 192.0.2.1 | wc -l)
447	if [ $states -ne 0 ] ;
448	then
449		atf_fail "Expected zero states, found $states"
450	fi
451}
452
453match_cleanup()
454{
455	pft_cleanup
456}
457
458atf_test_case "interface" "cleanup"
459interface_head()
460{
461	atf_set descr 'Test killing states based on interface'
462	atf_set require.user root
463	atf_set require.progs scapy
464}
465
466interface_body()
467{
468	pft_init
469
470	epair=$(vnet_mkepair)
471	ifconfig ${epair}a 192.0.2.1/24 up
472
473	vnet_mkjail alcatraz ${epair}b
474	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
475	jexec alcatraz pfctl -e
476
477	pft_set_rules alcatraz "block all" \
478		"pass in proto icmp" \
479		"set skip on lo"
480
481	# Sanity check & establish state
482	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
483		--sendif ${epair}a \
484		--to 192.0.2.2 \
485		--replyif ${epair}a
486
487	# Change rules to now deny the ICMP traffic
488	pft_set_rules noflush alcatraz "block all"
489	if ! find_state;
490	then
491		atf_fail "Setting new rules removed the state."
492	fi
493
494	# Flushing states on a different interface doesn't affect our state
495	jexec alcatraz pfctl -i ${epair}a -Fs
496	if ! find_state;
497	then
498		atf_fail "Flushing on a different interface removed the state."
499	fi
500
501	# Flushing on the correct interface does (even with floating states)
502	jexec alcatraz pfctl -i ${epair}b -Fs
503	if find_state;
504	then
505		atf_fail "Flushing on a the interface did not remove the state."
506	fi
507}
508
509interface_cleanup()
510{
511	pft_cleanup
512}
513
514atf_test_case "id" "cleanup"
515id_head()
516{
517	atf_set descr 'Test killing states by id'
518	atf_set require.user root
519	atf_set require.progs scapy
520}
521
522id_body()
523{
524	pft_init
525
526	epair=$(vnet_mkepair)
527	ifconfig ${epair}a 192.0.2.1/24 up
528
529	vnet_mkjail alcatraz ${epair}b
530	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
531	jexec alcatraz pfctl -e
532
533	pft_set_rules alcatraz "block all" \
534		"pass in proto tcp" \
535		"pass in proto icmp" \
536		"set skip on lo"
537
538	# Sanity check & establish state
539	atf_check -s exit:0 -o ignore ${common_dir}/pft_ping.py \
540		--sendif ${epair}a \
541		--to 192.0.2.2 \
542		--replyif ${epair}a
543
544	# Change rules to now deny the ICMP traffic
545	pft_set_rules noflush alcatraz "block all"
546	if ! find_state;
547	then
548		atf_fail "Setting new rules removed the state."
549	fi
550
551	# Get the state ID
552	id=$(jexec alcatraz pfctl -ss -vvv | grep -A 3 icmp |
553	    grep -A 3 192.0.2.2 | awk '/id:/ { printf("%s/%s", $2, $4); }')
554
555	# Kill the wrong ID
556	jexec alcatraz pfctl -k id -k 1
557	if ! find_state;
558	then
559		atf_fail "Killing a different ID removed the state."
560	fi
561
562	# Kill the correct ID
563	jexec alcatraz pfctl -k id -k ${id}
564	if find_state;
565	then
566		atf_fail "Killing the state did not remove it."
567	fi
568}
569
570id_cleanup()
571{
572	pft_cleanup
573}
574
575atf_init_test_cases()
576{
577	atf_add_test_case "v4"
578	atf_add_test_case "v6"
579	atf_add_test_case "label"
580	atf_add_test_case "multilabel"
581	atf_add_test_case "gateway"
582	atf_add_test_case "match"
583	atf_add_test_case "interface"
584	atf_add_test_case "id"
585}
586