Route only specific traffic through VPN
What you are asking for does not exist. This is why you are dissatisfied with the answers you found (some of them, possibly, being mine): all of them have suggested workarounds, not a genuine solution, either simple or complex.
Let me explain. Routing in all OSes is determined by destination address: you may very well have several routes, but the choice between them is not based upon the application invoking the connection, but simply upon the destination address. Full stop.
Let me give you a non-trivial example. When a VPN client has established a connection to its server, it is still possible to route a connection to a given site, say example.org, outside the VPN. But all applications trying to reach that special address will be routed outside the VPN: you cannot have some applications going to example.org thru the VPN while other apps pass outside the VPN.
The situation becomes richer with the Linux kernel, which allows source routing: this means you can have two or more routing tables, and the choice between them is based upon the source address, not the destination address.
A non-trivial example: my pc has two outside lines, with two distinct public IPs. It can be contacted thru either interface, and it is important that my replies to a given connection go thru the same interface that connection came in thru: otherwise they will be discarded as irrelevant when they reach the person who initiated the connection. This is source routing.
Fair enough, what about connections that we start? Some apps allow you to specify the bind address, like the openssh client:
-b bind_address
Use bind_address on the local machine as the source address of the connection. Only useful on systems with more than one address.
For them, there is no problem in having one instance going thru the VPN (say, routing table 1) while another instance will go outside the VPN (say routing table 2). But other apps, like Firefox, not only are notoriously difficult to bind to a specific source ip address (but see here for a very smart workaround), but also are mean and nasty in that they will not allow you to have two copies of themselves running simultaneously, each bound to a different source address. In other words, while thanks to the trick referenced above you can oblige one instance to bind to a source address of your choice, then you cannot have another version of it binding to the other source address.
This explains why we use workarounds: they are all based upon the same idea, that they work with a separate network stack than the rest of the pc. So you can have, in decreasing approximate order of complexity, VMs, dockers, containers, namespaces. In each of them you will have one or more routing tables, but you can have several instances of each (VM/dockers/containers/namespaces) and you can also admix them freely, each one of them running its own app like Firefox happily separated from the other ones.
Perhaps you are still interested in one of the workarounds?
EDIT:
The simplest work-around is a network namespace. The script below handles all necessary aspects of a NNS: put it in a file (you pick your name, I generally use newns
, but you do whatever you prefer) in /usr/local/bin
, then chmod 755 FILE_NAME
, and you can use it as follows:
newns NAMESPACE_NAME start
newns NAMESPACE_NAME stop
It will open an xterm
for you (that's because I like xterm to work, but you can change it if you wish to use anything else), which belongs to the new namespace. From inside the xterm you may, if you wish, start your vpn, and then start your game. You may easily check that you are using the VPN thru the following command:
wget 216.146.38.70:80 -O - -o /dev/null | cut -d" " -f6 | sed 's/<\/body><\/html>//'
which returns you your public IP. After setting up the VPN in the xterm, you may check that your public IP is different in your other windows. You may open up to 254 xterms, with 254 different NNSes, and different connections.
#!/bin/bash
#
# This script will setup an internal network 10.173.N.0/24; if this causes
# any conflict, change the statement below.
export IP_BASE=10.173
# It will open an xterm window in the new network namespace; if anything
# else is required, change the statement below.
export XTERM=/usr/bin/xterm
# The script will temporarily activate ip forwarding for you. If you
# do not wish to retain this feature, you will have to issue, at the
# end of this session, the command
# echo 0 > /proc/sys/net/ipv4/ip_forward
# yourself.
###############################################################################
WHEREIS=/usr/bin/whereis
# First of all, check that the script is run by root:
[ "root" != "$USER" ] && exec sudo $0 "$@"
if [ $# != 2 ]; then
echo "Usage $0 name action"
echo "where name is the network namespace name,"
echo " and action is one of start| stop| reload."
exit 1
fi
# Do we have all it takes?
IERROR1=0
IERROR2=0
IERROR3=0
export IP=$($WHEREIS -b ip | /usr/bin/awk '{print $2}')
if [ $? != 0 ]; then
echo "please install the iproute2 package"
IERROR1=1
fi
export IPTABLES=$($WHEREIS -b iptables | /usr/bin/awk '{print $2}')
if [ $? != 0 ]; then
echo "please install the iptables package"
IERROR2=1
fi
XTERM1=$($WHEREIS -b $XTERM | /usr/bin/awk '{print $2}')
if [ $? != 0 ]; then
echo "please install the $XTERM package"
IERROR3=1
fi
if [ IERROR1 == 1 -o IERROR2 == 1 -o IERROR3 == 1 ]; then
exit 1
fi
prelim() {
# Perform some preliminary setup. First, clear the proposed
# namespace name of blank characters; then create a directory
# for logging info, and a pid file in it; then determine
# how many running namespaces already exist, for the purpose
# of creating a unique network between the bridge interface (to
# be built later) and the new namespace interface. Lastly,
# enable IPv4 forwarding.
VAR=$1
export NNSNAME=${VAR//[[:space:]]}
export OUTDIR=/var/log/newns/$NNSNAME
if [ ! -d $OUTDIR ]; then
/bin/mkdir -p $OUTDIR
fi
export PID=$OUTDIR/pid$NNSNAME
# Find a free subnet
ICOUNTER=0
while true; do
let ICOUNTER=ICOUNTER+1
ip addr show | grep IP_BASE.$ICOUNTER.1 2>&1 1> /dev/null
if [ ! $? == 0 -a $ICOUNTER -lt 255 ]; then
export Nns=$ICOUNTER
break
elif [ ! $? == 0 -a $ICOUNTER -gt 254 ]; then
echo "Too many open network namespaces"
exit 1
fi
done
if [ $Nns == 1 ]; then
echo 1 > /proc/sys/net/ipv4/ip_forward
fi
}
start_nns() {
# Check whether a namespace with the same name already exists.
$IP netns list | /bin/grep $1 2> /dev/null
if [ $? == 0 ]; then
echo "Network namespace $1 already exists,"
echo "please choose another name"
exit 1
fi
# Here we take care of DNS
/bin/mkdir -p /etc/netns/$1
echo "nameserver 8.8.8.8" > /etc/netns/$1/resolv.conf
echo "nameserver 8.8.4.4" >> /etc/netns/$1/resolv.conf
# The following creates the new namespace, the veth interfaces, and
# the bridge between veth1 and a new virtual interface, tap0.
# It also assigns an IP address to the bridge, and brings everything up
$IP netns add $1
$IP link add veth-a$1 type veth peer name veth-b$1
$IP link set veth-a$1 up
$IP tuntap add tap$1 mode tap user root
$IP link set tap$1 up
$IP link add br$1 type bridge
$IP link set tap$1 master br$1
$IP link set veth-a$1 master br$1
$IP addr add $IP_BASE.$Nns.1/24 dev br$1
$IP link set br$1 up
# We need to enable NAT on the default namespace
$IPTABLES -t nat -A POSTROUTING -j MASQUERADE
# This assigns the other end of the tunnel, veth2, to the new
# namespace, gives it an IP address in the same net as the bridge above,
# brings up this and the (essential) lo interface, sets up the
# routing table by assigning the bridge interface in the default namespace
# as the default gateway, creates a new terminal in the new namespace and
# stores its pid for the purpose of tearing it cleanly, later.
$IP link set veth-b$1 netns $1
$IP netns exec $1 $IP addr add $IP_BASE.$Nns.2/24 dev veth-b$1
$IP netns exec $1 $IP link set veth-b$1 up
$IP netns exec $1 $IP link set dev lo up
$IP netns exec $1 $IP route add default via $IP_BASE.$Nns.1
$IP netns exec $1 su -c $XTERM $SUDO_USER &
$IP netns exec $1 echo "$!" > $PID
}
stop_nns() {
# Check that the namespace to be torn down really exists
$IP netns list | /bin/grep $1 2>&1 1> /dev/null
if [ ! $? == 0 ]; then
echo "Network namespace $1 does not exist,"
echo "please choose another name"
exit 1
fi
# This kills the terminal in the separate namespace,
# removes the file and the directory where it is stored, and tears down
# all virtual interfaces (veth1, tap0, the bridge, veth2 is automatically
# torn down when veth1 is), and the NAT rule of iptables.
/bin/kill -TERM $(cat $PID) 2> /dev/null 1> /dev/null
/bin/rm $PID
/bin/rmdir $OUTDIR
$IP link set br$1 down
$IP link del br$1
$IP netns del $1
$IP link set veth-a$1 down
$IP link del veth-a$1
$IP link set tap$1 down
$IP link del tap$1
$IPTABLES -t nat -D POSTROUTING -j MASQUERADE
/bin/rm /etc/netns/$1/resolv.conf
/bin/rmdir /etc/netns/$1
}
case $2 in
start)
prelim "$1"
start_nns $NNSNAME
;;
stop)
prelim "$1"
stop_nns $NNSNAME
;;
reload)
prelim "$1"
stop_nns $NNSNAME
prelim "$1"
start_nns $NNSNAME
;;
*)
# This removes the absolute path from the command name
NAME1=$0
NAMESHORT=${NAME1##*/}
echo "Usage:" $NAMESHORT "name action,"
echo "where name is the name of the network namespace,"
echo "and action is one of start|stop|reload"
;;
esac
If you want, you can even start a whole desktop inside the new network namespace, by means of
sudo startx -- :2
then you can search for it using Alt+Ctrl+Fn, where Fn is one of F1,F2,....-
I need to add one caveat: DNS handling inside namespaces is a bit buggy, be patient.