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