OpenBSD Home Router : WireGuard

Run your own VPN service on an OpenBSD router using the open-source software.

Motivation

Virtual Private Networks (VPNs) allow a device to connect to a private network from afar. For example, one could travel to a remote location yet still be able to act as if connected to the home LAN. As a secondary benefit, internet traffic can be tunnelled through the VPN to hide it from untrusted networks such as public WiFi.

IPSec and OpenVPN are the most well-known VPN protocols, but are difficult to understand and complex to configure. OpenSSH can provide some of the necessary features at the cost of convenience and performance.

WireGuard is a new work-in-progress VPN protocol and software intended to be simple, secure and performant. It has a small code-base, uses only strong cryptographic algorithms, and provides perfect forward secrecy.

This guide will demonstrate how to run WireGuard on an already functioning OpenBSD home router to let clients access the home network remotely.

Concepts

Networking

  1. Each device in the network is assigned a key pair (a public and private key) and an internal IP address

  2. Outbound traffic is encrypted with the target host’s public key and encapsulated in UDP before being transmitted

  3. Inbound encrypted traffic is decrypted with the receiving host’s private key

Topology

  1. Each host is configured with information about its peers: the public keys and the IP address ranges that they can route

  2. For for tunnelling all internet traffic through one host (the VPN “server”), allow that device to route all addresses

  3. VPN clients need to know the public IP address of the server to initiate a connection

Server

Networking

In this example, the 10.0.0.0/24 subnet is used for the VPN. The home router will be assigned 10.0.0.1 and will be the server through which traffic is tunnelled.

On the router, create a tunnel interface with the chosen private IP address. Since tun(4) is used as a point‑to‑point interface, a static route is needed to support multiple clients.

/etc/hostname.tun2
inet 10.0.0.1 255.255.255.0
!/sbin/route add -inet 10.0.0.0/24 10.0.0.1

Firewall

WireGuard authenticates packets on the tunnel network interface so it doesn’t need any filtering—add it to the PF skip rule.

set skip on { lo0 tun2 }
Updated PF rule disabling filtering on the WireGuard interface

Open the port that WireGuard will listen on so that clients can connect to this server over the internet.

pass in quick on egress proto udp to port 51820
PF rule to allow WireGuard traffic in over the internet

Traffic going out to the internet from the VPN will need NAT, but if you have a match rule performing NAT on !(egress:network), then this is already done.

DNS

For clients to be able to access the local unbound DNS resolver, add an access-control directive with the subnet of the VPN.

/var/unbound/etc/unbound.conf
server:
  interface: 192.168.1.1
  interface: 192.168.2.1
  interface: 127.0.0.1
  access-control: 192.168.1.0/24 allow
  access-control: 192.168.2.0/24 allow
  access-control: 10.0.0.1/24 allow
  do-not-query-localhost: no
  hide-identity: yes
  hide-version: yes
[…]

WireGuard

Install and enable the WireGuard userspace daemon on the OpenBSD (6.6+) router to run on your chosen tunnel interface.

# pkg_add wireguard-tools wireguard-go
# rcctl enable wireguard_go
# rcctl set wireguard_go flags tun2

Each device in the VPN needs a key pair.

$ wg genkey
sEw/H2BjShZovIn5FeOun/sgjMsxl6hzBzEDvqIYrUk=
$ wg pubkey <<END
sEw/H2BjShZovIn5FeOun/sgjMsxl6hzBzEDvqIYrUk=
END
GhBU6Sqss+s/ZqMuJhVM1RBIDdG5YQ9bK0EwcZNxU2Q=
Private keys are created by wg genkey; the public key is derived from the private key by piping it to wg pubkey

Once the keys are generated, use wg(8) to configure the wireguard-go daemon. This must be done each time it is restarted.

# wg set tun2 listen-port 51820 \
    private-key /etc/wireguard/private_key \
    peer '«client-1 public key»' allowed-ips 10.0.0.2/32 \
    peer '«client-2 public key»' allowed-ips 10.0.0.3/32
WireGuard daemon configured by command-line arguments

Instead of setting parameters through command-line arguments, you could also create and load the configuration through an ini file.

/etc/wireguard.conf
[Interface]
PrivateKey = «server private key»
ListenPort = 51820

