The placement location of filter rules in pf.conf

Hi,

I have been reading several different items on how to properly build my pf.conf. One of the issues I am coming across is the placement of rules. I see the block all at the bottom of some and at the top of others. I see antispoofing at the bottom and in the middle of filter rules on others. I see things that say filter rules are last match and others that seem to indicate that does not apply anymore.

So in regards to filter rules, are they last match and would that mean it is better for the block all to be at the bottom or is this incorrect?

This is being done on a box using FreeBSD 8.2-STABLE

Sincerely,
Brendhan
 
It depends on the firewall. Most firewalls if they 'hit' a rule that matches it will stop processing the rest of the ruleset. This is not the case with PF, it will keep parsing rules. This could mean a packet can match more than one rule. But it's the last state that's important. Unless you use the quick keyword to 'short-circuit' the parsing.
 
SirDice said:
It depends on the firewall. Most firewalls if they 'hit' a rule that matches it will stop processing the rest of the ruleset. This is not the case with PF, it will keep parsing rules. This could mean a packet can match more than one rule. But it's the last state that's important. Unless you use the quick keyword to 'short-circuit' the parsing.

Forgive me but doesn't that sound a lot like traffic trying to pass in on a pf firewall could end up in a loop if the ruleset is not configured correctly? You almost made it sound like the block all should then at least be the last rule to stop anything that doesn't know where it is going.

A badly configured pf firewall sounds like it could be a disaster.

Boy, if I wasn't nervous before.

Sincerely,
Brendhan
 
A badly configured anything can be a disaster, I don't get your point?

As for the rule evaluation, it's not possible to create a loop with pf rules, the pf formalism lacks branching based on a condition and anything that would resemble the GOTO statement and those two are the minimums to create loops. So, in pf the rule evaluation will stop for every packet sooner or later, no endless loops.

The normal order for pf rules is more general rules first, then more specific later and the last matching rule wins (the order is always the same, there's no option to change that in pf). This means that you start with:

Code:
block all

And later you have rules that allow traffic, for example

Code:
pass in on $ext_if proto tcp from any to $ext_if port ssh

Some prefer the first matching rule wins -order (I know that the pfSense firewall software that is based on FreeBSD and pf uses this order), that can be achieved with quick keyword. In the example I gave you would just turn the rules into reverse order and use the quick keyword in the pass rule.
 
Code:
### Packet Filtering Rules-Last Match
# Default Block Everything
block log all
# allow outgoing connections and keep state
pass out keep state
# Prevent forged ip addresses from getting through
antispoof quick for $ext_if
# antispoof on $tun_if
antispoof quick for $int_if
# Allow all icmp
pass in on $ext_if proto icmp
# Allow ssh in
pass in inet proto tcp from any to $ext_if port ssh
# Block the damn martians
block drop in quick on $ext_if from $martians
block drop out quick on $ext_if to $martians
# Pass local traffic
pass quick on $int_if all

Okay I am using this as my example. The only rules I have that are quick are the ones to deal with martians and traffic going out from the internal network.

Below is an example I found where the block all is at the bottom.

Code:
# SECTION 6:  Packet Filtering (last-match except quick)

# SECTION 6.1:  General configuration rules for all networks and self

pass quick on lo0 from self to self   # must be before antispoof -- see pf.conf(5)
antispoof log quick for $all_ifs
pass out quick inet proto icmp from any to any icmp-type echoreq keep state


# SECTION 6.2:  Specific policy rules follow

# SECTION 6.2.1:  Traffic for self
# must white list "in" traffic to self as well
pass in quick from any to self port ssh keep state
pass out quick from self to any keep state


# SECTION 6.2.2:  Traffic for External
pass out quick on $external_if inet proto tcp from any to any flags S/SARF modulate state
pass out quick on $external_if inet proto udp from any to any keep state


# SECTION 6.2.3:  Traffic for DMZ

# Primary server on $my_server
pass out quick on $dmz_if inet proto tcp from <trusted_ips> to $my_server flags S/SARF modulate state
pass out quick on $dmz_if inet proto tcp from any to $my_server port 22 flags S/SARF modulate state    # ssh
pass out quick on $dmz_if inet proto tcp from any to $my_server port 25 flags S/SARF modulate state    # smtp
pass out quick on $dmz_if inet proto udp from any to $my_server port 53 keep state
pass out quick on $dmz_if inet proto tcp from any to $my_server port 53 flags S/SARF modulate state    # domain
pass out quick on $dmz_if inet proto tcp from any to $my_server port 80 flags S/SARF modulate state    # http
pass out quick on $dmz_if inet proto tcp from any to $my_server port 113 flags S/SARF modulate state   # ident
pass out quick on $dmz_if inet proto tcp from any to $my_server port 443 flags S/SARF modulate state   # https
pass out quick on $dmz_if inet proto tcp from any to $my_server port 993 flags S/SARF modulate state   # imaps
pass out quick on $dmz_if inet proto tcp from any to $my_server port 995 flags S/SARF modulate state   # pop3s
block out quick on $dmz_if inet from any to $my_server

# All others (block unless passed above)
block out quick on $dmz_if inet from any to any

# SECTION 6.2.4:  Traffic for Private
block out quick on $private_if inet from any to any


# SECTION 6.2.5:  Traffic for Wireless
# The wireless trusts everything except external traffic
pass out quick on $wireless_if inet proto tcp from 10.0.0.0/8 to any flags S/SARF modulate state
pass out quick on $wireless_if inet proto udp from 10.0.0.0/8 to any keep state
block out quick on $wireless_if inet from any to any


# SECTION 6.3:  More generic rules
# XXX: Why is this?  Without these rules, all "pass out keep state" rules seem to break.  It's more efficient to use the state table anyway, but why is this?
pass in log quick proto tcp from any to !<self> all flags S/SARF keep state
pass in log quick proto udp from any to !<self> all keep state
pass in log quick proto icmp all keep state
#pass in log quick all  # XXX: I'd expect this to work just as well as the rules above

# If it hasn't been passed yet, block
block log quick all

I am assuming because basically every rule in the lower example is a quick that the order becomes reverse.

This seems to agree with what you pointed out.

So once a packet matches the quick rule it passed or is block based on the rule line regardless of the order?

Sincerely,
Brendhan
 
@Understudy,

your confusion is absolutely normal. Most firewalls process rules with a first match wins policy. That's why the standard <deny all> usually comes last. In PF you can use the quick keyword to achieve this kind of behavior.

As a matter of fact, in large rule base firewalls, you want to place your most used rules at the top and move on from there. That way, you don't load the firewall with extra rule processing.

Similarly, antispoofing and fragmentation, are always processed first. This is the case with all the firewalls I have worked with.
 
Back
Top