Some important things about OpenBSD PF's max-* options

April 10, 2013

In older versions of the OpenBSD pf.conf manpage (such as the one you may be running on a firewall that is too important to reboot, much less put through a chancy upgrade), the 'max <number>' option for stateful tracking is described this way:

Limits the number of concurrent states the rule may create. When this limit is reached, further packets that would create state will not match this rule until existing states time out.

This is, how shall I put it, a lie (as before). In current versions of pf.conf this phrasing has been declared inoperative and revised to be 'further packets that would create state are dropped until existing states time out'. This new phrasing is correct as far as it goes but it leaves several important things out.

First this also applies to all of the max-* variants (max-src-nodes, max-src-states, max-src-conn, and max-src-conn-rate), which you could maybe deduce because the manpage doesn't say anything about what they do when the limit is hit so clearly they inherit max's behavior (this is the way of Unix manpages).

Next, how things are logged (if they are logged) depends on your OpenBSD version. In OpenBSD 4.4, this dropping is completely silent and in fact happens after the point where packets are logged (so if you specify log on such a rule, what it logs will be sometimes be a lie; it will claim that packets are accepted when they were in fact dropped). Because overload <table> is only used (or allowed) for the TCP connection limits, this means that there is essentially no way to tell when a UDP ratelimit (perhaps one to limit traffic to your DNS server) has triggered or what it affects.

(You can watch some pfctl -si counters tick up. This is not very much use if you want to know what your ratelimit is affecting and whether it is too small, too big, or just right.)

In OpenBSD 5.2 the logs are now honest, as far as I can tell from the kernel source code (I don't have a handy 5.2-based firewall where I can test this). The logs will now accurately record both that a packet was dropped and that it was dropped due to connection limits. There is still no way to log just dropped packets but at least you can now log all traffic and sort out the mess later (assuming that your logs do not explode from the volume).

(The overload <table> clause still only applies to TCP connections. As far as I can tell this is a completely artificial limitation in PF and I personally think it's a stupid one. I would certainly like to be able to automatically put IPs that are hammering on our DNS server with UDP queries into a table to be blocked wholesale for a while.)

My overall conclusion from my recent experiences (this included) is that OpenBSD PF is not very good for UDP ratelimiting. For instance, actual volume per time limits can only be constructed indirectly and only work for some UDP-based protocols (and, I think, often only for cooperative clients).

(I'm not completely sure how OpenBSD matches states for UDP packets, but I have a sneaking suspicion that a DDoS program that reused the same UDP source port for all its forged DNS queries would match an existing PF UDP state table entry and so never hit PF's state table entry based rate limits. You can't play this trick with TCP connections because they have actual connection state.)

Written on 10 April 2013.
« Fedora 18's TexLive packaging failure
Something I'd like to be easier in Solaris's IPS »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Apr 10 00:15:53 2013
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.