ctx->guides->openbsd-gateway

Setting up a home gateway with OpenBSD + other goodies

If you have a spare box with two or more NICs, you can turn it into a powerful OpenBSD router.
In this tutorial, I will walk you through my gateway configuration.

Remember! Do not blindly copy paste the configuration files!

Available hardware and network layout

My router is a Shuttle XH81V. It has two Realtek NICs.

I have a single local subnet, 10.0.0.0/24.

Because of lack of additional NICs or a VLAN capable switch, there is no DMZ. To avoid exposing many services to the outside, I typically use ssh tunneling or a VPN to access local services behind the gateway.

I have a dedicated server hosted in a DC. I use tinc in a bridged mode configuration to make the server appear on my local subnet. This way, I can access the server transparently even on machines on my local network that I cannot install tinc to.

For IPv6, I use a Hurricane Electric tunnel. Their service has proven reliable with virtually no downtime experienced in the past year.

Topics covered

The following topics will be discussed:

Firewall, routing and NAT configuration

There is an excellent tutorial in the pf FAQ.
Fore more information, check the pf.conf(5), hostname.if(5) and ifconfig(8) manpages.

/etc/pf.conf

int_if   = "re1"
sshbox   = "10.0.0.2"
martians = "{ 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 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 \
              192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 \
              224.0.0.0/4 240.0.0.0/4 255.255.255.255/32 }"

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

match in all scrub (no-df random-id)

match out on egress inet from !(egress:network) to any nat-to (egress:0)

block drop in quick on egress from { no-route urpf-failed $martians }
block return out quick on egress to $martians
block

pass in on $int_if
pass in on egress inet proto icmp to (egress)
pass in on egress inet proto tcp to (egress) port ssh rdr-to $sshbox
pass out

/etc/sysctl.conf

net.inet.ip.forwarding=1

/etc/hostname.re0

description "WAN"
dhcp

/etc/hostname.re1

description "LAN"
inet 10.0.0.1 255.255.255.0 10.0.0.255
up

Reboot the router. This isn't required but it is a good idea to test that your changes are correctly set after a fresh boot.

DHCP server configuration

I use 2f30.org as the default search domain. I have a split horizon DNS configuration so I can access my machines from my local network as well as from the outside.

/etc/dhcpd.conf

option domain-name "2f30.org";
option domain-name-servers 10.0.0.1;

subnet 10.0.0.0 netmask 255.255.255.0 {
    option routers 10.0.0.1;
    range 10.0.0.32 10.0.0.127;

    host sshbox {
        hardware ethernet aa:bb:cc:dd:ee:ff;
        fixed-address 10.0.0.2;
    }
}

Update /etc/rc.conf.local:

dhcpd_flags="re1"

Restart dhcpd:

/etc/rc.d/dhcpd restart

Split horizon DNS

I am using unbound(8) as a caching DNS resolver.

/var/unbound/etc/unbound.conf

server:
    interface: 10.0.0.1
    access-control: 10.0.0.0/24 allow

    local-data: "gw.2f30.org. IN A 10.0.0.1"
    local-data-ptr: "10.0.0.1 gw.2f30.org."

    local-data: "sshbox.2f30.org. IN A 10.0.0.2"
    local-data-ptr: "10.0.0.2 sshbox.2f30.org."

forward-zone:
    name: "."
    forward-addr: 208.67.222.222
    forward-addr: 208.67.220.220

Update /etc/rc.conf.local:

unbound_flags=

Restart unbound:

/etc/rc.d/unbound restart

At this point, you should be able to plug a machine to your switch, get an IP address and browse the web.

You should also be able to access $sshbox from the outside over ssh on the default port. In my configuration this is a separate machine but could just as well be the router itself.

Using dnscrypt_proxy with unbound

First install dnscrypt_proxy from packages.

Adjust /etc/rc.conf.local:

dnscrypt_proxy_flags="-l /dev/null -R dnscrypt.eu-nl -a 127.0.0.1:53"
pkg_scripts="dnscrypt_proxy"

Start it:

/etc/rc.d/dnscrypt_proxy start

Then adjust the unbound configration:

/var/unbound/etc/unbound.conf

