Running WireGuard on OpenBSD


I discovered WireGuard several months ago and had read a lot about it. As an avid OpenBSD user, I believe strongly in security, simplicity, and elegance and WireGuard is it! Jason Donenfeld is to be commended for developing a protocol that is currently only 4,000 lines in programming code and uses state of the art encryption. Compare that with the god awful kludge that is IPSEC and OpenVPN and you’ve got something really and truly worth exploring. Despite WireGuard still being in its infancy, it’s perfectly usable and probably more secure than the others out there. The one thing that might stop WireGuard from being in larger deployments is that it doesn’t have a DHCP service like OpenVPN but that could always be added later.

Site-to-Site VPN

With that brief introduction, I am going to show you how you can use WireGuard for both site to site VPN and for a roadwarrior situation. One thing to note is that WireGuard currently only supports VPN at Layer 3. I think that there might eventually be Layer 2 support althought I’ve no evidence to back this up. Let’s dive into it and begin with joining two sites together with WireGuard. Site 1 is going to have a public IP address of 192.0.2.1 and site 2 is going to have 192.0.2.2. Site 1’s LAN is going to be on the subnet 172.16.8.0/24 and site 2’s LAN will be on 172.16.16.0/24. The private IP tunnel endpoint will be 192.168.254.1 and 192.168.254.2

The first step is to enable on kernel IP forwarding on the gateway machines of both site 1 and 2 and add the wireguard protocols

sysctl net.inet.ip.forwarding=1 
echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf
pkg_add wireguard-go wireguard-tools

Set up the tunneling interfaces on both of the gateway machines.

#Site 1
ifconfig tun0 inet 192.168.254.1 192.168.254.2 netmask 255.255.255.254 up
route add -net 172.16.16.0/24 192.168.254.2

#Site 2
ifconfig tun0 inet 192.168.254.2 192.168.254.1 netmask 255.255.255.254 up
route add -net 172.16.8.0/24 192.168.254.1

Make these permanent by creating the appropriate hostname.if files.

#Site 1 - /etc/hostname.tun0
!ifconfig tun0 inet 192.168.254.1 192.168.254.2 netmask 255.255.255.254 up
!route add -net 172.16.16.0/24 192.168.254.2

#Site 2 - /etc/hostname.tun0
!ifconfig tun0 inet 192.168.254.2 192.168.254.1 netmask 255.255.255.254 up
!route add -net 172.16.8.0/24 192.168.254.1

Once we have the basic interfaces created, we can begin to configure WireGuard. On both machines, the configuration will live in /etc/wireguard/wg.conf. WireGuard uses something called crypto key routing so routing decisions are based on the private/public key pairings. Create the key pairs by doing the following for both of the site 1 and 2 gateway machines. Copy the site 1 public key to site 2 and vice versa.

#Site 1
cd /etc/wireguard
wg genkey | tee privatekey-site1 | wg pubkey > publickey-site1

#Site 2
cd /etc/wireguard
wg genkey | tee privatekey-site2 | wg pubkey > publickey-site2

WireGuard works on a client/server model so we are going to designate the site 1 as the server. Another way of considering this is that site 1 is going to be the responder and site 2 is going to be the initiator. While you can choose any port above 1024, I use 8000. It’s not listed in /etc/services and seems to work well. Let’s create the /etc/wireguard/wg.conf.

#Site 1 - wg.conf
[Interface]
PrivateKey=<privatekey-site1>
ListenPort=8000 

[Peer]
PublicKey=<publickey-site2>
AllowedIPs=192.168.254.2/32, 172.16.16.0/24
PersistentKeepAlive=30


#Site 2 - wg.conf
[Interface]
PrivateKey=<privatekey-site2>

[Peer]
PublicKey=<publickey-site1>
Endpoint=192.0.2.1
AllowedIPs=192.168.254.1/32, 172.16.8.0/24
PersistentKeepAlive=30

Before I forget to mention, it may be necessary to add pf rules to allow incoming connections. Use rules similar to the ones below but do not copy these verbatim because they might not work. Instead, use them only as an example.

#Site 1
pass in on egress inet proto udp from 192.0.2.2 to 192.0.2.1 port 8000 keep state
pass in on tun0 from {192.168.254.2/32, 172.16.16.0/24} to any keep state

#Site 2
pass in on egress inet proto udp from 192.0.2.1 to 192.0.2.2 port 8000 keep state
pass in on tun0 from {192.168.254.1/32, 172.16.16.0/24} to any keep state

