Scalable PF Rulesets

Writing a PF ruleset that scales linearly when you add more interfaces or policies can be a daunting task. The first-impulse way to write PF rulesets usually results in something that scales quadradically as more interfaces are added. Usually it's possible to rearrange things a little, though, so it scales linearly. The trick is to write each policy with input or output rules based on which requires fewer rules.

Perhaps this is easiest to demonstrate by example. The following is an example PF ruleset with five interfaces.


 * 1) SECTION 1:  Macros & Tables

all_ifs = "{ lo0, sf0, sf1, sf2, sf3 }" external_if = "sf0" dmz_if = "sf1" private_if = "sf2" wireless_if = "sf3" table  file "/etc/pf.trusted_ips"

table {self}
 * 1) Necessary for the pass in rules at the bottom

my_server = "$my_server"


 * 1) SECTION 2:  Options

set block-policy return set fingerprints "/dev/null" set state-policy if-bound


 * 1) SECTION 3:  Traffic Normalization

scrub in all


 * 1) SECTION 4:  Queueing


 * 1) SECTION 5:  Translation (first-match)

nat on $external_if from {$sf2, $sf3} to any -> ($ext_if)


 * 1) SECTION 6:  Packet Filtering (last-match except quick)


 * 1) 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


 * 1) SECTION 6.2:  Specific policy rules follow

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

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
 * 1) SECTION 6.2.2:  Traffic for External


 * 1) SECTION 6.2.3:  Traffic for DMZ

pass out quick on $dmz_if inet proto tcp from  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
 * 1) Primary server on $my_server

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

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

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
 * 1) SECTION 6.2.5:  Traffic for Wireless
 * 2) The wireless trusts everything except external traffic

pass in log quick proto tcp from any to ! all flags S/SARF keep state pass in log quick proto udp from any to ! all keep state pass in log quick proto icmp all keep state
 * 1) SECTION 6.3:  More generic rules
 * 2) 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?
 * 1) pass in log quick all # XXX: I'd expect this to work just as well as the rules above

block log quick all
 * 1) If it hasn't been passed yet, block