I recently spent a couple of days reading various pf documentation, including the man pages, the faq, and some guides (including Peter Hansteen's), and fiddling with my firewall configuration, and there are a few subtleties that I haven't found addressed anywhere.
1) The big one is what I would call the 'double state problem'. It seems to me that the big disadvantage of a default-deny ruleset is that because explicit pass rules are required on all interfaces, traffic passing through the firewall machine needs state on all interfaces. This isn't a problem for most applications, but it seems like it would be a memory issue for large routers.
Supposing you have the following in your pf.conf [note: all rules in my (simplified) examples will omit the implicit 'keep state']:
--- block all pass on $int_if pass out on $ext_if pass in on $ext_if from any to $ext_if port 22 ---
If you now initiate a web connection from a machine on the internal network to one on the internet, two states are created, one on each interface, to allow return packets through.
The same problem, less obviously, occurs with a default-deny-in ruleset like: --- block in all pass out all pass in on $int_if pass in on $ext_if from any to $ext_if port 22 --- or even with rules like pass from $int_if:network to $ext_if:network
If you have --- block all pass on $int_if no state pass out on $ext_if pass in on $ext_if from any to $ext_if port 22 --- then the ruleset is effectively the same, and there is only one state per connection, but traffic is (slightly) slower because return traffic through $int_if needs to be checked against the ruleset instead of the state table.
But if you create a default-permit ruleset with wide-ranging block rules, like:
--- block in on $ext_if pass in on $ext_if from any to $ext_if port 22 ---
you get the same filtering results with fewer rules, one state per connection, and no need for ruleset lookups on established streams.
The most obvious benefit of a default-deny ruleset is that it saves you if you make a block rule too narrow, or comment it out by accident or something. But is there a way to have only one state per connection with a default-deny ruleset? And if not, does it ever actually matter, or am I just being pedantic?
2) The other (shorter) question: If I want one of my internal networks to be able to access the internet, but not be able to access my other internal networks, is
--- table <non_local> { 0.0.0.0/0 !$int_net1 !$int_net2 } block all pass in on $int_net3 from any to <non_local> [etc] ---
better or worse in speed and resources than
--- block all pass in on $int_net3 block in on $int_net3 from any to $int_net1:network block in on $int_net3 from any to $int_net2:network [etc] ---
?
Any enlightenment from the pf gurus? Thanks.
PS: please cc me on replies; I can't seem to get the list server to accept my subscribe requests. It bounces them as spam. (!?)
> 1) The big one is what I would call the 'double state problem'. It > seems to me that the big disadvantage of a default-deny ruleset is > that > because explicit pass rules are required on all interfaces, traffic > passing through the firewall machine needs state on all interfaces.
...
> The most obvious benefit of a default-deny ruleset is that it saves > you > if you make a block rule too narrow, or comment it out by accident or > something.
The big benefit of default-deny is that you're sure you know what traffic you are passing.
But is there a way to have only one state per connection
> with a default-deny ruleset? And if not, does it ever actually > matter, > or am I just being pedantic?
You have a choice.
set state-policy if-bound|floating
Whether it matters or not depends on your application. It surely can matter, e.g. with muti-homed hosts where replies may come in an interface other than the one out which the request was sent. OTOH if you're gatewaying multiple very different networks you may want things locked down tightly with state bound to the interfaces because there's no way such traffic should be allowed.
> 2) The other (shorter) question: > If I want one of my internal networks to be able to access the > internet, > but not be able to access my other internal networks, is
...
> better or worse in speed and resources than
I don't know. My general rule is that unless performance is really an issue it's a lot more important to write programs/ configurations in a way that people can read than it is to write them so that they are executed optimally. If nobody can read the file then it's useless.
> table <non_local> { 0.0.0.0/0 !$int_net1 !$int_net2 } > pass in on $int_net3 from any to <non_local>
At first glance, this won't work. An address in $int_net1 will match !$int_net2 and so will pass and vice versa. My brain is full right now so I could be wrong but I am sure there are issues just like this with ! to watch out for.
Regards,
Karl <k...@meme.com> Free Software: "You don't pay back, you pay forward." -- Robert A. Heinlein
> But if you create a default-permit ruleset with wide-ranging block > rules, like:
> --- > block in on $ext_if > pass in on $ext_if from any to $ext_if port 22 > ---
> you get the same filtering results with fewer rules, one state per > connection, and no need for ruleset lookups on established streams.
You don't have a rule handling $int_if or outbound traffic, so the default _non stateful_ pass rule is used. You're doing a whole ruleset evaluation per outgoing packet.
On 2009/09/12 21:25, Karl O. Pinc wrote:
> > table <non_local> { 0.0.0.0/0 !$int_net1 !$int_net2 } > > pass in on $int_net3 from any to <non_local>
> At first glance, this won't work. An address in > $int_net1 will match !$int_net2 and so will pass > and vice versa. My brain is full right now so I > could be wrong but I am sure there are issues just > like this with ! to watch out for.
Negation works as expected in a single table so that's ok. The problem with ! is where you do this,
pass in to {0.0.0.0/0 !$int_net1 !$int_net2}
which expands to three rules,
pass in to 0.0.0.0/0 pass in to !$int_net1 pass in to !$int_net2
* Daniel Malament <dani...@bluetiger.net> [2009-09-12 23:04]:
> 1) The big one is what I would call the 'double state problem'. It > seems to me that the big disadvantage of a default-deny ruleset is that > because explicit pass rules are required on all interfaces, traffic > passing through the firewall machine needs state on all interfaces. This > isn't a problem for most applications, but it seems like it would be a > memory issue for large routers.
there's no memory problem really. you will have memory bandwidth / bus bandwidth / interface bandwidth maxed out long before memory for the states becomes an issue. i am not aware of a _single_ case of state table size problem in at least 5 years (in the early days the pools used had limitations that actually made that a bit problematic, but that is long solved).
you want default deny and double states... really. not bored enough to write that down again tho.
and if you don't like the double states you can still set skip on one of the interfaces. but understand the consequences.
> 2) The other (shorter) question: > If I want one of my internal networks to be able to access the internet, > but not be able to access my other internal networks, is
> --- > table <non_local> { 0.0.0.0/0 !$int_net1 !$int_net2 } > block all > pass in on $int_net3 from any to <non_local> > [etc] > ---
> better or worse in speed and resources than
> --- > block all > pass in on $int_net3 > block in on $int_net3 from any to $int_net1:network > block in on $int_net3 from any to $int_net2:network > [etc] > ---
won't make a difference that matters. with just 3 entries they are probably about the same, the more entries the more advantage for the table. but then the optimizer will make that a table anyway (exceptions apply)
-- Henning Brauer, h...@bsws.de, henn...@openbsd.org BS Web Services, http://bsws.de Full-Service ISP - Secure Hosting, Mail and DNS Services Dedicated Servers, Rootservers, Application Hosting - Hamburg & Amsterdam
On 9/12/2009 10:25 PM, Karl O. Pinc wrote: > set state-policy if-bound|floating > > Whether it matters or not depends on your application. It surely > can matter, e.g. with muti-homed hosts where replies may come in > an interface other than the one out which the request was sent. > OTOH if you're gatewaying multiple very different networks you may want > things locked down tightly with state bound to the interfaces > because there's no way such traffic should be allowed.
That's not going to remove double states, because the traffic is passing through the interfaces in opposite directions. [shrug]
> I don't know. My general rule is that unless performance is > really an issue it's a lot more important to write programs/ > configurations in a way that people can read than it is to > write them so that they are executed optimally. If nobody > can read the file then it's useless.
That's a good point. I rewrote my ruleset recently in a way that uses more rules, but is way more maintainable and easy to understand. (Multiple pass in rules on different interfaces instead of single pass out rules that applied to a bunch of things.)
On 9/13/2009 5:38 AM, Stuart Henderson wrote: >> --- >> block in on $ext_if >> pass in on $ext_if from any to $ext_if port 22 >> --- > You don't have a rule handling $int_if or outbound traffic, so the > default _non stateful_ pass rule is used. You're doing a whole ruleset > evaluation per outgoing packet.
Ahhhh. I didn't realize the default rule worked like that. So that certainly removes any efficiency argument even without the other reasons.
On 9/13/2009 10:18 AM, Henning Brauer wrote:
> there's no memory problem really. you will have memory bandwidth / bus > bandwidth / interface bandwidth maxed out long before memory for the > states becomes an issue. i am not aware of a _single_ case of state > table size problem in at least 5 years (in the early days the pools > used had limitations that actually made that a bit problematic, but > that is long solved).
Ok. That's pretty much the info I was looking for in terms of my efficiency question.
> you want default deny and double states... really. not bored enough to > write that down again tho.
I should clarify that these questions aren't necessarily practical, since I'm not working with a huge ruleset, so much as coming from a desire to understand how the packet filter actually works.
> won't make a difference that matters. > with just 3 entries they are probably about the same, the more entries > the more advantage for the table. > but then the optimizer will make that a table anyway (exceptions apply)
It didn't when I tried it, but like you said, I guess exceptions apply...
Thanks, guys. I have some more to come, but I'll save it for another email.