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):

The local endpoints are identical if no redirection is performed on the inbound packets. For outbound packets:

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)