For a long time, I have used SSH port forwarding to access a lot of my home files and services, but I’ve found myself hitting the limits of usefulness. So I decided to try and create a VPN. This isn’t a true VPN – it’s NAT over SSH with tun. Good enough for me.

Much of the heavy lifting for this post was done by daleroberts at his wiki site. My only real contribution here is around the last steps of getting the routing working.

So, here’s what you’ll need:

  1. Two macs – one is the remote “client” and one is the home “server”. Both should be running OS X 10.4.10+ client. If you have OS X Server, I’d suggest looking around for OpenVPN.
  2. Both machines connected to the network. You’ll probably want to use a service like DynDNS so you can access the server machine via a hostname, especially if your ISP uses dynamic IP addresses
  3. Your should know how to SSH to your home server
  4. Your should know how to enable and use the root account on both your remote machine and home server
  5. You should know how to handle yourself on the command line via Terminal.

Conventions used

  • The remote “client” (aka. the client) is the machine you are VPN-ing from. It is off the target network (aka. the home network). This is the machine you’re sitting at saying “I wish I had access to this file on my home network”
  • The home “server” (aka. the server) is the machine you are VPN-ing to. It serves as the gateway to all the other machines on your home network.
  • For the purposes of this example, the “home network” has an IP of ranges 192.168.0.1 to 192.168.0.255 – which will be notated as 192.168.0.0/24
  • I have used en0 as the interface on both the client and server machines – this is the Ethernet inteface. If either your client or server are connected via another mechanism (e.g. Airport), then you will need to use the appropriate interface at the appropriate time. This is only applicable to the client and server – if machines on your home network are connected wirelessly, etc. it should not be an issue.

Getting the Tun drivers setup

Even though OS X has a tun manpage, it appears to not actually have tun installed. The easiest way to get the tun/tap drivers is to download tunnelblick. You will need it both on the client and server machines. Once you have it, mount the disk image, and copy the application somewhere (for illustrative purposes, I put it on my Desktop).

At the command line, execute the following (assuming you are in your home dir):

sudo cp -rp Desktop/Tunnelblick.app/Contents/Resources/*kext /System/Library/Extensions/

cd /System/Library/Extensions

sudo chown -R root:wheel tun.kext

sudo chmod -R go-w tun.kext

sudo chown -R root:wheel tap.kext

sudo chmod -R go-w tap.kext

sudo kextload /System/Library/Extensions/tun.kext

Repeat the above on the server

The first SSH connection

Edit /private/etc/sshd_config on the server as root to permit tunnelling. Make sure the following lines are uncommented (in a default OS X install, they should be there, but will be commented out with a ‘#’ sign – you just need to delete the ‘#’)

PermitRootLogin yes
PermitTunnel yes

Then, from the client, execute:

sudo ssh -w 0:0 root@home while true \; do echo . \&\& sleep 60 \; done

Replace “home” with the internet available hostname of the server. The output should be a “period”, with a new “period” every 60 seconds.
If this worked correctly, you should be able to open a new command prompt on the client and execute the following:

ifconfig tun0

which should return

tun0: flags=8851 mtu 1500
open (pid ABCDE)

where ABCDE isn’t really relevant. If you execute the same ‘ifconfig tun0′ on the server, you should see something similar.

Setting up the VPN

Execute the following on the client:

sudo ifconfig tun0 172.16.0.1 172.16.0.2

ssh root@home ifconfig tun0 172.16.0.2 172.16.0.1 \&\& sysctl -w net.inet.ip.forwarding=1

Again, replacing “home” with the internet available hostname of the server.
To test this, add the route on your client

sudo route add -net 172.16.0 -interface tun0

You should now be able to access your server from your client via the IP address 172.16.0.2. Congrats, you now have a basic VPN

NAT

So, what if you have more machines on your home network than just your server? You’ll need to set up NAT and some routing rules
First, a route to push traffic to 172.16.0.* via the tun0 interface:

sudo route add -net 172.16.0 -interface tun0

Second, a route to push traffic headed to the home network via the 172.16.0.2 gateway

sudo route add 192.168.0 172.16.0.2

Finally, you need to set up NAT on the server:

sudo /usr/sbin/natd -interface en0 -l -s -m

sudo ipfw add 00002 allow ip from any to any via tun0

sudo ipfw add 00003 divert 8668 ip from any to 192.168.0/24 via en0

sudo ipfw add 00004 allow ip from any to 172.16.0.1

You should now be able to access any of the machines on the home network by IP address!

Getting it running the next time

I have setup some aliases and scripts so I don’t have to remember the exact commands to execute. This is probably not the most efficient way to do this, but it’s better than nothing.

  1. (Optional) If NAT is not running on the server, you will need to get it setup. I have dumped the server commands from above into a shell script and just ssh into the box, and execute the script
  2. Execute the first ssh tunnel on the client: “sudo ssh -w 0:0 root@home while true \; do echo . \&\& sleep 60 \; done” Leave it running
  3. Execute the second ssh tunnel on the client: “sudo ifconfig tun0 172.16.0.1 172.16.0.2; sudo ssh root@home ifconfig tun0 172.16.0.2 172.16.0.1 \&\& sysctl -w net.inet.ip.forwarding=1″ If successful, this will return the prompt to you
  4. Setup the routes on the client: “sudo route add -net 172.16.0 -interface tun0; sudo route add 192.168.0 172.16.0.2″ The VPN is running
    When you’re done

  1. Remove the routes on the client: “sudo route delete 172.16.0; sudo route delete 192.168.0″
  2. Kill the running ssh client with a Control-C
  3. That’s it. I’m sure some people will comment on how this could be made better, but it’s meant to be quick and dirty but work.