Ever get into a situation that your router is too slow, has a firmware no longer supported, or you are scary of tons of reported vulnerabilities and your vendor does not even care?

It is time to use set up a better router at home! With this, you can upgrade easily, always up-to-date, have security vulnerabilities fixed as soon as possible, and nearly zero downtime because of its reliability.

I used OpenBSD as my home router. It has a lot of advantages, like the following:

  • It runs on a normal machine as long as OpenBSD supports it, but in my case, I used a low powered FitLet2 from CompuLab. You do need at least 2 ethernet port though. Before the FitLet2, I used a PC Engines APU for almost 5 years and it keeps being upgraded.
  • OpenBSD is one of the more secure operating system in the world. The community focus on 1 thing - Security.
  • It has the easiest to configure firewall ruleset in the world, and it is a stateful firewall.
  • You can run a lot of other network related options from here, like a VPN server for iOS / Mac / Android, multiple LAN support, update your dynamic DNS entry (as long as you know scripting), or even allow different machines to go to the Internet using different outgoing providers!
  • It has excellent documentation and manual pages, which accurately describes its functionality and limitations so that you can think through what you need to do.

What do you need?

To build yourself a router, you will need the following:

  • A computer with 2 network ports at minimum, obviously with monitor and keyboard, which can be disconnected later. You do not need a monitor or keyboard to conduct upgrades / configuration changes.
  • A copy of OpenBSD downloaded from their website and installed through their standard installation guide. The default OpenBSD installation will have SSH enabled, and if you choose to, DHCP client enabled so that you can get a proper IP address from your original router. If you have PPPoE based Internet connectivity, refer to the appendix below for additional information.
  • A WIFI router which will be used as a bridge for your WIFI only devices. WIFI support is not a strong selling point for OpenBSD due to its strict open-source nature.
  • Assuming your internet network interface is em0, and your internal network interface is em1. If you are using Intel-based ethernet chipsets this will normally be the case. The internal network address range will be 192.168.0.0-192.168.0.255 (which is commonly referred to as 192.168.0.0/24, with a netmask of 255.255.255.0).
  • The router's internal IP address is 192.168.0.1

What's in the router?

After setup, you will get:

  1. A fully functional router with firewall for your home, which can allow you to connect to multiple devices and share the connection.
  2. Stateful inspection L2 / L3 firewall through packet filter, a long time trustworthy firewall implementation from OpenBSD
  3. DHCP server through DNSmasq, a very popular DHCP server that is used in almost all router distributions.
  4. DNS server through unbound, again, another popular choice for DNS server.
  5. Later in another article, I will share how you can setup a VPN for iOS, Android and MacOS (never tried Windows, so not sure if it will work) through IKED, a secured clean implementation of IPSec IKEv2 protocol.

Additional softwares that need to be installed on OpenBSD

We try to stick with OpenBSD networking software as much as possible, which is beneficial because OpenBSD's standard software are often of very high quality standard with security in mind. We will explain the reasons behind the following additional softwares. You can use the following command to install the software packages pre-compiled and hosted in OpenBSD website.

In OpenBSD, pre-built packaged softwares are called ports. They can either be compiled from source, or download from OpenBSD ports repository if the default compilation options are sufficient.

# Add doas (doas pkg_add) if you are running it
# with your admin user rather than root

pkg_add <software_package_name>

dnsmasq

We use dnsmasq as our DHCP server, which is responsible for distributing IP addresses to the machines within your home network. In OpenBSD, there is also another built-in software called dhcpd serving the same purpose. However, dhcpd does not serve DNS requests, nor it is integrated with the DNS server for OpenBSD (unbound). As a result, you cannot do reverse DNS lookup and find your machines by their name easily.

avahi_daemon (strictly optional just for convenience)

avahi is a software which will both broadcast and receive Bonjour requests to and from the machines in your network. If you use a Mac OS based machine, or if you use iOS, you can have this optionally installed to allow the Mac-based softwares set your router.

Installing avahi_daemon also installs messagebus as part of the requirement.

Setting up OpenBSD networking

It is really simple to setup OpenBSD networking. According to the assumption:

  • em0 is your Internet port (WAN), where you plug your upstream Internet connection to, and
  • em1 is your internal network port (LAN).
  • Also, since we will be setting up a IPSec VPN, we will need to enable the enc0 interface as well.

Thus you will have 2 configuration files in /etc folder.

  • hostname.em0
  • hostname.em1

/etc/hostname.em0 probably already exist when you setup OpenBSD, it should contain 1 line only:

