2013-04-10
Some important things about OpenBSD PF's max-*
options
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.)