Which WireGuard on FreeBSD 13.1?
I’m enjoying using FreeBSD 13.1 on my old ThinkPad X230.
One thing I haven’t tried yet is using WireGuard for VPN connections on FreeBSD.
There are two WireGuard ports: wireguard-kmod
, the experimental in-kernel version, and wireguard-go
, the stable, cross-platform implementation.
I tried both to see which I should use.
Setting Up Experimental In-Kernel Wireguard
Let’s start with the fun version. This port is the in-kernel version from zx2c4, written by Jason Donenfeld and Kyle Evans—not to be confused with the infamous Netgate-sponsored port that was yanked from FreeBSD 13. It’s still under development and comes with disclaimers about using it in production.
To use it, install the package and the userland tools to control it.
# pkg install wireguard-kmod wireguard-tools
The package notes (use pkg info -D wireguard-kmod
to see them again) show a warning that the code is unvetted and might contain security issues.
They don’t include information about how to use the kernel module.
I assumed I would have to load it by name with kldload
.
To find the name, I listed all the files installed by the package.
$ pkg info -l wireguard-kmod
wireguard-kmod-0.0.20211105_1:
/boot/modules/if_wg.ko
/usr/local/share/licenses/wireguard-kmod-0.0.20211105_1/LICENSE
/usr/local/share/licenses/wireguard-kmod-0.0.20211105_1/MIT
/usr/local/share/licenses/wireguard-kmod-0.0.20211105_1/catalog.mk
However, I got an error when I loaded it by name.
# kldload if_wg
kldload: can't load if_wg: module already loaded or in kernel
Checking whether the module is loaded gave me exit code 0 (success). I guess it’s loaded by default!
# kldstat -q -n if_wg; echo $?
0
Configuring, starting, and stopping tunnels works the same as on other platforms with wireguard-tools
.
I defined the wg0
tunnel by creating /usr/local/etc/wireguard/wg0.conf
.
#
# wg0.conf - wireguard configuration for a client machine (e.g. a laptop)
#
[Interface]
Address = # 10.2.0.<Your Address Here>/24
PrivateKey = # <Your Private Key Here>
DNS = 1.1.1.1
# Gateway server
[Peer]
Endpoint = url.of.gateway:51820
PublicKey = # <Gateway Server's Public Key Here>
AllowedIPs = 10.2.0.0/24,192.168.1.0/24
PersistentKeepAlive = 25
Starting the tunnel revealed another difference between FreeBSD and Linux.
# wg-quick up wg0
[#] ifconfig wg create name wg0
[#] wg setconf wg0 /dev/stdin
[#] ifconfig wg0 inet 10.2.0.10/24 alias
[#] ifconfig wg0 mtu 1420
[#] ifconfig wg0 up
[#] resolvconf -a wg0 -x
[#] route -q -n add -inet 192.168.1.0/24 -interface wg0
[#] resolvconf -d wg0
[#] ifconfig wg0 destroy
Uh oh, ifconfig
destroyed my wg0 interface immediately after creating it!
Apparently, FreeBSD won’t let me add a route that conflicts with one on another active interface.
Linux lets me override the existing route.
I already had this route because I was setting WireGuard up while connected to my home network. Adding a route through the tunnel for 192.168.1.0/24 requires that I not already be on a network with that subnet.
This configuration would work if I were on a different network—provided that it didn’t also use that subnet—so, for now, I removed the home subnet from AllowedIPs.
/usr/local/etc/wireguard/wg0.conf
AllowedIPs = 10.2.0.0/24
That change fixed the issue.
# wg-quick up wg0
[#] ifconfig wg create name wg0
[#] wg setconf wg0 /dev/stdin
[#] ifconfig wg0 inet 10.2.0.10/24 alias
[#] ifconfig wg0 mtu 1420
[#] ifconfig wg0 up
[#] resolvconf -a wg0 -x
[+] Backgrounding route monitor
# wg show
interface: wg0
public key: <My client's public key>
private key: (hidden)
listening port: 40839
peer: <My gateway server's public key>
endpoint: <IP Address>:51820
allowed ips: 10.2.0.0/24
latest handshake: 30 seconds ago
transfer: 92 B received, 212 B sent
persistent keepalive: every 25 seconds
The last step is to enable the tunnel on startup.
I suspected that wireguard-tools came with an rc
script to do so.
$ pkg info -l wireguard-tools
wireguard-tools-1.0.20210914_1:
/usr/local/bin/wg
/usr/local/bin/wg-quick
/usr/local/etc/rc.d/wireguard
[...]
Yep, there it is. The comment at the top of /usr/local/etc/rc.c/wireguard
explains how to configure it in /etc/rc.conf
.
wireguard_enable="YES"
wireguard_interfaces="wg0"
With those lines, the tunnel can be started using service wireguard start
.
Setting Up Stable Userland WireGuard
All we have to do to try out wireguard-go
is to swap out the package that’s installed.
Configuring, starting, and stopping tunnels is handled by wireguard-tools
and works exactly the same way with both versions.
# pkg remove wireguard-kmod
[...]
# pkg install wireguard-go
Now, running service wireguard start
shows this:
# service wireguard start
[#] ifconfig wg create name wg0
[!] Missing WireGuard kernel support (ifconfig: SIOCIFCREATE2:
Invalid argument). Falling back to slow userspace implementation.
[#] wireguard-go wg0
┌──────────────────────────────────────────────────────┐
│ │
│ Running wireguard-go is not required because this │
│ kernel has first class support for WireGuard. For │
│ information on installing the kernel module, │
│ please visit: │
│ https://www.wireguard.com/install/ │
│ │
└──────────────────────────────────────────────────────┘
[...]
This is a bit of a mixed message.
With wireguard-kmod
, I got a scary warning about it being experimental, but wireguard-go
says it’s OBE.
I want to use the in-kernel version for better performance, but I also want security and stability.
Let’s see if the slow userspace implementation is really all that slow.
FreeBSD Wireguard Shootout
As an unscientific test, I set up a tunnel between my laptop with FreeBSD 13.1 and my Raspberry Pi with both connected to the same switch over gigabit ethernet.
I ran iperf3
to measure the throughput and vmstat
to see CPU and memory usage.
Here are the network throughput results.
Baseline (No Tunnel)
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 1.09 GBytes 935 Mbits/sec 0 sender
[ 5] 0.00-10.04 sec 1.09 GBytes 932 Mbits/sec receiver
wireguard-go
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 661 MBytes 555 Mbits/sec 423 sender
[ 5] 0.00-10.02 sec 661 MBytes 553 Mbits/sec receiver
wireguard-kmod
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 787 MBytes 660 Mbits/sec 138 sender
[ 5] 0.00-10.03 sec 786 MBytes 658 Mbits/sec receiver
Compare the CPU usage stats from vmstat
.
Each line is a half-second interval from the middle of the iperf test.
Baseline (No tunnel)
cpu
sy cs us sy id
97596 40992 1 12 87
96434 40968 3 11 87
98020 40760 4 12 84
wireguard-go
cpu
sy cs us sy id
210358 85010 17 16 67
221720 89424 21 15 63
215976 87518 12 20 68
wireguard-kmod
cpu
sy cs us sy id
14908 41894 1 27 72
13274 35634 2 27 71
14110 37270 1 29 70
The number of system calls (the first sy
column) is why we want WireGuard in the kernel instead of in userland; the userland version made more system calls to transfer less data.
The user and system CPU time numbers (us
and the second sy
) reflect that difference.
That said, I’d love to know why there were fewer system calls with in-kernel wireguard than with no tunnel at all.
The idle number (id
) shows a small difference in unused CPU capacity. The in-kernel version eats up less CPU time overall.
On Linux?
Just for fun, I booted a live USB stick of Fedora Linux, which has WireGuard in the kernel, and tried the same test.
Baseline (No Tunnel) on Linux
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 1.09 GBytes 936 Mbits/sec 0 sender
[ 5] 0.00-10.00 sec 1.09 GBytes 934 Mbits/sec receiver
wireguard
on Linux
[ ID] Interval Transfer Bitrate Retr
[ 5] 0.00-10.00 sec 717 MBytes 601 Mbits/sec 78 sender
[ 5] 0.00-10.00 sec 715 MBytes 600 Mbits/sec receiver
Conclusions
The goal of using WireGuard is to have a secure VPN connection with very little overhead.
In this test, wireguard-go
on FreeBSD doesn’t really deliver that, even for casual use.
If I were happy cutting network performance almost in half, I might as well use a different protocol.
Fortunately, wireguard-kmod
performs much better.
It even performs as well or better than the real competition, WireGuard on Linux.
Of course, this port isn’t done yet, but considering that it’s been over a year since the in-kernel WireGuard kerfuffle, I’ll wager that it’s secure enough to use.