By now, you have spent significant time designing your network and implementing that design in your PF configuration. Getting your setup just right—removing any remaining setup bugs and inefficiencies—can be quite challenging at times.
This chapter describes some options and methods that will help you get the setup you need. First, we will take a look at global options and some settings that can have a profound influence on how your configuration behaves.
Network configurations are inherently very tweakable. While browsing the pf.conf
man page or other reference documentation, it is easy to be overwhelmed by the number of options and settings that you could conceivably adjust in order to get that perfectly optimized setup.
Keep in mind that for PF in general, the defaults are sane for most setups. Some settings and variables lend themselves to tuning; others should come with a big warning that they should be adjusted only in highly unusual circumstances, if at all.
Here, we’ll look at some of the global settings that you should know about, although you won’t need to change them in most circumstances.
These options are written as set
option setting
and go after any macro definitions in your pf.conf file, but before translation or filtering rules.
If you read the pf.conf
man page, you will discover that a few other options are available. However, most of those are not relevant in a network testing and performance tuning context.
The block-policy
option determines which feedback, if any, PF will give to hosts that try to create connections that are subsequently blocked. The option has two possible values:
drop
drops blocked packets with no feedback.
return
returns with status codes such as Connection refused
or similar.
The correct strategy for block policies has been the subject of considerable discussion over the years. The default setting for block-policy
is drop
, which means that the packet is silently dropped without any feedback. However, silently dropping packets makes it likely that the sender will resend the unacknowledged packets, rather than drop the connection. Thus, the effort is kept up until the relevant timeout counter expires. Unless you can think of a good reason to set the block policy to something else, set it to return
:
set block-policy return
This means that the sender’s networking stack will receive an unambiguous signal indicating that the connection was refused.
This setting specifies the global default for your block policy. If necessary, you can still vary the blocking type for specific rules.
For example, you could change the brute-force protection rule set from Chapter 6 to have block-policy
set to return
, but use block drop quick from <bruteforce>
to make the brute forcers waste time if they stick around once they have been added to the <bruteforce>
table. You could also specify drop
for traffic from nonroutable addresses coming in on your Internet-facing interface.
The skip
option lets you exclude specific interfaces from all PF processing. The net effect is like a pass-all
rule for the interface, but actually disables all PF processing on the interface. One common example is to disable filtering on the loopback interface group, where filtering in most configurations adds little in terms of security or convenience, as follows:
set skip on lo
In fact, filtering on the loopback interface is almost never useful, and can lead to odd results with a number of common programs and services. The default is that skip
is unset, which means that all configured interfaces can take part in PF processing. In addition to making your rule set slightly simpler, setting skip
on interfaces where you do not want to perform filtering results in a slight performance gain.
The state-policy
option specifies how PF matches packets to the state table. There are two possible values:
With the default floating
state policy, traffic can match state on all interfaces, not just the one where the state was created.
With an if-bound
policy, traffic will match only on the interface where the state is created; traffic on other interfaces will not match the existing state.
Like block-policy
, this option specifies the global state-matching policy.
You can override state policy on a per-rule basis if needed. For example, in a rule set with the default floating
state policy, you could have a rule like this:
pass out on egress inet proto tcp to any port $allowed modulate state (if-bound)
With this rule, any return traffic trying to pass back in would need to pass on the same interface where the state was created in order to match the state-table entry.
The situations in which state-policy if-bound
is useful are rare enough that you should leave this setting at the default.
The state-defaults
option was introduced in OpenBSD 4.5 to enable setting specific state options as the default options to all rules in the rule set unless specifically overridden by other options in individual rules.
Here is a common example:
set state-defaults pflow
This sets up all pass
rules in the configuration to generate NetFlow data to be exported via a pflow
device.
In some contexts, it makes sense to apply state-tracking options, such as connection limits, as a global state default for the entire rule set. Here is an example:
set state-defaults max 1500, max-src-conn 100, source-track rule
This sets the default maximum number of state entries per rule to 1,500, with a maximum of 100 simultaneous connections from any one host, with separate limits for each rule in the loaded rule set.
Any option that is valid inside parentheses for keep state
in an individual rule can also be included in a set state-defaults
statement. Setting state defaults in this way is useful if there are state options that are not already system defaults that you want to apply to all rules in your configuration.
The timeout
option sets the timeouts and related options for various interactions with the state-table entries. The majority of the available parameters are protocol-specific values stored in seconds and prefixed tcp.
, udp.
, icmp.
, and other.
. However, adaptive.start
and adaptive.end
denote the number of state-table entries.
The following timeout
options affect state-table memory use and to some extent lookup speed:
The adaptive.start
and adaptive.end
values set the limits for scaling down timeout values once the number of state entries reach the adaptive.start
value. When the number of states reaches adaptive.end
, all timeouts are set to 0, essentially expiring all states immediately. The defaults are 6,000 and 12,000 (calculated as 60 percent and 120 percent of the state limit), respectively. These settings are intimately related to the memory pool limit parameters you set via the limit
option.
The interval
value denotes the number of seconds between purges of expired states and fragments. The default is 10 seconds.
The frag
value denotes the number of seconds a fragment will be kept in an unassembled state before it is discarded. The default is 30 seconds.
When set, src.track
denotes the number of seconds source-tracking data will be kept after the last state has expired. The default is 0 seconds.
You can inspect the current settings for all timeout
parameters with pfctl -s timeouts
. For example, the following display shows a system running with default values.
$ sudo pfctl -s timeouts
tcp.first 120s
tcp.opening 30s
tcp.established 86400s
tcp.closing 900s
tcp.finwait 45s
tcp.closed 90s
tcp.tsdiff 30s udp.first 60s udp.single 30s udp.multiple 60s icmp.first 20s icmp.error 10s other.first 60s other.single 30s other.multiple 60s frag 30s interval 10s adaptive.start 6000 states adaptive.end 12000 states src.track 0s
These options can be used to tweak your setup for performance. However, changing the protocol-specific settings from the default values creates a significant risk that valid but idle connections might be dropped prematurely or blocked outright.
The limit
option sets the size of the memory pools PF uses for state tables and address tables. These are hard limits, so you may need to increase or tune the values for various reasons. If your network is a busy one with larger numbers than the default values allow for, or if your setup requires large address tables or a large number of tables, then this section will be very relevant for you.
Keep in mind that the total amount of memory available through memory pools is taken from the kernel memory space, and the total available is a function of total available kernel memory. The kernel allocates a fixed amount of memory for its own use at system startup. However, since kernel memory is never swapped, the amount of memory allocated to the kernel can never equal or exceed all physical memory in the system. (If that happened, there would be no space for user mode programs to run.)
The amount of available pool memory depends on which hardware platform you use, as well as on a number of hard-to-predict variables specific to the local system. On the i386 architecture, the maximum kernel memory is in the 768MB to 1GB range, depending on a number of factors, including the number and kind of hardware devices in the system. The amount actually available for allocation to memory pools comes out of this total, again depending on a number of system-specific variables.
To inspect the current limit
settings, use pfctl -sm
. Typical output looks like this:
$ sudo pfctl -sm
states hard limit 10000
src-nodes hard limit 10000
frags hard limit 5000
tables hard limit 1000
table-entries hard limit 200000
To change these values, edit pf.conf to include one or more lines with new limit
values. For example, you could use the following lines to raise the hard limit for number of states to 25,000 and table entries to 300,000:
set limit states 25000 set limit table-entries 300000
You can also set several limit
parameters at the same time in a single line by enclosing them in brackets, like this:
set limit { states 25000, src-nodes 25000, table-entries 300000 }
In the end, you almost certainly should not change the limits at all. If you do, however, it is important to watch your system logs for any indication that your changed limits do not have undesirable side effects or do not fit in available memory. Setting the debug
level to a higher value is potentially quite useful for watching the effects of tuning limit
parameters.
The debug
option determines what, if any, error information PF will generate at the kern.debug log level. The default value is err
, which means that only serious errors will be logged. Since OpenBSD 4.7, the log levels here correspond to the ordinary syslog
levels, which range from emerg
(panics are logged), alert
(correctable but very serious errors logged), crit
(critical conditions logged), err
(errors are logged), warning
(warnings are logged), notice
(unusual conditions are logged), info
(informational messages are logged), to debug
(full debugging information, likely only useful to developers).
In pre-OpenBSD 4.7 versions, PF used its own log-level system, with a default of urgent
(equivalent to err
in the new system). The other possible settings were none
(no messages), misc
(reporting slightly more than urgent
), and loud
(producing status messages for most operations). The pfctl
parser still accepts the older-style debug levels for compatibility.
After running one of my gateways at the debug
level for a little while, this is what a typical chunk of the /var/log/messages file looked like:
$ tail -f /var/log/messages
Oct 4 11:41:11 skapet /bsd: pf_map_addr: selected address 194.54.107.19
Oct 4 11:41:15 skapet /bsd: pf: loose state match: TCP 194.54.107.19:25
194.54.107.19:25 158.36.191.135:62458 [lo=3178647045 high=3178664421 win=33304
modulator=0 wscale=1] [lo=3111401744 high=3111468309 win=17376 modulator=0
wscale=0] 9:9 R seq=3178647045 (3178647044) ack=3111401744 len=0 ackskew=0
pkts=9:12
Oct 4 11:41:15 skapet /bsd: pf: loose state match: TCP 194.54.107.19:25
194.54.107.19:25 158.36.191.135:62458 [lo=3178647045 high=3178664421 win=33304
modulator=0 wscale=1] [lo=3111401744 high=3111468309 win=17376 modulator=0
wscale=0] 10:10 R seq=3178647045 (3178647044) ack=3111401744 len=0 ackskew=0
pkts=10:12
Oct 4 11:42:24 skapet /bsd: pf_map_addr: selected address 194.54.107.19
As you can see, the debug
level gives you a level of detail where PF repeatedly reports the IP address for the interface it is currently handling. In between the selected address messages, PF warns twice for the same packet that the sequence number is at the very edge of the expected range. This level of detail seems almost breathtaking at first glance, but in some circumstances, studying this kind of output is the best way to diagnose a problem and later check to see if your solution actually helped.
This option can be set from the command line with pfctl -x
, followed by the debug level you want. The command pfctl -x debug
gives you maximum debugging information; pfctl -x none
turns off debug messages entirely.
Keep in mind that some debug
settings can produce large amounts of log data, and in extreme cases, could impact performance all the way to self-denial-of-service level.
The ruleset-optimization
option enables or sets the mode for the rule set optimizer. The default setting for ruleset-optimization
in OpenBSD 4.1 and equivalents is none
, which means that no rule set optimization is performed at load time. From OpenBSD 4.2 onward, the default is basic
, which means that when the rule set loads, the optimizer does the following things:
Removes duplicate rules
Removes rules that are subsets of other rules
Merges rules into tables if appropriate (typical rule-to-table optimizations are rules that pass, redirect, or block based on identical criteria except source and/or target addresses)
Changes the order of rules to improve performance
For example, say you have the macro tcp_services = { ssh, www, https }
combined with the rule pass proto tcp from any to self port $tcp_services
. Elsewhere in your rule set, you have a different rule that says pass proto tcp from any to self port ssh
. The second rule is clearly a subset of the first, and they can be merged into one. Another common combination is having a pass
rule like pass proto tcp from any to int_if:network port $tcp_services
with otherwise identical pass
rules where the target addresses are all in the int_if:network
range.
With ruleset-optimization
set to profile
, the optimizer analyzes the loaded rule set relative to the actual network traffic in order to determine the optimal order of quick
rules.
You can also set the value of the optimization option from the command line with pfctl
:
$ sudo pfctl -o basic
This example enables the rule set optimization in basic
mode.
Since the optimization may remove or reorder rules, the meaning of some statistics—mainly the number of evaluations per rule—may change in ways that may be hard to predict. In most cases, however, the effect is negligible.
The optimization
option specifies profiles for state-timeout handling. The possible values are normal
, high-latency
, satellite
, aggressive
, and conservative
. The recommendation is to keep the default normal
setting unless you have very specific needs.
The values high-latency
and satellite
are synonyms, where states expire more slowly in order to compensate for potential high latency.
The aggressive
setting expires states early in order to save memory. This could, in principle, increase the risk of dropping idle-but-valid connections if your system is already close to its load and traffic limits, but anecdotal evidence indicates that the aggressive
optimization setting rarely, if ever, interferes with valid traffic.
The conservative
setting goes to great lengths to preserve states and idle connections, at the cost of some additional memory use.
The fragment reassembly options tied to scrub
were significantly reworked in OpenBSD 4.6, introducing the new set reassemble
option to turn reassembly of fragmented packets on or off. If reassemble
is set to off
, fragmented packets are simply dropped. The default is set reassemble on
, which means fragments are reassembled and reassembled packets where the do not fragment bit was set on individual fragments will have the bit cleared.
3.133.156.251