Packet filtering takes place in the kernel. A pseudo-device,
/dev/pf, allows userland processes to control the
behavior of the packet filter through an
ioctl(2) interface.
There are commands to enable and disable the filter, load rulesets, add and
remove individual rules or state table entries, and retrieve statistics. The
most commonly used functions are covered by
pfctl(8).

Manipulations like loading a ruleset that involve more than a single
ioctl(2) call require
a so-called ticket, which prevents the occurrence
of multiple concurrent manipulations.

Fields of ioctl(2)
parameter structures that refer to packet data (like addresses and ports) are
generally expected in network byte-order.

Rules and address tables are contained in so-called
anchors. When servicing an
ioctl(2) request, if
the anchor field of the argument structure is empty, the kernel will use the
default anchor (i.e., the main ruleset) in operations. Anchors are specified
by name and may be nested, with components separated by ‘/’
characters, similar to how file system hierarchies are laid out. The final
component of the anchor path is the anchor under which operations will be
performed. Anchor names with characters after the terminating null byte are
considered invalid; if used in an ioctl,
EINVAL will be returned.

Add rule at the end of the inactive
ruleset. This call requires a ticket
obtained through a preceding DIOCXBEGIN
call. The optional anchor name indicates
the anchor in which to append the rule.
nr and
action are ignored.

Get the number nr of rulesets (i.e.,
anchors) directly attached to the anchor named by
path for use in subsequent
DIOCGETRULESET calls. Nested anchors,
since they are not directly attached to the given anchor, will not be
included. This ioctl returns EINVAL if
the given anchor does not exist.

Get a ruleset (i.e., an anchor)
name by its number
nr from the given anchor
path, the maximum number of which can be
obtained from a preceding
DIOCGETRULESETS call. This ioctl
returns EINVAL if the given anchor does
not exist or EBUSY if another process
is concurrently updating a ruleset.

This was primarily used to support transparent proxies with rdr-to rules.
New proxies should use divert-to rules instead. These do not require
access to the privileged /dev/pf device and
preserve the original destination address for
getsockname(2).
For SOCK_DGRAM sockets, the
ip(4) socket options
IP_RECVDSTADDR and
IP_RECVDSTPORT can be used to retrieve
the destination address and port.

If ps_len is non-zero on entry, as many
states as possible that can fit into this size will be copied into the
supplied buffer ps_states. On exit,
ps_len is always set to the total size
required to hold all state table entries (i.e., it is set to
sizeof(struct pfsync_state) * nr).

ticket must be set to the value obtained
with PF_CHANGE_GET_TICKET for all
actions except PF_CHANGE_GET_TICKET.
anchor indicates to which anchor the
operation applies. nr indicates the rule
number against which
PF_CHANGE_ADD_BEFORE,
PF_CHANGE_ADD_AFTER, or
PF_CHANGE_REMOVE actions are
applied.

Create one or more tables. On entry,
pfrio_buffer must point to an array of
struct pfr_table containing at least
pfrio_size elements.
pfrio_esize must be the size of
struct pfr_table. On exit,
pfrio_nadd contains the number of tables
effectively created.

Delete one or more tables. On entry,
pfrio_buffer must point to an array of
struct pfr_table containing at least
pfrio_size elements.
pfrio_esize must be the size of
struct pfr_table. On exit,
pfrio_ndel contains the number of tables
effectively deleted.

Get the list of all tables. On entry,
pfrio_buffer[pfrio_size] contains a valid
writeable buffer for pfr_table
structures. On exit, pfrio_size contains
the number of tables written into the buffer. If the buffer is too small,
the kernel does not store anything but just returns the required buffer
size, without error.

Clear the statistics of one or more tables. On entry,
pfrio_buffer must point to an array of
struct pfr_table containing at least
pfrio_size elements.
pfrio_esize must be the size of
struct pfr_table. On exit,
pfrio_nzero contains the number of tables
effectively cleared.

Add one or more addresses to a table. On entry,
pfrio_table contains the table ID and
pfrio_buffer must point to an array of
struct pfr_addr containing at least
pfrio_size elements to add to the table.
pfrio_esize must be the size of
struct pfr_addr. On exit,
pfrio_nadd contains the number of
addresses effectively added.

Delete one or more addresses from a table. On entry,
pfrio_table contains the table ID and
pfrio_buffer must point to an array of
struct pfr_addr containing at least
pfrio_size elements to delete from the
table. pfrio_esize must be the size of
struct pfr_addr. On exit,
pfrio_ndel contains the number of
addresses effectively deleted.

Replace the content of a table by a new address list. This
is the most complicated command, which uses all the structure members.

On entry, pfrio_table contains the table ID
and pfrio_buffer must point to an array
of struct pfr_addr containing at least
pfrio_size elements which become the new
contents of the table. pfrio_esize must
be the size of struct pfr_addr.
Additionally, if pfrio_size2 is non-zero,
pfrio_buffer[pfrio_size..pfrio_size2]
must be a writeable buffer, into which the kernel can copy the addresses
that have been deleted during the replace operation. On exit,
pfrio_ndel,
pfrio_nadd, and
pfrio_nchange contain the number of
addresses deleted, added, and changed by the kernel. If
pfrio_size2 was set on entry,
pfrio_size2 will point to the size of the
buffer used, exactly like
DIOCRGETADDRS.

