Mobile and VPNs

|

I recognize that so far, I haven’t managed to live up to my promise: I have not been posting much in the way of HOWTOs here. It turns out that thoroughly documenting the work you’ve done is both time-consuming and hard. Documentation has never been a particular strong-suit of mine, so this is a good exercise for me.

VPN Protocols

One subject I’ve wanted to address for a while is the state of VPN support on mobile platforms, both on the device side as well as on the network side. Both iOS and Android support PPTP, L2TP over IPsec, and raw IPsec. I have successfully used PPTP and L2TP/IPsec in the past, but a few months ago, my service provider made some changes to the way they gateway internet traffic. These changes affect more than just VPN traffic and have not been popular with users, and to be honest I’m none too pleased about them myself. The long and short of it is that GRE/IP and ESP/IP no longer work across their network, effectively preventing people from using PPTP, L2TP/IPsec, or IPsec by itself…the only three options that are supported by either mobile software platform.1

There are, of course, other options that exist for establishing secure network tunnels, but the trick is that whatever software platform you are using has to support it, as does the endpoint you want to connect to. The most desireable options are those that can “just work” regardless of the network you are connected to. NAT has become so prevalent in the IPv4 world that using a VPN protocol that either doesn’t work over NAT or is broken by certain NAT implementations (or which requires a NAT helper that the router performing the translation does not provide) is oftentimes just not worth bothering with.

In any discussion about VPN protocols, OpenVPN will inevitably come up simply because it is generally NAT-friendly and pretty flexible to boot; however, I’ve never been particularly fond of it. For one, the way it is configured and used feels very “foreign” to me in a way I can’t quite put into words, and the few experiences I have had with it left a bad taste in my mouth.2 The other problem with it is that practically nothing supports it out-of-the-box, and historically, attempts by third-parties to add support for OpenVPN to either platform have felt very grafted-on.3 Finally, in my particular case, I want to use RouterOS as my VPN access concentrator platform, and its OpenVPN support is currently limited to TCP mode, and TCP-over-TCP is a bad idea.

Despite some obvious security shortcomings, I personally think that the use of raw L2TP, unaccompanied by IPsec, is as good a compromise at this point as you’re going to find. It’s basically pure PPP running over pure UDP, so it doesn’t have most of the NAT issues that plague PPTP (GRE) or IPsec (ESP), and there are very few NAT engines that you will be unable to get your tunnel to punch through. Most router OSes and VPN access concentrators support it (it’s, y’know, kind-of a prerequisite for L2TP/IPsec support), and iOS and Android both have built-in support for L2TP already as well.

There’s just one problem: neither iOS nor Android inherently support using it the way we want to be able to use it. By default, they insist that L2TP must be paired with IPsec. Unlike OpenVPN, though, 99.9% of the software support for this is already present in the underlying OS…we just have to coax it into doing what we want.

Raw L2TP on iOS

The solution to getting iOS to establish an L2TP session without first setting up IPsec turns out to be fairly straightforward, assuming that you are using a jailbroken device. iOS is based on Darwin, which is certified UNIX, and just like OS X, iOS uses the same practically de-facto UNIX pppd implemention that you run into almost everywhere else: the formerly known as ANU pppd pppd.

pppd expects most of the options for the connection to be supplied to it as direct arguments when it is invoked, but it will also grab arguments from the plain-text /etc/ppp/options file, if present. The Darwin L2TP client is implemented as a pppd plugin and can be passed arguments by way of pppd as well. So if you create such a pppd “options” file and stuff the following into it, you can leave the “Secret” field (IPsec PSK) blank when you configure your L2TP connection, and it will not try to negotiate IPsec between you and your access concentrator:

plugin L2TP.ppp
l2tpnoipsec

Incidentally, everything works exactly the same way on OS X, if you ever find yourself needing to do raw L2TP from a Mac.

Raw L2TP on Android

Before Ice Cream Sandwich (4.0), raw L2TP was actually one of the options presented when one went to configure a new VPN connection on Android. For some reason, Google decided to pull that option.4

Also just as unfortunate, trying to talk Android down from negotiating IPsec before making an L2TP connection is nowhere near as easy as stuffing a few arguments into the pppd “options” file. Android also uses Paul’s pppd, but unlike iOS, the IPsec negotiation is not kicked off by the L2TP client, and the L2TP client is not a pppd plugin. It’s a userspace binary, and both it and the IPsec IKE daemon are called as necessary by the Android framework when the user requests an L2TP VPN session.

The IPsec daemon is called racoon and the PPTP/L2TP client daemon — unique to Android — is named mtpd. mtpd sets up the L2TP or PPTP session, and then it in turn executes pppd. mtpd does not detach itself into the background, nor does it ask pppd to do so either.

If you try to run mtpd from the shell, you’ll get some usage information back:

# mtpd
Usages:
  mtpd interface l2tp <server> <port> <secret> pppd-arguments
  mtpd interface pptp <server> <port> pppd-arguments

