Table of Contents

Wireguard

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:


Introduction

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, there is a lot of ongoing development, 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, you should consult the official documentation's Quick Start guide, and possibly the unofficial version as well.

Overview

Until Wireguard is integrated into FreshTomato's web interface, it is available only via command line. However, once you understand some basic principles, 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

Syntax

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]

Configuration

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.

Point-to-Point Connection

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”.


Keys

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.




The Consequences of the Endpoint Sitting behind a NAT Device




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 routerA [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 wg0.conf file for this 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.



The Consequences of Having Two Endpoints Sit behind a NAT Device




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.

Automated Script with Full Mesh Support

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 


Assumptions:







The script will display an introduction screen:





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 so it will not respond. This allows safe fail-back, for example, onto tinc or another VPN protocol/module.

Bringing up the VPN

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

DNS Resolution

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



Running Wireguard at Boot

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.


Running multiple VPN technologies at once

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 we decide to add Wireguard (blue). We now now find ourselves in the situation shown above.



If something happens to Wireguard (say, a storage failure where the script is) and Site A gets disconnected, its now unreachable. This is illustrated in the diagram below by red stars to represent the broken links:





However, let's say the addressing were done in a certain way:


Under those conditions, the tinc-only devices could play a very important role in resilience/redundancy.

If the Wireguard script is up (the “wg0” device exists), it 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.


Routing verification

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

Wireguard Notes and Troubleshooting

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 also that wg is a shortcut to using wg show and will display 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 in the configuration. 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.

Known Bugs


With the current version of the code (v1.0.20200827), if you use the wg (“wg show”) command, it appears as if sent traffic is not counted as it should be.




This should be fixed in a later version of the code.