xref: /freebsd/tests/sys/netpfil/pf/ether.sh (revision e3aa18ad)
1# $FreeBSD$
2#
3# SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4#
5# Copyright © 2021. Rubicon Communications, LLC (Netgate). All Rights Reserved.
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
30atf_test_case "mac" "cleanup"
31mac_head()
32{
33	atf_set descr 'Test MAC address filtering'
34	atf_set require.user root
35}
36
37mac_body()
38{
39	pft_init
40
41	epair=$(vnet_mkepair)
42	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
43
44	ifconfig ${epair}a 192.0.2.1/24 up
45
46	vnet_mkjail alcatraz ${epair}b
47	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
48
49	pft_set_rules alcatraz \
50		"ether block from ${epair_a_mac}"
51
52	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
53
54	# Now enable. Ping should fail.
55	jexec alcatraz pfctl -e
56
57	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
58
59	# Should still fail for 'to'
60	pft_set_rules alcatraz \
61		"ether block to ${epair_a_mac}"
62	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
63
64	# Succeeds if we block a different MAC address
65	pft_set_rules alcatraz \
66		"ether block to 00:01:02:03:04:05"
67	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
68
69	# Should still fail for 'to', even if it's in a list
70	pft_set_rules alcatraz \
71		"ether block to { ${epair_a_mac}, 00:01:02:0:04:05 }"
72	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
73
74	# Now try this with an interface specified
75	pft_set_rules alcatraz \
76		"ether block on ${epair}b from ${epair_a_mac}"
77	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
78
79	# Wrong interface should not match
80	pft_set_rules alcatraz \
81		"ether block on ${epair}a from ${epair_a_mac}"
82	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
83
84	# Test negation
85	pft_set_rules alcatraz \
86		"ether block in on ${epair}b from ! ${epair_a_mac}"
87	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
88
89	pft_set_rules alcatraz \
90		"ether block out on ${epair}b to ! ${epair_a_mac}"
91	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
92
93	# Block everything not us
94	pft_set_rules alcatraz \
95		"ether block out on ${epair}b to { ! ${epair_a_mac} }"
96	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
97
98	# Block us now
99	pft_set_rules alcatraz \
100		"ether block out on ${epair}b to { ! 00:01:02:03:04:05 }"
101	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
102
103	# Block with a masked address
104	pft_set_rules alcatraz \
105		"ether block out on ${epair}b to { ! 00:01:02:03:00:00/32 }"
106	jexec alcatraz pfctl -se
107	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
108
109	epair_prefix=$(echo $epair_a_mac | cut -c-8)
110	pft_set_rules alcatraz \
111		"ether block out on ${epair}b to { ${epair_prefix}:00:00:00/24 }"
112	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
113
114	pft_set_rules alcatraz \
115		"ether block out on ${epair}b to { ${epair_prefix}:00:00:00&ff:ff:ff:00:00:00 }"
116	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
117
118	# Check '-F ethernet' works
119	jexec alcatraz pfctl -F ethernet
120	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
121}
122
123mac_cleanup()
124{
125	pft_cleanup
126}
127
128atf_test_case "proto" "cleanup"
129proto_head()
130{
131	atf_set descr 'Test EtherType filtering'
132	atf_set require.user root
133}
134
135proto_body()
136{
137	pft_init
138
139	epair=$(vnet_mkepair)
140	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
141
142	ifconfig ${epair}a 192.0.2.1/24 up
143
144	vnet_mkjail alcatraz ${epair}b
145	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
146
147	pft_set_rules alcatraz \
148		"ether block proto 0x0810"
149	jexec alcatraz pfctl -e
150
151	atf_check -s exit:0 -o ignore ping -c 1 -t 1 192.0.2.2
152
153	# Block IP
154	pft_set_rules alcatraz \
155		"ether block proto 0x0800"
156	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
157
158	# Block ARP
159	pft_set_rules alcatraz \
160		"ether block proto 0x0806"
161	arp -d 192.0.2.2
162	atf_check -s exit:2 -o ignore ping -c 1 -t 1 192.0.2.2
163}
164
165proto_cleanup()
166{
167	pft_cleanup
168}
169
170atf_test_case "direction" "cleanup"
171direction_head()
172{
173	atf_set descr 'Test directionality of ether rules'
174	atf_set require.user root
175	atf_set require.progs jq
176}
177
178direction_body()
179{
180	pft_init
181
182	epair=$(vnet_mkepair)
183	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
184	epair_b_mac=$(ifconfig ${epair}b ether | awk '/ether/ { print $2; }')
185
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
191	pft_set_rules alcatraz \
192		"ether block in proto 0x0806"
193	jexec alcatraz pfctl -e
194
195	arp -d 192.0.2.2
196	jexec alcatraz arp -d 192.0.2.1
197
198	# We don't allow the jail to receive ARP requests, so if we try to ping
199	# from host to jail the host can't resolve the MAC address
200	ping -c 1 -t 1 192.0.2.2
201
202	mac=$(arp -an --libxo json \
203	    | jq '."arp"."arp-cache"[] |
204	    select(."ip-address"=="192.0.2.2")."mac-address"')
205	atf_check_not_equal "$mac" "$epair_b_mac"
206
207	# Clear ARP table again
208	arp -d 192.0.2.2
209	jexec alcatraz arp -d 192.0.2.1
210
211	# However, we allow outbound ARP, so the host will learn our MAC if the
212	# jail tries to ping
213	jexec alcatraz ping -c 1 -t 1 192.0.2.1
214
215	mac=$(arp -an --libxo json \
216	    | jq '."arp"."arp-cache"[] |
217	    select(."ip-address"=="192.0.2.2")."mac-address"')
218	atf_check_equal "$mac" "$epair_b_mac"
219
220	# Now do the same, but with outbound ARP blocking
221	pft_set_rules alcatraz \
222		"ether block out proto 0x0806"
223
224	# Clear ARP table again
225	arp -d 192.0.2.2
226	jexec alcatraz arp -d 192.0.2.1
227
228	# The jail can't send ARP requests to us, so we'll never learn our MAC
229	# address
230	jexec alcatraz ping -c 1 -t 1 192.0.2.1
231
232	mac=$(jexec alcatraz arp -an --libxo json \
233	    | jq '."arp"."arp-cache"[] |
234	    select(."ip-address"=="192.0.2.1")."mac-address"')
235	atf_check_not_equal "$mac" "$epair_a_mac"
236}
237
238direction_cleanup()
239{
240	pft_cleanup
241}
242
243atf_test_case "captive" "cleanup"
244captive_head()
245{
246	atf_set descr 'Test a basic captive portal-like setup'
247	atf_set require.user root
248}
249
250captive_body()
251{
252	# Host is client, jail 'gw' is the captive portal gateway, jail 'srv'
253	# is a random (web)server. We use the echo protocol rather than http
254	# for the test, because that's easier.
255	pft_init
256
257	epair_gw=$(vnet_mkepair)
258	epair_srv=$(vnet_mkepair)
259	epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }')
260
261	vnet_mkjail gw ${epair_gw}b ${epair_srv}a
262	vnet_mkjail srv ${epair_srv}b
263
264	ifconfig ${epair_gw}a 192.0.2.2/24 up
265	route add -net 198.51.100.0/24 192.0.2.1
266	jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up
267	jexec gw ifconfig lo0 127.0.0.1/8 up
268	jexec gw sysctl net.inet.ip.forwarding=1
269
270	jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up
271	jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up
272	jexec srv route add -net 192.0.2.0/24 198.51.100.1
273
274	# Sanity check
275	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
276
277	pft_set_rules gw \
278		"ether pass quick proto 0x0806" \
279		"ether pass tag captive" \
280		"rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo"
281	jexec gw pfctl -e
282
283	# ICMP should still work, because we don't redirect it.
284	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
285
286	# Run the echo server only on the gw, so we know we've redirectly
287	# correctly if we get an echo message.
288	jexec gw /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf
289
290	# Confirm that we're getting redirected
291	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7"
292
293	jexec gw killall inetd
294
295	# Now pretend we've authenticated, so add the client's MAC address
296	pft_set_rules gw \
297		"ether pass quick proto 0x0806" \
298		"ether pass quick from ${epair_gw_a_mac}" \
299		"ether pass tag captive" \
300		"rdr on ${epair_gw}b proto tcp to port echo tagged captive -> 127.0.0.1 port echo"
301
302	# No redirect, so failure.
303	atf_check -s exit:1 -x "echo foo | nc -N 198.51.100.2 7"
304
305	# Start a server in srv
306	jexec srv /usr/sbin/inetd $(atf_get_srcdir)/echo_inetd.conf
307
308	# And now we can talk to that one.
309	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 7"
310}
311
312captive_cleanup()
313{
314	pft_cleanup
315}
316
317atf_test_case "captive_long" "cleanup"
318captive_long_head()
319{
320	atf_set descr 'More complex captive portal setup'
321	atf_set require.user root
322}
323
324captive_long_body()
325{
326	# Host is client, jail 'gw' is the captive portal gateway, jail 'srv'
327	# is a random (web)server. We use the echo protocol rather than http
328	# for the test, because that's easier.
329	dummynet_init
330
331	epair_gw=$(vnet_mkepair)
332	epair_srv=$(vnet_mkepair)
333	epair_gw_a_mac=$(ifconfig ${epair_gw}a ether | awk '/ether/ { print $2; }')
334
335	vnet_mkjail gw ${epair_gw}b ${epair_srv}a
336	vnet_mkjail srv ${epair_srv}b
337
338	ifconfig ${epair_gw}a 192.0.2.2/24 up
339	route add -net 198.51.100.0/24 192.0.2.1
340	jexec gw ifconfig ${epair_gw}b 192.0.2.1/24 up
341	jexec gw ifconfig lo0 127.0.0.1/8 up
342	jexec gw sysctl net.inet.ip.forwarding=1
343
344	jexec gw ifconfig ${epair_srv}a 198.51.100.1/24 up
345	jexec srv ifconfig ${epair_srv}b 198.51.100.2/24 up
346	jexec srv route add -net 192.0.2.0/24 198.51.100.1
347
348	jexec gw dnctl pipe 1 config bw 300KByte/s
349
350	# Sanity check
351	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
352
353	pft_set_rules gw \
354		"ether anchor \"captiveportal\" on { ${epair_gw}b } {" \
355			"ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \
356			"ether pass tag \"captive\"" \
357		"}" \
358		"rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo"
359	jexec gw pfctl -e
360
361	# ICMP should still work, because we don't redirect it.
362	atf_check -s exit:0 -o ignore ping -c 1 -t 1 198.51.100.2
363
364	jexec gw /usr/sbin/inetd -p gw.pid $(atf_get_srcdir)/echo_inetd.conf
365	jexec srv /usr/sbin/inetd -p srv.pid $(atf_get_srcdir)/daytime_inetd.conf
366
367	echo foo | nc -N 198.51.100.2 13
368
369	# Confirm that we're getting redirected
370	atf_check -s exit:0 -o match:"^foo$" -x "echo foo | nc -N 198.51.100.2 13"
371
372	# Now update the rules to allow our client to pass without redirect
373	pft_set_rules gw \
374		"ether anchor \"captiveportal\" on { ${epair_gw}b } {" \
375			"ether pass quick proto { 0x0806, 0x8035, 0x888e, 0x88c7, 0x8863, 0x8864 }" \
376			"ether pass quick from { ${epair_gw_a_mac} } dnpipe 1" \
377			"ether pass tag \"captive\"" \
378		"}" \
379		"rdr on ${epair_gw}b proto tcp to port daytime tagged captive -> 127.0.0.1 port echo"
380
381	# We're not being redirected and get datime information now
382	atf_check -s exit:0 -o match:"^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)" -x "echo foo | nc -N 198.51.100.2 13"
383
384	jexec gw killall inetd
385	jexec srv killall inetd
386}
387
388captive_long_cleanup()
389{
390	pft_cleanup
391}
392
393atf_test_case "dummynet" "cleanup"
394dummynet_head()
395{
396	atf_set descr 'Test dummynet for L2 traffic'
397	atf_set require.user root
398}
399
400dummynet_body()
401{
402	pft_init
403
404	if ! kldstat -q -m dummynet; then
405		atf_skip "This test requires dummynet"
406	fi
407
408	epair=$(vnet_mkepair)
409	vnet_mkjail alcatraz ${epair}b
410
411	ifconfig ${epair}a 192.0.2.1/24 up
412	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
413
414	# Sanity check
415	atf_check -s exit:0 -o ignore ping -i .1 -c 3 -s 1200 192.0.2.2
416
417	jexec alcatraz dnctl pipe 1 config bw 30Byte/s
418	jexec alcatraz pfctl -e
419	pft_set_rules alcatraz \
420		"ether pass in dnpipe 1"
421
422	# single ping succeeds just fine
423	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
424
425	# Saturate the link
426	ping -i .1 -c 5 -s 1200 192.0.2.2
427
428	# We should now be hitting the limits and get this packet dropped.
429	atf_check -s exit:2 -o ignore ping -c 1 -s 1200 192.0.2.2
430
431	# We can now also dummynet outbound traffic!
432	pft_set_rules alcatraz \
433		"ether pass out dnpipe 1"
434
435	# We should still be hitting the limits and get this packet dropped.
436	atf_check -s exit:2 -o ignore ping -c 1 -s 1200 192.0.2.2
437}
438
439dummynet_cleanup()
440{
441	pft_cleanup
442}
443
444atf_test_case "anchor" "cleanup"
445anchor_head()
446{
447	atf_set descr 'Test ether anchors'
448	atf_set require.user root
449}
450
451anchor_body()
452{
453	pft_init
454
455	epair=$(vnet_mkepair)
456	epair_a_mac=$(ifconfig ${epair}a ether | awk '/ether/ { print $2; }')
457
458	vnet_mkjail alcatraz ${epair}b
459
460	ifconfig ${epair}a 192.0.2.1/24 up
461	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
462
463	# Sanity check
464	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
465
466	jexec alcatraz pfctl -e
467	pft_set_rules alcatraz \
468		"ether anchor \"foo\" in on lo0 {" \
469			"ether block" \
470		"}"
471
472	# That only filters on lo0, so we should still be able to pass traffic
473	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
474
475	pft_set_rules alcatraz \
476		"ether block in" \
477		"ether anchor \"foo\" in on ${epair}b {" \
478			"ether pass" \
479		"}"
480	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
481
482	pft_set_rules alcatraz \
483		"ether pass" \
484		"ether anchor \"bar\" in on ${epair}b {" \
485			"ether block" \
486		"}"
487	atf_check -s exit:2 -o ignore ping -c 1 -t 2 192.0.2.2
488
489	pft_set_rules alcatraz \
490		"ether block in" \
491		"ether anchor \"baz\" on ${epair}b {" \
492			"ether pass in from 01:02:03:04:05:06" \
493		"}" \
494		"ether pass in from ${epair_a_mac}"
495	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
496
497	atf_check -s exit:0 -o match:'baz' jexec alcatraz pfctl -sA
498}
499
500anchor_cleanup()
501{
502	pft_cleanup
503}
504
505atf_test_case "ip" "cleanup"
506ip_head()
507{
508	atf_set descr 'Test filtering based on IP source/destination'
509	atf_set require.user root
510}
511
512ip_body()
513{
514	pft_init
515
516	epair=$(vnet_mkepair)
517
518	vnet_mkjail alcatraz ${epair}b
519
520	ifconfig ${epair}a 192.0.2.1/24 up
521	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
522
523	# Sanity check
524	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
525
526	jexec alcatraz pfctl -e
527	pft_set_rules alcatraz \
528		"ether pass" \
529		"ether block in l3 from 192.0.2.1"
530
531	atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2
532
533	# Change IP address and we can ping again
534	ifconfig ${epair}a 192.0.2.3/24 up
535	atf_check -s exit:0 -o ignore ping -c 1 192.0.2.2
536
537	# Test the 'to' keyword too
538	pft_set_rules alcatraz \
539		"ether pass" \
540		"ether block out l3 to 192.0.2.3"
541	atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2
542
543	# Test table
544	pft_set_rules alcatraz \
545		"table <tbl> { 192.0.2.3 }" \
546		"ether pass" \
547		"ether block out l3 to <tbl>"
548	atf_check -s exit:2 -o ignore ping -c 1 192.0.2.2
549}
550
551ip_cleanup()
552{
553	pft_cleanup
554}
555
556atf_init_test_cases()
557{
558	atf_add_test_case "mac"
559	atf_add_test_case "proto"
560	atf_add_test_case "direction"
561	atf_add_test_case "captive"
562	atf_add_test_case "captive_long"
563	atf_add_test_case "dummynet"
564	atf_add_test_case "anchor"
565	atf_add_test_case "ip"
566}
567