How to use VPN for only one application on Linux

The default approach with routing all traffic over VPN can hit user experience sometimes. If user need to do some tasks with application which requires low traffic or speed - it can be done with cheap VPN, but _comfortable web browsing will be definitely broken_.

How to use VPN for only one application on Linux
Photo by Franck / Unsplash

Why it's useful

The default approach with routing all traffic over VPN can hit user experience sometimes. If user need to do some tasks with application which requires low traffic or speed - it can be done with cheap VPN, but comfortable web browsing will be definitely broken.

Corporate network and web surfing

Remote job opened perfect opportunities for many people. In 99% cases, remote worker should be connected to corporate network over VPN and all traffic will be routed through company server and all browsing history will be collected. Do you want to buy a ticket or check a football match score? Surely it all can be done with personal mobile phone via 4G or 5G connection, but hey - you have already the working laptop or desktop around when doing your daily job.

Torrent client

Torrent sharing now heavily promoted by media as bad and illegal way to share files. You can't find a torrent app for iPhone, thanks Apple! Despite all, torrent protocol still popular in music, amateur movie and documentary video industries as fast way to share demo files. If you need to give your colleagues or friends 20 GB raw video with last school game - uploading it all to a file sharing will be slow in mostly cases, way easier is to create a torrent and share the magnet link.

To hide your torrent activity from automatic scanning, it's reasonable to use VPN for torrent client only and keep your web browsing experience good like before.

Namespaces on Linux

Namespaces is an environment for a process, user, block device, also for network. And this works very well for network isolation, so we can tune network stack and routing and create a full virtual network for a simple app.

  • Let's create a namespace called vpn, all ip commands should be executed with sudo:
ip netns add vpn
  • Now we need to create virtual interfaces to access our namespace:

root namespace <-> eth1v <-> traffic <-> peer1 <-> namespace

  • Create the virtual interface:
ip link add eth1v type veth peer name peer1
  • Now add the peer1 to our vpn namespace:
ip link set peer1 netns vpn
  • Set IP to the virtual interface:
ip addr add 10.0.1.1/24 dev eth1v
  • Now activate this interface:
ip link set eth1v up
  • Now add IP 10.0.1.2 and /24 subnet to the vpn namespace:
ip netns exec vpn ip addr add 10.0.1.2/24 dev peer1
  • Activate the interface:
ip -n vpn link set peer1 up
  • Add local interface in our vpn namespace:
ip -n vpn link set lo up
  • Now route traffic from vpn namespace to root namespace through ethv interface:
ip -n vpn route add default via 10.0.1.1

Here we go! Our namespace called vpn is up and running. One task left - traffic routing through virtual interfaces.

  • Enable forwarding via sysctl command:
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
  • Flush all forward and NAT rules, set policy DROP by default:
iptables -P FORWARD DROP
iptables -F FORWARD
iptables -t nat -F
  • Enable masquerading:
iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -o eth0 -j MASQUERADE
  • Allow forwarding between physical eth0 and virtual eth1v interfaces:
iptables -A FORWARD -i eth0 -o eth1v -j ACCEPT
iptables -A FORWARD -o eth0 -i eth1v -j ACCEPT
  • Allow all output traffic:
iptables -P OUTPUT ACCEPT

DNS Configuration

DNS configuration in vpn namespace can be different from default. For example, let's use Quad9 DNS servers 9.9.9.9.

mkdir -p /etc/netns/vpn
echo "nameserver 9.9.9.9" > /etc/netns/vpn/resolv.conf
echo "nameserver 149.112.112.112" >> /etc/netns/vpn/resolv.conf

Run and hide

Let's run an application inside our namespace. We need to activate Wireguarg connection inside vpn namespace:

ip netns exec vpn wg-quick up wg-us-florida

And finally, now it's possible to run our application inside namespace the same way as we did before. From security point of view, let's avoid using the superuser this time:

sudo ip netns exec vpn runuser $USER -c transmission

Let's summarize it all in one Bash script called vpn.sh:

#!/bin/bash
ip netns add vpn
ip link add eth1v type veth peer name peer1
ip link set peer1 netns vpn
ip addr add 10.0.1.1/24 dev eth1v
ip link set eth1v up
ip netns exec vpn ip addr add 10.0.1.2/24 dev peer1
ip -n vpn link set peer1 up
ip -n vpn link set lo up
ip -n vpn route add default via 10.0.1.1
echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
iptables -P FORWARD DROP
iptables -F FORWARD
iptables -t nat -F
iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -o eth0 -j MASQUERADE
iptables -A FORWARD -i eth0 -o eth1v -j ACCEPT
iptables -A FORWARD -o eth0 -i eth1v -j ACCEPT
iptables -P OUTPUT ACCEPT
mkdir -p /etc/netns/vpn
echo "nameserver 9.9.9.9" > /etc/netns/vpn/resolv.conf
echo "nameserver 149.112.112.112" >> /etc/netns/vpn/resolv.conf
ip netns exec vpn wg-quick up wg-us-florida
sudo ip netns exec vpn runuser $USER -c "$1"

Now we can use it for many applications too:

sudo vpn.sh firefox
sudo vpn.sh chrome

Another script called reset.sh for removing the namespace and undo all changes:

#!/bin/bash
ip netns exec vpn wg-quick down wg-us-florida
rm -rf /etc/netns
ip netns delete vpn
ip addr del 10.0.1.1/24 dev eth1v
ip link delete eth1v
ip link delete peer1
echo 0 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward = 0" >> /etc/sysctl.conf
iptables -F

Usage is super simple: sudo reset.sh.

Congrats! Stay secure.