dhcp
up
hostname.em0

Create a file called /etc/hostname.em1, and it should look like below:

inet 192.168.0.1 255.255.255.0
up
hostname.em1

Create another file called /etc/hostname.enc0, and it should look like below:

up
hostname.enc0

Base Operating System configuration

Adjust the system parameters to enable forwarding of packets, which essentially makes the operating system act like a router.

# Enable packet forwarding
net.inet.ip.forwarding=1
#net.inet6.ip6.forwarding=1

# Enable IP compression, required for IPSec
net.inet.ipcomp.enable=1				

# Do go to panic console when the system crash, 
# instead simply reboot it. (Never crashed anyway!)
ddb.panic=0							

Enable the following software by modifying the file /etc/rc.conf.local, which controls all daemons in OpenBSD. It has a very simple syntax which we will describe below:

# Enable APM daemon to save power
apmd_flags=-A

# Enable IKED if you want to enable
# VPN, simply having a line here
# with no value will enable the
# daemon. To disable it, comment
# the line
iked_flags=

# Enable IP multicast, which is needed
# for a lot of home appliances
multicast=YES

# Enable NTPD so that it can be used to
# synchronize time between the outside world
ntpd_flags=

# Enable firewall and logging
pflogd_flags=-f /dev/null

# Optional daemons to be enabled
# avahi_daemon - For Bonjour, if you have a network
#                with more MacOS machines.
# messagebus - Required for avahi_daemon
# dnsmasq - To allow the router to assign DHCP 
#           IP addresses to other machines
pkg_scripts=messagebus avahi_daemon dnsmasq

# Enable SLAACD if your ISP provides IPv6
# by removing "NO" from this line.
slaacd_flags=NO

# Disable Sound IO daemon, which is not used
# for routers.
sndiod_flags=NO

# Enable unbound, which is used for serving DNS
unbound_flags="-c /var/unbound/etc/unbound.conf"
rc.conf.local

DNS configuration

We will use unbound, a very popular DNS server package which is also in the base package of OpenBSD, as our DNS. The following is a very simple working DNS configuration for this purpose. In this configuration, we will forward all DNS queries received by unbound from your network to the Google DNS, utilising DNS over TLS technology, which secures DNS traffic.

Create the file /var/unbound/etc/unbound.conf and use the contents similar to below:

server:
        # Fully allow access from all hosts, since the router
        # is protected by firewall for easy setup and
        # troubleshooting
        #
        access-control: 0.0.0.0/0 allow
        access-control: ::0/0 allow
        do-ip6: yes
        port: 53
        pidfile: /var/run/unbound.pid
        hide-identity: yes
        hide-version: yes

        # Uncomment to synthesize NXDOMAINs from DNSSEC NSEC chains
        # https://tools.ietf.org/html/rfc8198
        #
        aggressive-nsec: yes

        # Mostly performance related catching adjustments
        #
        prefetch: yes
        num-threads: 4
        msg-cache-slabs: 16
        rrset-cache-slabs: 16
        infra-cache-slabs: 16
        key-cache-slabs: 16
        so-reuseport: yes
        rrset-cache-size: 100m
        msg-cache-size: 50m
        cache-max-ttl: 86400
        cache-min-ttl: 3600
        qname-minimisation: yes
        interface-automatic: yes

        # Security related settings
        #
        minimal-responses: yes
        use-caps-for-id: yes
        chroot: /var/unbound
        directory: /var/unbound
        rrset-roundrobin: yes
        
        # Specify a root certificate bundle to allow
        # DNS over TLS to work. Unbound will need to
        # verify the DNS over TLS certificate against
        # the root certificate
        #
        tls-cert-bundle: /etc/ssl/cert.pem

		# !!! IMPORTANT !!!
        # This specify the internal domain name that
        # your internal network will use. Typically
        # people simply use "lan"
        # This must also match with the relevant DHCP
        # setting in DNSMASQ
        #
        domain-insecure: "lan"

        # UDP EDNS reassembly buffer advertised to peers.
        # Default 4096. May need lowering on broken networks
        # with fragmentation/MTU issues,
        # particularly if validating DNSSEC.
        #
        edns-buffer-size: 1280
        
        # To allow local forward zone to be used.
        local-zone: "0.168.192.in-addr.arpa." transparent

# Forward zone for internal network
# This allows all internal name resolution
# to be forwarded to DNSMASQ in port 55
# for internal direct and reverse DNS
# resolution
#

forward-zone:
        name: "lan."
        forward-addr: 192.168.0.1@55
        forward-no-cache: yes

