# OpenBSD rdomains and wireguard

2020-11-17

This post is a bit wider than usual. This is to preserve the examples
intact and paste-able. Hopefully it looks allright in your client. Let me
know otherwise.

The last few weeks I've been experimenting with Wireguard on OpenBSD in
various ways.  Since I saw that support showed up in the kernel a while
ago, I was curious what it could be used for. Turns out that it is nicely
integrated, so that utilities like ifconfig and pf can be used to control
wireguard-interfaces, and hence they can also be defined in "hostname.if"
to be started at system boot and so on.

The obvious first thing was to try to set up a server, which I did quite
easily on a VPS:

       server# cat /etc/hostname.wg0
       wgkey $SERVER_PRIVKEY_HERE
       wgport 4343 # Peer 1
       wgpeer $PEER_PUBKEY_HERE wgaip 192.168.100.100/32
       wgaip fda8:9887:eecb:9051::11/128
       # Peer 2 wgpeer $PEER_PUBKEY_HERE
       wgaip 192.168.100.101/32
       wgaip fda8:9887:eecb:9051::12/128
       # Peer 3 wgpeer $PEER_PUBKEY_HERE
       wgaip 192.168.100.102/32
       wgaip fda8:9887:eecb:9051::13/128
       # Peer 4
       wgpeer $PEER_PUBKEY_HERE
       wgaip 192.168.100.103/32
       wgaip fda8:9887:eecb:9051::14/128
       inet 192.168.100.1/24
       inet6 fda8:9887:eecb:9051::1/64
       up

This is all it really takes to set up a server on OpenBSD. You can start
it by doing:

       sh /etc/netstart wg0

Now for the peers. There are multiple ways to set these up. A common
use-case is to set the default route via the wg-interface:

       peer1# cat /etc/hostname.wg0
       wgkey $PEER_PRIVKEY_HERE wgpeer $SERVER_PUBKEY_HERE
       wgendpoint server.example.net 4343
       wgaip 0.0.0.0/0 wgaip ::/0
       inet 192.168.100.100/24
       inet6 fda8:9887:eecb:9051::11/64
       #!route add -inet -priority 7 default 192.168.100.1
       #!route add -inet6 -priority 7 default fda8:9887:eecb:9051::1/64

This can be a bit clunky though. What I opted for instead is to use
rdomains and rtables to set up a separate routing table for the
wg-interface on the peer. That way, I can utilize pf on the peer to
control which traffic goes through the tunnel, or use "route -Tn exec" to
run specific applications in that specific rdomain, making it only go out
via the wg-interface.  This is what I ended up with:

       peer1# cat /etc/hostname.wg0
       wgkey $PEER_PRIVKEY_HERE wgpeer $SERVER_PUBKEY_HERE
       wgendpoint server.example.net 4343 wgaip 0.0.0.0/0 wgaip ::/0
       rdomain 1
       inet 192.168.100.100/24
       inet6 fda8:9887:eecb:9051::11/64
       !route -T 1 add -inet -priority 7 default 192.168.100.1
       !route -T 1 add -inet6 -priority 7 default fda8:9887:eecb:9051::1/64
       !ifconfig lo1 rdomain 1 127.0.0.1

Here, I set the interface to belong to the rdomain 1, and I also add the
default routes, but to the rtable 1 in rdomain 1. I also set up a
loopback-interface so stuff that expects to be able to bind to it can do
that. This may or may not be desired. In my specific use-case, I used it
because the tor-browser-bundle expects to be able to bind to 127.0.0.1,
which it cannot if it's not available in the rdomain. Next, I used some
rules in pf to re-direct DNS to specific servers reachable through
wireguard. In this case, it's an unbound-instance running on the
wireguard-server, and I wanted all DNS-requests to go to that server. So I
did something along these lines on the peer:

       pass out on rdomain 1 proto { udp tcp } from any to any port 53 \
       rdr-to 192.168.100.1 port 53

This will make any traffic towards anything on port 53 (either tcp or udp)
be redirected to 192.168.100.1.  This will force DNS-requests happening in
the rdomain go to a server on the other side of the tunnel. Once this is
done and the pf-config and brought up the interfaces, you should be able
to do stuff like this on the peer:

       route exec curl ifconfig.io/all
       route -T 1 exec curl ifconfig.io/all

The first example should go through the default routing table, showing
your public IP. The second should go through the tunnel, and show the
corresponding public IP for that system. Success! (Hopefully). With this,
you can do all sorts of interesting things. You could for example run:

       route -T 1 exec ksh

This will give you a shell belonging to rdomain 1, and hence, any process
spawned in that shell will inherit that. This is a simple way to not have
to prepend the route/exec-combination to run stuff through the tunnel.

So I hope that you enjoyed this little post. It may be a bit unclear, but
it's also a bit of a mental note to myself, even though the idea of
sharing it here is to give others a way to utilize similar solutions as
well. I've had a lot of fun with this during the last few days, and
hopefully it can be meaningful for others as well. Please send me some
feedback if you enjoyed it!

The content for this site is CC-BY-SA-4.0.