Simple and Secure VPN in FreeBSD – Introducing WireGuard (2024)

WireGuard is a Virtual Private Network (VPN) technology that enables the easy deployment and configuration of encrypted network tunnels. WireGuard is intended to replace the use of IPSec or OpenVPN for many VPN applications.

WireGuard offers both kernel and userspace implementations. The in-kernel version is faster, but must be customized for each OS, and not every OS supports this yet. The first kernel implementation was offered for Linux, but there is now an in-kernel implementation for FreeBSD, and also one for OpenBSD, with a NetBSD implementation in progress.

WireGuard needs crypto primitives that were not present in the FreeBSD kernel—so high quality native support required a large amount of work and communication with the upstream WireGuard project.

FreeBSD kernel-mode WireGuard—currently flagged experimental—is available from the wireguard-kmod package. The main userspace implementation of wireguard is a golang application, and it is available via the wireguard-go package.

A wireguard network configuration is formed of interfaces and peers. Each interface is defined by its private key, peers are defined by their public key and the set of addresses they are allowed to use. This forms a network that uses cryptokey routing.

Simple and Secure VPN in FreeBSD – Introducing WireGuard (1)

With cryptokey routing, hosts are defined by their cryptographic keys, where public keys are used as identifiers. This means that most of the configuration for WireGuard requires dealing with these public keys. WireGuard does not specify mechanisms for key distribution and management, and it is fair to compare this with the distribution of ssh keys. Unlike ssh, the public keys need to be available for both sides of the connection.

WireGuard uses TOML files for static configuration. Here’s a simple example config file:

[Interface]PrivateKey = EO9b4bV1qg0veyFelERv69v4eamKu/evGZ/Hbiq82Eg=ListenPort = 444[Peer]PublicKey = ZBF6vo22sBkT0ro/xnlZ0EZsFGTYPTOvY8luFFtJwwk= AllowedIPs = 10.10.0.2/32[Peer]PublicKey = fnpAo3hMf9UOtPpM+CUtpB+ETvB5XjNLIuTGsX+qnCcAllowedIPs = 10.10.0.138/32[Peer]PublicKey = Tpdhd68aaDAQfNsnqzoViMl1h+yHeYuVZ22xrY1BM0I=AllowedIPs = 10.10.0.3/32, 192.168.2.1/24

The allowed IP address stanzas act as a routing table. When traffic is sent, the packet is compared to the peer’s AllowedIPs. If it matches the address or the subnet, the packet is encrypted using the peer’s public key and forwarded over the tunnel in its direction.

On receipt of a packet, AllowedIPs acts as a receive filter. The packet is first decrypted and authenticated, and then it is evaluated against the AllowedIPs to verify that it is allowed to enter the network.

Installation

WireGuard makes a serious effort to make the tools it offers consistent on all platforms. To do this, there are two components that determine whether the kernel or userspace implementation is used. The first is a method to create a WireGuard interface—this is the component that changes between kernel and userspace versions. A single configuration tool—wg(8)—manages these interfaces, whether they are created in kernel or userspace mode.

This approach intends to smooth over platform configuration differences. The platform specific part is creating and configuring the network interface, but the VPN configuration should all be manageable with a single consistent tool.

FreeBSD has an in-kernel implementation of wireguard available from ports or packages, but the in-kernel implementation is still considered experimental—so in this article we will use the userspace tool, as it is available in the most supported versions of FreeBSD today. The in-kernel implementation key generation is still performed using the wg(8) tool.

We can install wg(8) and wireguard-go (the WireGuard userspace implementation) from ports with:

# pkg install wireguard-go wireguard-tools

Creating Keys

Our host’s identity is defined by its key pair. We need to generate a WireGuard private key using thewg genkeycommand; WireGuard helpfully (if frustratingly) insists that we don’t place the private key in a world readable file. To generate the key and append it to a file, we need to temporally change our umask:

# (umask 0077; wg genkey > private.key)# cat private.keyMOtM8w02ONtGK9vmLCXlx6em+5pRTm6C0z7HCeIrPlY=

This command creates a private key and places it into a file called private.key. We can view the private key by catting the file; anyone with the private key can access your tunnels, so you should be very careful with WireGuard private keys. WireGuard tools will typically censor the private key in output unless additional flags are given.

WireGuard uses your public key to identify you to remote systems as well as encrypt your data. Thewg pubkey command will display the public key derived from a given private key:

# wg pubkey < private.keyAPWANIrAD3drcSNUdDuUuXLsCRksKxuQxwBHf56UxiM=

