Categories
Linux and L2TP -- a peek under the hood (part 1)
The Linux Kernel has had support for the L2TP protocol since 2007. In that time the API for interfacing with the kernel's L2TP code has developed and evolved, giving a rich and nuanced interface.
The downside of this is that it can be quite hard to know where to get started if you want to write an L2TP application!
This series of posts seeks to explore the API more fully from the user space programming perspective to give an idea of how to implement L2TP server and client applications using the Linux kernel.
To get the most out of these posts you should have a working knowledge of the POSIX socket programming API and the C language, and be comfortable using Linux.
- In this first post we'll explore application architecture
- In the second post we'll look at the Linux L2TP API in detail
- In the third and final post we'll look at debugging and unmanaged tunnels
L2TP in 30 seconds
If you're reading this article you probably already know what L2TP is. But if not, here's a brief summary.
The Layer 2 Tunnelling Protocol (L2TP) describes a method of transporting Data Link Layer frames over an Internet Protocol (IP) network. This is referred to as "tunnelling", and hence L2TP connections between IP hosts are called "tunnels". Multiple logical connections can be multiplexed into a single tunnel; these are called "sessions".
There are two versions of L2TP. Somewhat confusingly, these are "version 2" (L2TPv2) and "version 3" (L2TPv3). L2TPv2 is described by RFC 2661, L2TPv3 by RFC 3931. L2TPv2 is concerned only with the transport of PPP frames encapsulated in UDP packets. L2TPv3 makes the protocol more generic in order to be able to transport any sort of Layer-2 frame over any sort of packet-switched network.
Although L2TP is quite an obscure protocol to most people, it's widely used. One common use case is to connect home routers into an Internet Service Provider's network. L2TP can also be used in combination with IPSec to implement a Virtual Private Network (VPN).
Application architecture: the big picture
Before we get stuck into any code, we need to cover some fundamental principles which will help you understand the APIs we'll be describing.
Splitting the control plane and data plane
The L2TP RFCs define two basic kinds of L2TP packets: control packets (the "control plane"), and data packets (the "data plane").
Data packets contain session data being carried in the tunnel. The L2TP portions of these packets are only concerned with getting the packet from one end of the tunnel to the other.
Control packets, by contrast, relate to the setup and ongoing management of tunnels and sessions. They contain more complex data in the form of Attribute Value Pairs (AVPs).
The code in the Linux kernel deals only with data packets. The more complex control packets are handled by user space. This split in responsibilities allows the transmission of bulk data to be efficient (it can all be handled by the kernel) while keeping more complex AVP building and parsing code in user space.
When we set up tunnels and sessions using the Linux kernel API, we're just setting up the L2TP data path. All aspects of the control protocol are to be handled by user space.
This split in responsibilities leads to a natural sequence of operations when establishing tunnels and sessions. In broad strokes, the process looks like this:
- Create a tunnel socket. Exchange L2TP control protocol messages with the peer over that socket in order to establish a tunnel.
- Create a tunnel context in the kernel, using information obtained from the peer using the control protocol messages.
- Exchange L2TP control protocol messages with the peer over the tunnel socket in order to establish a session.
- Create a session context in the kernel using information obtained from the peer using the control protocol messages.
The implementation of the L2TP control protocol is a complex subject in its own right. Because we're concentrating on the Linux kernel L2TP API in this article, we're going to ignore the details of the control protocol. If you're interested in what an implementation involves, there are various open-source projects you can examine, for example xl2tpd or openl2tpd.
The Linux netlink L2TP API
The glue that allows a user space application to control the kernel's L2TP code is the L2TP subsystem's netlink API.
Netlink is a bi-directional Inter Process Communication (IPC) mechanism which is widely used by the Linux network stack. A user space application builds and sends netlink messages to the kernel in order to create tunnel and session instances.
If you're unfamiliar with netlink, it's worth reviewing the netlink manpage at this point for an overview.
The Linux L2TP API is defined in the Linux l2tp.h header, which is usually installed on Linux systems as a part of the system headers /usr/include/linux/l2tp.h
. The header calls out the socket address structures used for L2TPv3 IP-encapsulated tunnels, the netlink command and attribute types, and various constants. The L2TP kernel code is exposed via. the "generic netlink" family of APIs.
Note that when creating a netlink message, there's no upfront verification that a given message contains all the required attributes, or that these attributes have the expected type. If you make a mistake, the kernel will reject the message with an error code. Sometimes working out what the problem with the message is requires a bit of reading between the lines!
In the case of the Linux L2TP API, the header l2tp.h
goes some way to documenting the valid attributes for each command. However, as is often the case with kernel programming, the best documentation is the source code! The source code to refer to in this case is the kernel netlink handlers for the L2TP subsystem. This file calls out a table of handlers for each command type and will show you what attribute types are required for each command.
Choosing a netlink library
Although it's possible to create netlink messages from scratch, it's much easier to use a supporting library which simplifies the process and avoids you having to duplicate boilerplate netlink code.
We recommend using either libnl for a comprehensive library, or libmnl for a slightly more minimalistic approach. Either library is a good choice: libnl does a bit more work for you, while libmnl is somewhat more flexible, so it really comes down to which approach fits best for your application.
The details of these libraries are outside the scope of this article, but they're both documented well on their home pages and you shouldn't find it too difficult to get started with either of them.
In the remainder of this series of posts we are going to assume you're using libnl, but the principles are easily applied to using libmnl or even building netlink messages directly with no helper libraries at all.
Next time...
In this post we've given you an overview of how L2TP applications are structured on a Linux system. You could use this approach to build L2TP client or server daemons.
And in fact, that's exactly what we did with our ProL2TP suite of applications!
ProL2TP is a production-ready suite of daemons supporting L2TPv2 and L2TPv3, PPP, and PPPoE. It is scalable from small network devices up to big servers, and we've a range of licensing options, including source code licensing, to suit your deployment.
In the second post in the series we're going to take a look at the details of writing to the Linux kernel L2TP API. See you there!