Build a simple router/firewall

Introduction #

This guide will help you setup the software part of building a router/firewall with OpenBSD. The setup will consist of two network interfaces: 1 WAN connection, this is the connection with your ISP, and one LAN connection, which is the connection with the other machines in your network.

The router/firewall will be able to perform the following functions:

  • Routing: Send and receive packets from the internet and route them to the correct destinations.

  • Firewall: Filter incoming and outgoing packets to only allow those packets we actually want.

  • DHCP: Assign network addresses to local clients.

  • DNS: Cache the responses from upstream DNS providers to provide a faster answer to similar future queries.

All steps below require root privileges.
This guide uses 2 RealTek network interfaces: re0 and re1. Replace these interface names with the ones used on your system.

Routing and Networking #

First, we need to instruct OpenBSD to act as a router and forward packets.

For ipv4:

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

For ipv6:

echo 'net.inet6.ip6.forwarding=1' >> /etc/sysctl.conf

Depending on the ISP and address will either be assigned by DHCP:

echo 'dhcp' > /etc/hostname.re0

Or they assign you a static IP address, in which case you should add it like this:

echo 'inet $address $netmask $broadcast' > /etc/hostname.re0

Give the LAN interface a static network address (10.0.0.1). Usually the first address in the pool is used.

echo 'inet 10.0.0.1 255.255.255.0 10.0.0.255' > /etc/hostname.re1

And run the new configuration:

sh /etc/netstart

DHCP #

First we need to configure dhcpd to start at boot:

rcctl enable dhcpd

And tell it to only run on the LAN interface:

rcctl set dhcpd flags re1

Depending on your wishes, the dhcpd config can be as simple or as complex as you desire. The following is a very minimal config.

subnet 10.0.0.0 netmask 255.255.255.0 {
	option routers 10.0.0.1;
	option domain-name-servers 10.0.0.1;
	range 10.0.0.51 10.0.0.250;
}

Let’s break it down:

subnet 10.0.0.0 netmask 255.255.255.0 Here we define the actual range we want to use. In this case from 10.0.0.0 to 10.0.0.255.

option routers 10.0.0.1; Here we define the default gateway or default router for clients. In the case of this guide, the DHCP server and router are one machine, which is why we use the router’s address here.

option domain-name-servers 10.0.0.1; This option defines the name server(s). It’s possible to add more name servers seperated with a comma. As with DHCP, since this guide also sets up a name server on the router, the router’s address is defined again.

range 10.0.0.51 10.0.0.250; This option defines the range of the pool of addresses to be assigned to clients.

It’s also possible to assign addresses based on the hardware address (MAC address) of a client.

host server {
		fixed-address 10.0.0.30;
		hardware ethernet 00:00:00:00:00:00;

When added to the rest of the dhcpd config, it looks like this:

subnet 10.0.0.0 netmask 255.255.255.0 {
    option routers 10.0.0.1;
    option domain-name-servers 10.0.0.1;
    range 10.0.0.51 10.0.0.99;
    host server1 {
      fixed-address 10.0.0.30;
      hardware ethernet 00:00:00:00:00:00;
  }
}

Firewall #

Next up is the configuration of PF (Packet Filter), the OpenBSD firewall.

Open the pf configuration file.

vi /etc/pf.conf

And add something like the following. Keep in mind to change whatever is needed to properly run on your system and in your setup.

# Set up some macros
lan = "re1"

# Define a table with non-routable addresses
# These addresses should only be used internally and should not appear on the WAN interface
table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16  \
	 	   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3          \
	 	   192.168.0.0/16 198.18.0.0/15 198.51.100.0/24                 \
	 	   203.0.113.0/24 }

# Block silently in case traffic is blocked
# The other option is reject which informs the other system that the connection is not allowed
# This can be useful for debugging purposes.
set block-policy drop
# Enable logs for outgoing traffic

set loginterface egress

# Do not process any traffic for the loopback interface
set skip on lo

# Normalize network traffic
match in all scrub (no-df random-id max-mss 1440)
# Perform NAT
match out on egress inet from !(egress:network) to any nat-to (egress:0)

# Prevent spoofing
antispoof quick for { egress $lan }
block in quick on egress from <martians> to any
block return out quick on egress from any to <martians>

# Setup default deny policy for all traffic
block all

# Allow outgoing traffic for LAN and the gateway
pass out quick inet keep state

pass in on { $lan } inet

You can test the validity of the rules and the config with:

pfctl -n -f /etc/pf.conf

To start pf, you can run:

pfctl -e

to disable pf, you can run:

pfctl -d

A quick overview of how to use the pfctl command can be found here.

DNS #

First we need to enable the DNS server, Unbound, to start at boot time

rcctl enable unbound

Then we need to add some configuration so that it can server client requests

vi /var/unbound/etc/unbound.conf

server:
	interface: 10.0.0.1
	interface: 127.0.0.1
	interface: ::1

	access-control: 127.0.0.0/8 allow
	access-control: 10.0.0.0/24 allow
	access-control: 0.0.0.0/0 refuse
	access-control: ::0/0 refuse
	access-control: ::1 allow

  hide-identity: yes
  hide-version: yes

forward-zone:
	name: "."				# use for ALL queries
	forward-addr: 64.6.64.6			# Verisign
	forward-addr: 94.75.228.29		# Chaos Computer Club
	forward-first: yes			# try direct if forwarder fails

Modify to your needs and then start the Unbound DNS server:

rcctl start unbound