vpn

Setting up Client VPNs, Policy Routing

Executive Summary

This blog post is the continuation my previous blog post Setting up Dual Stack VPNs and explains how I use client VPNs together with simple Policy Routing on my Linux server in order to relegate outgoing connections to various network interfaces and, ultimately, to different countries. The examples use IPv4 only.

Background

The approach was originally developed back in 2011…2014 when I lived in China and maintained several outgoing VPN connections from my Linux server to end points “in the West” so that I could circumvent internet censorship in China [8]. With the VPN service described Setting up Dual Stack VPNs, it was then possible for me to be in town and to connect the smartphone to my Linux server (in the same town). From there, the connections to sites blocked in China would run over the client VPNs of the Linux server so that I could use Google Maps on my smartphone, for example (which at that time had already been blocked in China).

Preconditions

Routing in Linux follows some very clever approaches which can be combined in mighty ways. Those readers who want to understand all of the underlying theory, are encouraged to study the (older) documents [1], [2], [3], even if parts of the content might not be relevant any more. Those readers who just want to follow and replicate the approach in this blog, should at least study the documents [4], [5], [6].

Apart from that, in order to replicate the approach described here, you should:

  • … fulfil all preconditions listed in the blog post Setting up Dual Stack VPNs
  • … have running the setup similar to the one described in the blog post Setting up Dual Stack VPNs
  • … have access to a commercial VPN provider allowing you to run several client connections on the same machine
  • … have at least read the documents [4], [5], [6]

Description and Usage

The graph below shows the setup on my machine caipirinha.spdns.org with. The 5 VPN services (blue, green color) were already described in blog post Setting up Dual Stack VPNs. Now, we have a close look at the 3 VPN clients which use a commercial VPN service (ocker color) in order to connect to VPN end points in 3 different countries (Portugal, Singapore, Thailand).

VPN Setup on my Linux server

Enabling Routing

Routing needs to be enabled on the Linux server. I personally also decided to switch off the privacy extensions on the Linux server, but that is a personal matter of taste:

# Enable "loose" reverse path filtering and prohibit icmp redirects
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.conf.eth0.send_redirects=0
sysctl -w net.ipv4.icmp_errors_use_inbound_ifaddr=1

# Enable IPv6 routing, but keep SLAAC for eth0
sysctl -w net.ipv6.conf.eth0.accept_ra=2
sysctl -w net.ipv6.conf.all.forwarding=1

# Switch off the privacy extensions
sysctl -w net.ipv6.conf.eth0.use_tempaddr=0

Routing Tables

We now must have a closer look at the concept of the routing table. A routing tables basically lists routes to particular network destinations. An example is the routing table main on my Linux server. It reads:

caipirinha:~ # ip route list table main
default via 192.168.2.1 dev eth0 proto dhcp
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 dev tun4 proto kernel scope link src 192.168.10.1
192.168.11.0/24 dev tun5 proto kernel scope link src 192.168.11.1
192.168.12.0/24 dev tun6 proto kernel scope link src 192.168.12.1
192.168.13.0/24 dev tun7 proto kernel scope link src 192.168.13.1
192.168.14.0/24 dev wg0 proto kernel scope link src 192.168.14.1

This table has 7 entries, and they have this meaning:

  1. (“default via…”) Connections to IP addresses that do not have a corresponding entry in the routing table shall be forwarded via the interface eth0 and to the router IP address 192.168.2.1 (an AVM Fritz! Box).
  2. Connections to the network 192.168.2.0/24 shall be forwarded via the interface eth0 using the source IP address 192.168.2.3 (the Linux server itself).
  3. Connections to the network 192.168.10.0/24 shall be forwarded via the interface tun4 using the source IP address 192.168.10.1 (the Linux server itself). This network belongs to one of the 5 VPN services on my Linux server.
  4. Connections to the network 192.168.11.0/24 shall be forwarded via the interface tun5 using the source IP address 192.168.11.1 (the Linux server itself). This network belongs to one of the 5 VPN services on my Linux server.
  5. Connections to the network 192.168.12.0/24 shall be forwarded via the interface tun6 using the source IP address 192.168.12.1 (the Linux server itself). This network belongs to one of the 5 VPN services on my Linux server.
  6. Connections to the network 192.168.13.0/24 shall be forwarded via the interface tun7 using the source IP address 192.168.13.1 (the Linux server itself). This network belongs to one of the 5 VPN services on my Linux server.
  7. Connections to the network 192.168.14.0/24 shall be forwarded via the interface wg0 using the source IP address 192.168.14.1 (the Linux server itself). This network belongs to one of the 5 VPN services on my Linux server.

Usually, a routing table should have a default entry which sends all IP traffic that is not explicitly routed to other network interfaces to the default router of a network. Otherwise, no meaningful internet access is possible.

A Linux system can have up to 256 routing tables which are defined in /etc/iproute2/rt_tables. They can either be used by their number or by their name. On my Linux server, I have set up 3 additional routing tables, named “Portugal”, “Singapur”, “Thailand”. You can see in the file /etc/iproute2/rt_tables that besides the table main, the tables local, default, and unspec do already exist, but they are not of interest for our purposes.

#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1  inr.ruhep

240 Portugal
241 Singapur
242 Thailand

Right now (before we set up the client VPNs), all 3 routing tables look the same as shown here:

caipirinha:~ # ip route list table Portugal
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0
caipirinha:~ # ip route list table Singapur
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0
caipirinha:~ # ip route list table Thailand
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0

The content of routing tables can be listed with the command ip route list table ${tablename}, and ${tablename} needs to exist in /etc/iproute2/rt_tables. It is important to notice that so far, none of these 3 routing tables have a default route. They only contain the home network and the networks of the 5 VPN services. Right now, these tables are not yet useful. In case you wonder how it comes that these 3 routing tables are populated with their entries. That needs to done either manually or by a script (see next chapter).

OpenVPN Server Configuration (Update)

Now that we have 3 additional routing tables, we must ensure that the networks of our 5 VPN services are also inserted in these 3 routing tables. Therefore, we modify the configuration files described in the blog post Setting up Dual Stack VPNs so that a script runs when the VPN service is started. In the configuration files for the openvpn configuration, we insert the statement:

up /etc/openvpn/start_vpn.sh

In the configuration files for the WireGuard configuration, we insert the statement:

PostUp = /etc/openvpn/start_vpn.sh %i - - 192.168.14.1

The effect of these statements is that the script /etc/openvpn/start_vpn.sh is executed when the VPN service has been set up. If no arguments are specified, openvpn hands over 5 arguments to the scripts (see [9], section “–up cmd”). In the WireGuard configuration, we have to explicitly specify the arguments, the “%i” means the interface (see [10], “PostUp”). In my case, “%i” hence stands for wg0.

The script /etc/openvpn/start_vpn.sh was originally developed for the openvpn configuration and therefore intakes all the default arguments that openvpn transmits, although only the first and the fourth argument are used. Therefore, in the WireGuard configuration, there are two “-” inserted as bogus arguments. That is surely something that can be solved more elegantly.

What does this script do? It essentially writes the same entry that is done automatically in the routing table main to the 3 additional routing tables Portugal, Singapur, and Thailand. It assumes that VPN services have a /24 network (true in my own case, not necessarily for other setups).

#!/bin/bash
#
# This script sets the VPN parameters in the routing tables "Portugal", "Singapur" and "Thailand" once the server has been started successfully.

# Set the correct PATH environment
PATH='/sbin:/usr/sbin:/bin:/usr/bin'

VPN_DEV="${1}"
VPN_SRC="${4}"
VPN_NET=$(echo "${VPN_SRC}" | cut -d . -f 1-3)".0/24"

for TABLE in Portugal Singapur Thailand; do
    ip route add ${VPN_NET} dev ${VPN_DEV} via ${VPN_SRC} table ${TABLE}
done

For our experiments, we now also need to allocate 3 dedicated IP addresses to 3 devices in one of the VPN services on the Linux server so that the devices always get the same IP address by the VPN service when they connect (pseudo-static IP configuration). As described in the blog post Setting up Dual Stack VPNs, section “Dedicated Configurations”, we can achieve this by creating 3 files with the common names of the devices (gabriel-SM-G991B, gabriel-SM-N960F, gabriel-SM-T580) that were used to create their certificates. I did that for the UDP-based VPN, full tunneling openvpn, and the 3 configuration files are listed here:

caipirinha:~ # cat /etc/openvpn/conf-1194/gabriel-SM-G991B
# Spezielle Konfigurationsdatei für Gabriels Galaxy S20 (gabriel-SM-G991B)
#

ifconfig-push      192.168.10.250  255.255.255.0
ifconfig-ipv6-push fd01:0:0:a:0:0:1:fa/111 fd01:0:0:a::1
caipirinha:~ # cat /etc/openvpn/conf-1194/gabriel-SM-N960F
# Spezielle Konfigurationsdatei für Gabriels Galaxy Note 9 (gabriel-SM-N960F)
#

ifconfig-push      192.168.10.251  255.255.255.0
ifconfig-ipv6-push fd01:0:0:a:0:0:1:fb/111 fd01:0:0:a::1
caipirinha:~ # cat /etc/openvpn/conf-1194/gabriel-SM-T580
# Spezielle Konfigurationsdatei für Gabriels Galaxy Tablet A (gabriel-SM-T580)
#

