I Modified vpnc Cisco VPN Client to Use OS X Native User Tunnels

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&quot;;>
<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
view raw vpnc.conf hosted with ❤ by GitHub

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
view raw vpnc-start hosted with ❤ by GitHub

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
view raw vpnc-stop hosted with ❤ by GitHub

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.

14 Responses to I Modified vpnc Cisco VPN Client to Use OS X Native User Tunnels

  1. Felipe Cerqueira says:

    Work perfectly!! Thank you so much! Great idea!!!!!

  2. Michel says:

    Thanks Brian, really happy to get this working again on my Macbook Air. I keep running into an error while trying to get it working on my iMac (and a colleague’s). The error I get is during the “make” of vpnc and reads:
    “ld: symbol(s) not found for architecture x86_64” (right after _crypto_verify_chain in crypto-gnutls.o)
    I had a look through the source files, but could not find anything obvious. Do you have a suggestion?

    Thanks again!
    Best regards, Michel

    • Brian Reiter says:

      My guess is you don’t have the gnutls library dependency installed. My advice is to install libpgp-error, libgcrypt and gnutls with Macports.

      sudo port install libpgp-error libgcrypt gnutls

      Once you have these dependencies satisfied the vpnc should build for you without a problem.

      • Michel says:

        Hi Brian,

        Thanks for the quick response. I tried reinstalling the mentioned libraries. Unfortunately, no change.

      • Brian Reiter says:

        We haven’t had any problem at my company building this, although everyone has pretty close to the same setup. Do you have the command line build tools for Xcode 6.x installed?

        crypto-verify-chain() is a function declared in crypt-gnutls.c that depends on library calls to libgnutls. I think for some reason the linker can’t find a valid x86_64 to link with. I’m guessing it is missing libgnutls but I can’t really tell without the full error text from the build process.

        The Makefile uses pkg-config to find the library dependencies and create the necessary linker and compiler flags to find the gnutls library.

        Here are some things to compare to my environment.

        [master] $ which pkg-config
        /opt/local/bin/pkg-config
        [master] $ pkg-config --libs gnutls
        -L/opt/local/lib -lgnutls
        [master] $ pkg-config --cflags gnutls
        -I/opt/local/include -I/opt/local/include/p11-kit-1 -I/opt/local/include
        [master] $ file vpnc
        vpnc: Mach-O 64-bit x86_64 executable
        [master] $ file crypto-gnutls.o
        crypto-gnutls.o: Mach-O 64-bit x86_64 object
        [master] $ file /opt/local/lib/libgnutls.dylib
        /opt/local/lib/libgnutls.dylib: symbolic link to libgnutls.28.dylib
        [master] $ file /opt/local/lib/libgnutls.28.dylib
        /opt/local/lib/libgnutls.28.dylib: Mach-O 64-bit x86_64 dynamically linked shared library
        [master] $ cc --version
        Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn)
        Target: x86_64-apple-darwin14.1.0
        Thread model: posix
        [master] $ ld -v
        @(#)PROGRAM:ld PROJECT:ld64-236.3
        configured to support archs: i386 x86_64 x86_64h armv6 armv7 armv7s armv7m arm64
        LTO support using: LLVM version 3.5.1
        [master] $ xcode-select --version
        xcode-select version 2339.
        [master] $ xcode-select --install
        xcode-select: error: command line tools are already installed, use "Software Update" to install updates

  3. Michel says:

    Hi Brian,
    Thanks! The pkg-config was missing. Now everything works like a charm!

    Michel

  4. Dmitry says:

    Hi Brain,
    Thanks!
    But I got an error in the log:
    “Couldn’t open socket of ESP. Maybe something registered ESP already.
    Please try ‘–natt-mode force-natt’ or disable whatever is using ESP.
    socket(PF_INET, SOCK_RAW, IPPROTO_ESP): Operation not supported on socket”

    How to fix it?

  5. emizzzle says:

    Wow, what a legend, thanks for this!!

    A few things I encountered along the way…. excuse my non-knowledge of linux…

    Had to use Homebrew as opposed to MacPorts as I couldn’t install some of the required libraries and homebrew seemed to install the libs without issue. Problem is that by default, Homebrew installs into /usr/local, where as the build is looking in /opt/local.

    After cloning repo and then running make, I got the error:

    No rule to make target `/opt/local/include/gcrypt.h'

    To workaround, I copied the required files from /usr/local to /opt/local, but there is probably a way to get Homebrew to install into this directory.

    I set everything else up, then when I tried to run the client via:

    sudo vpnc-start

    I got the following error:

    /Library/LaunchDaemons/com.wolfereiter.vpnc.plist: Path had bad ownership/permissions

    I’m guessing because I didn’t create the .plist file under sudo via the CLI and instead created it with a GUI text editor (always seems easier to me), the .plist file had my user as the owner, and apparently to have a valid LaunchDaemon, .plist files must be owned by root and disallow group and world writes (http://superuser.com/a/832673), so run the following commands to update the permissions on this file:

    sudo chmod 600 /Library/LaunchDaemons/Library/LaunchDaemons/com.wolfereiter.vpnc.plist
    sudo chown root /Library/LaunchDaemons/Library/LaunchDaemons/com.wolfereiter.vpnc.plist
    

    After restarting the vpnc daemon (vpnc-stop then vpnc-start), my VPN still wasn’t connecting. I checked the log we created at /private/var/log/vpnc/vpnc.log and the vpnc client was spitting out a bunch of errors and some recommendations to fix it. The first error was:

    Error binding to source port. Try '--local-port 0'

    The second error was:

    Couldn't open socket of ESP. Maybe something registered ESP already.
    Please try '--natt-mode force-natt' or disable whatever is using ESP.

    To fix these errors, I changed my /Library/LaunchDaemons/Library/LaunchDaemons/com.wolfereiter.vpnc.plist to:

    <?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>
    	<string>--local-port</string>
    	<string>0</string>
    	<string>--natt-mode</string>
    	<string>force-natt</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>
    

    Hope this helps someone. Thanks again, this is awesome!

  6. Vidanez says:

    Another for the funny implementation of native CISCO Ipsec. It doesn’t work of you used more than 170 vlan behind the VPN:
    https://discussions.apple.com/message/29398097#29398097

    • Brian Reiter says:

      Hi Vidanez. You should report this to Apple’s bug reporter system. It will be more likely to do some good than on the support forums:

      http://bugreport.apple.com.

      • hirvanao says:

        I tried with their support, but looks like that triage first level doesn’t understand deep technical questions and the refer me to Cisco even after whole troubleshooting done by us. Support are not easy places to work I guess, and the power to say “no” must be tentative.
        Thanks for the link I’ll try with that

  7. Quang Tran says:

    Thank you so much, Brian

  8. quangtqag says:

    Thank you so much, Brian

  9. ahmetfay says:

    Thank you Brian, it’s worked 🙂

Leave a comment