In the "usual" VPN setup, we'd cut a hole in the existing DHCP pool in the protected network to reserve as addresses to be given to VPN clients. In a /24 subnet, network gear might get addresses up to 30, the DHCP pool could run from 31-150, and other static IPs (hi, printers!) might live at 200-254, with 151-199 open for VPN access.
Unfortunately, my protected network in this scenario is AWS, which I can't actually carve random IPs out of to assign to connecting clients. I have to assign one in a different netblock and hope none of the three (the client, AWS, and my new L2TP-only address pool) conflict.
Luckily, there are three private network spaces defined, and everyone always forgets about the weird one. Let's set up the connection something like this:
Some background: PPP on LinuxThis turned out to be basically irrelevant, but I found it pretty fascinating, so I'm going to mention it here. PPP in Linux has a kernel component and a userspace PPP daemon. The kernel handles the technical, low-level details of PPP framing (which is "like" synchronous HLDC), and passes the various encapsulated packets off to the appropriate destination.
For example, IP is delivered directly to the kernel's IP stack without invoking the daemon, sparing a couple of context switches and maybe a process switch every packet. Packets that aren't handled in the kernel, like LCP and CCP, are passed to the daemon for processing. Mostly, the daemon is involved in the setup and teardown of the link, and in between, the kernel handles the "real" traffic over the link.
More background: CompressionSkimming the pppd manual page will reveal a number of compression options. What do they all mean?
- accomp: Address/Control compression. PPP is based on HDLC which could connect several devices to one master, but PPP is strictly point-to-point instead. This compression option omits the addresses because with only one "other" side to the link, the address would always be the same. For the control field, it is unused in PPP, so it can likewise be eliminated.
- pcomp: Protocol compression. For protocols below a certain magic value, uses a single byte for the protocol field instead of two. Except for LCP, which may not be compressed so that its frames are always recognizable. Notably, IP packets can be reduced to a 1-byte protocol field with this option.
- vjcomp: Van Jacobson TCP/IP header compression. Instead of sending the headers in full, a "TCP state" is maintained at each end, and only changes to the state are transmitted. This can reduce the 40-byte header to 3-4 bytes, at the cost of memory and processing.
- ccp (well, the noccp option): the Compression Control Protocol, which allows for compressing the entire PPP frame being transmitted. A number of algorithms can be used here according to the spec, including BSD Compress, deflate, Predictor-1, and Predictor-2. Except on LCP frames.
- bsdcomp: the LZW algorithm from BSD's compress command, for CCP.
- deflate: the deflate algorithm from gzip, for CCP.
Since the CCP is wrapped around the rest of the PPP frame, the kernel must have support for the CCP algorithm in order to use it successfully. Otherwise, the kernel would have no way to get the real frame for unpacking and delivering the data. This is also why the man page mentions Predictor-1 only works if the kernel supports it.
In keeping with my "compress data once, at the highest layer possible" philosophy, I disable CCP and let the rest of the options negotiate normally. accomp and pcomp are clearly desirable, while vjcomp is something I'd have to measure to be certain about.
AuthenticationThere are several authentication methods that pppd supports:
- PAP: the Password Authentication Protocol, the barest-bones possibility. It simply sends the username and password over the link, exposing it to anyone able to sniff the link traffic.
- CHAP: the Challenge-Handshake Authentication Protocol, RFC 1994 (August 1996). Designed to avoid sending the plain-text password over the wire (as in PAP); the challenge is hashed with a counter and the password to produce a response. The disadvantage to this protocol is that the password must be stored in plain text at both ends, which is not secure practice.
- MS CHAP v1: Microsoft CHAP version 1, removed from Windows Vista and above. Designed to allow both ends of the connection to authenticate with each other in the same number of packets that CHAP used, and to lift the requirement to store unencrypted passwords. Unfortunately, the encryption is notoriously weak (DES-ECB). Yes, single DES. Called mschap by pppd.
- MS CHAP v2: Microsoft CHAP version 2, introduced in NT 4. This is an improved version of MS CHAP v1, obviously, but remains weak: its security depends on the MD4 (yes, four) hash of the password encrypted with... DES-ECB again. Yes, single DES. This is exposed to the network every time a PPTP connection is set up, so don't do that. Called mschap-v2 by pppd.
- EAP: the Extensible Authentication Protocol Train Wreck. Instead of making more specific authentication methods, EAP is more of a message format that allows the use of any protocol defined within EAP that the client and server both support. Originally this was EAP-TLS that did client authentication based on the client presenting an X.509 certificate; a number of other plaintext password methods were added over time, and then "EAP in TLS" was invented (but, as far as I know, hasn't been added back to PPP) because EAP was busy assuming a protected communication channel.
So, all the options suck in their own unique ways. As long as
refuse-mschap-v2isn't in effect, things should work. For an L2TP VPN connection, the underlying channel is encrypted by IPSec, so sending around plain-text passwords isn't a big deal if the other auth mechanisms do get used.
Configuring L2TP and PPP
I'm using xl2tpd, the currently-living fork of the original l2tpd. Let's have a look at some configuration, with comments to follow:
port = 1701
ip range = 172.16.0.2-172.16.0.254
local ip = 172.16.0.1
require authentication = yes
name = l2tpd
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
The important parts in here (i.e., the parts there is a meaningful choice for) are the IP addresses, and the location of the pppoptfile which will provide our options to pppd. The IP range defines the addresses that may be handed out to VPN clients, thus implicitly limiting the number of clients that can be connected simultaneously, and the local IP defines the IP of the server over L2TP. Of course, it has to be outside of the IP range.
As for the pppoptfile, it gets options of its own:
ms-dns 172.16.0.1asyncmap 0
We lead off with options for pppd to not worry its pretty little head about the IP addresses, since we're assigning those with xl2tpd. Then comes noccp (as discussed above and in part I, I prefer to compress at higher layers of the stack) and a DNS server address which probably doesn't work, much to my eternal disappointment. Then there's a bunch of magic again.
I would like to research most of those more—especially the
mruvalues, which limit the data size of the packets—but I have not, at the time of this writing, had a chance to get into it. I have seen other mtu/mru values in the 1200s but I also saw 1200 and just took the minimum to be safe. I think the right value depends on the PPP and IPSec (and NAT-T) framing overhead, with the goal of fitting the "largest packet inside PPP" into a 1500-byte Ethernet frame once all the icing is on it.
The magic ends at
localwhich tells pppd that this is not a real, physical modem or serial port. The name specifies the server name to use for authentication. It's important for this to match the corresponding field in /etc/ppp/chap-secrets, but it's apparently not related to xl2tpd's name option at all.
proxyarp specifies that ARP requests should be bridged across the link, which I think we'll come back to in part III. Finally, there's a pair of dead link detection parameters, that check if the client is connected every
intervalseconds, and disconnect it if it doesn't reply
failuretimes in a row.
Getting back to /etc/ppp/chap-secrets, it generally ships with some example lines and documentation included, but on the off chance it doesn't, the lines should look something like this:
sapphirepaw xl2tpd b3stp455wrrd *
Obviously sapphirepaw is the username and b3stp455wrrd is the password. (But it is not actually the best password. Or a real password.) Using the name option here, sapphirepaw could conceivably have a different password for a different means of connecting to pppd that used its own options file.
Debugging xl2tpd/pppdI honestly can't remember having much trouble at this layer, in spite of the amount of effort I put into learning about it and studying what was going on. It's parts I and III of this series that really made me work.
That said, OS X's advanced logging, or using pppd from a Linux machine in debug/nodetach mode, are pretty helpful for figuring out things like "oh, we need mschap-v2 enabled" when the two sides get into an LCP war. From OS X, it looked like "got config-request for PAP, rejecting it, sending config-request for mschap-v2, got config-request for PAP, ..."