I’ve always wondered how difficult it would be to do something like this, so I decided to give it a try. Turns out the answer is, since the addition of UID matching to
ip rule, not very difficult.
The basic approach is to give the VPN interface a separate routing table, and redirect suspect processes to that routing table instead. Since working with numeric IDs directly is sort of a pain, you can give them friendly names:
Confining your process to a specific user
ip rule can only match based on UID, rather than PID (which is more stable anyway), the first step is making sure your process is running under some suitable user. For example, suppose you’re trying to isolate
transmission-daemon, then the appropriate user would be
transmission, which (at least on my system)
transmission-daemon gets run under. If your program lacks such a convenient user, then you could always add your own and use something like sudo to switch to it, e.g.:
joe could use
sudo -u rtorrent /usr/bin/rtorrent to run rtorrent as a separate user
The second part of the configuration is making sure to set up the correct routing table as part of OpenVPN’s initialization. For the purposes of this example, I want to ignore the VPN provider’s pushed routes (since they try overriding my system-wide routing to go through their VPN, whereas I only want it for certain processes), which the addition of
The magic happens due to the
ip rule invocation. Basically, it creates a rule that looks like this:
This means that any packet originating from UID
transmission) will get routed as according to the table
vpn, which looks like this: (as an example)
ip and root privileges
For these scripts to work, openvpn needs to be able to execute
ip commands (with root privilege). You could either accomplish this by preventing
openvpn from ever dropping privileges (bad), or, as I prefer, using
sudo to re-gain access to
ip for the openvpn user:
Note that dropping privileges for OpenVPN is done by adding something like the following to your
It’s possible that due to the way source route verification works under Linux, you will not receive any replies directed your way (and e.g.
ping as the confined user will fail). The solution to this is setting
rp_filter to 2, e.g.
If it still doesn’t work, you may need to flush the routing cache, i.e.
ip route flush cache.
Disclaimer and warning
A word on DNS
If you use a local DNS server (e.g. one pushed by your DHCP server), then DNS lookups from the
confined user will fail, because there’s no appropriate route for the local DNS server. There are several solutions to this:
- Use a public DNS server that’s accessible via the VPN as well.
- Hard-code domains you care about to
- Add an extra route for your local DNS server to the
While #3 seems the most attractive, this is a privacy risk because DNS requests will leak your real IP! Only do this if you’re sure you know what you’re signing yourself up for.
Other sources of IP leaks
It’s possible that all your effort will be for naught and your client will find other ways of leaking your ‘real’ IP to the internet. Unless you have carefully audited and tested your specific program, do NOT take this guide as any sort of guarantee. WebRTC, torrent clients etc. have all found ways to inadvertently de-anonymize VPN users.
One website you can use for testing these sorts of things is
ipleak.net, which includes support for testing torrent clients in particular. Handy if you just want to make sure your client isn’t egregiously advertising your real IP to trackers.