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