In PF, an anchor is a sub-ruleset at a specific point in the filter rules that you can change without reloading the rules. It’s a spot marked “insert rules here,” letting you dynamically add and remove filter rules, tables, and other PF configurations.
The most common users of anchors are software programs. Human beings or sysadmins should probably just edit pf.conf and reload the rules.
OpenBSD includes several programs that take advantage of anchors, however, including the FTP proxy ftp-proxy(8)
, the authenticated firewall access system authpf(8)
, and the load balancer relayd(8)
. You could also use anchors to trigger conditional evaluation of rules.
A ruleset with an anchor might look something like the following, where the interface group egress
faces the Internet, and the interface group lan
faces a small office with the addresses 192.0.2.0/24.
block pass in on egress from any to 192.0.2.45 port {25, 80} anchor "antivirus/*" pass in on lan from 192.0.2.0/27 to any
These rules block all traffic by default. Incoming traffic is allowed to a specific address on ports 25 and 80 because those are the mail and web servers. There’s an anchor in the middle of the rules. I don’t yet know what’s in the antivirus
anchor, but any rules in it are processed next. Finally, a small subnet of the addresses is allowed out.
Now let’s add some rules to the anchor.
You can insert rules into anchors from a file, within pf.conf itself, or via pfctl
.
Adding rules to an anchor from a file is a good way to initialize your anchor when first starting the packet filter. You can set base rules here that you can expand later. Give the filename in pf.conf.
anchor dhcp load anchor dhcp from "/etc/pf/dhcp-anchor.conf"
I created an /etc/pf/ directory because I didn’t want to have a whole bunch of PF configuration files scattered throughout /etc. I’m easily confused, after all. This file contains PF rules like this:
block from 192.0.2.192/26 to any
This is one way to load basic rules into an anchor when you start PF.
If you were paying attention, you probably noticed that my first example anchor had a /*
after its name. This example doesn’t. I’ll explain why in Nested Anchors: /*.
You can place anchor rules directly inside pf.conf. If you don’t intend to dynamically alter the rules, you don’t even need to name the anchor. Just use curly braces to define the beginning and end of the anchor.
anchor "smtp" on egress { pass proto tcp from 192.0.2.12 to any port 25 }
This is just slightly more complicated than the anchors in the default pf.conf.
Why would you want to do this? Read Conditional Filtering.
To dynamically alter anchor rules with pfctl
, you need the name of the anchor and the rule you want to put in its place. For example, suppose I want to add a rule to the antivirus
anchor in the first anchor example.
# 1 echo "block in from 203.0.113.8 to any" 2 | pfctl 3 -a antivirus 4 -f -
Let’s look at this command slightly backwards. The -a
argument to pfctl
specifies an anchor name—in this case, the antivirus
anchor 3. The -f
argument normally gives a filename that contains the new anchor rule, much like -f
when loading a PF ruleset, but rather than a path to a file, I use a single dash that tells pfctl
to read the new rule from standard input, or the command line 4. I start everything by echoing the rule to be added 1, and then piping that into pfctl
2.
Taken as a whole, this adds the rule block in from 203.0.113.8 to any
to the anchor antivirus
.
You could also write the new rule to a file, and tell pfctl
to load the rules from that file into the anchor.
# pfctl -a antivirus -f newrule.conf
If you’re writing rules to a file to load them into an anchor, however, chances are you’re better off editing pf.conf.
Use the pfctl
view (-s
), flush (-F
), and load (-f
) commands on anchors by specifying the anchor name with -a
.
# pfctl -a antivirus -s rules
block drop in inet from 203.0.113.8 to any
To erase the rules from an anchor, flush the rules in the anchor.
# pfctl -a antivirus -F rules
rules cleared
Your anchor is now empty.
Rulesets within anchors are completely separate from each other, and also from the main ruleset. Flushing all the rules in a specific anchor does not affect the rules in any other anchor, or the rules in the main ruleset. For that matter, flushing the rules in the main ruleset does not impact the rules in the anchor. To destroy an anchor, you must remove everything in the anchor, including any child anchors.
“Child anchors?” I hear you cry. “What are you babbling about now, dude?”
Consider the following pf.conf snippet:
… anchor "office/*" in from lan to any { pass out proto tcp from any to {80, 443} } …
The office/*
anchor has a filter condition after it, and only traffic that matches the filter condition will pass through the anchor. In this case, only packets that come from the lan
interface group will pass through the rules within the anchor. Your rules within the anchor might be easier to write, simply because everything in the anchor is already known to be originating from the lan
interfaces.
If your packet filter is very heavily loaded, you might be able to reduce the amount of time it spends processing packets by careful conditional filtering.
Anchors can contain other anchors.
anchor "office" in from lan to any{ … anchor "ftp-proxy/*" pass in quick inet proto tcp to port ftp divert-to 127.0.0.1 port 8021 } …
Only traffic that passes into the office
anchor can pass through the ftp-proxy
anchor. The FTP proxy can have its own sub-anchors as well. In fact, you might have several layers of anchors to support a complicated protocol, such as FTP.
This is where the /*
after some anchor names comes in. An anchor name without this is executed all by itself. By adding the /*
, you tell PF to evaluate all sub-anchors within this anchor, in alphabetical order.
Anchors and sub-anchors deliberately resemble a filesystem. You can have a file /office or a directory /office/ containing more files. If you list the files in a directory, they appear in alphabetical order. Anchors work much the same way.
All of this anchor stuff is very theoretical. How about a practical example? Read on to see how PF uses anchors to handle that most annoying of network protocols: FTP.