The impact of CARP and pfsync on Packet Filter rules is really minimal. First, you need to let the PFSYNC and CARP protocols pass on their own interfaces:
pass quick on rl2 proto pfsync keep state (no-sync)
pass on { rl0, rl1 } proto carp keep state (no-sync)
Then, when writing firewall rules, keep in mind that, from pf(4)'s point of view, all traffic passes through the physical interface. Thus, in cases like:
pass in on $ext_if [...]
you can keep referring to the physical, not the virtual interface.
On the other hand, the virtual address is associated to the CARP interface; thus, you need to refer to it if the firewall offers any services on its virtual address:
# SSH on the virtual interface pass in on $int_if inet proto tcp from $int_if:network to carp0 port ssh
or on a NATed server, through traffic redirection:
# Mail server accessible from the internet pass in on $ext_if inet proto tcp from any to carp2 port $mail_ports rdr-to $mail_srv
In all other cases, CARP is perfectly transparent to pf(4), as for services offered by the firewall on its physical addresses:
# SSH on the physical address pass in on $int_if inet proto tcp from $int_if:network to $int_if port ssh
or for normal filtering:
# External DNS
pass in on $int_if inet proto { tcp, udp } from $int_if:network to $dns_srv \
port domain
pass out on $ext_if inet proto { tcp, udp } from $ext_if to $dns_srv \
port domain
As an example, let's see a basic PF ruleset for our external firewalls, Donald and Daisy:
################################################################################
# Macros and lists #
################################################################################
ext_if = rl0 # External interface
int_if = rl1 # DMZ interface
pfs_if = rl2 # Pfsync interface
carp_if = carp1 # External CARP interface
mail_srv = "mail.kernel-panic.it" # Mail server
web_srv = "{ www1.kernel-panic.it, www2.kernel-panic.it }" # Web servers
dns_srv = "{ dns1.isp.com, dns2.isp.com }" # DNS servers
int_fw = "{ mickey.kernel-panic.it, minnie.kernel-panic.it }" # Internal fw
mail_ports = "{ smtp, submission, imap, imaps }" # Mail server ports
web_ports = "{ www, https }" # Web server ports
# Allowed incoming ICMP types
icmp_types = "{ echoreq, timex, paramprob, unreach code needfrag }"
# Private networks (RFC 1918)
priv_nets = "{ 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }"
################################################################################
# Options, scrub and NAT #
################################################################################
set block-policy drop
set loginterface $ext_if
set syncookies always
set skip on lo
# NAT outgoing connections
match out on $ext_if from !$ext_if to any nat-to $ext_if
# Redirect web services (with load balancing)
match in on $ext_if inet proto tcp from any to $carp_if port $web_ports \
rdr-to $web_srv round-robin sticky-address
# Redirect mail services
match in on $ext_if inet proto tcp from any to $carp_if port $mail_ports \
rdr-to $mail_srv
################################################################################
# Filtering rules #
################################################################################
block all # Default deny
block in quick from urpf-failed # Spoofed address protection
# Scrub incoming packets
match in all scrub (no-df)
pass quick on $pfs_if proto pfsync keep state (no-sync) # Enable pfsync
pass on { $int_if, $ext_if } proto carp keep state (no-sync) # Enable CARP
block in quick on $ext_if from $priv_nets to any
block out quick on $ext_if from any to $priv_nets
# Mail server
pass in on $ext_if inet proto tcp from any to $mail_srv port $mail_ports
pass out on $int_if inet proto tcp from any to $mail_srv port $mail_ports
pass in on $int_if inet proto tcp from $mail_srv to any port smtp
pass out on $ext_if inet proto tcp from $ext_if to any port smtp modulate state
# Web servers
pass in on $ext_if inet proto tcp from any to $web_srv port $web_ports \
synproxy state
pass out on $int_if inet proto tcp from any to $web_srv port $web_ports
# ICMP
pass in inet proto icmp all icmp-type $icmp_types
pass out inet proto icmp all
# DNS
pass in on $int_if inet proto { tcp, udp } from $int_if:network to $dns_srv \
port domain
pass out on $ext_if inet proto { tcp, udp } from $ext_if to $dns_srv \
port domain
# Internet web servers
pass in on $int_if inet proto tcp from $int_fw to any port $web_ports
pass out on $ext_if inet proto tcp from $ext_if to any port $web_ports \
modulate state