[Peer]
PublicKey = «client-1 public key»
AllowedIPs = 10.0.0.2/32

[Peer]
PublicKey = «client-2 public key»
AllowedIPs = 10.0.0.3/32
# wg setconf tun2 /etc/wireguard.conf
WireGuard daemon configured by a file

The config must be reloaded whenever the daemon is restarted.

Refer to the wg(8) manual for details.

wg-quick

Instead of creating the tunnel interface and routes manually as above, you can use the included wg-quick script. This creates a tunnel interface, sets the IP address, adds routes, and configures and runs the WireGuard daemon. Simply add an Address parameter to the WireGuard configuration file then run the script.

/etc/wireguard/server.conf
[Interface]
PrivateKey = «server private key»
ListenPort = 51820
Address = 10.0.0.1

[Peer]
PublicKey = «client-1 public key»
AllowedIPs = 10.0.0.2/32

[Peer]
PublicKey = «client-2 public key»
AllowedIPs = 10.0.0.3/32
# wg-quick up server
[#] wireguard-go tun
INFO: (tun0) 2019/07/18 12:00:00 Starting wireguard-go version 0.0.20190517
[+] Interface for server is tun0
[#] wg setconf tun0 /dev/fd/63
[#] ifconfig tun0 inet 10.0.0.1 alias
[#] ifconfig tun0 mtu 1420
[#] ifconfig tun0 up
[#] route -q -n add -inet 10.0.0.2/32 -iface 10.0.0.1
[#] route -q -n add -inet 10.0.0.3/32 -iface 10.0.0.1
[+] Backgrounding route monitor

See the wg-quick(8) manual for details.

Clients

Mobile (Android & iOS)

WireGuard applications for mobile operating systems handle setting up the tunnel interface and routes. Simply set a private key and internal IP address, and provide details of the public key and public IP address (or domain name) of the server so the client can connect.

The allowed IPs of the server will be all addresses (0.0.0.0/0, ::/0) so that all traffic is tunnelled. If you only wish to access particular subnets in your home LAN, add the relevant addresses instead.

client.conf
[Interface]
PrivateKey = «client-1 private key»
Address = 10.0.0.2/32
DNS = 192.168.1.1

[Peer]
PublicKey = «server public key»
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = «server public IP or FQDN»:51820

Setting the DNS parameter to the internal IP address of your router tells the mobile client to use the resolver that you presumably have running—useful if you perform DNS-based ad blocking. You may of course use a different resolver if you desire or if you don’t run a resolver.

If you create the configuration file on an OpenBSD system, an easy way to transfer it to the mobile applications is by generating then scanning a QR code

# pkg_add libqrencode
$ qrencode -t ansiutf8 < client.conf

OpenBSD

Follow the same process of adding an interface and installing WireGuard as for the server, and let the server route all addresses.

# pkg_add wireguard-tools wireguard-go
# rcctl enable wireguard_go
# rcctl set wireguard_go flags tun2
/etc/hostname.tun2
inet 10.0.0.3 255.255.255.0 10.0.0.1
/etc/wireguard.conf
[Interface]
PrivateKey = «client-2 private key»

[Peer]
PublicKey = «server public key»
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = «server public IP or FQDN»:51820
# wg setconf tun2 /etc/wireguard.conf

Set up routes for the traffic you want to tunnel through the VPN.

To tunnel all traffic, add a high priority (priority < 8) default route through the VPN server. You will need an even higher priority (smaller priority number) route for the server itself through the normal external interface (so that the WireGuard traffic doesn’t get routed back to the WireGuard interface).

# route add -priority 2 «server public IP» "$(netstat -rn -f inet | awk '/^default/ { print $2 }')"
# route add -priority 7 default 10.0.0.1

OpenBSD with wg-quick

As above, but the wg-quick script configures the interface and routing.

/etc/wireguard/client.conf
[Interface]
PrivateKey = «client-2 private key»
Address = 10.0.0.3

[Peer]
PublicKey = «server public key»
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = «server public IP or FQDN»:51820
# pkg_add wireguard-tools wireguard-go
# wg-quick up client

Done

Connect to the VPN. Check that you can ping the router on its VPN internal address, access other hosts on the LAN, and that your external IP appears as that of your home network.