IPv4 Router and PF Firewall

This article will detail how to configure an IPv4 only router and secure it with PF firewall.

In the examples provided re0 is the external and re1 is the internal interface.
Substitute them with your system's respective iface names.

 

Configure System Startup

Configure the network interfaces, enable routing, and enable PF firewall by adding the following to /etc/rc.conf:

ifconfig_re0="SYNCDHCP"
ifconfig_re1="inet 192.0.2.1 netmask 255.255.255.0"
gateway_enable="YES"

# PF
pf_enable="YES"
pf_rules="/etc/pf.conf"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"

 

Configure the PF firewall rules

Edit /etc/pf.conf

#### Macros ####
ext_if = re0
int_if = re1

# ICMP Types
icmp_types = "{ echorep }"

# Private and documentation example networks
priv_nets = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8, 255.255.255.255/32 }"
doc_example_nets = "{ 192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24 }"

#### Options ####
set block-policy drop
set skip on lo0

# Scrub
scrub on $ext_if all no-df random-id reassemble tcp

# NAT
nat on $ext_if from 192.0.2.0/24 -> ($ext_if)


#### Block ####
block log all

# Antispoof
antispoof quick log for $ext_if
block in on $ext_if from { urpf-failed, no-route }
block in quick log on $ext_if from { $priv_nets, $doc_example_nets }

# Block ICMP Port Unreachable Messages to Prevent and Mitigate DDOS and Other Attacks i.e. SAD DNS
block out quick log on $ext_if inet proto icmp icmp-type unreach code port-unr


#### Allow ####
# Allow all traffic on internal interface
pass quick on $int_if flags any

# Allow all traffic out via external interface
pass out on $ext_if flags any

# ICMP
pass in on $ext_if proto icmp all icmp-type $icmp_types

### Inbound Services
# pass in on $ext_if proto tcp to port http

Note: When PF loads the ruleset into memory it automatically appends keep state to all valid pass rules. If the rule is a TCP pass rule then flags S/SA keep state is appended instead. For example:

pass in on $ext_if proto tcp from any to any port http

is written into memory as:

pass in on re0 proto tcp from any to any port = http flags S/SA keep state

 

Reboot the system watching the console for any errors during startup.
Login and confirm that you can ping external Internet hosts.

 

When you make changes to /etc/pf.conf execute the following command to load the new rule-set:
pfctl -f /etc/pf.conf
If there are errors then the rule-set will not be loaded and the affected line number(s) will be identified.
Existing states in the states table will be retained. This means that connected systems and devices on your LAN will not be interrupted when reloading the rule-set.

Another useful command is pfctl -s modifier
pfctl -s rules will show the currently loaded filter rules.
There are several other useful modifiers available, refer to pfctl(8) for more information.