The syntax of PF rules is deceptively similar to IPF syntax:
action match-parameter optional-action-1 optional-action-2 ...
However, the results of identical rule interpretation can be very different. IPF rules do not translate easily into PF rules.
For example, the following rule set is valid in PF and IPF. However, IPF lets client 198.51.100.2 connect to 203.0.113.2 using Secure Shell, while PF does not. The explanation of different behavior is explained in Differences Between PF and IPF in State Matching.
block in from 203.0.113.2 to 198.51.100.2 block in from 198.51.100.2 to 203.0.113.2 pass in proto tcp from 198.51.100.2 to 203.0.113.2 port = 22 keep state
A rule in PF uses actions, match parameters, and optional actions to process packets and determine whether they are accepted or dropped. PF applies the action to the packet if the packet matches the rule. You write a rule by using the following elements in order:
Begin the rule with an action. For a list, see Packet Filter Rule Actions.
Match desired parameters. For a list, see Packet Filter Rule Match Parameters.
Include desired optional actions. For a list, see Packet Filter Rule Optional Actions.
For the complete grammar and syntax of PF rules, see the pf.conf(7) man page. For examples of rules whose policy or syntax differs between IPF and PF rules, see Examples of PF Rules Compared to IPF Rules.
Each rule begins with an action. PF applies the action to the packet if the packet matches the rule. The following list includes the commonly used actions applied to a packet and indicates whether the action was also in IP Filter.
Only in PF. Opens a new rule set which should be further applied to a matching packet.
Prevents the packet from passing through the filter.
## without an in, out, or on match parameter, ## blocks all traffic on all interfaces block all
Allows the packet through the filter. Applies to all packets, incoming and outgoing.
pass all
Provides fine-grained filtering without altering the block or pass state of a packet.
match rules differ from block and pass rules in that the parameters are set every time a packet matches the rule, not only on the last matching rule. These sticky parameters are in effect until explicitly overridden. Parameters affected are nat-to, binat-to, rdr-to, and scrub.
Keywords in this list define criteria that determine whether a packet matches the rule.
Applies the rule only if rule matches an inbound packet.
pass in from any to any port = 22
Applies the rule only if the rule matches an outbound packet.
pass out log on net0
Matches a packet that is moving in or out of the specified interface.
pass out log on net0
Applies the rule only if the rule matches a packet from the specified source.
A source or a destination can be one of the following:
An IP address.
CIDR notation is accepted, for example, 198.51.100.0/27.
A reference to a table, such as a whitelist of approved email sources.
A network interface name such as net0, which gets expanded to list of IP addresses and those addresses are plumbed to the interface.
The following example illustrates that well-formed rules do not require the use of in, out, or on. This rule matches all inbound and outbound packets on any interface on the host.
pass from any to any
The any keyword is used to accept packets from all sources and to all destinations. The IP addresses can be modified by a port number. The following example rule matches every inbound packet coming to port 22.
pass in from any to any port = 22
Matches TCP packets based on the TCP flags that are set.
The rule applies only to TCP packets that have the flags a set out of set b. Flags not in b are ignored.
The flags are: (F)IN, (S)YN, (R)ST, (P)USH, (A)CK, (U)RG, (E)CE, and C(W)R.
The following examples describe some sample flags settings:
flags S/S – Flag S is set and the other flags are ignored.
flags any – No flags are checked.
flags S/SA – Flag S is set. Default setting for stateful connections.
SYN and ACK together cannot match a packet. SYN+PSH, SYN+RST, and SYN can match a packet, but SYN+ACK, ACK+RST, and ACK cannot.
flags /SFRA – If the a set is not specified, it defaults to none. All a flags must be unset before using this filter.
See the pf.conf(7) man page for further uses and consequences of flags settings.
Matches the packet based on ICMP type. This keyword is used only when the proto option is set to icmp and is not used if the flags option is used.
Matches a specific protocol. You can use any of the protocol names specified in the /etc/protocols file, or use a decimal number to represent the protocol. The keyword {tcp, udp} matches either a TCP or a UDP packet.
Matches the packet based on the type-of-service value expressed as either a hexadecimal or a decimal integer.
Matches the packet based on its time-to-live value. A packet's stored time-to-live value indicates the number of hops the packet can make before being discarded.
Keywords in this list define additional optional actions.
When specified for a pass rule, allows packets to pass the filter based on the last matching rule even if the packets contain IP options or routing extension headers. Without allow-opts, PF blocks IPv4 packets with IP options and IPv6 packets with routing extension headers.
An abbreviation for keep state. Determines the information that is kept for a packet that matches a given rule. Because the pass rules in PF create a state by default, the keep option is useful to further specify options for the kept state, such as sloppy and if-bound.
Specifies that IP addresses are to be changed as the packet traverses the given interface. This technique allows one or more IP addresses on the translating host to support network traffic for a larger range of systems on an internal network. Internal network addresses should conform to the address ranges defined in Address Allocation for Private Internets, RFC 1918.
Redirects the packet to another destination and possibly a different port. rdr-to can specify port ranges as well as single ports.
Logs the matching packet. If log is used in a match action, then the packet is logged immediately on match even if the state is not kept. Multiple matching match rules will log the packet multiple times.
log accepts the following arguments:
(all) to log all packets regardless of state
(matches) to log the packet on all subsequent matching rules
(user) to add UID and PID socket information to the log
(to interface) to send logs to a specified capture interface
Executes the rule containing the quick option if the packet matches the rule. All further rule checking stops.
Moves the matching packets to an outbound queue on a named interface. Additionally, route-to can send different packets to different interfaces or different next hops by way of specified interfaces. Implements policy-based routing (PBR).
route-to accepts the following arguments. Note that the (interface-name + next-hop address) pairs have two syntax variants:
interface-name
(interface-name next-hop-address) or next-hop-address@interface-name
{ (interface1-name next-hop1-address), (interface2-name next-hop2-address) [, ...] } or { next-hop1-address@interface1-name, next-hop2-address@interface2-name[, ...] }
For how to select which interface to use when specifying several interfaces, see http://www.openbsd.org/faq/pf/pools.html.
PF provides macros, tables, and interface groups to ease the readability, extensibility, and reuse of PF rules. Macros and tables accept lists. Interface groups simplify applying the same policy to multiple systems where the interface names that handle the policy are different on those systems.
Macro – Defines one or more items to be treated as one item. Specify a name that is easy to understand. For example, the following rule uses two easily understood macros:
emailserver = 192.1.2.223 email = "{ smtp, pop3, imap }" ## mail services pass in from any to $emailserver port = $email
Table – Lists of IP addresses that can be manipulated without reloading the entire rule set. Promotes fast lookups.
A table is defined by a rule or set of rules. If all rules which refer to a table are deleted, the table is destroyed. To create a table that would outlive its rules, use the persist keyword.
For example, the following clients table is a list of clients. One address in the client range is disallowed:
table <clients> persist { 198.51.100.0/27, !198.51.100.5 }
Group – Name for a group of interfaces. The group name is used in PF configuration rules. Any policy that is defined for the group is then applied to interfaces that are members of the group. A group is also known as a firewall interface group, where the members of the group handle similar types of network traffic. The group name can be any string of up to 31 characters, starting with an alphabetic character and not ending in a digit.
Administrators add interfaces to the groups and use the group name in rules. After the administrator adds interfaces to groups using the ipadm command and loads rules referring to the group, PF can use the group name to match packets. An interface can be a member of several interface groups. For an example, see Example 11, PF Configuration File Using Firewall Interface Groups.
This example illustrates how to construct a NAT rule. This rule translates the source address in the outbound packet to an address which is bound to the net2 interface. It rewrites a packet that goes out on the net2 device with a source address of 198.51.100.0/27 and externally shows its source address as net2:
## NAT rule that externally shows net2 as source address pass out on net2 from 198.51.100.0/27 to any nat-to (net2)Example 4 Spam Rule in PF
This example illustrates the use of tables in PF. The administrator creates a spam table for IP addresses that send spam. The following firewall rule blocks incoming packets from all addresses in the spam table.
table <spam> { 198.51.100.0/27 } block in from <spam> to any
To block a new spam source, the administrator updates the table only.
# pfctl -t spam -T add 203.0.113.0/16
The pfctl command updates the firewall configuration without altering the rule set. In the kernel, the table now reads:
table <spam> { 198.51.100.0/27 203.0.113.0/16 }
For the complete grammar and syntax of PF rules, see the pf.conf(7) man page. For arguments to the ipadm command, see the ipadm(8) man page.
PF and IPF have different default filtering behavior but use a similar syntax. Note that unmodified IPF rules in the PF configuration file are likely to implement the wrong security policy. The following examples illustrate some of the differences. For ways to verify your PF rules, see Using PF Features to Administer the Firewall.
Unlike IPF, PF processes the packets that are bound to the loopback interface by default. Consider the following rule set:
pass out all block in all
The pass out all rule enables the system to connect to remote servers. Incoming packets from remote clients get dropped by the block in all rule. Because this rule set also applies to loopback traffic, the system is effectively prevented from connecting to local servers. For example, ssh localhost fails with the error message, connection time out.
PF and IPF match particular packets to state in different ways. Consider the following rule set on a router that forwards traffic between the 198.51.100.0/27 and 203.0.113.0/16 networks:
## Valid rule set in IPF and PF block in from 198.51.100.0/27 to any block in from 203.0.113.0/16 to any pass in from 198.51.100.2 to 203.0.113.2 keep state
Although these rules are valid for both firewalls, they implement different policies.
In IPF, the 198.51.100.2 client can reach the 203.0.113.2 server and the server's response packets can reach the client.
The block rules prevent all traffic between the 198.51.100.0/27 and 203.0.113.0/16 networks.
The pass rule allows the 198.51.100.2 client to connect to the 203.0.113.2 server.
The keep state action in the pass rule allows the server's responses to reach the client.
As soon as the first request packet sent by 198.51.100.2 matches the pass rule, a state is created: 198.51.100.2 -> 203.0.113.2. The state also matches the reverse packets sent by the server back to the client: 203.0.113.2 -> 198.51.100.2.
In PF rule processing, the 198.51.100.2 client cannot connect to the 203.0.113.2 server.
PF state inspection is stricter. The in match parameter to the pass rule instructs PF to match only inbound packets. Therefore, the state that the pass in rule creates matches only two kinds of packets:
Inbound forward packet, sent by client to server: 198.51.100.2 -> 203.0.113.2
Reverse outbound packet, sent by server to client: 203.0.113.2 -> 198.51.100.2
When a response arrives from the server to the PF firewall, PF does not see the packet as a reverse packet but as inbound for the first time, so the packet does not match the state that the pass in rule creates. Rule processing continues to look for a rule that matches the packet to determine whether to forward the packet or drop it. The packet matches the second block rule, block in from 203.0.113.0/16 to any, so PF drops the response sent by the server.
For the traffic to be routed in PF as it is in IPF, you have two options:
Add a rule that creates a state for forward outbound packets. This state will enable the inbound reverse packets from the server to be valid.
## PF rule set1 enforces IPF rule set policy block in from 198.51.100.0/27 to any block in from 203.0.113.0/16 to any pass in from 198.51.100.2 to 203.0.113.2 keep state pass out from 198.51.100.2 to 203.0.113.2 keep state
The pass rules create two states:
198.51.100.2 -> 203.0.113.2 @ in 198.51.100.2 -> 203.0.113.2 @ out
These states allow reverse inbound packets from the server:
in: 203.0.113.2 -> 198.51.100.2
Write a simpler rule set that does not use packet direction:
## PF rule set2 enforces IPF rule set policy block in from 198.51.100.0/27 to any block in from 203.0.113.0/16 to any pass from 198.51.100.2 to 203.0.113.2 keep state
The pass rule without an in parameter matches packets sent by the client regardless of direction. Therefore, when PF intercepts a packet from 198.51.100.2 as inbound for the first time, it creates a state:
198.51.100.2 -> 203.0.113.2 @ in
After the packet is routed by the firewall, it is intercepted by PF again, this time as an outbound packet. When the packet hits the pass rule, PF creates a second state:
198.51.100.2 -> 203.0.113.2 @ out
The OpenBSD version of PF supports NAT-64 as described by RFC 6146, while the Oracle Solaris version of PF supports traditional NAT only, as described by RFC 2663.
NAT typically sets up mapping between a network with a private address range and the Internet. NAT enables hosts in private networks to talk to any host on the Internet. In a typical deployment, the mapping is one to many, which means that many hosts in a private network share one public IP address to connect to Internet servers.
In PF, NAT processing is an optional rule action. A NAT rule in PF effectively creates a state that tells the firewall to alter the IP address depending on the action type of either rdr-to or nat-to, and the matching packet's direction, either inbound or outbound.
In PF, the nat-to and rdr-to actions are executed as soon as a packet matches the rule with the nat-to or rdr-to optional action. The subsequent rules see an already-translated packet. Both actions also create state for the matching packet.
nat-to – Typically used for outbound packets. Allows a network client in a private network to talk to an Internet server. nat-to tells the firewall to overwrite the source address and port in the matching packet according to additional parameters in the rule.
For example, the following rule changes a private source address from the address range 198.51.100.0/27 in a packet to a public IP address that is bound to the net0 interface:
pass out on net0 from 198.51.100.0/27 to any nat-to (net0)
rdr-to – Typically used for inbound packets. The rdr-to optional action is the opposite of nat-to in that it allows a client on the Internet to talk to a server behind the PF firewall in a network with private IP addresses. rdr-to changes the destination address in a matching packet.
For example, the following rule redirects inbound SMTP traffic to 203.0.113.2:2525, the IP address of the SMTP server on a private network behind the firewall. This rule matches any TCP inbound packet coming to the net0 interface and having the same destination IP address as the address that is bound to net0, and whose destination port is 25. The rdr-to action overwrites the destination address and port in the packet with 203.0.113.2:2525.
pass in on net0 inet proto tcp from any to (net0) port = 25 rdr-to 203.0.113.2 port 2525
Changes from the rdr-to or nat-to actions are immediately visible to subsequent rules unless the affected packet does not match a pass or block rule that creates a state. In that case, the packet that leaves PF is not touched by the rdr-to or nat-to option. A log action still logs the packet.
## Block packets going to 198.51.100.2 match out to 198.51.100.2 nat-to 198.51.100.1 block from 198.51.100.1
The effect of the preceding example is that the packets sent by the *2 address hit the block rule because they are matched to *1, which is blocked. If the match out to rule were not in the rule set, the *2 packets would go through.
All of the parameters of match rules that require a pass or block rule create a state. Sometimes a pass rule can be equivalent to match plus pass rules. For example, the following PF rule sets are equivalent:
webserver = "198.51.100.7" webports = "{ http, https }" emailserver = "198.51.100.5" email = "{ smtp, pop3, imap }" ### Rule set 1 - match action then pass action match in on $ext_if proto tcp to $ext_if port $webports rdr-to $webserver match in on $ext_if proto tcp to $ext_if port $email rdr-to $emailserver pass proto tcp from any to $webserver port $webports pass proto tcp from any to $emailserver port $email pass proto tcp from $emailserver to any port smtp ### Rule set 2 - no match action, only pass action pass in on $ext_if inet proto tcp to $ext_if port $webports rdr-to $webserver pass in on $ext_if inet proto tcp to $ext_if port $email rdr-to $mailserver pass on $int_if inet proto tcp to $webserver port $webports pass on $int_if inet proto tcp to $mailserver port $email