pf(4) is the OpenBSD packet filter that provides stateful packet filtering and network address translation (NAT). It is used in OpenBSD, FreeBSD, NetBSD, DragonflyBSD, and other systems. Some key features of pf include its flexible rule syntax, atomic ruleset updates, integrated traffic shaping, and ability to divert packets to userspace processes like spamd for inspection. It provides logging in tcpdump format and can integrate with CARP and other services. The pf code was developed for OpenBSD after the previous IPFilter code was removed due to licensing issues.
2. What is pf ?
Packet filtering is the selective passing or blocking of data packets as
they pass through a network interface
The criteria that pf(4) uses when inspecting packets are based on the
Layer 3 (IPv4 and IPv6) and Layer 4 (TCP, UDP, ICMP, and ICMPv6)
headers
last rule wins
3. Why you should use pf ?
syntax (not a pain with lot of rules; macros, tables, interface groups)
atomic rule set commit
integrated traffic shaper
log files in tcpdump(8) format
carp(4)
spamd(8) and relayd(8) integration
proxies in user space, not in kernel space
4. pf(4) story
OpenBSD up to 2.9 used Darren Reed’s IPFilter
IPFilter was almost, but not quite BSD licensed - no right to distribute
changed versions
IPFilter removed on May 29th, 2001
First commit of the new PF code June 24, 2001
5. Who uses pf(4) ?
OpenBSD (upstream development)
FreeBSD, NetBSD and DragonflyBSD
Apple MacOSX and IOS (via FreeBSD)
Oracle in Solaris 11.3 and 12 (IPF replaced)
Blackberry (via NetBSD)
6. network interfaces in *BSD world
interface name is similar to the driver’s name and has some ”properties”
all interfaces can be part of a group (enc,pppx,trunk,vlan,wlan, ...)
the group egress is automatically set to the interface you are using for
outbound connections
7. network interfaces in *BSD world
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 32768
index 4 priority 0 llprio 3
groups: lo
inet6 ::1 prefixlen 128
inet6 fe80::1%lo0 prefixlen 64 scopeid 0x4
inet 127.0.0.1 netmask 0xff000000
em0: flags=8b43<UP,BROADCAST,RUNNING,PROMISC,ALLMULTI,SIMPLEX,MULTICAST> mtu 1500
lladdr b8:6b:23:7a:3c:8a
index 1 priority 0 llprio 3
trunk: trunkdev trunk0
media: Ethernet autoselect (none)
status: no carrier
iwm0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
lladdr b8:6b:23:7a:3c:8a
index 2 priority 4 llprio 3
trunk: trunkdev trunk0
groups: wlan
media: IEEE802.11 autoselect (HT-MCS14 mode 11n)
status: active
ieee80211: nwid XXX chan 112 bssid XXX 79% wpakey <not displayed> wpaprotos wpa2 wpaakms psk ...
enc0: flags=0<>
index 3 priority 0 llprio 3
groups: enc
status: active
trunk0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr b8:6b:23:7a:3c:8a
index 5 priority 0 llprio 3
trunk: trunkproto failover
trunkport iwm0 active
trunkport em0 master
groups: trunk egress
media: Ethernet autoselect
status: active
inet 192.168.11.32 netmask 0xffffff00 broadcast 192.168.11.255
pflog0: flags=141<UP,RUNNING,PROMISC> mtu 33144
index 6 priority 0 llprio 3
groups: pflog
8. pf.conf(5)
macros
tables (can be used with pfctl -t $table -T add $addr)
divert(4) to pass packets to userland for further inspection
ftp-proxy(8)
squid(8)
spamd(8)
anchors
tags
9. pf.conf(5)
clients = "192.168.11/24"
tcp_ports = "{ ftp, ssh, domain, ntp, whois, www, https, auth, nntp, imaps,
rtsp, submission, 8080:8082 }"
udp_ports = "{domain, ntp}"
match out on egress inet nat-to (egress)
block log all
pass inet proto tcp from $clients to port $tcp_ports
pass inet proto udp from $clients to port $udp_ports
10. pf.conf(5)
queue rootq on $ext_if bandwidth 20M
queue main parent rootq bandwidth 20479K min 1M max 20479K qlimit 100
queue qdef parent main bandwidth 9600K min 6000K max 18M default
queue qweb parent main bandwidth 9600K min 6000K max 18M
queue qpri parent main bandwidth 700K min 100K max 1200K
queue qdns parent main bandwidth 200K min 12K burst 600K for 3000ms
queue spamd parent rootq bandwidth 1K min 0K max 1K qlimit 300
match out on $ext_if proto tcp to port { www https }
set queue (qweb, qpri) set prio (5,6)
match out on $ext_if proto { tcp udp } to port domain
set queue (qdns, qpri) set prio (6,7)
match out on $ext_if proto icmp
set queue (qdns, qpri) set prio (6,7)
pass in log on egress proto tcp to port smtp
divert-to 127.0.0.1 port spamd set queue spamd set prio 0
11. spamd(8)
table <spamd-white> persist
table <nospamd> persist file "/etc/mail/nospamd"
pass in log on egress proto tcp to port smtp
divert-to 127.0.0.1 port spamd
pass in log on egress proto tcp from <nospamd> to port smtp
pass in log on egress proto tcp from <spamd-white> to port smtp
pass out log on egress proto tcp to port smtp
12. spamd(8)
Aug 8 09:08:35 home spamd[77105]: 114.36.210.200: connected (1/0)
Aug 8 09:08:47 home spamd[2758]: new entry 114.36.210.200 from <support@microsoft.com> to <support@microsoft.com>,
helo 79.22.69.186
Aug 8 09:08:47 home spamd[77105]: 114.36.210.200: disconnected after 12 seconds.
Aug 8 09:48:20 home spamd[77105]: 186.225.243.130: connected (1/1), lists: nixspam
Aug 8 09:49:16 home spamd[77105]: 186.225.243.130: disconnected after 56 seconds. lists: nixspam
Aug 8 09:50:25 home spamd[77105]: 47.89.53.55: connected (1/0)
Aug 8 09:50:38 home spamd[2758]: new entry 47.89.53.55 from <cycnvoeyildos@163.com> to <gogo@linwayedm.com.tw>,
helo 79.22.69.186
Aug 8 09:50:38 home spamd[77105]: 47.89.53.55: disconnected after 13 seconds.
Aug 8 09:50:39 home spamd[77105]: 47.89.53.55: connected (1/0)
Aug 8 09:50:46 home spamd[77105]: 121.228.209.208: connected (2/0)
Aug 8 09:50:51 home spamd[2758]: new entry 47.89.53.55 from <mfjtuaukmnuj@163.com> to <gogo@linwayedm.com.tw>,
helo 79.22.69.186
Aug 8 09:50:51 home spamd[77105]: 47.89.53.55: disconnected after 12 seconds.
Aug 8 09:50:52 home spamd[77105]: 47.89.53.55: connected (2/0)
Aug 8 09:51:04 home spamd[2758]: new entry 47.89.53.55 from <bhuehnlslwiaw@163.com> to <gogo@linwayedm.com.tw>,
helo 79.22.69.186
Aug 8 09:51:05 home spamd[77105]: 47.89.53.55: disconnected after 13 seconds.
14. log files
log files in binary, tcpdump(8) readable format
pflog(4) is a cloneable interface, rules can log to a specific interface
pass log (all, to pflog2) inet proto tcp from $mailserver to port smtp
15. log files
Mar 12 15:33:49.592419 rule 0/(match) [uid 0, pid 8015] block out on trunk0:
[uid 4294967295, pid 100000]
192.168.11.32.43849 > 216.58.205.165.25: S 1629842603:1629842603(0) win 16384
<mss 1460,nop,nop,sackOK,nop,wscale 6,nop,nop,timestamp 1140560815[|tcp]> (DF)
(ttl 64, id 13397, len 64, bad ip cksum 14! -> 94ba)
17. pfctl(8)
Status: Enabled for 0 days 01:24:26 Debug: err
State Table Total Rate
current entries 23
searches 24884 4.9/s
inserts 2078 0.4/s
removals 2055 0.4/s
Counters
match 2240 0.4/s
bad-offset 0 0.0/s
fragment 0 0.0/s
short 0 0.0/s
normalize 0 0.0/s
memory 0 0.0/s
bad-timestamp 0 0.0/s
congestion 0 0.0/s
ip-option 159 0.0/s
proto-cksum 0 0.0/s
state-mismatch 0 0.0/s
state-insert 0 0.0/s
state-limit 0 0.0/s
src-limit 0 0.0/s
synproxy 0 0.0/s
translate 0 0.0/s
no-route 0 0.0/s
18. systat(8)
4 users Load 1.39 1.60 1.58 fw.domain.net 15:47:44
LOCAL ADDRESS FOREIGN ADDRESS PROTO RECV-Q SEND-Q STATE
192.168.11.32:48285 mil04s28-in-f161.1e100.net:443 tcp 0 0 ESTABLISHED
192.168.11.32:33012 mil04s28-in-f163.1e100.net:443 tcp 0 0 ESTABLISHED
192.168.11.32:46466 mil04s26-in-f14.1e100.net:443 tcp 0 0 ESTABLISHED
192.168.11.32:19338 mil04s28-in-f174.1e100.net:443 tcp 0 0 ESTABLISHED
192.168.11.32:23282 74.125.111.135:443 tcp 0 0 ESTABLISHED
192.168.11.32:8453 server-54-192-25-25.mxp4.r.cloudfront.net:www tcp 0 0 TIME_WAIT
192.168.11.32:45682 host33-137-dynamic.26-79-r.retail.telecomital tcp 0 0 ESTABLISHED
192.168.11.32:43126 weber.freenode.net:6667 tcp 0 0 ESTABLISHED
192.168.11.32:22989 host69-172-177-94.serverdedicati.aruba.it:ntp udp 0 0
192.168.11.32:42148 213.251.52.250:ntp udp 0 0
19. pf(4)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <net/if.h>
#include <net/pfvar.h>
#include <stdio.h>
#include <err.h>
int main(int argc, char *argv[]) {
int dev;
dev = open("/dev/pf", O_RDWR);
if (dev == -1)
err(1, "open("/dev/pf") failed");
if (ioctl(dev, DIOCSTOP))
err(1, "DIOCSTOP");
else
printf("pf disabledn");
return 0;
}
20. Need some more info ?
man pages about pf(4), pfctl(8) and pf.conf(5)
man pages about tcpdump(8) and pcap-filter(3)
https://www.openbsd.org/faq/pf/index.html
https://home.nuug.no/˜peter/pf/newest/
https://www.freebsd.org/doc/en/books/handbook/firewalls-pf.html