Add L2TP support.

Patch from James Chapman.

This patch adds support for L2TP. It allows pppd to interface with the
pppol2tp driver in the Linux kernel. All data packets are handled by
the Linux kernel in order that the datapath be as efficient as
possible, while a userspace daemon implements the L2TP control
protocol, handling tunnel/session setup and teardown. The
implementation uses the PPPoX infrastructure; the architecture is
similar to PPPoE/PPPoATM in that a userspace daemon spawns a pppd
process per PPP session and uses a protocol-specific plugin to connect
pppd with the kernel.

The pppol2tp Linux kernel driver was integrated in the Linux kernel
from 2.6.23. For earlier kernels, an out of tree driver is available
from the pppol2tp-kmod package on the OpenL2TP project site at
http://sourceforge.net/projects/openl2tp.

Signed-off-by: James Chapman <jchapman@katalix.com>
This commit is contained in:
Paul Mackerras 2008-06-15 07:08:49 +00:00
parent b82a6c7601
commit c3480c5c56
5 changed files with 625 additions and 3 deletions

66
README.pppol2tp Normal file
View File

@ -0,0 +1,66 @@
PPPoL2TP plugin
===============
The pppol2tp plugin lets pppd use the Linux kernel driver pppol2tp.ko
to pass PPP frames in L2TP tunnels. The driver was integrated into the
kernel in the 2.6.23 release. For kernels before 2.6.23, an
out-of-tree kernel module is available from the pppol2tp-kmod package
in the OpenL2TP project.
Note that pppd receives only PPP control frames over the PPPoL2TP
socket; data frames are handled entirely by the kernel.
The pppol2tp plugin adds extra arguments to pppd and uses the Linux kernel
PPP-over-L2TP driver to set up each session's data path.
Arguments are:-
pppol2tp <fd> - FD for PPPoL2TP socket
pppol2tp_lns_mode - PPPoL2TP LNS behavior. Default off.
pppol2tp_send_seq - PPPoL2TP enable sequence numbers in
transmitted data packets. Default off.
pppol2tp_recv_seq - PPPoL2TP enforce sequence numbers in
received data packets. Default off.
pppol2tp_reorderto <millisecs> - PPPoL2TP data packet reorder timeout.
Default 0 (no reordering).
pppol2tp_debug_mask <mask> - PPPoL2TP debug mask. Bitwise OR of
1 - verbose debug
2 - control
4 - kernel transport
8 - ppp packet data
Default: 0 (no debug).
pppol2tp_ifname <ifname> - Name of PPP network interface visible
to "ifconfig" and "ip link".
Default: "pppN"
pppol2tp_tunnel_id <id> - L2TP tunnel_id tunneling this PPP
session.
pppol2tp_session_id <id> - L2TP session_id of this PPP session.
The tunnel_id/session_id pair is used
when sending event messages to openl2tpd.
pppd will typically be started by an L2TP daemon for each L2TP sesion,
supplying one or more of the above arguments as required. The pppd
user will usually have no visibility of these arguments.
Two hooks are exported by this plugin.
void (*pppol2tp_send_accm_hook)(int tunnel_id, int session_id,
uint32_t send_accm, uint32_t recv_accm);
void (*pppol2tp_ip_updown_hook)(int tunnel_id, int session_id, int up);
Credits
=======
This plugin was developed by Katalix Systems as part of the OpenL2TP
project, http://openl2tp.sourceforge.net. OpenL2TP is a full-featured
L2TP client-server, suitable for use as an enterprise L2TP VPN server
or a VPN client.
Please copy problems to the OpenL2TP mailing list:
openl2tp-users@lists.sourceforge.net.
Maintained by:
James Chapman
jchapman@katalix.com
Katalix Systems Ltd
http://www.katalix.com

5
configure vendored
View File

