wiki:Vpn

Methods for Creating a VPN

I like to work on my desktop computer. It's got plenty of memory, plenty of cores, and I've got all the software I need set up just how I like it. It's also nice to have non-miniaturized input peripherals and two big displays. When I'm not home, I like to use whatever device I have to access my desktop computer so that I always have a familiar environment. There are several methods I use to stay connected, from simple port-forwarding to full-fledged VPN.

This article is not so much a complete guide to each method of VPN; rather, I'll explain in brief what I use to connect with my desktop at home.

Port-forwarding Using OpenSSH

My desktop is always connected, but it's behind a gateway. I can SSH to the gateway and from there SSH to my desktop, but it's often more convenient to throw up a quick "tunnel" and SSH to my desktop, as if directly. Many people do this, it works really well, and it's easy. It works by opening a port on your local device and forwarding traffic to and from that and a port on some remote device behind the gateway, like this:

ssh -L2000:compy:22 chaz@dogcows.com -NC

In this example, 2000 refers to the local port on the device I'm using, compy is the hostname (or IP address on the private subnet) of my desktop machine behind the gateway, and 22 is the port number to which connections will be made on that machine. With this command, you're actually logging into the gateway and not yet into any other machine which may be behind the gateway. In this example, I have a user, chaz, on the gateway which I use to login and create the port-forwarding process. The option -N means that I'm not interested in accessing a shell on the gateway; the port-forwarding is all I want from the connection. The option -C means to compress network traffic, which is often useful. Of course, since this is SSH, all forwarded traffic is also encrypted.

Now, to actually login to my desktop at home, it's a simple matter of just using SSH to connect to the local port that was just opened:

ssh -p2000 chaz@localhost -Y

The only thing interesting about this command is the fact that I'm explicitly using a non-standard port and seemingly connecting to the machine I'm currently on. I use port 2000 so that I don't have to be root and so the port used for forwarding won't conflict with any real SSH server I might have running on the local machine. The port-forwarding will make it look like I'm connected directly with my desktop computer at home. The option -Y is just used to enable X11 forwarding.

Proxying With OpenSSH

Now let's go a step further. I actually have several services on my internal network which, for various good reasons usually related to security, are not accessible from outside the network. For instance, my router has a control panel accessible via HTTP, and the firewall rightly prohibits connections from outside the network.

It may be possible to forward more local ports to any protected services I need, but there's an easier way. Using dynamic port-forwarding, it's possible to throw up a quick SOCKS proxy to access internal services, as if from the gateway. This allows for a type of application-level port-forwarding, and it's also very simple to use:

ssh -D8080 chaz@dogcows.com -NC

Now it's as if there is a SOCKS proxy running on the local machine, though it's actually on the gateway. From here it's a simple matter to set up a web browser to use the proxy at localhost:8080. Once set up, internal HTTP services can be accessed as if you were on the private network. To make it even quicker, on my laptop, I have  FoxyProxy installed so that toggling between a proxy at localhost:8080 and no proxy is easy. It should also be noted, in case it's not completely obvious, that the local and dynamic port-forwarding options can be combined. I use this to open a port directly to my desktop as well as a SOCKS proxy:

ssh -L2000:compy:22 -D8080 chaz@dogcows.com -NC

This is also a quick and easy way to get around internet filters, especially on campus or on corporate networks. You'll have as much access as you have from your home network, and web servers will log the IP address you use to connect to the internet at home.

Tunneling With OpenSSH

Oh, but OpenSSH is even more versatile still, and dynamic port-forwarding won't solve everything. Proxying requires application-awareness, and some applications cannot use it. With tunneling, we're now stepping into the realm of what most people think of when the term VPN is mentioned. That is, with the OpenSSH tunneling feature and some routing tricks, we can immitate being connected to a private subnet from anywhere across the internet, either at the IP level or the ethernet level. The magic is made possible by tun/tap drivers available for most operating systems. These drivers can create virtual network interfaces. OpenSSH can be used to create such an interface at each end and facilitate traffic between them.

Now, it's neat that OpenSSH can do this, but this method does have its drawbacks:

  • It requires root access on both ends of the connection.
  • It is considerably more complicated to set up than local or dynamic port-forwarding.
  • Tunneling TCP through TCP is just weird, and it can cause abnormal strain on the network, causing degraded performance.

