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:
- 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;
- 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