This is an old revision of the document!
Wireguard is a revolutionary VPN technology that allows for very fast throughput lowe latency compared to traditional VPN technologies.
Here are some rough benchmarks that illustrate the performance differences:
Wireguard's exceptional performance is possible because the code is executed within kernel-space. Other technologies, such as OpenVPN, PPTP, or tinc run in user-space which is much slower. Wireguard still uses asymmetric-key technology (similar to OpenVPN) that is more basic in functionality. However, a lot of development is going on right now, and improvements in encryption keys are expected soon.
Wireguard is not a “talkative” protocol. It tends to send data only when needed (unless a peer is defined with a forced keepalive option). One new approach taken 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). Wireguard communicates by default over UDP port 51820.
Before configuring Wireguard, it's best to consult the official quickstart documentation and possibly also the unofficial version.
Until a graphical interface is developed for FreshTomato, Wireguard is available only via command line. Once the basic principles are understood, configuring Wireguard is relatively simple. There are just a few caveats to be aware of. Currently, only ARM-based devices include the code needed to run Wireguard. If you're unsure, you can try loading the kernel module as follows:
root@router:/# modprobe wireguard
If you see no output, it means that 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 is not supported on your system, you will see the following error:
root@router:/# modprobe wireguard modprobe: module wireguard not found in modules.dep
The first step is 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 multiple way wireguard can be set up but in principle you'll need a configuration file (as in VPN configuration, peers, keys, IPs, etc) and a script to initiate the process. The two work hand in hand.
Here, we will illustrate how to achieve a point-to-point connection, the simplest kind. Let's assume we have two devices with these prerequisites:
You will need some type of permanent storage so settings will survive a reboot. Here, you have several options, including:
If that storage becomes unavailable, the VPN won't function. For this example, and for the final setup, we will rely on JFFS. Thus, it is assumed you have a JFFS partition mounted in the filesystem of 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 have created 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 needs to be added to the configuration file. In this case, we will call that file: wg0.conf
:
Please do not use the keys from this example. They are fake/hypothetical and only used as 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) that isn't reachable from the Internet, the connection will be initiated from the NATed device. However, you'll need to force keepalive activity towards the unNATed device to maintain the connection. Remember, by default, Wireguard doesn't use keepalive packets. Let's assume RouterB is behind an unmanaged NAT device (so your WAN has a private IP) your router A [peer] definition within wg0.conf will need to have the PersistentKeepalive
defined. Doing this allows the main router mapping table to stay updated, and make the defined Wireguard port reachable.
The necessary changes to the wf0.conf file are seen here:
[peer] # RouterA = remote Endpoint = rtra.ddns.org:51820 PublicKey = Pr1EV/OukTXsj0eeEM96mOCW4Jy00iUMIFp24Z93owo= AllowedIPs = 192.168.200.2/32, 10.1.2.0/24 PersistentKeepalive = 25
The PersistentKeepalive value of 25 seconds represents best practices, since a typical NAT mapping can expire in as little as 30 seconds of inactivity. However, you can try increasing or decreasing this, as needed. Just make certain to monitor the connection stability.
On a point-to-point connection, we need at least one public IP address or a properly-mapped port. However, it's possible to have two endpoints behind a NAT, as long as they also terminate towards a non-NATed endpoint. The PersistentKeepalive guidance given above will still apply to every NATed endpoint.
Current version: 1.22
This all-in-one solution uses a wizard to create the configuration. It will automate all other steps including key creation, loading, unloading and more.
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 some questions to fully automate the configuration. The result is placed in the: “/tmp/wireguard” folder. Subfolder(s) will be created there, named after the FQDN(s) of each site that is created. Each of these folders contains two files:
You do not need to make any 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 such 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 optional stop
parameter to unload the wireguard module and remove any relevant configuration it added to the system. Running the script will add a cron reference to selectively check the reachability of every defined VPN endpoint, and remove it from the routing table is they are not to respond. This allows safe fail-back, for example, onto tinc or another VPN protocol/module.
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 technologies, if you want the local DNS server to respond to DNS queries from other VPN sites, you need to tell dnsmasq to enable listening 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 not a good idea to mix and match different technologies. There are also some resources utilization considerations relating to this, however there may 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 your mobile phone or a site that uses slow DSL would probably create unwanted overhead. So why use two VPNs at once?
Let's say we have five sites, all connected over tinc (green) and three of them have high speed connectivity where I decide to add Wireguard (blue). I now find myself in the situation in the above diagram.
Now, if something happens to Wireguard (say, a storage failure where the script resides) and Site A gets disconnected, it is now unreachable. This is illustrated in the diagram below by red stars to represent the dropped links:
However, let's say the addressing were done a certain way:
In this case, the tinc-only devices would play a very important role in resilience here.
If the wireguard script is up (the “wg0” device exists), tinc won't save you automatically. However, it could allow you to connect via ssh into a tinc-only device (say, Site E), and from there you could SSH into Site A again to try 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 issue is experienced while running the script, consider changing the first line from
#!/bin/sh
to
#!/bin/sh -x
this will run the script in trace (debug) mode.
root@routera:/# wg help
is your friend and remember wg
is a shortcut to wg show
and would produce some helpful information e.g.
but you can run advanced output like
root@routera:/# wg show all dump
If there's no issue with the VPN you might have a routing problem which can be verified via:
root@routera:/# route
Pinging the intra-VPN Ip address first will help understanding is the issue is wireguard or rather routing related.
Finally please pay attention to the AllowedIPs field in the configuration, each intra-vpn router's IP is defined with a /32 in there where on the wg.sh it uses a /24. This is meant.