forward-zone:
        name: "0.168.192.in-addr.arpa."
        forward-addr: 192.168.0.1@55
        forward-no-cache: yes

# Default forward zone for external Name resolution
# We will use Google DNS which provides
# DNS over TLS, which prevents anyone from sniffing
# your DNS traffic. Note the setting
# forward-tls-upstream, use of port 853 and
# @dns.google suffix which is used to validate the
# TLS certificate
#
forward-zone:
        name: "."                               # use for ALL queries
        forward-tls-upstream: yes
        forward-addr: 8.8.8.8@853#dns.google
        forward-addr: 8.8.4.4@853#dns.google
unbound.conf

You can use the following command to restart unbound service:

# Should be run as root, or use doas
rcctl restart unbound

DHCP configuration

The following is a sample /etc/dnsmasq.conf for DHCP configuration. We will be listening to port 55 (instead of 53) for the DNS portion of DNSmasq so that we can stick with unbound as the default DNS server.

# Read the /etc/ethers file for mac address
# mapping to hostname, if you want to use
# static leases
#
read-ethers
dhcp-leasefile=/var/run/dnsmasq.leases

# Various DHCP options
#
dhcp-option=option:dns-server,192.168.0.1
dhcp-option=option:ntp-server,192.168.0.1

# Assign DHCP from range of
# 192.168.0.10 - 192.168.0.100
#
dhcp-range=em1,192.168.0.10,192.168.0.100,1h
dhcp-rapid-commit

# IPv6 options, if you want to enable
# IPv6
#
# dhcp-range=::100,::400,constructor:em1,ra-stateless
# ra-param=vether0,high,200,3600
# enable-ra
# quiet-ra

# !!! Important !!!
# domain specified here must match the configuration
# from unbound.conf
#
domain=lan
local=/lan/

# Listen to port 55
#
port=55

# If you have more than 1 ethernet port
# for internal network, you can bridge them and use
# the following to treat the bridge as 1 single
# DHCP network range.
# bridge-interface=vether0,em1,em2

# Specify interfaces which DHCP is not used
# In our assumption, em0 is the Internet port
#
no-dhcp-interface=em0,enc0

# Misc configurations normally useful
# but they seldom need to be changed.
bind-interfaces
user=_dnsmasq
group=_dnsmasq
all-servers
bogus-priv
domain-needed
log-async
log-facility=daemon
no-negcache
dnsmasq.conf

Firewall configuration

OpenBSD's firewall component, packet filter (PF),  is extremely famous in the world of Unix operating system. It is the foundation of multiple other operating systems' firewall, such as FreeBSD, Mac OS and iOS.

It has a very simple syntax, and is very easy to understand. In our sample configuration below, we will use pf using "quick" rules so that all rules in the beginning will take precedence, which makes things easier to understand.

Create a file called /etc/pf.conf and it should look like below as a standard working configuration.

wan = em0
lan = em1
vpn = enc0

table <reserved> const { 
        240.0.0.0/4, \
        224.0.0.22, \
        127.0.0.0/8, \
        192.168.0.0/16, \
        172.16.0.0/12, \
        10.0.0.0/8, \
        169.254.0.0/16, \
        192.0.2.0/24, \
        198.51.100.0/24, \
        203.0.113.0/24, \
        169.254.0.0/16, \
        0.0.0.0/8
}

set block-policy return
set state-policy floating
set loginterface egress
set skip on lo0

# Scrub IPv4
match on $wan inet scrub (random-id max-mss 1460 no-df)

# IPv4 NAT
match out on $wan inet nat-to ($wan:0)

# Security rules
block in log quick on egress from { <reserved> no-route urpf-failed } to any label noroute

# VPN - Allow IPSec traffic from Internet
pass in quick on $wan inet proto udp from any to ($wan:0) port {500, 4500} keep state label ipsec

# Default rules to allow internal network
# devices to access Internet
pass quick on $lan inet
pass in quick on $vpn inet keep state (if-bound)
pass out quick inet keep state

# Allow internet to ping your WAN IP address
pass in quick on $wan inet proto icmp from any to ($wan:0) icmp-type {echoreq unreach} keep state

# Block everything else, last rule in pf.conf
block in log all
pf.conf`

Restarting Everything

As of this point, you can restart the services to make things effective:

# Run the following as root, or use
# doas
sysctl -w net.inet.ip.forwarding=1
rcctl restart dnsmasq unbound
pfctl -ef /etc/pf.conf

And you have a working Internet router already!

Readouts