Server Side

First, let’s configure our server side. The server is going to act as the central host in our network with several clients connecting to it for access. This host needs to have a stable public IP and a known shared port to accept WireGuard traffic on.

We need to generate keys for the server:

wg-server (umask 0077; wg genkey > server.key)wg-server # cat server.keyAJKFyhDoLfLnlclEcKV97bRonVKGgEbRGi7vGfeYNnw=wg-server # wg pubkey < server.key # show the public key for this private keyj2klzAC0RnWOOUAcZw+/GEtp5URCSwf9r3yW+JYJRRE=

Next, there are a few steps to set up the network. We need to create a wireguard interface using wireguard-go:

wg-server # wireguard-go wg0INFO: (wg0) 2021/01/08 14:35:42 Starting wireguard-go version 0.0.20200320

This interface needs to be configured to have an address in our private network on the host and a route to direct traffic into the wireguard tunnel. We use the wg(8) tool to configure the interface’s private key.

wg-server # ifconfig wg0 inet 10.10.0.1/24 10.10.0.1wg-client # route add 10.10.0.0/24 -interface wg0wg-server # wg set wg0 private-key ./server.key listen-port 444

If we don’t specify the listen port here, then WireGuard will select a random dynamic port when the wg0 interface is brought up. For a destination server like this, we need to have a consistent port between addresses. If possible, it’s a good idea to use a non-dynamic port—one below 49152—since WireGuard traffic is UDP, and many routers don’t expect stable port assignments in the dynamic range.

wg-server # ifconfig wg0 downwg-server # ifconfig wg0 up

Finally, we need to configure each peer with its public key and the IP addresses it is allowed to use:

wg-server # wg set wg0 peer <peer pubkey> allowed-ips 10.10.0.2/32 

FreeBSD Client

Each client needs to go through similar steps: first create a private key to identify the client:

wg-client (umask 0077; wg genkey > client.key)wg-client # cat client.keygM3ydSBBTZOw1nzIyV/nxJuONB8/MNe/RhcOikQbE3Y=wg-client # wg pubkey < client.key # show the public key for this private keyjILr21Xt+3sXDZepu5Z3syuJMXyc29f5GBXHZFPulEE=

We need to create the WireGuard interface by running wireguard-go:

wg-client # wireguard-go wg0 # create the wg0 interface

This interface is then configured for the internal VPN network. wireguard-go creates a tun interface, and tun interfaces need to be configured with a source and destination address. As that is not important here, we can use the same address for both parts. To make sure we can reach the internal network properly, we also need to add a route:

wg-client # ifconfig wg0 inet 10.10.0.2/24 10.10.0.2wg-client # route add 10.10.0.0/24 -interface wg0

With the interface configured, we can now configure WireGuard:

wg-client # wg set wg0 private-key ./client.key

Finally, we need to configure the wg0 interface with an endpoint for the server:

wg-client # ifconfig wg0 downwg-client # ifconfig wg0 upwg-client # wg set wg0 peer <peer pubkey> allowed-ips 10.10.0.0/24 endpoint 192.0.2.42:444

When we configured the client peer, we told WireGuard where to find the server with the endpoint parameter. In this case the client doesn’t listen on a predictable port, but instead it first has to create the WireGuard tunnel with the server when traffic needs to be sent.

Now that we have created and configured our client, we need to add its public key and allowed-ip addresses to the server.

wg-server # wg set wg0 peer jILr21Xt+3sXDZepu5Z3syuJMXyc29f5GBXHZFPulEE= allowed-ips 10.10.0.2/32 

Finally we can test the set up using ping:

wg-client # ping 10.10.0.1PING 10.10.0.1 (10.10.0.1): 56 data bytes 64 bytes from 10.10.0.1: icmp_seq=0 ttl=64 time=3.253 ms 64 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=1.358 ms 64 bytes from 10.10.0.1: icmp_seq=2 ttl=64 time=1.089 ms 64 bytes from 10.10.0.1: icmp_seq=3 ttl=64 time=1.649 ms ^C 

Wireguard is not a ‘chatty’ protocol. If there is no traffic to send from the client for a long time, and it doesn’t have a fixed address, the server can ‘forget’ how to reach the client.

It might be the case that the client is behind a NAT network. To support this case, wireguard supports thepersistent-keepaliveconfiguration option. This option is disabled by default, but it allows our client to receive packets from the server when it is not sending traffic itself.

RFC4787 recommends a 2 minute timeout interval for NATs and middleboxes, but recent UDP protocols (such as QUIC), recommend sending packets every 30 seconds to prevent middleboxes fromlosing state for UDP flows.

