An basic pf.conf Setup for IPv4/IPv6 FreeBSD Routers
Table of contents
This configuration include:
- IPv4 and IPv6 support
- Two physical interface WAN & LAN
- One wireguard interface
- IPv4 source NAT for the WAN interface
- Port forwarding for public IPv4 services (dnat + forwarding)
- Forwarding for public IPv6 services
- Forwarding for internal services such as dhcp dns ntp
- ICMP and ICMP6 filtering
- NAT Reflection/Hairpinning rules for internal access to public services
/etc/pf.conf
ext_if="igb0" #WAN interface
int_if="igb1" #LAN interface
wg_if="wg0" #Wireguard interface
# An server used as an host with mostly HTTP(S) services
server_inet6_addr="2001:db8::5"
server_inet_addr="192.0.2.5"
# Internal IPv4/IPv6 subnets
inet6_subnet="2001:db8::/48"
inet_subnet="192.0.2.5/24"
### options ###
# default policy, drop packets for block action
set block-policy drop
# aggressively expire connections
set optimization aggressive
# enable statistics for the external interface
set loginterface $ext_if
# Do not filter on loopback interface ( allow in and out )
set skip on lo0
### scrub ###
scrub in all
### nat src ###
# snat, translate IPv4 src addresses to the IPv4 assigned to the WAN interface
nat on $ext_if from $int_if:network \
to any -> ($ext_if)
# (d)nat reflection stage 2
nat on $int_if proto { tcp udp } from $int_if:network \
to $server_inet_addr port { http https } -> $int_if
### nat dest ###
# tcp dnat for http & https
rdr pass on $ext_if proto tcp from any \
to ($ext_if) port { http https } -> $server_inet_addr
# udp dnat for quic http/3
rdr pass on $ext_if proto udp from any \
to ($ext_if) port https -> $server_inet_addr
# (s)nat reflection stage 1
rdr pass on $int_if proto { tcp udp } from $int_if:network \
to ($ext_if) port { http https } -> $server_inet_addr
### SSH ###
# always all SSH from anywhere, avoiding lockout
pass in quick proto tcp from any to self port ssh
### filters ###
# block traffic entering from the external interface
block in on $ext_if from any to any
# reject with icmp message when entering from internal interface
block return-icmp in log on $int_if from any to any
### inbound int_if ###
# drop all packets with the same src addr as the specified interface
antispoof for $int_if
# allow traffic to pass from internal network on internal interface to any LAN->WAN
# traffic to the internal subnets is rejected since it would render internal rules like dns and dhcp useless
pass in on $int_if from $int_if:network to ! $inet_subnet
pass in on $int_if from $int_if:network to ! $inet6_subnet
# pass icmp(6) packets on the internal interface
pass in quick on $int_if inet proto icmp
pass in quick on $int_if inet6 proto ipv6-icmp
# pass tcp, udp for dns
pass in quick on $int_if proto { tcp udp } from $int_if:network \
to self port domain
# pass udp for ntp dhcp and wireguard
pass in quick on $int_if proto udp from $int_if:network \
to self port { ntp bootpc 51820 }
# pass udp for dhcpv6
pass in quick on $int_if proto udp from fe80::/10 \
to ff02::1/16 port dhcpv6-server
### inbound ext_if ###
antispoof for $ext_if
icmp_types = "{ echorep, unreach, echoreq, timex, paramprob }"
pass in quick on { $int_if $ext_if } inet proto icmp all icmp-type $icmp_types
icmp6_types="{ echoreq, echorep, unreach, toobig, timex, paramprob }"
icmp6_types_ext_if="{ echoreq, echorep, unreach, toobig, timex paramprob \
routersol, routeradv, neighbrsol, neighbradv }"
pass in quick on { $int_if $ext_if } inet6 proto ipv6-icmp \
icmp6-type $icmp6_types keep state
pass in quick on $ext_if inet6 proto ipv6-icmp from any \
to { ( $ext_if ), ff02::1/16 } icmp6-type $icmp6_types_ext_if keep state
# pass wireguard on self
pass in quick on $ext_if proto udp from any to self port 51820
# pass ssh to server directly in IPv6
pass in quick on $ext_if proto tcp from any \
to $server_inet6_addr port ssh
# pass tcp http to server
pass in quick on $ext_if proto tcp from any \
to { $server_inet_addr $server_inet6_addr } port http
# pass tcp/udp https to server
pass in quick on $ext_if proto { tcp udp } from any \
to { $server_inet_addr $server_inet6_addr } port https
### inbound wg_if ###
# pass everything from wireguard to anywhere
pass in quick on $wg_if from ($wg_if:network) to any
# No outbound filtering
### outbound int_if ###
pass out on $int_if
### outbound ext_if ###
pass out on $ext_if
### outbound wg_if ###
pass out on $wg_if
Thanks for reading! Read other posts?