VPN Chains

The earliest mention I can find of VPN chains is a 2010 article by Eric Crist. That article envisaged a literal chain, in which one client connects to a server, which in turn connects to a further server, and on. It did not give any practical details.

The interest in VPN chains since then has been more in tunneling one connection through another. In particular, the question of how to build a tunnel through a tunnel was raised in a 2011 forum thread started by Bebop. A finished set of bash scripts was posted on SourceForge in 2012 by br41n. In 2017, the scripts were added to GitHub by TensorTom. These inspired Mirimir to create an alternative set of bash scripts in 2019.

A VPN chain disguises your destination from your ISP and VPN1, and disguises your origin from VPN2 and your final destination website. If your goal is anonymity, you will likely use commercial VPN services who accept anonymous payment. However, for demonstration purposes we will build our own VPN servers from scratch.

The two servers and one client in this demonstration all run Debian 10. We will give the IP addresses of the two OpenVPN servers as 11.11.11.11 and 22.22.22.22 respectively. We also refer to them as VPN1 and VPN2.

1. Set Up Servers

1.1. Create VPN1 Server

Since this article is not primarily about VPN server configuration, we will go through this quickly. Install a basic firewall on your server:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP
apt update && apt upgrade -y
apt install iptables-persistent -y

Install OpenVPN using a prewritten script:

apt install curl -y
curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
chmod +x openvpn-install.sh
./openvpn-install.sh

When the script asks you questions, you can the defaults all the way through, unless you have some reason to change them. When it asks for the name of a client, call it vpn1. This results in the script creating a client configuration file named vpn1.ovpn.

For clarity, and so that we do not end up with overlapping virtual address ranges, we will change the IP addresses given out by the OpenVPN server. Edit the server configuration file:

vi /etc/openvpn/server.conf

Change the address range to 10.8.1.0/24:

server 10.8.1.0 255.255.255.0

Save the file. Also edit the iptables rules created when the server comes up, and make the corresponding change there:

vi /etc/iptables/add-openvpn-rules.sh
iptables -t nat -I POSTROUTING 1 -s 10.8.1.0/24 -o eth0 -j MASQUERADE

Save the file. Do the same thing when the iptables rules are deleted when the server goes down:

vi /etc/iptables/rm-openvpn-rules.sh
iptables -t nat -D POSTROUTING -s 10.8.1.0/24 -o eth0 -j MASQUERADE

Save the file. At this point, it’s cleanest to simply reboot:

reboot

1.2. Create VPN2 Server

This is pretty much the same procedure as the VPN1 server, with some small changes. Create the basic firewall:

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -P INPUT DROP
apt update && apt upgrade -y
apt install iptables-persistent -y

Install OpenVPN using the script:

apt install curl -y
curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
chmod +x openvpn-install.sh
./openvpn-install.sh

You can leave the server operating on port 1194 by UDP. Name the client vpn2. This creates a file on this server named vpn2.ovpn.

To avoid confusion, we will use the range 10.8.2.0/24 for VPN2. Edit the server configuration file:

vi /etc/openvpn/server.conf

Change the address range to 10.8.2.0/24:

server 10.8.2.0 255.255.255.0

Save the file. Also edit the iptables rules created when the server comes up:

vi /etc/iptables/add-openvpn-rules.sh
iptables -t nat -I POSTROUTING 1 -s 10.8.2.0/24 -o eth0 -j MASQUERADE

Save the file. Do the same thing when the iptables rules are deleted:

vi /etc/iptables/rm-openvpn-rules.sh
iptables -t nat -D POSTROUTING -s 10.8.2.0/24 -o eth0 -j MASQUERADE

Save the file, and reboot:

reboot

2. Set Up Client

2.1. Install Packages

Now go to work on your client PC. Install all the packages we’ll need for this demonstration:

sudo apt update && sudo apt upgrade -y
sudo apt install openvpn resolvconf curl tcpdump wireshark -y

2.2. Get OVPN Files

Retrieve the client configuration files from their respective servers:

scp root@11.11.11.11:vpn1.ovpn ~/Downloads/
scp root@22.22.22.22:vpn2.ovpn ~/Downloads/

2.3. Set Up Client for VPN1

Determine the IP address of your PC’s default gateway:

ip r | grep default

We will use as our example the IP address 10.0.2.2.

Edit the downloaded configuration file for VPN1:

vi ~/Downloads/vpn1.ovpn