@ -1,5 +1,5 @@
#!/bin/sh
# $Id: configure,v 1.37 2005/06/26 23:53:17 carlsonj Exp $
# $Id: configure,v 1.38 2008/06/15 07:08:49 paulus Exp $
# Where to install stuff by default
DESTDIR=/usr/local
@ -194,7 +194,8 @@ if [ -d "$ksrc" ]; then
mkmkf $ksrc/Makefile.top Makefile
mkmkf $ksrc/Makedefs$compiletype Makedefs.com
for dir in pppd pppstats chat pppdump pppd/plugins pppd/plugins/rp-pppoe \
pppd/plugins/radius pppd/plugins/pppoatm; do
pppd/plugins/radius pppd/plugins/pppoatm \
pppd/plugins/pppol2tp; do
mkmkf $dir/Makefile.$makext $dir/Makefile
done
if [ -f $ksrc/Makefile.$makext$archvariant ]; then

View File

@ -9,7 +9,7 @@ BINDIR = $(DESTDIR)/sbin
MANDIR = $(DESTDIR)/share/man/man8
LIBDIR = $(DESTDIR)/lib/pppd/$(VERSION)
SUBDIRS := rp-pppoe pppoatm
SUBDIRS := rp-pppoe pppoatm pppol2tp
# Uncomment the next line to include the radius authentication plugin
SUBDIRS += radius
PLUGINS := minconn.so passprompt.so passwordfd.so winbind.so

View File

@ -0,0 +1,29 @@
CC = gcc
COPTS = -O2 -g
CFLAGS = $(COPTS) -I../.. -I../../../include -fPIC
LDFLAGS = -shared
INSTALL = install
#***********************************************************************
DESTDIR = @DESTDIR@
LIBDIR = $(DESTDIR)/lib/pppd/$(VERSION)
VERSION = $(shell awk -F '"' '/VERSION/ { print $$2; }' ../../patchlevel.h)
PLUGINS := pppol2tp.so
all: $(PLUGINS)
%.so: %.o
$(CC) $(CFLAGS) -o $@ -shared $^ $(LIBS)
install: all
$(INSTALL) -d -m 755 $(LIBDIR)
$(INSTALL) -c -m 4550 $(PLUGIN) $(LIBDIR)
clean:
rm -f *.o *.so
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<

View File

