VPN in a Nutshell
published on Sunday, January 29, 2017
Ever wanted to tunnel only specific applications through your VPN?
UPDATE: Also consider my follow-up post VPN autostart, and the openvpn-routing-examples repository made from the code in this post.
- Why you ask?
- Recommended solution
- Always trouble with DNS
- For your convenience
- Leaving VPN config files unmodified
- Alternative approaches
Why you ask?
Plenty of reason for that:
- Never accidentally leak traffic through the unencrypted connection, e.g. when your VPN dies.
- Traffic-intense applications like youtube or downloads can go over normal connection and are unaffected by the typically lower bandwidth limit on VPN.
- Can normally use applications that may be inaccessible through your VPN, e.g. email providers that block logins from the IP of the VPN.
- When your VPN goes online, unaffected applications don't need to reconnect using a different IP. This could e.g. cause your instant messanger to miss messages sent to the old IP.
- Not sharing the same IP for authenticated logins such as your email/youtube/IM with security-critical applications, potentially improving anonymity.
IMO, the best solution is to move the VPN network adapter to a linux network namespace. This ensures that only specific applications have access to the VPN and these applications have only access to VPN. Proceed as follows:
Extend your OpenVPN config file by adding the following lines at the end of the file:
Download the following move-to-netns.sh script and make it executable:
Note this is a slightly modified version of Sebastian Thorarensen's netns-script. The main difference is that I prefer not to destroy the namespace when VPN goes down. This will allow to restart VPN and attach it to an already existing network namespace without having to restart tunneled applications.
Now, when your VPN is online you can start applications with
You should check this now using a ping command.
Also observe (and verify!) that the ping fails to reach the destination once you stop the VPN — and succeeds again once you restart.
Always trouble with DNS
You may find that you can access internet sites by IP address but not by hostname (check using ping). In this case you may also need the update-resolv-conf script to update your DNS configuration for use with the VPN. Save it to /etc/openvpn/update-resolv-conf and make it executable.
For your convenience
Once everything works, make your life easier by adding the following script:
Now you can start applications using the simpler notation vpnbox COMMAND.
You could simply add it as an alias, but I prefer it to be a real command so non-shell applications and non-interactive shells can use it too. Of course, it can be put anywhere in your $PATH, personally I use ~/bin/.
If you're using zsh, add command completion for your shiny new vpnbox command as follows:
As a further convenience, you can modify the vpnbox command to start the VPN (if not already running) before executing the user-requested command.
passwords are for nerds
If you want to enable password-less access to the VPN network namespace, fire up sudo visudo and append a line such as the following
Note the final -- is important to prevent the user from passing other options to sudo.
There is a minor complication when starting firefox: The command vpnbox firefox --private-window does not work as expected! The boxed firefox process will first look for existing instances and if one is open, tell it to open the new window instead, leaving you with a new window that is not inside the network namespace.
To prevent this from happening, you have to specify --no-remote. However, in this case, you cannot open the same user profile with both firefox instances. Therefore, first setup a new profile called vpn using firefox -p. Now you can add an alias or command to start the profile in a tunneled instance:
This will open up a new tunneled firefox displaying your external IP address.
Leaving VPN config files unmodified
If you don't like to fiddle around with the VPN config files in expectation of making them harder to maintain when your VPN provider releases updated versions, you need not worry. The additional options can simply be given as command line arguments to openvpn instead, e.g.:
Moving the VPN adapter to a network namespace is the simplest and most failsafe way for a lot of use cases. However, it cannot hurt to have a few options at your disposal to learn from.
I know of three basic approaches to restrict certain applications to VPN:
The advantage of the network namespace is that it allows cleaner separation without further firewall rules and can also prevent normal applications from accessing the VPN tunnel.
Configure application to use VPN tunnel
Some applications allow to specify which network address they should bind to. The IP address can be obtained using a command such as (you best put this to your ~/.bashrc):
Now, you can for example wget through the VPN by doing:
To make this work, you must also create a routing table (as root, once):
and add to your VPN config:
and save the following route-up-nopull.sh script:
How this works:
- the ip rule add commands define rules that say all communication with the IP address of the VPN tunnel should be routed using the routing table called vpn.
- the table vpn defines only one route: through the VPN tunnel device
- DNS requests may still be going over your unencrypted connection
- the VPN interface is visible to all applications, but they will not use it as long as you do not add a route through the tunnel.
- sensitive applications see all network interfaces, but they will not use them if they are programmed properly and the routing table contains no other routes.
- in principal all applications can use both network interfaces
Start applications with dedicated user/group
A commonly suggested possibility is to create a special user or group and create firewall rules that will route all traffic of the user using a dedicated routing table.
WARNING: I deem this method unsafe and advise against using it. For more details, see the end of the section.
This solution also requires a routing table (if you haven't created it already for the previous approach):
Also, create a linux group vpn (don't confuse the group with the table, their names can be chosen independently, but I happen to like vpn in both cases):
Creating a dedicated user is the more commonly described variant of this approach, but I prefer using a group. It seems more modular to me in the sense that it allows to start VPN constrained applications as any particular user, i.e. without having to worry about filesystem access, etc...
Now add to your OpenVPN config file:
And place the setup-for-group.sh script in your openvpn folder:
Note that there is no conflict in sharing the same vpn routing table with the one needed for the solution in the previous section.
The command prefix to start tunneled applications is now sudo -g vpn --, e.g.:
Nice, this was easier than expected. But do I really have to enter my password? If you prefer not to, fire up sudo visudo and append a line as the following
This allows the user alice to start applications with group vpn without having to enter her password.
WARNING: This method can leak traffic if for some reason the routing table/iptable rules are ineffective, e.g.:
- some unforseen edge-case is not covered
- one or more of the rules is deleted (playing with your firewall?)
- other rules interfere
- before the rules are created
To emphasize: Before the rules are in effect there is no protection at all. The implementation given here sets up the rules after starting the VPN rather than at system boot, which means that programs will happily communicate over the default interface until the VPN is first started.
In fact, it would be much better to setup all static rules (i.e. everything done in the up() function except for the MASQUERADE rule) at system boot time rather than when the VPN starts.
Virtual ethernet tunnel to network namespace
I have already shown how to enforce VPN inside a network namespace by moving the adapter to the namespace (Recommended solution). While this is most likely the best choice in most cases, there is a set of variants of this strategy which I find more delightful from a learning perspective about linux network technology, and which I will list just for the fun of it.
The basic idea is to first create a virtual ethernet adapter pair and then move one of the adapters into the netns. We will put this functionality into a /etc/openvpn/create-veth-pair.sh script.
From here there are several slightly different ways to get VPN within the netns:
- Start VPN normally; leave it outside the netns but connect it to the VPN adapter tunneling into the netns
- Start VPN normally; then move it into netns; then connect the VPN adapter to the virtual ethernet peer in the netns
- Bridge the outer virtual ethernet adapter to your ethernet/wifi and then start VPN directly inside the netns
In every case, applications can now be started with the vpnbox command. However, unlike for the Recommended solution, these methods do also establish principal a connection for all applications to both the plain network and the VPN — which means that it is possible to simultaneously support the two alternative methods (Configure application to use VPN tunnel, Start applications with dedicated user/group) described in the previous sections.
Be aware that these options offer little benefit compared with the recommended solution, and they are far worse in terms of complexity. I believe it is easy to miss some edge-case when designing the firewall rules required to make these variants work, resulting in the possibility to leak traffic in some way or the other. Personally, I wouldn't trust myself doing it correctly given my limited knowledge in this subject.
In particular, the third variant will not protect you against leaking traffic when the VPN goes down, if you don't take special care.
I will not discuss implementations for these methods in further detail. You can get an idea how to achieve this from the methods presented above as well as the following resources:
- Bridging an ethernet with a virtual ethernet adapter
- Nice illustration of virtual ethernet adapter pairs
- You should also get a fair knowledge about iptables.