Packet Filter Engine - Userland Control
The userland tool for injecting and inspecting data in the Packet Filter is pfctl(8).
NAME pfctl - control the packet filter (PF) device DESCRIPTION The pfctl utility communicates with the packet filter device using the ioctl interface described in pf(4) . It allows ruleset and parameter configuration and retrieval of status information from the packet filter.
Table of Contents
- Basic Invocation
- Common Invocations
- Load/Parse Rulesets
- Showing current state
- Show more details
- Flush Configuration
- Killing State
- Interesting Invocations
As your curiosity level with pf increases, (also known as “not knowing, what’s going on”) pfctl is the tool to use for inspecting how pf has viewed your ruleset (-s rules) and discover active details about the packet filter.
Work in Progress —
- What are some very basic commands
- What are very commonly used commands
- What are some additional interesting commands
- Start: pfctl -e
- Stop: pfctl -d
- Start with a ruleset file pfctl -ef /etc/pf.conf
- Check ruleset pfctl -nf /etc/pf.conf
- Reload ruleset file pfctl -f /etc/pf.conf
Below are the more common uses of pfctl in my context
Feeding the PF Engine is simplified using pfctl’s “-f” option for specifying a file with PF rules.
-f file Load the rules contained in file. This file may contain macros, tables, options, and normalization, queueing, translation, and filtering rules. With the exception of macros and tables, the statements must appear in that order.
Common invocations using “-f” include:
-f /etc/pf.conf Load the pf.conf file -nf /etc/pf.conf Parse the file, but don't load it -Nf /etc/pf.conf Load only the NAT rules from the file -Rf /etc/pf.conf Load only the filter rules from the file
As such, my most common invocation is to ‘parse’ a modified ruleset using “pfctl -nf /etc/pf.conf” where the rules are not loaded but are checked for syntax and some logic.
Similarly, because of Unix’s piping capabilities, we can verify and inject a new PF directive from the command line such as:
# echo "pass on eth0 from any to any port 80" | pfctl -nf -
stdin:1: port only applies to tcp/udp stdin:1: skipping rule due to errors stdin:1: rule expands to no valid combination
The above shows pfctl parsing the console input, and finding errors, which can be corrected in the below update to the rule (read: add “proto tcp”).
# echo "pass on eth0 proto tcp from any to any port 80" | pfctl -nf -
Note: No output indicates success.
Warning: note that “-f” loads the ruleset to replace the existing ruleset. If you intend to use the command-line to “add rules”, consider something as the below:
- Insert a new rule at the beginning of the existing ruleset
(echo “block all”; pfctl -sr) | pfctl -f -
- Insert a new rule at the end of the current ruleset
(pfctl -sr; echo “pass quick on lo0”) | pfctl -f -
Test your configuration
Install the ‘new’ ruleset for a set amount of time, and then re-install the ‘production’ ruleset.
pfctl -vf /etc/pf.conf.new ; sleep 30; pfctl -f /etc/pf.conf
Showing current state
We can view PF’s interpretation, by using the “-s rules”
-s modifier Show the filter parameters specified by modifier (may be abbreviated):
You create rules following the manual pages pf.conf and the FAQ, but you are still uncertain that you and the Packet Filter understand the same thing from manuals.
-s rules Show the currently loaded filter rules. When used together with -v, the per-rule statistics (number of evaluations, packets and bytes) are also shown. Note that the ``skip step'' optimization done automatically by the kernel will skip evaluation of rules where possible. Packets passed statefully are counted in the rule that created the state (even though the rule isn't evaluated more than once for the entire connection).
For an example, to use the default ruleset (/etc/pf.conf) as an example
set skip on lo pass # to establish keep-state block in on ! lo0 proto tcp to port 6000:6010
pfctl -s rules
pass all flags S/SA keep state block drop in on ! lo0 proto tcp from any to any port 6000:6010
The rules inside the PF engine is more specific than what is written in PF (a simplistic example) but provides a simple overview of how you can use pfctl to review your understanding, against how the PF engine is interpreting it.
-s nat Show the currently loaded NAT rules -s queue Show the currently loaded queue rules -s rules Show the currently loaded filter rules -s Anchors Show the currently loaded anchors directly attached to the main ruleset. -s states Show the contents of the state table. -s Sources Show the contents of the source tracking table. -s info Show filter information (statistics and counters) -s labels Show per-rule statistics. -s timeouts Show the current global timeouts -s memory Show the current pool memory hard limits -s Tables Show the list of tables -s osfp Show the list of operating system fingerprints -s Interfaces Show the list of interfaces and interface drivers available to PF. -s all Show all of the above.
Show more details
To get more detailed usage data for each show values option, pfctl can be invoked with the “-v” (verbose) option. The following example shows how to get detailed usage data for each firewall rule:
% sudo pfctl -v -s rules
scrub in all no-df fragment reassemble [ Evaluations: 373620187 Packets: 179290329 Bytes: 45895535624 States: 0 ] [ Inserted: uid 0 pid 24100 ] scrub out all no-df fragment reassemble [ Evaluations: 194329858 Packets: 194329858 Bytes: 58848165945 States: 0 ] [ Inserted: uid 0 pid 24100 ] block drop in log on ! lo0 inet from 127.0.0.0/8 to any [ Evaluations: 2934066 Packets: 0 Bytes: 0 States: 0 ] [ Inserted: uid 0 pid 24100 ]
To optimize ruleset evaluation, tables is an important tool in storing data for the PF Engine. pfctl provides some useful invocations to let you monitor and manage tables.
-s Tables Show Tables -T show -t table-name Display entries in table 'table-name' -T show -vt table-name Display table counters for 'table-name' -vs Tables Display global information -T add -t table-name ip-address Add an 'ip-address' to table 'table-name' -T delete -t table-name ip-address Delete an 'ip-address' from table 'table-name'
The general invocation of pfctl to reload the ruleset is
pfctl -f /etc/pf.conf
pfctl instructs the PF Engine to load a new ruleset from /etc/pf.conf and on completion switch from the existing ruleset to the newly loaded ruleset.
Noted practises use -F rules -f /etc/pf.conf which, noted by dhartmei’s article PF Firewall Management introduces a moment (between Flusing the rules, and the kernel loading the new rules) where no rules exist.
We can flush and remove other items from the PF Engine.
-F nat Flush the NAT rules -F queue Flush the queue rules -F rules Flush the filter rules -F states Flush the state table (NAT and filter) -F sources Flush the sources tracking table -F info Flush the filter information (statistics that are not bound to rules) -F Tables Flush the tables -F osfp Flush the passive operating system signatures -F all Flush all of the above
As in the above table, when you’ve made changes to /etc/pf.conf and are reloading the ruleset, make sure you flush the appropriate rules (whether they’re changes you’ve made to nat or filter rules etc.)
Remember that you don’t want to flush without loading a new set of replacements.
[Ref: PF Firewall Management]
When refreshing firewall rules, you can kill state information by explicitly using the “-k | -K” pfctl option.
-K host | network -k host | network | label | id For example, to kill all of the state entries originating from ``host'': # pfctl -k host A second -k host or -k network option may be specified, which will kill all the state entries from the first host/network to the second. To kill all of the state entries from ``host1'' to ``host2'': # pfctl -k host1 -k host2 A network prefix length of 0 can be used as a wildcard. To kill all states with the target ``host2'': # pfctl -k 0.0.0.0/0 -k host2 It is also possible to kill states by rule label or state ID. In this mode the first -k argument is used to specify the type of the second argument. The following command would kill all states that have been created from rules carrying the label ``foobar'': # pfctl -k label -k foobar To kill one specific state by its unique state ID (as shown by pfctl -s state -vv), use the id modifier and as a second argument the state ID and optional creator ID. To kill a state with ID 4823e84500000003 use: # pfctl -k id -k 4823e84500000003 To kill a state with ID 4823e84500000018 created from a backup firewall with hostid 00000002 use: # pfctl -k id -k 4823e84500000018/2
Obviously there are some cooler uses of pfctl, want to share?