PF (packet filter) is one of the firewall technologies available on FreeBSD. PF originated from OpenBSD, although the two versions have since diverged significantly; FreeBSD uses the same version of PF as OpenBSD 4.5.
PF is a last-matching-rule-wins firewall. (The “quick” keyword on a rule means to stop and not evaluate subsequent rules.)
See pf.conf(5)
and pfctl(8)
and /usr/share/examples/pf/*
.
Enable PF in /etc/rc.conf
with:
pf_enable="YES"
pflog_enable="YES"
By default, pf loads its rules from /etc/pf.conf
.
This is a very simple PF config file:
block in all
pass out all keep state
Start PF (and PF logging):
# service pf start
# service pflog start
The pfctl utility configures rulesets and parameters, and retrieves status info from PF.
pfctl -e Enable PF.
pfctl -d Disable PF.
pfctl -F all -f /etc/pf.conf Flush all NAT, filter, state, and table rules and reload /etc/pf.conf.
pfctl -s [ rules | nat | states ] Report on the filter rules, NAT rules, or state table.
pfctl -vnf /etc/pf.conf Check /etc/pf.conf for errors, but do not load ruleset.
pfctl -k host Kill all state entries originating from "host"
pfctl -s states -vv Show state ID's, ages, and rule numbers
pfctl -s rules -vv Show rules with stats and rule numbers
pfctl -s Tables List tables
pfctl -t foo -T show Show the contents of table "foo"
pfctl -t foo -T delete xx.xx.xx.xx Delete address "xx.xx.xx.xx" from table "foo"
First, set:
# sysctl net.inet.ip.forwarding=1
# sysctl net.inet6.ip6.forwarding=1
Add to /etc/rc.conf
:
gateway_enable="YES"
ipv6_gateway_enable="YES"
And create the rules /etc/pf.conf
:
########################
## Macros
########################
ext_if = "em0"
int_if = "em1"
wifi_if = "wlan0"
int_server = "10.0.1.10"
int_nets = "{ 10.0.1.0/24, 10.0.2.0/24 }"
tcp_pass_out = "{ bootpc, bootps, dhcpv6-client, dhcpv6-server, domain, https, ipp, nicname, ntp, ssh, www, 6667, 6697 }"
udp_pass_out = "{ bootpc, bootps, dhcpv6-client, dhcpv6-server, domain, nicname, ntp }"
icmp_ok_types = "{ echoreq, unreach }"
########################
## Tables
########################
table <friendly_ip_addrs> { 192.0.2.17, 198.51.100.223, 203.0.113.110 }
table <sshprobe> persist
########################
## Options
########################
set skip on lo
########################
## Normalization
########################
scrub in
########################
## Translation
########################
nat on $ext_if inet from !($ext_if) to any -> ($ext_if)
rdr on $ext_if proto tcp from any to any port 8000 -> $int_server
########################
## Filtering
########################
block in
antispoof for $ext_if
antispoof for $int_if
antispoof for $wifi_if
pass quick on $ext_if from <friendly_ip_addrs> keep state
block quick from <sshprobe>
pass in inet proto tcp from any to $ext_if port ssh keep state (max-src-conn 5, max-src-conn-rate 3/5, overload <sshprobe> flush global)
pass proto tcp from $int_nets to any port $tcp_pass_out keep state
pass proto udp from $int_nets to any port $udp_pass_out keep state
pass proto tcp from $ext_if to any port $tcp_pass_out keep state
pass proto udp from $ext_if to any port $udp_pass_out keep state
pass inet6 proto tcp from fe80::/10 to any port $tcp_pass_out keep state
pass inet6 proto udp from fe80::/10 to any port $udp_pass_out keep state
pass inet6 proto icmp6 from any
pass inet proto icmp icmp-type $icmp_ok_types keep state
pass in from $int_nets to $int_if keep state # Anti-lockout rule
Note the use of variables like $ext_if
, macros like udp_pass_out = "{ domain, ntp }"
, and tables like table <friendly_ip_addrs> { 192.0.2.17, 198.51.100.223, 203.0.113.110 }
.
Scrub before any NAT or redirection rules. Define NAT and redirection rules before filtering rules.
If the external interface receives its address via DHCP, the PF rules may fail to load if FreeBSD tries before the interface gets its address.
Edit /etc/rc.conf
.
Change ifconfig_em0="DHCP"
to ifconfig_em0="SYNCDHCP"
.