Friday, July 15, 2011

Auto connect for Cisco vpn via vpnc

How to auto-connect a Cisco VPN with OpenWRT

In my last post I outlined a design for auto-connecting Cisco VPNs using OpenWRT and the vpnc client. In this post I’ll share the code, and highlight a couple of details. Finally, in my next post, I’ll share some thoughts on improving these scripts.

This process requires some knowledge about your VPN setup. To keep my post from getting too long, I’m assuming that you know your VPN domain name and IP address range, that you can quickly figure out the IP addresses of hosts and DNS servers, and that you have a working vpnc config file.

The first step is installing additional packages on OpenWRT. You can install these from the web interface, or using opkg install at the OpenWRT shell:

  1. vpnc, the Cisco VPN client
  2. ulogd-mod-extra, which pulls down the ulog daemon
  3. kmod-ipt-ulog, kernel modules for iptables and ulog
  4. iptables-mod-ulog, part of the tool for adding rules to iptables

Next, we need to make sure you can always reach the VPN gateway host. So we configure it into /etc/hosts. Look up the IP address (using nslookup, dig, or a similar tool) and add a line to /etc/hosts like:

aaa.bbb.ccc.ddd vpn.example.org

Now that we know we can reach the VPN gateway, we will redirect dnsmasq to always use the internal servers for the VPN’s domain. Look up the domain nameservers using nslookup or dig from inside the VPN, or just look at the nameservers in /etc/resolv.conf when you’re connected from your PC. Then edit /etc/config/dhcp on OpenWRT and add lines like this:

# EXAMPLE.ORG private servers
list server '/example.org/aaa.bbb.ccc.ddd'
list server '/example.org/aaa.bbb.ccc.eee'

Now, we need the script that will manage the VPN connection. Cut and paste this code into /usr/bin/autostart-vpn.sh:

#!/bin/sh
#
# Autostart vpnc
#
# From http://www.kaufmanfamily.net/blog/2010/05/how-to-auto-connect-a-cisco-vpn-with-openwrt
# DHK 4/17/2010

MYPID=/var/run/autostart-vpnc.pid
LOGFILE=/var/log/ulogd.syslogemu
PIDFILE=/var/run/vpnc/pid

is_vpn_connected() {
connected=0
if [ -s $PIDFILE ]; then
ps=`ps | awk -v pid=\`cat $PIDFILE\` '$1 == pid && $5 == "vpnc" { print $0 }'`
if [ -n "$ps" ]; then
connected=1
fi
fi
}

# Fill in our PID file
echo $ > $MYPID

# Loop, monitoring the VPN
while true; do
is_vpn_connected

if [ $connected -eq 0 ]; then
# VPN is not connected. Wait for a request, then start it

# Wait for a log message, denoting that someone is trying to connect
while [ ! -e $LOGFILE ]; do
sleep 10
done
pkt=`tail -0 -f $LOGFILE | head -1`
logger "autoconnect[$] connecting due to $pkt"

# Start the vpn
date >> /tmp/autoconnect-vpnc.log
vpnc >> /tmp/autoconnect-vpnc.log

# Let the VPN settle
sleep 2
else
# VPN is connected. Wait for it to drop, then clean up

# Wait for the VPN to disconnect
while [ $connected -eq 1 ]; do
is_vpn_connected

# sleep 10 seconds, check again
sleep 10
done
logger "autoconnect[$] disconnected"

# Clean up route table
if [ -f /var/run/vpnc/defaultroute ]; then
outsidegw=`awk '{print $3}' /var/run/vpnc/defaultroute`
currentgw=`netstat -rn | awk '$1 == "0.0.0.0" && $4 == "UG" {print $2}'`
if [ "X$outsidegw" != "X$currentgw" ]; then
if [ "X$currentgw" != "X" ]; then
route delete default gw $currentgw
fi

# Restore exactly what was saved, except vpnc syntax is slightly wrong
route add `sed -e 's/via/gw/;' /var/run/vpnc/defaultroute`
fi
fi

# Clean up resolv.conf
resolvconf -d
fi
done

We’re almost there. Cut and paste the following code into /etc/init.d/autostart-vpn; this is the startup script that creates the iptables rules and starts the last script at boot time. Make sure you edit the script to list the correct networks for your VPN, and check that the locations (hardcoded, unfortunately) for inserting vpn_trigger in the FORWARD and OUTPUT rulesets makes sense:

#!/bin/sh /etc/rc.common<br />#<br /># From http://www.kaufmanfamily.net/blog/2010/05/how-to-auto-connect-a-cisco-vpn-with-openwrt<br /># DHK 4/17/2010<br />#<br /># Start after dnsmasq<br />START=80<br /><br />VPN_NETWORKS="10.0.0.0/8 192.152.0.0/16"<br />EXT_IF="eth0.1"<br /><br />check_firewall_rules() {<br />	trigger=`iptables -L vpn_trigger 2>/dev/null`<br /><br />	if [ "X$trigger" == "X" ]; then<br />		# Fill in vpn_trigger ruleset<br />		iptables -N vpn_trigger<br /><br />		for net in $VPN_NETWORKS; do<br />			iptables -A vpn_trigger -o $EXT_IF --dest "$net" -j ULOG<br />		done<br /><br />		# Hook vpn_trigger into OUTPUT and FORWARD rules<br />		# Ought to do something smarter than hardcoding the position<br />		iptables -I OUTPUT  4 -j vpn_trigger<br />		iptables -I FORWARD 4 -j vpn_trigger<br />	fi<br />}<br /><br />start() {<br />	check_firewall_rules<br /><br />	/usr/bin/autostart-vpnc.sh &<br />}<br /><br />stop() {<br />	if [ -f /var/run/autostart-vpnc.pid ]; then<br />		kill `cat /var/run/autostart-vpnc.pid`<br />	fi<br />}<br />

One detail I skipped last time is that vpnc, as packaged for OpenWRT Kamikaze, will stomp on your resolv.conf file. Its default configuration just doesn’t work on OpenWRT. (The issue is that OpenWRT puts the WAN resolv.conf details in a non-standard place.) There’s an easy fix for this, though. Cut and paste the following code into /sbin/resolvconf. vpnc will find resolvconf and use it to manage /etc/resolv.conf correctly.

#!/bin/sh<br />#<br /># Simple resolvconf manager to integrate vpnc better with WRT<br />#<br /># Update /tmp/resolv.conf.auto, NOT /etc/resolv.conf. This affects the<br /># DNS resolver operation, which is actually the right thing.<br />#<br /># Usage:<br />#<br />#   resolvconf -a [if] < new-resolvconf<br />#<br />#   resolvconf -d [if<br />#<br /># We ignore the [if] argument.<br />#<br /># From http://www.kaufmanfamily.net/blog/2010/05/how-to-auto-connect-a-cisco-vpn-with-openwrt<br /># DHK 4/13/2010<br /><br />BACKUP=/tmp/resolv.conf.bak<br />RESOLV=/tmp/resolv.conf.auto<br /><br />if [ $1 == "-a" ]; then<br />	# Change resolv.conf<br /><br />	if [ ! -e $BACKUP ]; then<br />		cp $RESOLV $BACKUP<br />	fi<br /><br />	cat > $RESOLV<br />fi<br /><br />if [ $1 == "-d" ]; then<br />	# Restore original resolv.conf<br /><br />	if [ -e $BACKUP ]; then<br />		mv $BACKUP $RESOLV<br />	fi<br />fi<br />

Now is a good time to make sure you’ve installed your VPN configuration into /etc/vpnc/default.conf. It’s a good idea to test out your vpnc config on another machine before running it on OpenWRT.

OK, let’s enable the services we need. You can do this from the OpenWRT web interface, or the command line:

/etc/init.d/ulogd enable
/etc/init.d/autostart-vpnc enable

Reboot your OpenWRT to get all the services set up. You’ll want to watch the system message log, so in one ssh connection run the log reader:

logread -f

and in another ssh connection start pinging a host in the VPN:

ping somehost.example.org

You should see a message in the system log, and after a short delay you’ll start getting ping responses. Make sure to test the auto-connect from a host plugged in to your OpenWRT’s LAN port as well as from the shell: if auto-connect works directly from the OpenWRT shell, but not from the LAN, then your iptables OUTPUT rule is correct but your FORWARD rule isn’t. (If the problem is reversed, then the rules are reversed.)

If auto-connection doesn’t work, you can check the log at /tmp/autoconnect-vpnc.log and then debug the process step-by-step:

  1. First, check that your vpnc configuration works:
    vpnc
  2. Then, check that the vpn_trigger iptables rule is being called by looking at the packet counts:
    iptables -L vpn_trigger -v
  3. If vpn_trigger is being called, make sure that ulogd is writing to the correct file:
    cat /var/log/ulogd.syslogemu
  4. check that the autoconnect script is actually running with ps

The hardest thing to check is that you have your DNS setup correct. I usually do this by checking the vpn_trigger rules first, then use nslookup to query a behind-the-vpn host.



No comments:

Post a Comment