If you need the tunnel connection kept alive, add thepersistent-keepaliveparameter:

wg-client # wg set wg0 peer <peer pubkey> allowed-ips 10.10.0.0/24 persistent-keepalive 30 endpoint 192.0.2.42:444

Debugging

It can help to run wireguard-go in the foreground and you can enable debug output from wireguard-go with the LOG_LEVEL environment variable:

# export LOG_LEVEL=DEBUG# wireguard-go -f wg0INFO: (wg0) 2021/01/11 10:43:49 Starting wireguard-go version 0.0.20201118DEBUG: (wg0) 2021/01/11 10:43:49 Debug log enabledDEBUG: (wg0) 2021/01/11 10:43:49 Routine: event worker - startedDEBUG: (wg0) 2021/01/11 10:43:49 Routine: encryption worker - startedDEBUG: (wg0) 2021/01/11 10:43:49 Routine: decryption worker - startedDEBUG: (wg0) 2021/01/11 10:43:49 Routine: TUN reader - startedDEBUG: (wg0) 2021/01/11 10:43:49 Routine: handshake worker - startedINFO: (wg0) 2021/01/11 10:43:49 Device startedINFO: (wg0) 2021/01/11 10:43:49 UAPI listener started

If you are launching wireguard-go using sudo, remember that sudo uses its own environment:

$ sudo LOG_LEVEL=debug wireguard-go -f wg0

wireguard-go doesn’t seem to always detect that the wg0 interface has been brought up and ends up not creating the UDP sockets required to send packets. You can check this in sockstat by looking for wireguard-go listening on UDP for v4 and v6, or you can check the wireguard-go log. If you don’t see a “Interface set up” message in the log, try toggling it by taking wg0 up and down:

# ifconfig wg0 down# ifconfig wg0 up

Wireguard does not appear to offer a mechanism to decrypt packets based on pre-shared keys in the way that IPSec enables. This might appear in the future and I would check with wireshark.

Even without decryption, we can verify that packets are making it through the wg interface and across the network by checking for packets on the client and server’s wg port. If 444 is the server’s port, then listening with tcpdump for udp traffic should return some packets:

# tcpdump udp and port 444 [tj@freebsd-wg-client] $ sudo tcpdump udp and port 444tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on hn0, link-type EN10MB (Ethernet), capture size 262144 bytes 19:54:31.217241 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 11619:54:32.248686 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 116 19:54:33.321003 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 11619:54:34.027525 IP 52.178.136.74.444 > freebsd-wg-client.internal.internet.net.45052: UDP, length 32 19:54:34.348493 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 116 19:54:35.398882 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 11619:54:36.425414 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 11619:54:37.448561 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 11619:54:38.520893 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.444: UDP, length 116 

The above capture is from a debugging session where the wg interface on the client was configured with the wrong address. It shows ping packets going from the client to the server, but no ICMP replies returning. These packets were encrypted, but their presence helped point to which part of the configuration wasn’t working.

Conclusion

This has been an introduction on how you can use WireGuard on FreeBSD to create VPNs. We haven’t covered the detail of static configuration or how best to manage key distribution to a large number of hosts. You can check wireguard.com for more information, documentation and pointers to tools that help with distribution of keys and mass configuration.

Simple and Secure VPN in FreeBSD – Introducing WireGuard (2)

Meet the author: Tom Jones

Tom Jones is an Internet Researcher and FreeBSD developer that works on improving the core protocols that drive the Internet. He is a contributor to open standards in the IETF and is enthusiastic about using FreeBSD as a platform to experiment with new networking ideas as they progress towards standardisation.

Read more

  • Share on Twitter
  • Share on LinkedIn
  • Share via Email
  • Share on Reddit
Simple and Secure VPN in FreeBSD – Introducing WireGuard (2024)

References

Top Articles
Latest Posts
Article information

Author: Ms. Lucile Johns

Last Updated:

Views: 6103

Rating: 4 / 5 (61 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Ms. Lucile Johns

Birthday: 1999-11-16

Address: Suite 237 56046 Walsh Coves, West Enid, VT 46557

Phone: +59115435987187

Job: Education Supervisor

Hobby: Genealogy, Stone skipping, Skydiving, Nordic skating, Couponing, Coloring, Gardening

Introduction: My name is Ms. Lucile Johns, I am a successful, friendly, friendly, homely, adventurous, handsome, delightful person who loves writing and wants to share my knowledge and understanding with you.