ifconfig-push      192.168.10.252  255.255.255.0
ifconfig-ipv6-push fd01:0:0:a:0:0:1:fc/111 fd01:0:0:a::1

One can easily identify the respective IPv4 and IPv6 addresses which shall be allocated to the 3 named devices:

  • gabriel-SM-G991B shall get the IPv4 192.168.10.250 and the IPv6 fd01:0:0:a:0:0:1:fa.
  • gabriel-SM-N960F shall get the IPv4 192.168.10.251 and the IPv6 fd01:0:0:a:0:0:1:fb.
  • gabriel-SM-T580 shall get the IPv4 192.168.10.252 and the IPv6 fd01:0:0:a:0:0:1:fc.

Let us not forget that this is the configuration for only one out of the 5 VPN services. If the devices connect to a VPN service different from the UDP-based VPN, full tunneling openvpn, then, these configurations do not have any effect.

OpenVPN Client Configuration

For the experiments below, we will set up 3 client VPN connections to different countries. As I do not have infrastructure outside of Germany, I use a commercial VPN provider, in my case this is PureVPN™ (as I once got an affordable 5-years subscription). Choosing a suitable VPN provider is not easy, and I strongly recommend to research test reports and forums which deal with the configuration on Linux before you choose any subscription to a commercial VPN provider. In my case, the provider (PureVPN™) offers openvpn Linux configuration as a download. I just had to make some modifications as otherwise, the VPN wants to be the default connection for all internet traffic; this is not what we want when we do our own policy routing. I chose the TCP configuration as the UDP configuration, which is normally preferred, did not run in a stable fashion at the time of writing this article. The client configuration files also contain the ca, the certificate, and the key file at the end (not shown here).

TCP-based split VPN to Portugal

# Konfigurationsdatei für den openVPN-Client auf CAIPIRINHA zur Verbindung nach PureVPN (Portugal)

auth-user-pass       /etc/openvpn/purevpn.login
auth-nocache 
auth-retry           nointeract
client
comp-lzo
dev                  tun0
ifconfig-nowarn
key-direction        1
log                  /var/log/openvpn_PT.log
lport                5456
mute                 20
proto                tcp
persist-key
persist-tun
remote               pt2-auto-tcp.ptoserver.com 80
remote-cert-tls      server
route-nopull
script-security      2
status               /var/run/openvpn/status_PT
up                   /etc/openvpn/start_purevpn.sh
down                 /etc/openvpn/stop_purevpn.sh
verb                 3

<ca>
-----BEGIN CERTIFICATE-----
MIIE6DCCA9CgAwIBAgIJAMjXFoeo5uSlMA0GCSqGSIb3DQEBCwUAMIGoMQswCQYD
...
4ZjTr9nMn6WdAHU2
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
MIIEnzCCA4egAwIBAgIBAzANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMCSEsx
...
21oww875KisnYdWjHB1FiI+VzQ1/gyoDsL5kPTJVuu2CoG8=
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMbJ8p+L+scQz57g
...
d7q7xhec5WHlng==
-----END PRIVATE KEY-----
</key>
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
e30af995f56d07426d9ba1f824730521
...
dd94498b4d7133d3729dd214a16b27fb
-----END OpenVPN Static key V1-----
</tls-auth>

TCP-based split VPN to Singapore

# Konfigurationsdatei für den openVPN-Client auf CAIPIRINHA zur Verbindung nach PureVPN (Singapur)

auth-user-pass       /etc/openvpn/purevpn.login
auth-nocache
auth-retry           nointeract
client
comp-lzo
dev                  tun1
ifconfig-nowarn
key-direction        1
log                  /var/log/openvpn_SG.log
lport                5457
mute                 20
proto                tcp
persist-key
persist-tun
remote               sg2-auto-tcp.ptoserver.com 80
remote-cert-tls      server
route-nopull
script-security      2
status               /var/run/openvpn/status_SG
up                   /etc/openvpn/start_purevpn.sh
down                 /etc/openvpn/stop_purevpn.sh
verb                 3
...

TCP-based split VPN to Thailand

# Konfigurationsdatei für den openVPN-Client auf CAIPIRINHA zur Verbindung nach PureVPN (Thailand)

auth-user-pass       /etc/openvpn/purevpn.login
auth-nocache
auth-retry           nointeract
client
comp-lzo
dev                  tun2
ifconfig-nowarn
key-direction        1
log                  /var/log/openvpn_TH.log
lport                5458
mute                 20
proto                tcp
persist-key
persist-tun
remote               th2-auto-tcp.ptoserver.com 80
remote-cert-tls      server
route-nopull
script-security      2
status               /var/run/openvpn/status_TH
up                   /etc/openvpn/start_purevpn.sh
down                 /etc/openvpn/stop_purevpn.sh
verb                 3
...

I stored these configurations in the files:

  • /etc/openvpn/client_PT.conf
  • /etc/openvpn/client_SG.conf
  • /etc/openvpn/client_TH.conf

Let us discuss some configuration items:

  • auth-user-pass refers to the file /etc/openvpn/purevpn.login which contains the login and password for my VPN service. It is referenced here so that I do not have to enter them when I start the connection or when the connection restarts after a breakdown.
  • cipher refers to an algorithm that PureVPN™ uses on their server side.
  • PureVPN™ also uses compression on the VPN connection, and this is turned on by the line comp-lzo.
  • As we want to do policy routing, we need to know which VPN we are dealing with. Therefore, I attribute a dedicated tun device as well as a dedicated lport (source port) to each connection.
  • remote names the server and port given in the downloaded configuration files.
  • route-nopull is very important as otherwise, the default route would be changed. However, for our purposes, we do not want any routes to be changed automatically, we will do that by policy routing later.
  • up and down name a start and a stop script. The start script is executed after the connection has been established, and the stop script is executed when the connection is disbanded. As the scripts use various command, we need to set script-security accordingly.
  • The initial configuration always takes some time, and so I have set verb to “3” in order to have more verbosity in the log file, for debugging purposes.

Let’s now look at the start script /etc/openvpn/start_purevpn.sh. This script depends on the installation of the tool library ipcalc as this library eases some computations.

#!/bin/bash
#
# This script sets the VPN parameters in the routing tables "main", "Portugal", "Singapur" and "Thailand" once the connection has been successfully established.
# This script requires the tool "ipcalc" which needs to be installed on the target system.

# Set the correct PATH environment
PATH='/sbin:/usr/sbin:/bin:/usr/bin'

VPN_DEV=$1
VPN_SRC=$4
VPN_MSK=$5

VPN_GW=$(ipcalc ${VPN_SRC}/${VPN_MSK} | sed -n 's/^HostMin:\s*\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\).*/\1/p')
VPN_NET=$(ipcalc ${VPN_SRC}/${VPN_MSK} | sed -n 's/^Network:\s*\([0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\/[0-9]\{1,2\}\).*/\1/p')

case "${VPN_DEV}" in
  "tun0") ROUTING_TABLE='Portugal';;
  "tun1") ROUTING_TABLE='Singapur';;
  "tun2") ROUTING_TABLE='Thailand';;
esac

iptables -t filter -A INPUT   -i ${VPN_DEV} -m state --state NEW,INVALID -j DROP
iptables -t filter -A FORWARD -i ${VPN_DEV} -m state --state NEW,INVALID -j DROP

ip route add ${VPN_NET} dev ${VPN_DEV} proto kernel scope link src ${VPN_SRC} table ${ROUTING_TABLE}
ip route replace default dev ${VPN_DEV} via ${VPN_GW} table ${ROUTING_TABLE}

What does this script do? It executes these steps:

  • It blocks connections with the state NEW or INVALID in the filter chains INPUT and FORWARD. Later (down in this article), this shall be explained more in detail. For now, it suffices to know that we want to avoid those connections that originate from the commercial VPN network shall be blocked. We must keep in mind that by using commercial VPN connections, we make the Linux server vulnerable to connections that might come from these networks. If everything was correctly configured on the side of the VPN provider, there should never be such a connection that originates from the network because individual VPN users should not be able to “see” each other. There should only be connections that originate from our Linux server, and subsequently, we will get reply packets, of course, and have a bidirectional communication. Nevertheless, my own experience with various VPN providers has shown that there is a certain amount of unrelated stray packets that reach the Linux server, and I want to filter those out.
  • It adds the client network (here, a /27 network) to the respective routing table Portugal, Singapore, or Thailand.
  • It sets the default route in the respective routing table to the VPN endpoint. Ultimately, every routing table gets a default route if all 3 client VPNs are engaged. I use ip route replace rather than ip route add because ip route replace does not throw an error if there is already a default route in the routing table.

Consequently, the script /etc/openvpn/stop_purevpn.sh serves to clean up the entries in the filter table. We do not have to remove the entries in the 3 additional routing tables as they disappear automatically when the VPN connection is disbanded. This script is somewhat smaller:

#!/bin/bash
#
# This script removes some routing table entries when the connection is terminated.

