Mac OS X,  Mac OS X Server,  Mac Security,  Mass Deployment

A Cheat Sheet For Using pf in OS X Lion and Up

I’ve done plenty of writing on the Application Layer Firewall (ALF) and the IP FireWall (IPFW) in OS X over the years. There will be more on ALF coming in “July” but in the meantime, there’s something I hadn’t written much about in Lion and that’s the pf implementation.


To get started, let’s look at the /etc/pf.conf configuration file that comprises pf:

scrub-anchor "com.apple/*"
nat-anchor "com.apple/*"
rdr-anchor "com.apple/*"
dummynet-anchor "com.apple/*"
anchor "com.apple/*"
load anchor "com.apple" from "/etc/pf.anchors/com.apple"

Here, you can see that pf is configured with a number of anchors. An anchor is a collection of rules and tables. Basically, the anchor file being loaded is /etc/pf.anchors/com.apple. In here, we see some rules (without comments):

scrub-anchor "100.InternetSharing/*"
scrub-anchor "300.NetworkLinkConditioner/*"
nat-anchor "100.InternetSharing/*"
rdr-anchor "100.InternetSharing/*"
anchor "100.InternetSharing/*"
anchor "200.AirDrop/*"
anchor "250.ApplicationFirewall/*"
dummynet-anchor "300.NetworkLinkConditioner/*"
anchor "300.NetworkLinkConditioner/*"
anchor "400.AdaptiveFirewall/*"
load anchor "400.AdaptiveFirewall/" from "/Applications/Server.app/Contents/ServerRoot/private/etc/pf.anchors/400.AdaptiveFirewall"

These are mostly just allowing the Apple services to work with services enabled in the Sharing system preference pane, etc. The scrub options are pretty cool as it cleans dirty packets prior to passing them to their destination. To see how the rules are interpreted, let’s run pfctl with the -sa option, which shows all information/stats:

sudo pfctl -sa

Here we see information like stats on timeouts, limits to rules, etc. Let’s look at the rules specifically:

sudo pfctl -sr

Now let’s load a line below the previously called anchors in the first file:

pass in quick on lo0 all
pass out quick on lo0 all

This is going to always allow local traffic, which we need for a few internal processes. Then let’s block some stuff (after all, if we’re not filtering, why use a packet filter). First add the following to the pf.conf file to block all otherwise allowed incoming sockets:

block in all

And this one for outbound traffic:

block out all

Or to knock the two above lines out with one:

block all

Then to do something pretty straight forward, like allow incoming icmp traffic for en0:

pass in quick on en0 proto icmp

One more rule, to show how we’re going to pass and log data for data coming into en0 for both tcp and udp from anyone to the IP on that interface running 192.168.210.10 for port 548:

pass in log quick on en0 proto { tcp, udp } from any to 192.168.210.10 port 548 keep state

Of the above, tables allow you to define ranges and basically alias IPs. Anything in this section of pf.conf in angled (<>) brackets is a table that has been defined. You can also build a list, which allows multiple criteria to be defined for a given rule and macros, which are essentially arrays of IPs, ports, etc, designed to reduce the amount of typing you have to do if you’re building out a big configuration file. Once we’ve edited our configuration file, let’s run a quick sanity check on it:

sudo pfctl -v -n -f /etc/pf.conf

Now, provided we don’t get any crazy errors, let’s load pf with our rules (which also loads the anchors):

sudo pfctl -f /etc/pf.conf

Then let’s set pf to be verbose while we’re testing (we’ll turn it off later):

sudo pfctl -v

Then let’s enable pf:

sudo pfctl -e

The return code should be something along the lines of the following:

pf enabled

You can also add information on the fly. For example, to add a table of 127.0.0.0/24 call localsub:

sudo pfctl -t localsub -T add 127.0.0.0/24

If you want to flush your rules later:

sudo pfctl -Fa -f /etc/pf.conf

To clear your stats:

sudo pfctl -z ; pfctl -si

Once we feel good about the pf configuration, set it to be quiet to keep the logs small and make it a little quicker:

sudo pfctl -q

And to disable pfctl when you’re done tinkeratin’:

sudo pfctl -d

And to watch what it’s doing:

ifconfig pflog0

Followed by

sudo tcpdump -v -n -e -ttt -i pflog0

Overall, pfctl is pretty straight forward to use. There is a really good post (thanks to @sacrilicious for pointing it out) at http://ikawnoclast.com/2012/04/using-the-lion-pf-firewall-with-the-emerging-threats-list.html for syncing the Emerging Threats anchor from emergingthreats.net. And of course, OpenBSDs pf page is the best source of information on the project, available here. There are a few limitations. The pf command is limited to one processor, so running a dedicated pf host on an 8 core machine is pretty much overkill. RAM is important as pf doesn’t use swap space. The more you pay for a card, the better a card you get, for the most part. Check out the Small Tree cards as they’re pretty efficient…

A few things I haven’t gotten working, the logging is kinda’ wonky. The antispoof protection seems odd (see the antispoof docs on the pf page), osfp (which might be other devices in my walled garden) and dummynet integration (which I have working w/ ipfw)… If I can get them working I’ll put together another post for that in my infinite amounts of free time. I also didn’t end up figuring out the upper limit for packets/rule lookups/table lookups per second… As I write more efficient tables I do more lookups and can therefore process packets faster. It’s annoying when I realize ***I*** am the bottleneck…