Insert a couple of lines that prevent the OpenVPN client from accepting routes pushed by the server, and instead manually add a route to the VPN1 server via your default gateway:

route-nopull
route 11.11.11.11 255.255.255.255 10.0.2.2

Save the file. Copy the amended configuration file into the OpenVPN directory:

sudo cp ~/Downloads/vpn1.ovpn /etc/openvpn/vpn1.conf

Connect your PC to the VPN1 server:

sudo systemctl start openvpn@vpn1

2.4. Test Connection to VPN1

Check the connection to the VPN1 server:

sudo systemctl status openvpn@vpn1

You should see a message Initialization Sequence Completed.

Now, just for testing purposes, manually add routes that will send the client’s entire traffic through the tunnel:

sudo ip r add 0.0.0.0/1 via 10.8.1.1 dev tun0
sudo ip r add 128.0.0.0/1 via 10.8.1.1 dev tun0

Check that you have outbound connectivity:

ping 8.8.8.8

Do Ctrl+c to stop ping. Check that DNS resolution works:

ping www.yahoo.com

Do Ctrl+c to stop ping. Confirm the address at which your packets emerge from the tunnel:

curl ifconfig.me && echo ''

You should see the IP address of VPN1. Assuming all is well, manually delete your added routes:

sudo ip r del 0.0.0.0/1
sudo ip r del 128.0.0.0/1

2.5. Set Up Client for VPN2

Get the IP address of your gateway on the first tunnel interface, which is usually named tun0. In our example, the gateway will be 10.8.1.1.

Edit your client configuration file for VPN2:

vi ~/Downloads/vpn2.ovpn

Insert a couple of lines that prevent the OpenVPN client from accepting routes pushed by the server, and instead manually add a route to the VPN2 server via your first tunnel:

route-nopull
route 22.22.22.22 255.255.255.255 10.8.1.1

Save the file. Copy the client configuration file for VPN2 into the OpenVPN directory:

sudo cp ~/Downloads/vpn2.ovpn /etc/openvpn/vpn2.conf

Start the OpenVPN client for VPN2:

sudo systemctl start openvpn@vpn2

Check the connection to the server:

sudo systemctl status openvpn@vpn2

You should see a message Initialization Sequence Completed.

Manually add routes that will send your PC’s entire traffic through the second tunnel:

sudo ip r add 0.0.0.0/1 via 10.8.2.1 dev tun1
sudo ip r add 128.0.0.0/1 via 10.8.2.1 dev tun1

3. End-to-End Test

Check that you have outbound connectivity:

ping 8.8.8.8

Do Ctrl+c to stop ping. Check that DNS resolution works:

ping www.yahoo.com

Do Ctrl+c to stop ping. Confirm the address at which your packets emerge from the tunnels:

curl ifconfig.me && echo ''

You should now see the IP address of VPN2.

Determine your main interface:

ip a

Let’s say, for example, that it is enp0s3. Capture the packets that go out from enp0s3 to either VPN1 or VPN2:

sudo tcpdump -i enp0s3 -w capture.pcap host 11.11.11.11 or host 22.22.22.22

Leave tcpdump running in your first terminal window. Open a second terminal window and reissue the command:

curl ifconfig.me && echo ''

Of course, you should still see the VPN2 server address, but we did this mainly to capture the packets going out.

Close the second terminal window. In the first terminal window, do Ctrl+c to stop tcpdump.

Launch the Wireshark application. In Wireshark, open your file capture.pcap.

You should see all your packets go out to VPN1, and none to VPN2.

Finally, open Firefox and visit IPchicken.com to make sure HTTPS traffic emerges from the tunnels at the VPN2 IP address.

4. Finish Up

When you are done, delete the manually added rules:

sudo ip r del 0.0.0.0/1
sudo ip r del 128.0.0.0/1

Stop the two OpenVPN clients:

sudo systemctl stop openvpn@vpn1
sudo systemctl stop openvpn@vpn2

Check that everything is back to normal and that traffic is now emerging directly from your ISP:

curl ifconfig.me && echo ''

5. Get Help and Report Issues

This is a niche area, and there are no official support arrangements.

If you run into problems, a couple of suggestions made by people who’ve previously tested VPN chains are to change the MTU size to 1400 or to use TCP instead of UDP for one of the connections.

For the OpenVPN manual, do man openvpn on Linux, or consult the online Reference Manual for OpenVPN 2.4. If you need further support, you can ask on the OpenVPN forums.