PF overview
PF is OpenBSD’s powerful packet filtering utility. It has implementations across several other BSDs, most notably pfSense (a FreeBSD derivative)1.
It can be configured to act as a sensible firewall on a router.
Configuration
Below is a basic configuration for a dual-stack network; it has been adapted from https://markshroyer.com/guides/router/ch07.html#sec-gateway-pf.conf with IPv6 additions.
pf.conf
#####################
# Macros and Tables #
#####################
### Interfaces ###
if_lo = "lo0"
if_wan = "pppoe0"
if_lan = "em1"
### Subnets and address ranges ###
# LAN subnet
net_lan = "192.168.1.0/24"
### Address tables ###
# Address groups which shouldn't be in circulation on the Internet
table <martians> const { \
0.0.0.0/8 \
10.0.0.0/8 \
127.0.0.0/8 \
169.254.0.0/16 \
172.16.0.0/12 \
192.0.2.0/24 \
192.168.0.0/16 \
::1/128 \
::/128 \
::ffff:0:0/96 \
64:ff9b:1::/48 \
100::/64 \
2001::/32 \
2001:2::/48 \
2001:db8::/32 \
fc00::/7 \
fe80::/10 \
}
# Table of ddresses that have attempted to brute-force attack our SSH
# service from the Internet, for blocking purposes.
table <wan_bruteforce> persist
##############
# pf Options #
##############
# Reject disallowed TCP and UDP connections without a RST/ICMP UNREACHABLE
set block-policy return
# Log packet- and byte-count statistics for our Internet connection
set loginterface egress
# Don't filter on loopback interface
set skip on $if_lo
#########################
# Traffic Normalisation #
#########################
# Replace TCP timestamps and IP identification fields with random values to
# compensate for less secure values that may be generated by other hosts.
# Also, lower the MSS for traversing TCP connections to 1440 because our
# PPPoE interface's MTU is lower than that of clients' LAN interfaces.
match on egress scrub (reassemble tcp random-id max-mss 1440)
###############
# Translation #
###############
match out on egress inet from $net_lan to !(egress:network) nat-to (egress:0)
####################
# Packet Filtering #
####################
# Block everything unless we say otherwise
block all
# Prevent loopback address spoofing
antispoof quick for $if_lo
# Prevent spoofing where an antispoof rule wouldn't be a robust choice.
# However, this is incompatible with the bridged tap device configuration
# used for OpenVPN unless we set skip on one of the bridged interfaces.
block return in quick from urpf-failed
#################
# WAN interface #
#################
## Ingress
# Allow SSH access, but block any communications from hosts found trying to
# brute-force the SSH server...
block return in quick on egress from <wan_bruteforce>
pass in quick on egress proto tcp from any to (egress:network) \
port 22 modulate state \
(max-src-conn-rate 3/30, overload <wan_bruteforce> flush global)
# Necessary IPv4 ICMP types
pass in quick inet proto icmp \
icmp-type { echoreq unreach redir timex paramprob } keep state
# Necessary IPv6 ICMP types
# RFC 4890, Section 4.3.1
pass in quick on egress inet6 proto icmp6 all icmp6-type { unreach toobig echoreq echorep } keep state
pass in quick on egress inet6 proto icmp6 all icmp6-type timex code 0 keep state
pass in quick on egress inet6 proto icmp6 all icmp6-type paramprob code 1 keep state
pass in quick on egress inet6 proto icmp6 all icmp6-type paramprob code 2 keep state
# RFC 4890, Section 4.3.2
pass in quick on egress inet6 proto icmp6 all icmp6-type { 144 145 146 147 } keep state
pass in quick on egress inet6 proto icmp6 all icmp6-type timex code 1 keep state
pass in quick on egress inet6 proto icmp6 all icmp6-type paramprob code 0 keep state
# RFC 4861, Section 4
# TODO: egress instead of $if_wan doesn't work???
pass in quick on $if_wan inet6 proto icmp6 from any to { $if_wan ff02::1/16 } \
icmp6-type { routersol routeradv neighbrsol neighbradv redir } keep state
block return in on egress from <martians> to any
## Egress
pass out on egress inet6 all modulate state
pass out on egress from egress to any modulate state
block return out log on egress from any to <martians>
#################
# LAN interface #
#################
## Ingress
# Allow DHCP requests
pass in quick on $if_lan inet proto udp from { 0.0.0.0 $net_lan } \
port 68 to { ($if_lan) 255.255.255.255 } port 67
# Just to make sure we don't accidentally lock ourselves out of SSH
pass in quick on $if_lan proto tcp from any to (self) port ssh \
keep state
pass in on $if_lan
## Egress
pass out on $if_lan
After writing this, you should set pf_flags=YES
in /etc/rc.conf.local
to enable the daemon on boot, and then run pfctl -f /etc/pf.conf && pfctl -e
to run pf.
2018-08-06: EDIT: Add ICMPv6 types for SLAAC to work (router solicitation, etc.).
-
OpenBSD’s configuration syntax is incompatible with all other implementations; FreeBSD uses the old configuration syntax for compatibility. As such, the configuration here cannot be used for Free/NetBSD, although it can be used for ideas. ↩︎