# Set the correct PATH environment
PATH='/sbin:/usr/sbin:/bin:/usr/bin'

VPN_DEV=$1

iptables -t filter -D INPUT   -i ${VPN_DEV} -m state --state NEW,INVALID -j DROP
iptables -t filter -D FORWARD -i ${VPN_DEV} -m state --state NEW,INVALID -j DROP

Now, that we have all these pieces together, we start the 3 client VPNs with the commands:

systemctl start openvpn@client_PT
systemctl start openvpn@client_SG
systemctl start openvpn@client_TH

After some seconds, the 3 client VPN connections should have fully been set up, and the respective network devices tun0, tun1, tun2 should exist. Similar to what was described in the blog post Setting up Dual Stack VPNs, we must configure network address translation for the 3 client VPNs so that outgoing packets get modified in a way that they have the source IP address of the Linux server for the specific interface over which those packets shall travel. That is done with:

iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -t nat -A POSTROUTING -o tun1 -j MASQUERADE
iptables -t nat -A POSTROUTING -o tun2 -j MASQUERADE

We use MASQUERADE in this case because the IP address of the Linux server can change at each VPN connection, and we do not know the source address beforehand. Otherwise SNAT would be the better option that consumes less CPU power.

Now, we should be able to ping a machine (in this example, Google‘s DNS) via each of the 3 client VPN connections, as shown here:

caipirinha:~ # ping -c 3 -I tun0 8.8.8.8
PING 8.8.8.8 (8.8.8.8) from 172.17.66.34 tun0: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=119 time=57.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=119 time=54.5 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=119 time=54.7 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 54.516/55.649/57.727/1.483 ms
caipirinha:~ # ping -c 3 -I tun1 8.8.8.8
PING 8.8.8.8 (8.8.8.8) from 10.12.42.41 tun1: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=58 time=249 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=58 time=247 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=58 time=247 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 247.120/247.972/249.111/1.015 ms
caipirinha:~ # ping -c 3 -I tun2 8.8.8.8
PING 8.8.8.8 (8.8.8.8) from 10.31.6.38 tun2: 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=13.9 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=117 time=14.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=117 time=22.6 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 13.910/16.934/22.641/4.039 ms
caipirinha:~ # traceroute -i eth0 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  Router-EZ (192.168.2.1)  3.039 ms  2.962 ms  2.927 ms
 2  fra1813aihr002.versatel.de (62.214.63.145)  15.440 ms  16.978 ms  18.866 ms
 3  62.72.71.113 (62.72.71.113)  16.116 ms  19.534 ms  19.506 ms
 4  89.246.109.249 (89.246.109.249)  24.717 ms  25.460 ms  24.659 ms
 5  72.14.204.148 (72.14.204.148)  20.530 ms  20.602 ms 89.246.109.250 (89.246.109.250)  24.573 ms
 6  * * *
 7  dns.google (8.8.8.8)  20.265 ms  16.966 ms  14.751 ms
caipirinha:~ # traceroute -i tun0 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.96.10.33 (10.96.10.33)  50.579 ms  101.574 ms  102.216 ms
 2  91.205.230.65 (91.205.230.65)  121.175 ms  121.171 ms  151.320 ms
 3  cr1.lis1.edgoo.net (193.163.151.1)  102.156 ms  102.155 ms  102.150 ms
 4  Google.AS15169.gigapix.pt (193.136.250.20)  102.145 ms  103.099 ms  103.145 ms
 5  74.125.245.100 (74.125.245.100)  103.166 ms 74.125.245.118 (74.125.245.118)  103.156 ms 74.125.245.117 (74.125.245.117)  103.071 ms
 6  142.250.237.83 (142.250.237.83)  120.681 ms 142.250.237.29 (142.250.237.29)  149.742 ms 142.251.55.151 (142.251.55.151)  110.302 ms
 7  74.125.242.161 (74.125.242.161)  108.651 ms 108.170.253.241 (108.170.253.241)  108.594 ms 108.170.235.178 (108.170.235.178)  108.426 ms
 8  74.125.242.161 (74.125.242.161)  108.450 ms  108.429 ms 142.250.239.27 (142.250.239.27)  107.505 ms
 9  142.251.54.149 (142.251.54.149)  107.406 ms 142.251.60.115 (142.251.60.115)  108.446 ms 142.251.54.151 (142.251.54.151)  157.613 ms
10  dns.google (8.8.8.8)  107.380 ms  89.640 ms  73.506 ms
caipirinha:~ # traceroute -i tun1 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.12.34.1 (10.12.34.1)  295.583 ms  561.820 ms  625.195 ms
 2  146.70.67.65 (146.70.67.65)  687.601 ms  793.792 ms  825.806 ms
 3  193.27.15.178 (193.27.15.178)  1130.988 ms  1198.522 ms  1260.560 ms
 4  37.120.220.218 (37.120.220.218)  1383.152 ms 37.120.220.230 (37.120.220.230)  825.525 ms 37.120.220.218 (37.120.220.218)  925.081 ms
 5  103.231.152.50 (103.231.152.50)  1061.923 ms  1061.945 ms 15169.sgw.equinix.com (27.111.228.150)  993.095 ms
 6  108.170.240.225 (108.170.240.225)  1320.654 ms 74.125.242.33 (74.125.242.33)  1164.303 ms 108.170.254.225 (108.170.254.225)  1008.590 ms
 7  74.125.251.205 (74.125.251.205)  1009.043 ms 74.125.251.207 (74.125.251.207)  993.251 ms 142.251.49.191 (142.251.49.191)  969.879 ms
 8  dns.google (8.8.8.8)  1001.502 ms  1065.558 ms  1073.731 ms
caipirinha:~ # traceroute -i tun2 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
 1  10.31.3.33 (10.31.3.33)  189.134 ms  399.914 ms  399.941 ms
 2  * * *
 3  * * *
 4  * * *
 5  198.84.50.182.static-corp.jastel.co.th (182.50.84.198)  411.679 ms  411.729 ms  411.662 ms
 6  72.14.222.138 (72.14.222.138)  433.761 ms 74.125.48.212 (74.125.48.212)  444.509 ms 72.14.223.80 (72.14.223.80)  444.554 ms
 7  108.170.250.17 (108.170.250.17)  647.768 ms * 108.170.249.225 (108.170.249.225)  439.883 ms
 8  142.250.62.59 (142.250.62.59)  635.318 ms 142.251.224.15 (142.251.224.15)  417.842 ms dns.google (8.8.8.8)  600.600 ms

A traceroute to Google‘s DNS via the 3 client VPN connections shows us the route the packets travel; the first example shows the route via the default connection (eth0):

Finally, we look at the routing tables that have changed after we have established the 3 client VPN connections:

caipirinha:~ # ip route list table main
default via 192.168.2.1 dev eth0 proto dhcp
10.12.42.32/27 dev tun1 proto kernel scope link src 10.12.42.41
10.31.6.32/27 dev tun2 proto kernel scope link src 10.31.6.38
172.17.66.32/27 dev tun0 proto kernel scope link src 172.17.66.34
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 dev tun4 proto kernel scope link src 192.168.10.1
192.168.11.0/24 dev tun5 proto kernel scope link src 192.168.11.1
192.168.12.0/24 dev tun6 proto kernel scope link src 192.168.12.1
192.168.13.0/24 dev tun7 proto kernel scope link src 192.168.13.1
192.168.14.0/24 dev wg0 proto kernel scope link src 192.168.14.1
caipirinha:~ # ip route list table Portugal
default via 172.17.66.33 dev tun0
172.17.66.32/27 dev tun0 proto kernel scope link src 172.17.66.34
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0
caipirinha:~ # ip route list table Singapur
default via 10.12.42.33 dev tun1
10.12.42.32/27 dev tun1 proto kernel scope link src 10.12.42.41
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0
caipirinha:~ # ip route list table Thailand
default via 10.31.6.33 dev tun2
10.31.6.32/27 dev tun2 proto kernel scope link src 10.31.6.38
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.3
192.168.10.0/24 via 192.168.10.1 dev tun4
192.168.11.0/24 via 192.168.11.1 dev tun5
192.168.12.0/24 via 192.168.12.1 dev tun6
192.168.13.0/24 via 192.168.13.1 dev tun7
192.168.14.0/24 via 192.168.14.1 dev wg0

In the routing tables, we can observe the following new items:

  • Each client VPN connection has added a /27 network to the routing table main.
  • The script /etc/openvpn/start_purevpn.sh has added the /27 networks to the corresponding routing tables Portugal, Singapur, Thailand so that each routing table only has the /27 network of the connection that leads to the corresponding destination.
  • The script /etc/openvpn/start_purevpn.sh has also modified the default route of each of the routing tables Portugal, Singapur, Thailand so that each routing table has the default route of the connection that leads to the corresponding destination.

Routing Policies

Now, we are all set to define routing policies and do our first steps in the field of policy routing.

Simple Policy Routing

In the first example, we will “place” each device (gabriel-SM-G991B, gabriel-SM-N960F, gabriel-SM-T580) in a different country. Let us recall that, when each of these devices connects to the Linux server via the UDP-based, full tunneling openvpn, then each device gets a defined IP address. This allows us to define routing policies based on the IP address [11]. In order to modify the routing policy database of the Linux server, we enter the commands:

ip rule add from 192.168.10.250/32 table Portugal priority 2000
ip rule add from 192.168.10.251/32 table Singapur priority 2000
ip rule add from 192.168.10.252/32 table Thailand priority 2000

The resulting routing policy database looks like this:

caipirinha:~ # ip rule list
0:      from all lookup local
2000:   from 192.168.10.250 lookup Portugal
2000:   from 192.168.10.251 lookup Singapur
2000:   from 192.168.10.252 lookup Thailand
32766:  from all lookup main
32767:  from all lookup default

The number at the beginning of each line in the routing policy database is the priority; this allows us to define routing policies in a defined order. As soon as the selector of a rule matches the a packet, the corresponding action is executed, and no further rules are checked for this packet. [11] lists the possible selectors and actions, and we can see that there are a lot of possibilities, especially when we combine different matching criteria. In the case shown here, our rules tell the Linux server the following:

  • Packets with the source IP 192.168.10.250 (device gabriel-SM-G991B) shall be processed in the routing table Portugal.
  • Packets with the source IP 192.168.10.251 (device gabriel-SM-N960F) shall be processed in the routing table Singapur.
  • Packets with the source IP 192.168.10.252 (device gabriel-SM-T580) shall be processed in the routing table Thailand.

An important rule is the one with the priority 32766; this one tells all packets to use the routing table main. This rule has a very low priority because we want to enable administrators to create many other rules with higher priority that match packets and that are subsequently dealt with in a special way. The rules with the priorities 0, 32766, 32767 are already in the system by default.

When we place the 3 devices gabriel-SM-G991B, gabriel-SM-N960F, and gabriel-SM-T580 outside the home network, either in a different WiFi network or in a mobile network and connect to the Linux server via the VPN services, then, because of the routing policy defined above, the devices will appear in:

  • Portugal (gabriel-SM-G991B)
  • Singapore (gabriel-SM-N960F)
  • Thailand (gabriel-SM-T580)

We can test this with one of the websites that display IP geolocation, for example [13], and the result will look like this:

We must keep in mind that this kind of routing policy routes all outgoing traffic from the 3 devices to the respective countries, irrespective whether this is web or email or any other traffic. This is true for any protocol, and so, a traceroute to Google‘s DNS (8.8.8.8) will really go via the respective country. The images below compare the device gabriel-SM-N960F without VPN (4G mobile network) and with the VPN to the Linux server which then routes the connection via Singapore. One can easily recognize the much higher latency via Singapore. The traceroutes were taken with [14].

Policy Routing with Firewall Marking

While the ip-rule command [11] already offers a lot of possible combinations for the selection of packets, sometimes, one needs more elaborate selection criteria. This is when we use policy routing using firewall marking and the mangle table [15]. We first delete our rule set from above with the sequence:

ip rule del from 192.168.10.250/32 table Portugal priority 2000
ip rule del from 192.168.10.251/32 table Singapur priority 2000
ip rule del from 192.168.10.252/32 table Thailand priority 2000

Then, we enter new rules. Instead of using IP addresses in the selector, we use a so-called “firewall mark” (fwmark). We tell the Linux server to process packets that have a special mark in the routing tables mentioned in the action field of ip-rule:

ip rule add from all fwmark 0x1 priority 5000 lookup Portugal
ip rule add from all fwmark 0x2 priority 5000 lookup Singapur
ip rule add from all fwmark 0x3 priority 5000 lookup Thailand

But how do we mark packets? This is done in the mangle table, one of the 4 tables of the iptables [12] command. With command listed below we specify the marking of TCP packets originating from the listed IP address and going to the destination ports 80 (http) and 443 (https). All other traffic from the device with the listed IP address (e.g., smtp, imap, UDP, ICMP, …) will not be marked.

iptables -t mangle -F
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j ACCEPT
iptables -t mangle -A PREROUTING -s 192.168.10.250/32 -p tcp -m multiport --dports 80,443 -m state --state NEW,RELATED -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -s 192.168.10.251/32 -p tcp -m multiport --dports 80,443 -m state --state NEW,RELATED -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -s 192.168.10.252/32 -p tcp -m multiport --dports 80,443 -m state --state NEW,RELATED -j MARK --set-mark 3
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark

Let us have a closer look at the 7 iptables commands:

  1. This command flushes all chains of the mangle table so that the mangle table is empty.
  2. This command restores the marks of packets. Here, one must know that the mark of a packet is not stored in the packet itself, as the IP header does not contain a field for such a mark. Rather than that, the Linux Kernel keeps track of the mark and the packet it belongs to. However, when the Linux server sends out a packet to its destination, and the computer at the destination (e.g., a web server) answers with his own packets, then when these packets arrive at our Linux server, we want to mark them, too, because they belong to a data connection whose packets were initially marked and we might need the mark in order to process them correctly. Therefore, we “restore” the mark in the PREROUTING chain of the mangle table.
  3. This command accepts all packets that have a non-zero mark. I am not really sure if that command is needed at all (should be tested).
  4. This command sets the mark “1” to those packets that fulfil all these requirements:
    1. It comes from the source IP address 192.168.10.250.
    2. It uses TCP.
    3. It goes to one of the destination ports 80 (http) or 443 (https).
    4. It constitutes a NEW or RELATED connection.
  5. This command sets the mark “2” to those packets that fulfil all these requirements:
    1. It comes from the source IP address 192.168.10.251.
    2. It uses TCP.
    3. It goes to one of the destination ports 80 (http) or 443 (https).
    4. It constitutes a NEW or RELATED connection.
  6. This command sets the mark “3” to those packets that fulfil all these requirements:
    1. It comes from the source IP address 192.168.10.252.
    2. It uses TCP.
    3. It goes to one of the destination ports 80 (http) or 443 (https).
    4. It constitutes a NEW or RELATED connection.
  7. This command stores in the mark of the packets in the connection tracking table.

After we have entered these commands, the mangle table should look somewhat like this:

caipirinha:/etc/openvpn # iptables -t mangle -L -n -v
Chain PREROUTING (policy ACCEPT 210K packets, 106M bytes)
 pkts bytes target     prot opt in     out     source               destination
 953K  384M CONNMARK   all  --  *      *       0.0.0.0/0            0.0.0.0/0            CONNMARK restore
 177K   88M ACCEPT     all  --  *      *       0.0.0.0/0            0.0.0.0/0            mark match ! 0x0
  989 76505 MARK       tcp  --  *      *       192.168.10.250       0.0.0.0/0            multiport dports 80,443 state NEW,RELATED MARK set 0x1
 1233 82791 MARK       tcp  --  *      *       192.168.10.251       0.0.0.0/0            multiport dports 80,443 state NEW,RELATED MARK set 0x2
 1017 72624 MARK       tcp  --  *      *       192.168.10.252       0.0.0.0/0            multiport dports 80,443 state NEW,RELATED MARK set 0x3
 776K  296M CONNMARK   all  --  *      *       0.0.0.0/0            0.0.0.0/0            CONNMARK save

