Quick and dirty VPN with stunnel and pppd to a network behind NAT

Punching holes in firewalls for fun and profit

Updated 8 Feb 2008 to check certificates at both ends

Our new office is behind NAT but we want to be able to connect into it from the outside world. What to do? Set up a VPN of course.

We have a webserver with a public IP address which we can SSH into. We'll use that as one end of the VPN, so we can ssh into that and then ssh out again into our office. Here's what you'll need:

First create a "vpn" directory on your server with the public IP address (the "server"), and the same directory on the machine behind the NAT firewall (the "client"). Change into that directory on both boxes, then create a keypair to encrypt the exchange:

openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout vpn.pem -out vpn.pem
and put a copy of "vpn.pem" in the "vpn" directory on both the client and server. It doesn't matter what fields you put in the certificate.

Next, on the server create the "vpn.conf" file so it looks like this:

CAfile  = vpn.pem
cert    = vpn.pem
key     = vpn.pem
output  = vpn.log
verify  = 2
#debug   = 7
client  = no
foreground = no

[vpn]
accept = 9871
exec = /usr/bin/sudo
execargs = /usr/bin/sudo /usr/sbin/pppd debug noauth 192.168.254.254:192.168.254.253
pty = yes
We're going to put the endpoint for our VPN on port 9871, but it doesn't matter what it is so long as it's not firewalled. We're also going to use the two addresses 192.168.254.254 and 192.168.254.253 as the endpoints for our VPN. Check you have sudo access for this, or run it as root.

stunnel vpn.conf

Then, on the client, create a "vpn.conf" that looks like this:

CAfile  = vpn.pem
cert    = vpn.pem
key     = vpn.pem
verify  = 2
#debug   = 7
output  = vpn.log
foreground = no
client  = yes
connect = public.server.com:9871

Change "public.server.com" to your public web server and make sure the port matches your server part. That's it! To fire up the ppp link, run this command on the client:

pppd noccp novj novjccomp nopcomp noaccomp noauth debug dump logfd 2 passive updetach name -client linkname vpn1 pty "stunnel vpn.conf"
This should bring up a link and background it. You can now ssh onto your public server and then ssh out again to 192.168.254.253 to get behind the NAT. In fact, any ports open on the client will be accessible unless you've firewalled them. You're checking certificates at both ends (that's the "verify=2"), so anyone else connecting to the port won't be able to do anything with it. If you have trouble, set the "debug" parameter in the conf files to something useful.

If you're on a flakey connection, run a cron job on the client to bring the link up if it drops. Something like this worked for us (running on Debian Etch)

cd /root/stunnel && /sbin/ifconfig ppp0 > /dev/null 2>&1 || /usr/sbin/pppd noccp novj novjccomp nopcomp noaccomp noauth debug dump logfd 2 passive updetach name -client linkname vpn1 pty "/usr/bin/stunnel4 vpn.conf"

For bonus points, set up a port forward on the server to forward to the client, so you can ssh directly into the client from the public internet. Something like this in fact:

iptables -t nat -A PREROUTING -p tcp --dport 2022 -j DNAT --to-destination 192.168.254.253:22
iptables -t nat -A POSTROUTING -d 192.168.254.253 -j SNAT --to 192.168.254.254
The first line routes packets from 2022 on the server to 22 on the client, and the second line rewrites the response to make sure they can get back. Then simply
ssh public.webserver.com -p 2022
and watch as layers of expensive firewalls are neatly sidestepped and you connect directly through to the client. Have fun, and remember - if your internal network gets hosed, it's entirely your fault.

mike at bfo dot co dot uk