VyOS Policy Based Routing with WireGuard + Mullvad
Here is a quick guide to setting up policy based routes for VyOS or other Vyatta variants such as EdgeOS.
I wrote a similar post on doing this with OpenVPN using PIA, however this setup is much simpler as we are able to make use of interface routes. Check out the older article in the link below if you want to learn more.
VPN Provider
Although PIA says they support WireGuard, their implementation is non-standard and confusing. I found some projects that attemt to reverse engineer the protocol such as https://github.com/thrnz/docker-wireguard-pia, and PIA themselves later released their own scripts for manual connections.
In the end I ended up going for Mullvad, mostly due to their standardized WireGuard setup https://mullvad.net/en/help/why-wireguard/, and its been going great. Currently i am able to saturate my 300mbit link using a single tunnel.
VyOS configuration
This guide assumes that you are familiar with doing configuration changes in VyOS. While I have chosen mullvad, this could be done using any other provider as well. Im currently on the latest rolling release which is 1.4-rolling-202101300218. There may be some issues with earlier images not accepting interface routes, so I can't vouch for other versions. One issue I had with this image is that I can no longer configure smp_affinity which I am still resolving.
WireGuard connections
First we need to generate a named wireguard keypair per tunnel. I ended up with 4 keypairs, as I wanted 4 wireguard tunnels (each to a different country).
$ generate wireguard named-keypairs fi1-wireguard
$ show wireguard keypairs privkey fi1-wireguard
xxxxxxxxredactedxxxxxxxxxxxxx
Upload the public key to mullvad by registering the private key via their wireguard configurator found here. Then use the same page to generate a wg-wquick configuration using that same key for a country of your choice.
Next we program VyOS with the wireguard configuration for that country. I repeated this 4 times creating wg101-104 with different connection details per link.
set interfaces wireguard wg101 address 'xx.xx.xx.xx/32' #Address
set interfaces wireguard wg101 description 'Mullvad fi1-wireguard'
set interfaces wireguard wg101 firewall in name 'WAN_IN'
set interfaces wireguard wg101 firewall local name 'WAN_LOCAL'
set interfaces wireguard wg101 peer mullvad address 'xx.xx.xx.xx' #Endpoint
set interfaces wireguard wg101 peer mullvad allowed-ips '0.0.0.0/0'
set interfaces wireguard wg101 peer mullvad persistent-keepalive '10'
set interfaces wireguard wg101 peer mullvad port '51820'
set interfaces wireguard wg101 peer mullvad pubkey 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' # PublicKey
set interfaces wireguard wg101 private-key 'fi1-wireguard'
Firewall groups
We want to have a method to control which devices use which VPN connection. For this we can create firewall groups for each WireGuard link, and one final load-balanced group that uses all connections.
set firewall group address-group WG-MULLVAD-FI address 192.168.x.x
set firewall group address-group WG-MULLVAD-NO address 192.168.y.y
set firewall group address-group WG-MULLVAD-SE address 192.168.z.z
set firewall group address-group WG-MULLVAD-DK address 192.168.a.a
set firewall group address-group WG-MULLVAD-LB address 192.168.b.b
We also need a firewall group for RFC1918 destinations so that they aren't also routed out on the wg interfaces
set firewall group network-group RFC1918 network '192.168.0.0/16'
set firewall group network-group RFC1918 network '10.0.0.0/8'
set firewall group network-group RFC1918 network '172.16.0.0/12'
Policy
We set simple routing policies that bind hosts found in the predefined firewall groups, to alternative routing tables.
set policy route VPN rule 1 description 'Local destinations'
set policy route VPN rule 1 destination group network-group 'RFC1918'
set policy route VPN rule 1 set table 'main'
set policy route VPN rule 100 description 'LoadBalance'
set policy route VPN rule 100 destination address '0.0.0.0/0'
set policy route VPN rule 100 set table '100'
set policy route VPN rule 100 source group address-group 'WG-MULLVAD-LB'
set policy route VPN rule 101 description 'NO'
set policy route VPN rule 101 destination address '0.0.0.0/0'
set policy route VPN rule 101 set table '101'
set policy route VPN rule 101 source group address-group 'WG-MULLVAD-NO'
set policy route VPN rule 102 description 'SE'
set policy route VPN rule 102 destination address '0.0.0.0/0'
set policy route VPN rule 102 set table '102'
set policy route VPN rule 102 source group address-group 'WG-MULLVAD-SE'
set policy route VPN rule 103 description 'DK'
set policy route VPN rule 103 destination address '0.0.0.0/0'
set policy route VPN rule 103 set table '103'
set policy route VPN rule 103 source group address-group 'WG-MULLVAD-DK'
set policy route VPN rule 104 description 'FI'
set policy route VPN rule 104 destination address '0.0.0.0/0'
set policy route VPN rule 104 set table '104'
set policy route VPN rule 104 source group address-group 'WG-MULLVAD-FI'
Next we apply the policy to our LAN interfaces.
set interfaces ethernet eth1 policy route 'VPN'
Interface routes
Now that we have assigned traffic from devices that will use VPN links, on to their own routing tables, we need to define the default routes for these.
set protocols static table 100 interface-route 0.0.0.0/0 next-hop-interface wg101
set protocols static table 100 interface-route 0.0.0.0/0 next-hop-interface wg102
set protocols static table 100 interface-route 0.0.0.0/0 next-hop-interface wg103
set protocols static table 100 interface-route 0.0.0.0/0 next-hop-interface wg104
set protocols static table 100 route 0.0.0.0/0 blackhole distance '255'
set protocols static table 101 interface-route 0.0.0.0/0 next-hop-interface wg101
set protocols static table 101 route 0.0.0.0/0 blackhole distance '255'
set protocols static table 102 interface-route 0.0.0.0/0 next-hop-interface wg102
set protocols static table 102 route 0.0.0.0/0 blackhole distance '255'
set protocols static table 103 interface-route 0.0.0.0/0 next-hop-interface wg103
set protocols static table 103 route 0.0.0.0/0 blackhole distance '255'
set protocols static table 104 interface-route 0.0.0.0/0 next-hop-interface wg104
set protocols static table 104 route 0.0.0.0/0 blackhole distance '255'
NAT
Remember to nat the wireguard connections or your clients will not get routed properly:
set nat source rule 101 outbound-interface 'wg101'
set nat source rule 101 translation address 'masquerade'
set nat source rule 102 outbound-interface 'wg102'
set nat source rule 102 translation address 'masquerade'
set nat source rule 103 outbound-interface 'wg103'
set nat source rule 103 translation address 'masquerade'
set nat source rule 104 outbound-interface 'wg104'
set nat source rule 104 translation address 'masquerade'
Killswitch
Lastly, it may be a good idea to block clients designeted for using these VPN links from using the wan connection. This is simple to set up using a basic firewall rule on our WAN out rule.
set firewall name WAN_OUT default-action 'accept'
set firewall name WAN_OUT rule 10 action 'accept'
set firewall name WAN_OUT rule 10 state established 'enable'
set firewall name WAN_OUT rule 10 state related 'enable'
set firewall name WAN_OUT rule 20 action 'reject'
set firewall name WAN_OUT rule 20 source group address-group 'WG-MULLVAD-LB'
set firewall name WAN_OUT rule 21 action 'reject'
set firewall name WAN_OUT rule 21 source group address-group 'WG-MULLVAD-NO'
set firewall name WAN_OUT rule 22 action 'reject'
set firewall name WAN_OUT rule 22 source group address-group 'WG-MULLVAD-SE'
set firewall name WAN_OUT rule 23 action 'reject'
set firewall name WAN_OUT rule 23 source group address-group 'WG-MULLVAD-DK'
set firewall name WAN_OUT rule 24 action 'reject'
set firewall name WAN_OUT rule 24 source group address-group 'WG-MULLVAD-FI'
Enjoy!
Im enjoying this simple setup as it involves no custom scripting. Let me know if you have have any issues!