OpenBSD Router : VPN in userspace

WireGuard for OpenBSD 6.6–6.7

Introduction

OpenBSD 6.8 supports WireGuard natively, but older versions of OpenBSD can run WireGuard with a userspace daemon.

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.

pass in quick on tun2

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

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

If you run a local unbound DNS resolver and wish for VPN clients to be able to make use of it, 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.

Private keys are created by wg genkey; the public key is derived from the private key by piping it to wg pubkey.

$ wg genkey
sEw/H2BjShZovIn5FeOun/sgjMsxl6hzBzEDvqIYrUk=
$ wg pubkey <<END
sEw/H2BjShZovIn5FeOun/sgjMsxl6hzBzEDvqIYrUk=
END
GhBU6Sqss+s/ZqMuJhVM1RBIDdG5YQ9bK0EwcZNxU2Q=

Once the keys are generated, you can 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

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

/etc/wireguard/server.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

The config must be reloaded whenever the daemon is restarted. I expect this step to be incorporated into the init script in the future. For now, setting the configuration once at boot suffices for most cases since the daemon is rarely restarted.

# wg setconf tun2 /etc/wireguard/server.conf
# echo '/usr/local/bin/wg setconf tun2 /etc/wireguard/server.conf' >> /etc/rc.local

Now run wg without any arguments (which is equivalent to the wg show all command) to print a list of interfaces and their attributes, including the public keys and allowed IPs of the peers. If the configuration has loaded correctly, configure the clients.

wg-quick

Instead of creating the tunnel interface and routes manually, 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.

See the wg-quick(8) manual for details, and consider reading the source code.

/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
# pkg_add wireguard-tools wireguard-go
# wg-quick up /etc/wireguard/server.conf
[#] 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
The use of wg-quick up to easily enable WireGuard. It applies and strips out certain parameters (like Address and DNS in the Interface section), then passes the remaining configuration to the wireguard-go daemon.
# wg-quick down /etc/wireguard/server.conf
[+] Interface for server is tun0
[#] rm -f /var/run/wireguard/tun0.sock
[#] rm -f /var/run/wireguard/server.name
Disable and stop a running WireGuard daemon with wg-quick down

Clients

Mobile (Android & iOS)

Install the WireGuard application on your device and add a new connection. Set the private key and internal IP address to match the appropriate Peer section from the server configuration, and provide details of the public key and public IP address (or domain name) of the server – the syntax is of the wg-quick(8) configuration file format.

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.

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.

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

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 (manual)

To connect to a VPN from an OpenBSD host, follow the same process of adding an interface and installing WireGuard as for the server – with the only difference being that we want to tunnel all traffic (0.0.0.0, ::/0) to the server.

# 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/server.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/server.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 (wg-quick)

The wg-quick script simplifies the setup. No manual networking steps are needed – just create the configuration file and call the script.

/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.

Other Guides

jasper, WireGuard on OpenBSD @
Written by the packager of WireGuard on OpenBSD, this blog post shows a configuration which supports a single OpenBSD client / peer.
tedu, toying with wireguard on openbsd @
Written before WireGuard packages were available, this blog post shows a configuration which supports a single mobile client / peer.