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.
I know the question is tagged fedora
, but I just got the newns
script from the excellent answer by @MariusMatutiae to work on Ubuntu 18.04 - so I thought I should jot down my notes, as there are several steps I found non-trivial.
As it is in the answer, newns
seems to work fine on Ubuntu 18.04 at first; however, one can quickly realize, that after one has ran the command newns MYNS start
and the new xterm
has been raised, only the xterm
still has network connectivity (rather, DNS resolution, since I got ping: google.com: Name or service not known
; some things I tried by IP still seemed to work fine), while the rest of the system does not - and that situation persists, until you shutdown with newns MYNS start
.
The thing is, I connect to the Internet from my Ubuntu 18.04 over Wi-Fi, and the Wi-Fi router (or Ubuntu?) assigns both IPv6 and IPv4 addresses to the Wi-Fi network adapter (which, ifconfig
tells me, is called wlp2s0
in my Ubuntu). So at first I thought this is a problem due to IPv6, but it seems to not be - as eventually I found Use VPN connection only for selected applications ( more specifically https://superuser.com/a/1262250 ), with the critical comment being:
As my default interface is a wireless one, I use wl+ (which may match wlan0, wlp3s0, etc.) in iptables for the outgoing interface; if you use a wired interface you should probably use en+ (or br+ for a bridged interface)
So, I did this replacement in the original newns
:
@@ -134,7 +134,8 @@
# We need to enable NAT on the default namespace
- $IPTABLES -t nat -A POSTROUTING -j MASQUERADE
+ #$IPTABLES -t nat -A POSTROUTING -j MASQUERADE
+ $IPTABLES -t nat -A POSTROUTING -s $IP_BASE.$Nns.1/24 -o wl+ -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,
@@ -181,7 +182,8 @@
$IP link del veth-a$1
$IP link set tap$1 down
$IP link del tap$1
- $IPTABLES -t nat -D POSTROUTING -j MASQUERADE
+ #$IPTABLES -t nat -D POSTROUTING -j MASQUERADE
+ $IPTABLES -t nat -D POSTROUTING -s $IP_BASE.$Nns.1/24 -o wl+ -j MASQUERADE
/bin/rm /etc/netns/$1/resolv.conf
/bin/rmdir /etc/netns/$1
If you replace only the -A POSTROUTING
line (without changing the -D POSTROUTING
line), you will get iptables: No chain/target/match by that name.
when you try to run newns MYNS stop
after newns
has been started.
Another relevant note here may be, that I had stored the VPN I want to connect to in Gnome's Network Manager. However, it seems that you can only call the command line openvpn
in the xterm
from newns
(if you try starting the VPN via Network Manager desktop GUI for rest of the system, in hopes that the xterm
will remain "direct"/non-VPNed, then no network connection will resolve from anywhere, until you shutdown VPN from Network Manager).
So, here is the problem: Network Manager actually saves the VPN settings as a "network connection" in /etc/NetworkManager/system-connections
( https://askubuntu.com/questions/27168/config-import-on-network-manager-openvpn ), and I have no idea where it saves related passwords; and this can definitely not be used by command-line openvpn
client directly. Luckily, I had saved the original .ovpn
file (that I had imported in Network Manager to create the VPN connection there), so then I have to do, in the xterm
started from newns
:
sudo openvpn --config /path/to/myfile.ovpn
... upon which I'm asked for username and password - and when entered successfully, openvpn
blocks.
And that is the other problem - since if openvpn
blocks, then I cannot start any other program in that xterm
:)
So one idea would be to run openvpn
in background (as "service"), however, just appending ampersand actually runs sudo
in background, and you cannot enter username and password (that, assuming you cached the sudo
password previously):
user@PC:~$ sudo openvpn --config /path/to/file.ovpn &
[6] 3113
user@PC:~$ Thu Aug 20 20:29:51 2020 OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019
Thu Aug 20 20:29:51 2020 library versions: OpenSSL 1.1.1 11 Sep 2018, LZO 2.08
Enter Auth Username: RemoteUser
[6]+ Stopped sudo openvpn --config /path/to/file.ovpn
RemoteUser: command not found
... and neither you can wrap the whole thing in bash -c
:
user@PC:~$ sudo bash -c 'openvpn --config /path/to/file.ovpn &'
user@PC:~$ Thu Aug 20 20:35:05 2020 OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019
Thu Aug 20 20:35:05 2020 library versions: OpenSSL 1.1.1 11 Sep 2018, LZO 2.08
Broadcast message from root@MyPCName (Thu 2020-08-20 20:35:05 CEST):
Password entry required for 'Enter Auth Username:' (PID 3261).
Please enter password with the systemd-tty-ask-password-agent tool!
... so actually, what I have to do is this:
- run
newns MYNS start
- it starts anxterm
- From that
xterm
, first runxterm &
- to get anotherxterm
, with the same ("newns") network settings - Now in the first
xterm
, runsudo openvpn --config /path/to/file.ovpn
- it will block, but that is no problem now, because we can: - Switch to second
xterm
, confirm withifconfig
that the tunnel for openvpn has been made, then withwget -qO- ifconfig.co
that you have the VPN public IP in that terminal - then you can run whatever network using programs that you want to run through the VPN
So all good - the only problem for me, is that I cannot run gnome-terminal
, which I prefer, from the second xterm
- rather, I can, but then it connects to the system Network Manager and apparently uses its network, and not that of the parent xterm
that spawned it. (EDIT: posted about this here https://unix.stackexchange.com/questions/605485/possible-to-start-gnome-terminal-from-xterm-inheriting-xterms-network-settings - turns out, starting mate-terminal &
from this xterm
preserves the VPN network configuration - and so do subsequent tabs opened in this mate-terminal
window... and also changing export XTERM=/usr/bin/mate-terminal
in the newns
script also seems to work)