Nevertheless, many people consider this approach an acceptable way to do "quick 'n dirty" VPN. First, make sure the SSH server on the gateway is set up to allow this. The option PermitTunnel must be set in your sshd_config:

PermitTunnel yes

You must also make sure that your kernel is tun/tap-ready, both on the gateway and on your mobile computer. On Linux, the driver may be compiled as a kernel module which must be loaded:

modprobe tun

Now, to create the tunnel, I use a command such as this:

ssh -w0:0 root@dogcows.com -C

The option -w is what causes a tunnel to be created, and the 0:0 refers to the tun/tap device numbers on the local and remote sides, respectively. If you get "administratively prohibited" errors, it may be because the devices you've specified already exist. I should also point out that I have had no luck getting this to work with NetBSD's sshd on the gateway, and I believe that implementation is broken with regards to tunneling, at this time anyway. Fortunately, the version of OpenSSH in pkgsrc works like a charm.

After creating the tunnel, it will not be of much use until the interfaces at each end are given addresses and the routing is set up appropriately. For that, I usually put the virtual interfaces on the 10.7.0.0/24 subnet. So, I give my gateway the 10.7.0.1 address and my local device the 10.7.0.2 address. On the gateway, which runs NetBSD, a point-to-point link can be set up with the interface like this:

ifconfig tun0 10.7.0.1 10.7.0.2 netmask 0xffffff00

On the client, it's just the opposite, only the syntax for the ifconfig command is a little different on a Linux machine, requiring an extra pointopoint keyword:

ifconfig tun0 10.7.0.2 pointopoint 10.7.0.1 0xffffff00

At this point, the link is established and you should be able to ping the gateway at its new address. To access the private subnet at home, behind the gateway, it's now just a matter of setting up some routing rules. On my router at home, I have a static routing rule set up to forward traffic bound for 10.0.0.0/8 to my gateway. The gateway will then forward traffic bound for 10.7.0.2 across the link, so that machines at home on my private subnet will be able to send packets to me. That's half the battle, but I also need to set up routing on the mobile client in order to push packets bound for the private subnet across the link. Since my home network is on the 192.168.7.0/24 subnet, I add the route, using the iproute2 tools, like this:

ip route add 192.168.7.0/24 via 10.7.0.1

Basically this rule means that any packets bound for an address 192.168.7.* will go through the tunnel interface to be forwarded on the other side. Be advised that you're going to have complications if your home subnet is the same as the one connected to your actual interface; it's best if you just choose a more unique subnet for your home so you don't have to deal with such a situation.

Or you can set it up so that all traffic goes through the link:

ip route add $HOMEADDR/32 via $LOCALGWAY
ip route change default via 10.7.0.1

In this example, $HOMEADDR is the IP address of my home gateway in front of my own private subnet and $LOCALGWAY is the IP address of the router on the local network I'm currently attached to. The first rule will make sure outbound traffic to my gateway will still go through the public internet. If it were not so, the link between the virtual interfaces would break. Everything else goes through the tunnel.

At this point, you should be able to ping any machine in the private subnet. The only thing left to do is to optionally set the nameservers in /etc/resolv.conf to something more appropriate. If the nameservers were on the local network, they will no longer be accessible after completing the VPN, and domain names will not be able to resolve.

Finally, VPN With OpenVPN

Setting up a VPN like this with OpenSSH can be a bit of a hassle, as you have seen. One alternative is to set up an OpenVPN server on the gateway and let OpenVPN handle all of the nitty gritty. This approach has its advantages:

  • It's designed specifically for this sort of thing.
  • You don't typically have to set up the routing table yourself.
  • It's built on top of OpenSSL and can use either a TCP or UDP transport.
  • It doesn't require IPsec like other VPN solutions; presumably it's more friendly toward mis-configured routers.

The disadvantage of OpenVPN over OpenSSH is that it's not SSH, meaning that after all we've been able to accomplish with just a SSH server running, we'll now need to take the leap and run another daemon. The versatility of OpenSSH has given us what we can consider true VPN, but if we want the advantages listed, we need OpenVPN. Oh well.

To get this working, you need to create some encryption keys. OpenVPN usually comes with a set of scripts called easy-rsa to make this, well, easy. They're usually installed to the shared folder (i.e. /usr/share/openvpn/easy-rsa). On the gateway, just copy that entire folder to some place where you can work:

cp -r /usr/share/openvpn/easy-rsa ~/
cd ~/easy-rsa

You'll be creating your own certificate authority, and there are some options to set, so first edit the vars file to your liking. I changed the key size and the identification information:

# The default key size should be just fine, too.
export KEY_SIZE=2048

export KEY_PROVINCE="UT"
export KEY_CITY="Salt Lake City"
export KEY_ORG="Sevenology"
export KEY_EMAIL="operator@dogcows.com"

Now source that file and make sure the destination directory is clean:

. ./vars
./clean-all

Now build the root certificate authority (CA) certificate:

./build-ca

You will be prompted for several fields. If you edited vars correctly, the values you entered should be the default. Just push enter a couple of times. Now, generate the certificate and key for the server:

./build-key-server server

In this example, server should be the hostname of the server. You should also enter this value as the Common Name when the script prompts. You can also password-protect certificates if you want to be asked for a password each time you use it. Then, sign and commit the certificate as prompted. You also need to build certificates and keys for each client:

./build-key lappy

Here, I'm creating a certificate and key for my laptop with hostname lappy. Whatever name you choose, remember to also enter it as the Common Name when prompted. Each client needs a unique name. Finally, we generate one more file with the Diffie Hellman parameters. It's not kidding when it says that it is going to take a long time:

./build-dh

Now the keys directory is full of your certificates and keys. The files you need to keep on the gateway are ca.crt, ca.key, dh1024.pem or dh2048.pem, server.crt, and server.key, where the gateway's certificate and key are named according to what you entered when you created them. Copy these files to your OpenVPN configuration directory (i.e. /etc/openvpn). The files you need to copy to the client are ca.crt, lappy.crt, and lappy.key, where lappy is the name you entered previously when creating the client's certificate and key. Remember that the key must remain private, so copy it over a secure channel. Copy those files to the OpenVPN configuration directory on your client machine.

To configure the OpenVPN server on the gateway, it's easiest to start from an example condiguration, a collection of which are typically installed with the OpenVPN package. Copy server.conf into the OpenVPN configuration directory and get to work editing it:

# Make sure these are the correct names for the files you copied to the
# configuration directory:
ca ca.crt
cert server.crt
key server.key
# Leave this as dh1024.pem if you stuck with 1024 bit keys:
dh dh2048.pem
# With this, the virtual devices will be on the 10.7.0.0/24 subnet:
server 10.7.0.0 255.255.255.0
# I use this so that VPN clients can direct packets to other machines on
# the private subnet:
push "route 192.168.7.0 255.255.255.0"
# I use this to cause VPN clients to set their default gateway through the
# VPN:
push "redirect-gateway def1 bypass-dhcp"
# I use this to set VPN client nameservers to those on my home subnet when
# they connect:
push "dhcp-option DNS 192.168.7.2"
push "dhcp-option DNS 192.168.7.1"
# This allows VPN clients to see each other:
client-to-client
# Finally, it's a good idea to let the OpenVPN server drop privileges once
# it's up and running:
user nobody
group nobody

Similarly, on the client, copy client.conf from the collection of example configurations to the OpenVPN configuration directory, and edit it to match the server settings:

# Set the hostname and port of the VPN gateway to connect to:
remote dogcows.com 1194
# Again, make sure you've copied the appropriate certificates and keys to
# the configuration directory:
ca ca.crt
cert lappy.crt
key lappy.key

And that's basically it! Start the OpenVPN server on the gateway:

openvpn --config /etc/openvpn/server.conf

Of course, make sure you use the actual path to your configuration file. Your operating system may have a better way to manage daemons. Making a VPN connection from the client is very similar:

openvpn --config /etc/openvpn/client.conf

A Final Word

Both OpenSSH and OpenVPN are capable of creating either level 3 (IP) or level 2 (ethernet) tunnels. What I've described in these examples is level 3 and guiding traffic via static routes. This method is better because it has lower overhead (not having to transmit the entire ethernet packet) and it's generally easier to setup. Yet, there are advantages to creating a level 2 tunnel, mainly that any level 3 protocol (think IPX, AppleTalk) will also work through the tunnel, as will frames sent to the broadcast address. This is important for some applications like Samba and games, so you'll need to set up a level 2 VPN if those are important to you.

References