You can also find some example mtpd invocations over here, which proved to be extremely useful. (Do note that there were apparently some changes between 2.x and 4.x; mtpd now for some reason also requires that you specify the network interface that you want to make the connection through.) All this to say that it is entirely possible to invoke mtpd manually and initiate a raw L2TP connection by doing so.

Armed with this knowledge, I concocted the following shell script to automate the process of connecting to my VPN at work. To use this, if you are running stock AOSP, you will need a busybox binary that supplies you with awk and a couple of other utilities that are not supplied with the Android userland by default. It assumes you want to send all data over the VPN. If the VPN connection drops, it will automatically restart it again; to quit, you will need to Ctrl-C or kill the process. It will work over WiFi or cellular, but if your particular phone uses a device name other than rmnet_usb0 for the cellular modem then you should also change the CELLULAR variable to match the actual interface name for your phone. Change the 3 variables at the very top to reflect the VPN access concentrator IP, your username, and your password:

#!/system/bin/sh

ENDPOINT=my.vpn.server.com
USERNAME=username
PASSWORD=password
CELLULAR=rmnet_usb0

IPROUTE=`ip route list exact 0.0.0.0/0`
INTERFACE=`echo $IPROUTE | awk '{print $5}'`
DEFAULTROUTE=`echo $IPROUTE | awk '{print $3}'`
ENDPOINT_IP=`ping -c 1 $ENDPOINT | awk 'NR==1{print $3}' | tr -d \(\):`

ip route del default
if [ "$INTERFACE" != "$CELLULAR" ]; then
    ip route add $ENDPOINT_IP/32 dev $INTERFACE via $DEFAULTROUTE
else
    ip route add $ENDPOINT_IP/32 dev $INTERFACE
fi
until (false) do
until (mtpd $INTERFACE l2tp $ENDPOINT_IP 1701 '' linkname vpn name $USERNAME password $PASSWORD defaultroute) do
    echo VPN dropped...reconnecting.
done
done

This works, but it’s kind of a drag to have to fire up a Terminal and peck out a shell script name every time I want to make a VPN connection. Also, the connection script above deletes the original default route that Android installed so that there is no ambiguity that the one pppd installs in the routing table is the one that should be used, and the easiest (read: laziest) way to clean up that damage is to reset the network interface, which I do by simply toggling airplane mode off and on after I’m done using the VPN. Which is stupid.

Ultimately, I think the right answer to this problem is to patch up Android so that standalone L2TP is an option again, and this is something that I plan on pursuing. In the meantime, though, I discovered Gscript, which is a great little tool that allows me to create shortcuts to shell commands on my launcher.5 I created two other shell scripts, a VPN-Start and a VPN-Stop; the first one calls my VPN connection script, and the second kills it. I then used Gscript to put shortcuts to those two scripts on my launcher.

I quickly discovered a problem, though: for some reason, I can detach mtpd from my active Terminal session, quit Terminal, and still have mtpd running in the background, but if I try to do the same thing from within Gscript, mtpd and pppd die the instant the script reaches its end. So I had to come up with a new strategy. My VPN-Start script detaches my VPN connection script and then starts pinging an address on the other side of the VPN. For as long as I want the VPN connection to remain up, I just continue to let that run in the background. When I want to disconnect, I run VPN-Stop, which kills ping. With the ping no longer running, the VPN-Start script reaches its end, that Gscript instance dies, and takes mtpd and pppd with it. Finally, VPN-Stop also toggles airplane mode off and on for me before it ends.6

VPN-Start
---------

#!/system/bin/sh
#I called the VPN script 'workvpn.sh'
workvpn.sh &
ping 8.8.8.8

VPN-Stop
--------

#!/system/bin/sh
killall pppd
killall ping
settings put global airplane_mode_on 1
am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true
sleep 2
settings put global airplane_mode_on 0
am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false

One final note: depending on your provider and whether you are connected to WiFi or the cellular network when you are trying to use your VPN, you may experience DNS issues. In my particular case, I discovered that my provider’s DNS servers do not respond to requests from IP addresses outside of their network. This meant that as soon as the VPN came up, I was unable to resolve any names. Furthermore, the pppd option usepeerdns does not work on Android; Android doesn’t consult /etc/resolv.conf for its nameserver list but instead looks to Android system properties which are settable via setprop, and undoubtedly pppd was not updated by the Android folk to be aware of this.

You could add something to this effect to the end of VPN-Start, just before the ping command:

setprop net.dns1 8.8.8.8
setprop net.dns2 8.8.4.4

…or whatever DNS servers you want to use. If you wanted to get really fancy, you could probably script something that would read the values out of /etc/resolv.conf and then execute setprop for each value you come across. Unfortunately, this isn’t foolproof since this will only be executed once, and if you hit a rough patch signal-wise and your connection blips, your DNS settings will most likely end up reverting back to the ones your provider supplies. In my case, since I actually have control over the access concentrator I connect to, I configured it to proxy any incoming UDP port 53 traffic to our DNS servers instead, which means I’m able to avoid having to deal with the issue on the Android/client side of things. Most people connecting to their corporate VPN won’t have the freedom to implement a similar workaround, however.

A Word About Security

