I’ve recently been testing out a VPN service, and normally while running the VPN, all internet traffic goes over the VPN interface. This isn’t really ideal, as I only want traffic from specific applications to use the VPN. IPTables doesn’t seem to have the option to filter specific processes, but it can filter based on a specific user account.
IPTables itself doesn’t really deal with routing packets to interfaces, so we can’t use it to directly route packets. We can however mark packets from the user so they can be routed by the ip routing table. I’ve created a script to flush and apply firewall rules, which does what we need ( obviously set the variables at the beginning of the script to match your details ):
#! /bin/bash export INTERFACE="tun0" export VPNUSER="vpnuser" export LANIP="192.168.1.0/24" export NETIF="br0" iptables -F -t nat iptables -F -t mangle iptables -F -t filter # mark packets from $VPNUSER iptables -t mangle -A OUTPUT ! --dest $LANIP -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1 iptables -t mangle -A OUTPUT --dest $LANIP -p udp --dport 53 -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1 iptables -t mangle -A OUTPUT --dest $LANIP -p tcp --dport 53 -m owner --uid-owner $VPNUSER -j MARK --set-mark 0x1 iptables -t mangle -A OUTPUT ! --src $LANIP -j MARK --set-mark 0x1 # allow responses iptables -A INPUT -i $INTERFACE -m conntrack --ctstate ESTABLISHED -j ACCEPT # allow bittorrent iptables -A INPUT -i $INTERFACE -p tcp --dport 59560 -j ACCEPT iptables -A INPUT -i $INTERFACE -p tcp --dport 6443 -j ACCEPT iptables -A INPUT -i $INTERFACE -p udp --dport 8881 -j ACCEPT iptables -A INPUT -i $INTERFACE -p udp --dport 7881 -j ACCEPT # block everything incoming on $INTERFACE iptables -A INPUT -i $INTERFACE -j REJECT # send DNS to google for $VPNUSER iptables -t nat -A OUTPUT --dest $LANIP -p udp --dport 53 -m owner --uid-owner $VPNUSER -j DNAT --to-destination 8.8.4.4 iptables -t nat -A OUTPUT --dest $LANIP -p tcp --dport 53 -m owner --uid-owner $VPNUSER -j DNAT --to-destination 8.8.8.8 # let $VPNUSER access lo and $INTERFACE iptables -A OUTPUT -o lo -m owner --uid-owner $VPNUSER -j ACCEPT iptables -A OUTPUT -o $INTERFACE -m owner --uid-owner $VPNUSER -j ACCEPT # all packets on $INTERFACE needs to be masqueraded iptables -t nat -A POSTROUTING -o $INTERFACE -j MASQUERADE # reject connections from predator ip going over $NETIF iptables -A OUTPUT ! --src $LANIP -o $NETIF -j REJECT
Now all packets from the user will be marked for the VPN. We also need to add a routing table, by adding the table name to the rt_tables file. In Gentoo this file is in /etc/iproute2/rt_tables , other distros will have it in different places, on my machine the file looks like this, the last line being the one added:
# # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep 200 vpnuser
Next we need a script to configure the routing rules for the marked packets:
#! /bin/bash
VPNIF="tun0"
VPNUSER="vpnuser"
GATEWAYIP=`ifconfig $VPNIF | egrep -o '([0-9]{1,3}\.){3}[0-9]{1,3}' | egrep -v '255|(127\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})' | tail -n1`
if [[ `ip rule list | grep -c 0x1` == 0 ]]; then
ip rule add from all fwmark 0x1 lookup $VPNUSER
fi
ip route replace default via $GATEWAYIP table $VPNUSER
ip route append default via 127.0.0.1 dev lo table $VPNUSER
ip route flush cache
If you are using OpenVPN, you will need to ensure this line is in your config file, to prevent all traffic from sending over the VPN by default:
route-nopull
You may also need to add these lines into /etc/sysctl.d/9999-vpn.conf to ensure the kernel lets the traffic get routed correctly ( this disables reverse path filtering ):
net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.default.rp_filter = 0 net.ipv4.conf.br0.rp_filter = 0
Then run:
sysctl -p
To apply the new sysctl rules. You may also need to restart your VPN if you are already connected.
Now run the two scripts ( the second script needs to run when the network interface starts – this is in /etc/conf.d/net on Gentoo, or the ‘up’ command in OpenVPN’s config file ) , and the specific user should only be able to access traffic on the VPN, and other users on the system should access the network as normal.
I tryed to setup your script. It works with ip adresses. But if i want to connect to hostnames it times out ?
Merry Christmas 🙂
This is going to be the most noobish question asked yet..
If I have a system right now running Ubuntu desktop with everything already set up and my goal is to route my rtorrent traffic through the vpnuser account how do I go about that after using these scripts ?
For example, I have this system setup and login under user: main Now I want to add these scripts withe the flags to make user: vpnuser be the only user that has their traffic routed through the VPN. What do I have to change in my system to make rtorrent be flagged and run as: vpnuser ?
I realize this must be really obvious, but I have no idea how to do this.
Thanks man it works like a charm!
Hello,
Please can someone explain me on details this line:
iptables -t mangle -A OUTPUT ! –src $LANIP -j MARK –set-mark 0x1
Why do you specify a source address for an output packet ?
What is this source address exactly?
Thanks
Following the guide everything works as should but I can’t access Transmisison webui on local network (port 9091). Is there anybody who got this working and can access port 9091? I’m not speaking about the WAN side access, but from local network.
Thanks!
Didn’t work for me on Ubuntu 14.04.4. My vpnuser cant route any traffic. Other user can still route fine. Not sure where / how to start looking.
Is there any way to get this working without setting rp_filter off ? As suggested in this post ( http://serverfault.com/questions/456308/linux-policy-routing-packets-not-coming-back/456330#456330 ) it may not be the best idea to turn off reverse path filtering. This guy suggest the same can be achieved with saving and restoring CONNMARK values but I don’t know how to implement here:
# keep that rule
OUTPUT -m owner …. -j MARK 0x3
# add this one after the previous one: it saves the current mark into connmark
OUTPUT -j CONNMARK –save-mark
# and add this one (in mangle), which sets the mark to the connmark
# if conntrack determines that it is from the same connection.
PREROUTING -j CONNMARK –restore-mark
Thanks for your scripts! I had been fighting with this for a couple of hours, I tried to do SNAT for 1 user, combined with a rule table for the SNAT’ed source IP, until I realized that SNAT happens post-routing and thus would not work. Your solution works great though!
ilium007: Do you have control over both openvpn endpoints? I debugged the traffic by running tcpdump -i tun0 on both ends (I also own the openvpn server). All I needed to do, in addition to this setup, is to also masquerade IP traffic on the server side, e.g.: iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE – after that, it worked great!
great example, thanks. the problem is, if the interface tun0 is not present, the user accesses the regular interface, eth0. if i try to use iptables to block access to eth0, it also drops access to tun0. any idea how can i resolve that conflict?