Hack 80 Create a Cross-Platform VPN

figs/expert.gif figs/hack80.gif

Use OpenVPN to easily tie your networks together.

Creating a VPN can be quite difficult, especially when dealing with clients using multiple platforms. Quite often, a single VPN implementation isn't available for all of them. As an administrator, you can be left with trying to get different VPN implementations to operate on all the different platforms that you need to support, which can become a nightmare.

Luckily, someone has stepped in to fill the void in cross-platform VPN packages and has written OpenVPN (http://openvpn.sourceforge.net). It supports Linux, Solaris, OpenBSD, FreeBSD, NetBSD, Mac OS X, and Windows 2000/XP. OpenVPN achieves this by implementing all of the encryption, key-management, and connection-setup functionality in a user-space daemon, leaving the actual tunneling portion of the job to the host operating system.

To accomplish the tunneling, OpenVPN makes use of the host operating system's virtual TUN or TAP device. These devices export a virtual network interface, which is then managed by the openvpn process to provide a point-to-point interface between the hosts participating in the VPN. Instead of traffic being sent and received on these devices, it's sent and received from a user-space program. Thus, when data is sent across the virtual device, it is relayed to the openvpn program, which then encrypts it and sends it to the openvpn process running on the remote end of the VPN link. When the data is received on the other end, the openvpn process decrypts it and relays it to the virtual device on that machine. It is then processed just like a packet being received on any other physical interface.

OpenVPN uses SSL and relies on the OpenSSL library (http://www.openssl.org) for encryption, authentication, and certification functionality. Tunnels created with OpenVPN can either use preshared static keys or take advantage of TLS dynamic keying and digital certificates. Since OpenVPN makes use of OpenSSL, it can support any cipher that OpenSSL supports. The main advantage of this is that OpenVPN will be able to transparently support any new ciphers as they are added to the OpenSSL distribution.

If you're using a Windows-based operating system, all you need to do is download the executable installer and configure OpenVPN. On all other platforms, you'll need to compile OpenVPN yourself. Before you compile and install OpenVPN, make sure that you have OpenSSL installed. You can also install the LZO compression library (http://www.oberhumer.com/opensource/lzo/), which is generally a good idea. Using LZO compression can make much more efficient use of your bandwidth, and even greatly improve performance in some circumstances. To compile and install OpenVPN, download the tarball and type something similar to this:

$ tar xfz openvpn-1.5.0.tar.gz

$ cd openvpn-1.5.0

$ ./configure && make

If you installed the LZO libraries and header files somewhere other than /usr/lib and /usr/include, you will probably need to use the --with-lzo-headers and --with-lzo-lib configure script options.

For example, if you have installed LZO under the /usr/local hierarchy, you'll want to run the configure script like this:

$ ./configure --with-lzo-headers=/usr/local/include \


If the configure script cannot find the LZO libraries and headers, it will print out a warning that looks like this:

LZO library and headers not found.

LZO library available from http://www.oberhumer.com/opensource/lzo/

configure: error: Or try ./configure --disable-lzo

If the script does find the LZO libraries, you should see output on your terminal that is similar to this:

configure: checking for LZO Library and Header files...

checking lzo1x.h usability... yes

checking lzo1x.h presence... yes

checking for lzo1x.h... yes

checking for lzo1x_1_15_compress in -llzo... yes

Now that that's out of the way, you can install OpenVPN by running the usual make install. If you are running Solaris or Mac OS X, you'll also need to install a TUN/TAP driver. The other Unix-based operating systems already include one, and the Windows installer installs the driver for you. You can get the source code to the Solaris driver from the SourceForge project page (http://vtun.sourceforge.net/tun/). The Mac OS X driver is available in both source and binary form from http://chrisp.de/en/projects/tunnel.html.

Once you have LZO, OpenSSL, the TUN/TAP driver, and OpenVPN all installed, you can test everything by setting up a rudimentary VPN from the command line.

On machine A (kryten in this example), run a command similar to this one:

# openvpn --remote zul --dev tun0 --ifconfig

The command that you'll need to run on machine B (zul) is a lot like the previous command, except the arguments to --ifconfig are swapped:

# openvpn --remote kryten --ifconfig

The first IP address is the local end of the tunnel, and the second is for the remote end; this is why you need to swap the IP addresses on the other end. When running these commands, you should see a warning about not using encryption, as well as some status messages. Once OpenVPN starts, run ifconfig to see that the point-to-point tunnel device has been set up:

[andrew@kryten andrew]$ /sbin/ifconfig tun0

tun0: flags=51<UP,POINTOPOINT,RUNNING> mtu 1300

        inet --> netmask 0xffffffff

Now try pinging the remote machine, using its tunneled IP address:

[andrew@kryten andrew]$ ping -c 4

PING ( 56 data bytes

64 bytes from icmp_seq=0 ttl=255 time=0.864 ms

64 bytes from icmp_seq=1 ttl=255 time=1.012 ms

64 bytes from icmp_seq=2 ttl=255 time=0.776 ms

64 bytes from icmp_seq=3 ttl=255 time=0.825 ms

--- ping statistics ---

4 packets transmitted, 4 packets received, 0% packet loss

round-trip min/avg/max = 0.776/0.869/1.012 ms

Now that you have verified that OpenVPN is working properly, it is time to create a configuration that's a little more useful in the real world. First you will need to create SSL certificates [Hack #45] for each end of the connection. After you've done this, you'll need to create configuration files and connection setup and teardown scripts for each end of the connection.

Let's look at the configuration files first. For these examples, zul will be the gateway into the private network and kryten will be the external client.

The configuration file for zul that is used for kryten is stored in /etc/openvpn/openvpn.conf. Here are the contents:

dev tun0


up /etc/openvpn/openvpn.up

down /etc/openvpn/openvpn.down


dh /etc/openvpn/dh1024.pem

ca /etc/ssl/ca.crt

cert /etc/ssl/zul.crt

key /etc/ssl/private/zul.key

ping 15

verb 0

You can see that the dev and ifconfig options are used in the same way as they are on the command line. The up and down options specify scripts that will be executed when the VPN connection is initiated or terminated. The tls-server option enables TLS mode and specifies that you want to designate this side of the connection as the server during the TLS handshaking process. The dh option specifies the Diffie-Hellman parameters to use during key exchange.These are encoded in a .pem file and can be generated with the following openssl command:

# openssl dhparam -out dh1024.pem 1024

The next few configuration options deal with the SSL certificates. The ca option specifies the Certificate Authority's public certificate, and the cert option specifies the public certificate to use for this side of the connection. Similarly, the key option specifies the private key that corresponds to the public certificate. To help ensure that the VPN tunnel doesn't get dropped from any intervening firewalls that are doing stateful filtering, the ping option is used. This causes OpenVPN to ping the remote host every n seconds so that the tunnel's entry in the firewall's state table does not time out.

On kryten, the following configuration file is used:

dev tun0

remote zul


up /etc/openvpn/openvpn.up

down /etc/openvpn/openvpn.down


ca /etc/ssl/ca.crt

cert /etc/ssl/kryten.crt

key /etc/ssl/private/kryten.key

ping 15

verb 0

The main differences with this configuration file are that the remote and tls-client options have been used. Other than that, the arguments to the ifconfig option have been swapped, and the file uses kryten's public and private keys instead of zul's. To turn on compression, add the comp-lzo option to the configuration files on both ends of the VPN.

Finally, create the openvpn.up and openvpn.down scripts on both hosts participating in the tunnel. These scripts set up and tear down the actual routes and other networking requirements.

The openvpn.up scripts are executed whenever a VPN connection is established. On kryten it looks like this:


/sbin/route add -net gw $5 netmask

This sets a route telling the operating system to send all traffic destined for the 10/24 network to the remote end of our VPN connection. From there it will be routed to the interface on zul that has been assigned an address from the 10/24 address range. The $5 in the script is replaced by the IP address used by the remote end of the tunnel. In addition to adding the route, you might want to set up nameservers for the network you are tunneling into in this script. Unless you are doing something fancy, the openvpn.down script on kryten is empty, since the route is automatically dropped by the kernel when the connection ends.

No additional routes are needed on zul, because it already has a route to the network that kryten is tunneling into. In addition, since tun0 on zul is a point-to-point link between itself and kryten, there is no need to add a route to pass traffic to kryten?by virtue of having a point-to-point link, a host route will be created for kryten.

The only thing that needs to be in the openvpn.up script on zul is this:


arp -s $5 00:00:d1:1f:3f:f1 permanent pub

This causes zul to answer ARP queries for kryten, since otherwise the ARP traffic will not be able to reach kryten. This sort of configuration is popularly called proxy arp. In this particular example, zul is running OpenBSD. If you are running Linux, simply remove the permanent keyword from the arp command. Again, the $5 is replaced by the IP address that is used at the remote end of the connection, which in this case is kryten's.

The openvpn.down script on zul simply deletes the ARP table entry:


arp -d kryten

Unfortunately, since scripts run through the down configuration file option are not passed an argument telling them what IP address they should be dealing with, you have to explicitly specify the IP address or hostname to delete from the ARP table. Now the only thing to worry about is firewalling. You'll want to allow traffic coming through your tun0 device, as well as UDP port 5000.

Finally, you are ready to run openvpn on both sides, using a command like this:

# openvpn --config /etc/openvpn/openvpn.conf --daemon

Setting up OpenVPN under Windows is even easier. Simply run the installer, and everything you need will be installed onto your system. This includes OpenSSL, the TUN/TAP driver, and OpenVPN itself. The installer will also associate the .ovpn file extension with OpenVPN. Simply put your configuration information in a .ovpn file, double-click it, and you're ready to go.

This should get you started using OpenVPN, but it has far too many configuration options to discuss here. Be sure to look at the OpenVPN web site for more information.