@ -0,0 +1,526 @@
/* pppol2tp.c - pppd plugin to implement PPPoL2TP protocol
* for Linux using kernel pppol2tp support.
*
* Requires kernel pppol2tp driver which is integrated into the kernel
* from 2.6.23 onwards. For earlier kernels, a version can be obtained
* from the OpenL2TP project at
* http://www.sourceforge.net/projects/openl2tp/
*
* Original by Martijn van Oosterhout <kleptog@svana.org>
* Modified by jchapman@katalix.com
*
* Heavily based upon pppoatm.c: original notice follows
*
* Copyright 2000 Mitchell Blank Jr.
* Based in part on work from Jens Axboe and Paul Mackerras.
* Updated to ppp-2.4.1 by Bernhard Kaindl
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "pppd.h"
#include "pathnames.h"
#include "fsm.h"
#include "lcp.h"
#include "ccp.h"
#include "ipcp.h"
#include <sys/stat.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <linux/version.h>
#include <linux/sockios.h>
#ifndef aligned_u64
/* should be defined in sys/types.h */
#define aligned_u64 unsigned long long __attribute__((aligned(8)))
#endif
#include <linux/types.h>
#include <linux/if_ether.h>
#include <linux/ppp_defs.h>
#include <linux/if_ppp.h>
#include <linux/if_pppox.h>
#include <linux/if_pppol2tp.h>
/* should be added to system's socket.h... */
#ifndef SOL_PPPOL2TP
#define SOL_PPPOL2TP 273
#endif
const char pppd_version[] = VERSION;
static int setdevname_pppol2tp(char **argv);
static int pppol2tp_fd = -1;
static char *pppol2tp_fd_str;
static bool pppol2tp_lns_mode = 0;
static bool pppol2tp_recv_seq = 0;
static bool pppol2tp_send_seq = 0;
static int pppol2tp_debug_mask = 0;
static int pppol2tp_reorder_timeout = 0;
static char pppol2tp_ifname[32] = { 0, };
int pppol2tp_tunnel_id = 0;
int pppol2tp_session_id = 0;
static int device_got_set = 0;
struct channel pppol2tp_channel;
static void (*old_snoop_recv_hook)(unsigned char *p, int len) = NULL;
static void (*old_snoop_send_hook)(unsigned char *p, int len) = NULL;
static void (*old_ip_up_hook)(void) = NULL;
static void (*old_ip_down_hook)(void) = NULL;
/* Hook provided to allow other plugins to handle ACCM changes */
void (*pppol2tp_send_accm_hook)(int tunnel_id, int session_id,
uint32_t send_accm, uint32_t recv_accm) = NULL;
/* Hook provided to allow other plugins to handle IP up/down */
void (*pppol2tp_ip_updown_hook)(int tunnel_id, int session_id, int up) = NULL;
static option_t pppol2tp_options[] = {
{ "pppol2tp", o_special, &setdevname_pppol2tp,
"FD for PPPoL2TP socket", OPT_DEVNAM | OPT_A2STRVAL,
&pppol2tp_fd_str },
{ "pppol2tp_lns_mode", o_bool, &pppol2tp_lns_mode,
"PPPoL2TP LNS behavior. Default off.",
OPT_PRIO | OPRIO_CFGFILE },
{ "pppol2tp_send_seq", o_bool, &pppol2tp_send_seq,
"PPPoL2TP enable sequence numbers in transmitted data packets. "
"Default off.",
OPT_PRIO | OPRIO_CFGFILE },
{ "pppol2tp_recv_seq", o_bool, &pppol2tp_recv_seq,
"PPPoL2TP enforce sequence numbers in received data packets. "
"Default off.",
OPT_PRIO | OPRIO_CFGFILE },
{ "pppol2tp_reorderto", o_int, &pppol2tp_reorder_timeout,
"PPPoL2TP data packet reorder timeout. Default 0 (no reordering).",
OPT_PRIO },
{ "pppol2tp_debug_mask", o_int, &pppol2tp_debug_mask,
"PPPoL2TP debug mask. Default: no debug.",
OPT_PRIO },
{ "pppol2tp_ifname", o_string, &pppol2tp_ifname,
"Set interface name of PPP interface",
OPT_PRIO | OPT_PRIV | OPT_STATIC, NULL, 16 },
{ "pppol2tp_tunnel_id", o_int, &pppol2tp_tunnel_id,
"PPPoL2TP tunnel_id.",
OPT_PRIO },
{ "pppol2tp_session_id", o_int, &pppol2tp_session_id,
"PPPoL2TP session_id.",
OPT_PRIO },
{ NULL }
};
static int setdevname_pppol2tp(char **argv)
{
union {
char buffer[128];
struct sockaddr pppol2tp;
} s;
int len = sizeof(s);
char **a;
int tmp;
int tmp_len = sizeof(tmp);
if (device_got_set)
return 0;
if (!int_option(*argv, &pppol2tp_fd))
return 0;
if(getsockname(pppol2tp_fd, (struct sockaddr *)&s, &len) < 0) {
fatal("Given FD for PPPoL2TP socket invalid (%s)",
strerror(errno));
}
if(s.pppol2tp.sa_family != AF_PPPOX) {
fatal("Socket of not a PPPoX socket");
}
/* Do a test getsockopt() to ensure that the kernel has the necessary
* feature available.
*/
if (getsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_DEBUG,
&tmp, &tmp_len) < 0) {
fatal("PPPoL2TP kernel driver not installed");
}
/* Setup option defaults. Compression options are disabled! */
modem = 0;
lcp_allowoptions[0].neg_accompression = 1;
lcp_wantoptions[0].neg_accompression = 0;
lcp_allowoptions[0].neg_pcompression = 1;
lcp_wantoptions[0].neg_pcompression = 0;
ccp_allowoptions[0].deflate = 0;
ccp_wantoptions[0].deflate = 0;
ipcp_allowoptions[0].neg_vj = 0;
ipcp_wantoptions[0].neg_vj = 0;
ccp_allowoptions[0].bsd_compress = 0;
ccp_wantoptions[0].bsd_compress = 0;
the_channel = &pppol2tp_channel;
device_got_set = 1;
return 1;
}
static int connect_pppol2tp(void)
{
if(pppol2tp_fd == -1) {
fatal("No PPPoL2TP FD specified");
}
return pppol2tp_fd;
}
static void disconnect_pppol2tp(void)
{
if (pppol2tp_fd >= 0) {
close(pppol2tp_fd);
pppol2tp_fd = -1;
}
}
static void send_config_pppol2tp(int mtu,
u_int32_t asyncmap,
int pcomp,
int accomp)
{
struct ifreq ifr;
int on = 1;
int fd;
char reorderto[16];
char tid[8];
char sid[8];
if (pppol2tp_ifname[0]) {
struct ifreq ifr;
int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd >= 0) {
memset (&ifr, '\0', sizeof (ifr));
strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
strlcpy(ifr.ifr_newname, pppol2tp_ifname,
sizeof(ifr.ifr_name));
ioctl(fd, SIOCSIFNAME, (caddr_t) &ifr);
strlcpy(ifname, pppol2tp_ifname, 32);
if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) {
dbglog("ppp%d: interface name %s",
ifunit, ifname);
}
}
close(fd);
}
if ((lcp_allowoptions[0].mru > 0) && (mtu > lcp_allowoptions[0].mru)) {
warn("Overriding mtu %d to %d", mtu, lcp_allowoptions[0].mru);
mtu = lcp_allowoptions[0].mru;
}
netif_set_mtu(ifunit, mtu);
reorderto[0] = '\0';
if (pppol2tp_reorder_timeout > 0)
sprintf(&reorderto[0], "%d ", pppol2tp_reorder_timeout);
tid[0] = '\0';
if (pppol2tp_tunnel_id > 0)
sprintf(&tid[0], "%hu ", pppol2tp_tunnel_id);
sid[0] = '\0';
if (pppol2tp_session_id > 0)
sprintf(&sid[0], "%hu ", pppol2tp_session_id);
dbglog("PPPoL2TP options: %s%s%s%s%s%s%s%s%sdebugmask %d",
pppol2tp_recv_seq ? "recvseq " : "",
pppol2tp_send_seq ? "sendseq " : "",
pppol2tp_lns_mode ? "lnsmode " : "",
pppol2tp_reorder_timeout ? "reorderto " : "", reorderto,
pppol2tp_tunnel_id ? "tid " : "", tid,
pppol2tp_session_id ? "sid " : "", sid,
pppol2tp_debug_mask);
if (pppol2tp_recv_seq)
if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_RECVSEQ,
&on, sizeof(on)) < 0)
fatal("setsockopt(PPPOL2TP_RECVSEQ): %m");
if (pppol2tp_send_seq)
if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_SENDSEQ,
&on, sizeof(on)) < 0)
fatal("setsockopt(PPPOL2TP_SENDSEQ): %m");
if (pppol2tp_lns_mode)
if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_LNSMODE,
&on, sizeof(on)) < 0)
fatal("setsockopt(PPPOL2TP_LNSMODE): %m");
if (pppol2tp_reorder_timeout)
if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_REORDERTO,
&pppol2tp_reorder_timeout,
sizeof(pppol2tp_reorder_timeout)) < 0)
fatal("setsockopt(PPPOL2TP_REORDERTO): %m");
if (pppol2tp_debug_mask)
if (setsockopt(pppol2tp_fd, SOL_PPPOL2TP, PPPOL2TP_SO_DEBUG,
&pppol2tp_debug_mask, sizeof(pppol2tp_debug_mask)) < 0)
fatal("setsockopt(PPPOL2TP_DEBUG): %m");
}
static void recv_config_pppol2tp(int mru,
u_int32_t asyncmap,
int pcomp,
int accomp)
{
if ((lcp_allowoptions[0].mru > 0) && (mru > lcp_allowoptions[0].mru)) {
warn("Overriding mru %d to mtu value %d", mru,
lcp_allowoptions[0].mru);
mru = lcp_allowoptions[0].mru;
}
if ((ifunit >= 0) && ioctl(pppol2tp_fd, PPPIOCSMRU, (caddr_t) &mru) < 0)
error("Couldn't set PPP MRU: %m");
}
/*****************************************************************************
* Snoop LCP message exchanges to capture negotiated ACCM values.
* When asyncmap values have been seen from both sides, give the values to
* L2TP.
* This code is derived from Roaring Penguin L2TP.
*****************************************************************************/
static void pppol2tp_lcp_snoop(unsigned char *buf, int len, int incoming)
{
static bool got_send_accm = 0;
static bool got_recv_accm = 0;
static uint32_t recv_accm = 0xffffffff;
static uint32_t send_accm = 0xffffffff;
static bool snooping = 1;
uint16_t protocol;
uint16_t lcp_pkt_len;
int opt, opt_len;
int reject;
unsigned char const *opt_data;
uint32_t accm;
/* Skip HDLC header */
buf += 2;
len -= 2;
/* Unreasonably short frame?? */
if (len <= 0) return;
/* Get protocol */
if (buf[0] & 0x01) {
/* Compressed protcol field */
protocol = buf[0];
} else {
protocol = ((unsigned int) buf[0]) * 256 + buf[1];
}
/* If it's a network protocol, stop snooping */
if (protocol <= 0x3fff) {
if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) {
dbglog("Turning off snooping: "
"Network protocol %04x found.",
protocol);
}
snooping = 0;
return;
}
/* If it's not LCP, do not snoop */
if (protocol != 0xc021) {
return;
}
/* Skip protocol; go to packet data */
buf += 2;
len -= 2;
/* Unreasonably short frame?? */
if (len <= 0) return;
/* Look for Configure-Ack or Configure-Reject code */
if (buf[0] != CONFACK && buf[0] != CONFREJ) return;
reject = (buf[0] == CONFREJ);
lcp_pkt_len = ((unsigned int) buf[2]) * 256 + buf[3];
/* Something fishy with length field? */
if (lcp_pkt_len > len) return;
/* Skip to options */
len = lcp_pkt_len - 4;
buf += 4;
while (len > 0) {
/* Pull off an option */
opt = buf[0];
opt_len = buf[1];
opt_data = &buf[2];
if (opt_len > len || opt_len < 2) break;
len -= opt_len;
buf += opt_len;
if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) {
dbglog("Found option type %02x; len %d", opt, opt_len);
}
/* We are specifically interested in ACCM */
if (opt == CI_ASYNCMAP && opt_len == 0x06) {
if (reject) {
/* ACCM negotiation REJECTED; use default */
accm = 0xffffffff;
if (pppol2tp_debug_mask & PPPOL2TP_MSG_DATA) {
dbglog("Rejected ACCM negotiation; "
"defaulting (%s)",
incoming ? "incoming" : "outgoing");
}
recv_accm = accm;
send_accm = accm;
got_recv_accm = 1;
got_send_accm = 1;
} else {
memcpy(&accm, opt_data, sizeof(accm));
if (pppol2tp_debug_mask & PPPOL2TP_MSG_DATA) {
dbglog("Found ACCM of %08x (%s)", accm,
incoming ? "incoming" : "outgoing");
}
if (incoming) {
recv_accm = accm;
got_recv_accm = 1;
} else {
send_accm = accm;
got_send_accm = 1;
}
}
if (got_recv_accm && got_send_accm) {
if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) {
dbglog("Telling L2TP: Send ACCM = %08x; "
"Receive ACCM = %08x", send_accm, recv_accm);
}
if (pppol2tp_send_accm_hook != NULL) {
(*pppol2tp_send_accm_hook)(pppol2tp_tunnel_id,
pppol2tp_session_id,
send_accm, recv_accm);
}
got_recv_accm = 0;
got_send_accm = 0;
}
}
}
}
static void pppol2tp_lcp_snoop_recv(unsigned char *p, int len)
{
if (old_snoop_recv_hook != NULL)
(*old_snoop_recv_hook)(p, len);
pppol2tp_lcp_snoop(p, len, 1);
}
static void pppol2tp_lcp_snoop_send(unsigned char *p, int len)
{
if (old_snoop_send_hook != NULL)
(*old_snoop_send_hook)(p, len);
pppol2tp_lcp_snoop(p, len, 0);
}
/*****************************************************************************
* Interface up/down events
*****************************************************************************/
static void pppol2tp_ip_up_hook(void)
{
if (old_ip_up_hook != NULL)
(*old_ip_up_hook)();
if (pppol2tp_ip_updown_hook != NULL) {
(*pppol2tp_ip_updown_hook)(pppol2tp_tunnel_id,
pppol2tp_session_id, 1);
}
}
static void pppol2tp_ip_down_hook(void)
{
if (old_ip_down_hook != NULL)
(*old_ip_down_hook)();
if (pppol2tp_ip_updown_hook != NULL) {
(*pppol2tp_ip_updown_hook)(pppol2tp_tunnel_id,
pppol2tp_session_id, 0);
}
}
/*****************************************************************************
* Application init
*****************************************************************************/
static void pppol2tp_check_options(void)
{
/* Enable LCP snooping for ACCM options only for LNS */
if (pppol2tp_lns_mode) {
if ((pppol2tp_tunnel_id == 0) || (pppol2tp_session_id == 0)) {
fatal("tunnel_id/session_id values not specified");
}
if (pppol2tp_debug_mask & PPPOL2TP_MSG_CONTROL) {
dbglog("Enabling LCP snooping");
}
old_snoop_recv_hook = snoop_recv_hook;
old_snoop_send_hook = snoop_send_hook;
snoop_recv_hook = pppol2tp_lcp_snoop_recv;
snoop_send_hook = pppol2tp_lcp_snoop_send;
}
/* Hook up ip up/down hooks to send indicator to openl2tpd
* that the link is up
*/
old_ip_up_hook = ip_up_hook;
ip_up_hook = pppol2tp_ip_up_hook;
old_ip_down_hook = ip_down_hook;
ip_down_hook = pppol2tp_ip_down_hook;
}
/* Called just before pppd exits.
*/
static void pppol2tp_cleanup(void)
{
if (pppol2tp_debug_mask & PPPOL2TP_MSG_DEBUG) {
dbglog("pppol2tp: exiting.");
}
disconnect_pppol2tp();
}
void plugin_init(void)
{
#if defined(__linux__)
extern int new_style_driver; /* From sys-linux.c */
if (!ppp_available() && !new_style_driver)
fatal("Kernel doesn't support ppp_generic - "
"needed for PPPoL2TP");
#else
fatal("No PPPoL2TP support on this OS");
#endif
add_options(pppol2tp_options);
}
struct channel pppol2tp_channel = {
options: pppol2tp_options,
process_extra_options: NULL,
check_options: &pppol2tp_check_options,
connect: &connect_pppol2tp,
disconnect: &disconnect_pppol2tp,
establish_ppp: &generic_establish_ppp,
disestablish_ppp: &generic_disestablish_ppp,
send_config: &send_config_pppol2tp,
recv_config: &recv_config_pppol2tp,
close: NULL,
cleanup: NULL
};