I Modified vpnc Cisco VPN Client to Use OS X Native User Tunnels
December 3, 2014 14 Comments
OS X Built-in Cisco IPSec VPN Sucks
My company works with a client site that uses a Cisco ASA-based IPSec VPN for remote access. I’m not a fan. Theoretically, there is a Cisco IPSec VPN client built into OS X based on raccoon(8) from the KAME project which ended in 2006. We’ve been trying to use it since OS X 10.8 “Lion” without a lot of success. What the OS X IPSec GUI does is dynamically generate raccoon config files and invoke the raccoon binary as root for you when you click connect. It doesn’t work as well as one would hope.
- Routes for subnets within the vpn are not reliably configured
- The VPN disconnects randomly
- The built-in VPN client does not reconnect automatically and
- I have to authenticate with a password manually every time I want to connect
- I also hate the bizarre rectangle with some vertical lines VPN status icon in the menu bar
Other Options
OK, not all of these are significant issues; but the random disconnects and unreliable subnet route configuration is a serious problem. One option is to obtain the latest source code for racoon(8) and try to build it and replace the system version with a newer one or have a parallel version. I’m not even sure that this is racoon’s fault. I have a suspicion that Cisco is doing something that is not strictly standards compliant. In any case, just building my own raccoon doesn’t imply it would work any better. I would probably have to get deep in the weeds of esoterica debugging what is actually causing my problems and figuring out a workaround — because I can’t change the Cisco server software. Another option is strongSwan. I have to be honest, strongSwan looks like a huge complex package to try when other options don’t work. Finally, there is vpnc which was actually where I started because I used the cisco-decrypt binary from vpnc to deobfuscate teh enc_GroupPwd field from the .pcf configuration file for the Cisco VPN client in order to provided the Shared Secret field for the OS X Cisco VPN client configuration.
It also happens that I’m a MacPorts user and vpnc is a package in MacPorts while strongSwan and raccoon are not (although strongSwan is available these days through HomeBrew). Since I had already installed vpnc in order to get cicso-decrypt I tried out vpnc as an alternative to racoon. It turned out that it didn’t totally eliminate the disconnects but it would generally stay connected for a really long time as long as the network was up and since the config file for vpnc stores the VPN password I didn’t have to remember it and type the damn thing in all the time. Then I had a brainwave that I could use launchd to run it in foreground mode (–no-detach) with the KeepAlive option. That way whenever a disconnect did happen the vpnc process would exit and launchd would restart it, reconnecting the tunnel automatically.
Nirvana.
Yosemite Throws a Wrench in TunTapOSX
VPNC relies on tuntaposx to provide character devices for attaching network tunnels or taps to. On OS X, this is a 3rd party kernel extension or kext. In OS X 10.10 Yosemite, only signed kernel extensions will load by default. Vpnc stopped working when I upgraded to Yosemite.
Remediation A
The simplest fix is to put Yosemite into kernel extension developer mode which essentially reverts the behavior to Mavericks and previous so that unsigned kexts load and work by setting a kernel boot argument and rebooting.
sudo nvram boot-args="kext-dev-mode=1"
This can be reverted thusly:
sudo nvram -d boot-args
Remediation B
The idea of requiring signed kexts seems like a good security measure. It limits the ability for anyone evil to inject code into the kernel, so I would prefer to keep it. It turns out that kext signing requires a special dispensation from Apple. Just a normal OS X developer account doesn’t cut it. You need to ask for a kext signing certificate and they have to approve it. I didn’t even try. I did try using the signed tuntaposx kexts out of Tunnelblick and that works but I also had a kernel panic happen while doing that. I don’t know that it was related but be forewarned.
Final Solution
I noticed that OpenVPN kept working when vpnc stopped. When I first started using OpenVPN it required tuntaposx in order to work, just like vpnc. I also noticed that OpenVPN routes were showing up on utun devices rather than tun. Intrigued, I looked into this a little deeper. It turns out that the xnu kernel in Darwin 10 shipped a feature called Native User Tunnels as part of OS X 10.6 Snow Leopard which I think came out of NetBSD and that very same KAME project that spawned raccoon. Native User Tunnels is a kernel feature that allows binding a tunnel to a special socket and reading and writing to it with standard socket APIs. At some point OpenVPN started using this feature on OS X and I wanted vpnc to do the same.
XNU Native User Tunnels for VPNC
I grafted in native user tunnel support in vpnc. You can build it yourself. The pre-requisites are libgpg-error, libgcrypt and libgnutls (libgpg-error is also a prerequisite for libgcrypt). The build process also needs pkg-config to find where the libraries are installed. Once you have those, such as port install libgcrypt gnutls pkg-config
you can clone my repo and build vpnc. My version of vpnc patched for native user tunnels will still use tuntaposx for tap-based vpns or if you have more than 256 utun devices in use.
git clone https://github.com/breiter/vpnc.git
cd vpnc
make
sudo make install
The binaries install to /usr/local/sbin by default. You configure vpnc default.conf for your default VPN connection in /etc/vpnc/default.conf. See man vpnc for details.
A sample config file might look something like this where you supply the parts delimited with ** (note the ** is not part of the file format but part of what you replace).
IPSec gateway **vpn-server-hostname-or-ip**
IPSec ID **GroupName-from-.pcf**
IPSec secret **output-of-cisco-decrypt-here**
IKE Authmode psk
Xauth username **my-corporate-username**
Xauth password **super-secure-password**
NAT Traversal Mode cisco-udp
DPD idle timeout (our side) 0
Controlling VPNC with Launchd
I mentioned earlier that the happy scenario of using vpnc was combining it with launchd so that launchd would automatically restore your vpn tunnel after it is disconnected for any reason.
Place com.wolfereiter.vpnc.plist in /Library/LaunchDaemons.
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd";> | |
<plist version="1.0"> | |
<dict> | |
<key>Disabled</key> | |
<true/> | |
<key>Label</key> | |
<string>com.wolfereiter.vpnc</string> | |
<key>ProgramArguments</key> | |
<array> | |
<string>/usr/local/sbin/vpnc</string> | |
<string>--debug</string> | |
<string>2</string> | |
<string>--no-detach</string> | |
<string>/etc/vpnc/default.conf</string> | |
</array> | |
<key>StandardErrorPath</key> | |
<string>/var/log/vpnc/vpnc.log</string> | |
<key>StandardOutPath</key> | |
<string>/var/log/vpnc/vpnc.log</string> | |
<key>RunAtLoad</key> | |
<true/> | |
<key>KeepAlive</key> | |
<!-- NetworkState key is no longer implemented in OS X 10.10 Yosemite. | |
<dict> | |
<key>NetworkState</key> | |
<true/> | |
</dict> --> | |
<true/> | |
</dict> | |
</plist> |
Create a directory to hold the log file defined in this plist.
mkdir /var/log/vpnc
Create vpnc.conf in /etc/newsyslog.d to clean up old logs.
# logfilename [owner:group] mode count size when flags [/pid_file] [sig_num] | |
/var/log/vpnc/*.log 644 3 1000 * J |
Create vpnc-start script in /usr/local/bin.
#!/bin/sh | |
if [ "$(id -u)" -ne 0 ]; then | |
SELF=`echo $0 | sed -ne 's|^.*/||p'` | |
echo "$SELF must be run as root." 1>&2 | |
echo "try: sudo $SELF" 1>&2 | |
exit 1 | |
fi | |
PLIST=/Library/LaunchDaemons/com.wolfereiter.vpnc.plist | |
CONF=`grep \.conf $PLIST | sed 's/<[^>]*>//g' | tr -d " \t"` | |
GATEWAY=`grep gateway $CONF` | |
ERROR=$( { /bin/launchctl load -w $PLIST; } 2>&1 ) | |
if [ -z "$ERROR" ]; then | |
echo "starting vpnc daemon connection to $GATEWAY." | |
else | |
echo $ERROR | |
fi |
Create vpnc-stop script in /usr/local/bin.
#!/bin/sh | |
if [ "$(id -u)" -ne 0 ]; then | |
SELF=`echo $0 | sed -ne 's|^.*/||p'` | |
echo "$SELF must be run as root." 1>&2 | |
echo "try: sudo $SELF" 1>&2 | |
exit 1 | |
fi | |
PLIST=/Library/LaunchDaemons/com.wolfereiter.vpnc.plist | |
CONF=`grep \.conf $PLIST | sed 's/<[^>]*>//g' | tr -d " \t"` | |
GATEWAY=`grep gateway $CONF` | |
ERROR=$( { /bin/launchctl unload -w $PLIST; } 2>&1 ) | |
if [ -z "$ERROR" ]; then | |
echo "stopping vpnc daemon connection to $GATEWAY." | |
else | |
echo $ERROR | |
fi |
Once you have everything set up and a working default.conf in /etc/vpnc, then you can use the vpnc-start
command to launch vpnc in the background and vpnc-stop
to close the tunnel. Once vpnc-start
is invoked, launchd will keep it running through sleep/wake and moving around between wired and wireless connections. Whatever.
$ sudo vpnc-start
Password:
starting vpnc daemon connection to IPSec gateway vpn.corporate.com.
$ sudo vpnc-stop
stopping vpnc daemon connection to IPSec gateway vpn.corporate.com.
$
Happy tunneling.