[PATCH ppp-2.4.4] Introduce L2TP support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The attached patch set introduces L2TP support for Linux. There are 2 patches.

ppp-add-pppol2tp-plugin.patch
Adds a pppol2tp plugin which lets pppd use the Linux pppol2tp.ko kernel driver to pass PPP frames in L2TP tunnels using PPPoL2TP sockets.

ppp-add-openl2tp-plugin.patch
Adds an event interface which is used by the OpenL2TP implementation to receive events from pppd using a Unix socket.

These plugins are being actively used by users of OpenL2TP and other
L2TP daemons. Please consider for inclusion in the next release of
pppd.

Thanks!

--
James Chapman
Katalix Systems Ltd
http://www.katalix.com
Catalysts for your Embedded Linux software development

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@xxxxxxxxxxx>

Index: ppp-2.4.4/README.pppol2tp
===================================================================
--- /dev/null
+++ ppp-2.4.4/README.pppol2tp
@@ -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@xxxxxxxxxxxxxxxxxxxxxx
+
+Maintained by:
+	James Chapman
+	jchapman@xxxxxxxxxxx
+	Katalix Systems Ltd
+	http://www.katalix.com
Index: ppp-2.4.4/pppd/plugins/pppol2tp/Makefile.linux
===================================================================
--- /dev/null
+++ ppp-2.4.4/pppd/plugins/pppol2tp/Makefile.linux
@@ -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 $@ $<
Index: ppp-2.4.4/pppd/plugins/pppol2tp/pppol2tp.c
===================================================================
--- /dev/null
+++ ppp-2.4.4/pppd/plugins/pppol2tp/pppol2tp.c
@@ -0,0 +1,500 @@
+/* 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@xxxxxxxxx>
+ * Modified by jchapman@xxxxxxxxxxx
+ *
+ * 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
+};
Index: ppp-2.4.4/pppd/plugins/Makefile.linux
===================================================================
--- ppp-2.4.4.orig/pppd/plugins/Makefile.linux
+++ ppp-2.4.4/pppd/plugins/Makefile.linux
@@ -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
Index: ppp-2.4.4/configure
===================================================================
--- ppp-2.4.4.orig/configure
+++ ppp-2.4.4/configure
@@ -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: ppp-2.4.4/include/linux/if_pppol2tp.h
===================================================================
--- /dev/null
+++ ppp-2.4.4/include/linux/if_pppol2tp.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ * Linux PPP over L2TP (PPPoL2TP) Socket Implementation (RFC 2661)
+ *
+ * This file supplies definitions required by the PPP over L2TP driver
+ * (pppol2tp.c).  All version information wrt this file is located in pppol2tp.c
+ *
+ * License:
+ *		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.
+ *
+ */
+
+#ifndef __LINUX_IF_PPPOL2TP_H
+#define __LINUX_IF_PPPOL2TP_H
+
+#include <asm/types.h>
+
+#ifdef __KERNEL__
+#include <linux/in.h>
+#endif
+
+/* Structure used to connect() the socket to a particular tunnel UDP
+ * socket.
+ */
+struct pppol2tp_addr
+{
+	pid_t	pid;			/* pid that owns the fd.
+					 * 0 => current */
+	int	fd;			/* FD of UDP socket to use */
+
+	struct sockaddr_in addr;	/* IP address and port to send to */
+
+	__u16 s_tunnel, s_session;	/* For matching incoming packets */
+	__u16 d_tunnel, d_session;	/* For sending outgoing packets */
+};
+
+/* Socket options:
+ * DEBUG	- bitmask of debug message categories
+ * SENDSEQ	- 0 => don't send packets with sequence numbers
+ *		  1 => send packets with sequence numbers
+ * RECVSEQ	- 0 => receive packet sequence numbers are optional
+ *		  1 => drop receive packets without sequence numbers
+ * LNSMODE	- 0 => act as LAC.
+ *		  1 => act as LNS.
+ * REORDERTO	- reorder timeout (in millisecs). If 0, don't try to reorder.
+ */
+enum {
+	PPPOL2TP_SO_DEBUG	= 1,
+	PPPOL2TP_SO_RECVSEQ	= 2,
+	PPPOL2TP_SO_SENDSEQ	= 3,
+	PPPOL2TP_SO_LNSMODE	= 4,
+	PPPOL2TP_SO_REORDERTO	= 5,
+};
+
+/* Debug message categories for the DEBUG socket option */
+enum {
+	PPPOL2TP_MSG_DEBUG	= (1 << 0),	/* verbose debug (if
+						 * compiled in) */
+	PPPOL2TP_MSG_CONTROL	= (1 << 1),	/* userspace - kernel
+						 * interface */
+	PPPOL2TP_MSG_SEQ	= (1 << 2),	/* sequence numbers */
+	PPPOL2TP_MSG_DATA	= (1 << 3),	/* data packets */
+};
+
+
+
+#endif
This patch adds a plugin that lets OpenL2TP receive events from
pppd. It requires the pppol2tp plugin. Events are passed using a Unix
socket.