Get all the addresses of a table. On entry,
pfrio_table contains the table ID and
pfrio_buffer[pfrio_size] contains a valid
writeable buffer for pfr_addr structures.
On exit, pfrio_size contains the number
of addresses written into the buffer. If the buffer was too small, the
kernel does not store anything but just returns the required buffer size,
without returning an error.

Clear the statistics of one or more addresses. On entry,
pfrio_table contains the table ID and
pfrio_buffer must point to an array of
struct pfr_addr containing at least
pfrio_size elements to be cleared from
the table. pfrio_esize must be the size
of struct pfr_addr. On exit,
pfrio_nzero contains the number of
addresses effectively cleared.

Test if the given addresses match a table. On entry,
pfrio_table contains the table ID and
pfrio_buffer must point to an array of
struct pfr_addr containing at least
pfrio_size elements, each of which will
be tested for a match in the table.
pfrio_esize must be the size of
struct pfr_addr. On exit, the kernel
updates the pfr_addr array by setting the
pfra_fback member appropriately.

Change the
PFR_TFLAG_CONST or
PFR_TFLAG_PERSIST flags of a table. On
entry, pfrio_buffer must point to an
array of struct pfr_table containing at
least pfrio_size elements.
pfrio_esize must be the size of
struct pfr_table.
pfrio_setflag must contain the flags to
add, while pfrio_clrflag must contain the
flags to remove. On exit, pfrio_nchange
and pfrio_ndel contain the number of
tables altered or deleted by the kernel. Yes, tables can be deleted if one
removes the PFR_TFLAG_PERSIST flag of
an unreferenced table.

Defines a table in the inactive set. On entry,
pfrio_table contains the table ID and
pfrio_buffer[pfrio_size] contains an
array of pfr_addr structures to put in
the table. A valid ticket must also be supplied to
pfrio_ticket. On exit,
pfrio_nadd contains 0 if the table was
already defined in the inactive list or 1 if a new table has been created.
pfrio_naddr contains the number of
addresses effectively put in the table.

Atomically switch a vector of inactive rulesets to the
active rulesets. This call is implemented as a standard two-phase commit,
which will either fail for all rulesets or completely succeed. All tickets
need to be valid. This ioctl returns
EBUSY if another process is
concurrently updating some of the same rulesets.

Add a passive OS fingerprint to the table. Set
fp_os.fp_os to the packed fingerprint,
fp_os.fp_class_nm to the name of the
class (Linux, Windows, etc),
fp_os.fp_version_nm to the name of the
version (NT, 95, 98), and
fp_os.fp_subtype_nm to the name of the
subtype or patchlevel. The members
fp_mss,
fp_wsize,
fp_psize,
fp_ttl,
fp_optcnt, and
fp_wscale are set to the TCP MSS, the TCP
window size, the IP length, the IP TTL, the number of TCP options, and the
TCP window scaling constant of the TCP SYN packet, respectively.

The fp_flags member is filled according to
the
<net/pfvar.h>
include file PF_OSFP_* defines. The
fp_tcpopts member contains packed TCP
options. Each option uses
PF_OSFP_TCPOPT_BITS bits in the packed
value. Options include any of
PF_OSFP_TCPOPT_NOP,
PF_OSFP_TCPOPT_SACK,
PF_OSFP_TCPOPT_WSCALE,
PF_OSFP_TCPOPT_MSS, or
PF_OSFP_TCPOPT_TS.

The fp_getnum member is not used with this
ioctl.

The structure's slack space must be zeroed for correct operation;
memset(3) the
whole structure to zero before filling and sending to the kernel.

Get the passive OS fingerprint number
fp_getnum from the kernel's fingerprint
list. The rest of the structure members will come back filled. Get the
whole list by repeatedly incrementing the
fp_getnum number until the ioctl returns
EBUSY.

Get the list of source nodes kept by sticky addresses and source tracking.
The ioctl must be called once with
psn_len set to 0. If the ioctl returns
without error, psn_len will be set to the
size of the buffer required to hold all the
pf_src_node structures held in the table.
A buffer of this size should then be allocated, and a pointer to this
buffer placed in psn_buf. The ioctl must
then be called again to fill this buffer with the actual source node data.
After that call, psn_len will be set to
the length of the buffer actually used.

If not empty, pfiio_name can be used to
restrict the search to a specific interface or driver.
pfiio_buffer[pfiio_size] is the
user-supplied buffer for returning the data. On entry,
pfiio_size contains the number of
pfi_kif entries that can fit into the
buffer. The kernel will replace this value by the real number of entries
it wants to return. pfiio_esize should be
set to sizeof(struct pfi_kif).