py-pf - Managing OpenBSD's Packet Filter with Python
4. Managing state tables
Like most modern firewalls, Packet Filter provides stateful packet inspection, i.e. the ability to keep track of the state of a network connection in order to speed up the processing of subsequent packets belonging to the same connection. Stateful filtering offers several advantages both in terms of performance, since packets matching stateful connections don't go through ruleset evaluation, and ease of configuration, because rules only need to care about the initial packet of a connection.
4.1 PFState objects
As seen in an earlier chapter, the PacketFilter.get_states() method allows you to access the contents of Packet Filter's state table: it returns a tuple of pf.PFState objects, which have the following attributes:
- PFState.id
- A unique identifier for the state.
- PFState.ifname
- The name of the interface the state refers to.
- PFState.rule
- The numeric ID of the rule that allowed the creation of this state.
- PFState.anchor
- The numeric ID of the anchor containing the rule that allowed the creation of this state.
- PFState.creation
- The age of the state in seconds.
- PFState.expire
- The seconds before the state will expire.
- PFState.packets
- A two-item tuple containing the number of packets transmitted and received.
- PFState.bytes
- A two-item tuple containing the number of bytes transmitted and received.
- PFState.creatorid
- The host ID of the firewall that created this state.
- PFState.af
- The address family (AF_INET or AF_INET6) of the underlying connection; these constants are available through the socket module.
- PFState.proto
- The transport layer protocol of the communication (e.g. IPPROTO_TCP, IPPROTO_UDP, IPPROTO_ICMP, etc.); these constants are available through the socket module.
- PFState.direction
- The direction of the state; can be either PF_IN or PF_OUT.
- PFState.state_flags
- Bitmask of flags for tracking source IP addresses; valid flags are PFSYNC_FLAG_NATSRCNODE (corresponding to the "sticky-address" option in pf.conf(5)), PFSYNC_FLAG_SRCNODE ("source-track" option) and PFSTATE_SLOPPY ("sloppy" option).
- PFState.sync_flags
- Bitmask of flags for syncing the states among multiple firewalls with pfsync(4); the only valid flag is PFSTATE_NOSYNC (corresponding to the "no-sync" option in pf.conf(5)).
- PFState.src
- A PFStatePeer instance containing various information about the local endpoint of the connection.
- PFState.dst
- A PFStatePeer instance containing various information about the remote endpoint of the connection.
- PFState.nk
- A PFStateKey instance representing the local endpoint of the connection.
- PFState.sk
- A PFStateKey instance representing the remote endpoint of the connection.
Printing a PFState object will produce an output similar to that of the "pfctl -s states -v" command.
4.2 PFStateKey objects
PFStateKey objects are address/port pairs representing how the endpoints of a state are seen by Packet Filter on the stack
and on the wire
. They have the following attributes:
- PFStateKey.addr
- A two-items tuple of PFAddr objects.
- PFStateKey.port
- A two-items tuple of PFPort objects.
- PFStateKey.rdomain
- The id of the routing domain of the state.
PFState objects contain two PFStateKey objects, PFState.nk and PFState.sk, whose interpretation differs depending on the direction of traffic (i.e. the value of PFState.direction); for inbound packets (i.e. when PFState.direction is PF_IN):
- PFState.nk is the stack side (PFState.nk.addr[1] and PFState.nk.port[1] represent the local endpoint and PFState.nk.addr[0] and PFState.nk.port[0] the remote endpoint);
- PFState.sk is the wire side (PFState.sk.addr[1] and PFState.sk.port[1] represent the local endpoint and PFState.nk.addr[0] and PFState.nk.port[0] the remote endpoint);
The local endpoints are identical if no redirection is performed on the inbound packets. For outbound packets:
- PFState.nk is the wire side (PFState.nk.addr[1] and PFState.nk.port[1] represent the local endpoint and PFState.nk.addr[0] and PFState.nk.port[0] the remote endpoint);
- PFState.sk is the stack side (PFState.sk.addr[1] and PFState.sk.port[1] represent the local endpoint and PFState.nk.addr[0] and PFState.nk.port[0] the remote endpoint);
The local endpoints are identical if no translation is performed on the outbound packets.
4.3 PFStatePeer objects
PFState instances contain a couple of PFStatePeer objects (PFState.src and PFSTate.dst) which hold some layer-4 parameters regarding the two endpoints of the state. They have the following attributes:
- PFStatePeer.seqlo
- Maximum sequence number sent.
- PFStatePeer.seqhi
- Maximum sequence number acknowledged by the other end plus the window size.
- PFStatePeer.seqdiff
- Sequence number modulator.
- PFStatePeer.max_win
- Largest window.
- PFStatePeer.mss
- Maximum segment size option.
- PFStatePeer.state
- Active state level (one of the TCPS_* constants).
- PFStatePeer.wscale
- Window scaling factor.
- PFStatePeer.pfss_ttl
- Stashed TTL.
- PFStatePeer.scrub_flag
- Scrub flag (0 or pf.PFSYNC_SCRUB_FLAG_VALID).
- PFStatePeer.pfss_ts_mod
- Timestamp modulation.
Below is a simple example that displays the current entries in the state table:
from socket import AF_INET
filter = pf.PacketFilter()
for state in filter.get_states():
nk, sk = state.nk, state.sk
s = "{}".format(state.nk.addr[1])
if nk.port[1]:
s += (":{}" if state.af == AF_INET else "[{}]").format(nk.port[1])
if (nk.addr[1] != sk.addr[1] or nk.port[1] != sk.port[1]):
s += " ({}".format(sk.addr[1])
if sk.port[1]:
s += (":{})" if state.af == AF_INET else "[{}])").format(sk.port[1])
s += (" -> " if state.direction == pf.PF_OUT else " <- ")
s += "{0}".format(nk.addr[0])
if nk.port[0]:
s += (":{0}" if state.af == AF_INET else "[{0}]").format(nk.port[0])
if (nk.addr[0] != sk.addr[0] or nk.port[0] != sk.port[0]):
s += " ({0}".format(sk.addr[0])
if sk.port[0]:
s += (":{0})" if state.af == AF_INET else "[{0}])").format(sk.port[0])
print "State {0.id:#x} on {0.ifname} ({1})".format(state, s)
print " Packets: {0.packets[0]}:{0.packets[1]}".format(state)
print " Bytes: {0.bytes[0]}:{0.bytes[1]}".format(state)
print " Expires in {0.expire} seconds".format(state)
py-pf - Managing OpenBSD's Packet Filter with Python