server:
    interface: 10.0.0.1
    access-control: 10.0.0.0/24 allow
    do-not-query-localhost: no  # needed as dnscrypt is listening on localhost

    local-data: "gw.2f30.org. IN A 10.0.0.1"
    local-data-ptr: "10.0.0.1 gw.2f30.org."

    local-data: "sshbox.2f30.org. IN A 10.0.0.2"
    local-data-ptr: "10.0.0.2 sshbox.2f30.org."

forward-zone:
    name: "."
    forward-addr: 127.0.0.1
    forward-addr: 208.67.220.220

Restart unbound:

/etc/rc.d/unbound restart

You should use tcpdump(8) to confirm that DNS requests are encrypted.

PXE booting

I use PXE booting on my laptop to upgrade OpenBSD. I run a tftp server on my router to serve the latest bsd.rd.

Setting up tftpd and dhcpd for PXE booting

Prepare /tftpboot:

mkdir /tftpboot
cp /usr/mdec/pxeboot /tftpboot

Update /etc/dhcpd.conf:

subnet 10.0.0.0 netmask 255.255.255.0 {
    filename "pxeboot";
    next-server 10.0.0.1;
    ...
}

Update /etc/rc.conf.local:

tftpd_flags="-l 10.0.0.1 /tftpboot"

Restart dhcpd and tftpd:

/etc/rc.d/tftpd restart
/etc/rc.d/dhcpd restart

Cron job to fetch latest bsd.rd

Use crontab -e as root to add a new job as follows:

15 10 * * * /usr/bin/ftp -o /tftpboot/bsd.rd http://ftp.openbsd.org/pub/OpenBSD/snapshots/amd64/bsd.rd 1>/dev/null

It will download bsd.rd once a day at 10:15 in the morning.

To test, plug your laptop to the switch and choose to boot over the network. Once you get to the OpenBSD boot prompt, type /bsd.rd and hit return.

Configuring an IPv6 gif(4) tunnel with Hurricane Electric

First of all, you will have to create an account on their website. From there, follow their guide to set up a tunnel. You will basically have to choose the tunnel endpoint. Find the one with the minimum latency.

/etc/sysctl.conf

net.inet6.ip6.forwarding=1

/etc/hostname.gif0

description "Hurricane Electric 6in4 link"
tunnel <your-ipv4-endpoint> <their-ipv4-endpoint>
mtu 1480
!ifconfig gif0 inet6 alias 2001:XXXX:XXXX:XXXX::2 2001:XXXX:XXXX:XXXX::1 prefixlen 128
!route -n add -inet6 default 2001:XXXX:XXXX:XXXX::1

/etc/hostname.re1

inet6 alias 2001:XXXX:XXXX:XXXX::1 64

This will add an IPv6 alias on your router's internal interface.

/etc/pf.conf

pass in on egress inet proto 41 from <their-ipv4-endpoint> to (egress)
pass in on gif0 inet6

Update /etc/rc.conf.local:

rtadvd_flags="re1"

Reboot your router.

On your OpenBSD client, enable autoconfiguration:

ifconfig em0 inet6 autoconf

NetFlow sensor and collector configuration

I use NetFlow to get an idea of what kind of traffic passes through my gateway.

To configure a netflow sensor on the gateway:

/etc/pf.conf

set state-defaults pflow

/etc/hostname.pflow0

flowsrc 10.0.0.1 flowdst 10.0.0.2:5555

Activate sensor:

pfctl -f /etc/pf.conf
sh /etc/netstart pflow0

On the receiver, in this case the box with address 10.0.0.2 we'll install flowd. It is a secure and minimal netflow collector written by Damien Miller.

First, install flowd from ports.

/etc/flowd.conf

logfile "/var/log/flowd"
listen on 10.0.0.2:5555
flow source 10.0.0.1
store ALL

Restart flowd:

/etc/rc.d/flowd restart

Give it a moment and use flowd-reader(8) on the specified logfile to examine the flows.

Debugging tips

One of the advantages of using OpenBSD as opposed to a standard consumer grade router is that you have all the needed tools at your disposal for debugging your network. The following manpages should be of interest.

Keep your configuration as simple as possible. Do not randomly poke on sysctl knobs you do not understand. Rely on the defaults unless you have a good reason not to.

If you have made extensive changes on a running system, do a final reboot to make sure everything comes back up as expected.

Consider having a second machine connected over serial to your router. This way you can capture a trace if the router crashes. It can also be used as an out-of-band mechanism to configure your router without hooking up a monitor and a keyboard.

Reading material

I've found the following references highly informative and useful.