Documentation

Server 3.x

Policy Routing#

Policy routing, or sometimes: source routing, is useful when “normal” routing is not sufficient, i.e. you want your VPN client traffic to not be handled by the VPN server’s normal routing table.

We identified two use cases for this:

  1. You want your VPN client traffic, that does not stay on your institute’s network, to be relayed through an existing NAT router/gateway/firewall that you have and already use for your institute’s client traffic destined for the Internet;
  2. You are hosting the VPN server in a data center and you want to relay the VPN client traffic to the institute’s network over a “layer 2” connection.

This document will explain how to set up policy routing on your VPN server using systemd-networkd for scenario 2.

NOTE: we will only document policy routing with systemd-networkd and WireGuard, not OpenVPN.

Scenario#

Our VPN server will have two “physical” interfaces, and one virtual WireGuard interface:

Interface IPv4 IPv6 Purpose
eth0 192.0.2.5/24 2001:db8::5/64 Upstream Internet Connection (Default Route)
eth1 10.10.10.10/24 fd10::10/64 Private Network with NAT Router
wg0 10.42.42.0/24 fd42::/64 VPN Clients

Network Configuration#

You can configure eth0 as described in the Other Interfaces section of the systemd-networkd documentation.

NOTE: you do NOT need to add the IPv4Forwarding and IPv6Forwarding options in that case, because the upstream Internet connection is not used for routing VPN client traffic.

The eth1 configuration is shown below, it is placed in the file /etc/systemd/network/50-eth1.network:

[Match]
Name = eth1

[Network]
Address = 10.10.10.10/24
Address = fd10::10/64
IPv6AcceptRA = no
IPv4Forwarding = yes
IPv6Forwarding = yes

[Route]
Table = 5000
Destination = 0.0.0.0/0
Gateway = 10.10.10.1

[Route]
Table = 5000
Destination = ::/0
Gateway = fd10::1

We want the VPN client traffic to go over the eth1 link, and NOT through eth0, which is why we create an additional routing table with number 5000 to which we will shortly policy route the VPN client traffic.

We need to make the WireGuard client traffic refer to this routing table instead of the default routing table. In order to do this, we create an “override” file for the 60-wg0.network file in /etc/systemd/network/60-wg0.network.d/override.conf.

[Route]
To = 10.42.42.0/24
Table = 5000

[Route]
To = fd42::/64
Table = 5000

[RoutingPolicyRule]
From = 10.42.42.0/24
Table = 5000

[RoutingPolicyRule]
From = fd42::/64
Table = 5000

Firewall#

The firewall also needs some updating in this scenario. You’ll need to modify /etc/nftables.conf on Debian / Ubuntu, or /etc/sysconfig/nftables.conf on Fedora / Enterprise Linux, if you are using the firewall that was configured out of the box when you deployed the VPN server.

The entire postrouting chain can be deleted as the VPN server no longer needs to do NAT. In addition, the forward chain requires some changes to allow forwarding between the right network interfaces, e.g.:

chain forward {

    // other rules...

    iifname "wg0" oifname "eth1" accept
}

NOTE: if you want to allow traffic between VPN clients, you can use iifname "wg0" oifname { "eth1", "wg0" } accept.

NOTE: if you do not want any forwarding restrictions and also allow incoming connections to the VPN client, you can also remove the entire forward chain.

Apply#

As documented in systemd-networkd it MAY be possible to remove the /etc/sysctl.d/70-vpn.conf file and possibly also remove any other network configuration files.

Apply the systemd-networkd changes:

$ sudo networkctl reload
$ sudo networkctl reconfigure eth1
$ sudo networkctl reconfigure wg0 

Restart the firewall, if you modified it:

$ sudo systemctl restart nftables

Debugging#

Below we’ll show some commands and their expected output in case you want to debug the policy routing configuration.

IPv4#

First we make sure that traffic coming from the 10.42.42.0/24 prefix is sent to the routing table 5000:

$ ip -4 rule show
0:  from all lookup local
32765:  from 10.42.42.0/24 lookup 5000 proto static
32766:  from all lookup main
32767:  from all lookup default

Then we view the routing table to make sure the default route is using the eth1 interface via 10.10.10.1, and that we do NOT route traffic to the 10.42.42.0/24 prefix through the default route:

$ ip -4 ro show table 5000
default via 10.10.10.1 dev eth1 proto static 
10.42.42.0/24 dev wg0 proto static scope link 

IPv6#

First we make sure that traffic coming from the fd42::/64 prefix is sent to the routing table 5000:

$ ip -6 rule show
0:  from all lookup local
32765:  from fd42::/64 lookup 5000 proto static
32766:  from all lookup main

Then we view the routing table to make sure the default route is using the eth1 interface via fd10::1, and that we do NOT route traffic to the fd42::/64 prefix through the default route:

$ ip -6 ro show table 5000
fd42::/64 dev wg0 proto static metric 1024 pref medium
default via fd10::1 dev eth1 proto static metric 1024 pref medium

NAT Router Configuration#

Your NAT router needs to know where to find your VPN clients. For this it needs a static route to the IP prefixes 10.42.42.0/24 and fd42::/64 via 10.10.10.10 and fd10::10.

If you are using systemd-networkd on your NAT router as well, you could configure it like this, assuming the interface going to your VPN server is also eth1.

[Match]
Name = eth1

[Network]
Address = 10.10.10.1/24
Address = fd10::1/64
IPv6AcceptRA = no
IPv4Forwarding = yes
IPv6Forwarding = yes

[Route]
Destination = 10.42.42.0/24
Gateway = 10.10.10.10

[Route]
Destination = fd42::/64
Gateway = fd10::10