{wire}guard from your ISP

WireGuard aims to be as easy to configure and deploy as SSH. You establish a VPN connection by simply exchanging public keys, and the rest is transparently handled by WireGuard.

There are many other technologies, however wireguard is uniquley interesting for:

  • cryptokey routing: the first principles simply mapping public keys and sets of allowed addreses, making wireguard easier to grok for deployments.
  • endpoints and roaming: also initial principles that facilitate NAT traversal and utilization of dynamic addressing through keepalives.
  • focusing on simplicity and ease-of-auditability, discussed here by the author, compared to other technologies which are sprawling implementations that make auditing difficult.
  • having formal verification coverage: presented here and discussed here.
  • having recognition for eventually belonging in the kernel: mentioned here by Linus himself.

People are getting into a recent uproar about entities like facebook and cambridge analytica while willing putting their information into these ecosystems. We should instead be considerably more concerned about the unwilling surveillance we're a part of from our ISP. Recently the guardian and some discussion pushed me to finally look into applying wireguard to something like the home network design.

So we're going to:

  • install wiregurad on an edgemax router
  • bring up a wireguard tunnel
  • send all DNS requests over the tunnel
  • send selective hosts or subnets over the tunnel

For choosing a provider I was compelled to use mullvad based on seeing their presence in a bunch of places over recent years and seeing them consistently earn the respect of reviewers. They were one very early providers of wireguard end points and they've been supportive of the upstream development project. So, sign up for an account. Then let's begin:

Install WireGuard on the EdgeRouter

The vyatta-wireguard package is a Vyatta module and pre-built binaries for the Ubiquiti EdgeRouter to support WireGuard. It gets built when Ubiquiti releases the GPL Archive for firmware releases, this only occurs on stable releases. Let's assume you have an ER-4 and that you've got ssh already set up:

ssh router
add system image https://dl.ubnt.com/firmwares/edgemax/v1.10.x/ER-e300.v1.10.1.5067768.tar
reboot

The way the EdgeRouter handles upgrades is to unpack and create an entirely new filesystem. As we want some files (e.g. {public,private}keys) to persist between updates we will utilize the /config area of the filesystem as the entire /config directory is recursively copied to the new image. We'll get the most recent release of vyatta-wireguard and install it:

ssh router
sudo su
cd /config/
mkdir packages
cd packages/
curl link-to-wireguard-e300-0.0.20180304-1 --output 'wireguard-e300-0.0.20180304-1.deb'
dpkg -i wireguard-e300-0.0.20180304-1.deb

As the link for the download will be to some obscure aws mirror you might find it easier to use a browser plugin like cliget.

Bring up WireGuard Tunnel

First we'll want to generate a keypair for our WireGuard interface. Personally I think its easier to have different interfaces for difference purposes. As a convention I start with wg0 being my vpn backhaul tunnel, and create more WireGuard interfaces for other purposes. Keypair for wg0:

ssh router
sudo su
cd /config/auth
wg genkey | tee wg0 | wg pubkey > wg0.pub

Now using our account number and public key we need to obtain an address to use with our tunnel:

curl https://api.mullvad.net/wg/ -d account=ACCOUNT --data-urlencode pubkey=PUBKEY

Where:

  • ACCOUNT: your mullvad account number
  • PUBKEY: literally cat your /config/auth/wg0.pub

This should return and ipv4 and ipv6 address, we'll just use the ipv4 for now. Let's set up the interface:

ssh router
config
set interfaces wireguard wg0 address ADDRESS
set interfaces wireguard wg0 peer PEERKEY allowed-ips 0.0.0.0/0
set interfaces wireguard wg0 peer PEERKEY endpoint 'SERVER:PORT'
set interfaces wireguard wg0 peer PEERKEY persistent-keepalive 15
set interfaces wireguard wg0 private-key /config/auth/wg0
set interfaces wireguard wg0 route-allowed-ips false
commit;save;

Where:

  • ADDRESS: is the ipv4 address returned by our query above
  • PEERKEY: is the server "Public Key" from this list that you want to use
  • SERVER: is the "Wireguard Server" you want to use from this list
  • PORT: is the "Multihop port" from that same server this list

It is important to recognize that you're setting route-allowed-ips false, as you've set allowed-ips to 0.0.0.0/0 for this peer and that can wreak havoc with the system.

We now need to set up a source NAT translation for the layer3 WireGuard interface:

set service nat rule 5010 outbound-interface wg0
set service nat rule 5010 outside-address address ADDRESS
set service nat rule 5010 type source
commit;save;

Where:

  • ADDRESS: is the ipv4 address returned by our query above without the CIDR notation

Now, this WireGuard inerface is effectively a WAN, we want to treat it with the same concern that we do to our ISP WAN and apply an appropriate firewall:

