class: center, middle # A Few of My Favorite Things About The OpenBSD Packet Filter Tools #### A Sunday Lunch at [SEMIBUG](https://semibug.org/) #### September 25, 2022 ### Peter N. M. Hansteen This presentation: [home.nuug.no/~peter/pf_favorite/](https://home.nuug.no/~peter/pf_favorite/) or [full text](https://bsdly.blogspot.com/2022/09/a-few-of-my-favorite-things-about.html) [trackerless](https://nxdomain.no/~peter/better_off_with_pf.html) --- # So who am I and what can I offer? I am Peter N. M. Hansteen
In information technology since the late 1980s, OpenBSD user since OpenBSD 2.5 (1999) Senior Tech, Tietoevry Create Cloud (;D) -- Unix/Linux sysadmin, networker Wrote [The Book of PF](https://www.nostarch.com/pf3), and [OpenBSD](https://www.openbsd.org/) has frequently made my life better. This presentation is about some of those moments. From The Other West Coast (Bergen, Norway) --- # A Few of My Favorite Things About The OpenBSD Packet Filter Tools The [OpenBSD](https://www.openbsd.org/) *Packet Filter* subsystem was introduced in [OpenBSD 3.0](https://www.openbsd.org/30.html) We'll take a look at some features of PF I have enjoyed using. The initial PF version was very close in syntax to the system it replaced, but even the *working prototype* performed better Improvements came quickly - NAT (*network address translation*) moved into **pf.conf** in [OpenBSD 3.1](https://www.openbsd.org/31.html) * [OpenBSD 3.2](https://www.openbsd.org/32.html) moved ALTQ *traffic shaping* to **pf.conf**, also introduced *anchors* (named sub-rulesets), *tables*, and *[spamd](https://man.openbsd.org/spamd>spamd(8))* There's more, we'll get back to those. --- # PF Rulesets: The Basics So how do we write the *perfect* ruleset? This one is totally *secure*: ``` block ``` which actually loads as ``` block drop all ``` - will not let *any* traffic through. Also, virtually unplugged from the rest of the world. --- # PF Rulesets: The basics (cont'd) The basic building blocks of a PF rule: **verb** _criteria_ **actions** ... options ```shell pass in on egress proto tcp to egress port ssh ``` => Packets arriving on **egress** (the *interface group* with a default route) will pass *in* to members of that interface group => You can refer to well known services by name, see eg **[/etc/services](https://man.openbsd.org/services)** Other default interface groups on OpenBSD include **wlan** (all configured WiFI interfaces). You can create your own as needed, using **[ifconfig(8)](https://man.openbsd.org/ifconfig)** and refer to them in your rules --- # PF Rulesets: The basics (cont'd) ```shell match out on egress nat-to egress ``` *match*es outgoing traffic on *egress*, applies **nat-to** on outgoing. Typical on a gateway for a small IPv4 network with one or few allocated routeable addresses The *match* keyword appeared in [OpenBSD 4.6](https://www.openbsd.org/46.html), seemed like a *prelude* to more extensive changes to come. Next up we have a variation on the *totally secure* ruleset: ```shell block all ``` But wait, --- # PF Basics: Rule evaluation order This ruleset ```shell pass in on egress proto tcp to egress port ssh match out on egress nat-to egress block all ``` is equivalent to ``` block ``` because **last match wins** --- # PF Basics: Rule evaluation order - Ruleset evaluated top to bottom - **Last match wins** (unless _quick_ is used) - Stateful by default - For more details, see [man pf.conf](https://man.openbsd.org/pf.conf) - https://man.openbsd.org/pf.conf --- # A basic ruleset This would work better ```shell block all match out on egress nat-to egress pass in on egress proto tcp to egress port ssh ``` and you would want to add further rules and use other features --- # More PF basics - Options **Options:** Set basic configuration parameters ```shell set limit states 100000 set debug debug set loginterface dc0 set timeout tcp.first 120 set timeout tcp.established 86400 set timeout { adaptive.start 6000, adaptive.end 12000 } ``` - For more details, see [man pf.conf](https://man.openbsd.org/pf.conf) - https://man.openbsd.org/pf.conf --- # More PF basics - Macros **Macros:** Content that will expand in place. A *readability* feature ```shell ext_if = "kue0" all_ifs = "{" $ext_if lo0 "}" pass out on $ext_if from any to any pass in on $ext_if proto tcp from any to any port 25 ``` Lists may expand to several rules in the *loaded* ruleset --- # More PF basics - Tables **Tables:** For IP addresses (and networks) exclusively. Referenced directly in rules: ```shell table <badhosts> persist counters file "/home/peter/badhosts" # ... block from <badhosts> ``` Also manipulate from the command line with [pfctl(8)](https://man.openbsd.org/pfctl): ```shell $ doas pfctl -t badhosts -T add 192.0.2.11 2001:db8::dead:beef:baad:f00d ``` Several deamons in [OpenBSD](https://www.openbsd.org/) base system can manage tables, such as [spamd](https://man.openbsd.org/spamd), [bgpd](https://man.openbsd.org/bgpd) and [dhcpd](https://man.openbsd.org/dhcpd). --- # More PF basics - Rules A basic host ruleset ```shell block pass from (self) ``` *self* is a builtin macro that expands to all configured local interfaces The ruleset could expand to ```shell $ doas pfctl -vnf /etc/pf.conf block drop all pass inet6 from ::1 to any flags S/SA pass on lo0 inet6 from fe80::1 to any flags S/SA pass on iwm0 inet6 from fe80::a2a8:cdff:fe63:abb9 to any flags S/SA pass inet6 from 2001:470:28:658:a2a8:cdff:fe63:abb9 to any flags S/SA pass inet6 from 2001:470:28:658:8c43:4c81:e110:9d83 to any flags S/SA pass inet from 127.0.0.1 to any flags S/SA pass inet from 192.168.103.126 to any flags S/SA ``` "[pfctl](https://man.openbsd.org/pfctl) **v**erbosely *parse* but do **n**ot load rules from the **f**ile [/etc/pf.conf](https://man.openbsd.org/pf.conf)" Recommended practice before actually loading a changed [pf.conf](https://man.openbsd.org/pf.conf). --- # PF: A Filtering Gateway A gateway forwards between networks. Enable from the command line: ```shell # sysctl net.inet.ip.forwarding=1 # sysctl net.inet6.ip6.forwarding=1 ``` Make permanent by adding to [/etc/sysctl.conf](https://man.openbsd.org/sysctl.conf): ```shell net.inet.ip.forwarding=1 net.inet6.ip6.forwarding=1 ``` Now you have forwarding, and can set up as a gateway. --- # PF: Basic ruleset A basic, likely incomplete, ruleset: ```shell ext_if=bge0 int_if=bge1 client_out = "{ ftp-data ftp ssh domain pop3, imaps nntp https }" udp_services = "{ domain ntp }" icmp_types = "echoreq unreach" match out on egress inet nat-to ($ext_if) block pass inet proto icmp all icmp-type $icmp_types keep state pass quick proto { tcp, udp } to port $udp_services keep state pass proto tcp from $int_if:network to port $client_out pass proto tcp to self port ssh ``` Keep your rulesets *readable*, preserve your sanity. --- # A Configuration That Learns and Adapts You can create a configuration that *learns* and *adapts* to network conditions. Using data the kernel keeps anyway, you can apply *state tracking options* and have your ruleset act on them. Case in point - SSH password guessers. ```shell table <bruteforce> persist block quick from <bruteforce> pass inet proto tcp from any to $localnet port $tcp_services \ flags S/SA keep state \ (max-src-conn 100, max-src-conn-rate 15/5, \ overload
flush global) ``` **max-src-conn**: Upper limit of connections from one host **max-src-conn-rate**: Upper speed limit for new connections, here 15 per 5 seconds **flush global**: Exceed either limit, we will also kill all your connections => more than 100 connections *or* more than 15 new over 5 seconds -> added to the table, blocked and connections cut. --- # Adaptive State Tracking: Generally Do Expire State tracking and *overload* tables are useful for other services too. A few examples for inspiration can be found in the article [Forcing the password gropers through a smaller hole with OpenBSD's PF queues](https://bsdly.blogspot.com/2017/04/forcing-password-gropers-through.html). **Do** remember to *expire* entries after a while (see [man pfctl](https://man.openbsd.org/pfctl) and look for *expire*) -- I now favor a few weeks over the classical 24 hours. Like *greytrapping* (more on that in a moment), this lets you build a configuration that *adapts* to network conditions and *learns* from the traffic it sees. The *buzzwordability* potential is enormous, I'm puzzled as to why none of the other *big names* imitated this and marketed to the max. --- # The Adaptive Firewall and the Greytrapping Game - [spamd(8)](http://man.openbsd.org/spamd) originally only tarpitted addresses from imported lists - Speaks enough SMTP to do [greylisting](https://en.wikipedia.org/wiki/Greylisting) - Can tarpit known bad senders and generate blocklists by greytrapping - Default [spamd.conf](http://man.openbsd.org/spamd.conf) gives you one blocklist import and basics - (**Hint:** no real SMTP service required) - You can even generate your own blocklists by *greytrapping* via *non-deliverable* spamtrap addresses in your own domain(s) --- # Greytrapping, set up: - Set up tables - Divert traffic to the spamd process - Only let the "good guys" pass to the real SMTP daemon ```shell table <spamd-white> persist table <nospamd> persist file "/etc/mail/nospamd" pass in on egress proto tcp to any port smtp divert-to 127.0.0.1 port spamd pass in on egress proto tcp from <nospamd> to any port smtp pass in log on egress proto tcp from <spamd-white> to any port smtp pass out log on egress proto tcp to any port smtp ``` --- # Greytrapping, set up: Also edit [/etc/rc.conf.local](https://man.openbsd.org/rc.conf) ```shell spamd_flags="-v -G 2:8:864 -n "mailwalla 17.25" -c 1200 -C /etc/mail/fullchain.pem -K /etc/mail/privkey.pem -w 1 -y em1 -Y em1 -Y 158.36.191.225" spamdlogd_flags="-i em1 -Y 158.36.191.225" ``` The IP address is that of a sync partner. See the [spamd(8)](http://man.openbsd.org/spamd) man page for further options. You probably do not need to edit the [spamd.conf](http://man.openbsd.org/spamd.conf) file (unless you want other or more blocklists) Then enable and start using [rcctl(8)](http://man.openbsd.org/rcctl). See [In The Name Of Sane Email: Setting Up OpenBSD's spamd(8) With Secondary MXes](http://bsdly.blogspot.ca/2012/05/in-name-of-sane-email-setting-up-spamd.html) --- # Greylisting in practice I started greytrapping in 2007, by July 2007, I started to [publish](https://bsdly.blogspot.com/2007/07/hey-spammer-heres-list-for-you.html) both the *trapped* IP addresses and the *spamtraps*. *Trapped* IP addresses typically in the 1500 to 5000 range, exported once per hour, has been past the 20' mark Bizarrely, number of *spamtraps* is now over 300', still growing (see [The Things Spammers Believe - A Tale of 300,000 Imaginary Friends](https://bsdly.blogspot.com/2022/09/the-things-spammers-believe-tale-of.html)). It's absurd but fun. A few notes on care and feeding: [Maintaining A Publicly Available Blacklist](http://bsdly.blogspot.ca/2013/04/maintaining-publicly-available.html) --- # Traffic Shaping You Can Actually Understand *Traffic shaping is hard. Hard to do and hard to understand.* Traditionally, traffic shaping in BSDs were done with the *experimental* ALTQ code. ALTQ syntax was *inelegant at best*, and several **very** different algorithms with their own syntax were available, adding to confusion. OpenBSD, with a fixed six month release cycle, set about to replace ALTQ over several releases. --- # Traffic Shaping: Always-on Priorities First we got always-on, settable priorities. The scale is 0 (garbage) through 7 (want!) - Default for most traffic is 3. So if you want *all* ssh traffic to move ahead of other traffic you could do a ```shell pass proto tcp to port ssh set prio 6 ``` While this attempts to speed up transmission by prioritizing lowdelay packets, typically ACKs, ```shell match out on $ext_if proto tcp from $ext_if set prio (3, 7) match in on $ext_if proto tcp to $ext_if set prio (3, 7) ``` --- # Traffic Shaping: New Queues in OpenBSD 5.5 The [Hierarchical fair-service curve](https://en.wikipedia.org/wiki/Hierarchical_fair-service_curve) (HFSC) algorithm uses a *hierarchy* of queues, where only *leaf* queues can be assigned traffic: ```shell queue main on $ext_if bandwidth 20M queue defq parent main bandwidth 3600K default queue ftp parent main bandwidth 2000K queue udp parent main bandwidth 6000K queue web parent main bandwidth 4000K queue ssh parent main bandwidth 4000K queue ssh_interactive parent ssh bandwidth 800K queue ssh_bulk parent ssh bandwidth 3200K queue icmp parent main bandwidth 400K ``` --- # Traffic Shaping: Tying in Queues with match Tie in the queue *assignment* with **match** rules ```shell match log quick on $ext_if proto tcp to port ssh \ queue (ssh_bulk, ssh_interactive) match in quick on $ext_if proto tcp to port ftp queue ftp match in quick on $ext_if proto tcp to port www queue http match out on $ext_if proto udp queue udp match out on $ext_if proto icmp queue icmp ``` It is also possible to tack on **queue somethingorother** at the end of **pass** rules --- # Traffic Shaping: Flexibility and Bursts You can have flexible allocations with *min* and *max* values, child queues borrow from parent allocations. ```shell 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 ``` *burst* values allow for shorter bursts, here for 3 seconds. *qlimit* sets the size of the buffer. Larger buffers *may* lead to delays. --- # Traffic shaping: systat queues Live view of queues ```shell 1 users Load 2.56 2.27 2.28 skapet.bsdly.net 20:55:50 QUEUE BW SCH PRI PKTS BYTES DROP_P DROP_B QLEN BOR SUS P/S B/S rootq on bge0 20M 0 0 0 0 0 0 0 main 20M 0 0 0 0 0 0 0 qdef 9M 6416363 2338M 136 15371 0 462 30733 qweb 9M 431590 144565K 0 0 0 0.6 480 qpri 2M 2854556 181684K 5 390 0 79 5243 qdns 100K 802874 68379K 0 0 0 0.6 52 spamd 1K 596022 36021K 1177533 72871514 299 2 136 ``` Notice the last. I call that *mission accomplished*. --- # Things I Chose Not to Cover The OpenBSD PF toolset is large. Some other goodies are - [carp(4)](http://man.openbsd.org/carp) based redundancy for highly available service - [relayd(8)](http://man.openbsd.org/relayd) for load balancing, application delivery and general network trickery - PF logs and the fact that [tcpdump(8)](http://man.openbsd.org/tcpdump) is your friend Please explore those and others via the literature given in the *Resources* section at the end. --- # Who Else Uses PF Today? PF has been ported from the original [OpenBSD](https://www.openbsd.org) to the other BSDs, including [FreeBSD](https://www.freebsd.org/) [NetBSD](https://www.netbsd.org/) [DragonFlyBSD](https://www.dragonflybsd.org/) [Apple](https://www.apple.com/)'s MacOSX, IOS (via [FreeBSD](https://www.freebsd.org/)) Blackberry (via [NetBSD](https://www.netbsd.org/)) [Oracle](https://www.oracle.com/), in [Solaris 11.3](http://www.oracle.com/us/products/servers-storage/solaris/solaris11/overview/index.html) as one of two options, from *Solaris 12* the [only packet filter](https://marc.info/?l=openbsd-tech&m=142822852613581&w=2), replacing IPF. Also see this [blog post](http://bsdly.blogspot.ca/2015/04/solaris-admins-for-glimpse-of-your.html). Other than Oracle with Solaris, most ports happened before the OpenBSD 4.7 NAT rewrite and have kept the previous syntax. There are probably others (they don't need to actively advertise the fact) --- # Resources for further exploration [The PF User's Guide](https://www.openbsd.org/faw/pf/) Peter N. M. Hansteen: [The Book of PF, 3rd ed.](https://www.nostarch.com/pf3) Michael W Lucas: [Absolute OpenBSD, 2nd ed.](https://www.nostarch.com/openbsd2e) [OpenBSD Journal](https://undeadly.org/) News items about [OpenBSD](https://www.openbsd.org/), generally short with references to material elsewhere. [bsdly.blogspot.com](https://bsdly.blogspot.com/) Blog posts by Peter Hansteen This presentation: [home.nuug.no/~peter/pf_favorite/](https://home.nuug.no/~peter/pf_favorite/)