A client of mine recently had need of a VPN link between their main office and a remote office. Based on previous experience, I decided the OpenVPN version of DD-WRT running on two low-cost routers would provide a cost-effective solution to the problem. My goal was to create a VPN that would allow systems on either side of the connection to have connectivity with systems on the other side, but not pass broadcast traffic. I also wanted the routers to automatically connect on startup and continually try to reconnect if Internet connectivity was lost. I couldn’t find any other documentation on the Interwebs that described this solution, so I decided to lay it out here on ShortBus.
Installing the Firmware
The first thing you will need is two routers capable of running DD-WRT. In my case, I chose two Buffalo WHR-G54S’s. Next, you will need to download the latest VPN version of DD-WRT. Make sure your get the right version for your brand of router. Finally, follow the firmware installation instructions making sure you check for any special procedure for your router. Buffalo routers require a unique procedure for the initial flash of DD-WRT.
The LAN subnets of the routers must be different for this configuration. This needs to be setup in the web interface of DD-WRT. For the purposes of this tutorial, the OpenVPN server subnet is 192.168.1.0/255.255.255.0 and the OpenVPN client subnet is 192.168.2.0/255.255.255.0
Creating OpenSSL Keys
The next step is to generate Public Key Infrastructure (PKI) certificates and keys for your routers. The easiest way to do this is to download a copy of OpenVPN onto your local system. If you use Ubuntu Linux, you can do this with
# apt-get install openvpn
The key generation scripts are located in /usr/share/doc/openvpn/examples/easy-rsa. cd there and follow the PKI generation instructions on the OpenVPN web site. You need to generate a client cert/key pair the server and one for each client. Be sure to give each client certificate a unique Common Name.
Creating the DD-WRT Startup Scripts
The procedure here is to generate the OpenVPN config files and cert/key files from the startup script in /tmp on each boot.
Replace the …INSERT YOUR OWN CONTENT HERE… with the certs/keys generated in the last section.
rc_startup – server
# all files will be created in /tmp
cd /tmp
openvpn –mktun –dev tun0
ifconfig tun0 10.8.0.1 netmask 255.255.255.0 promisc up
echo \”
# Tunnel options
mode server # Set OpenVPN major mode
proto udp # Setup the protocol (server)
port 1194 # TCP/UDP port number
dev tun0 # TUN/TAP virtual network device
keepalive 15 60 # Simplify the expression of –ping
daemon # Become a daemon after all initialization
verb 3 # Set output verbosity to n
comp-lzo # Use fast LZO compression
client-config-dir ccd
route 192.168.2.0 255.255.255.0
client-to-client
server 10.8.0.0 255.255.255.0
push \\\”route 192.168.1.0 255.255.255.0\\\”
push \\\”route 192.168.2.0 255.255.255.0\\\”
# TLS Mode Options
tls-server # Enable TLS and assume server role during TLS handshake
ca ca.crt # Certificate authority (CA) file
dh dh1024.pem # File containing Diffie Hellman parameters
cert server.crt # Local peer’s signed certificate
key server.key # Local peer’s private key
\” > openvpn.conf
echo \”
—–BEGIN CERTIFICATE—–
…INSERT YOUR OWN CONTENT HERE…
—–END CERTIFICATE—–
\” > ca.crt
echo \”
—–BEGIN RSA PRIVATE KEY—–
…INSERT YOUR OWN CONTENT HERE…
—–END RSA PRIVATE KEY—–
\” > server.key
chmod 600 server.key
echo \”
—–BEGIN CERTIFICATE—–
…INSERT YOUR OWN CONTENT HERE…
—–END CERTIFICATE—–
\” > server.crt
echo \”
—–BEGIN DH PARAMETERS—–
…INSERT YOUR OWN CONTENT HERE…
—–END DH PARAMETERS—–
\” > dh1024.pem
#Client routing setup
mkdir ccd
echo \”
iroute 192.168.2.0 255.255.255.0
\” > ccd/client1
sleep 5
ln -s /usr/sbin/openvpn /tmp/myvpn
/tmp/myvpn –config openvpn.conf
This configuration file will allow clients on either subnet to see clients on the other subnet (see addendum at the end of this post for a problem I ran into with this). The push lines push the routes to each client. This allows for file sharing, client/server communication, etc. between subnets without passing broadcast traffic across the VPN, which it what would happen if we created a bridged connection with a tap interface. For more information on what each parameter does, see the openvpn man page or HOWTO.
In the second-to-last section
rc_startup – client
cd /tmp
openvpn –mktun –dev tun0
echo \”
# Tunnel options
client # Set OpenVPN major mode
dev tun0 # TUN/TAP virtual network device
proto udp # Setup the protocol (server)
remote <SERVER WAN ADDRESS> 1194 # TCP/UDP port number
resolv-retry infinite
nobind
daemon
persist-key
persist-tun
ca ca.crt # Certificate authority (CA) file
cert client1.crt
key client1.key
ns-cert-type server
comp-lzo # Use fast LZO compression
verb 3 # Set output verbosity to n
\” > openvpn.conf
echo \”
—–BEGIN CERTIFICATE—–
…INSERT YOUR OWN CONTENT HERE…
—–END CERTIFICATE—–
\” > ca.crt
echo \”
—–BEGIN CERTIFICATE—–
…INSERT YOUR OWN CONTENT HERE…
—–END CERTIFICATE—–
\” > client1.crt
echo \”
—–BEGIN RSA PRIVATE KEY—–
…INSERT YOUR OWN CONTENT HERE…
—–END RSA PRIVATE KEY—–
\” > client1.key
chmod 600 client1.key
sleep 5
ln -s /usr/sbin/openvpn /tmp/myvpn
/tmp/myvpn –config openvpn.conf
This client configuration will continuously retry to connect to the server. If the Internet connection goes down, the VPN link will reconnect when Internet connectivity is restored.
rc_firewall – client and server
/usr/sbin/iptables -I INPUT 2 -p udp –dport 1194 -j ACCEPT
/usr/sbin/iptables -I FORWARD -i br0 -o tun0 -j ACCEPT
/usr/sbin/iptables -I FORWARD -i tun0 -o br0 -j ACCEPT
/usr/sbin/iptables -I INPUT -i tun+ -j ACCEPT
/usr/sbin/iptables -I FORWARD -i tun+ -j ACCEPT
The firewall rules can be used on both the client and server.
These files need to be saved in the router’s flash memory with the nvram set command. We will be saving rc_startup and rc_firewall scripts for both the server and client. Execute the commands below for both the client and server, pasting the appropriate file where indicated:
# nvram set rc_startup=”
><paste rc_startup here>
>”
# nvram set rc_firewall=”
><paste rc_firewall here>
>”
# nvram commit
Testing The Connection
This setup can be tested on a LAN by connecting the WAN ports of each router to your LAN switch. Enable remote administration in the DD-WRT web interface so you can login to both routers. Also, the LAN subnet you are testing on must not be the same as either of the router LAN subnets. In this case, it could be 192.168.10.0/255.255.255.0.
Once the startup scripts have been saved to flash memory, reboot both routers. SSH into both routers and check that openvpn is running with # ps | grep myvpn. If it is running, congratulations, you have a VPN link. If it is not running on one or both routers, uncomment the daemon line in /tmp/openvpn.conf and start openvpn manually with # /tmp/myvpn –config openvpn.conf. You should receive an error message on the console to help with debugging. I found it very handy to put OpenVPN in the background by issuing a CTRL-Z and then bg. This allows you to issue commands but still see any error messages echoed to the console. If your connection is established but you can’t ping across it, check the OpenVPN FAQ.
Conclusion
If you followed this correctly, you should now have a routed VPN network allowing systems on both the server and client side to ping and connect to systems on the other side.
Addendum
I did run into one strange problem that has me stumped. Routing from the server subnet (192.168.1.x) to the client subnet (192.168.2.x) does work correctly if the server is in daemon mode. If I take the daemon line out of the server configuration file and restart OpenVPN, packets are routed correctly. Routing from the client subnet to the server subnet works correctly either way. If anyone knows why this is, please leave a comment.