Wireguard is a revolutionary VPN technology that allows for very fast throughput with low latency compared to traditional VPN technologies.
Here are some rough benchmarks that illustrate the performance differences:
Release 2024.1 and later allow Wireguard configuration in the web interface. Configuration is also still available via command line. Instructions on this wiki page detail how to configure Wireguard via the command-line interface. Instructions for the graphical web interface will follow later. The main principles apply regardless of the interface used.
Wireguard's fast performance is possible because code is executed inside kernel-space. Other technologies like OpenVPN, PPTP, or tinc, run in the much slower user-space. Wireguard still uses asymmetric-key technology (like OpenVPN) that is more basic in functionality. However, development is ongoing, and encryption key improvements are expected soon.
Wireguard is not a “talkative” protocol. It tends to send data only when needed (unless there's a peer with a forced keepalive option). A new approach with Wireguard was to completely remove handshaking. Now, data is accepted only if the decryption key works. This makes Wireguard less “chatty”, simpler, and faster. By default, Wireguard communicates over UDP port 51820.
Before configuring Wireguard, consult the official documentation's Quick Start guide, and maybe the unofficial version too.
Wireguard is now available in FreshTomato's web interface since release 2024.1. It is also still available via command line.
Once you understand some basic principles, it is fairly simple to configure. Currently, only ARM-based devices include the code needed to run Wireguard.
If you're unsure, try loading the kernel module as follows:
root@router:/# modprobe wireguard
If you see no output, it means Wireguard executed.
You should now be able to find it in the list of loaded modules:
root@router:/# lsmod | grep wireguard wireguard 131012 0
If Wireguard isn't supported on your system, you'll see the following error:
root@router:/# modprobe wireguard modprobe: module wireguard not found in modules.dep
The first step is to familiarize yourself with the wg
command. For this, typing: wg help
is a great place to start.
root@router:/# wg help Usage: wg <cmd> [<args>] Available subcommands: show: Shows the current configuration and device information showconf: Shows the current configuration of a given WireGuard interface, for use with `setconf' set: Change the current configuration, add peers, remove peers, or change peers setconf: Applies a configuration file to a WireGuard interface addconf: Appends a configuration file to a WireGuard interface syncconf: Synchronizes a configuration file to a WireGuard interface genkey: Generates a new private key and writes it to stdout genpsk: Generates a new preshared key and writes it to stdout pubkey: Reads a private key from stdin and writes a public key to stdout You may pass `--help' to any of these subcommands to view usage.
In general, typing any subcommand followed by “help” offers additional help for the command.
For example:
root@router:/# wg show help Usage: wg show { <interface> | all | interfaces } [public-key | private-key | listen-port | fwmark | peers | preshared-keys | endpoints | allowed-ips | latest-handshakes | transfer | persistent-keepalive | dump]
There are many ways Wireguard can be set up. However, in principle, you'll need a configuration file (containing VPN configuration, peers, keys, IPs, and so on) and a script to initiate the process. The two work together.
This will illustrate how to achieve a point-to-point connection, the simplest kind.
Let's assume there are two devices with these prerequisites:
You will need some type of permanent storage so settings will survive a reboot.
The options include:
If the storage becomes unavailable, the VPN won't function.
For this example, and the final setup, we'll use JFFS. It's assumed you have a JFFS partition mounted at: “/jffs”.
The first step is to create a working pair of keys for each device:
root@routerA:/jffs# wg genkey > privateKey_$(hostname) root@routerA:/jffs# wg pubkey < privateKey_$(hostname) > publicKey_$(hostname)
The above two key generation programs should create two files:
root@routerA:/jffs# ls -l -rw-r--r-- 1 root root 45 Feb 13 10:51 privateKey_routerA -rw-r--r-- 1 root root 45 Feb 13 10:51 publicKey_routerA
The content of these files must be added to the configuration file. In this case, we will call that file: “wg0.conf”.
Do not use the keys from this example. They are hypothetical and only an example.
The contents of the wg0.conf file on routerA are as follows:
root@routerA:/jffs# cat wg0.conf [Interface] # routerA = local PrivateKey = WOOgLRpUxq3XjGfuP79JHKR/f7dd+/0HkbCR1YMDakU= # This is the generated privateKeyrouterA on the local router ListenPort = 51820 # Default port this router listen to, but can be changed if needed [peer] # routerB = remote Endpoint = rtrb.ddns.org:51820 # FDQN:port of Router B PublicKey = iu3524WoHe0UHkY4o6kQSTe1sx9lBArrdBR9mbe+0yA= # This is the public key as generated on the remote device. AllowedIPs = 192.168.200.1/32, 10.1.1.0/24 # 192. is the dedicated VPN IP addressing (intra-router), notice the /32! The 10. in this example is the LAN address space reachable via this endpoint. Do use a /24 for the LAN subnet.
The contents of the wg0.conf file on routerB look like this:
root@routerB:/jffs# cat wg0.conf [Interface] # routerB = local PrivateKey = WOOgLRpUxq3XjGfuP79JHKR/f7dd+/0HkbCR1YMDakU= # This is the generated privateKeyrouterB on the local router ListenPort = 51820 # Default port this router listen to, but can be changed if needed [peer] # routerA = remote Endpoint = rtra.ddns.org:51820 # FDQN:port of Router A PublicKey = Pr1EV/OukTXsj0eeEM96mOCW4Jy00iUMIFp24Z93owo= # This is the public key as generated on the remote device. AllowedIPs = 192.168.200.2/32, 10.1.2.0/24 # 192. is the dedicated VPN IP addressing (intra-router), notice the /32! The 10. in this example is the LAN address space reachable via this endpoint. Do use a /24 for the LAN subnet.
On a network with private addressing (behind NAT), unreachable from the Internet, the connection is initiated from the NAT'd device. However, you must force keepalive activity towards the unNAT'd device to maintain the connection. By default, Wireguard doesn't use keepalive packets.
Let's assume routerB is behind an unmanaged NAT device (your WAN has a private IP). Your routerA [peer] definition in wg0.conf will need to have PersistentKeepalive
defined. Doing this keeps the main router mapping table updated, making the defined Wireguard port reachable.
The necessary changes to wg0.conf for this are:
[peer] # routerA = remote Endpoint = rtra.ddns.org:51820 PublicKey = Pr1EV/OukTXsj0eeEM96mOCW4Jy00iUMIFp24Z93owo= AllowedIPs = 192.168.200.2/32, 10.1.2.0/24 PersistentKeepalive = 25
A PersistentKeepalive value of 25 seconds is best practice, since a typical NAT mapping can expire in just 30 seconds of inactivity. However, you can adjust this, as needed. Just make sure to to monitor the connection stability when making adjustments.
On a point-to-point connection, you need at least one public IP address or mapped port. However, it's possible to have two endpoints behind a NAT, if they also terminate towards a non-NAT'd endpoint. The PersistentKeepalive guidance above still applies to every NAT'd endpoint.
A wizard creates the configuration in this all-in-one solution. It will automate all other steps including key creation, loading, unloading and more. The current version of this script is: 1.22
wget https://tinyurl.com/28b5rckn -O- | tr -d "\r" > /jffs/wg.sh ; chmod 777 /jffs/wg.sh
Running “wg.sh” with the makeconf parameter will guide you through questions to fully automate the configuration. The result is placed in the: “/tmp/wireguard” folder. Subfolder(s) will be created there, named after the FQDNs of each site created. Each of these folders contains two files:
You do not need to make changes to those files. Simply copy them both to the relevant device (preferably jffs). This means you must run the makeconf on any one, and only one, device.
The wg.sh script has been written so that it can be run multiple times, even consecutively. Router and iptables/router rules that are already present in the configuration it creates will not be added again.
The script also supports an stop
parameter option, to unload the Wireguard module and remove any relevant settings it added to the system. Running the script adds a cron reference to selectively check the connectivity of every defined VPN endpoint, and remove it from the routing table. This way, it will not respond. This allows safe fail-back, for example, onto tinc or other VPN protocols/modules.
Now that we have the “wg.sh” and “wg0.conf” files on each site, we can bring up the VPN simply running wg.sh
. If you do this remotely, you might get disconnected while the script is running. For this reason, you should consider running something like { /jffs/wg.sh stop ; /jffs/wg.sh ; } &
to ensure the script has fully completed (or in this case, restarted).
Once the script completes (in about 5-10 seconds), the next step is to verify the last handshake command between the two hosts using the show parameter, like this:
wg show
This can be shortened to just wg
, since the show parameter is implicit:
root@routera:/jffs# wg interface: wg0 public key: Pr1EV/OukTXsj0eeEM96mOCW4Jy00iUMIFp24Z93owo= private key: (hidden) listening port: 51820 peer: iu3524WoHe0UHkY4o6kQSTe1sx9lBArrdBR9mbe+0yA= endpoint: 60.61.62.63:51820 allowed ips: 192.168.200.2/32, 10.1.2.0/24 latest handshake: 16 seconds ago transfer: 0 B received, 1.44 MiB sent
The next step is to ping the remote intra-VPN IP address.
For example, from RouterA: ping 192.168.200.2
Then ping the remote LAN IP address for example, from RouterA: ping 10.1.2.1
As with other VPN protocols, if you want the local DNS server to respond to DNS queries from other VPN sites, you must tell dnsmasq to listen on the “wg0” interface. This is done by adding the following directive to dnsmasq's custom configuration:
interface=wg0
As hinted previously, you must rely on permanent storage to make this work. Regardless of what type of storage you choose, it might become unavailable. For this reason, JFFS has been used throughout the examples, as it arguably the most reliable form. To run Wireguard automatically, place this line in the Firewall field in the Administration/Scripts menu:
until [ $(ping -c 1 -A -W 5 -q google.com &>/dev/null && echo 1 || echo 0) -eq 1 ]; do sleep 5; done; /jffs/wg.sh
It should work as is in the Execute after mounted field on the JFFS page, but be advised that it has not yet been tested.
It is generally unwise to mix and match different technologies. There are resource utilization considerations relating to this. However, there can also be secondary positive effects to running tinc in the background while Wireguard is running. You could run tinc between all the hosts with a wider network mask, (such as 255.255.0.0) first and then add Wireguard only on specific links (ideally, the high performance ones).
Having a Wireguard client connect from a mobile phone or a site that uses slow DSL could create unwanted overhead. So why use two VPNs at once?
Let's assume we have five sites connected via tinc (green). Three of these sites have high speed connectivity where we decide to add Wireguard (blue). We now now find ourselves in the situation illustrated above.
If something were to cause a Wireguard failure (say, storage of the script) and Site A were to be disconnected, it would now be unreachable. This is illustrated in the diagram below, in which red stars represent the broken links:
However, what if addressing was done such that:
Under these conditions, the tinc-only devices could greatly increase resilience and redundancy.
If the Wireguard script is up (“wg0” device exists), that could allow you to connect via SSH into a tinc-only device (say, Site E). From there, you could use SSH to connect to Site A again to fix the issue.
When your routing table has multiple options to reach a destination, a good way to verify this is to use the built-in ip route
command.
In the same example, with two VPN technologies running at once, you can verify the routing table's elected path, like this:
ip route get x.x.x.x
root@router:/# ip route get 10.10.10.1 10.10.10.1 dev wg0 src 192.168.192.6 cache mtu 1420 advmss 1380 root@router:# ip route get 10.10.8.1 10.10.8.1 dev tinc src 10.10.6.1 cache mtu 1500 advmss 1460
If any problems arise while running the script, you should consider changing the first line from:
#!/bin/sh
to the following:
#!/bin/sh -x
Using the script with the “-x” option causes it to run in trace (debug) mode.
Remember that you can get help with any subcommand by typing:
root@routera:/# wg help
Remember that wg
is a shortcut to using wg show
and displays helpful information, as seen below:
You can also display more advanced information by using:
root@routera:/# wg show all dump
If there's no issue with the VPN, there might be a routing problem which can be verified using the route subcommand:
root@routera:/# route
Pinging the intra-VPN IP address first will help to identify if Wireguard is the issue or if it is routing-related.
Finally, pay attention to the AllowedIPs field. There, each intra-vpn router's IP address is defined with a /32 subnet. However, in the wg.sh script, a /24 subnet is used. This is meant.