Setting Up DH + TLS-based VPN With OpenVPN on a Tomato-based Home Router

I recently spent the better part of an afternoon trying to get TLS and Diffie-Hellman VPN working with OpenVPN on a Tomato-based router. This is farily aggressive VPN configuration in terms of protecting the data stream; see the OpenVPN docs for benefits of this setup. Despite the OpenVPN and Tomato folks providing pretty decent documentation, the process was less than straight-forward, mostly due to the relative lack of documentation from the Ubuntu side, and so I thought I would share the details both for folks who might be struggling with similar and also for posterity. I expect I’ll have to do this again at some point. ( Sure would be nice if the Open Source router folks provided REST APIs for this kind of thing so this stuff could be automated with cfgmgmt tools… Perhaps I should stop complaining and work out a pull request. ;) )

FWIW, I advise keeping the CA data, client config, and router configs in a private git repo for safe-keeping and rollback purposes. I sometimes fat-finger a small detail and hate having to deal with certificate revocation. Much easier to just version control the whole thing and rollback to an earlier commit of the CA data when I screw something up. Also, sometimes SOHO-grade routers die and its pretty easy to just upload an old config when you get a new router than re-configuring everything by hand.

That piece of advice out of the way, let’s get started…

Creating the crypto artifacts

First, let’s start with the setup of the required crypto artifacts. Thankfully, the OpenVPN documentation for the ‘easy-rsa’ tooling is quite good. Make sure to generate the following:

a CA cert a “server” cert for your Tomato-based gateway/router a “user” cert and key for each user you would like to have access to the VPN a Diffie-Hellman static key generated with the ‘openvpn’ utility and the ‘–genkey’ and ‘–secret’ options

Setting up the server-side of the OpenVPN TAP

The Basic tab

Once you have each of the above crypto artifacts you are ready to configure the server-side of the VPN. I’m assuming you have some experience with Tomato and I don’t need to walk you through logging into the web-based interface to Tomato. In my case, and in the screenshots below, I’m using the Shibby version of Tomato with a custom theme and running on a NETGEAR Nighthawk R7000. As far as I know none of those things make for any discernible difference for anyone using Tomato on different hardware and the theming is cosmetic only.

When you are logged into the router’s web-based admin you’ll find the VPN configuration via the left-side menu under the “VPN Tunneling” section. Screenshots follow for each of the relevant configs tabs under “Server 1”:

OpenVPN Basic settings

A couple of things to note in the above image:

First, the tls-auth option in the dropdown at the bottom of the screen allows 3 values: Bi-directional, Incoming(1), and Outgoing(2). You’ll want to choose either Incoming(1) or Outgoing(2) and make sure to choose the opposite when setting up the client side of the VPN tunnel.

Second, I’ve chose to use a tunnel type of “TAP”. This means the VPN connection will be at Layer 2 (as opposed to “TUN” which is Layer 3). A Layer 2 tunnel will make the client appear to be directly on the LAN at the other end of the tunnel which is the less efficient approach in terms of minimizing the amount of traffic across the tunnel but also is the most functional in that it makes it easier for your client side to access nodes on the other end of the tunnel using various broadcast protocols like Avahi/Zeroconf and Bonjour.

Third, notice I’ve turned off DHCP for tunnel addresses. I’ve never been able to get DHCP working over a Tomato VPN tunnel. Instead I split the IP ranges on the LAN side into non-overlapping ranges with some static and some DHCP-managed. For instance, I have the following:

  • LAN network of x.x.x.0/24
  • x.x.x.1-10 - non-DHCP and reserved for use of networking devices or other odds-and-ends which might not support DHCP
  • x.x.x.11-100 - statically-assigned nodes (in my case primarily for Openstack Floating IPs)
  • x.x.x.101-149 - IP address for VPN tunnel termination
  • x.x.x.150-254 - DHCP addresses

Ok, on to the Advanced tab.

The Advanced tab

OpenVPN Advanced settings

A few things to call out for the Advanced tab…

  • I did a fair amount of research as to the best setting for ‘Encryption cipher’. In the end, ‘AES-256-CBC’ seemed to offer the best protection with the greatest likelihood of also being supported on any clients which might be connecting.

  • I believe the option for ‘Allow User/Pass Auth’ was checked by default. Since we are building out a CA where each user will have their own cert and key there’s really not a good reason to allow u/p auth and open up the possibility of someone grinding that data or somehow getting access to user credentials and accessing the VPN.

  • You might choose to turn the ‘Direct clients to redirect Internet traffic’ on if you’d prefer that clients not do split tunneling.

  • Because I’m sending DNS settings to VPN clients my internal node names are resolvable when a client is connected to the VPN. Kinda nice.

The Keys tab

OpenVPN Keys settings

The Keys tab is fairly self-explanatory; just copy/paste the cert and key information from the CA you generated in the first section of this blog. A couple of things to note here:

  • Make sure you don’t include anything but cert data in the server cert field. The OpenVPN docs are very clear on this matter but it is easy to overlook.

  • The Diffie-Hellman static symmetric key field is fine with or without the included comments.

Wrapping up the server-side config

At this point the server side of the VPN tunnel should be ready but, as we are about to see, that doesn’t mean that things will go smoothly on the client side. It’s a good idea to get familiar with the Status/System Logs and Administration/Admin Access of the Tomato web admin. The System Logs section will allow to see the router’s syslog in a web brower. In Administration/Admin Access you can enable SSH-based access to the router which then allows you to login to the router and use ‘tail -f’ on /var/log/syslog. Both are supremely useful if you need to do any troubleshooting of the VPN tunnel setup and tear down.

Seting up the client-side of the OpenVPN TAP

The process of configuring the client-side of the OpenVPN tunnel will vary wildly depending on the client OS. I’ll demonstrate the process for Ubuntu 15.04 using the GNOME desktop and the bundled config tools for NetworkManager. We’ll start at NetworkManager/Edit Connections/Add/OpenVPN. That brings up the first screen(see below) where we’ll set the NetworkManager connection name and, more importantly the hostname/IP address of the VPN tunnel device.

OpenVPN top-level

Things really start to get interesting once we click the ‘Advanced’ button in the lower right-hand corner which takes us to the General tab where we set the tunnel type to ‘TAP’ and enable LZO compression.

OpenVPN General tab

In the next tab, Security, we’ll select the cipher type which we set on the server-side, AES-256-CBC, and enable the HMAC Authentation by selecting ‘SHA-1’. Note that nowhere in the OpenVPN docs, at least not anywhere I can find it, is it mentioned that the DH key we generated is of type SHA-1.

OpenVPN Security tab

Lastly, we set a few things in the TLS Authentication tab. Namely, find the DH key in your CA repository and set the Key Direction to the opposite value of what you set server-side. For the purposes of this post that would mean setting this value to ‘1’.

OpenVPN TLS tab

The Subject Match and certificate matching are optional but probably not a bad idea as an extra measure.


As I mentioned previously, I find it very useful to be able to login to the router tail the syslog output:

# tail -f /var/log/syslog

Similarly, assuming the client-side is Ubuntu 15.04 which uses systemd, you can get the client-side logs via:

$ sudo journalctl -f

The error messages spit out by NetworkManager and OpenVPN are actually quite useful and accurate.


Comments powered by Disqus