Options are basic settings that affect core PF functions. Options answer questions like these:
Do we reassemble fragments into packets?
How many entries should the state table support?
Is logging on?
All options start with the set
keyword. Because options affect how all other parts of PF operate, I recommend placing them at the very top of pf.conf.
Here, we’ll look at some of the more commonly used options.
Will your firewall silently drop forbidden packets, or respond to the client with “sorry, not allowed?” The block policy determines which approach it takes. By default, PF drops blocked packets, but you can override the global block policy on individual filter rules.
Strictly speaking, when PF drops packets, it should return an error to the client, so that legitimate clients can immediately recognize that they cannot connect. Using set block-policy return
tells PF to return these polite errors: an RST for TCP connections and an ICMP unreachable message for other types of connections.
Unfortunately, politeness has largely been overwhelmed by the modern Internet. PF’s default, set block-policy drop
, tells PF to not return any kind of error on blocked packets. Client applications such as web browsers, vulnerability scanners, worms, and other malware must wait for the network protocol to time out before realizing that they cannot connect.
I recommend dropping blocked packets silently.[47]
PF includes limits on the size of various internal tables used to track fragments, states, address tables, and other memory-consuming items. I have needed to adjust these limits on very rare occasions. The existing limits are chosen because they are sufficient for most users in most environments.
View the existing limits with pfctl
.
# pfctl -s memory
states hard limit 10000
src-nodes hard limit 10000
frags hard limit 1536
tables hard limit 1000
table-entries hard limit 200000
Let’s take a look at what each limit represents.
When PF receives a fragmented packet, it holds onto that fragment and waits for other fragments of that packet to arrive. Once it has all the pieces, it reassembles the fragment and processes it. The frags
limit controls the number of packet fragments awaiting reassembly at one time. (You shouldn’t need to change this.)
To see the total number of fragments PF has processed, and how many arrive per second, use pfctl -s info
and look at the Counters section.
# pfctl -s info
…
fragment 368 0.0/s
…
This host has been sitting on the naked Internet for three months in an Internet colocation site, and has received only 368 fragments. I do not need to increase PF’s memory for fragments, and I certainly don’t want to reduce the limit in case I receive a sudden barrage of fragments.
If you suspect that fragments are flowing in, run systat pf
for constantly updating counters of PF statistics.
PF can track a number of states per source address. You might want to limit each client to, say, 10 connections to a specific server. This connection limit includes connections being set up and those still waiting to finish. Here’s an example of this sort of rule:
pass in proto tcp to $webserver port 80 keep state(max-src-states 10)
PF’s load balancer features use src-nodes
to help track which clients are attached to which servers, through the sticky-address
and source-track
options.
If you use these features, and think you might be out of source nodes, check usage with pfctl -s Sources
.
The states
limit controls how many entries can be in the stateful inspection list. The default of 10,000 is adequate for most environments.
You can view the current usage with pfctl -s info
.
# pfctl -s info
Status: Enabled for 1 days 18:01:06 Debug: err
State Table Total Rate
current entries 30
searches 54510751 6.3/s
inserts 2459724 0.3/s
removals 2459694 0.3/s
…
I have needed to change the state table more than once. Each time, it was because of a strangely written application that required clients to make dozens of connections to a single TCP/IP port. I’m certain that the application developers had their reasons for doing so (possible reasons do include ignorance and malice). Multiplied by thousands of simultaneous users, that became a lot of states. As I wasn’t in a position to tell the developers to write their application like normal people, I had to adjust the state table.
If you suspect that the state table is having trouble, use systat pf
and/or systat states
to view state activity in real time.
The tables
and table-entries
limits control how many tables PF can create, and how many entries can go into a single table. I have never had to adjust these, and I would suggest that if your filter rules need more than 1000 tables, you should probably reconsider how you’ve designed it. A table might need to hold more than 100,000 addresses, but that’s very much the exception these days.
To change a limit, use set limit
, the name of the limit, and the new value. Here’s how to double the size of the default state table:
set limit states 20000
Again, don’t change these defaults lightly. Increase them only if existing limits cause a specific problem. And don’t decrease them, or you won’t be prepared for problems and spikes.
PF includes a variety of timeouts, which default to values reasonable for the modern Internet. Some environments, such as satellite uplinks, do require slightly different timeouts.
You can adjust PF’s timeouts with set optimization
. (The name is a leftover from the early days of PF, but has stuck around.) This has four values:
normal
The
normal
optimization is the default. If you don’t specify an optimization, the standard timeouts are used.
conservative
The
conservative
optimization is for environments where you want to be absolutely sure you don’t time out connections. (State table entries will stick around longer.) This setting uses more memory and processor time—possibly much more on a busy network. I use it to ease the minds of managers of industrial networks who are less concerned about buying more hardware and more concerned about the possibility of a meeting caused by some executive’s idle connection timing out.
high-latency
If you connect over a satellite uplink or carrier pigeon, use the
high-latency
optimization.
aggressive
If you have a busy firewall, with many connections coming and going, you might try the
aggressive
optimization. This times out idle connections more quickly, reducing memory and processor use. Many people report thataggressive
timeouts work perfectly well in their environments, but if low timeouts cause trouble for you, turn them off.
Configure any of these by using set optimization
and the optimization name.
set optimization conservative
You can tell PF to not manage an interface. By default, it watches all interfaces, but some interfaces don’t really require filtering. Your loopback interface, lo0
, passes traffic only from the local machine to itself. Packet filtering on lo0
is an interesting educational exercise, but not terribly useful in production.
set skip on lo0
You can also specify multiple interfaces to skip.
set skip on {lo0 fxp0 fxp1}
It’s fairly common to skip filtering on the physical interfaces beneath a trunk in favor of filtering on the trunk itself.
This will get you started with packet filtering. If you have a single server with simple functions, you can protect it quite nicely using the techniques covered in this chapter. But PF can do a lot more than what we’ve talked about here, such as control bandwidth and have applications dynamically change rules. In the next chapter, we’ll touch on a few of PF’s more advanced functions.
[45] Sorry, cats and elephants, find your own place to live.
[46] Blatantly stolen from Henning Brauer. Thankfully, he’s so sick of this book by now, he won’t notice.
[47] Mind you, if PF included an option to insult the client when a packet is dropped, somewhat like sudo, I would need to change my recommendation. But that’s a fault in the underlying network protocol, not PF.