Add L2TP support.
authorPaul Mackerras <paulus@samba.org>
Sun, 15 Jun 2008 07:08:49 +0000 (07:08 +0000)
committerPaul Mackerras <paulus@samba.org>
Sun, 15 Jun 2008 07:08:49 +0000 (07:08 +0000)
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>
README.pppol2tp [new file with mode: 0644]
configure
pppd/plugins/Makefile.linux
pppd/plugins/pppol2tp/Makefile.linux [new file with mode: 0644]
pppd/plugins/pppol2tp/pppol2tp.c [new file with mode: 0644]

diff --git a/README.pppol2tp b/README.pppol2tp
new file mode 100644 (file)
index 0000000..f34e89f
--- /dev/null
@@ -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
index 2a658dbf6d936547ee10db6ef9b0a9f9570f80e4..56816b8240f0458183501d3785dded9a12af5b6f 100755 (executable)
--- a/configure
+++ b/configure
@@ -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
index 39f523a9203d9712818325dc6bfb9a3b4b97ff1d..6de115edcae86a45861d6f229a6427003f322311 100644 (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
diff --git a/pppd/plugins/pppol2tp/Makefile.linux b/pppd/plugins/pppol2tp/Makefile.linux
new file mode 100644 (file)
index 0000000..7a4b18f
--- /dev/null
@@ -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 $@ $<
diff --git a/pppd/plugins/pppol2tp/pppol2tp.c b/pppd/plugins/pppol2tp/pppol2tp.c
new file mode 100644 (file)
index 0000000..a7e3400
--- /dev/null
@@ -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
+};