xref: /freebsd/tests/sys/netpfil/pf/pflog.sh (revision 39cbfc78b50c2b940d131834c8a5fb72442358c0)
1#
2# SPDX-License-Identifier: BSD-2-Clause
3#
4# Copyright (c) 2023 Rubicon Communications, LLC (Netgate)
5# Copyright (c) 2024 Deciso B.V.
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
32atf_test_case "malformed" "cleanup"
33malformed_head()
34{
35	atf_set descr 'Test that we do not log malformed packets as passing'
36	atf_set require.user root
37	atf_set require.progs python3 scapy
38}
39
40malformed_body()
41{
42	pflog_init
43
44	epair=$(vnet_mkepair)
45
46	vnet_mkjail srv ${epair}b
47	jexec srv ifconfig ${epair}b 192.0.2.1/24 up
48
49	vnet_mkjail cl ${epair}a
50	jexec cl ifconfig ${epair}a 192.0.2.2/24 up
51
52	jexec cl pfctl -e
53	jexec cl ifconfig pflog0 up
54	pft_set_rules cl \
55		"pass log keep state"
56
57	# Not required, but the 'pf: dropping packet with ip options' kernel log can
58	# help when debugging the test.
59	jexec cl pfctl -x loud
60
61	jexec cl tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
62	sleep 1 # Wait for tcpdump to start
63
64	# Sanity check
65	atf_check -s exit:0 -o ignore \
66	    jexec srv ping -c 1 192.0.2.2
67
68	jexec srv ${common_dir}/pft_ping.py  \
69	    --sendif ${epair}b \
70	    --to 192.0.2.2 \
71	    --send-nop \
72	    --recvif ${epair}b
73
74	atf_check -o match:".*rule 0/8\(ip-option\): block in on ${epair}a: 192.0.2.1 > 192.0.2.2: ICMP echo request.*" \
75	    cat pflog.txt
76}
77
78malformed_cleanup()
79{
80	pft_cleanup
81}
82
83atf_test_case "matches" "cleanup"
84matches_head()
85{
86	atf_set descr 'Test the pflog matches keyword'
87	atf_set require.user root
88}
89
90matches_body()
91{
92	pflog_init
93
94	epair=$(vnet_mkepair)
95
96	vnet_mkjail alcatraz ${epair}a
97	jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
98
99	ifconfig ${epair}b 192.0.2.2/24 up
100
101	# Sanity check
102	atf_check -s exit:0 -o ignore \
103	    ping -c 1 192.0.2.1
104
105	jexec alcatraz pfctl -e
106	jexec alcatraz ifconfig pflog0 up
107	pft_set_rules alcatraz \
108		"match log(matches) inet proto icmp" \
109		"match log(matches) inet from 192.0.2.2" \
110		"pass"
111
112	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt &
113	sleep 1 # Wait for tcpdump to start
114
115	atf_check -s exit:0 -o ignore \
116	    ping -c 1 192.0.2.1
117
118	echo "Rules"
119	jexec alcatraz pfctl -sr -vv
120	echo "States"
121	jexec alcatraz pfctl -ss -vv
122	echo "Log"
123	cat ${PWD}/pflog.txt
124
125	atf_check -o match:".*rule 0/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
126	    cat pflog.txt
127	atf_check -o match:".*rule 1/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
128	    cat pflog.txt
129}
130
131matches_cleanup()
132{
133	pft_cleanup
134}
135
136atf_test_case "matches_logif" "cleanup"
137matches_logif_head()
138{
139	atf_set descr 'Test log(matches, to pflogX)'
140	atf_set require.user root
141}
142
143matches_logif_body()
144{
145	pflog_init
146
147	epair=$(vnet_mkepair)
148
149	vnet_mkjail alcatraz ${epair}a
150	jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
151
152	ifconfig ${epair}b 192.0.2.2/24 up
153
154	# Sanity check
155	atf_check -s exit:0 -o ignore \
156	    ping -c 1 192.0.2.1
157
158	jexec alcatraz pfctl -e
159	jexec alcatraz ifconfig pflog0 up
160	jexec alcatraz ifconfig pflog1 create
161	jexec alcatraz ifconfig pflog1 up
162	pft_set_rules alcatraz \
163		"match log(matches, to pflog1) inet proto icmp" \
164		"match log inet from 192.0.2.2" \
165		"pass log(to pflog0)"
166
167	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog1 >> ${PWD}/pflog1.txt &
168	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog0.txt &
169	sleep 1 # Wait for tcpdump to start
170
171	atf_check -s exit:0 -o ignore \
172	    ping -c 1 192.0.2.1
173
174	echo "Rules"
175	jexec alcatraz pfctl -sr -vv
176	echo "States"
177	jexec alcatraz pfctl -ss -vv
178	echo "Log 0"
179	cat ${PWD}/pflog0.txt
180	echo "Log 1"
181	cat ${PWD}/pflog1.txt
182
183	atf_check -o match:".*rule 0/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
184	    cat pflog1.txt
185	atf_check -o match:".*rule 1/0\(match\): match in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
186	    cat pflog1.txt
187}
188
189matches_logif_cleanup()
190{
191	pft_cleanup
192}
193
194atf_test_case "state_max" "cleanup"
195state_max_head()
196{
197	atf_set descr 'Ensure that drops due to state limits are logged'
198	atf_set require.user root
199}
200
201state_max_body()
202{
203	pflog_init
204
205	epair=$(vnet_mkepair)
206
207	vnet_mkjail alcatraz ${epair}a
208	jexec alcatraz ifconfig ${epair}a inet6 ifdisabled
209	jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
210
211	ifconfig ${epair}b 192.0.2.2/24 up
212	ifconfig ${epair}b inet6 ifdisabled
213
214	# Sanity check
215	atf_check -s exit:0 -o ignore \
216	    ping -c 1 192.0.2.1
217
218	jexec alcatraz pfctl -e
219	jexec alcatraz ifconfig pflog0 up
220	pft_set_rules alcatraz "pass log inet keep state (max 1)"
221
222	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt &
223	sleep 1 # Wait for tcpdump to start
224
225	atf_check -s exit:0 -o ignore \
226	    ping -c 1 192.0.2.1
227
228	atf_check -s exit:2 -o ignore \
229	    ping -c 1 192.0.2.1
230
231	echo "Rules"
232	jexec alcatraz pfctl -sr -vv
233	echo "States"
234	jexec alcatraz pfctl -ss -vv
235	echo "Log"
236	cat ${PWD}/pflog.txt
237
238	# First ping passes.
239	atf_check -o match:".*rule 0/0\(match\): pass in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
240	    cat pflog.txt
241
242	# Second ping is blocked due to the state limit.
243	atf_check -o match:".*rule 0/12\(state-limit\): block in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
244	    cat pflog.txt
245
246	# At most three lines should be written: one for the first ping, and
247	# two for the second: one for the initial pass through the ruleset, and
248	# then a drop because of the state limit.  Ideally only the drop would
249	# be logged; if this is fixed, the count will be 2 instead of 3.
250	atf_check -o match:3 grep -c . pflog.txt
251
252	# If the rule doesn't specify logging, we shouldn't log drops
253	# due to state limits.
254	pft_set_rules alcatraz "pass inet keep state (max 1)"
255
256	atf_check -s exit:0 -o ignore \
257	    ping -c 1 192.0.2.1
258
259	atf_check -s exit:2 -o ignore \
260	    ping -c 1 192.0.2.1
261
262	atf_check -o match:3 grep -c . pflog.txt
263}
264
265state_max_cleanup()
266{
267	pft_cleanup
268}
269
270atf_test_case "unspecified_v4" "cleanup"
271unspecified_v4_head()
272{
273	atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
274	atf_set require.user root
275}
276
277unspecified_v4_body()
278{
279	pflog_init
280
281	vnet_mkjail alcatraz
282	jexec alcatraz ifconfig lo0 inet 127.0.0.1
283	jexec alcatraz route add default 127.0.0.1
284
285	jexec alcatraz pfctl -e
286	jexec alcatraz ifconfig pflog0 up
287	pft_set_rules alcatraz "block log on lo0 to 0.0.0.0"
288
289	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
290	sleep 1 # Wait for tcpdump to start
291
292	atf_check -s not-exit:0 -o ignore -e ignore \
293	    jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0
294
295	atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \
296	    cat pflog.txt
297}
298
299unspecified_v4_cleanup()
300{
301	pft_cleanup
302}
303
304atf_test_case "unspecified_v6" "cleanup"
305unspecified_v6_head()
306{
307	atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
308	atf_set require.user root
309}
310
311unspecified_v6_body()
312{
313	pflog_init
314
315	vnet_mkjail alcatraz
316	jexec alcatraz ifconfig lo0 up
317	jexec alcatraz route -6 add ::0 ::1
318
319	jexec alcatraz pfctl -e
320	jexec alcatraz ifconfig pflog0 up
321	pft_set_rules alcatraz "block log on lo0 to ::0"
322
323	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
324	sleep 1 # Wait for tcpdump to start
325
326	atf_check -s not-exit:0 -o ignore -e ignore \
327	    jexec alcatraz ping -6 -S ::1 -c 1 ::0
328
329	cat pflog.txt
330	atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \
331	    cat pflog.txt
332}
333
334unspecified_v6_cleanup()
335{
336	pft_cleanup
337}
338
339atf_test_case "rdr_action" "cleanup"
340rdr_head()
341{
342	atf_set descr 'Ensure that NAT rule actions are logged correctly'
343	atf_set require.user root
344}
345
346rdr_action_body()
347{
348	pflog_init
349
350	j="pflog:rdr_action"
351	epair_c=$(vnet_mkepair)
352	epair_srv=$(vnet_mkepair)
353
354	vnet_mkjail ${j}srv ${epair_srv}a
355	vnet_mkjail ${j}gw ${epair_srv}b ${epair_c}a
356	vnet_mkjail ${j}c ${epair_c}b
357
358	jexec ${j}srv ifconfig ${epair_srv}a inet6 ifdisabled
359	jexec ${j}gw ifconfig ${epair_srv}b inet6 ifdisabled
360	jexec ${j}gw ifconfig ${epair_c}a inet6 ifdisabled
361	jexec ${j}c ifconfig ${epair_c}b inet6 ifdisabled
362
363	jexec ${j}srv ifconfig ${epair_srv}a 198.51.100.1/24 up
364	# No default route in srv jail, to ensure we're NAT-ing
365	jexec ${j}gw ifconfig ${epair_srv}b 198.51.100.2/24 up
366	jexec ${j}gw ifconfig ${epair_c}a 192.0.2.1/24 up
367	jexec ${j}gw sysctl net.inet.ip.forwarding=1
368	jexec ${j}c ifconfig ${epair_c}b 192.0.2.2/24 up
369	jexec ${j}c route add default 192.0.2.1
370
371	jexec ${j}gw pfctl -e
372	jexec ${j}gw ifconfig pflog0 up
373	pft_set_rules ${j}gw \
374		"rdr log on ${epair_srv}b proto tcp from 198.51.100.0/24 to any port 1234 -> 192.0.2.2 port 1234" \
375		"block quick inet6" \
376		"pass in log"
377
378	jexec ${j}gw tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt &
379	sleep 1 # Wait for tcpdump to start
380
381	# send a SYN to catch in the log
382	jexec ${j}srv nc -N -w 0 198.51.100.2 1234
383
384	echo "Log"
385	cat ${PWD}/pflog.txt
386
387	# log line generated for rdr hit (pre-NAT)
388	atf_check -o match:".*.*rule 0/0\(match\): rdr in on ${epair_srv}b: 198.51.100.1.[0-9]* > 198.51.100.2.1234: Flags \[S\].*" \
389	    cat pflog.txt
390
391	# log line generated for pass hit (post-NAT)
392	atf_check -o match:".*.*rule 1/0\(match\): pass in on ${epair_srv}b: 198.51.100.1.[0-9]* > 192.0.2.2.1234: Flags \[S\].*" \
393	    cat pflog.txt
394
395	# only two log lines shall be written
396	atf_check -o match:2 grep -c . pflog.txt
397}
398
399rdr_action_cleanup()
400{
401	pft_cleanup
402}
403
404atf_test_case "rule_number" "cleanup"
405rule_number_head()
406{
407	atf_set descr 'Test rule numbers with anchors'
408	atf_set require.user root
409}
410
411rule_number_body()
412{
413	pflog_init
414
415	epair=$(vnet_mkepair)
416
417	vnet_mkjail alcatraz ${epair}b
418	jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up
419
420	ifconfig ${epair}a 192.0.2.2/24 up
421	ifconfig ${epair}a inet alias 192.0.2.3/24 up
422	ifconfig ${epair}a inet alias 192.0.2.4/24 up
423
424	jexec alcatraz pfctl -e
425	jexec alcatraz ifconfig pflog0 up
426	pft_set_rules alcatraz \
427		"pass log from 192.0.2.2" \
428		"anchor \"foo\" {\n \
429			pass log from 192.0.2.3\n \
430		}" \
431		"pass log from 192.0.2.4"
432
433	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
434	sleep 1 # Wait for tcpdump to start
435
436	atf_check -s exit:0 -o ignore \
437	    ping -c 1 -S 192.0.2.2 192.0.2.1
438	atf_check -s exit:0 -o ignore \
439	    ping -c 1 -S 192.0.2.3 192.0.2.1
440	atf_check -s exit:0 -o ignore \
441	    ping -c 1 -S 192.0.2.4 192.0.2.1
442
443	jexec alcatraz pfctl -sr -a '*' -vv
444
445	# Give tcpdump a little time to finish writing to the file
446	sleep 1
447	cat pflog.txt
448
449	atf_check -o match:"rule 0/0\(match\): pass in.*: 192.0.2.2.*ICMP echo request" \
450	    cat pflog.txt
451	atf_check -o match:"rule 1.foo.0/0\(match\): pass in.*: 192.0.2.3.*: ICMP echo request" \
452	    cat pflog.txt
453	atf_check -o match:"rule 2/0\(match\): pass in.*: 192.0.2.4.*: ICMP echo request" \
454	    cat pflog.txt
455}
456
457rule_number_cleanup()
458{
459	pft_cleanup
460}
461
462atf_init_test_cases()
463{
464	atf_add_test_case "malformed"
465	atf_add_test_case "matches"
466	atf_add_test_case "matches_logif"
467	atf_add_test_case "state_max"
468	atf_add_test_case "unspecified_v4"
469	atf_add_test_case "unspecified_v6"
470	atf_add_test_case "rdr_action"
471	atf_add_test_case "rule_number"
472}
473