Route selective traffic via VPN and prevent packet leaking
I finally found a way to route traffic outgoing from a specific user via VPN without packet leaking. I’m using NetworkManager to connect to my VPN network, but you can do the same using any client you want.
First of all, I’ve created another user whose apps will connect only via VPN. This user has UID 1005 in my scripts.
Prevent default routing
This step will set NetworkManager to prevent routing all your connections via VPN.
So, if you sometimes don’t need that, just unmark again the checkboxes…
- Right click on the nm-applet icon
- Click Edit connection
- Choose your VPN
- Click Edit
- Choose IPv4 Settings tab
- Click Routes
- Check “Ignore automatically obtained routes”
- Check “Use this connection only for resources on its network”
iptables configuration
This script marks user’s packets with a number and prevent packet leaking when VPN connection drops. Just run this during the boot, or save this config with iptables-save.
#!/bin/sh
# The chosen one
UID=1005
# A custom mark id (same as the script below)
MARKID=2
# Your VPN interface
VPNINT=tun0
# (1) Mark packets owned by that user
iptables -t mangle -A OUTPUT -m owner --uid-owner $UID -j MARK --set-mark $MARKID
# (2) Allow those packets via VPN interface
iptables -t mangle -A POSTROUTING -o $VPNINT -m mark --mark $MARKID -j ACCEPT
# (2.5) (Updated on 2015-10-04) Allow loopback packets
iptables -t mangle -A POSTROUTING -o lo -m mark --mark $MARKID -j ACCEPT
# (3) Prevent leaks
iptables -t mangle -A POSTROUTING -m mark --mark $MARKID -j DROP
NetworkManager dispatcher script
NetworkManager executes this script when VPN connection is established
- Put this script into /etc/NetworkManager/dispatcher.d/somename
- chown root:root
- chmod +x
#!/bin/bash
# Same as the other script
MARKID=2
if [ "$2" == "vpn-up" ]; then
INT=$1
IP=\$\(ip addr | egrep \"inet.\*peer.\*\$INT\" | awk '{print \$2}')
# Remove old rules
iptables -t nat -D POSTROUTING -o $INT -m mark --mark $MARKID -j SNAT --to-source $IP >/dev/null 2>&1
iptables -t nat -D POSTROUTING -o $INT -m mark --mark $MARKID -j SNAT --to-source $IP >/dev/null 2>&1
# Change source ip when forwarding via VPN
iptables -t nat -A POSTROUTING -o $INT -m mark --mark $MARKID -j SNAT --to-source $IP
# Remove old ip rules
ip rule del fwmark 2 lookup 200 > /dev/null 2>&1
ip rule del fwmark 2 lookup 200 > /dev/null 2>&1
# Add a new ip rule
ip rule add fwmark 2 table 200
ip route add table 200 default via $IP
echo 2 > /proc/sys/net/ipv4/conf/$INT/rp_filter
fi;
Conclusion
Restart your VPN connection.
From now all(and only) the apps running under that user will be routed via VPN.
If the connection drops, rule (3) will prevent leaks.