Signed-off-by: James Chapman <jchapman@xxxxxxxxxxx>

Index: ppp-2.4.4/pppd/plugins/pppol2tp/Makefile.linux
===================================================================
--- ppp-2.4.4.orig/pppd/plugins/pppol2tp/Makefile.linux
+++ ppp-2.4.4/pppd/plugins/pppol2tp/Makefile.linux
@@ -1,6 +1,6 @@
 CC	= gcc
 COPTS	= -O2 -g
-CFLAGS	= $(COPTS) -I../.. -I../../../include -fPIC
+CFLAGS	= $(COPTS) -I. -I../.. -I../../../include -fPIC
 LDFLAGS	= -shared
 INSTALL	= install
 
@@ -11,7 +11,7 @@ LIBDIR = $(DESTDIR)/lib/pppd/$(VERSION)
 
 VERSION = $(shell awk -F '"' '/VERSION/ { print $$2; }' ../../patchlevel.h)
 
-PLUGINS := pppol2tp.so
+PLUGINS := pppol2tp.so openl2tp.so
 
 all: $(PLUGINS)
 
Index: ppp-2.4.4/pppd/plugins/pppol2tp/l2tp_event.h
===================================================================
--- /dev/null
+++ ppp-2.4.4/pppd/plugins/pppol2tp/l2tp_event.h
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * Copyright (C) 2008 Katalix Systems Ltd
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ *****************************************************************************/
+
+/*
+ * OpenL2TP application event interface definition.
+ *
+ * This plugin is used by OpenL2TP to receive events from pppd.
+ *
+ * Events are used as follows:-
+ * PPP_UPDOWN_IND	- tells OpenL2TP of PPP session state changes.
+ * PPP_ACCM_IND		- tells OpenL2TP of PPP ACCM negotiated options
+ *
+ * Non-GPL applications are permitted to use this API.
+ */
+
+#ifndef L2TP_EVENT_H
+#define L2TP_EVENT_H
+
+#include <stdint.h>
+
+/*****************************************************************************
+ * API definition
+ *****************************************************************************/
+
+#define OPENL2TP_EVENT_SOCKET_NAME		"/tmp/openl2tp-event.sock"
+
+#define OPENL2TP_MSG_TYPE_NULL			0
+#define OPENL2TP_MSG_TYPE_PPP_UPDOWN_IND	1
+#define OPENL2TP_MSG_TYPE_PPP_ACCM_IND		2
+#define OPENL2TP_MSG_TYPE_MAX			3
+
+enum {
+	OPENL2TP_TLV_TYPE_TUNNEL_ID,
+	OPENL2TP_TLV_TYPE_SESSION_ID,
+	OPENL2TP_TLV_TYPE_PPP_ACCM,
+	OPENL2TP_TLV_TYPE_PPP_UNIT,
+	OPENL2TP_TLV_TYPE_PPP_IFNAME,
+	OPENL2TP_TLV_TYPE_PPP_USER_NAME,
+	OPENL2TP_TLV_TYPE_PPP_STATE
+};
+#define OPENL2TP_TLV_TYPE_MAX		(OPENL2TP_TLV_TYPE_PPP_STATE + 1)
+
+#define OPENL2TP_MSG_MAX_LEN		512
+#define OPENL2TP_MSG_SIGNATURE		0x6b6c7831
+
+#define ALIGN32(n) (((n) + 3) & ~3)
+
+/* Each data field in a message is defined by a Type-Length-Value
+ * (TLV) tuplet.
+ */
+struct openl2tp_event_tlv {
+	uint16_t	tlv_type;
+	uint16_t	tlv_len;
+	uint8_t		tlv_value[0];
+};
+
+/* Messages contain a small header followed by a list of TLVs. Each
+ * TLV starts on a 4-byte boundary.
+ */
+struct openl2tp_event_msg {
+	uint32_t	msg_signature;	/* OPENL2TP_MSG_SIGNATURE */
+	uint16_t	msg_type;	/* OPENL2TP_MSG_TYPE_* */
+	uint16_t	msg_len;	/* length of data that follows */
+	uint8_t		msg_data[0];	/* list of TLVs, each always longword aligned */
+};
+
+/* These structs define the data field layout of each TLV.
+ */
+struct openl2tp_tlv_tunnel_id {
+	uint16_t	tunnel_id;
+};
+
+struct openl2tp_tlv_session_id {
+	uint16_t	session_id;
+};
+
+struct openl2tp_tlv_ppp_accm {
+	uint32_t	send_accm;
+	uint32_t	recv_accm;
+};
+
+struct openl2tp_tlv_ppp_unit {
+	uint32_t	unit;
+};
+
+struct openl2tp_tlv_ppp_state {
+	uint8_t		up;		/* 0=down, 1=up */
+};
+
+struct openl2tp_tlv_ppp_ifname {
+	char		ifname[0];
+};
+
+struct openl2tp_tlv_ppp_user_name {
+	char		user_name[0];
+};
+
+#endif /* L2TP_EVENT_H */
Index: ppp-2.4.4/pppd/plugins/pppol2tp/openl2tp.c
===================================================================
--- /dev/null
+++ ppp-2.4.4/pppd/plugins/pppol2tp/openl2tp.c
@@ -0,0 +1,308 @@
+/*****************************************************************************
+ * Copyright (C) 2006,2007,2008 Katalix Systems Ltd
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *****************************************************************************/
+
+/* pppd plugin for interfacing to openl2tpd */
+
+#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 <sys/un.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>
+
+#include "l2tp_event.h"
+
+extern int pppol2tp_tunnel_id;
+extern int pppol2tp_session_id;
+
+extern void (*pppol2tp_send_accm_hook)(int tunnel_id, int session_id, uint32_t send_accm, uint32_t recv_accm);
+extern void (*pppol2tp_ip_updown_hook)(int tunnel_id, int session_id, int up);
+
+const char pppd_version[] = VERSION;
+
+static int openl2tp_fd = -1;
+
+static void (*old_pppol2tp_send_accm_hook)(int tunnel_id, int session_id, uint32_t send_accm, uint32_t recv_accm) = NULL;
+static void (*old_pppol2tp_ip_updown_hook)(int tunnel_id, int session_id, int up) = NULL;
+static int (*old_new_phase_hook)(int phase) = NULL;
+
+/*****************************************************************************
+ * OpenL2TP RPC interface.
+ * We send a PPP_ACCM_IND to openl2tpd to report ACCM values and
+ * SESSION_PPP_UPDOWN_IND to indicate when the PPP link comes up or
+ * goes down.
+ * Async RPC is used so that we don't stall waiting for openl2tpd to send a
+ * reply. Since we're running on the same host as openl2tpd, UDP is reliable,
+ * hence there's no need to worry about whether our message arrives.
+ *****************************************************************************/
+
+static int openl2tp_client_create(void)
+{
+	struct sockaddr_un addr;
+	int result;
+
+	if (openl2tp_fd < 0) {
+		openl2tp_fd = socket(PF_UNIX, SOCK_DGRAM, 0);
+		if (openl2tp_fd < 0) {
+			error("openl2tp connection create: %m");
+			return -ENOTCONN;
+		}
+
+		addr.sun_family = AF_UNIX;
+		strcpy(&addr.sun_path[0], OPENL2TP_EVENT_SOCKET_NAME);
+
+		result = connect(openl2tp_fd, (struct sockaddr *) &addr, sizeof(addr));
+		if (result < 0) {
+			error("openl2tp connection connect: %m");
+			return -ENOTCONN;
+		}
+	}
+
+	return 0;
+}
+
+static void openl2tp_client_delete(void)
+{
+	if (openl2tp_fd >= 0) {
+		close(openl2tp_fd);
+		openl2tp_fd = -1;
+	}
+}
+
+
+static void openl2tp_send_accm_ind(int tunnel_id, int session_id, uint32_t send_accm, uint32_t recv_accm)
+{
+	int result;
+	uint8_t buf[OPENL2TP_MSG_MAX_LEN];
+	struct openl2tp_event_msg *msg = (void *) &buf[0];
+	struct openl2tp_event_tlv *tlv;
+	uint16_t tid = tunnel_id;
+	uint16_t sid = session_id;
+	struct openl2tp_tlv_ppp_accm accm;
+
+	if (openl2tp_fd < 0) {
+		result = openl2tp_client_create();
+		if (result < 0) {
+			goto out;
+		}
+	}
+
+	accm.send_accm = send_accm;
+	accm.recv_accm = recv_accm;
+
+	msg->msg_signature = OPENL2TP_MSG_SIGNATURE;
+	msg->msg_type = OPENL2TP_MSG_TYPE_PPP_ACCM_IND;
+	msg->msg_len = 0;
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_TUNNEL_ID;
+	tlv->tlv_len = sizeof(tid);
+	memcpy(&tlv->tlv_value[0], &tid, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_SESSION_ID;
+	tlv->tlv_len = sizeof(sid);
+	memcpy(&tlv->tlv_value[0], &sid, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_PPP_ACCM;
+	tlv->tlv_len = sizeof(accm);
+	memcpy(&tlv->tlv_value[0], &accm, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	result = send(openl2tp_fd, msg, sizeof(*msg) + msg->msg_len, MSG_NOSIGNAL);
+	if (result < 0) {
+		error("openl2tp send: %m");
+	}
+	if (result != (sizeof(*msg) + msg->msg_len)) {
+		warn("openl2tp send: unexpected byte count %d, expected %d", result, sizeof(msg) + msg->msg_len);
+	}
+	dbglog("openl2tp send: sent PPP_ACCM_IND, %d bytes", result);
+
+out:
+	if (old_pppol2tp_send_accm_hook != NULL) {
+		(*old_pppol2tp_send_accm_hook)(tunnel_id, session_id, send_accm, recv_accm);
+	}
+	return;
+}
+
+static void openl2tp_ppp_updown_ind(int tunnel_id, int session_id, int up)
+{
+	int result;
+	uint8_t buf[OPENL2TP_MSG_MAX_LEN];
+	struct openl2tp_event_msg *msg = (void *) &buf[0];
+	struct openl2tp_event_tlv *tlv;
+	uint16_t tid = tunnel_id;
+	uint16_t sid = session_id;
+	uint8_t state = up;
+	int unit = ifunit;
+	char *user_name = NULL;
+
+	if (openl2tp_fd < 0) {
+		result = openl2tp_client_create();
+		if (result < 0) {
+			goto out;
+		}
+	}
+
+	if (peer_authname[0] != '\0') {
+		user_name = strdup(peer_authname);
+	}
+
+	msg->msg_signature = OPENL2TP_MSG_SIGNATURE;
+	msg->msg_type = OPENL2TP_MSG_TYPE_PPP_UPDOWN_IND;
+	msg->msg_len = 0;
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_TUNNEL_ID;
+	tlv->tlv_len = sizeof(tid);
+	memcpy(&tlv->tlv_value[0], &tid, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_SESSION_ID;
+	tlv->tlv_len = sizeof(sid);
+	memcpy(&tlv->tlv_value[0], &sid, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_PPP_STATE;
+	tlv->tlv_len = sizeof(state);
+	memcpy(&tlv->tlv_value[0], &state, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_PPP_UNIT;
+	tlv->tlv_len = sizeof(unit);
+	memcpy(&tlv->tlv_value[0], &unit, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	tlv = (void *) &msg->msg_data[msg->msg_len];
+	tlv->tlv_type = OPENL2TP_TLV_TYPE_PPP_IFNAME;
+	tlv->tlv_len = strlen(ifname) + 1;
+	memcpy(&tlv->tlv_value[0], ifname, tlv->tlv_len);
+	msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+
+	if (user_name != NULL) {
+		tlv = (void *) &msg->msg_data[msg->msg_len];
+		tlv->tlv_type = OPENL2TP_TLV_TYPE_PPP_USER_NAME;
+		tlv->tlv_len = strlen(user_name) + 1;
+		memcpy(&tlv->tlv_value[0], user_name, tlv->tlv_len);
+		msg->msg_len += sizeof(*tlv) + ALIGN32(tlv->tlv_len);
+	}
+
+	result = send(openl2tp_fd, msg, sizeof(*msg) + msg->msg_len, MSG_NOSIGNAL);
+	if (result < 0) {
+		error("openl2tp send: %m");
+	}
+	if (result != (sizeof(*msg) + msg->msg_len)) {
+		warn("openl2tp send: unexpected byte count %d, expected %d", result, sizeof(msg) + msg->msg_len);
+	}
+	dbglog("openl2tp send: sent PPP_UPDOWN_IND, %d bytes", result);
+
+out:
+	if (old_pppol2tp_ip_updown_hook != NULL) {
+		(*old_pppol2tp_ip_updown_hook)(tunnel_id, session_id, up);
+	}
+
+	return;
+}
+
+/*****************************************************************************
+ * When a multilink interface is created, there are 2 cases to consider.
+ *
+ * 1. The new interface is the first of a multilink bundle (master).
+ * 2. The new interface is being attached to an existing bundle.
+ *
+ * The first case is handled by existing code because the interface
+ * generates ip-up events just like standard interfaces. But in the
+ * second case, where the interface is added to an existing ppp
+ * bundle, pppd does not do IP negotiation and so as a result, no
+ * ip-up event is generated when the interface is created. Since
+ * openl2tpd needs the SESSION_PPP_UPDOWN_IND for all interfaces of a
+ * PPP bundle, we must fake the event.
+ *
+ * Unfortunately, pppd provides no easy way to tell that the new link
+ * successfully attaches to the bundle. We use its new_phase_hook to
+ * watch state changes. If the new interface reaches PHASE_NETWORK,
+ * then it is up. However, pppd sets variables that we need to test
+ * _after_ it reaches PHASE_NETWORK, so we use a timer to check the
+ * variables. This is messy. It would be much easier if pppd exported
+ * a hook for multilink interface up.
+ *****************************************************************************/
+
+static void openl2tp_multilink_check(void *p)
+{
+	if (doing_multilink && !multilink_master) {
+		/* send event only if not master */
+		openl2tp_ppp_updown_ind(pppol2tp_tunnel_id, pppol2tp_session_id, 1);
+	}
+}
+
+static int openl2tp_new_phase_hook(int phase)
+{
+	if ((phase == PHASE_NETWORK) && multilink) {
+		TIMEOUT(openl2tp_multilink_check, NULL, 1);
+	}
+
+	return 0;
+}
+
+/*****************************************************************************
+ * Application init
+ *****************************************************************************/
+
+void plugin_init(void)
+{
+	old_pppol2tp_send_accm_hook = pppol2tp_send_accm_hook;
+	pppol2tp_send_accm_hook = openl2tp_send_accm_ind;
+
+	old_pppol2tp_ip_updown_hook = pppol2tp_ip_updown_hook;
+	pppol2tp_ip_updown_hook = openl2tp_ppp_updown_ind;
+
+	old_new_phase_hook = new_phase_hook;
+	new_phase_hook = openl2tp_new_phase_hook;
+}
+

[Index of Archives]     [Linux Audio Users]     [Linux for Hams]     [Kernel Newbies]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Fedora Users]

  Powered by Linux