Chain INPUT (policy ACCEPT 203K packets, 104M bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 137K packets, 69M bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 199K packets, 107M bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain POSTROUTING (policy ACCEPT 335K packets, 176M bytes)
 pkts bytes target     prot opt in     out     source               destination

The values in the columns pkts, bytes will most probably be different in your case. They show how many IP packets and bytes have matched this rule and can help in controlling or debugging the configuration and the traffic flows.

The entries in the mangle table consequently mark the packets that traverse our Linux router and that are from one of the devices gabriel-SM-G991B, gabriel-SM-N960F, or gabriel-SM-T580 and that are destined to a web server (TCP ports 80, 443) with either the fwmark “1”, “2”, or “3”. Based on this fwmark, the packets are then sent to the routing tables Portugal, Singapur or Thailand. Using both the routing policy database and the mangle table is a powerful instrument for selecting packets and connections that shall be routed in a special way which gives us a lot of flexibility.

For example, if we only had one device to be considered (Gabriel-SM-G991B), the two command that we have issues before:

ip rule add from all fwmark 0x1 priority 5000 lookup Portugal
iptables -t mangle -A PREROUTING -s 192.168.10.250/32 -p tcp -m multiport --dports 80,443 -m state --state NEW,RELATED -j MARK --set-mark 1

have the same effect as these two commands:

ip rule add from 192.168.10.250/32 fwmark 0x1 priority 5000 lookup Portugal
iptables -t mangle -A PREROUTING -p tcp -m multiport --dports 80,443 -m state --state NEW,RELATED -j MARK --set-mark 1

In the first case, the source address selection is done in the iptables command, in the second case it is done in the ip rule command. With the 3 devices that have, the second way is not a solution as it would create the fwmark “1” on all packets that go to a web server and the subsequent entries in the mangle table would not be executed any more. We therefore have to create rules with extreme caution, in order not to jeopardize our intended routing behavior. I therefore recommend being as specific as possible already in the iptables command, also in order to avoid excessive packet marking as this complicates your life when you have to debug your setup.

When we have the setup described in this chapter active, the 3 devices (gabriel-SM-G991B, gabriel-SM-N960F, gabriel-SM-T580) will appear to be in the respective countries, similar to the setup in the previous chapter where we only modified the routing policy database. We can test this with one of the websites that display IP geolocation, for example [13], and the result will look like this:

However, if we execute a traceroute command on one of the devices, the traceroute does not go via the respective country because it uses the protocol ICMP rather than TCP and is therefore not marked and consequently is not routed via any of the client VPNs. This can be seen in the rightest image in the following gallery where a traceroute to Google‘s DNS (8.8.8.8) has been made. The traceroutes were taken with [14].

Connection Tracking

In the previous chapter, the iptables statements for the mangle table apply to NEW or RELATED connections only. Let us therefore look into the concept of connection tracking [4], [21]. This is achieved by the netfilter component [16] which is linked to the Linux Kernel keeps track of stateful connections in the connection tracking table, very similar to a stateful firewall [17]. Important states are [4]:

  • NEW: The packet does not belong to an existing connection.
  • ESTABLISHED: The packet belongs to an “established” connection. A connection is changed from NEW to ESTABLISHED when it receives a valid response in the opposite direction.
  • RELATED: Packets that are not part of an existing connection but are associated with a connection already in the system are labeled RELATED. An example are ftp connections which open connections adjacent to the initial one [18].
  • INVALID: Packets can be marked INVALID if they are not associated with an existing connection and are not appropriate for opening a new connection.

The mighty conntrack toolset [19] contains the command conntrack [20] which can be used to see the connection tracking table (actually, there are also different tables) and to inspect various behaviors around the connection tracking table. We can, for example, examine which connections have the fwmark “2” set, that is, which connections have been set up by the device Gabriel-Tablet using the source IP address 192.168.10.251:

caipirinha:/etc/openvpn # conntrack -L -m2
tcp      6 431952 ESTABLISHED src=192.168.10.251 dst=172.217.19.74 sport=38330 dport=443 src=172.217.19.74 dst=10.12.42.37 sport=443 dport=38330 [ASSURED] mark=2 use=1
tcp      6 431832 ESTABLISHED src=192.168.10.251 dst=34.107.165.5 sport=38924 dport=443 src=34.107.165.5 dst=10.12.42.37 sport=443 dport=38924 [ASSURED] mark=2 use=1
tcp      6 431955 ESTABLISHED src=192.168.10.251 dst=142.250.186.170 sport=58652 dport=443 src=142.250.186.170 dst=10.12.42.37 sport=443 dport=58652 [ASSURED] mark=2 use=1
tcp      6 65 TIME_WAIT src=192.168.10.251 dst=142.250.185.66 sport=57856 dport=443 src=142.250.185.66 dst=10.12.42.37 sport=443 dport=57856 [ASSURED] mark=2 use=1
tcp      6 431954 ESTABLISHED src=192.168.10.251 dst=142.250.186.170 sport=58640 dport=443 src=142.250.186.170 dst=10.12.42.37 sport=443 dport=58640 [ASSURED] mark=2 use=1
conntrack v1.4.5 (conntrack-tools): 5 flow entries have been shown.

Each line contains a whole set of information which is explained in detail in [2], § 7. For a quick orientation, we have to know the following points:

  • Each line has two parts. The first part lists the IP header information of the newly initiated connection; in our case, we observe:
    • The source IP address is always 192.168.10.251 (Gabriel-Tablet).
    • The destination port is either 80 or 443 as we change the routing for exactly these destination ports only
  • The second part lists the IP header information of the expected or received (as an answer) packets, and, we observe:
    • The source IP address of the answering packet is the destination IP address of the initiating packet which makes sense.
    • The source port of the answering packet is the destination port of the initiating packet which again makes sense.
    • The destination IP address of the answering packet is not 192.168.10.251, but 10.12.42.37. This is because 10.12.42.37 is the IP address of the tun1 device of the Linux server. When we send out the initiating packet from 192.168.10.251, the packet will go to the Linux server who acts as router. In the server, the packet will be changed, and the server will use its source address on the outgoing interface tun1 as source address on the packet as the remote end point of the client VPN connection that we use would not know how to route a packet to 192.168.10.251 (the remote end point does not know anything of the network 192.168.10.0/24 on our side).
    • [ASSURED] means that the connection has already seen traffic in both directions, the connections has therefore been set up successfully.
  • Coincidentally, in our example, we only have TCP connections; however, the connection tracking table can also comprise UDP or ICMP connections.

The command conntrack [20] offers much more opportunities and even allows to change entries in the connection tracking table. So, we barely scratched the surface.

Conclusion

In this blog post, we have used client VPN connections to execute some experiments on policy routing in which we make different devices appear to be located in different countries. We touched the concepts of routing tables, the routing policy database, the mangle table, and the connection tracking table. The possibilities of all these items go far beyond of what we discussed in this blog post. The interested reader is referred to the sources listed below to get in-depth knowledge and to understand the vast possibilities that these items offer to the expert.

Sources

Setting up Dual Stack VPNs

Executive Summary

This blog post explains how I set up dual stack (IPv4, IPv6) virtual private networks (VPN) with the open-source packages openvpn and WireGuard on my Linux server caipirinha.spdns.org. Clients (smartphones, tablets, notebooks) which connect to my server will be supplied with a dual stack VPN connection and can therefore use both IPv4 as well as IPv6 via the Linux server to the internet.

Background

The implementation was originally intended to help a friend who lived in China and who struggled with his commercial VPN that only tunneled IPv4 and did not block IPv6. He often experienced blockages when he tried to access social media sites as his system would prefer IPv6 over IPv4 and so the connection would not run through his VPN. However, due to [6], openvpn alone is no longer suited to circumvent censorship in China [7]. WireGuard might still work in geographies where openvpn is blocked, however.

Preconditions

In order to use the approach described here, you should:

  • … have a full dual stack internet connection (IPv4, IPv6)
  • … have access to a Linux machine which is already properly configured for dual stack on its principal network interface (e.g., eth0)
  • … have the Linux machine set up as a router
  • … have the package openvpn and/or WireGuard installed (preferably from a repository of your Linux distribution)
  • … know how to create client and server certificates for openvpn [11] and/or WireGuard [12], [13], [19] although this blog post will also contain a short description on how to create a set of certificates and keys for openvpn
  • … have knowledge of routing concepts, networks, some understanding of shell scripts and configuration files
  • … know related system commands like sysctl

Description and Usage

The graph below shows the setup on my machine caipirinha.spdns.org with 5 VPN services (blue, green color) that will be described in this blog post. The machine has also 3 VPN clients configured which are mapped to a commercial service (ocker color), but this will not be topic of this blog post.

Configuration of VPN Services on caipirinha.spdns.org

Home Network Setup

Let us now look at some details of the network setup:

  • The Linux server is not connected to the internet directly, but it is connected to a small SoHo router which acts as basic firewall and forwards a selection of ports and protocols to the Linux server.
  • The internal IPv4 network which is setup by the SoHo router is 192.168.2.0/24.
  • The internal IPv6 network which is setup by the SoHo router is fd00:0:0:2/64; this is configured in the respective menu of the SoHo router as shown below and is within the IPv6 unique local address space [1], [2]. I decided to use an IPv6 with a “2” in the network address like the “2” in the IPv4 network.
  • The Linux server also gets a public IPv6 address allocated (like all other devices in my home network); this is accomplished by the SoHo router that has IPv6 enabled.
Configuration of the internal IPv6 network

When everything has been set up correctly, the Linux server should get various IP addresses, and among them various IPv6 addresses:

  • a “real” and routable one starting with numbers in the range from “2000:” until “3fff:”.
  • a SLAAC [3] one starting with “fe80:”
  • a “private” one starting with “fd00::2:”

An example is shown here:

caipirinha:~ # ifconfig eth0
eth0: flags=4163 mtu 1500
inet 192.168.2.3 netmask 255.255.255.0 broadcast 192.168.2.255
inet6 2001:16b8:306c:c700:76d4:35ff:fe5c:d2c3 prefixlen 64 scopeid 0x0
inet6 fe80::76d4:35ff:fe5c:d2c3 prefixlen 64 scopeid 0x20
inet6 fd00::2:76d4:35ff:fe5c:d2c3 prefixlen 64 scopeid 0x0
...

Enabling Routing

Routing for IPv4 and IPv6 needs to be enabled on the Linux server. I personally also decided to switch off the privacy extensions on the Linux server, but that is a personal matter of taste:

# Enable "loose" reverse path filtering and prohibit icmp redirects
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.all.send_redirects=0
sysctl -w net.ipv4.conf.eth0.send_redirects=0
sysctl -w net.ipv4.icmp_errors_use_inbound_ifaddr=1

# Enable IPv6 routing, but keep SLAAC for eth0
sysctl -w net.ipv6.conf.eth0.accept_ra=2
sysctl -w net.ipv6.conf.all.forwarding=1

# Switch off the privacy extensions
sysctl -w net.ipv6.conf.eth0.use_tempaddr=0

OpenVPN Key Management with Easy-RSA

For the openVPN server and clients, we need a certification authority and we ultimately need to create signed certificates and keys. This can be done with the help of the package easy-rsa that is available for various platforms [22] and often is part of Linux distributions, too. Documentation and hands-on examples are given in [20] and [21].

We start with the initialization of a Public Key Infrastructure (PKI) and the creation of a Certificate Authority (CA) followed by the creation of a Certificate Revocation List (CRL)

easyrsa init-pki
easyrsa build-ca nopass
easyrsa gen-crl

The next step is the creation of a key pair for the server. The public key will be signed by the CA and thus become our server certificate. Furthermore, we create Diffie-Hellman parameters for the server (not needed if you create elliptic keys). All this can be done by:

easyrsa --days=3652 build-server-full caipirinha.spdns.org nopass
easyrsa gen-dh

In this example, the server certificate is valid for some 3652 days (10 years), the certificate is named caipirinha.spdns.org.crt, and the private key which must remain on the server is named caipirinha.spdns.org.key.

Now, we can create client certificates in a similar way. In the example, the client certificates will have a validity of 5 years only:

easyrsa --days=1825 build-client-full gabriel-SM-G991B nopass
easyrsa --days=1825 build-client-full gabriel-SM-N960F nopass
easyrsa --days=1825 build-client-full gabriel-SM-N915FY nopass
easyrsa --days=1825 build-client-full gabriel-SM-T580 nopass
...

I chose not to use passwords for the private key in order to facilitate the handling. Furthermore, I went for the easy way and created all certificates and keys on one system only. If you intend to deploy a professional solution, you have to keep cyber-security in mind and you may therefore want to exercise more caution and separate the certificate authority on a secured system from the creation of server and client key pairs as it has been advised in [20].

OpenVPN Server Configuration

Before we go into details of the configuration, we must distinguish 3 concepts of VPNs:

  • A (full) tunneling VPN tunnels all connections through the VPN, once the VPN connection has been established. This offers possibilities, but also has implications:
    • The VPN client appears to be in the geographic location of the VPN server unless the server itself tunnels through more nodes. This can be useful to circumvent censorship in the geography where the VPN client is located as all connections from the client to services in the internet are channeled through the VPN server.
    • In a complex multi-level server setup, it can make the client appear in different countries, depending on which destination the client is trying to access. A VPN client might, for example, be in Angola, but connect to a VPN server in Germany which itself has VPN connections to Brazil and to Portugal. If the VPN server is configured accordingly, the VPN client in Angola may appear as being in Portugal when accessing Portuguese web sites and might appear as being located in Brazil when accessing Brazilian web sites and might appear as being located in Germany for everything else.
    • The VPN server can implement filtering services like filtering out ad servers or doing virus scans of downloads.
    • The VPN server can implement access restrictions; companies use this sometimes to disallow clients to access web sites which they deem to be related to “sex, hate, crime, gambling, …”.
  • A split tunneling VPN tunnels only connections to certain networks between the VPN client and the VPN server while all other connections from the VPN client access the internet through the local provider. The typical usage scenario is not related to censorship, but to dedicated resources to which the VPN server grants access (e.g., network shares aka “samba”, proxy services, etc.) that shall be accessed from the VPN client while the latter is not physically connected to the home or company network.
  • An inverse split tunneling VPN tunnels almost all connections, with a few exceptions. This concept is often used in companies which want basically all connections to run through their infrastructure so that they can execute virus scans and access restrictions, but which have (correctly) realized that bandwidth-intensive operations like cloud access, access to video conferencing services, etc. should be taken off the VPN tunnel as their performance is deteriorated otherwise.

The following configurations will create 4 different VPNs based on 2 concepts above.

  • UDP-based VPN, full tunneling: This is the preferred VPN when all connections shall be tunneled.
  • UDP-based VPN, split tunneling: This is the preferred VPN when you want to blend in resources from your home network.
  • TCP-based VPN, full tunneling: TCP can be used when the connection quality to the VPN server is unstable or when UDP is blocked by some gateway in between.
  • TCP-based VPN, split tunneling: This is the preferred VPN when you want to blend in resources from your home network, but when the connection quality to the VPN server is unstable or when UDP is blocked by some gateway in between.

UDP-based VPN, full tunneling

# Konfigurationsdatei für den openVPN-Server auf CAIPIRINHA (UDP:1194)

ca                    /root/pki/ca.crt
cert                  /etc/openvpn/caipirinha.spdns.org.crt
client-config-dir     /etc/openvpn/conf-1194
crl-verify            /root/pki/crl.pem
dev                   tun4
dh                    /root/pki/dh.pem
hand-window           90
ifconfig              192.168.10.1  255.255.255.0
ifconfig-pool         192.168.10.2  192.168.10.239  255.255.255.0
ifconfig-ipv6         fd01:0:0:a::1 fd00::2:3681:c4ff:fecb:5780
ifconfig-ipv6-pool    fd01:0:0:a::2/112
ifconfig-pool-persist /etc/openvpn/ip-pool-1194.txt
keepalive             20 80
key                   /etc/openvpn/caipirinha.spdns.org.key
log                   /var/log/openvpn-1194.log
mode                  server
persist-key
persist-tun
port                  1194
proto                 udp6
reneg-sec             86400
script-security       2
status                /var/run/openvpn/status-1194
tls-server
topology              subnet
up                    /etc/openvpn/start_vpn.sh
verb                  1
writepid              /var/run/openvpn/server-1194.pid

# Topologie des VPN und Default-Gateway
push "topology subnet"
push "route-gateway 192.168.10.1"
push "redirect-gateway def1 bypass-dhcp"
push "tun-ipv6"
push "route-ipv6 2000::/3"
# DNS-Server
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"

UDP-based VPN, split tunneling

# Konfigurationsdatei für den openVPN-Server auf CAIPIRINHA (UDP:4396)

ca                    /root/pki/ca.crt
cert                  /etc/openvpn/caipirinha.spdns.org.crt
client-config-dir     /etc/openvpn/conf-4396
crl-verify            /root/pki/crl.pem
dev                   tun7
dh                    /root/pki/dh.pem
hand-window           90
ifconfig              192.168.13.1  255.255.255.0
ifconfig-pool         192.168.13.2  192.168.13.239  255.255.255.0
ifconfig-ipv6         fd01:0:0:d::1 fd00::2:3681:c4ff:fecb:5780
ifconfig-ipv6-pool    fd01:0:0:d::2/112
ifconfig-pool-persist /etc/openvpn/ip-pool-4396.txt
keepalive             20 80
key                   /etc/openvpn/caipirinha.spdns.org.key
log                   /var/log/openvpn-4396.log
mode                  server
persist-key
persist-tun
port                  4396
proto                 udp6
reneg-sec             86400
script-security       2
status                /var/run/openvpn/status-4396
tls-server
topology              subnet
up                    /etc/openvpn/start_vpn.sh
verb                  1
writepid              /var/run/openvpn/server-4396.pid

# Topologie des VPN und Default-Gateway
push "topology subnet"
push "route-gateway 192.168.13.1"
push "tun-ipv6"
push "route-ipv6 2000::/3"
# Routen zum internen Netzwerk setzen
push "route 192.168.2.0 255.255.255.0"

TCP-based VPN, full tunneling

# Konfigurationsdatei für den openVPN-Server auf CAIPIRINHA  (TCP:8080)

ca                    /root/pki/ca.crt
cert                  /etc/openvpn/caipirinha.spdns.org.crt
client-config-dir     /etc/openvpn/conf-8080
crl-verify            /root/pki/crl.pem
dev                   tun5
dh                    /root/pki/dh.pem
hand-window           90
ifconfig              192.168.11.1  255.255.255.0
ifconfig-pool         192.168.11.2  192.168.11.239  255.255.255.0
ifconfig-ipv6         fd01:0:0:b::1 fd00::2:3681:c4ff:fecb:5780
ifconfig-ipv6-pool    fd01:0:0:b::2/112
ifconfig-pool-persist /etc/openvpn/ip-pool-8080.txt
keepalive             20 80
key                   /etc/openvpn/caipirinha.spdns.org.key
log                   /var/log/openvpn-8080.log
mode                  server
persist-key
persist-tun
port                  8080
proto                 tcp6-server
reneg-sec             86400
script-security       2
status                /var/run/openvpn/status-8080
tls-server
topology              subnet
up                    /etc/openvpn/start_vpn.sh
verb                  1
writepid              /var/run/openvpn/server-8080.pid

# Topologie des VPN und Default-Gateway
push "topology subnet"
push "route-gateway 192.168.11.1"
push "redirect-gateway def1 bypass-dhcp"
push "tun-ipv6"
push "route-ipv6 2000::/3"
# DNS-Server
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"

TCP-based VPN, split tunneling

# Konfigurationsdatei für den openVPN-Server auf CAIPIRINHA  (TCP:8081)

ca                    /root/pki/ca.crt
cert                  /etc/openvpn/caipirinha.spdns.org.crt
client-config-dir     /etc/openvpn/conf-8081
crl-verify            /root/pki/crl.pem
dev                   tun6
dh                    /root/pki/dh.pem
hand-window           90
ifconfig              192.168.12.1  255.255.255.0
ifconfig-pool         192.168.12.2  192.168.12.239  255.255.255.0
ifconfig-ipv6         fd01:0:0:c::1 fd00::2:3681:c4ff:fecb:5780
ifconfig-ipv6-pool    fd01:0:0:c::2/112
ifconfig-pool-persist /etc/openvpn/ip-pool-8081.txt
keepalive             20 80
key                   /etc/openvpn/caipirinha.spdns.org.key
log                   /var/log/openvpn-8081.log
mode                  server
persist-key
persist-tun
port                  8081
proto                 tcp6-server
reneg-sec             86400
script-security       2
status                /var/run/openvpn/status-8081
tls-server
topology              subnet
up                    /etc/openvpn/start_vpn.sh
verb                  1
writepid              /var/run/openvpn/server-8081.pid

# Topologie des VPN und Default-Gateway
push "topology subnet"
push "route-gateway 192.168.12.1"
push "tun-ipv6"
push "route-ipv6 fd00:0:0:2::/64"
# Routen zum internen Netzwerk setzen
push "route 192.168.2.0 255.255.255.0"

We shall now look at some configuration parameters and their meaning:

  • cert, key: The location of the server certificate and the server key has to be listed.
  • ca: The location of the certificate authority certificate has to be listed.
  • crl-verify: This point to a certificate revocation list and contains the certificates that once were issued for devices that have been retired meanwhile or for users that only needed a temporary VPN access .
  • dev: This determines the tun device that shall be used for the connection. I recommend using dedicated tun devices for all VPNs rather than having them randomly assigned during start-up.
  • ifconfig, ifconfig-pool: This determines the IPv4 address of the server and the pool from which IPv4 addresses are granted to the clients. I decided to use a different /24 network for each VPN configuration, that is, the networks 192.168.10.0/24, 192.168.11.0/24, 192.168.12.0/24, and 192.168.13.0/24. However, I decided not to use the full IP address range for dynamic allocation as I have some VPN clients (smartphones, notebooks) which get a dedicated client address so that I can easily tweak settings on the Linux server for those clients. These clients have a small, dedicated configuration file in the folder named in client-config-dir. Such a dedicated configuration can be used to allocate the same IP address to a certain VPN client.
  • ifconfig-ipv6: The first parameter is the IPv6 address of the server, and the second IPv6 address is the one of the router to the internet; in that case, I put the SoHo router there (fd00::2:3681:c4ff:fecb:5780), see the image Configuration of the internal IPv6 network.
  • ifconfig-ipv6-pool: This is the pool of IPv6 addresses that are granted to the clients. I follow a similar approach as with the IPv4 networks and set up separate networks for each VPN, that is, the networks fd01:0:0:a::2/112, fd01:0:0:b::2/112, fd01:0:0:c::2/112 and fd01:0:0:d::2/112. Keep in mind that the first address of the IP address pool is the one mentioned here, e.g., fd01:0:0:a:0:0:0:2, as fd01:0:0:a:0:0:0:1 is already used for the server.
  • keepalive: Sets the interval of ping-alive requests and its timeout. This is useful as gateways that are in between the VPN client and the VPN server might keep connections open and port allocations reserved only for some time; subsequently, they might be freed up. Ideally, you want to use the longest time periods possible as shorter periods create unnecessary traffic (and might eat up the data volume of mobile clients).
  • port: While the standard port for openvpn is 1194, with more than one VPN you are better advised to use different, dedicated ports that are not used by other service on your server.
  • proto: This determines the protocol used and is either udp6 or tcp6-server. The “6” in both arguments indicates that the service shall be provided both on the IPv4 as well as on the IPv6 address. Leaving the “6” away only provides the service on the IPv4 address.
  • push “redirect-gateway def1 bypass-dhcp” tells the VPN client to bypass the VPN for DHCP queries. Otherwise, the client machine gets stuck when the DHCP lease on the client side terminates.
  • push “route-ipv6 2000::/3” tells the VPN client machine to use the VPN for all IPv6 addresses that start with “2000::/3”, and those are currently all routable IPv6 addresses [2].
  • push “dhcp-option DNS 8.8.8.8” and push “dhcp-option DNS 8.8.4.4” set Google‘s DNS servers for the VPN client machine and so gives us an excellent and fast service.
  • reneg-sec: The specified 86400 seconds re-negotiate new encryption keys only once per day. For security reasons, a lower time period would be better, but some countries have put in efforts to detect and block encrypted communication, and this detection happens though the key exchange which seems to have a characteristic bit pattern [6]; therefore, a longer period has been set here.

OpenVPN Client Configuration

The generation of client configuration files is explained in [11], and there are numerous guidelines in the internet. Therefore, I just want to give 2 examples and briefly point out some useful considerations.

UDP-based VPN, full tunneling

# Konfigurationsdatei für den openVPN-Client auf ...

client
dev              tun
explicit-exit-notify
hand-window      90
keepalive        10 60
nobind
persist-key
persist-tun
proto            udp
remote           caipirinha.spdns.org 1194
remote-cert-tls  server
reneg-sec        86400
script-security  2
verb             1

<ca>
-----BEGIN CERTIFICATE-----
MIIE2D...NNmlTg=
-----END CERTIFICATE-----
</ca>

<cert>
-----BEGIN CERTIFICATE-----
MIIFJj...nbuzbI=
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
MIIEvA...QcO+Q==
-----END PRIVATE KEY-----
</key>

TCP-based VPN, full tunneling

# Konfigurationsdatei für den openVPN-Client auf ...

client
dev              tun
hand-window      90
keepalive        10 60
nobind
persist-key
persist-tun
proto            tcp
remote           caipirinha.spdns.org 8080
remote-cert-tls  server
reneg-sec        86400
script-security  2
verb             1

<ca>
-----BEGIN CERTIFICATE-----
MIIE2D...NNmlTg=
-----END CERTIFICATE-----
</ca>

<cert>
-----BEGIN CERTIFICATE-----
MIIFJj...nbuzbI=
-----END CERTIFICATE-----
</cert>

<key>
-----BEGIN PRIVATE KEY-----
MIIEvA...QcO+Q==
-----END PRIVATE KEY-----
</key>

The following points proved to be useful for me:

  • Rather than using separate files for the ca, the certificate, and the key of the client, all information can actually be packed into one file which then gives a complete configuration of the client. This eases the installation on mobile clients (smartphones, tablets) as you do not have to consider path names on the target device. For security reasons, the respective code blocks are not printed here.
  • It is possible to use several remote statements. You can refer to different servers (if the client configuration is suitable for the servers) or you can name the very same server with different ports. On my server, I have different ports open which all point to the very same 4 different server processes. The reason is that sometimes, the dedicated openvpn port 1194 is blocked in some geographies, but other ports might still work. In that case, you have to ensure that connections coming to all these ports are mapped back to the port of the server process. And you might include the statement remote-random so that one connection of the ones listed is chosen randomly.
  • On the client, is it normally not necessary to bind the openvpn process to a certain tun device or a certain port, different from the VPN server.

Further Improvements (OpenVPN)

Different network conditions might require tweaking one or the other parameters of the openvpn service. [8], [9], [10] contain some indications, especially with respect to different hardware and network conditions.

Dedicated Configurations

As mentioned above, the client-config-dir directive can be used to refer to a folder that contains configurations for dedicated devices. An example is the file /etc/openvpn/conf-1194/Gabriel-SM960F. It contains the content:

# Spezielle Konfigurationsdatei für Gabriels Galaxy Note 9 (gabriel-SM-N960F)
#

ifconfig-push      192.168.10.251  255.255.255.0
ifconfig-ipv6-push fd01:0:0:a:0:0:1:fb/111 fd01:0:0:a::1

This file makse the device with the client certificate named gabriel-SM-N960F always receive the same IP addresses, namely 192.168.10.251 (IPv4) and fd01:0:0:a:0:0:1:fb (IPv6). The name of the file must exactly match the VPN client’s common name (CN) that was defined when the client certificate was created [5].

WireGuard Server Configuration

WireGuard is a new and promising VPN protocol [15] which is not yet as widespread as openvpn; it may therefore “escape” censorship authorities easier than openvpn which can be detected by statistical analysis [16]. The setup of the server is described in [12], [13], a more complex configuration is described in [19]. A big advantage of WireGuard is also that the code base is very lean, and hence performance on any given platform is higher than with openvpn.

I personally find it unusual that you have to list the clients in the server configuration file rather than just having a general server configuration where any number of allowed clients can connect to. On my Linux server caipirinha.spdns.org, the WireGuard server configuration contains of 3 files:

  • /etc/wireguard/wg_caipirinha_public.key is the public key of the service (the generation is described in [12], [13], [19]).
  • /etc/wireguard/wg_caipirinha_private.key is the private key of the service (the generation is described in [12], [13], [19]).
  • /etc/wireguard/wg0.conf is the configuration file (the network device is named “wg0” on my machine).

Similar to the openvpn configurations described above, I spent a dedicated IPv4 and IPv6 subnet for the WireGuard server, in this case 192.168.14.0/24 and fd01:0:0:e::/64. The configuration file /etc/wireguard/wg0.conf is easy to understand and contains important parameters that shall be discussed below:

[Interface]
Address = 192.168.14.1/24,fd01:0:0:e::1/64
ListenPort = 44576
PrivateKey = SHo...

[Peer]
PublicKey = pjp2PEboXA4RJhVoybXKuicNkz4XDZaW+c9yLtJq1gE=
AllowedIPs = 192.168.14.2/32,fd01:0:0:e::2/128
PersistentKeepalive = 30
...
[Peer]
PublicKey = fcEcFYQ6cOqe7H9L2PvkM78mkKottJLnKwiqp4WO91s=
AllowedIPs = 192.168.14.7/32,fd01:0:0:e::7/128
PersistentKeepalive = 30
...

The section [Interface] describes the server setup:

  • Address lists the server’s IPv4 and IPv6 addresses.
  • ListenPort is the UDP port on which the service will listen.
  • PrivateKey is the private server key (can be read from the file /etc/wireguard/wg_caipirinha_private.key). For security reasons, the key has only been displayed partly here.

Each section [Peer] lists a possible client configuration. If you want to enable 10 clients on your server, you therefore need 10 such sections.

  • PublicKey is the public key of the client.
  • AllowedIPs lists the IPv4 and IPv6 addresses which will be allocated to the client upon connection.
  • PersistentKeepalive configures the time in seconds after which “keep-alive packets” will be exchanged between the server and the client. This helps to keep connection settings on gateways that are in between the VPN client and the VPN server open; often firewalls and routers in between might otherwise delete the connection from their tables. A value of 25…30 is recommended.

WireGuard Client Configuration

WireGuard clients exist for all major operating systems. I would like to show a Windows 10 configuration that I set up on one of my notebooks according to [14].

WireGuard Windows 10 Client Configuration

As we can see, I also named the respective network interface on the client wg0, but you can use any other name, too. The detailed configuration of the only client connection is also easy to understand:

[Interface]
PrivateKey = 2B2...
Address = 192.168.14.7/32, fd01:0:0:e::7/128
DNS = 192.168.14.1, fd01:0:0:e::1

[Peer]
PublicKey = GvgCag5cvRaE18YUkAd+q/NSOb54JYvXhylm1oz8OxI=
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = caipirinha.spdns.org:44576

The section [Interface] describes the client setup:

  • PrivateKey is the private key of the client which is generated in the application itself [14]. For security reasons, the key has only been displayed partly here.
  • Address lists the client’s IPv4 and IPv6 addresses.
  • DNS lists the DNS servers which shall be used when the VPN connection is active. In this case, the Linux server caipirinha.spdns.org has a DNS service running, hence I listed the server IPv4 and IPv6 addresses here. You might also use Google’s DNS with the IPv4 addresses 8.8.8.8, 8.8.4.4, for example. It is important to list the DNS servers as the ones that you were using before the VPN was established (e.g., the router’s IP like 192.168.4.1) might no longer be accessible after the VPN has been established.

The section [Peer] contains information related to the server.

  • PublicKey is the public key of the server which is located on the server in the file /etc/wireguard/wg_caipirinha_public.key.
  • AllowedIPs is set to 0.0.0.0/0, ::/0 on this client which means that all traffic shall be sent via the VPN (fully tunneling VPN). Here, you have the chance to move from a fully tunneling VPN to a split VPN by listing subnets like 192.168.2.0/24, for example.
  • Endpoint lists the server FQDN and the port to which the client shall connect to.

In a similar way, I set up another client on an Android smartphone using the official WireGuard – Apps bei Google Play, following the configuration model at [23].

WireGuard Android Client Configuration

Let’s see how that works out in reality. For the experiment, I am in Brazil and connect to my Linux server caipirinha.spdns.org in Germany with the configurations described above. Once, the connection has been established, I do a traceroute in Windows to www.google.com in IPv4 and IPv6:

C:\Users\Dell>tracert www.google.com

Routenverfolgung zu www.google.com [142.250.185.68]
über maximal 30 Hops:

  1   235 ms   236 ms   236 ms  CAIPIRINHA [192.168.14.1]
  2   241 ms   238 ms   238 ms  Router-EZ [192.168.2.1]
  3   259 ms   248 ms   249 ms  fra1813aihr002.versatel.de [62.214.63.145]
  4   250 ms   249 ms   248 ms  62.214.38.105
  5   249 ms   247 ms   249 ms  72.14.204.149
  6   249 ms   250 ms   251 ms  72.14.204.148
  7   249 ms   249 ms   248 ms  108.170.236.175
  8   251 ms   249 ms   250 ms  142.250.62.151
  9   248 ms   247 ms   247 ms  fra16s48-in-f4.1e100.net [142.250.185.68]

Ablaufverfolgung beendet.

C:\Users\Dell>tracert -6 www.google.com

Routenverfolgung zu www.google.com [2a00:1450:4001:829::2004]
über maximal 30 Hops:

  1   235 ms   235 ms   235 ms  fd01:0:0:e::1
  2   239 ms   239 ms   237 ms  2001:16b8:30b3:f100:3681:c4ff:fecb:5780
  3   249 ms   249 ms   247 ms  2001:1438::62:214:63:145
  4   248 ms   249 ms   247 ms  2001:1438:0:1::4:302
  5   250 ms   248 ms   248 ms  2001:1438:1:1001::1
  6   250 ms   249 ms   248 ms  2001:1438:1:1001::2
  7   252 ms   250 ms   249 ms  2a00:1450:8163::1
  8   250 ms   249 ms   250 ms  2001:4860:0:1::5894
  9   248 ms   250 ms   251 ms  2001:4860:0:1::5009
 10   250 ms   249 ms   249 ms  fra24s06-in-x04.1e100.net [2a00:1450:4001:829::2004]

Ablaufverfolgung beendet.

C:\Users\Dell>

We can see a couple of interesting points:

  • The latency from Brazil to my server is already > 200 ms, that is not a very competitive connection.
  • Despite the fact that between my Linux server caipirinha.spdns.org and Google, there are a range of machines, that connections has quite a low (additional) latency.

Let’s now switch off the VPN and do a traceroute to www.google.com directly from the local ISP:

C:\Users\Dell>tracert www.google.com

Routenverfolgung zu www.google.com [172.217.29.100]
über maximal 30 Hops:

  1    <1 ms    <1 ms    <1 ms  fritz.box [192.168.4.1]
  2     1 ms    <1 ms    <1 ms  192.168.100.1
  3     4 ms     4 ms     3 ms  179-199-160-1.user.veloxzone.com.br [179.199.160.1]
  4     5 ms     5 ms     4 ms  100.122.52.96
  5    12 ms     6 ms     5 ms  100.122.25.245
  6    11 ms    11 ms    11 ms  100.122.17.180
  7    12 ms    12 ms    12 ms  100.122.18.52
  8    12 ms    11 ms    11 ms  72.14.218.158
  9    14 ms    13 ms    14 ms  108.170.248.225
 10    15 ms    14 ms    14 ms  142.250.238.235
 11    13 ms    13 ms    13 ms  gru09s19-in-f100.1e100.net [172.217.29.100]

Ablaufverfolgung beendet.

For this test, only IPv4 was possible as I did not have IPv6 connection at the Ethernet port where my notebook was connected to. The overall connection is much faster, and we can clearly identify that it uns in the Brazilian internet (“179-199-160-1.user.veloxzone.com.br”).

Further Improvements (WireGuard)

A range of graphical user interfaces (GUIs) for the configuration of WireGuard have come up that seek to overcome the need to deal with various configuration files on the server and the client side and align public keys and IP addresses. [17] compares some GUIs for WireGuard, [18] shows a further possibility.

Configuring Network Address Translation (NAT)

Additionally, to the configuration of the VPNs (and their respective start on the VPN server), we need to set up network address translation so that connections from the VPN networks are translated to the SoHo network. This is done with the sequence:

readonly STD_IF='eth0'
…
iptables -t nat -F
iptables -t nat -A POSTROUTING -s 192.168.10.0/24 -o ${STD_IF} -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.11.0/24 -o ${STD_IF} -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.12.0/24 -o ${STD_IF} -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.13.0/24 -o ${STD_IF} -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.14.0/24 -o ${STD_IF} -j MASQUERADE
…

# Setup the NAT table for the VPNs.
ip6tables -t nat -F
ip6tables -t nat -A POSTROUTING -s fd01:0:0:a::/64 -o ${STD_IF} -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s fd01:0:0:b::/64 -o ${STD_IF} -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s fd01:0:0:c::/64 -o ${STD_IF} -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s fd01:0:0:d::/64 -o ${STD_IF} -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s fd01:0:0:e::/64 -o ${STD_IF} -j MASQUERADE
...

Conclusion

The VPN configuration mentioned above shows how to set up different VPNs that allow dual stack operations on a Linux server. Thus, VPN clients can initiate connections in IPv4 or IPv6 mode using the assigned IP addresses from a private address space; the configured network address translation (NAT) translates the connections to a real-world IP address on the server.

Outlook

The VPN server itself can also act as VPN client itself, and so, the connection from the original VPN client can be forwarded via other VPNs to other countries allowing the original VPN client to appear in different geographies depending upon the destination address of its outgoing connection. This can be useful in order to circumvent geo-blocking of media content, for example.

Sources