BSD DevCenter
oreilly.comSafari Books Online.Conferences.


Changes in pf: Packet Filtering
Pages: 1, 2, 3, 4, 5, 6

Will you use stateful filtering? (keep state, modulate state)

pf(4) is a stateful packet filter, which means that it is capable of keeping track of the state of connections. Stateful filtering has the following advantages:

  • makes packet processing faster
  • makes writing rulesets easier
  • makes connections safer

The basic principle behind stateful filtering is simple. When the initial packet makes the connection on the firewall, the packet filter will create an entry in its state table for that connection. All subsequent packets that belong to the connection for which an entry in the state table exist will be let through without matching them against the whole ruleset. State tables are checked before the filter begins evaluating filtering rules.

The packet filter decides if a packet belongs to a connection for which a state exists by checking the packet's sequence number stored in the TCP header. When the sequence number falls out of a narrow window, the packet is dropped. This mechanism prevents spoofed packet injection into an established connection.

Stateful inspection of packets is turned on with the keep state keywords placed near the end of a filtering rule.

pass out on $ext_if proto TCP all keep state

To keep memory usage under control, information about connections is removed from the state table after connections are closed or after they time out.

By the way, when you use nat/binat/rdr rules, you are already using stateful filtering, as these rules create states automatically.

There are two schools of thought about state creation. Some administrators insist that only packets with the SYN flag (i.e., the packets that initialize the connection) can create state. Others say that any packet ought to be able to create state, because such rules allow existing connections to create state and continue after the state tables are flushed with pfctl -F state or after the firewall is rebooted. Rules that create state only for packets with the SYN flag set will not be able to create state for existing connections.

The following rules allow all departing TCP packets to create state. As for inbound packets, only those sent to port 80 will be able to create state:

pass in  proto tcp all port 80 keep state
pass out proto tcp all keep state

If you want to limit packets that can create state to those that have the SYN flag set, add the flags S/SA condition, as in:

pass in proto tcp all port 80 flags S/SA keep state pass
out proto tcp all flags S/SA keep state

What about UDP or ICMP packets? Can pf(4) create state for these as well? Yes, it can. With UDP packets, which do not carry sequence numbers, the filter matches them to states using only address and port information.

As for ICMP, these are treated differently depending on their category. ICMP error messages that refer to TCP or UDP packets are matched against states for connections they refer to. As such they do not require separate rules, the packet filter will take care of this automatically. ICMP queries (like ping(8)) may need their own separate rules, like:

pass out inet proto icmp all icmp-type echoreq keep state

Initial sequence numbers, if chosen carelessly, can be used in dangerous attacks that exploit the fact that some TCP stacks use easily predictable values for initial sequence numbers. For more information about these attacks read CERT Vulnerability Note VU#498440, or Rik Farrow's Sequence Number Attacks.

pf(4) can prevent these attacks with the modulate state rule. To turn it on, use modulate state instead of keep state.

pass in  proto tcp all port 80 flags S/SA keep state
pass out proto tcp all flags S/SA keep state


pass in  proto tcp all port 80 flags S/SA modulate state
pass out proto tcp all flags S/SA modulate state

Remember that modulate state can only be used with TCP connections. For other connections use keep state.

Each keep state or modulate state can have its own set of options. These options are:

  • max n: maximum number (n) of concurrent states that can be created for this rule. See the description of the limit states option in NAT with pf. This option, unlike limit states, works on a per-rule basis.
  • timeout: timeout values for states created with this rule. See the description of the timeout option in NAT with pf. This option, unlike timeout, works on a per-rule basis.

A rule using state options could look like this:

pass in proto tcp all port 80 flags S/SA \
	modulate state (max 1000, tcp.established 120, tcp.closing 10)

Will IP options be allowed or blocked (allow-opts)?

IP options are blocked by default, which is good from the point of view of security. If you want to allow them, you explicitly state your wish with the allow-opts keyword.

In practice there is very little need for allowing these options, save for special application, as they may be used by attackers to mess with your network, or with other hosts on the Internet (in such cases, you might end up being accused of deliberate wrongdoing). IP options have their legitimate uses, but if you don't explicitly need them, leave them disabled. That is, do not use allow-opts.

If you're curious, read RFC791 [Postel 1981], RFC1108 [Kent 1991]. For a more detailed discussion, refer to [Stevens, Wright 1994] where you will find details of operation and implementation of IP options processing in BSD systems.

The allow-opts keyword can only be used in pass rules.

Labels (label)

Labels are used to mark rules for which pf(4) will keep separate statistics. You can display these stats with pfctl(1). A label is added with the label keyword followed by a text string. Labels are placed at the end of rules:

pass in  on rl0 all label "incoming"
pass out on rl0 all label "departing"

To view statistics, use:

$ sudo pfctl -s labels
incoming 85 26 2024
departing 86 56 6960

The numbers that follow the labels are the number of rule evaluations, packets, and bytes.

Labels can contain pre-defined macros:

  • $srcaddr: source IP address. This is the source IP address listed after the from keyword in the rule, not the packet's source address, so if you use from any and label "from $srcaddr" in the same rule you'll see a message similar to from any 86 56 6960.
  • $dstaddr: destination IP address.
  • $srcport: source port.
  • $dstport: destination port.
  • $proto: protocol name.
  • $nr: rule number.

That's enough theory. Next time, we'll add a packet filtering section to the ruleset described in More NAT, discuss some modifications to that ruleset, solve the FTP connection problems with a proxy, cover IPv6 filtering and build an invisible filtering bridge.

Jacek Artymiak started his adventure with computers in 1986 with Sinclair ZX Spectrum. He's been using various commercial and Open Source Unix systems since 1991. Today, Jacek runs, writes and teaches about Open Source software and security, and tries to make things happen.

Read more Securing Small Networks with OpenBSD columns.

Return to the BSD DevCenter.

Sponsored by: