xref: /freebsd/tests/sys/netpfil/pf/pflog.sh (revision f8bd05add2158a433e1f5b9ffd18a9e164c4cfaf)
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 192.0.2.1/24 up
209
210	ifconfig ${epair}b 192.0.2.2/24 up
211
212	# Sanity check
213	atf_check -s exit:0 -o ignore \
214	    ping -c 1 192.0.2.1
215
216	jexec alcatraz pfctl -e
217	jexec alcatraz ifconfig pflog0 up
218	pft_set_rules alcatraz "pass log inet keep state (max 1)"
219
220	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt &
221	sleep 1 # Wait for tcpdump to start
222
223	atf_check -s exit:0 -o ignore \
224	    ping -c 1 192.0.2.1
225
226	atf_check -s exit:2 -o ignore \
227	    ping -c 1 192.0.2.1
228
229	echo "Rules"
230	jexec alcatraz pfctl -sr -vv
231	echo "States"
232	jexec alcatraz pfctl -ss -vv
233	echo "Log"
234	cat ${PWD}/pflog.txt
235
236	# First ping passes.
237	atf_check -o match:".*rule 0/0\(match\): pass in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
238	    cat pflog.txt
239
240	# Second ping is blocked due to the state limit.
241	atf_check -o match:".*rule 0/0\(match\): block in on ${epair}a: 192.0.2.2 > 192.0.2.1: ICMP echo request.*" \
242	    cat pflog.txt
243
244	# At most three lines should be written: one for the first ping, and
245	# two for the second: one for the initial pass through the ruleset, and
246	# then a drop because of the state limit.  Ideally only the drop would
247	# be logged; if this is fixed, the count will be 2 instead of 3.
248	atf_check -o match:3 grep -c . pflog.txt
249}
250
251state_max_cleanup()
252{
253	pft_cleanup
254}
255
256atf_test_case "unspecified_v4" "cleanup"
257unspecified_v4_head()
258{
259	atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
260	atf_set require.user root
261}
262
263unspecified_v4_body()
264{
265	pflog_init
266
267	vnet_mkjail alcatraz
268	jexec alcatraz ifconfig lo0 inet 127.0.0.1
269	jexec alcatraz route add default 127.0.0.1
270
271	jexec alcatraz pfctl -e
272	jexec alcatraz ifconfig pflog0 up
273	pft_set_rules alcatraz "block log on lo0 to 0.0.0.0"
274
275	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
276	sleep 1 # Wait for tcpdump to start
277
278	atf_check -s not-exit:0 -o ignore -e ignore \
279	    jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0
280
281	atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \
282	    cat pflog.txt
283}
284
285unspecified_v4_cleanup()
286{
287	pft_cleanup
288}
289
290atf_test_case "unspecified_v6" "cleanup"
291unspecified_v6_head()
292{
293	atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
294	atf_set require.user root
295}
296
297unspecified_v6_body()
298{
299	pflog_init
300
301	vnet_mkjail alcatraz
302	jexec alcatraz ifconfig lo0 up
303	jexec alcatraz route -6 add ::0 ::1
304
305	jexec alcatraz pfctl -e
306	jexec alcatraz ifconfig pflog0 up
307	pft_set_rules alcatraz "block log on lo0 to ::0"
308
309	jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
310	sleep 1 # Wait for tcpdump to start
311
312	atf_check -s not-exit:0 -o ignore -e ignore \
313	    jexec alcatraz ping -6 -S ::1 -c 1 ::0
314
315	cat pflog.txt
316	atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \
317	    cat pflog.txt
318}
319
320unspecified_v6_cleanup()
321{
322	pft_cleanup
323}
324
325atf_test_case "rdr_action" "cleanup"
326rdr_head()
327{
328	atf_set descr 'Ensure that NAT rule actions are logged correctly'
329	atf_set require.user root
330}
331
332rdr_action_body()
333{
334	pflog_init
335
336	j="pflog:rdr_action"
337	epair_c=$(vnet_mkepair)
338	epair_srv=$(vnet_mkepair)
339
340	vnet_mkjail ${j}srv ${epair_srv}a
341	vnet_mkjail ${j}gw ${epair_srv}b ${epair_c}a
342	vnet_mkjail ${j}c ${epair_c}b
343
344	jexec ${j}srv ifconfig ${epair_srv}a 198.51.100.1/24 up
345	# No default route in srv jail, to ensure we're NAT-ing
346	jexec ${j}gw ifconfig ${epair_srv}b 198.51.100.2/24 up
347	jexec ${j}gw ifconfig ${epair_c}a 192.0.2.1/24 up
348	jexec ${j}gw sysctl net.inet.ip.forwarding=1
349	jexec ${j}c ifconfig ${epair_c}b 192.0.2.2/24 up
350	jexec ${j}c route add default 192.0.2.1
351
352	jexec ${j}gw pfctl -e
353	jexec ${j}gw ifconfig pflog0 up
354	pft_set_rules ${j}gw \
355		"rdr log on ${epair_srv}b proto tcp from 198.51.100.0/24 to any port 1234 -> 192.0.2.2 port 1234" \
356		"block quick inet6" \
357		"pass in log"
358
359	jexec ${j}gw tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> ${PWD}/pflog.txt &
360	sleep 1 # Wait for tcpdump to start
361
362	# send a SYN to catch in the log
363	jexec ${j}srv nc -N -w 0 198.51.100.2 1234
364
365	echo "Log"
366	cat ${PWD}/pflog.txt
367
368	# log line generated for rdr hit (pre-NAT)
369	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\].*" \
370	    cat pflog.txt
371
372	# log line generated for pass hit (post-NAT)
373	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\].*" \
374	    cat pflog.txt
375
376	# only two log lines shall be written
377	atf_check -o match:2 grep -c . pflog.txt
378}
379
380rdr_action_cleanup()
381{
382	pft_cleanup
383}
384
385atf_init_test_cases()
386{
387	atf_add_test_case "malformed"
388	atf_add_test_case "matches"
389	atf_add_test_case "matches_logif"
390	atf_add_test_case "state_max"
391	atf_add_test_case "unspecified_v4"
392	atf_add_test_case "unspecified_v6"
393	atf_add_test_case "rdr_action"
394}
395