Redundant OpenBSD Firewalls

Part 2 - Routing and Packet Filter

In this part, we will set up fw1 as a stand-alone firewall providing routing and packet filtering. While configuring, we will make some accommodations for Part 4 and Part 5, where we add fw2 to the mix.

First you’ll need to install OpenBSD on fw1. Don’t forget to install any available binary system patches using syspatch.

Now we will configure fw1 so that it can perform routing, NAT, and packet filtering functions. Once we have everything working on a single host, we’ll begin the work of making everything redundant. All of the work for Part 1 will be done on fw1.

My ISP hands out IP addresses via DHCP. CARP was designed to work with static IP addresses. Part 3 of this guide exists to solve this problem, and it’s fairly complex. If your ISP gives static addresses, you can save yourself a bunch of configuration work.

Configure native network interfaces

First we need to set up our native network interfaces. My hardware has native network interfaces em0, em1, and em2. Yours may differ. I assigned em0 as the internal network interface, and assigned em1 as the public internet network interface, which is hooked up to my cable modem.

Create the file /etc/hostname.em0 with these contents:

inet description "internal interface"

And the file /etc/hostname.em1 with:

dhcp description "external interface"

Now start both interfaces:

fw1# sh /etc/netstart em0 em1

Configure gateway interface

The gateway IP address that all the hosts on the internal network will use to get to the internet is going to be a redundant, virtual IP address managed by CARP. CARP is enabled by default, but this ensures it’s enabled now, and at boot time:

fw1# sysctl net.inet.carp.allow=1
fw1# sysctl net.inet.carp.preempt=1
fw1# echo 'net.inet.carp.allow=1' >> /etc/sysctl.conf
fw1# echo 'net.inet.carp.preempt=1' >> /etc/sysctl.conf

We also turn on preempt, which allows hosts with a better advbase and advskew to preempt the master. CARP is enabled by default, but specifying these settings has the benefit of serving as a reminder that CARP is in use when we view the sysctl.conf file in the future.

Next, we create a carp0 network interface and assign it the IP address of, which is the gateway address all the other hosts on our internal network will use. Why are we bothering with a CARP interface when we don’t have two pieces of hardware yet? Because this way we have a CARP interface with the IP address of our default gateway. When we add our redundant firewall, we won’t have to reconfigure, we can just add the second host to the interface.

Put the following into /etc/hostname.carp0:

inet vhid 131 carpdev em0 pass vhid131passwd description "internal network gateway"

With the interface defined, we now apply the configuration with:

fw1# sh /etc/netstart carp0

In an effort to make things easier to remember, I have assigned the carp0 interface to the em0 interface. The CARP protocol uses a Virtual Host ID to identify the redundancy group (or virtual host) to other nodes on the network. Unfortunatly the Virtual Host ID doesn’t start at 0, it goes from 1 to 255. It would be super confusing to have the carp0 interface in group 1. So I chose to set the group to 131, which is similar to the last two octets of the IP address. Finally, the password needs to be the same on all hosts in the redundancy group. In this example, I’ve chosen a password that makes it clear that these passwords are shared with other nodes in the group.

Basic firewall and packet forwarding

With our network interfaces configured, we need to enable IP forwarding and network address translation.

fw1# sysctl net.inet.ip.forwarding=1
fw1# echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf

Packet filtering is enabled by default, we just need to create some rules in /etc/pf.conf:

# /etc/pf.conf
# remember to set net.inet.ip.forwarding=1


# options
set block-policy return
set loginterface egress
set skip on lo

# turn on NAT
match out on egress from ($int_if:network) to any nat-to (egress:0)

# filter rules
block in log
pass out quick
antispoof quick for { $int_if }

# pfsync and carp
pass on { $int_if } proto carp keep state (no-sync)

# allow traffic from the internal network
pass in quick on { $int_if } all

# allow icmp traffic
pass in inet proto icmp all

This basic configuration blocks inbound traffic from the internet, and passes outbound traffic while doing Network Address Translation. We also explicitly handle the carp protocol. I think good network citizens should allow ICMP traffic, so that’s enabled too.

Apply these new rules with:

fw1# pfctl -f /etc/pf.conf

That wraps up part 1, we have a basic firewall which will route traffic. In part 2 we’ll set up some additional network services like DNS, DHCP, and NTP.

Redundant OpenBSD Firewall Guide