Overview #
This post details how to use a virtual private network (VPN) running on a separate (virtual) machine. This allows you to “pre-filter” the network traffic, effectively split tunneling using a full-tunnel VPN connection.
Some possible use cases include:
- You are required to use a full tunnel VPN connection but you don’t want to send all of your traffic through the VPN for privacy or performance reasons.
- You want to connect a machine to multiple VPNs simultaneously.
- You want to connect multiple machines to a VPN simultaneously.
Setup #
The simplest setup is to use a virtual machine (VM) to connect to the VPN. However two different physical computers could also be used.
This post will assume you are running a KVM VM on a Linux host but it should be trivial to adapt it to other situations.
Create the VM #
- Choose a Linux distribution. You can use a distro optimized for networking like OpenWRT or a general purpose one like Debian or Arch.
- Create a VM with for example
virt-manager
. If you are paranoid you can enable full disk encryption. - Install the VPN client you want to use inside the VM.
Network interfaces #
Inspect the network interfaces of the machines using ip addr
or ifconfig
.
There are 3 interfaces of interest:
- On the host:
- interface to the LAN:
wlp1s0
or similar if connected to wifienp1s0
,eth0
or similar if connected to ethernet
- interface like
virbr0
to the VM
- interface to the LAN:
- On the VM:
- interface like
enp1s0
to the host
- interface like
Furthermore if you start the VPN in the VM a new interface like tun0
should be created.
This is a diagramm of the setup:
+------------------------------------------------------+
| |
| Host |
| |
| +--------+ +------------------------------+ |
LAN <-----> | wlp1s0 | | | |
| +--------+ | VM | |
| | | |
| +--------+ | +--------+ +--------+ | |
| | virbr0 | <-----> | enp1s0 | | tun0 | | |
| +--------+ | +--------+ +--------+ | |
| ... +------------------------------+ |
+------------------------------------------------------+
The interfaces might be named a bit differently on your machines, you will have to adapt the following commands accordingly.
Routing #
Linux uses a routing table to determine where to send packets.
The routing table can be consulted using the route
command.
Therefore:
- The routing table of the host has to be modified so that it sends packets meant for the VPN to the VM.
- The VM has to be adjusted so that it forwards these packets received from the host to the VPN.
VM #
Enable IP forwarding:
echo '1' >> /proc/sys/net/ipv4/ip_forward
Adjust the firewall to forward enp1s0
to tun0
:
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i enp1s0 -o tun0 -j ACCEPT
iptables -A FORWARD -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i tun0 -j ACCEPT
Host #
There are two ways to set up the host:
Static #
This is simpler if you only need to access a few resources.
If there is a website called site.example
that you need to access:
- Look up the IP address of
site.example
usingdig
ornslookup
. Let’s assume it is10.10.10.10
. - Add an entry to your
/etc/hosts
file:# /etc/hosts 10.10.10.10 site.example
- Find the IP address of the
enp1s0
network interface of the VM. It must be on the same subnet as thevirbr0
interface of the host. Let’s assume it is192.168.100.100
. - Add an entry to the routing table of the host specifying that packets bound for the IP of the website should use the VM as the gateway.
route add 10.10.10.10/32 gw 192.168.100.100 metric 200 dev virbr0
- You should be able to access the website.
curl site.example
DNS #
This is more complicated but might be necessary if a static setup would be too tedious.
- Find the VPN name server using
dig
ornslookup
. - Configure the host to use it for DNS resolution. The concrete steps to do this depend on your setup, but this is tricky:
- You should restrict usage of the VPN name server to domains that it is actually needed for.
- You might not know all of the domains this applies to.
- You cannot use the name server on your router because it doesn’t have access to the VPN.
- Therefore you might have to run a seperate name server on the host and configure the fallback logic between router and VPN name server there.
- Failing to set this up correctly could leak your DNS queries to the VPN name server and break a DNS sinkholing setup.
- Expose the VPN name server as well as the VPN subnets using the host routing table as explained in the static section.
Example #
DNS using systemd-resolved
with systemd-networkd
Run on the VM:
dig site1.example
# ...
# site1.example. 300 IN A 10.10.10.10
# ...
# ;; SERVER: 10.10.10.53#53(10.10.10.53) (UDP)
# ...
Thus the VPN name server is 10.10.10.53
.
Make sure you are actually using resolved for host resolution.
The /etc/nsswitch.conf
file should contain the following line:
hosts: files resolve dns
See also man nss-resolve
and man nsswitch.conf
.
Set the VPN name server as the network DNS server in /etc/systemd/network/wlp1s0.network
.
[Match]
Name=wlp1s0
[Network]
DNS=10.10.10.53
Domains=site1.example site2.example ~example
See also man systemd.network
.
Set your prefered non-VPN name server as your global DNS server in /etc/systemd/resolved.conf
:
[Resolve]
DNS=8.8.8.8
See also man systemd-resolved.service
.
Reload:
systemctl daemon-reload
systemctl restart systemd-networkd
systemctl restart systemd-resolved
This has the effect that host1.site1.example
will be resolved using 10.10.10.53
but on.the.internet
using 8.8.8.8
.
Add routing information for the VPN name server and other resources you need to access:
route add 10.10.10.53/32 gw 192.168.100.100 metric 200 dev virbr0
route add 10.10.10.10/32 gw 192.168.100.100 metric 200 dev virbr0
You can troubleshoot using:
resolvectl status
The output should look something like this:
Global
Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: foreign
Current DNS Server: 8.8.8.8
DNS Servers: 8.8.8.8
Link 2 (wlp1s0)
Current Scopes: DNS
Protocols: -DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.10.10.53
DNS Servers: 10.10.10.53
DNS Domain: dynatrace.org ~example
# ...
Persistence #
All of the commands are non-persistent. If you want them to survive a reboot you have to create a startup script running them on boot.
Ideally you want
- a startup script on the host setting up the routing table and starting the VM, and
- a startup script on the VM starting the VPN and adjusting the firewall.
The specifics will highly depend on your setup and use case.
Conclusion #
Notice how you can use these techniques to
- split tunnel before a full tunnel VPN,
- share a VPN connection with multiple computers or
- use multiple VPNs simultaneously
without the arbiters of the VPN being able to do much about it.