freebsd network virtualized

Simple and Secure VPN in FreeBSD – Introducing WireGuard

Simple and Secure VPN in FreeBSD – Introducing WireGuard

Wireguard is a Virtual Private Network (VPN) technology that aims to enable the easy deployment and configuration of servers and clients. 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 implementations for FreeBSD, and also one for OpenBSD, with a NetBSD implementation in progress. Wireguard needed crypto primitives that were not present in FreeBSD, having high quality native support required a large amount of work and communication with the upstream Wireguard project. The FreeBSD kernel implementation was written by Matt Macy with sponsorship from NetGate, without this vendor support it is unlikely that a project of this size would have been successful.

The main userspace implementation of wireguard is a golang application and it is available via the wireguard-go tool available in FreeBSD ports. 

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.

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 INI files for static configuration, a config file might look like:

[Interface]
PrivateKey = EO9b4bV1qg0veyFelERv69v4eamKu/evGZ/Hbiq82Eg=
ListenPort = 51820

[Peer]
PublicKey = ZBF6vo22sBkT0ro/xnlZ0EZsFGTYPTOvY8luFFtJwwk=    
AllowedIPs = 10.10.0.2/32

[Peer]
PublicKey = fnpAo3hMf9UOtPpM+CUtpB+ETvB5XjNLIuTGsX+qnCc
AllowedIPs = 10.10.0.138/32

[Peer]
PublicKey = Tpdhd68aaDAQfNsnqzoViMl1h+yHeYuVZ22xrY1BM0I=
AllowedIPs = 10.10.0.3/32, 192.168.2.1/24

The allowed IP addresses act as a routing table. When traffic is sent, the packet is compared to the peer’s AllowedIPs, and 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, then there is a single configuration wg(8) tool to manage the VPN.

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 that landed in the tree in November 2020. The in-kernel implementation is still very fresh, 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 wireguard-tools

Creating Keys

Our host’s identity is defined by its key pair. We need to generate a wireguard private key using the wg genkey command; 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.key
MOtM8w02ONtGK9vmLCXlx6em+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 act as the server, so you should be very careful with your wireguard private keys. Wireguard tools will typically censor the private key in output unless additional flags are given. 

Did you know?

You can maximize the power of your FreeBSD infrastructure with our Support Subscription!

Wireguard uses our public key as our identity, wg(8) will show us the public key for a private key with the wg pubkeycommand:

# wg pubkey < private.key
APWANIrAD3drcSNUdDuUuXLsCRksKxuQxwBHf56UxiM=

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.key
AJKFyhDoLfLnlclEcKV97bRonVKGgEbRGi7vGfeYNnw=
wg-server # wg pubkey < server.key      # show the public key for this private key
j2klzAC0RnWOOUAcZw+/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 wg0
INFO: (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.1
wg-client # route add 10.10.0.0/24 -interface wg0
wg-server # wg set wg0 private-key ./server.key listen-port 51820

If we don’t specify the listen port here, then wireguard will select a random port when the wg0 interface is brought up. For a destination server like this, we need to have a consistent port between addresses, and so we use one common to wireguard here.

wg-server # ifconfig wg0 down
wg-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.key
gM3ydSBBTZOw1nzIyV/nxJuONB8/MNe/RhcOikQbE3Y=
wg-client # wg pubkey < client.key      # show the public key for this private key
jILr21Xt+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.2
wg-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 down
wg-client # ifconfig wg0 up
wg-client # wg set wg0 peer <peer pubkey> allowed-ips 10.10.0.0/24 endpoint 192.0.2.42:51820

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.1
PING 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 the persistent-keepalive configuration 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 from losing state for UDP flows.

If you need the tunnel connection kept alive, add the persistent-keepalive parameter:

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

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 wg0
INFO: (wg0) 2021/01/11 10:43:49 Starting wireguard-go version 0.0.20201118
DEBUG: (wg0) 2021/01/11 10:43:49 Debug log enabled
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: event worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: encryption worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: decryption worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: TUN reader - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: handshake worker - started
INFO: (wg0) 2021/01/11 10:43:49 Device started
INFO: (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 51820 is the server’s port, then listening with tcpdump for udp traffic should return some packets:

# tcpdump udp and port 51820 
[tj@freebsd-wg-client] $ sudo tcpdump udp and port 51820                                                        
tcpdump: 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.51820: UDP, length 116
19:54:32.248686 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: UDP, length 116         
19:54:33.321003 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: UDP, length 116
19:54:34.027525 IP 52.178.136.74.51820 > 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.51820: UDP, length 116         
19:54:35.398882 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: UDP, length 116
19:54:36.425414 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: UDP, length 116
19:54:37.448561 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: UDP, length 116
19:54:38.520893 IP freebsd-wg-client.internal.internet.net.45052 > 52.178.136.74.51820: 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.

FreeBSD Kernel

The FreeBSD wireguard kernel implementation was committed in November 2020 landing in time there to be support when FreeBSD 13 is branched. The wg tool doesn’t support the use of the in-kernel implementation yet, and so ifconfig has to be used to configure the interface. So, while all the concepts remain the same, you need to use ifconfig, as of the writing of this article, there is not yet any documentation in the man page for how to set up wireguard.

On FreeBSD-13 we can create and configure the in-kernel wireguard like so:

# ifconfig wg create listen-port 51820 private-key  `cat server.key`
# ifconfig wg0 peer public-key <peer's public key>  endpoint 192.168.2.42:51820 allowed-ips 10.10.0.2/24

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.

<strong>Meet the Author</strong>: Tom Jones
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.

Like this article? Share it!

You might also want be interested in

Get more out of your FreeBSD networking

Networking is crucial to many companies. If you have a FreeBSD networking implementation or a driver you’re looking at, our team can help you further enable your efforts.

More on this topic

freebsd network virtualized

Routing and Firewalling VLANS with FreeBSD

VNET virtual network stacks are a powerful network stack isolation technology that gives FreeBSD jails super powers. Follow our guide to use VLANs on FreeBSD, combine VLANs and VNETs and use VLANs with VNET Jails. Learn useful tricks with many exemplifying instances.

freebsd network virtualized

Virtualize Your Network on FreeBSD with VNET

FreeBSD Jails – a well-known feature that has become core to many excellent tools on FreeBSD such as the Poudriere package builder. Jails offer process and file system isolation, but for a long time they did not offer very satisfying network isolation. Learn how to isolate networks, how to test potentially hazardous firewall changes and how to do proper jail networking.

Dummynet: The Better Way to Build FreeBSD Networks

Learn how to build better FreeBSD networks with Dummynet. Dummynet is the FreeBSD traffic shaper, packet scheduler, and network emulator. It allows you to emulate a whole set of network environments in a straight forward way, it has the ability to model delay, packet loss, and can act as a traffic shaper and policer. Follow our guide to learn how!

3 Comments on “Simple and Secure VPN in FreeBSD – Introducing WireGuard

  1. I enjoyed this article and found it very useful for understanding wireguard on FreeBSD.

    The only complaint that I have is not specific to wireguard but to most articles that I have found about VPNs. I think all of our examples should provide for both IPv6 and IPv4. VPN articles in particular need to show servers that are dual stacked and clients that may sometimes connect via IPv6 and sometimes via IPv4. The networks shared over the VPN should always be dual stack. It is much easier for a user to leave out parts they don’t need than to figure out how to add them in.

    I also think we should be using IPv6 for the default addresses for network examples to set a good example for people.

  2. Pingback: Valuable News – 2021/02/01 | 𝚟𝚎𝚛𝚖𝚊𝚍𝚎𝚗

Tell us what you think!

%d bloggers like this: