Comparing packets to complicated packet filter rulesets can be expensive in terms of CPU and memory usage. Some packet filters use rule grouping and the equivalent of a "goto" statement to reduce the number of rules an individual packet must be compared against. Instead of using groups, PF performs an automatic ruleset optimization called skip-steps. By simply putting your rules in the correct order, you can vastly accelerate PF's packet processing. Basically, PF can count ahead to see how many rules have similar parameters and can skip processing rules that are sufficiently similar if they fail to match.
Let's see how this works in practice with the following simple ruleset:
1 block in quick on fxp1 from 10.0.0.0/8 to any 2 block in quick on fxp1 from 172.16.0.0/12 to any 3 block in quick on fxp1 from 192.168.0.0/16 to any 4 pass in quick on fxp1 from 209.69.178.16/28 to any 5 block in quick on fxp0 6 pass in on fxp0 from 192.168.1.0/24 to any port = 22 keep state 7 pass in on fxp0 from 192.168.1.0/24 to any port = {80, 443} keep state 8 pass in on fxp0 from 192.168.2.0/24 to any port = 22 keep state
Assume that we have a packet crossing the fxp0 interface, with a source address of 192.168.2.8, bound for a SSH server somewhere. Let's see how PF optimizes crossing the rules in this instance.
Our first four rules cover packets going over the fxp1 interface. When a packet enters the packet filter, it is compared to the 1 first rule. If this packet is crossing the fxp0 interface, the first rule does not apply. PF can count ahead and see that rules 2, 3, 4 and all involve the fxp1 interface, so the packet does not have to be compared to these rules. The packet is immediately compared against rule5.
Our packet is crossing the fxp0 interface, so rule 5 does apply. It doesn't have a source address of 192.168.1.0/24, however, so rule 6 doesn't match. PF looks ahead and sees that rule 7 also involves fxp0 and the 192.168.1.0/24 source address, so it can skip that rule.
Finally, at rule 8, we have the fxp0 interface and a source address of 192.168.2.0/24. This matches, and the packet is allowed to proceed. We have crossed eight rules, but have only done full packet comparisons on three of them thanks to skip steps! If you had arranged your rules any which way, skip steps would not work.
PF computes skip steps based on the following fields:
interface
protocol
source address
source port
destination address
destination port
The best way to optimize PF is to have a neat and orderly ruleset.
Now that you have learned to configure PF to do almost anything, let's explore how to activate and manage the configuration that you've set up. You'll learn how to accomplish this in the next chapter.