set firewall name WG_IN default-action drop
set firewall name WG_IN description 'packets from wg0 through router to LAN'
set firewall name WG_IN enable-default-log
set firewall name WG_IN rule 1 action accept
set firewall name WG_IN rule 1 description 'allow established sessions'
set firewall name WG_IN rule 1 log disable
set firewall name WG_IN rule 1 protocol all
set firewall name WG_IN rule 1 state established enable
set firewall name WG_IN rule 1 state invalid disable
set firewall name WG_IN rule 1 state new disable
set firewall name WG_IN rule 1 state related enable
set firewall name WG_IN rule 2 action drop
set firewall name WG_IN rule 2 description 'drop invalid state'
set firewall name WG_IN rule 2 log enable
set firewall name WG_IN rule 2 protocol all
set firewall name WG_IN rule 2 state invalid enable
set firewall name WG_LOCAL default-action drop
set firewall name WG_LOCAL description 'packets from wg0 to router'
set firewall name WG_LOCAL enable-default-log
set firewall name WG_LOCAL rule 1 action accept
set firewall name WG_LOCAL rule 1 description 'allow established sessions'
set firewall name WG_LOCAL rule 1 log disable
set firewall name WG_LOCAL rule 1 protocol all
set firewall name WG_LOCAL rule 1 state established enable
set firewall name WG_LOCAL rule 1 state invalid disable
set firewall name WG_LOCAL rule 1 state new disable
set firewall name WG_LOCAL rule 1 state related enable
set firewall name WG_LOCAL rule 2 action drop
set firewall name WG_LOCAL rule 2 description 'drop invalid state'
set firewall name WG_LOCAL rule 2 log enable
set firewall name WG_LOCAL rule 2 protocol all
set firewall name WG_LOCAL rule 2 state invalid enable
set interfaces wireguard wg0 firewall in name WG_IN
set interfaces wireguard wg0 firewall local name WG_LOCAL
commit;save;

Now if you're wg0 interface was up before applying this firewall you will see packet loss. You can just reboot the router and wireguard will make that connection again at boot time. We didn't specify a listen port on the wg0 interface, so the traffic between the upstream vpn and the wg0 interface has to be connected via your router making the outbound request and the firewall allowing packet flow based on the WG_IN rule 1 above for established and related traffic.

Send DNS requests over the Tunnel

In the case that you're using the Mullvad DNS you can simply set the system domain-name to the associated DNS server on the Mullvad side, which is 10.x.0.1. However if you're using another recursor like 8.8.8.8 or the new 1.1.1.1 you can force this by adding routes in the main table for the resolver addresses (assuming 1.1.1.1,1.0.0.1):

ssh router
configure
set protocols static interface-route 1.1.1.1/32 next-hop-interface wg0
set protocols static interface-route 1.0.0.1/32 next-hop-interface wg0
commit;save;

Now from any host on the network you can do an mtr 1.1.1.1 and see the traced path that the traffic takes that is destined for that address.

I find that this is currently an easier way to hide your DNS requests from your ISP than trying to adopt any of the clients required to perform DNS-over-TLS or DNS-over-HTTPS.

Send Selective hosts over the Tunnel

In the case of the home network design we have a "home" and "guest" network. We'll send some hosts from the "home" network over the tunnel and all hosts from the "guest" network over the tunnel. We accomplish this through something called Policy Based Routing. First we make a routing table for the wireguard tunnel:

set protocols static table 1 description 'table to force wg0:mullvad'
set protocols static table 1 interface-route 0.0.0.0/0 next-hop-interface wg0
set protocols static table 1 route 0.0.0.0/0 blackhole distance 255

As we're creating multiple routing tables we need to be cognizant that traffic can "fall through" to the main routing table, which means if the wg0 interface goes down traffic would immedietly be traversing your ISP line raw. So we add a blackhole route with a higher distance than the next-hop-interface route (255) to stick traffic to this table.

Now we'll create a an address group that we will then use in a modify ruleset:

set firewall group address-group HOME_MULLVAD description 'hosts in HOME that route out via Mullvad'
set firewall group address-group HOME_MULLVAD address 172.16.0.10
set firewall group address-group HOME_MULLVAD address 172.16.0.11
set firewall group address-group HOME_MULLVAD address 172.16.0.12

In this case we're going to have the hosts in the "home" network at 10,11,12 respectively backhaul over the tunnel. We'll write the modify rule:

set firewall modify PBR_MODIFY description 'set routing tables selectively based on source address'
set firewall modify PBR_MODIFY rule 10 action accept
set firewall modify PBR_MODIFY rule 10 description 'exclude LAN to LAN traffic from PBR'
set firewall modify PBR_MODIFY rule 10 destination address 172.16.0.0/12
set firewall modify PBR_MODIFY rule 100 action modify
set firewall modify PBR_MODIFY rule 100 description 'modify all traffic coming from interloper'
set firewall modify PBR_MODIFY rule 100 modify table 1
set firewall modify PBR_MODIFY rule 100 source address 172.16.1.0/24
set firewall modify PBR_MODIFY rule 200 action modify
set firewall modify PBR_MODIFY rule 200 description 'modify selective hosts within haven'
set firewall modify PBR_MODIFY rule 200 modify table 1
set firewall modify PBR_MODIFY rule 200 source group address-group HOME_MULLVAD
set interfaces ethernet eth2 vif 7 firewall in modify PBR_MODIFY
set interfaces ethernet eth2 vif 14 firewall in modify PBR_MODIFY

Now any hosts enumerated in the HOME_MULLVAD address group will be being modified to use the wireguard tunnel. You can test by checking mtr to any arbitrary site.

I'll say on my network I noticed only a 15-20ms latency increase with little to no bandwidth drop. WireGuard is incredibly impressive.

Special thanks to Lochnair for learning how to package wireguard for vyatta and for patiently explaining how to do everything that is described above.