Now we have to enable and start the wireguard-go daemon and load the configuration into the daemon. Do this for both site gateway machines.

rcctl enable wireguard_go
rcctl set wireguard_go flags tun0
wg setconf tun0 /etc/wireguard/wg.conf

Create /etc/rc.local on both site gateway machines. Doing this will take care of loading the configuration into the daemon during system startup. Be certain to chmod 0555 the file so it gets executed.

#!/bin/ksh
if [ -r /usr/local/bin/wg ]; then
    /usr/local/bin/wg setconf tun0 /etc/wireguard/wg.conf
fi

Now hopefully we can send some pings. First, try to ping the tunnel endpoints 192.168.254.1 and 192.168.254.2 from both site gateway machines. Then try to ping a machine on each network.

PING 192.168.254.2 (192.168.254.2): 56 data bytes
64 bytes from 192.168.254.2: icmp_seq=0 ttl=255 time=12.665 ms
--- 192.168.254.2 ping statistics ---
1 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 12.665/16.147/22.379/3.757 ms
PING 192.168.254.1 (192.168.254.1): 56 data bytes
64 bytes from 192.168.254.1: icmp_seq=0 ttl=255 time=12.665 ms
--- 192.168.254.1 ping statistics ---
1 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/std-dev = 12.665/16.147/22.379/3.757 ms

Roadwarrior VPN

Roadwarrior VPN setup with WireGuard is even easier! I am going to show you how you can implement this to use on your OpenBSD laptop when you are using public WiFi. Unfortunately, some places like Dunkin’ Donuts’ free WiFi does port blocking so it won’t work there but should work in a lot of other places. What you’ll need to do first is to setup a server in the cloud. Normally, I do not plug for a cloud provider but I highly recommend Vultr. For 5 dollars a month or 60.00 a year, you have your own cloud server fully under your own control.

The setup is similar but with some noticeable differences. In this case, we are going to assume the cloud server has 192.0.2.1. We will use 192.168.254.1 for the server tunnel endpoint and 192.168.254.2 for the client endpoint. Enable IP forwarding and create the interface definition files on both the server and client. Create the public/private key pairs in the same fashion as above. Below are the WireGuard configuration files.

#Server - wg.conf
[Interface]
PrivateKey=<privatekey-server>
ListenPort=8000 

[Peer]
PublicKey=<publickey-client>
AllowedIPs=192.168.254.2/32
PersistentKeepAlive=30


#Client - wg.conf
[Interface]
PrivateKey=<privatekey-client>

[Peer]
PublicKey=<publickey-server>
Endpoint=192.0.2.1
AllowedIPs=192.168.254.1/32
PersistentKeepAlive=30

The pf configuration is a little different as well because you only really have to add a rule to the server allowing incoming connections to WireGuard on port 8000. Also, since the Roadwarrior most likely has a dynamic IP as an endpoint, the rule for the server needs to accept connections from any.

#Server
pass in on egress inet proto udp from any to 192.0.2.1 port 8000 keep state
pass in on tun0 from 192.168.254.2/32 to any keep state

Enable and start wireguard-go on the server.

rcctl enable wireguard_go
rcctl set wireguard_go flags tun0
wg setconf tun0 /etc/wireguard/wg.conf

Create /etc/rc.local on the server and chmod 0555 it.

#!/bin/ksh
if [ -r /usr/local/bin/wg ]; then
    /usr/local/bin/wg setconf tun0 /etc/wireguard/wg.conf
fi

This is where things diverge again from the site to site VPN. Unlike site 2, the client side is a little different. You do not have to enable and start the wireguard-go daemon from rcctl. Instead, use this script. This way you can start the VPN when you need it.

#!/bin/ksh

DEFAULTGW=`netstat -rn -finet | awk '/default/ {print $2}'`
SERVERIP=<serverip>

if [ $1 = "" ]; then
    print "You need to specify start or stop."
elif [ $1 = "start" ]; then
    doas wireguard-go tun0
    doas wg setconf tun0 /etc/wireguard/wg.conf
    doas route add -priority 2 $SERVERIP $DEFAULTGW
    doas route add -priority 7 default 192.168.254.1
elif [ $1 = "stop" ]; then
    doas route delete -priority 7 default 192.168.254.1
    doas route delete -priority 2 $SERVERIP
    doas pkill wireguard-go
else
    print "ERROR: Unknown option!"
fi

It really is that easy to have a secure connection over untrusted networks!


See also