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”

http://i.imgur.com/yjgPP9a.png

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.