As I glossed over near the beginning, this is not a particularly secure solution. By default, pppd will not attempt to negotiate any security. The security mechanisms built into PPP itself (ECP) are not great, and this is why people started running L2TP over IPsec in the first place. Probably the most secure solution — which, unfortunately, is not saying much — is to use MPPE. MPPE is the encryption mechanism that was developed for PPTP, but since it runs directly on top of PPP, there’s absolutely no reason that you cannot also use it with L2TP, provided that your access concentrator supports it, too. Fortunately, RouterOS treats all types of PPP as equals feature-wise, so it literally just works.

I ran into many problem while trying to use MPPE on the client-side, however:

  • iOS just won’t ever try to negotiate MPPE over L2TP, period. I have also not been able to locate any evidence that there is an option I can pass to the OS X L2TP module that will cause it to try, and the require-mppe option to pppd doesn’t seem to do anything, either. So, at least for the time being, using MPPE over L2TP on Apple operating systems appears to be a lost cause.
  • Android is trickier. You can easily enable MPPE over L2TP by passing the require-mppe parameter to pppd — just add it at the end, say after defaultroute. However, for reasons I have been as-yet unable to determine, it is wildly unstable and causes the L2TP connection to drop all the time, especially while it is under load. I found some references to MPPE issues in Android, but I’m not sure they are related to what I was seeing. First, they seem to be old references, and second, as far as I can tell, the problems are restricted to stateful MPPE which pppd doesn’t try to negotiate by default and which I was not using. Further research indicates that the stateful MPPE problems at least are traceable to bugs in the Linux MPPE implementation.

Even if I were able to coax MPPE into working properly, the reality is that it shouldn’t be considered secure and nobody should rely on it exclusively to protect one from snooping eyes. Much like WEP, it is generally accepted that using it is little better than having no security at all. Unless you are also using secure protocols such as SSL on top of the VPN itself, you should, for all intents and purposes, consider your VPN traffic to be open. So don’t be lulled into a false sense of security even if you can manage to get MPPE working for you in a stable manner. For people for whom security is everything and the sole point of using a VPN connection to begin with, this is going to be a deal-breaker for them, and it is the one area where L2TP+IPsec and even OpenVPN has got everything else beat.


  1. Some may think this generous of me, but I personally chalk this up to incompetence on StraightTalk’s part rather than malicious intent. But it’s largely academic because either way, the effect is the same. 

  2. I admit that I have not spent enough time with it to be able to claim that I have a properly informed opinion of it, so if anybody wants to challenge my view of OpenVPN, I’m not above admitting that I’m wrong! To clarify, I’ve only ever used it in routing mode, not bridge mode. I think much of my distaste for it stems from the fact that, at least when in routing mode, all traffic to and from all peers is bound to a single virtual interface, which just seems like a bad idea; that design decision, which was probably demanded by its userspace architecture, in turn led to (what I feel to be) confusing hacks such as “iroute”. There really is no need to re-invent the wheel for things like a routing table (and to do so in userspace, no less), and there wouldn’t be a felt need to do so if every connection were represented by a separate interface, just like they are with PPP.

    To be fair, there are definitely features of OpenVPN that I admire and that I wish PPP would adopt. One specific example is that ability of the server/access concetrator to inform clients what prefixes they should install routes for. That’s brilliant and head-and-shoulders above the “PPP way”, which is to let the client decide what traffic to send down the tunnel. Most client implementations only give you two options: install a default route, or install a route whose prefix length is determined by the old classful IP addressing scheme. If you as a client want to send other traffic down that tunnel, you’ve got to install those routes manually yourself after the tunnel comes up. Yuck. (I suppose one could exchange routes with a separate dynamic routing protocol such as RIP running on top of the PPP link, but…also yuck.) 

  3. Things have admittedly gotten better recently. It used to be that for either iOS or Android, you would have to jailbreak (iOS) or root (Android) the operating system in order to install OpenVPN support. Nowadays, both operating systems have third-party VPN APIs that developers can use to add support for new VPN protocols in the form of a self-contained app. There’s even an “official” OpenVPN iOS app in the Apple App Store. 

  4. I wish I knew why. I can’t believe it is because they were worried about the lack of security; raw L2TP is no less secure than PPTP. Sure, we all know that PPTP security — or, more specifically, MPPE + MSCHAPv2 — is flawed (also see here), but if they want to fall back on that argument for L2TP, then they should have also removed PPTP support.

    Maybe somebody over there is under the mistaken impression that you can’t “secure” an L2TP session with MPPE, and so the argument is that L2TP is entirely unencrypted whereas PPTP at least has some semblance of security? MPPE is negotiated during CCP; it is entirely a PPP thing and is completely unrelated to the external “wrapper” that PPP is being tunneled within. There is nothing PPP-wise that L2TP can’t do that PPTP can. 

  5. There is an older version of Gscript in the Play Store, but it is old old. I am using the latest 1.0.0-alpha posted by its author to XDA from last year, and it seems to work well. 

  6. I am still using Jellybean 4.2, and I do not know whether this method of systematically enabling and disabling airplane mode still works in newer versions of the OS.