Pf syntax
Documentation
Tutorials
digitalocean guide https://www.digitalocean.com/community/tutorials/how-to-configure-packet-filter-pf-on-freebsd-12-1 traffic shaping with PF/ALTQ http://aptivate.org/en/blog/2011/08/05/traffic-shaping-with-pf-altq-and-hfsc/ beginner guide to pf http://srobb.net/pf.html
Locations
/etc/rc.conf
service configuration /etc/pf.conf
ruleset /etc/services
named services/port mapping
Overview
Read man pf.conf, it is very well written.
Statement Types
Type Description Macros variables Tables arrays of addresses, address-ranges, or network interfaces Queueing rule based bandwidth/priority control Options global or scoped pf configuration options Translation NAT, redirect, etc. Packet Filtering rules to pass/block packets Overview
ruleset flow
By defaut, every packet goes through every rule in the firewall (no early exits on match).
This allows you to blacklist everything, then whitelist only the traffic that you want.
You can alter this for rules using thequick
keyword (exits ruleset on match).stateful firewall
This is very confusing. My understanding is that upon traffic creation, a traffic decision is created that is reused by related traffic that follows. This avoids unecessy rule processing on traffic that is essentially the same.keep state
modulate state
and other keywords are used on rules to alter this behaviour.Note that
keep state
is the default behaviour on OpenBSD, but must be explicitly defined on FreeBSDSee explanation https://www.openbsd.org/faq/pf/filter.html#state
Example
# ========= # Variables # ========= ext_if = "eth0" lo_if = "lo0" localnet = $lo_if:network # alternatively private addr range private_ranges = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \ 10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \ 0.0.0.0/8, 240.0.0.0/4 }" # ================== # General Protection # ================== # policy 'drop' by default block all # repair/protect input traffic scrub in all fragment reassemble # protect ext_if against forged IPs antispoof for $ext_if # explicitly block private address ranges # from accessing external internet block drop in quick on $ext_if from $private_ranges to any block drop out quick on $ext_if from any to $private_ranges # reject ICMP, unless from localnet pass inet proto icmp from $localnet to any keep state pass inet proto icmp from any to $ext_if keep state # ============== # SSH Protection # ============== table <bruteforce> persist block quick from <bruteforce> # protect against quick bruteforce pass inet proto tcp from any to $localnet port 22 \ flags S/SA keep state \ (max-src-conn 100, `# max simultaneous connections` \ max-src-conn-rate 15/5, `# max 15conns/5s` \ overload <bruteforce> `# offenders added to <bruteforce> table` \ flush global) # on blacklisting, all connections from that host will be terminated # ===== # Rules # ===== pass out any pass in \ on eth0 \ proto tcp \ to port { 22, 80, 443 }# set <bruteforce> table entries expire if not renewed in 86400s pfctl -t bruteforce -T expire 86400
Components
Formatting
# example comment # multiline statements using '\' table <private_addrs> const \ { 10/8, 172.16/12, 192.168/16 }Macros (variables)
# syntax variable = "value" array = "{ lo0 eth0 wlan0 }" # array of interfaces range = 137:139 # array of ports 137-139 (samba) # example use within rule lo_if = "lo1" pass out on $lo_if from any to anyTables
Unlike iptables, in pf tables are a named collection of addresses, address-ranges, and/or exclusively on OpenBSD (not FreeBSD) interfaces.
Tables can be defined inpf.conf
, read from a file, or managed dynamically with pfctl.Basics
# from pf.conf table <private_addrs> const { 10/8, 172.16/12, 192.168/16 } # from file table <spam_addrs> persist file "/etc/spammers" # [file "/etc/another" ...] # fully dynamic table <bad_hosts> persistYou can then refer to tables with rules.
block in on em0 to <bad_hosts>
Tables can be pre-defined and static, or dynamic - with entries created by pfctl/other-rules.
The type of table is determined by the keyword used at it's creation.
persist
tables is not deleted even when no other rules refer to it. const
table cannot be modified by pfctl/other rules. counters
todo Dynamic Table Options
You can use
pfctl
to manage tables dynamically (ex: expiry dates for entries).# set <bruteforce> table entries expire if not renewed in 86400s pfctl -t bruteforce -T expire 86400 # table 'bruteforce' entries expire if not renewed in 86400s pfctl -t bruteforce show # show table 'bruteforce' contents pfctl -t bruteforce -T add 1.1.1.1 # add '1.1.1.1' to table 'bruteforce' pfctl -t bruteforce -T delete 1.1.1.1 # delete '1.1.1.1' from table 'bruteforce'Interface Groups
Exclusively on FreeBSD, you can refer to collections of network interfaces using interface groups. These are managed using
ifconfig
ifconfig -g epair # list all members of epair ifconfig epair1a group vnet_epairs # add epair1a to vnet_epairs ifconfig epair1a -group vnet_epairs # remove epair1a from vnet_epairsOptions
Options generally configure pf as a whole, but you can also use
With
blocks for more limited scopes. There are many options, see pf.conf for the full list.Some Interesting Options:
set block-policy, fail-policy
Change the action that occurs on
block
ordrop
etc.
For example, do you want to silently drop the packet? or return invalid?set fail-policy drop set block-policy return
set skip on <ifspec>
Define network interfaces you'd like to skip packet filtering on.
set skip on lo0Traffic Normalization
Traffic normalization sanitizes incoming packets, to try to prevent attacks where packets have been altered with misleading information. It also reassembles partial/incomplete fragments.
This is complex, see pf.conf]
# Recommended scrub in all # Example with params scrub in on $ext_if all fragment reassembleQueueing
Queueing allows you to organize/prioritize your traffic.
For all options see pf.confQueueing Involves 2x steps.
- Assign Queues to an interface (all traffic will pass through one of these queues)
- Define Queues (bandwidth, priority, matched traffic, etc)
1. Assign Queues
You must assign queues to an interface using
altq
.
This requires network card drivers, see manual section Enabling ALTQ, it may also need a kernel rebuild.# =========================================================================== # altq on <iface> <type> \ # [bandwidth <bw> qlimit <limit> size <size> queue { std, http, ssh, ... }] # =========================================================================== # Assign Queues altq on eth0 cbq queue { developers, business, services } # limit queue bandwidth (so it does not take from others) altq on eth0 cbq queue bandwidth 1MB { developers, business, services }2. Define Queues
Define queues that
altq
statements refer to.# ============================================================== # queue <name> [general queue-options...] [type(type_args, ...)] # ============================================================== queue ssh bandwidth 30% priority 2 hfsc(upperlimit 99%)priq (Priority Queueing)
Prioritize traffic, without any regard for bandwidth.
Packets of highest priority are always processed first.cbq (Class Based Queueing)
Same as PRIQ, except in addition to priority, bandwidth is taken into consideration.
High priority is processed first, until it's the bandwidth limit is reached, then other queues get processed.
Supports composing a hierarchical tree of queues, and borrowing from parent queue's bandwidth.Examples
# ===================================== # NOTE: !INCOMPLETE/UNRELATED EXAMPLES! # ===================================== queue std bandwidth 10% \ cbq(default) queue http bandwidth 60% \ priority 2 cbq(borrow red) \ { employees, developers } queue developers bandwidth 75% cbq(borrow) queue employees bandwidth 15%hfsc (Hierarchical Fair Service Curve)
HFSC is the most advanced queue type, and it offers real-time traffic guarantees.
Concrete information about how this works does not appear in the docs, but it seems to be the preferred type for most situations.Guarantee min bandwidth for services
# http://aptivate.org/en/blog/2011/08/05/traffic-shaping-with-pf-altq-and-hfsc/ # We create four named queues under the root, which is # implicitly named root_em1. We reserve 30% of bandwidth # each for FTP, SSH and other traffic, and 10% for ICMP. # However, any class can exceed its reserved bandwidth, # up to the upperlimit, which defaults to 100%, which means # that one class can potentially cause delays to traffic # in other classes, so we override this to 99%. altq on em1 hfsc bandwidth 1Mb queue { ftp, ssh, icmp, other } queue ftp bandwidth 30% priority 0 hfsc (upperlimit 99%) queue ssh bandwidth 30% priority 2 hfsc (upperlimit 99%) queue icmp bandwidth 10% priority 2 hfsc (upperlimit 99%) queue other bandwidth 30% priority 1 hfsc (default upperlimit 99%) pass out quick on bridge0 inet proto tcp from any port 21 to any queue ftp pass out quick on bridge0 inet proto tcp from any port 22 to any queue ssh pass out quick on bridge0 inet proto icmp from any to any queue icmp pass out quick on bridge0 allLimit up/down bandwidth
# http://microsux.dk/?p=321 int_if="em0" ext_if="em1" internal_net="10.10.10.0/24" external_addr="x.x.x.x" altq on $ext_if hfsc bandwidth 1950Kb queue {def_up} altq on $int_if hfsc bandwidth 1950Kb queue {def_down} queue def_up bandwidth 1950Kb hfsc(default) queue def_down bandwidth 1950Kb hfsc(default) nat on $ext_if from $internal_net to any -> $external_addr pass in quick on $ext_if from any to any pass out quick on $int_if from any to any queue def_down pass in quick on $int_if from any to any pass out quick on $ext_if from any to any queue def_upTranslation (NAT/Redirect)
NOTE:
for rdr statements you must enable
gateway_enable="YES"
(on both sides)Translation rules modify either the source or destination address of a packet.
See Examples, and Translation fromman pf.conf
.
This can be constrained to a specific port, or it could be all traffic on an interface.# bidirectional between external/internal IP netblock # # $src addr traveling to $dst -- $src replaced with $ext # $dst addr traveling to $src -- $src replaced with $ext # # inside/outside both think they are talking directly to middleman (and have no knowledge of the other) binat from $src to $dst -> $ext# nat traffic (change source) # I belive NAT is from the point of view of the source (-> ip-address) nat on $ext_if from 10.0.0.1 to any -> ($ext_if)# redirect traffic (change dest) rdr on $ext_if proto tcp from any to 1.1.1.1 port 2222 -> 2.2.2.2 port 22 rdr pass on bce0 proto tcp from any to $IP_PUB port $SQLPORT -> $SQLJAILPacket Filtering
Here you can block/pass matching packets.
See all options in man pf.conf].Unlike iptables, rules to not stop being processed after the first matching rule.
Generally, you start with a very strict policy, then gradually whitelist traffic afterwards.
All packet filtering rules start with the keywordblock
orpass
, then optionally can be configured to very explicitly match a type of traffic.# Example block all # all traffic blocked pass in { 22, 80, 443 } # define 'in' traffic pass out all keep state # all 'out' traffic allowed# Some unrelated examples to show options block all block in all pass out all \ keep state pass out \ on eth0 \ inet proto tcp \ to port { 22, 80, 443 } pass out \ to any pass out \ to port { 22, 80, 443 } # when quick is used, if packet matches this rule, # it's operation is taken, and the rest of the rules are not applied to it. # (more similar to iptables) block in quick \ on eth0 \ from ! 192.168.1.1 \ to any
Other
Anchors
You can specify alternate firewall configurations that are tailored to a specific task, then use them to protect operations (say for example an FTP download).
# /etf/ftp-anchor pass out proto tcp from any to port 21 keep state pass out proto tcp from any to port > 1023 keep state# ... anchor ftpanchorpfctl -a ftpanchor -s rules # temporarily replace firewall pfctl -a ftpanchor -F rules # restore default rules