aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/ppp
diff options
context:
space:
mode:
authorJeff Kirsher <jeffrey.t.kirsher@intel.com>2011-07-31 02:38:19 -0700
committerJeff Kirsher <jeffrey.t.kirsher@intel.com>2011-08-27 00:58:26 -0700
commit224cf5ad14c038b13c119dff29422f178a306f54 (patch)
tree89bf411ea743e9d7bbd3c609eeb7220270a97fc5 /drivers/net/ppp
parentaab3ac26108642eaa06efa4697dab595c7de2bbd (diff)
downloadkernel_goldelico_gta04-224cf5ad14c038b13c119dff29422f178a306f54.zip
kernel_goldelico_gta04-224cf5ad14c038b13c119dff29422f178a306f54.tar.gz
kernel_goldelico_gta04-224cf5ad14c038b13c119dff29422f178a306f54.tar.bz2
ppp: Move the PPP drivers
Move the PPP drivers into drivers/net/ppp/ and make the necessary Kconfig and Makefile changes. CC: Paul Mackerras <paulus@samba.org> CC: Frank Cusack <fcusack@fcusack.com> CC: Michal Ostrowski <mostrows@speakeasy.net> CC: Michal Ostrowski <mostrows@earthlink.net> CC: Dmitry Kozlov <xeb@mail.ru> Signed-off-by: Jeff Kirsher <jeffrey.t.kirsher@intel.com>
Diffstat (limited to 'drivers/net/ppp')
-rw-r--r--drivers/net/ppp/Kconfig175
-rw-r--r--drivers/net/ppp/Makefile13
-rw-r--r--drivers/net/ppp/bsd_comp.c1170
-rw-r--r--drivers/net/ppp/ppp_async.c1028
-rw-r--r--drivers/net/ppp/ppp_deflate.c653
-rw-r--r--drivers/net/ppp/ppp_generic.c2954
-rw-r--r--drivers/net/ppp/ppp_mppe.c740
-rw-r--r--drivers/net/ppp/ppp_mppe.h86
-rw-r--r--drivers/net/ppp/ppp_synctty.c790
-rw-r--r--drivers/net/ppp/pppoe.c1208
-rw-r--r--drivers/net/ppp/pppox.c149
-rw-r--r--drivers/net/ppp/pptp.c717
12 files changed, 9683 insertions, 0 deletions
diff --git a/drivers/net/ppp/Kconfig b/drivers/net/ppp/Kconfig
new file mode 100644
index 0000000..872df3e
--- /dev/null
+++ b/drivers/net/ppp/Kconfig
@@ -0,0 +1,175 @@
+#
+# PPP network device configuration
+#
+
+config PPP
+ tristate "PPP (point-to-point protocol) support"
+ select SLHC
+ ---help---
+ PPP (Point to Point Protocol) is a newer and better SLIP. It serves
+ the same purpose: sending Internet traffic over telephone (and other
+ serial) lines. Ask your access provider if they support it, because
+ otherwise you can't use it; most Internet access providers these
+ days support PPP rather than SLIP.
+
+ To use PPP, you need an additional program called pppd as described
+ in the PPP-HOWTO, available at
+ <http://www.tldp.org/docs.html#howto>. Make sure that you have
+ the version of pppd recommended in <file:Documentation/Changes>.
+ The PPP option enlarges your kernel by about 16 KB.
+
+ There are actually two versions of PPP: the traditional PPP for
+ asynchronous lines, such as regular analog phone lines, and
+ synchronous PPP which can be used over digital ISDN lines for
+ example. If you want to use PPP over phone lines or other
+ asynchronous serial lines, you need to say Y (or M) here and also to
+ the next option, "PPP support for async serial ports". For PPP over
+ synchronous lines, you should say Y (or M) here and to "Support
+ synchronous PPP", below.
+
+ If you said Y to "Version information on all symbols" above, then
+ you cannot compile the PPP driver into the kernel; you can then only
+ compile it as a module. To compile this driver as a module, choose M
+ here. The module will be called ppp_generic.
+
+if PPP
+
+config PPP_BSDCOMP
+ tristate "PPP BSD-Compress compression"
+ depends on PPP
+ ---help---
+ Support for the BSD-Compress compression method for PPP, which uses
+ the LZW compression method to compress each PPP packet before it is
+ sent over the wire. The machine at the other end of the PPP link
+ (usually your ISP) has to support the BSD-Compress compression
+ method as well for this to be useful. Even if they don't support it,
+ it is safe to say Y here.
+
+ The PPP Deflate compression method ("PPP Deflate compression",
+ above) is preferable to BSD-Compress, because it compresses better
+ and is patent-free.
+
+ Note that the BSD compression code will always be compiled as a
+ module; it is called bsd_comp and will show up in the directory
+ modules once you have said "make modules". If unsure, say N.
+
+config PPP_DEFLATE
+ tristate "PPP Deflate compression"
+ depends on PPP
+ select ZLIB_INFLATE
+ select ZLIB_DEFLATE
+ ---help---
+ Support for the Deflate compression method for PPP, which uses the
+ Deflate algorithm (the same algorithm that gzip uses) to compress
+ each PPP packet before it is sent over the wire. The machine at the
+ other end of the PPP link (usually your ISP) has to support the
+ Deflate compression method as well for this to be useful. Even if
+ they don't support it, it is safe to say Y here.
+
+ To compile this driver as a module, choose M here.
+
+config PPP_FILTER
+ bool "PPP filtering"
+ depends on PPP
+ ---help---
+ Say Y here if you want to be able to filter the packets passing over
+ PPP interfaces. This allows you to control which packets count as
+ activity (i.e. which packets will reset the idle timer or bring up
+ a demand-dialed link) and which packets are to be dropped entirely.
+ You need to say Y here if you wish to use the pass-filter and
+ active-filter options to pppd.
+
+ If unsure, say N.
+
+config PPP_MPPE
+ tristate "PPP MPPE compression (encryption) (EXPERIMENTAL)"
+ depends on PPP && EXPERIMENTAL
+ select CRYPTO
+ select CRYPTO_SHA1
+ select CRYPTO_ARC4
+ select CRYPTO_ECB
+ ---help---
+ Support for the MPPE Encryption protocol, as employed by the
+ Microsoft Point-to-Point Tunneling Protocol.
+
+ See http://pptpclient.sourceforge.net/ for information on
+ configuring PPTP clients and servers to utilize this method.
+
+config PPP_MULTILINK
+ bool "PPP multilink support (EXPERIMENTAL)"
+ depends on PPP && EXPERIMENTAL
+ ---help---
+ PPP multilink is a protocol (defined in RFC 1990) which allows you
+ to combine several (logical or physical) lines into one logical PPP
+ connection, so that you can utilize your full bandwidth.
+
+ This has to be supported at the other end as well and you need a
+ version of the pppd daemon which understands the multilink protocol.
+
+ If unsure, say N.
+
+config PPPOATM
+ tristate "PPP over ATM"
+ depends on ATM && PPP
+ ---help---
+ Support PPP (Point to Point Protocol) encapsulated in ATM frames.
+ This implementation does not yet comply with section 8 of RFC2364,
+ which can lead to bad results if the ATM peer loses state and
+ changes its encapsulation unilaterally.
+
+config PPPOE
+ tristate "PPP over Ethernet (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && PPP
+ ---help---
+ Support for PPP over Ethernet.
+
+ This driver requires the latest version of pppd from the CVS
+ repository at cvs.samba.org. Alternatively, see the
+ RoaringPenguin package (<http://www.roaringpenguin.com/pppoe>)
+ which contains instruction on how to use this driver (under
+ the heading "Kernel mode PPPoE").
+
+config PPTP
+ tristate "PPP over IPv4 (PPTP) (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && PPP && NET_IPGRE_DEMUX
+ ---help---
+ Support for PPP over IPv4.(Point-to-Point Tunneling Protocol)
+
+ This driver requires pppd plugin to work in client mode or
+ modified pptpd (poptop) to work in server mode.
+ See http://accel-pptp.sourceforge.net/ for information how to
+ utilize this module.
+
+config PPPOL2TP
+ tristate "PPP over L2TP (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && L2TP && PPP
+ ---help---
+ Support for PPP-over-L2TP socket family. L2TP is a protocol
+ used by ISPs and enterprises to tunnel PPP traffic over UDP
+ tunnels. L2TP is replacing PPTP for VPN uses.
+
+config PPP_ASYNC
+ tristate "PPP support for async serial ports"
+ depends on PPP
+ select CRC_CCITT
+ ---help---
+ Say Y (or M) here if you want to be able to use PPP over standard
+ asynchronous serial ports, such as COM1 or COM2 on a PC. If you use
+ a modem (not a synchronous or ISDN modem) to contact your ISP, you
+ need this option.
+
+ To compile this driver as a module, choose M here.
+
+ If unsure, say Y.
+
+config PPP_SYNC_TTY
+ tristate "PPP support for sync tty ports"
+ depends on PPP
+ ---help---
+ Say Y (or M) here if you want to be able to use PPP over synchronous
+ (HDLC) tty devices, such as the SyncLink adapter. These devices
+ are often used for high-speed leased lines like T1/E1.
+
+ To compile this driver as a module, choose M here.
+
+endif # PPP
diff --git a/drivers/net/ppp/Makefile b/drivers/net/ppp/Makefile
new file mode 100644
index 0000000..a6b6297
--- /dev/null
+++ b/drivers/net/ppp/Makefile
@@ -0,0 +1,13 @@
+#
+# Makefile for the Linux PPP network device drivers.
+#
+
+obj-$(CONFIG_PPP) += ppp_generic.o
+obj-$(CONFIG_PPP_ASYNC) += ppp_async.o
+obj-$(CONFIG_PPP_BSDCOMP) += bsd_comp.o
+obj-$(CONFIG_PPP_DEFLATE) += ppp_deflate.o
+obj-$(CONFIG_PPP_MPPE) += ppp_mppe.o
+obj-$(CONFIG_PPP_SYNC_TTY) += ppp_synctty.o
+obj-$(CONFIG_PPPOE) += pppox.o pppoe.o
+obj-$(CONFIG_PPPOL2TP) += pppox.o
+obj-$(CONFIG_PPTP) += pppox.o pptp.o
diff --git a/drivers/net/ppp/bsd_comp.c b/drivers/net/ppp/bsd_comp.c
new file mode 100644
index 0000000..a9b759a
--- /dev/null
+++ b/drivers/net/ppp/bsd_comp.c
@@ -0,0 +1,1170 @@
+/*
+ * Update: The Berkeley copyright was changed, and the change
+ * is retroactive to all "true" BSD software (ie everything
+ * from UCB as opposed to other peoples code that just carried
+ * the same license). The new copyright doesn't clash with the
+ * GPL, so the module-only restriction has been removed..
+ */
+
+/* Because this code is derived from the 4.3BSD compress source:
+ *
+ * Copyright (c) 1985, 1986 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * James A. Woods, derived from original work by Spencer Thomas
+ * and Joseph Orost.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This version is for use with contiguous buffers on Linux-derived systems.
+ *
+ * ==FILEVERSION 20000226==
+ *
+ * NOTE TO MAINTAINERS:
+ * If you modify this file at all, please set the number above to the
+ * date of the modification as YYMMDD (year month day).
+ * bsd_comp.c is shipped with a PPP distribution as well as with
+ * the kernel; if everyone increases the FILEVERSION number above,
+ * then scripts can do the right thing when deciding whether to
+ * install a new bsd_comp.c file. Don't change the format of that
+ * line otherwise, so the installation script can recognize it.
+ *
+ * From: bsd_comp.c,v 1.3 1994/12/08 01:59:58 paulus Exp
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+
+#include <linux/ppp_defs.h>
+
+#undef PACKETPTR
+#define PACKETPTR 1
+#include <linux/ppp-comp.h>
+#undef PACKETPTR
+
+#include <asm/byteorder.h>
+
+/*
+ * PPP "BSD compress" compression
+ * The differences between this compression and the classic BSD LZW
+ * source are obvious from the requirement that the classic code worked
+ * with files while this handles arbitrarily long streams that
+ * are broken into packets. They are:
+ *
+ * When the code size expands, a block of junk is not emitted by
+ * the compressor and not expected by the decompressor.
+ *
+ * New codes are not necessarily assigned every time an old
+ * code is output by the compressor. This is because a packet
+ * end forces a code to be emitted, but does not imply that a
+ * new sequence has been seen.
+ *
+ * The compression ratio is checked at the first end of a packet
+ * after the appropriate gap. Besides simplifying and speeding
+ * things up, this makes it more likely that the transmitter
+ * and receiver will agree when the dictionary is cleared when
+ * compression is not going well.
+ */
+
+/*
+ * Macros to extract protocol version and number of bits
+ * from the third byte of the BSD Compress CCP configuration option.
+ */
+
+#define BSD_VERSION(x) ((x) >> 5)
+#define BSD_NBITS(x) ((x) & 0x1F)
+
+#define BSD_CURRENT_VERSION 1
+
+/*
+ * A dictionary for doing BSD compress.
+ */
+
+struct bsd_dict {
+ union { /* hash value */
+ unsigned long fcode;
+ struct {
+#if defined(__LITTLE_ENDIAN) /* Little endian order */
+ unsigned short prefix; /* preceding code */
+ unsigned char suffix; /* last character of new code */
+ unsigned char pad;
+#elif defined(__BIG_ENDIAN) /* Big endian order */
+ unsigned char pad;
+ unsigned char suffix; /* last character of new code */
+ unsigned short prefix; /* preceding code */
+#else
+#error Endianness not defined...
+#endif
+ } hs;
+ } f;
+ unsigned short codem1; /* output of hash table -1 */
+ unsigned short cptr; /* map code to hash table entry */
+};
+
+struct bsd_db {
+ int totlen; /* length of this structure */
+ unsigned int hsize; /* size of the hash table */
+ unsigned char hshift; /* used in hash function */
+ unsigned char n_bits; /* current bits/code */
+ unsigned char maxbits; /* maximum bits/code */
+ unsigned char debug; /* non-zero if debug desired */
+ unsigned char unit; /* ppp unit number */
+ unsigned short seqno; /* sequence # of next packet */
+ unsigned int mru; /* size of receive (decompress) bufr */
+ unsigned int maxmaxcode; /* largest valid code */
+ unsigned int max_ent; /* largest code in use */
+ unsigned int in_count; /* uncompressed bytes, aged */
+ unsigned int bytes_out; /* compressed bytes, aged */
+ unsigned int ratio; /* recent compression ratio */
+ unsigned int checkpoint; /* when to next check the ratio */
+ unsigned int clear_count; /* times dictionary cleared */
+ unsigned int incomp_count; /* incompressible packets */
+ unsigned int incomp_bytes; /* incompressible bytes */
+ unsigned int uncomp_count; /* uncompressed packets */
+ unsigned int uncomp_bytes; /* uncompressed bytes */
+ unsigned int comp_count; /* compressed packets */
+ unsigned int comp_bytes; /* compressed bytes */
+ unsigned short *lens; /* array of lengths of codes */
+ struct bsd_dict *dict; /* dictionary */
+};
+
+#define BSD_OVHD 2 /* BSD compress overhead/packet */
+#define MIN_BSD_BITS 9
+#define BSD_INIT_BITS MIN_BSD_BITS
+#define MAX_BSD_BITS 15
+
+static void bsd_free (void *state);
+static void *bsd_alloc(unsigned char *options, int opt_len, int decomp);
+static void *bsd_comp_alloc (unsigned char *options, int opt_len);
+static void *bsd_decomp_alloc (unsigned char *options, int opt_len);
+
+static int bsd_init (void *db, unsigned char *options,
+ int opt_len, int unit, int debug, int decomp);
+static int bsd_comp_init (void *state, unsigned char *options,
+ int opt_len, int unit, int opthdr, int debug);
+static int bsd_decomp_init (void *state, unsigned char *options,
+ int opt_len, int unit, int opthdr, int mru,
+ int debug);
+
+static void bsd_reset (void *state);
+static void bsd_comp_stats (void *state, struct compstat *stats);
+
+static int bsd_compress (void *state, unsigned char *rptr,
+ unsigned char *obuf, int isize, int osize);
+static void bsd_incomp (void *state, unsigned char *ibuf, int icnt);
+
+static int bsd_decompress (void *state, unsigned char *ibuf, int isize,
+ unsigned char *obuf, int osize);
+
+/* These are in ppp_generic.c */
+extern int ppp_register_compressor (struct compressor *cp);
+extern void ppp_unregister_compressor (struct compressor *cp);
+
+/*
+ * the next two codes should not be changed lightly, as they must not
+ * lie within the contiguous general code space.
+ */
+#define CLEAR 256 /* table clear output code */
+#define FIRST 257 /* first free entry */
+#define LAST 255
+
+#define MAXCODE(b) ((1 << (b)) - 1)
+#define BADCODEM1 MAXCODE(MAX_BSD_BITS)
+
+#define BSD_HASH(prefix,suffix,hshift) ((((unsigned long)(suffix))<<(hshift)) \
+ ^ (unsigned long)(prefix))
+#define BSD_KEY(prefix,suffix) ((((unsigned long)(suffix)) << 16) \
+ + (unsigned long)(prefix))
+
+#define CHECK_GAP 10000 /* Ratio check interval */
+
+#define RATIO_SCALE_LOG 8
+#define RATIO_SCALE (1<<RATIO_SCALE_LOG)
+#define RATIO_MAX (0x7fffffff>>RATIO_SCALE_LOG)
+
+/*
+ * clear the dictionary
+ */
+
+static void
+bsd_clear(struct bsd_db *db)
+{
+ db->clear_count++;
+ db->max_ent = FIRST-1;
+ db->n_bits = BSD_INIT_BITS;
+ db->bytes_out = 0;
+ db->in_count = 0;
+ db->ratio = 0;
+ db->checkpoint = CHECK_GAP;
+}
+
+/*
+ * If the dictionary is full, then see if it is time to reset it.
+ *
+ * Compute the compression ratio using fixed-point arithmetic
+ * with 8 fractional bits.
+ *
+ * Since we have an infinite stream instead of a single file,
+ * watch only the local compression ratio.
+ *
+ * Since both peers must reset the dictionary at the same time even in
+ * the absence of CLEAR codes (while packets are incompressible), they
+ * must compute the same ratio.
+ */
+
+static int bsd_check (struct bsd_db *db) /* 1=output CLEAR */
+ {
+ unsigned int new_ratio;
+
+ if (db->in_count >= db->checkpoint)
+ {
+ /* age the ratio by limiting the size of the counts */
+ if (db->in_count >= RATIO_MAX || db->bytes_out >= RATIO_MAX)
+ {
+ db->in_count -= (db->in_count >> 2);
+ db->bytes_out -= (db->bytes_out >> 2);
+ }
+
+ db->checkpoint = db->in_count + CHECK_GAP;
+
+ if (db->max_ent >= db->maxmaxcode)
+ {
+ /* Reset the dictionary only if the ratio is worse,
+ * or if it looks as if it has been poisoned
+ * by incompressible data.
+ *
+ * This does not overflow, because
+ * db->in_count <= RATIO_MAX.
+ */
+
+ new_ratio = db->in_count << RATIO_SCALE_LOG;
+ if (db->bytes_out != 0)
+ {
+ new_ratio /= db->bytes_out;
+ }
+
+ if (new_ratio < db->ratio || new_ratio < 1 * RATIO_SCALE)
+ {
+ bsd_clear (db);
+ return 1;
+ }
+ db->ratio = new_ratio;
+ }
+ }
+ return 0;
+ }
+
+/*
+ * Return statistics.
+ */
+
+static void bsd_comp_stats (void *state, struct compstat *stats)
+ {
+ struct bsd_db *db = (struct bsd_db *) state;
+
+ stats->unc_bytes = db->uncomp_bytes;
+ stats->unc_packets = db->uncomp_count;
+ stats->comp_bytes = db->comp_bytes;
+ stats->comp_packets = db->comp_count;
+ stats->inc_bytes = db->incomp_bytes;
+ stats->inc_packets = db->incomp_count;
+ stats->in_count = db->in_count;
+ stats->bytes_out = db->bytes_out;
+ }
+
+/*
+ * Reset state, as on a CCP ResetReq.
+ */
+
+static void bsd_reset (void *state)
+ {
+ struct bsd_db *db = (struct bsd_db *) state;
+
+ bsd_clear(db);
+
+ db->seqno = 0;
+ db->clear_count = 0;
+ }
+
+/*
+ * Release the compression structure
+ */
+
+static void bsd_free (void *state)
+{
+ struct bsd_db *db = state;
+
+ if (!db)
+ return;
+
+/*
+ * Release the dictionary
+ */
+ vfree(db->dict);
+ db->dict = NULL;
+/*
+ * Release the string buffer
+ */
+ vfree(db->lens);
+ db->lens = NULL;
+/*
+ * Finally release the structure itself.
+ */
+ kfree(db);
+}
+
+/*
+ * Allocate space for a (de) compressor.
+ */
+
+static void *bsd_alloc (unsigned char *options, int opt_len, int decomp)
+ {
+ int bits;
+ unsigned int hsize, hshift, maxmaxcode;
+ struct bsd_db *db;
+
+ if (opt_len != 3 || options[0] != CI_BSD_COMPRESS || options[1] != 3
+ || BSD_VERSION(options[2]) != BSD_CURRENT_VERSION)
+ {
+ return NULL;
+ }
+
+ bits = BSD_NBITS(options[2]);
+
+ switch (bits)
+ {
+ case 9: /* needs 82152 for both directions */
+ case 10: /* needs 84144 */
+ case 11: /* needs 88240 */
+ case 12: /* needs 96432 */
+ hsize = 5003;
+ hshift = 4;
+ break;
+ case 13: /* needs 176784 */
+ hsize = 9001;
+ hshift = 5;
+ break;
+ case 14: /* needs 353744 */
+ hsize = 18013;
+ hshift = 6;
+ break;
+ case 15: /* needs 691440 */
+ hsize = 35023;
+ hshift = 7;
+ break;
+ case 16: /* needs 1366160--far too much, */
+ /* hsize = 69001; */ /* and 69001 is too big for cptr */
+ /* hshift = 8; */ /* in struct bsd_db */
+ /* break; */
+ default:
+ return NULL;
+ }
+/*
+ * Allocate the main control structure for this instance.
+ */
+ maxmaxcode = MAXCODE(bits);
+ db = kzalloc(sizeof (struct bsd_db),
+ GFP_KERNEL);
+ if (!db)
+ {
+ return NULL;
+ }
+
+/*
+ * Allocate space for the dictionary. This may be more than one page in
+ * length.
+ */
+ db->dict = vmalloc(hsize * sizeof(struct bsd_dict));
+ if (!db->dict)
+ {
+ bsd_free (db);
+ return NULL;
+ }
+
+/*
+ * If this is the compression buffer then there is no length data.
+ */
+ if (!decomp)
+ {
+ db->lens = NULL;
+ }
+/*
+ * For decompression, the length information is needed as well.
+ */
+ else
+ {
+ db->lens = vmalloc((maxmaxcode + 1) * sizeof(db->lens[0]));
+ if (!db->lens)
+ {
+ bsd_free (db);
+ return NULL;
+ }
+ }
+/*
+ * Initialize the data information for the compression code
+ */
+ db->totlen = sizeof (struct bsd_db) +
+ (sizeof (struct bsd_dict) * hsize);
+
+ db->hsize = hsize;
+ db->hshift = hshift;
+ db->maxmaxcode = maxmaxcode;
+ db->maxbits = bits;
+
+ return (void *) db;
+ }
+
+static void *bsd_comp_alloc (unsigned char *options, int opt_len)
+ {
+ return bsd_alloc (options, opt_len, 0);
+ }
+
+static void *bsd_decomp_alloc (unsigned char *options, int opt_len)
+ {
+ return bsd_alloc (options, opt_len, 1);
+ }
+
+/*
+ * Initialize the database.
+ */
+
+static int bsd_init (void *state, unsigned char *options,
+ int opt_len, int unit, int debug, int decomp)
+ {
+ struct bsd_db *db = state;
+ int indx;
+
+ if ((opt_len != 3) || (options[0] != CI_BSD_COMPRESS) || (options[1] != 3)
+ || (BSD_VERSION(options[2]) != BSD_CURRENT_VERSION)
+ || (BSD_NBITS(options[2]) != db->maxbits)
+ || (decomp && db->lens == NULL))
+ {
+ return 0;
+ }
+
+ if (decomp)
+ {
+ indx = LAST;
+ do
+ {
+ db->lens[indx] = 1;
+ }
+ while (indx-- > 0);
+ }
+
+ indx = db->hsize;
+ while (indx-- != 0)
+ {
+ db->dict[indx].codem1 = BADCODEM1;
+ db->dict[indx].cptr = 0;
+ }
+
+ db->unit = unit;
+ db->mru = 0;
+#ifndef DEBUG
+ if (debug)
+#endif
+ db->debug = 1;
+
+ bsd_reset(db);
+
+ return 1;
+ }
+
+static int bsd_comp_init (void *state, unsigned char *options,
+ int opt_len, int unit, int opthdr, int debug)
+ {
+ return bsd_init (state, options, opt_len, unit, debug, 0);
+ }
+
+static int bsd_decomp_init (void *state, unsigned char *options,
+ int opt_len, int unit, int opthdr, int mru,
+ int debug)
+ {
+ return bsd_init (state, options, opt_len, unit, debug, 1);
+ }
+
+/*
+ * Obtain pointers to the various structures in the compression tables
+ */
+
+#define dict_ptrx(p,idx) &(p->dict[idx])
+#define lens_ptrx(p,idx) &(p->lens[idx])
+
+#ifdef DEBUG
+static unsigned short *lens_ptr(struct bsd_db *db, int idx)
+ {
+ if ((unsigned int) idx > (unsigned int) db->maxmaxcode)
+ {
+ printk ("<9>ppp: lens_ptr(%d) > max\n", idx);
+ idx = 0;
+ }
+ return lens_ptrx (db, idx);
+ }
+
+static struct bsd_dict *dict_ptr(struct bsd_db *db, int idx)
+ {
+ if ((unsigned int) idx >= (unsigned int) db->hsize)
+ {
+ printk ("<9>ppp: dict_ptr(%d) > max\n", idx);
+ idx = 0;
+ }
+ return dict_ptrx (db, idx);
+ }
+
+#else
+#define lens_ptr(db,idx) lens_ptrx(db,idx)
+#define dict_ptr(db,idx) dict_ptrx(db,idx)
+#endif
+
+/*
+ * compress a packet
+ *
+ * The result of this function is the size of the compressed
+ * packet. A zero is returned if the packet was not compressed
+ * for some reason, such as the size being larger than uncompressed.
+ *
+ * One change from the BSD compress command is that when the
+ * code size expands, we do not output a bunch of padding.
+ */
+
+static int bsd_compress (void *state, unsigned char *rptr, unsigned char *obuf,
+ int isize, int osize)
+ {
+ struct bsd_db *db;
+ int hshift;
+ unsigned int max_ent;
+ unsigned int n_bits;
+ unsigned int bitno;
+ unsigned long accm;
+ int ent;
+ unsigned long fcode;
+ struct bsd_dict *dictp;
+ unsigned char c;
+ int hval;
+ int disp;
+ int ilen;
+ int mxcode;
+ unsigned char *wptr;
+ int olen;
+
+#define PUTBYTE(v) \
+ { \
+ ++olen; \
+ if (wptr) \
+ { \
+ *wptr++ = (unsigned char) (v); \
+ if (olen >= osize) \
+ { \
+ wptr = NULL; \
+ } \
+ } \
+ }
+
+#define OUTPUT(ent) \
+ { \
+ bitno -= n_bits; \
+ accm |= ((ent) << bitno); \
+ do \
+ { \
+ PUTBYTE(accm >> 24); \
+ accm <<= 8; \
+ bitno += 8; \
+ } \
+ while (bitno <= 24); \
+ }
+
+ /*
+ * If the protocol is not in the range we're interested in,
+ * just return without compressing the packet. If it is,
+ * the protocol becomes the first byte to compress.
+ */
+
+ ent = PPP_PROTOCOL(rptr);
+ if (ent < 0x21 || ent > 0xf9)
+ {
+ return 0;
+ }
+
+ db = (struct bsd_db *) state;
+ hshift = db->hshift;
+ max_ent = db->max_ent;
+ n_bits = db->n_bits;
+ bitno = 32;
+ accm = 0;
+ mxcode = MAXCODE (n_bits);
+
+ /* Initialize the output pointers */
+ wptr = obuf;
+ olen = PPP_HDRLEN + BSD_OVHD;
+
+ if (osize > isize)
+ {
+ osize = isize;
+ }
+
+ /* This is the PPP header information */
+ if (wptr)
+ {
+ *wptr++ = PPP_ADDRESS(rptr);
+ *wptr++ = PPP_CONTROL(rptr);
+ *wptr++ = 0;
+ *wptr++ = PPP_COMP;
+ *wptr++ = db->seqno >> 8;
+ *wptr++ = db->seqno;
+ }
+
+ /* Skip the input header */
+ rptr += PPP_HDRLEN;
+ isize -= PPP_HDRLEN;
+ ilen = ++isize; /* Low byte of protocol is counted as input */
+
+ while (--ilen > 0)
+ {
+ c = *rptr++;
+ fcode = BSD_KEY (ent, c);
+ hval = BSD_HASH (ent, c, hshift);
+ dictp = dict_ptr (db, hval);
+
+ /* Validate and then check the entry. */
+ if (dictp->codem1 >= max_ent)
+ {
+ goto nomatch;
+ }
+
+ if (dictp->f.fcode == fcode)
+ {
+ ent = dictp->codem1 + 1;
+ continue; /* found (prefix,suffix) */
+ }
+
+ /* continue probing until a match or invalid entry */
+ disp = (hval == 0) ? 1 : hval;
+
+ do
+ {
+ hval += disp;
+ if (hval >= db->hsize)
+ {
+ hval -= db->hsize;
+ }
+ dictp = dict_ptr (db, hval);
+ if (dictp->codem1 >= max_ent)
+ {
+ goto nomatch;
+ }
+ }
+ while (dictp->f.fcode != fcode);
+
+ ent = dictp->codem1 + 1; /* finally found (prefix,suffix) */
+ continue;
+
+nomatch:
+ OUTPUT(ent); /* output the prefix */
+
+ /* code -> hashtable */
+ if (max_ent < db->maxmaxcode)
+ {
+ struct bsd_dict *dictp2;
+ struct bsd_dict *dictp3;
+ int indx;
+
+ /* expand code size if needed */
+ if (max_ent >= mxcode)
+ {
+ db->n_bits = ++n_bits;
+ mxcode = MAXCODE (n_bits);
+ }
+
+ /* Invalidate old hash table entry using
+ * this code, and then take it over.
+ */
+
+ dictp2 = dict_ptr (db, max_ent + 1);
+ indx = dictp2->cptr;
+ dictp3 = dict_ptr (db, indx);
+
+ if (dictp3->codem1 == max_ent)
+ {
+ dictp3->codem1 = BADCODEM1;
+ }
+
+ dictp2->cptr = hval;
+ dictp->codem1 = max_ent;
+ dictp->f.fcode = fcode;
+ db->max_ent = ++max_ent;
+
+ if (db->lens)
+ {
+ unsigned short *len1 = lens_ptr (db, max_ent);
+ unsigned short *len2 = lens_ptr (db, ent);
+ *len1 = *len2 + 1;
+ }
+ }
+ ent = c;
+ }
+
+ OUTPUT(ent); /* output the last code */
+
+ db->bytes_out += olen - PPP_HDRLEN - BSD_OVHD;
+ db->uncomp_bytes += isize;
+ db->in_count += isize;
+ ++db->uncomp_count;
+ ++db->seqno;
+
+ if (bitno < 32)
+ {
+ ++db->bytes_out; /* must be set before calling bsd_check */
+ }
+
+ /*
+ * Generate the clear command if needed
+ */
+
+ if (bsd_check(db))
+ {
+ OUTPUT (CLEAR);
+ }
+
+ /*
+ * Pad dribble bits of last code with ones.
+ * Do not emit a completely useless byte of ones.
+ */
+
+ if (bitno != 32)
+ {
+ PUTBYTE((accm | (0xff << (bitno-8))) >> 24);
+ }
+
+ /*
+ * Increase code size if we would have without the packet
+ * boundary because the decompressor will do so.
+ */
+
+ if (max_ent >= mxcode && max_ent < db->maxmaxcode)
+ {
+ db->n_bits++;
+ }
+
+ /* If output length is too large then this is an incomplete frame. */
+ if (wptr == NULL)
+ {
+ ++db->incomp_count;
+ db->incomp_bytes += isize;
+ olen = 0;
+ }
+ else /* Count the number of compressed frames */
+ {
+ ++db->comp_count;
+ db->comp_bytes += olen;
+ }
+
+ /* Return the resulting output length */
+ return olen;
+#undef OUTPUT
+#undef PUTBYTE
+ }
+
+/*
+ * Update the "BSD Compress" dictionary on the receiver for
+ * incompressible data by pretending to compress the incoming data.
+ */
+
+static void bsd_incomp (void *state, unsigned char *ibuf, int icnt)
+ {
+ (void) bsd_compress (state, ibuf, (char *) 0, icnt, 0);
+ }
+
+/*
+ * Decompress "BSD Compress".
+ *
+ * Because of patent problems, we return DECOMP_ERROR for errors
+ * found by inspecting the input data and for system problems, but
+ * DECOMP_FATALERROR for any errors which could possibly be said to
+ * be being detected "after" decompression. For DECOMP_ERROR,
+ * we can issue a CCP reset-request; for DECOMP_FATALERROR, we may be
+ * infringing a patent of Motorola's if we do, so we take CCP down
+ * instead.
+ *
+ * Given that the frame has the correct sequence number and a good FCS,
+ * errors such as invalid codes in the input most likely indicate a
+ * bug, so we return DECOMP_FATALERROR for them in order to turn off
+ * compression, even though they are detected by inspecting the input.
+ */
+
+static int bsd_decompress (void *state, unsigned char *ibuf, int isize,
+ unsigned char *obuf, int osize)
+ {
+ struct bsd_db *db;
+ unsigned int max_ent;
+ unsigned long accm;
+ unsigned int bitno; /* 1st valid bit in accm */
+ unsigned int n_bits;
+ unsigned int tgtbitno; /* bitno when we have a code */
+ struct bsd_dict *dictp;
+ int explen;
+ int seq;
+ unsigned int incode;
+ unsigned int oldcode;
+ unsigned int finchar;
+ unsigned char *p;
+ unsigned char *wptr;
+ int adrs;
+ int ctrl;
+ int ilen;
+ int codelen;
+ int extra;
+
+ db = (struct bsd_db *) state;
+ max_ent = db->max_ent;
+ accm = 0;
+ bitno = 32; /* 1st valid bit in accm */
+ n_bits = db->n_bits;
+ tgtbitno = 32 - n_bits; /* bitno when we have a code */
+
+ /*
+ * Save the address/control from the PPP header
+ * and then get the sequence number.
+ */
+
+ adrs = PPP_ADDRESS (ibuf);
+ ctrl = PPP_CONTROL (ibuf);
+
+ seq = (ibuf[4] << 8) + ibuf[5];
+
+ ibuf += (PPP_HDRLEN + 2);
+ ilen = isize - (PPP_HDRLEN + 2);
+
+ /*
+ * Check the sequence number and give up if it differs from
+ * the value we're expecting.
+ */
+
+ if (seq != db->seqno)
+ {
+ if (db->debug)
+ {
+ printk("bsd_decomp%d: bad sequence # %d, expected %d\n",
+ db->unit, seq, db->seqno - 1);
+ }
+ return DECOMP_ERROR;
+ }
+
+ ++db->seqno;
+ db->bytes_out += ilen;
+
+ /*
+ * Fill in the ppp header, but not the last byte of the protocol
+ * (that comes from the decompressed data).
+ */
+
+ wptr = obuf;
+ *wptr++ = adrs;
+ *wptr++ = ctrl;
+ *wptr++ = 0;
+
+ oldcode = CLEAR;
+ explen = 3;
+
+ /*
+ * Keep the checkpoint correctly so that incompressible packets
+ * clear the dictionary at the proper times.
+ */
+
+ for (;;)
+ {
+ if (ilen-- <= 0)
+ {
+ db->in_count += (explen - 3); /* don't count the header */
+ break;
+ }
+
+ /*
+ * Accumulate bytes until we have a complete code.
+ * Then get the next code, relying on the 32-bit,
+ * unsigned accm to mask the result.
+ */
+
+ bitno -= 8;
+ accm |= *ibuf++ << bitno;
+ if (tgtbitno < bitno)
+ {
+ continue;
+ }
+
+ incode = accm >> tgtbitno;
+ accm <<= n_bits;
+ bitno += n_bits;
+
+ /*
+ * The dictionary must only be cleared at the end of a packet.
+ */
+
+ if (incode == CLEAR)
+ {
+ if (ilen > 0)
+ {
+ if (db->debug)
+ {
+ printk("bsd_decomp%d: bad CLEAR\n", db->unit);
+ }
+ return DECOMP_FATALERROR; /* probably a bug */
+ }
+
+ bsd_clear(db);
+ break;
+ }
+
+ if ((incode > max_ent + 2) || (incode > db->maxmaxcode)
+ || (incode > max_ent && oldcode == CLEAR))
+ {
+ if (db->debug)
+ {
+ printk("bsd_decomp%d: bad code 0x%x oldcode=0x%x ",
+ db->unit, incode, oldcode);
+ printk("max_ent=0x%x explen=%d seqno=%d\n",
+ max_ent, explen, db->seqno);
+ }
+ return DECOMP_FATALERROR; /* probably a bug */
+ }
+
+ /* Special case for KwKwK string. */
+ if (incode > max_ent)
+ {
+ finchar = oldcode;
+ extra = 1;
+ }
+ else
+ {
+ finchar = incode;
+ extra = 0;
+ }
+
+ codelen = *(lens_ptr (db, finchar));
+ explen += codelen + extra;
+ if (explen > osize)
+ {
+ if (db->debug)
+ {
+ printk("bsd_decomp%d: ran out of mru\n", db->unit);
+#ifdef DEBUG
+ printk(" len=%d, finchar=0x%x, codelen=%d, explen=%d\n",
+ ilen, finchar, codelen, explen);
+#endif
+ }
+ return DECOMP_FATALERROR;
+ }
+
+ /*
+ * Decode this code and install it in the decompressed buffer.
+ */
+
+ wptr += codelen;
+ p = wptr;
+ while (finchar > LAST)
+ {
+ struct bsd_dict *dictp2 = dict_ptr (db, finchar);
+
+ dictp = dict_ptr (db, dictp2->cptr);
+#ifdef DEBUG
+ if (--codelen <= 0 || dictp->codem1 != finchar-1)
+ {
+ if (codelen <= 0)
+ {
+ printk("bsd_decomp%d: fell off end of chain ", db->unit);
+ printk("0x%x at 0x%x by 0x%x, max_ent=0x%x\n",
+ incode, finchar, dictp2->cptr, max_ent);
+ }
+ else
+ {
+ if (dictp->codem1 != finchar-1)
+ {
+ printk("bsd_decomp%d: bad code chain 0x%x "
+ "finchar=0x%x ",
+ db->unit, incode, finchar);
+
+ printk("oldcode=0x%x cptr=0x%x codem1=0x%x\n",
+ oldcode, dictp2->cptr, dictp->codem1);
+ }
+ }
+ return DECOMP_FATALERROR;
+ }
+#endif
+ *--p = dictp->f.hs.suffix;
+ finchar = dictp->f.hs.prefix;
+ }
+ *--p = finchar;
+
+#ifdef DEBUG
+ if (--codelen != 0)
+ {
+ printk("bsd_decomp%d: short by %d after code 0x%x, max_ent=0x%x\n",
+ db->unit, codelen, incode, max_ent);
+ }
+#endif
+
+ if (extra) /* the KwKwK case again */
+ {
+ *wptr++ = finchar;
+ }
+
+ /*
+ * If not first code in a packet, and
+ * if not out of code space, then allocate a new code.
+ *
+ * Keep the hash table correct so it can be used
+ * with uncompressed packets.
+ */
+
+ if (oldcode != CLEAR && max_ent < db->maxmaxcode)
+ {
+ struct bsd_dict *dictp2, *dictp3;
+ unsigned short *lens1, *lens2;
+ unsigned long fcode;
+ int hval, disp, indx;
+
+ fcode = BSD_KEY(oldcode,finchar);
+ hval = BSD_HASH(oldcode,finchar,db->hshift);
+ dictp = dict_ptr (db, hval);
+
+ /* look for a free hash table entry */
+ if (dictp->codem1 < max_ent)
+ {
+ disp = (hval == 0) ? 1 : hval;
+ do
+ {
+ hval += disp;
+ if (hval >= db->hsize)
+ {
+ hval -= db->hsize;
+ }
+ dictp = dict_ptr (db, hval);
+ }
+ while (dictp->codem1 < max_ent);
+ }
+
+ /*
+ * Invalidate previous hash table entry
+ * assigned this code, and then take it over
+ */
+
+ dictp2 = dict_ptr (db, max_ent + 1);
+ indx = dictp2->cptr;
+ dictp3 = dict_ptr (db, indx);
+
+ if (dictp3->codem1 == max_ent)
+ {
+ dictp3->codem1 = BADCODEM1;
+ }
+
+ dictp2->cptr = hval;
+ dictp->codem1 = max_ent;
+ dictp->f.fcode = fcode;
+ db->max_ent = ++max_ent;
+
+ /* Update the length of this string. */
+ lens1 = lens_ptr (db, max_ent);
+ lens2 = lens_ptr (db, oldcode);
+ *lens1 = *lens2 + 1;
+
+ /* Expand code size if needed. */
+ if (max_ent >= MAXCODE(n_bits) && max_ent < db->maxmaxcode)
+ {
+ db->n_bits = ++n_bits;
+ tgtbitno = 32-n_bits;
+ }
+ }
+ oldcode = incode;
+ }
+
+ ++db->comp_count;
+ ++db->uncomp_count;
+ db->comp_bytes += isize - BSD_OVHD - PPP_HDRLEN;
+ db->uncomp_bytes += explen;
+
+ if (bsd_check(db))
+ {
+ if (db->debug)
+ {
+ printk("bsd_decomp%d: peer should have cleared dictionary on %d\n",
+ db->unit, db->seqno - 1);
+ }
+ }
+ return explen;
+ }
+
+/*************************************************************
+ * Table of addresses for the BSD compression module
+ *************************************************************/
+
+static struct compressor ppp_bsd_compress = {
+ .compress_proto = CI_BSD_COMPRESS,
+ .comp_alloc = bsd_comp_alloc,
+ .comp_free = bsd_free,
+ .comp_init = bsd_comp_init,
+ .comp_reset = bsd_reset,
+ .compress = bsd_compress,
+ .comp_stat = bsd_comp_stats,
+ .decomp_alloc = bsd_decomp_alloc,
+ .decomp_free = bsd_free,
+ .decomp_init = bsd_decomp_init,
+ .decomp_reset = bsd_reset,
+ .decompress = bsd_decompress,
+ .incomp = bsd_incomp,
+ .decomp_stat = bsd_comp_stats,
+ .owner = THIS_MODULE
+};
+
+/*************************************************************
+ * Module support routines
+ *************************************************************/
+
+static int __init bsdcomp_init(void)
+{
+ int answer = ppp_register_compressor(&ppp_bsd_compress);
+ if (answer == 0)
+ printk(KERN_INFO "PPP BSD Compression module registered\n");
+ return answer;
+}
+
+static void __exit bsdcomp_cleanup(void)
+{
+ ppp_unregister_compressor(&ppp_bsd_compress);
+}
+
+module_init(bsdcomp_init);
+module_exit(bsdcomp_cleanup);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("ppp-compress-" __stringify(CI_BSD_COMPRESS));
diff --git a/drivers/net/ppp/ppp_async.c b/drivers/net/ppp/ppp_async.c
new file mode 100644
index 0000000..c6ba643
--- /dev/null
+++ b/drivers/net/ppp/ppp_async.c
@@ -0,0 +1,1028 @@
+/*
+ * PPP async serial channel driver for Linux.
+ *
+ * Copyright 1999 Paul Mackerras.
+ *
+ * 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 driver provides the encapsulation and framing for sending
+ * and receiving PPP frames over async serial lines. It relies on
+ * the generic PPP layer to give it frames to send and to process
+ * received frames. It implements the PPP line discipline.
+ *
+ * Part of the code in this driver was inspired by the old async-only
+ * PPP driver, written by Michael Callahan and Al Longyear, and
+ * subsequently hacked by Paul Mackerras.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/tty.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/crc-ccitt.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/spinlock.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <asm/uaccess.h>
+#include <asm/string.h>
+
+#define PPP_VERSION "2.4.2"
+
+#define OBUFSIZE 4096
+
+/* Structure for storing local state. */
+struct asyncppp {
+ struct tty_struct *tty;
+ unsigned int flags;
+ unsigned int state;
+ unsigned int rbits;
+ int mru;
+ spinlock_t xmit_lock;
+ spinlock_t recv_lock;
+ unsigned long xmit_flags;
+ u32 xaccm[8];
+ u32 raccm;
+ unsigned int bytes_sent;
+ unsigned int bytes_rcvd;
+
+ struct sk_buff *tpkt;
+ int tpkt_pos;
+ u16 tfcs;
+ unsigned char *optr;
+ unsigned char *olim;
+ unsigned long last_xmit;
+
+ struct sk_buff *rpkt;
+ int lcp_fcs;
+ struct sk_buff_head rqueue;
+
+ struct tasklet_struct tsk;
+
+ atomic_t refcnt;
+ struct semaphore dead_sem;
+ struct ppp_channel chan; /* interface to generic ppp layer */
+ unsigned char obuf[OBUFSIZE];
+};
+
+/* Bit numbers in xmit_flags */
+#define XMIT_WAKEUP 0
+#define XMIT_FULL 1
+#define XMIT_BUSY 2
+
+/* State bits */
+#define SC_TOSS 1
+#define SC_ESCAPE 2
+#define SC_PREV_ERROR 4
+
+/* Bits in rbits */
+#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+
+static int flag_time = HZ;
+module_param(flag_time, int, 0);
+MODULE_PARM_DESC(flag_time, "ppp_async: interval between flagged packets (in clock ticks)");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_PPP);
+
+/*
+ * Prototypes.
+ */
+static int ppp_async_encode(struct asyncppp *ap);
+static int ppp_async_send(struct ppp_channel *chan, struct sk_buff *skb);
+static int ppp_async_push(struct asyncppp *ap);
+static void ppp_async_flush_output(struct asyncppp *ap);
+static void ppp_async_input(struct asyncppp *ap, const unsigned char *buf,
+ char *flags, int count);
+static int ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd,
+ unsigned long arg);
+static void ppp_async_process(unsigned long arg);
+
+static void async_lcp_peek(struct asyncppp *ap, unsigned char *data,
+ int len, int inbound);
+
+static const struct ppp_channel_ops async_ops = {
+ .start_xmit = ppp_async_send,
+ .ioctl = ppp_async_ioctl,
+};
+
+/*
+ * Routines implementing the PPP line discipline.
+ */
+
+/*
+ * We have a potential race on dereferencing tty->disc_data,
+ * because the tty layer provides no locking at all - thus one
+ * cpu could be running ppp_asynctty_receive while another
+ * calls ppp_asynctty_close, which zeroes tty->disc_data and
+ * frees the memory that ppp_asynctty_receive is using. The best
+ * way to fix this is to use a rwlock in the tty struct, but for now
+ * we use a single global rwlock for all ttys in ppp line discipline.
+ *
+ * FIXME: this is no longer true. The _close path for the ldisc is
+ * now guaranteed to be sane.
+ */
+static DEFINE_RWLOCK(disc_data_lock);
+
+static struct asyncppp *ap_get(struct tty_struct *tty)
+{
+ struct asyncppp *ap;
+
+ read_lock(&disc_data_lock);
+ ap = tty->disc_data;
+ if (ap != NULL)
+ atomic_inc(&ap->refcnt);
+ read_unlock(&disc_data_lock);
+ return ap;
+}
+
+static void ap_put(struct asyncppp *ap)
+{
+ if (atomic_dec_and_test(&ap->refcnt))
+ up(&ap->dead_sem);
+}
+
+/*
+ * Called when a tty is put into PPP line discipline. Called in process
+ * context.
+ */
+static int
+ppp_asynctty_open(struct tty_struct *tty)
+{
+ struct asyncppp *ap;
+ int err;
+ int speed;
+
+ if (tty->ops->write == NULL)
+ return -EOPNOTSUPP;
+
+ err = -ENOMEM;
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ if (!ap)
+ goto out;
+
+ /* initialize the asyncppp structure */
+ ap->tty = tty;
+ ap->mru = PPP_MRU;
+ spin_lock_init(&ap->xmit_lock);
+ spin_lock_init(&ap->recv_lock);
+ ap->xaccm[0] = ~0U;
+ ap->xaccm[3] = 0x60000000U;
+ ap->raccm = ~0U;
+ ap->optr = ap->obuf;
+ ap->olim = ap->obuf;
+ ap->lcp_fcs = -1;
+
+ skb_queue_head_init(&ap->rqueue);
+ tasklet_init(&ap->tsk, ppp_async_process, (unsigned long) ap);
+
+ atomic_set(&ap->refcnt, 1);
+ sema_init(&ap->dead_sem, 0);
+
+ ap->chan.private = ap;
+ ap->chan.ops = &async_ops;
+ ap->chan.mtu = PPP_MRU;
+ speed = tty_get_baud_rate(tty);
+ ap->chan.speed = speed;
+ err = ppp_register_channel(&ap->chan);
+ if (err)
+ goto out_free;
+
+ tty->disc_data = ap;
+ tty->receive_room = 65536;
+ return 0;
+
+ out_free:
+ kfree(ap);
+ out:
+ return err;
+}
+
+/*
+ * Called when the tty is put into another line discipline
+ * or it hangs up. We have to wait for any cpu currently
+ * executing in any of the other ppp_asynctty_* routines to
+ * finish before we can call ppp_unregister_channel and free
+ * the asyncppp struct. This routine must be called from
+ * process context, not interrupt or softirq context.
+ */
+static void
+ppp_asynctty_close(struct tty_struct *tty)
+{
+ struct asyncppp *ap;
+
+ write_lock_irq(&disc_data_lock);
+ ap = tty->disc_data;
+ tty->disc_data = NULL;
+ write_unlock_irq(&disc_data_lock);
+ if (!ap)
+ return;
+
+ /*
+ * We have now ensured that nobody can start using ap from now
+ * on, but we have to wait for all existing users to finish.
+ * Note that ppp_unregister_channel ensures that no calls to
+ * our channel ops (i.e. ppp_async_send/ioctl) are in progress
+ * by the time it returns.
+ */
+ if (!atomic_dec_and_test(&ap->refcnt))
+ down(&ap->dead_sem);
+ tasklet_kill(&ap->tsk);
+
+ ppp_unregister_channel(&ap->chan);
+ kfree_skb(ap->rpkt);
+ skb_queue_purge(&ap->rqueue);
+ kfree_skb(ap->tpkt);
+ kfree(ap);
+}
+
+/*
+ * Called on tty hangup in process context.
+ *
+ * Wait for I/O to driver to complete and unregister PPP channel.
+ * This is already done by the close routine, so just call that.
+ */
+static int ppp_asynctty_hangup(struct tty_struct *tty)
+{
+ ppp_asynctty_close(tty);
+ return 0;
+}
+
+/*
+ * Read does nothing - no data is ever available this way.
+ * Pppd reads and writes packets via /dev/ppp instead.
+ */
+static ssize_t
+ppp_asynctty_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t count)
+{
+ return -EAGAIN;
+}
+
+/*
+ * Write on the tty does nothing, the packets all come in
+ * from the ppp generic stuff.
+ */
+static ssize_t
+ppp_asynctty_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *buf, size_t count)
+{
+ return -EAGAIN;
+}
+
+/*
+ * Called in process context only. May be re-entered by multiple
+ * ioctl calling threads.
+ */
+
+static int
+ppp_asynctty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct asyncppp *ap = ap_get(tty);
+ int err, val;
+ int __user *p = (int __user *)arg;
+
+ if (!ap)
+ return -ENXIO;
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGCHAN:
+ err = -EFAULT;
+ if (put_user(ppp_channel_index(&ap->chan), p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGUNIT:
+ err = -EFAULT;
+ if (put_user(ppp_unit_number(&ap->chan), p))
+ break;
+ err = 0;
+ break;
+
+ case TCFLSH:
+ /* flush our buffers and the serial port's buffer */
+ if (arg == TCIOFLUSH || arg == TCOFLUSH)
+ ppp_async_flush_output(ap);
+ err = tty_perform_flush(tty, arg);
+ break;
+
+ case FIONREAD:
+ val = 0;
+ if (put_user(val, p))
+ break;
+ err = 0;
+ break;
+
+ default:
+ /* Try the various mode ioctls */
+ err = tty_mode_ioctl(tty, file, cmd, arg);
+ }
+
+ ap_put(ap);
+ return err;
+}
+
+/* No kernel lock - fine */
+static unsigned int
+ppp_asynctty_poll(struct tty_struct *tty, struct file *file, poll_table *wait)
+{
+ return 0;
+}
+
+/* May sleep, don't call from interrupt level or with interrupts disabled */
+static void
+ppp_asynctty_receive(struct tty_struct *tty, const unsigned char *buf,
+ char *cflags, int count)
+{
+ struct asyncppp *ap = ap_get(tty);
+ unsigned long flags;
+
+ if (!ap)
+ return;
+ spin_lock_irqsave(&ap->recv_lock, flags);
+ ppp_async_input(ap, buf, cflags, count);
+ spin_unlock_irqrestore(&ap->recv_lock, flags);
+ if (!skb_queue_empty(&ap->rqueue))
+ tasklet_schedule(&ap->tsk);
+ ap_put(ap);
+ tty_unthrottle(tty);
+}
+
+static void
+ppp_asynctty_wakeup(struct tty_struct *tty)
+{
+ struct asyncppp *ap = ap_get(tty);
+
+ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ if (!ap)
+ return;
+ set_bit(XMIT_WAKEUP, &ap->xmit_flags);
+ tasklet_schedule(&ap->tsk);
+ ap_put(ap);
+}
+
+
+static struct tty_ldisc_ops ppp_ldisc = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "ppp",
+ .open = ppp_asynctty_open,
+ .close = ppp_asynctty_close,
+ .hangup = ppp_asynctty_hangup,
+ .read = ppp_asynctty_read,
+ .write = ppp_asynctty_write,
+ .ioctl = ppp_asynctty_ioctl,
+ .poll = ppp_asynctty_poll,
+ .receive_buf = ppp_asynctty_receive,
+ .write_wakeup = ppp_asynctty_wakeup,
+};
+
+static int __init
+ppp_async_init(void)
+{
+ int err;
+
+ err = tty_register_ldisc(N_PPP, &ppp_ldisc);
+ if (err != 0)
+ printk(KERN_ERR "PPP_async: error %d registering line disc.\n",
+ err);
+ return err;
+}
+
+/*
+ * The following routines provide the PPP channel interface.
+ */
+static int
+ppp_async_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg)
+{
+ struct asyncppp *ap = chan->private;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int err, val;
+ u32 accm[8];
+
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGFLAGS:
+ val = ap->flags | ap->rbits;
+ if (put_user(val, p))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSFLAGS:
+ if (get_user(val, p))
+ break;
+ ap->flags = val & ~SC_RCV_BITS;
+ spin_lock_irq(&ap->recv_lock);
+ ap->rbits = val & SC_RCV_BITS;
+ spin_unlock_irq(&ap->recv_lock);
+ err = 0;
+ break;
+
+ case PPPIOCGASYNCMAP:
+ if (put_user(ap->xaccm[0], (u32 __user *)argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSASYNCMAP:
+ if (get_user(ap->xaccm[0], (u32 __user *)argp))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGRASYNCMAP:
+ if (put_user(ap->raccm, (u32 __user *)argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSRASYNCMAP:
+ if (get_user(ap->raccm, (u32 __user *)argp))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGXASYNCMAP:
+ if (copy_to_user(argp, ap->xaccm, sizeof(ap->xaccm)))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSXASYNCMAP:
+ if (copy_from_user(accm, argp, sizeof(accm)))
+ break;
+ accm[2] &= ~0x40000000U; /* can't escape 0x5e */
+ accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */
+ memcpy(ap->xaccm, accm, sizeof(ap->xaccm));
+ err = 0;
+ break;
+
+ case PPPIOCGMRU:
+ if (put_user(ap->mru, p))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSMRU:
+ if (get_user(val, p))
+ break;
+ if (val < PPP_MRU)
+ val = PPP_MRU;
+ ap->mru = val;
+ err = 0;
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+
+ return err;
+}
+
+/*
+ * This is called at softirq level to deliver received packets
+ * to the ppp_generic code, and to tell the ppp_generic code
+ * if we can accept more output now.
+ */
+static void ppp_async_process(unsigned long arg)
+{
+ struct asyncppp *ap = (struct asyncppp *) arg;
+ struct sk_buff *skb;
+
+ /* process received packets */
+ while ((skb = skb_dequeue(&ap->rqueue)) != NULL) {
+ if (skb->cb[0])
+ ppp_input_error(&ap->chan, 0);
+ ppp_input(&ap->chan, skb);
+ }
+
+ /* try to push more stuff out */
+ if (test_bit(XMIT_WAKEUP, &ap->xmit_flags) && ppp_async_push(ap))
+ ppp_output_wakeup(&ap->chan);
+}
+
+/*
+ * Procedures for encapsulation and framing.
+ */
+
+/*
+ * Procedure to encode the data for async serial transmission.
+ * Does octet stuffing (escaping), puts the address/control bytes
+ * on if A/C compression is disabled, and does protocol compression.
+ * Assumes ap->tpkt != 0 on entry.
+ * Returns 1 if we finished the current frame, 0 otherwise.
+ */
+
+#define PUT_BYTE(ap, buf, c, islcp) do { \
+ if ((islcp && c < 0x20) || (ap->xaccm[c >> 5] & (1 << (c & 0x1f)))) {\
+ *buf++ = PPP_ESCAPE; \
+ *buf++ = c ^ PPP_TRANS; \
+ } else \
+ *buf++ = c; \
+} while (0)
+
+static int
+ppp_async_encode(struct asyncppp *ap)
+{
+ int fcs, i, count, c, proto;
+ unsigned char *buf, *buflim;
+ unsigned char *data;
+ int islcp;
+
+ buf = ap->obuf;
+ ap->olim = buf;
+ ap->optr = buf;
+ i = ap->tpkt_pos;
+ data = ap->tpkt->data;
+ count = ap->tpkt->len;
+ fcs = ap->tfcs;
+ proto = get_unaligned_be16(data);
+
+ /*
+ * LCP packets with code values between 1 (configure-reqest)
+ * and 7 (code-reject) must be sent as though no options
+ * had been negotiated.
+ */
+ islcp = proto == PPP_LCP && 1 <= data[2] && data[2] <= 7;
+
+ if (i == 0) {
+ if (islcp)
+ async_lcp_peek(ap, data, count, 0);
+
+ /*
+ * Start of a new packet - insert the leading FLAG
+ * character if necessary.
+ */
+ if (islcp || flag_time == 0 ||
+ time_after_eq(jiffies, ap->last_xmit + flag_time))
+ *buf++ = PPP_FLAG;
+ ap->last_xmit = jiffies;
+ fcs = PPP_INITFCS;
+
+ /*
+ * Put in the address/control bytes if necessary
+ */
+ if ((ap->flags & SC_COMP_AC) == 0 || islcp) {
+ PUT_BYTE(ap, buf, 0xff, islcp);
+ fcs = PPP_FCS(fcs, 0xff);
+ PUT_BYTE(ap, buf, 0x03, islcp);
+ fcs = PPP_FCS(fcs, 0x03);
+ }
+ }
+
+ /*
+ * Once we put in the last byte, we need to put in the FCS
+ * and closing flag, so make sure there is at least 7 bytes
+ * of free space in the output buffer.
+ */
+ buflim = ap->obuf + OBUFSIZE - 6;
+ while (i < count && buf < buflim) {
+ c = data[i++];
+ if (i == 1 && c == 0 && (ap->flags & SC_COMP_PROT))
+ continue; /* compress protocol field */
+ fcs = PPP_FCS(fcs, c);
+ PUT_BYTE(ap, buf, c, islcp);
+ }
+
+ if (i < count) {
+ /*
+ * Remember where we are up to in this packet.
+ */
+ ap->olim = buf;
+ ap->tpkt_pos = i;
+ ap->tfcs = fcs;
+ return 0;
+ }
+
+ /*
+ * We have finished the packet. Add the FCS and flag.
+ */
+ fcs = ~fcs;
+ c = fcs & 0xff;
+ PUT_BYTE(ap, buf, c, islcp);
+ c = (fcs >> 8) & 0xff;
+ PUT_BYTE(ap, buf, c, islcp);
+ *buf++ = PPP_FLAG;
+ ap->olim = buf;
+
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ return 1;
+}
+
+/*
+ * Transmit-side routines.
+ */
+
+/*
+ * Send a packet to the peer over an async tty line.
+ * Returns 1 iff the packet was accepted.
+ * If the packet was not accepted, we will call ppp_output_wakeup
+ * at some later time.
+ */
+static int
+ppp_async_send(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct asyncppp *ap = chan->private;
+
+ ppp_async_push(ap);
+
+ if (test_and_set_bit(XMIT_FULL, &ap->xmit_flags))
+ return 0; /* already full */
+ ap->tpkt = skb;
+ ap->tpkt_pos = 0;
+
+ ppp_async_push(ap);
+ return 1;
+}
+
+/*
+ * Push as much data as possible out to the tty.
+ */
+static int
+ppp_async_push(struct asyncppp *ap)
+{
+ int avail, sent, done = 0;
+ struct tty_struct *tty = ap->tty;
+ int tty_stuffed = 0;
+
+ /*
+ * We can get called recursively here if the tty write
+ * function calls our wakeup function. This can happen
+ * for example on a pty with both the master and slave
+ * set to PPP line discipline.
+ * We use the XMIT_BUSY bit to detect this and get out,
+ * leaving the XMIT_WAKEUP bit set to tell the other
+ * instance that it may now be able to write more now.
+ */
+ if (test_and_set_bit(XMIT_BUSY, &ap->xmit_flags))
+ return 0;
+ spin_lock_bh(&ap->xmit_lock);
+ for (;;) {
+ if (test_and_clear_bit(XMIT_WAKEUP, &ap->xmit_flags))
+ tty_stuffed = 0;
+ if (!tty_stuffed && ap->optr < ap->olim) {
+ avail = ap->olim - ap->optr;
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ sent = tty->ops->write(tty, ap->optr, avail);
+ if (sent < 0)
+ goto flush; /* error, e.g. loss of CD */
+ ap->optr += sent;
+ if (sent < avail)
+ tty_stuffed = 1;
+ continue;
+ }
+ if (ap->optr >= ap->olim && ap->tpkt) {
+ if (ppp_async_encode(ap)) {
+ /* finished processing ap->tpkt */
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ continue;
+ }
+ /*
+ * We haven't made any progress this time around.
+ * Clear XMIT_BUSY to let other callers in, but
+ * after doing so we have to check if anyone set
+ * XMIT_WAKEUP since we last checked it. If they
+ * did, we should try again to set XMIT_BUSY and go
+ * around again in case XMIT_BUSY was still set when
+ * the other caller tried.
+ */
+ clear_bit(XMIT_BUSY, &ap->xmit_flags);
+ /* any more work to do? if not, exit the loop */
+ if (!(test_bit(XMIT_WAKEUP, &ap->xmit_flags) ||
+ (!tty_stuffed && ap->tpkt)))
+ break;
+ /* more work to do, see if we can do it now */
+ if (test_and_set_bit(XMIT_BUSY, &ap->xmit_flags))
+ break;
+ }
+ spin_unlock_bh(&ap->xmit_lock);
+ return done;
+
+flush:
+ clear_bit(XMIT_BUSY, &ap->xmit_flags);
+ if (ap->tpkt) {
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ ap->optr = ap->olim;
+ spin_unlock_bh(&ap->xmit_lock);
+ return done;
+}
+
+/*
+ * Flush output from our internal buffers.
+ * Called for the TCFLSH ioctl. Can be entered in parallel
+ * but this is covered by the xmit_lock.
+ */
+static void
+ppp_async_flush_output(struct asyncppp *ap)
+{
+ int done = 0;
+
+ spin_lock_bh(&ap->xmit_lock);
+ ap->optr = ap->olim;
+ if (ap->tpkt != NULL) {
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ spin_unlock_bh(&ap->xmit_lock);
+ if (done)
+ ppp_output_wakeup(&ap->chan);
+}
+
+/*
+ * Receive-side routines.
+ */
+
+/* see how many ordinary chars there are at the start of buf */
+static inline int
+scan_ordinary(struct asyncppp *ap, const unsigned char *buf, int count)
+{
+ int i, c;
+
+ for (i = 0; i < count; ++i) {
+ c = buf[i];
+ if (c == PPP_ESCAPE || c == PPP_FLAG ||
+ (c < 0x20 && (ap->raccm & (1 << c)) != 0))
+ break;
+ }
+ return i;
+}
+
+/* called when a flag is seen - do end-of-packet processing */
+static void
+process_input_packet(struct asyncppp *ap)
+{
+ struct sk_buff *skb;
+ unsigned char *p;
+ unsigned int len, fcs, proto;
+
+ skb = ap->rpkt;
+ if (ap->state & (SC_TOSS | SC_ESCAPE))
+ goto err;
+
+ if (skb == NULL)
+ return; /* 0-length packet */
+
+ /* check the FCS */
+ p = skb->data;
+ len = skb->len;
+ if (len < 3)
+ goto err; /* too short */
+ fcs = PPP_INITFCS;
+ for (; len > 0; --len)
+ fcs = PPP_FCS(fcs, *p++);
+ if (fcs != PPP_GOODFCS)
+ goto err; /* bad FCS */
+ skb_trim(skb, skb->len - 2);
+
+ /* check for address/control and protocol compression */
+ p = skb->data;
+ if (p[0] == PPP_ALLSTATIONS) {
+ /* chop off address/control */
+ if (p[1] != PPP_UI || skb->len < 3)
+ goto err;
+ p = skb_pull(skb, 2);
+ }
+ proto = p[0];
+ if (proto & 1) {
+ /* protocol is compressed */
+ skb_push(skb, 1)[0] = 0;
+ } else {
+ if (skb->len < 2)
+ goto err;
+ proto = (proto << 8) + p[1];
+ if (proto == PPP_LCP)
+ async_lcp_peek(ap, p, skb->len, 1);
+ }
+
+ /* queue the frame to be processed */
+ skb->cb[0] = ap->state;
+ skb_queue_tail(&ap->rqueue, skb);
+ ap->rpkt = NULL;
+ ap->state = 0;
+ return;
+
+ err:
+ /* frame had an error, remember that, reset SC_TOSS & SC_ESCAPE */
+ ap->state = SC_PREV_ERROR;
+ if (skb) {
+ /* make skb appear as freshly allocated */
+ skb_trim(skb, 0);
+ skb_reserve(skb, - skb_headroom(skb));
+ }
+}
+
+/* Called when the tty driver has data for us. Runs parallel with the
+ other ldisc functions but will not be re-entered */
+
+static void
+ppp_async_input(struct asyncppp *ap, const unsigned char *buf,
+ char *flags, int count)
+{
+ struct sk_buff *skb;
+ int c, i, j, n, s, f;
+ unsigned char *sp;
+
+ /* update bits used for 8-bit cleanness detection */
+ if (~ap->rbits & SC_RCV_BITS) {
+ s = 0;
+ for (i = 0; i < count; ++i) {
+ c = buf[i];
+ if (flags && flags[i] != 0)
+ continue;
+ s |= (c & 0x80)? SC_RCV_B7_1: SC_RCV_B7_0;
+ c = ((c >> 4) ^ c) & 0xf;
+ s |= (0x6996 & (1 << c))? SC_RCV_ODDP: SC_RCV_EVNP;
+ }
+ ap->rbits |= s;
+ }
+
+ while (count > 0) {
+ /* scan through and see how many chars we can do in bulk */
+ if ((ap->state & SC_ESCAPE) && buf[0] == PPP_ESCAPE)
+ n = 1;
+ else
+ n = scan_ordinary(ap, buf, count);
+
+ f = 0;
+ if (flags && (ap->state & SC_TOSS) == 0) {
+ /* check the flags to see if any char had an error */
+ for (j = 0; j < n; ++j)
+ if ((f = flags[j]) != 0)
+ break;
+ }
+ if (f != 0) {
+ /* start tossing */
+ ap->state |= SC_TOSS;
+
+ } else if (n > 0 && (ap->state & SC_TOSS) == 0) {
+ /* stuff the chars in the skb */
+ skb = ap->rpkt;
+ if (!skb) {
+ skb = dev_alloc_skb(ap->mru + PPP_HDRLEN + 2);
+ if (!skb)
+ goto nomem;
+ ap->rpkt = skb;
+ }
+ if (skb->len == 0) {
+ /* Try to get the payload 4-byte aligned.
+ * This should match the
+ * PPP_ALLSTATIONS/PPP_UI/compressed tests in
+ * process_input_packet, but we do not have
+ * enough chars here to test buf[1] and buf[2].
+ */
+ if (buf[0] != PPP_ALLSTATIONS)
+ skb_reserve(skb, 2 + (buf[0] & 1));
+ }
+ if (n > skb_tailroom(skb)) {
+ /* packet overflowed MRU */
+ ap->state |= SC_TOSS;
+ } else {
+ sp = skb_put(skb, n);
+ memcpy(sp, buf, n);
+ if (ap->state & SC_ESCAPE) {
+ sp[0] ^= PPP_TRANS;
+ ap->state &= ~SC_ESCAPE;
+ }
+ }
+ }
+
+ if (n >= count)
+ break;
+
+ c = buf[n];
+ if (flags != NULL && flags[n] != 0) {
+ ap->state |= SC_TOSS;
+ } else if (c == PPP_FLAG) {
+ process_input_packet(ap);
+ } else if (c == PPP_ESCAPE) {
+ ap->state |= SC_ESCAPE;
+ } else if (I_IXON(ap->tty)) {
+ if (c == START_CHAR(ap->tty))
+ start_tty(ap->tty);
+ else if (c == STOP_CHAR(ap->tty))
+ stop_tty(ap->tty);
+ }
+ /* otherwise it's a char in the recv ACCM */
+ ++n;
+
+ buf += n;
+ if (flags)
+ flags += n;
+ count -= n;
+ }
+ return;
+
+ nomem:
+ printk(KERN_ERR "PPPasync: no memory (input pkt)\n");
+ ap->state |= SC_TOSS;
+}
+
+/*
+ * We look at LCP frames going past so that we can notice
+ * and react to the LCP configure-ack from the peer.
+ * In the situation where the peer has been sent a configure-ack
+ * already, LCP is up once it has sent its configure-ack
+ * so the immediately following packet can be sent with the
+ * configured LCP options. This allows us to process the following
+ * packet correctly without pppd needing to respond quickly.
+ *
+ * We only respond to the received configure-ack if we have just
+ * sent a configure-request, and the configure-ack contains the
+ * same data (this is checked using a 16-bit crc of the data).
+ */
+#define CONFREQ 1 /* LCP code field values */
+#define CONFACK 2
+#define LCP_MRU 1 /* LCP option numbers */
+#define LCP_ASYNCMAP 2
+
+static void async_lcp_peek(struct asyncppp *ap, unsigned char *data,
+ int len, int inbound)
+{
+ int dlen, fcs, i, code;
+ u32 val;
+
+ data += 2; /* skip protocol bytes */
+ len -= 2;
+ if (len < 4) /* 4 = code, ID, length */
+ return;
+ code = data[0];
+ if (code != CONFACK && code != CONFREQ)
+ return;
+ dlen = get_unaligned_be16(data + 2);
+ if (len < dlen)
+ return; /* packet got truncated or length is bogus */
+
+ if (code == (inbound? CONFACK: CONFREQ)) {
+ /*
+ * sent confreq or received confack:
+ * calculate the crc of the data from the ID field on.
+ */
+ fcs = PPP_INITFCS;
+ for (i = 1; i < dlen; ++i)
+ fcs = PPP_FCS(fcs, data[i]);
+
+ if (!inbound) {
+ /* outbound confreq - remember the crc for later */
+ ap->lcp_fcs = fcs;
+ return;
+ }
+
+ /* received confack, check the crc */
+ fcs ^= ap->lcp_fcs;
+ ap->lcp_fcs = -1;
+ if (fcs != 0)
+ return;
+ } else if (inbound)
+ return; /* not interested in received confreq */
+
+ /* process the options in the confack */
+ data += 4;
+ dlen -= 4;
+ /* data[0] is code, data[1] is length */
+ while (dlen >= 2 && dlen >= data[1] && data[1] >= 2) {
+ switch (data[0]) {
+ case LCP_MRU:
+ val = get_unaligned_be16(data + 2);
+ if (inbound)
+ ap->mru = val;
+ else
+ ap->chan.mtu = val;
+ break;
+ case LCP_ASYNCMAP:
+ val = get_unaligned_be32(data + 2);
+ if (inbound)
+ ap->raccm = val;
+ else
+ ap->xaccm[0] = val;
+ break;
+ }
+ dlen -= data[1];
+ data += data[1];
+ }
+}
+
+static void __exit ppp_async_cleanup(void)
+{
+ if (tty_unregister_ldisc(N_PPP) != 0)
+ printk(KERN_ERR "failed to unregister PPP line discipline\n");
+}
+
+module_init(ppp_async_init);
+module_exit(ppp_async_cleanup);
diff --git a/drivers/net/ppp/ppp_deflate.c b/drivers/net/ppp/ppp_deflate.c
new file mode 100644
index 0000000..1dbdf82
--- /dev/null
+++ b/drivers/net/ppp/ppp_deflate.c
@@ -0,0 +1,653 @@
+/*
+ * ==FILEVERSION 980319==
+ *
+ * ppp_deflate.c - interface the zlib procedures for Deflate compression
+ * and decompression (as used by gzip) to the PPP code.
+ * This version is for use with Linux kernel 1.3.X.
+ *
+ * Copyright (c) 1994 The Australian National University.
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation is hereby granted, provided that the above copyright
+ * notice appears in all copies. This software is provided without any
+ * warranty, express or implied. The Australian National University
+ * makes no representations about the suitability of this software for
+ * any purpose.
+ *
+ * IN NO EVENT SHALL THE AUSTRALIAN NATIONAL UNIVERSITY BE LIABLE TO ANY
+ * PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
+ * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
+ * THE AUSTRALIAN NATIONAL UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * THE AUSTRALIAN NATIONAL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
+ * ON AN "AS IS" BASIS, AND THE AUSTRALIAN NATIONAL UNIVERSITY HAS NO
+ * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS,
+ * OR MODIFICATIONS.
+ *
+ * From: deflate.c,v 1.1 1996/01/18 03:17:48 paulus Exp
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/string.h>
+
+#include <linux/ppp_defs.h>
+#include <linux/ppp-comp.h>
+
+#include <linux/zlib.h>
+#include <asm/unaligned.h>
+
+/*
+ * State for a Deflate (de)compressor.
+ */
+struct ppp_deflate_state {
+ int seqno;
+ int w_size;
+ int unit;
+ int mru;
+ int debug;
+ z_stream strm;
+ struct compstat stats;
+};
+
+#define DEFLATE_OVHD 2 /* Deflate overhead/packet */
+
+static void *z_comp_alloc(unsigned char *options, int opt_len);
+static void *z_decomp_alloc(unsigned char *options, int opt_len);
+static void z_comp_free(void *state);
+static void z_decomp_free(void *state);
+static int z_comp_init(void *state, unsigned char *options,
+ int opt_len,
+ int unit, int hdrlen, int debug);
+static int z_decomp_init(void *state, unsigned char *options,
+ int opt_len,
+ int unit, int hdrlen, int mru, int debug);
+static int z_compress(void *state, unsigned char *rptr,
+ unsigned char *obuf,
+ int isize, int osize);
+static void z_incomp(void *state, unsigned char *ibuf, int icnt);
+static int z_decompress(void *state, unsigned char *ibuf,
+ int isize, unsigned char *obuf, int osize);
+static void z_comp_reset(void *state);
+static void z_decomp_reset(void *state);
+static void z_comp_stats(void *state, struct compstat *stats);
+
+/**
+ * z_comp_free - free the memory used by a compressor
+ * @arg: pointer to the private state for the compressor.
+ */
+static void z_comp_free(void *arg)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ if (state) {
+ zlib_deflateEnd(&state->strm);
+ vfree(state->strm.workspace);
+ kfree(state);
+ }
+}
+
+/**
+ * z_comp_alloc - allocate space for a compressor.
+ * @options: pointer to CCP option data
+ * @opt_len: length of the CCP option at @options.
+ *
+ * The @options pointer points to the a buffer containing the
+ * CCP option data for the compression being negotiated. It is
+ * formatted according to RFC1979, and describes the window
+ * size that the peer is requesting that we use in compressing
+ * data to be sent to it.
+ *
+ * Returns the pointer to the private state for the compressor,
+ * or NULL if we could not allocate enough memory.
+ */
+static void *z_comp_alloc(unsigned char *options, int opt_len)
+{
+ struct ppp_deflate_state *state;
+ int w_size;
+
+ if (opt_len != CILEN_DEFLATE ||
+ (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
+ options[1] != CILEN_DEFLATE ||
+ DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
+ options[3] != DEFLATE_CHK_SEQUENCE)
+ return NULL;
+ w_size = DEFLATE_SIZE(options[2]);
+ if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE)
+ return NULL;
+
+ state = kzalloc(sizeof(*state),
+ GFP_KERNEL);
+ if (state == NULL)
+ return NULL;
+
+ state->strm.next_in = NULL;
+ state->w_size = w_size;
+ state->strm.workspace = vmalloc(zlib_deflate_workspacesize(-w_size, 8));
+ if (state->strm.workspace == NULL)
+ goto out_free;
+
+ if (zlib_deflateInit2(&state->strm, Z_DEFAULT_COMPRESSION,
+ DEFLATE_METHOD_VAL, -w_size, 8, Z_DEFAULT_STRATEGY)
+ != Z_OK)
+ goto out_free;
+ return (void *) state;
+
+out_free:
+ z_comp_free(state);
+ return NULL;
+}
+
+/**
+ * z_comp_init - initialize a previously-allocated compressor.
+ * @arg: pointer to the private state for the compressor
+ * @options: pointer to the CCP option data describing the
+ * compression that was negotiated with the peer
+ * @opt_len: length of the CCP option data at @options
+ * @unit: PPP unit number for diagnostic messages
+ * @hdrlen: ignored (present for backwards compatibility)
+ * @debug: debug flag; if non-zero, debug messages are printed.
+ *
+ * The CCP options described by @options must match the options
+ * specified when the compressor was allocated. The compressor
+ * history is reset. Returns 0 for failure (CCP options don't
+ * match) or 1 for success.
+ */
+static int z_comp_init(void *arg, unsigned char *options, int opt_len,
+ int unit, int hdrlen, int debug)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ if (opt_len < CILEN_DEFLATE ||
+ (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
+ options[1] != CILEN_DEFLATE ||
+ DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
+ DEFLATE_SIZE(options[2]) != state->w_size ||
+ options[3] != DEFLATE_CHK_SEQUENCE)
+ return 0;
+
+ state->seqno = 0;
+ state->unit = unit;
+ state->debug = debug;
+
+ zlib_deflateReset(&state->strm);
+
+ return 1;
+}
+
+/**
+ * z_comp_reset - reset a previously-allocated compressor.
+ * @arg: pointer to private state for the compressor.
+ *
+ * This clears the history for the compressor and makes it
+ * ready to start emitting a new compressed stream.
+ */
+static void z_comp_reset(void *arg)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ state->seqno = 0;
+ zlib_deflateReset(&state->strm);
+}
+
+/**
+ * z_compress - compress a PPP packet with Deflate compression.
+ * @arg: pointer to private state for the compressor
+ * @rptr: uncompressed packet (input)
+ * @obuf: compressed packet (output)
+ * @isize: size of uncompressed packet
+ * @osize: space available at @obuf
+ *
+ * Returns the length of the compressed packet, or 0 if the
+ * packet is incompressible.
+ */
+static int z_compress(void *arg, unsigned char *rptr, unsigned char *obuf,
+ int isize, int osize)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+ int r, proto, off, olen, oavail;
+ unsigned char *wptr;
+
+ /*
+ * Check that the protocol is in the range we handle.
+ */
+ proto = PPP_PROTOCOL(rptr);
+ if (proto > 0x3fff || proto == 0xfd || proto == 0xfb)
+ return 0;
+
+ /* Don't generate compressed packets which are larger than
+ the uncompressed packet. */
+ if (osize > isize)
+ osize = isize;
+
+ wptr = obuf;
+
+ /*
+ * Copy over the PPP header and store the 2-byte sequence number.
+ */
+ wptr[0] = PPP_ADDRESS(rptr);
+ wptr[1] = PPP_CONTROL(rptr);
+ put_unaligned_be16(PPP_COMP, wptr + 2);
+ wptr += PPP_HDRLEN;
+ put_unaligned_be16(state->seqno, wptr);
+ wptr += DEFLATE_OVHD;
+ olen = PPP_HDRLEN + DEFLATE_OVHD;
+ state->strm.next_out = wptr;
+ state->strm.avail_out = oavail = osize - olen;
+ ++state->seqno;
+
+ off = (proto > 0xff) ? 2 : 3; /* skip 1st proto byte if 0 */
+ rptr += off;
+ state->strm.next_in = rptr;
+ state->strm.avail_in = (isize - off);
+
+ for (;;) {
+ r = zlib_deflate(&state->strm, Z_PACKET_FLUSH);
+ if (r != Z_OK) {
+ if (state->debug)
+ printk(KERN_ERR
+ "z_compress: deflate returned %d\n", r);
+ break;
+ }
+ if (state->strm.avail_out == 0) {
+ olen += oavail;
+ state->strm.next_out = NULL;
+ state->strm.avail_out = oavail = 1000000;
+ } else {
+ break; /* all done */
+ }
+ }
+ olen += oavail - state->strm.avail_out;
+
+ /*
+ * See if we managed to reduce the size of the packet.
+ */
+ if (olen < isize) {
+ state->stats.comp_bytes += olen;
+ state->stats.comp_packets++;
+ } else {
+ state->stats.inc_bytes += isize;
+ state->stats.inc_packets++;
+ olen = 0;
+ }
+ state->stats.unc_bytes += isize;
+ state->stats.unc_packets++;
+
+ return olen;
+}
+
+/**
+ * z_comp_stats - return compression statistics for a compressor
+ * or decompressor.
+ * @arg: pointer to private space for the (de)compressor
+ * @stats: pointer to a struct compstat to receive the result.
+ */
+static void z_comp_stats(void *arg, struct compstat *stats)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ *stats = state->stats;
+}
+
+/**
+ * z_decomp_free - Free the memory used by a decompressor.
+ * @arg: pointer to private space for the decompressor.
+ */
+static void z_decomp_free(void *arg)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ if (state) {
+ zlib_inflateEnd(&state->strm);
+ vfree(state->strm.workspace);
+ kfree(state);
+ }
+}
+
+/**
+ * z_decomp_alloc - allocate space for a decompressor.
+ * @options: pointer to CCP option data
+ * @opt_len: length of the CCP option at @options.
+ *
+ * The @options pointer points to the a buffer containing the
+ * CCP option data for the compression being negotiated. It is
+ * formatted according to RFC1979, and describes the window
+ * size that we are requesting the peer to use in compressing
+ * data to be sent to us.
+ *
+ * Returns the pointer to the private state for the decompressor,
+ * or NULL if we could not allocate enough memory.
+ */
+static void *z_decomp_alloc(unsigned char *options, int opt_len)
+{
+ struct ppp_deflate_state *state;
+ int w_size;
+
+ if (opt_len != CILEN_DEFLATE ||
+ (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
+ options[1] != CILEN_DEFLATE ||
+ DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
+ options[3] != DEFLATE_CHK_SEQUENCE)
+ return NULL;
+ w_size = DEFLATE_SIZE(options[2]);
+ if (w_size < DEFLATE_MIN_SIZE || w_size > DEFLATE_MAX_SIZE)
+ return NULL;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state == NULL)
+ return NULL;
+
+ state->w_size = w_size;
+ state->strm.next_out = NULL;
+ state->strm.workspace = vmalloc(zlib_inflate_workspacesize());
+ if (state->strm.workspace == NULL)
+ goto out_free;
+
+ if (zlib_inflateInit2(&state->strm, -w_size) != Z_OK)
+ goto out_free;
+ return (void *) state;
+
+out_free:
+ z_decomp_free(state);
+ return NULL;
+}
+
+/**
+ * z_decomp_init - initialize a previously-allocated decompressor.
+ * @arg: pointer to the private state for the decompressor
+ * @options: pointer to the CCP option data describing the
+ * compression that was negotiated with the peer
+ * @opt_len: length of the CCP option data at @options
+ * @unit: PPP unit number for diagnostic messages
+ * @hdrlen: ignored (present for backwards compatibility)
+ * @mru: maximum length of decompressed packets
+ * @debug: debug flag; if non-zero, debug messages are printed.
+ *
+ * The CCP options described by @options must match the options
+ * specified when the decompressor was allocated. The decompressor
+ * history is reset. Returns 0 for failure (CCP options don't
+ * match) or 1 for success.
+ */
+static int z_decomp_init(void *arg, unsigned char *options, int opt_len,
+ int unit, int hdrlen, int mru, int debug)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ if (opt_len < CILEN_DEFLATE ||
+ (options[0] != CI_DEFLATE && options[0] != CI_DEFLATE_DRAFT) ||
+ options[1] != CILEN_DEFLATE ||
+ DEFLATE_METHOD(options[2]) != DEFLATE_METHOD_VAL ||
+ DEFLATE_SIZE(options[2]) != state->w_size ||
+ options[3] != DEFLATE_CHK_SEQUENCE)
+ return 0;
+
+ state->seqno = 0;
+ state->unit = unit;
+ state->debug = debug;
+ state->mru = mru;
+
+ zlib_inflateReset(&state->strm);
+
+ return 1;
+}
+
+/**
+ * z_decomp_reset - reset a previously-allocated decompressor.
+ * @arg: pointer to private state for the decompressor.
+ *
+ * This clears the history for the decompressor and makes it
+ * ready to receive a new compressed stream.
+ */
+static void z_decomp_reset(void *arg)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+
+ state->seqno = 0;
+ zlib_inflateReset(&state->strm);
+}
+
+/**
+ * z_decompress - decompress a Deflate-compressed packet.
+ * @arg: pointer to private state for the decompressor
+ * @ibuf: pointer to input (compressed) packet data
+ * @isize: length of input packet
+ * @obuf: pointer to space for output (decompressed) packet
+ * @osize: amount of space available at @obuf
+ *
+ * Because of patent problems, we return DECOMP_ERROR for errors
+ * found by inspecting the input data and for system problems, but
+ * DECOMP_FATALERROR for any errors which could possibly be said to
+ * be being detected "after" decompression. For DECOMP_ERROR,
+ * we can issue a CCP reset-request; for DECOMP_FATALERROR, we may be
+ * infringing a patent of Motorola's if we do, so we take CCP down
+ * instead.
+ *
+ * Given that the frame has the correct sequence number and a good FCS,
+ * errors such as invalid codes in the input most likely indicate a
+ * bug, so we return DECOMP_FATALERROR for them in order to turn off
+ * compression, even though they are detected by inspecting the input.
+ */
+static int z_decompress(void *arg, unsigned char *ibuf, int isize,
+ unsigned char *obuf, int osize)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+ int olen, seq, r;
+ int decode_proto, overflow;
+ unsigned char overflow_buf[1];
+
+ if (isize <= PPP_HDRLEN + DEFLATE_OVHD) {
+ if (state->debug)
+ printk(KERN_DEBUG "z_decompress%d: short pkt (%d)\n",
+ state->unit, isize);
+ return DECOMP_ERROR;
+ }
+
+ /* Check the sequence number. */
+ seq = get_unaligned_be16(ibuf + PPP_HDRLEN);
+ if (seq != (state->seqno & 0xffff)) {
+ if (state->debug)
+ printk(KERN_DEBUG "z_decompress%d: bad seq # %d, expected %d\n",
+ state->unit, seq, state->seqno & 0xffff);
+ return DECOMP_ERROR;
+ }
+ ++state->seqno;
+
+ /*
+ * Fill in the first part of the PPP header. The protocol field
+ * comes from the decompressed data.
+ */
+ obuf[0] = PPP_ADDRESS(ibuf);
+ obuf[1] = PPP_CONTROL(ibuf);
+ obuf[2] = 0;
+
+ /*
+ * Set up to call inflate. We set avail_out to 1 initially so we can
+ * look at the first byte of the output and decide whether we have
+ * a 1-byte or 2-byte protocol field.
+ */
+ state->strm.next_in = ibuf + PPP_HDRLEN + DEFLATE_OVHD;
+ state->strm.avail_in = isize - (PPP_HDRLEN + DEFLATE_OVHD);
+ state->strm.next_out = obuf + 3;
+ state->strm.avail_out = 1;
+ decode_proto = 1;
+ overflow = 0;
+
+ /*
+ * Call inflate, supplying more input or output as needed.
+ */
+ for (;;) {
+ r = zlib_inflate(&state->strm, Z_PACKET_FLUSH);
+ if (r != Z_OK) {
+ if (state->debug)
+ printk(KERN_DEBUG "z_decompress%d: inflate returned %d (%s)\n",
+ state->unit, r, (state->strm.msg? state->strm.msg: ""));
+ return DECOMP_FATALERROR;
+ }
+ if (state->strm.avail_out != 0)
+ break; /* all done */
+ if (decode_proto) {
+ state->strm.avail_out = osize - PPP_HDRLEN;
+ if ((obuf[3] & 1) == 0) {
+ /* 2-byte protocol field */
+ obuf[2] = obuf[3];
+ --state->strm.next_out;
+ ++state->strm.avail_out;
+ }
+ decode_proto = 0;
+ } else if (!overflow) {
+ /*
+ * We've filled up the output buffer; the only way to
+ * find out whether inflate has any more characters
+ * left is to give it another byte of output space.
+ */
+ state->strm.next_out = overflow_buf;
+ state->strm.avail_out = 1;
+ overflow = 1;
+ } else {
+ if (state->debug)
+ printk(KERN_DEBUG "z_decompress%d: ran out of mru\n",
+ state->unit);
+ return DECOMP_FATALERROR;
+ }
+ }
+
+ if (decode_proto) {
+ if (state->debug)
+ printk(KERN_DEBUG "z_decompress%d: didn't get proto\n",
+ state->unit);
+ return DECOMP_ERROR;
+ }
+
+ olen = osize + overflow - state->strm.avail_out;
+ state->stats.unc_bytes += olen;
+ state->stats.unc_packets++;
+ state->stats.comp_bytes += isize;
+ state->stats.comp_packets++;
+
+ return olen;
+}
+
+/**
+ * z_incomp - add incompressible input data to the history.
+ * @arg: pointer to private state for the decompressor
+ * @ibuf: pointer to input packet data
+ * @icnt: length of input data.
+ */
+static void z_incomp(void *arg, unsigned char *ibuf, int icnt)
+{
+ struct ppp_deflate_state *state = (struct ppp_deflate_state *) arg;
+ int proto, r;
+
+ /*
+ * Check that the protocol is one we handle.
+ */
+ proto = PPP_PROTOCOL(ibuf);
+ if (proto > 0x3fff || proto == 0xfd || proto == 0xfb)
+ return;
+
+ ++state->seqno;
+
+ /*
+ * We start at the either the 1st or 2nd byte of the protocol field,
+ * depending on whether the protocol value is compressible.
+ */
+ state->strm.next_in = ibuf + 3;
+ state->strm.avail_in = icnt - 3;
+ if (proto > 0xff) {
+ --state->strm.next_in;
+ ++state->strm.avail_in;
+ }
+
+ r = zlib_inflateIncomp(&state->strm);
+ if (r != Z_OK) {
+ /* gak! */
+ if (state->debug) {
+ printk(KERN_DEBUG "z_incomp%d: inflateIncomp returned %d (%s)\n",
+ state->unit, r, (state->strm.msg? state->strm.msg: ""));
+ }
+ return;
+ }
+
+ /*
+ * Update stats.
+ */
+ state->stats.inc_bytes += icnt;
+ state->stats.inc_packets++;
+ state->stats.unc_bytes += icnt;
+ state->stats.unc_packets++;
+}
+
+/*************************************************************
+ * Module interface table
+ *************************************************************/
+
+/* These are in ppp_generic.c */
+extern int ppp_register_compressor (struct compressor *cp);
+extern void ppp_unregister_compressor (struct compressor *cp);
+
+/*
+ * Procedures exported to if_ppp.c.
+ */
+static struct compressor ppp_deflate = {
+ .compress_proto = CI_DEFLATE,
+ .comp_alloc = z_comp_alloc,
+ .comp_free = z_comp_free,
+ .comp_init = z_comp_init,
+ .comp_reset = z_comp_reset,
+ .compress = z_compress,
+ .comp_stat = z_comp_stats,
+ .decomp_alloc = z_decomp_alloc,
+ .decomp_free = z_decomp_free,
+ .decomp_init = z_decomp_init,
+ .decomp_reset = z_decomp_reset,
+ .decompress = z_decompress,
+ .incomp = z_incomp,
+ .decomp_stat = z_comp_stats,
+ .owner = THIS_MODULE
+};
+
+static struct compressor ppp_deflate_draft = {
+ .compress_proto = CI_DEFLATE_DRAFT,
+ .comp_alloc = z_comp_alloc,
+ .comp_free = z_comp_free,
+ .comp_init = z_comp_init,
+ .comp_reset = z_comp_reset,
+ .compress = z_compress,
+ .comp_stat = z_comp_stats,
+ .decomp_alloc = z_decomp_alloc,
+ .decomp_free = z_decomp_free,
+ .decomp_init = z_decomp_init,
+ .decomp_reset = z_decomp_reset,
+ .decompress = z_decompress,
+ .incomp = z_incomp,
+ .decomp_stat = z_comp_stats,
+ .owner = THIS_MODULE
+};
+
+static int __init deflate_init(void)
+{
+ int answer = ppp_register_compressor(&ppp_deflate);
+ if (answer == 0)
+ printk(KERN_INFO
+ "PPP Deflate Compression module registered\n");
+ ppp_register_compressor(&ppp_deflate_draft);
+ return answer;
+}
+
+static void __exit deflate_cleanup(void)
+{
+ ppp_unregister_compressor(&ppp_deflate);
+ ppp_unregister_compressor(&ppp_deflate_draft);
+}
+
+module_init(deflate_init);
+module_exit(deflate_cleanup);
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("ppp-compress-" __stringify(CI_DEFLATE));
+MODULE_ALIAS("ppp-compress-" __stringify(CI_DEFLATE_DRAFT));
diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
new file mode 100644
index 0000000..10e5d98
--- /dev/null
+++ b/drivers/net/ppp/ppp_generic.c
@@ -0,0 +1,2954 @@
+/*
+ * Generic PPP layer for Linux.
+ *
+ * Copyright 1999-2002 Paul Mackerras.
+ *
+ * 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.
+ *
+ * The generic PPP layer handles the PPP network interfaces, the
+ * /dev/ppp device, packet and VJ compression, and multilink.
+ * It talks to PPP `channels' via the interface defined in
+ * include/linux/ppp_channel.h. Channels provide the basic means for
+ * sending and receiving PPP frames on some kind of communications
+ * channel.
+ *
+ * Part of the code in this driver was inspired by the old async-only
+ * PPP driver, written by Michael Callahan and Al Longyear, and
+ * subsequently hacked by Paul Mackerras.
+ *
+ * ==FILEVERSION 20041108==
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/kmod.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/idr.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/ppp_defs.h>
+#include <linux/filter.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/ppp-comp.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/if_arp.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/spinlock.h>
+#include <linux/rwsem.h>
+#include <linux/stddef.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <net/slhc_vj.h>
+#include <linux/atomic.h>
+
+#include <linux/nsproxy.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+
+#define PPP_VERSION "2.4.2"
+
+/*
+ * Network protocols we support.
+ */
+#define NP_IP 0 /* Internet Protocol V4 */
+#define NP_IPV6 1 /* Internet Protocol V6 */
+#define NP_IPX 2 /* IPX protocol */
+#define NP_AT 3 /* Appletalk protocol */
+#define NP_MPLS_UC 4 /* MPLS unicast */
+#define NP_MPLS_MC 5 /* MPLS multicast */
+#define NUM_NP 6 /* Number of NPs. */
+
+#define MPHDRLEN 6 /* multilink protocol header length */
+#define MPHDRLEN_SSN 4 /* ditto with short sequence numbers */
+
+/*
+ * An instance of /dev/ppp can be associated with either a ppp
+ * interface unit or a ppp channel. In both cases, file->private_data
+ * points to one of these.
+ */
+struct ppp_file {
+ enum {
+ INTERFACE=1, CHANNEL
+ } kind;
+ struct sk_buff_head xq; /* pppd transmit queue */
+ struct sk_buff_head rq; /* receive queue for pppd */
+ wait_queue_head_t rwait; /* for poll on reading /dev/ppp */
+ atomic_t refcnt; /* # refs (incl /dev/ppp attached) */
+ int hdrlen; /* space to leave for headers */
+ int index; /* interface unit / channel number */
+ int dead; /* unit/channel has been shut down */
+};
+
+#define PF_TO_X(pf, X) container_of(pf, X, file)
+
+#define PF_TO_PPP(pf) PF_TO_X(pf, struct ppp)
+#define PF_TO_CHANNEL(pf) PF_TO_X(pf, struct channel)
+
+/*
+ * Data structure describing one ppp unit.
+ * A ppp unit corresponds to a ppp network interface device
+ * and represents a multilink bundle.
+ * It can have 0 or more ppp channels connected to it.
+ */
+struct ppp {
+ struct ppp_file file; /* stuff for read/write/poll 0 */
+ struct file *owner; /* file that owns this unit 48 */
+ struct list_head channels; /* list of attached channels 4c */
+ int n_channels; /* how many channels are attached 54 */
+ spinlock_t rlock; /* lock for receive side 58 */
+ spinlock_t wlock; /* lock for transmit side 5c */
+ int mru; /* max receive unit 60 */
+ unsigned int flags; /* control bits 64 */
+ unsigned int xstate; /* transmit state bits 68 */
+ unsigned int rstate; /* receive state bits 6c */
+ int debug; /* debug flags 70 */
+ struct slcompress *vj; /* state for VJ header compression */
+ enum NPmode npmode[NUM_NP]; /* what to do with each net proto 78 */
+ struct sk_buff *xmit_pending; /* a packet ready to go out 88 */
+ struct compressor *xcomp; /* transmit packet compressor 8c */
+ void *xc_state; /* its internal state 90 */
+ struct compressor *rcomp; /* receive decompressor 94 */
+ void *rc_state; /* its internal state 98 */
+ unsigned long last_xmit; /* jiffies when last pkt sent 9c */
+ unsigned long last_recv; /* jiffies when last pkt rcvd a0 */
+ struct net_device *dev; /* network interface device a4 */
+ int closing; /* is device closing down? a8 */
+#ifdef CONFIG_PPP_MULTILINK
+ int nxchan; /* next channel to send something on */
+ u32 nxseq; /* next sequence number to send */
+ int mrru; /* MP: max reconst. receive unit */
+ u32 nextseq; /* MP: seq no of next packet */
+ u32 minseq; /* MP: min of most recent seqnos */
+ struct sk_buff_head mrq; /* MP: receive reconstruction queue */
+#endif /* CONFIG_PPP_MULTILINK */
+#ifdef CONFIG_PPP_FILTER
+ struct sock_filter *pass_filter; /* filter for packets to pass */
+ struct sock_filter *active_filter;/* filter for pkts to reset idle */
+ unsigned pass_len, active_len;
+#endif /* CONFIG_PPP_FILTER */
+ struct net *ppp_net; /* the net we belong to */
+};
+
+/*
+ * Bits in flags: SC_NO_TCP_CCID, SC_CCP_OPEN, SC_CCP_UP, SC_LOOP_TRAFFIC,
+ * SC_MULTILINK, SC_MP_SHORTSEQ, SC_MP_XSHORTSEQ, SC_COMP_TCP, SC_REJ_COMP_TCP,
+ * SC_MUST_COMP
+ * Bits in rstate: SC_DECOMP_RUN, SC_DC_ERROR, SC_DC_FERROR.
+ * Bits in xstate: SC_COMP_RUN
+ */
+#define SC_FLAG_BITS (SC_NO_TCP_CCID|SC_CCP_OPEN|SC_CCP_UP|SC_LOOP_TRAFFIC \
+ |SC_MULTILINK|SC_MP_SHORTSEQ|SC_MP_XSHORTSEQ \
+ |SC_COMP_TCP|SC_REJ_COMP_TCP|SC_MUST_COMP)
+
+/*
+ * Private data structure for each channel.
+ * This includes the data structure used for multilink.
+ */
+struct channel {
+ struct ppp_file file; /* stuff for read/write/poll */
+ struct list_head list; /* link in all/new_channels list */
+ struct ppp_channel *chan; /* public channel data structure */
+ struct rw_semaphore chan_sem; /* protects `chan' during chan ioctl */
+ spinlock_t downl; /* protects `chan', file.xq dequeue */
+ struct ppp *ppp; /* ppp unit we're connected to */
+ struct net *chan_net; /* the net channel belongs to */
+ struct list_head clist; /* link in list of channels per unit */
+ rwlock_t upl; /* protects `ppp' */
+#ifdef CONFIG_PPP_MULTILINK
+ u8 avail; /* flag used in multilink stuff */
+ u8 had_frag; /* >= 1 fragments have been sent */
+ u32 lastseq; /* MP: last sequence # received */
+ int speed; /* speed of the corresponding ppp channel*/
+#endif /* CONFIG_PPP_MULTILINK */
+};
+
+/*
+ * SMP locking issues:
+ * Both the ppp.rlock and ppp.wlock locks protect the ppp.channels
+ * list and the ppp.n_channels field, you need to take both locks
+ * before you modify them.
+ * The lock ordering is: channel.upl -> ppp.wlock -> ppp.rlock ->
+ * channel.downl.
+ */
+
+static DEFINE_MUTEX(ppp_mutex);
+static atomic_t ppp_unit_count = ATOMIC_INIT(0);
+static atomic_t channel_count = ATOMIC_INIT(0);
+
+/* per-net private data for this module */
+static int ppp_net_id __read_mostly;
+struct ppp_net {
+ /* units to ppp mapping */
+ struct idr units_idr;
+
+ /*
+ * all_ppp_mutex protects the units_idr mapping.
+ * It also ensures that finding a ppp unit in the units_idr
+ * map and updating its file.refcnt field is atomic.
+ */
+ struct mutex all_ppp_mutex;
+
+ /* channels */
+ struct list_head all_channels;
+ struct list_head new_channels;
+ int last_channel_index;
+
+ /*
+ * all_channels_lock protects all_channels and
+ * last_channel_index, and the atomicity of find
+ * a channel and updating its file.refcnt field.
+ */
+ spinlock_t all_channels_lock;
+};
+
+/* Get the PPP protocol number from a skb */
+#define PPP_PROTO(skb) get_unaligned_be16((skb)->data)
+
+/* We limit the length of ppp->file.rq to this (arbitrary) value */
+#define PPP_MAX_RQLEN 32
+
+/*
+ * Maximum number of multilink fragments queued up.
+ * This has to be large enough to cope with the maximum latency of
+ * the slowest channel relative to the others. Strictly it should
+ * depend on the number of channels and their characteristics.
+ */
+#define PPP_MP_MAX_QLEN 128
+
+/* Multilink header bits. */
+#define B 0x80 /* this fragment begins a packet */
+#define E 0x40 /* this fragment ends a packet */
+
+/* Compare multilink sequence numbers (assumed to be 32 bits wide) */
+#define seq_before(a, b) ((s32)((a) - (b)) < 0)
+#define seq_after(a, b) ((s32)((a) - (b)) > 0)
+
+/* Prototypes. */
+static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf,
+ struct file *file, unsigned int cmd, unsigned long arg);
+static void ppp_xmit_process(struct ppp *ppp);
+static void ppp_send_frame(struct ppp *ppp, struct sk_buff *skb);
+static void ppp_push(struct ppp *ppp);
+static void ppp_channel_push(struct channel *pch);
+static void ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb,
+ struct channel *pch);
+static void ppp_receive_error(struct ppp *ppp);
+static void ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb);
+static struct sk_buff *ppp_decompress_frame(struct ppp *ppp,
+ struct sk_buff *skb);
+#ifdef CONFIG_PPP_MULTILINK
+static void ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb,
+ struct channel *pch);
+static void ppp_mp_insert(struct ppp *ppp, struct sk_buff *skb);
+static struct sk_buff *ppp_mp_reconstruct(struct ppp *ppp);
+static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb);
+#endif /* CONFIG_PPP_MULTILINK */
+static int ppp_set_compress(struct ppp *ppp, unsigned long arg);
+static void ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound);
+static void ppp_ccp_closed(struct ppp *ppp);
+static struct compressor *find_compressor(int type);
+static void ppp_get_stats(struct ppp *ppp, struct ppp_stats *st);
+static struct ppp *ppp_create_interface(struct net *net, int unit, int *retp);
+static void init_ppp_file(struct ppp_file *pf, int kind);
+static void ppp_shutdown_interface(struct ppp *ppp);
+static void ppp_destroy_interface(struct ppp *ppp);
+static struct ppp *ppp_find_unit(struct ppp_net *pn, int unit);
+static struct channel *ppp_find_channel(struct ppp_net *pn, int unit);
+static int ppp_connect_channel(struct channel *pch, int unit);
+static int ppp_disconnect_channel(struct channel *pch);
+static void ppp_destroy_channel(struct channel *pch);
+static int unit_get(struct idr *p, void *ptr);
+static int unit_set(struct idr *p, void *ptr, int n);
+static void unit_put(struct idr *p, int n);
+static void *unit_find(struct idr *p, int n);
+
+static struct class *ppp_class;
+
+/* per net-namespace data */
+static inline struct ppp_net *ppp_pernet(struct net *net)
+{
+ BUG_ON(!net);
+
+ return net_generic(net, ppp_net_id);
+}
+
+/* Translates a PPP protocol number to a NP index (NP == network protocol) */
+static inline int proto_to_npindex(int proto)
+{
+ switch (proto) {
+ case PPP_IP:
+ return NP_IP;
+ case PPP_IPV6:
+ return NP_IPV6;
+ case PPP_IPX:
+ return NP_IPX;
+ case PPP_AT:
+ return NP_AT;
+ case PPP_MPLS_UC:
+ return NP_MPLS_UC;
+ case PPP_MPLS_MC:
+ return NP_MPLS_MC;
+ }
+ return -EINVAL;
+}
+
+/* Translates an NP index into a PPP protocol number */
+static const int npindex_to_proto[NUM_NP] = {
+ PPP_IP,
+ PPP_IPV6,
+ PPP_IPX,
+ PPP_AT,
+ PPP_MPLS_UC,
+ PPP_MPLS_MC,
+};
+
+/* Translates an ethertype into an NP index */
+static inline int ethertype_to_npindex(int ethertype)
+{
+ switch (ethertype) {
+ case ETH_P_IP:
+ return NP_IP;
+ case ETH_P_IPV6:
+ return NP_IPV6;
+ case ETH_P_IPX:
+ return NP_IPX;
+ case ETH_P_PPPTALK:
+ case ETH_P_ATALK:
+ return NP_AT;
+ case ETH_P_MPLS_UC:
+ return NP_MPLS_UC;
+ case ETH_P_MPLS_MC:
+ return NP_MPLS_MC;
+ }
+ return -1;
+}
+
+/* Translates an NP index into an ethertype */
+static const int npindex_to_ethertype[NUM_NP] = {
+ ETH_P_IP,
+ ETH_P_IPV6,
+ ETH_P_IPX,
+ ETH_P_PPPTALK,
+ ETH_P_MPLS_UC,
+ ETH_P_MPLS_MC,
+};
+
+/*
+ * Locking shorthand.
+ */
+#define ppp_xmit_lock(ppp) spin_lock_bh(&(ppp)->wlock)
+#define ppp_xmit_unlock(ppp) spin_unlock_bh(&(ppp)->wlock)
+#define ppp_recv_lock(ppp) spin_lock_bh(&(ppp)->rlock)
+#define ppp_recv_unlock(ppp) spin_unlock_bh(&(ppp)->rlock)
+#define ppp_lock(ppp) do { ppp_xmit_lock(ppp); \
+ ppp_recv_lock(ppp); } while (0)
+#define ppp_unlock(ppp) do { ppp_recv_unlock(ppp); \
+ ppp_xmit_unlock(ppp); } while (0)
+
+/*
+ * /dev/ppp device routines.
+ * The /dev/ppp device is used by pppd to control the ppp unit.
+ * It supports the read, write, ioctl and poll functions.
+ * Open instances of /dev/ppp can be in one of three states:
+ * unattached, attached to a ppp unit, or attached to a ppp channel.
+ */
+static int ppp_open(struct inode *inode, struct file *file)
+{
+ /*
+ * This could (should?) be enforced by the permissions on /dev/ppp.
+ */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return 0;
+}
+
+static int ppp_release(struct inode *unused, struct file *file)
+{
+ struct ppp_file *pf = file->private_data;
+ struct ppp *ppp;
+
+ if (pf) {
+ file->private_data = NULL;
+ if (pf->kind == INTERFACE) {
+ ppp = PF_TO_PPP(pf);
+ if (file == ppp->owner)
+ ppp_shutdown_interface(ppp);
+ }
+ if (atomic_dec_and_test(&pf->refcnt)) {
+ switch (pf->kind) {
+ case INTERFACE:
+ ppp_destroy_interface(PF_TO_PPP(pf));
+ break;
+ case CHANNEL:
+ ppp_destroy_channel(PF_TO_CHANNEL(pf));
+ break;
+ }
+ }
+ }
+ return 0;
+}
+
+static ssize_t ppp_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ppp_file *pf = file->private_data;
+ DECLARE_WAITQUEUE(wait, current);
+ ssize_t ret;
+ struct sk_buff *skb = NULL;
+ struct iovec iov;
+
+ ret = count;
+
+ if (!pf)
+ return -ENXIO;
+ add_wait_queue(&pf->rwait, &wait);
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ skb = skb_dequeue(&pf->rq);
+ if (skb)
+ break;
+ ret = 0;
+ if (pf->dead)
+ break;
+ if (pf->kind == INTERFACE) {
+ /*
+ * Return 0 (EOF) on an interface that has no
+ * channels connected, unless it is looping
+ * network traffic (demand mode).
+ */
+ struct ppp *ppp = PF_TO_PPP(pf);
+ if (ppp->n_channels == 0 &&
+ (ppp->flags & SC_LOOP_TRAFFIC) == 0)
+ break;
+ }
+ ret = -EAGAIN;
+ if (file->f_flags & O_NONBLOCK)
+ break;
+ ret = -ERESTARTSYS;
+ if (signal_pending(current))
+ break;
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&pf->rwait, &wait);
+
+ if (!skb)
+ goto out;
+
+ ret = -EOVERFLOW;
+ if (skb->len > count)
+ goto outf;
+ ret = -EFAULT;
+ iov.iov_base = buf;
+ iov.iov_len = count;
+ if (skb_copy_datagram_iovec(skb, 0, &iov, skb->len))
+ goto outf;
+ ret = skb->len;
+
+ outf:
+ kfree_skb(skb);
+ out:
+ return ret;
+}
+
+static ssize_t ppp_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct ppp_file *pf = file->private_data;
+ struct sk_buff *skb;
+ ssize_t ret;
+
+ if (!pf)
+ return -ENXIO;
+ ret = -ENOMEM;
+ skb = alloc_skb(count + pf->hdrlen, GFP_KERNEL);
+ if (!skb)
+ goto out;
+ skb_reserve(skb, pf->hdrlen);
+ ret = -EFAULT;
+ if (copy_from_user(skb_put(skb, count), buf, count)) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ skb_queue_tail(&pf->xq, skb);
+
+ switch (pf->kind) {
+ case INTERFACE:
+ ppp_xmit_process(PF_TO_PPP(pf));
+ break;
+ case CHANNEL:
+ ppp_channel_push(PF_TO_CHANNEL(pf));
+ break;
+ }
+
+ ret = count;
+
+ out:
+ return ret;
+}
+
+/* No kernel lock - fine */
+static unsigned int ppp_poll(struct file *file, poll_table *wait)
+{
+ struct ppp_file *pf = file->private_data;
+ unsigned int mask;
+
+ if (!pf)
+ return 0;
+ poll_wait(file, &pf->rwait, wait);
+ mask = POLLOUT | POLLWRNORM;
+ if (skb_peek(&pf->rq))
+ mask |= POLLIN | POLLRDNORM;
+ if (pf->dead)
+ mask |= POLLHUP;
+ else if (pf->kind == INTERFACE) {
+ /* see comment in ppp_read */
+ struct ppp *ppp = PF_TO_PPP(pf);
+ if (ppp->n_channels == 0 &&
+ (ppp->flags & SC_LOOP_TRAFFIC) == 0)
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ return mask;
+}
+
+#ifdef CONFIG_PPP_FILTER
+static int get_filter(void __user *arg, struct sock_filter **p)
+{
+ struct sock_fprog uprog;
+ struct sock_filter *code = NULL;
+ int len, err;
+
+ if (copy_from_user(&uprog, arg, sizeof(uprog)))
+ return -EFAULT;
+
+ if (!uprog.len) {
+ *p = NULL;
+ return 0;
+ }
+
+ len = uprog.len * sizeof(struct sock_filter);
+ code = memdup_user(uprog.filter, len);
+ if (IS_ERR(code))
+ return PTR_ERR(code);
+
+ err = sk_chk_filter(code, uprog.len);
+ if (err) {
+ kfree(code);
+ return err;
+ }
+
+ *p = code;
+ return uprog.len;
+}
+#endif /* CONFIG_PPP_FILTER */
+
+static long ppp_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct ppp_file *pf = file->private_data;
+ struct ppp *ppp;
+ int err = -EFAULT, val, val2, i;
+ struct ppp_idle idle;
+ struct npioctl npi;
+ int unit, cflags;
+ struct slcompress *vj;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ if (!pf)
+ return ppp_unattached_ioctl(current->nsproxy->net_ns,
+ pf, file, cmd, arg);
+
+ if (cmd == PPPIOCDETACH) {
+ /*
+ * We have to be careful here... if the file descriptor
+ * has been dup'd, we could have another process in the
+ * middle of a poll using the same file *, so we had
+ * better not free the interface data structures -
+ * instead we fail the ioctl. Even in this case, we
+ * shut down the interface if we are the owner of it.
+ * Actually, we should get rid of PPPIOCDETACH, userland
+ * (i.e. pppd) could achieve the same effect by closing
+ * this fd and reopening /dev/ppp.
+ */
+ err = -EINVAL;
+ mutex_lock(&ppp_mutex);
+ if (pf->kind == INTERFACE) {
+ ppp = PF_TO_PPP(pf);
+ if (file == ppp->owner)
+ ppp_shutdown_interface(ppp);
+ }
+ if (atomic_long_read(&file->f_count) <= 2) {
+ ppp_release(NULL, file);
+ err = 0;
+ } else
+ pr_warn("PPPIOCDETACH file->f_count=%ld\n",
+ atomic_long_read(&file->f_count));
+ mutex_unlock(&ppp_mutex);
+ return err;
+ }
+
+ if (pf->kind == CHANNEL) {
+ struct channel *pch;
+ struct ppp_channel *chan;
+
+ mutex_lock(&ppp_mutex);
+ pch = PF_TO_CHANNEL(pf);
+
+ switch (cmd) {
+ case PPPIOCCONNECT:
+ if (get_user(unit, p))
+ break;
+ err = ppp_connect_channel(pch, unit);
+ break;
+
+ case PPPIOCDISCONN:
+ err = ppp_disconnect_channel(pch);
+ break;
+
+ default:
+ down_read(&pch->chan_sem);
+ chan = pch->chan;
+ err = -ENOTTY;
+ if (chan && chan->ops->ioctl)
+ err = chan->ops->ioctl(chan, cmd, arg);
+ up_read(&pch->chan_sem);
+ }
+ mutex_unlock(&ppp_mutex);
+ return err;
+ }
+
+ if (pf->kind != INTERFACE) {
+ /* can't happen */
+ pr_err("PPP: not interface or channel??\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&ppp_mutex);
+ ppp = PF_TO_PPP(pf);
+ switch (cmd) {
+ case PPPIOCSMRU:
+ if (get_user(val, p))
+ break;
+ ppp->mru = val;
+ err = 0;
+ break;
+
+ case PPPIOCSFLAGS:
+ if (get_user(val, p))
+ break;
+ ppp_lock(ppp);
+ cflags = ppp->flags & ~val;
+ ppp->flags = val & SC_FLAG_BITS;
+ ppp_unlock(ppp);
+ if (cflags & SC_CCP_OPEN)
+ ppp_ccp_closed(ppp);
+ err = 0;
+ break;
+
+ case PPPIOCGFLAGS:
+ val = ppp->flags | ppp->xstate | ppp->rstate;
+ if (put_user(val, p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSCOMPRESS:
+ err = ppp_set_compress(ppp, arg);
+ break;
+
+ case PPPIOCGUNIT:
+ if (put_user(ppp->file.index, p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSDEBUG:
+ if (get_user(val, p))
+ break;
+ ppp->debug = val;
+ err = 0;
+ break;
+
+ case PPPIOCGDEBUG:
+ if (put_user(ppp->debug, p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGIDLE:
+ idle.xmit_idle = (jiffies - ppp->last_xmit) / HZ;
+ idle.recv_idle = (jiffies - ppp->last_recv) / HZ;
+ if (copy_to_user(argp, &idle, sizeof(idle)))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSMAXCID:
+ if (get_user(val, p))
+ break;
+ val2 = 15;
+ if ((val >> 16) != 0) {
+ val2 = val >> 16;
+ val &= 0xffff;
+ }
+ vj = slhc_init(val2+1, val+1);
+ if (!vj) {
+ netdev_err(ppp->dev,
+ "PPP: no memory (VJ compressor)\n");
+ err = -ENOMEM;
+ break;
+ }
+ ppp_lock(ppp);
+ if (ppp->vj)
+ slhc_free(ppp->vj);
+ ppp->vj = vj;
+ ppp_unlock(ppp);
+ err = 0;
+ break;
+
+ case PPPIOCGNPMODE:
+ case PPPIOCSNPMODE:
+ if (copy_from_user(&npi, argp, sizeof(npi)))
+ break;
+ err = proto_to_npindex(npi.protocol);
+ if (err < 0)
+ break;
+ i = err;
+ if (cmd == PPPIOCGNPMODE) {
+ err = -EFAULT;
+ npi.mode = ppp->npmode[i];
+ if (copy_to_user(argp, &npi, sizeof(npi)))
+ break;
+ } else {
+ ppp->npmode[i] = npi.mode;
+ /* we may be able to transmit more packets now (??) */
+ netif_wake_queue(ppp->dev);
+ }
+ err = 0;
+ break;
+
+#ifdef CONFIG_PPP_FILTER
+ case PPPIOCSPASS:
+ {
+ struct sock_filter *code;
+ err = get_filter(argp, &code);
+ if (err >= 0) {
+ ppp_lock(ppp);
+ kfree(ppp->pass_filter);
+ ppp->pass_filter = code;
+ ppp->pass_len = err;
+ ppp_unlock(ppp);
+ err = 0;
+ }
+ break;
+ }
+ case PPPIOCSACTIVE:
+ {
+ struct sock_filter *code;
+ err = get_filter(argp, &code);
+ if (err >= 0) {
+ ppp_lock(ppp);
+ kfree(ppp->active_filter);
+ ppp->active_filter = code;
+ ppp->active_len = err;
+ ppp_unlock(ppp);
+ err = 0;
+ }
+ break;
+ }
+#endif /* CONFIG_PPP_FILTER */
+
+#ifdef CONFIG_PPP_MULTILINK
+ case PPPIOCSMRRU:
+ if (get_user(val, p))
+ break;
+ ppp_recv_lock(ppp);
+ ppp->mrru = val;
+ ppp_recv_unlock(ppp);
+ err = 0;
+ break;
+#endif /* CONFIG_PPP_MULTILINK */
+
+ default:
+ err = -ENOTTY;
+ }
+ mutex_unlock(&ppp_mutex);
+ return err;
+}
+
+static int ppp_unattached_ioctl(struct net *net, struct ppp_file *pf,
+ struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int unit, err = -EFAULT;
+ struct ppp *ppp;
+ struct channel *chan;
+ struct ppp_net *pn;
+ int __user *p = (int __user *)arg;
+
+ mutex_lock(&ppp_mutex);
+ switch (cmd) {
+ case PPPIOCNEWUNIT:
+ /* Create a new ppp unit */
+ if (get_user(unit, p))
+ break;
+ ppp = ppp_create_interface(net, unit, &err);
+ if (!ppp)
+ break;
+ file->private_data = &ppp->file;
+ ppp->owner = file;
+ err = -EFAULT;
+ if (put_user(ppp->file.index, p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCATTACH:
+ /* Attach to an existing ppp unit */
+ if (get_user(unit, p))
+ break;
+ err = -ENXIO;
+ pn = ppp_pernet(net);
+ mutex_lock(&pn->all_ppp_mutex);
+ ppp = ppp_find_unit(pn, unit);
+ if (ppp) {
+ atomic_inc(&ppp->file.refcnt);
+ file->private_data = &ppp->file;
+ err = 0;
+ }
+ mutex_unlock(&pn->all_ppp_mutex);
+ break;
+
+ case PPPIOCATTCHAN:
+ if (get_user(unit, p))
+ break;
+ err = -ENXIO;
+ pn = ppp_pernet(net);
+ spin_lock_bh(&pn->all_channels_lock);
+ chan = ppp_find_channel(pn, unit);
+ if (chan) {
+ atomic_inc(&chan->file.refcnt);
+ file->private_data = &chan->file;
+ err = 0;
+ }
+ spin_unlock_bh(&pn->all_channels_lock);
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+ mutex_unlock(&ppp_mutex);
+ return err;
+}
+
+static const struct file_operations ppp_device_fops = {
+ .owner = THIS_MODULE,
+ .read = ppp_read,
+ .write = ppp_write,
+ .poll = ppp_poll,
+ .unlocked_ioctl = ppp_ioctl,
+ .open = ppp_open,
+ .release = ppp_release,
+ .llseek = noop_llseek,
+};
+
+static __net_init int ppp_init_net(struct net *net)
+{
+ struct ppp_net *pn = net_generic(net, ppp_net_id);
+
+ idr_init(&pn->units_idr);
+ mutex_init(&pn->all_ppp_mutex);
+
+ INIT_LIST_HEAD(&pn->all_channels);
+ INIT_LIST_HEAD(&pn->new_channels);
+
+ spin_lock_init(&pn->all_channels_lock);
+
+ return 0;
+}
+
+static __net_exit void ppp_exit_net(struct net *net)
+{
+ struct ppp_net *pn = net_generic(net, ppp_net_id);
+
+ idr_destroy(&pn->units_idr);
+}
+
+static struct pernet_operations ppp_net_ops = {
+ .init = ppp_init_net,
+ .exit = ppp_exit_net,
+ .id = &ppp_net_id,
+ .size = sizeof(struct ppp_net),
+};
+
+#define PPP_MAJOR 108
+
+/* Called at boot time if ppp is compiled into the kernel,
+ or at module load time (from init_module) if compiled as a module. */
+static int __init ppp_init(void)
+{
+ int err;
+
+ pr_info("PPP generic driver version " PPP_VERSION "\n");
+
+ err = register_pernet_device(&ppp_net_ops);
+ if (err) {
+ pr_err("failed to register PPP pernet device (%d)\n", err);
+ goto out;
+ }
+
+ err = register_chrdev(PPP_MAJOR, "ppp", &ppp_device_fops);
+ if (err) {
+ pr_err("failed to register PPP device (%d)\n", err);
+ goto out_net;
+ }
+
+ ppp_class = class_create(THIS_MODULE, "ppp");
+ if (IS_ERR(ppp_class)) {
+ err = PTR_ERR(ppp_class);
+ goto out_chrdev;
+ }
+
+ /* not a big deal if we fail here :-) */
+ device_create(ppp_class, NULL, MKDEV(PPP_MAJOR, 0), NULL, "ppp");
+
+ return 0;
+
+out_chrdev:
+ unregister_chrdev(PPP_MAJOR, "ppp");
+out_net:
+ unregister_pernet_device(&ppp_net_ops);
+out:
+ return err;
+}
+
+/*
+ * Network interface unit routines.
+ */
+static netdev_tx_t
+ppp_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ppp *ppp = netdev_priv(dev);
+ int npi, proto;
+ unsigned char *pp;
+
+ npi = ethertype_to_npindex(ntohs(skb->protocol));
+ if (npi < 0)
+ goto outf;
+
+ /* Drop, accept or reject the packet */
+ switch (ppp->npmode[npi]) {
+ case NPMODE_PASS:
+ break;
+ case NPMODE_QUEUE:
+ /* it would be nice to have a way to tell the network
+ system to queue this one up for later. */
+ goto outf;
+ case NPMODE_DROP:
+ case NPMODE_ERROR:
+ goto outf;
+ }
+
+ /* Put the 2-byte PPP protocol number on the front,
+ making sure there is room for the address and control fields. */
+ if (skb_cow_head(skb, PPP_HDRLEN))
+ goto outf;
+
+ pp = skb_push(skb, 2);
+ proto = npindex_to_proto[npi];
+ put_unaligned_be16(proto, pp);
+
+ netif_stop_queue(dev);
+ skb_queue_tail(&ppp->file.xq, skb);
+ ppp_xmit_process(ppp);
+ return NETDEV_TX_OK;
+
+ outf:
+ kfree_skb(skb);
+ ++dev->stats.tx_dropped;
+ return NETDEV_TX_OK;
+}
+
+static int
+ppp_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ struct ppp *ppp = netdev_priv(dev);
+ int err = -EFAULT;
+ void __user *addr = (void __user *) ifr->ifr_ifru.ifru_data;
+ struct ppp_stats stats;
+ struct ppp_comp_stats cstats;
+ char *vers;
+
+ switch (cmd) {
+ case SIOCGPPPSTATS:
+ ppp_get_stats(ppp, &stats);
+ if (copy_to_user(addr, &stats, sizeof(stats)))
+ break;
+ err = 0;
+ break;
+
+ case SIOCGPPPCSTATS:
+ memset(&cstats, 0, sizeof(cstats));
+ if (ppp->xc_state)
+ ppp->xcomp->comp_stat(ppp->xc_state, &cstats.c);
+ if (ppp->rc_state)
+ ppp->rcomp->decomp_stat(ppp->rc_state, &cstats.d);
+ if (copy_to_user(addr, &cstats, sizeof(cstats)))
+ break;
+ err = 0;
+ break;
+
+ case SIOCGPPPVER:
+ vers = PPP_VERSION;
+ if (copy_to_user(addr, vers, strlen(vers) + 1))
+ break;
+ err = 0;
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static const struct net_device_ops ppp_netdev_ops = {
+ .ndo_start_xmit = ppp_start_xmit,
+ .ndo_do_ioctl = ppp_net_ioctl,
+};
+
+static void ppp_setup(struct net_device *dev)
+{
+ dev->netdev_ops = &ppp_netdev_ops;
+ dev->hard_header_len = PPP_HDRLEN;
+ dev->mtu = PPP_MTU;
+ dev->addr_len = 0;
+ dev->tx_queue_len = 3;
+ dev->type = ARPHRD_PPP;
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
+ dev->features |= NETIF_F_NETNS_LOCAL;
+ dev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
+}
+
+/*
+ * Transmit-side routines.
+ */
+
+/*
+ * Called to do any work queued up on the transmit side
+ * that can now be done.
+ */
+static void
+ppp_xmit_process(struct ppp *ppp)
+{
+ struct sk_buff *skb;
+
+ ppp_xmit_lock(ppp);
+ if (!ppp->closing) {
+ ppp_push(ppp);
+ while (!ppp->xmit_pending &&
+ (skb = skb_dequeue(&ppp->file.xq)))
+ ppp_send_frame(ppp, skb);
+ /* If there's no work left to do, tell the core net
+ code that we can accept some more. */
+ if (!ppp->xmit_pending && !skb_peek(&ppp->file.xq))
+ netif_wake_queue(ppp->dev);
+ }
+ ppp_xmit_unlock(ppp);
+}
+
+static inline struct sk_buff *
+pad_compress_skb(struct ppp *ppp, struct sk_buff *skb)
+{
+ struct sk_buff *new_skb;
+ int len;
+ int new_skb_size = ppp->dev->mtu +
+ ppp->xcomp->comp_extra + ppp->dev->hard_header_len;
+ int compressor_skb_size = ppp->dev->mtu +
+ ppp->xcomp->comp_extra + PPP_HDRLEN;
+ new_skb = alloc_skb(new_skb_size, GFP_ATOMIC);
+ if (!new_skb) {
+ if (net_ratelimit())
+ netdev_err(ppp->dev, "PPP: no memory (comp pkt)\n");
+ return NULL;
+ }
+ if (ppp->dev->hard_header_len > PPP_HDRLEN)
+ skb_reserve(new_skb,
+ ppp->dev->hard_header_len - PPP_HDRLEN);
+
+ /* compressor still expects A/C bytes in hdr */
+ len = ppp->xcomp->compress(ppp->xc_state, skb->data - 2,
+ new_skb->data, skb->len + 2,
+ compressor_skb_size);
+ if (len > 0 && (ppp->flags & SC_CCP_UP)) {
+ kfree_skb(skb);
+ skb = new_skb;
+ skb_put(skb, len);
+ skb_pull(skb, 2); /* pull off A/C bytes */
+ } else if (len == 0) {
+ /* didn't compress, or CCP not up yet */
+ kfree_skb(new_skb);
+ new_skb = skb;
+ } else {
+ /*
+ * (len < 0)
+ * MPPE requires that we do not send unencrypted
+ * frames. The compressor will return -1 if we
+ * should drop the frame. We cannot simply test
+ * the compress_proto because MPPE and MPPC share
+ * the same number.
+ */
+ if (net_ratelimit())
+ netdev_err(ppp->dev, "ppp: compressor dropped pkt\n");
+ kfree_skb(skb);
+ kfree_skb(new_skb);
+ new_skb = NULL;
+ }
+ return new_skb;
+}
+
+/*
+ * Compress and send a frame.
+ * The caller should have locked the xmit path,
+ * and xmit_pending should be 0.
+ */
+static void
+ppp_send_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ int proto = PPP_PROTO(skb);
+ struct sk_buff *new_skb;
+ int len;
+ unsigned char *cp;
+
+ if (proto < 0x8000) {
+#ifdef CONFIG_PPP_FILTER
+ /* check if we should pass this packet */
+ /* the filter instructions are constructed assuming
+ a four-byte PPP header on each packet */
+ *skb_push(skb, 2) = 1;
+ if (ppp->pass_filter &&
+ sk_run_filter(skb, ppp->pass_filter) == 0) {
+ if (ppp->debug & 1)
+ netdev_printk(KERN_DEBUG, ppp->dev,
+ "PPP: outbound frame "
+ "not passed\n");
+ kfree_skb(skb);
+ return;
+ }
+ /* if this packet passes the active filter, record the time */
+ if (!(ppp->active_filter &&
+ sk_run_filter(skb, ppp->active_filter) == 0))
+ ppp->last_xmit = jiffies;
+ skb_pull(skb, 2);
+#else
+ /* for data packets, record the time */
+ ppp->last_xmit = jiffies;
+#endif /* CONFIG_PPP_FILTER */
+ }
+
+ ++ppp->dev->stats.tx_packets;
+ ppp->dev->stats.tx_bytes += skb->len - 2;
+
+ switch (proto) {
+ case PPP_IP:
+ if (!ppp->vj || (ppp->flags & SC_COMP_TCP) == 0)
+ break;
+ /* try to do VJ TCP header compression */
+ new_skb = alloc_skb(skb->len + ppp->dev->hard_header_len - 2,
+ GFP_ATOMIC);
+ if (!new_skb) {
+ netdev_err(ppp->dev, "PPP: no memory (VJ comp pkt)\n");
+ goto drop;
+ }
+ skb_reserve(new_skb, ppp->dev->hard_header_len - 2);
+ cp = skb->data + 2;
+ len = slhc_compress(ppp->vj, cp, skb->len - 2,
+ new_skb->data + 2, &cp,
+ !(ppp->flags & SC_NO_TCP_CCID));
+ if (cp == skb->data + 2) {
+ /* didn't compress */
+ kfree_skb(new_skb);
+ } else {
+ if (cp[0] & SL_TYPE_COMPRESSED_TCP) {
+ proto = PPP_VJC_COMP;
+ cp[0] &= ~SL_TYPE_COMPRESSED_TCP;
+ } else {
+ proto = PPP_VJC_UNCOMP;
+ cp[0] = skb->data[2];
+ }
+ kfree_skb(skb);
+ skb = new_skb;
+ cp = skb_put(skb, len + 2);
+ cp[0] = 0;
+ cp[1] = proto;
+ }
+ break;
+
+ case PPP_CCP:
+ /* peek at outbound CCP frames */
+ ppp_ccp_peek(ppp, skb, 0);
+ break;
+ }
+
+ /* try to do packet compression */
+ if ((ppp->xstate & SC_COMP_RUN) && ppp->xc_state &&
+ proto != PPP_LCP && proto != PPP_CCP) {
+ if (!(ppp->flags & SC_CCP_UP) && (ppp->flags & SC_MUST_COMP)) {
+ if (net_ratelimit())
+ netdev_err(ppp->dev,
+ "ppp: compression required but "
+ "down - pkt dropped.\n");
+ goto drop;
+ }
+ skb = pad_compress_skb(ppp, skb);
+ if (!skb)
+ goto drop;
+ }
+
+ /*
+ * If we are waiting for traffic (demand dialling),
+ * queue it up for pppd to receive.
+ */
+ if (ppp->flags & SC_LOOP_TRAFFIC) {
+ if (ppp->file.rq.qlen > PPP_MAX_RQLEN)
+ goto drop;
+ skb_queue_tail(&ppp->file.rq, skb);
+ wake_up_interruptible(&ppp->file.rwait);
+ return;
+ }
+
+ ppp->xmit_pending = skb;
+ ppp_push(ppp);
+ return;
+
+ drop:
+ kfree_skb(skb);
+ ++ppp->dev->stats.tx_errors;
+}
+
+/*
+ * Try to send the frame in xmit_pending.
+ * The caller should have the xmit path locked.
+ */
+static void
+ppp_push(struct ppp *ppp)
+{
+ struct list_head *list;
+ struct channel *pch;
+ struct sk_buff *skb = ppp->xmit_pending;
+
+ if (!skb)
+ return;
+
+ list = &ppp->channels;
+ if (list_empty(list)) {
+ /* nowhere to send the packet, just drop it */
+ ppp->xmit_pending = NULL;
+ kfree_skb(skb);
+ return;
+ }
+
+ if ((ppp->flags & SC_MULTILINK) == 0) {
+ /* not doing multilink: send it down the first channel */
+ list = list->next;
+ pch = list_entry(list, struct channel, clist);
+
+ spin_lock_bh(&pch->downl);
+ if (pch->chan) {
+ if (pch->chan->ops->start_xmit(pch->chan, skb))
+ ppp->xmit_pending = NULL;
+ } else {
+ /* channel got unregistered */
+ kfree_skb(skb);
+ ppp->xmit_pending = NULL;
+ }
+ spin_unlock_bh(&pch->downl);
+ return;
+ }
+
+#ifdef CONFIG_PPP_MULTILINK
+ /* Multilink: fragment the packet over as many links
+ as can take the packet at the moment. */
+ if (!ppp_mp_explode(ppp, skb))
+ return;
+#endif /* CONFIG_PPP_MULTILINK */
+
+ ppp->xmit_pending = NULL;
+ kfree_skb(skb);
+}
+
+#ifdef CONFIG_PPP_MULTILINK
+static bool mp_protocol_compress __read_mostly = true;
+module_param(mp_protocol_compress, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(mp_protocol_compress,
+ "compress protocol id in multilink fragments");
+
+/*
+ * Divide a packet to be transmitted into fragments and
+ * send them out the individual links.
+ */
+static int ppp_mp_explode(struct ppp *ppp, struct sk_buff *skb)
+{
+ int len, totlen;
+ int i, bits, hdrlen, mtu;
+ int flen;
+ int navail, nfree, nzero;
+ int nbigger;
+ int totspeed;
+ int totfree;
+ unsigned char *p, *q;
+ struct list_head *list;
+ struct channel *pch;
+ struct sk_buff *frag;
+ struct ppp_channel *chan;
+
+ totspeed = 0; /*total bitrate of the bundle*/
+ nfree = 0; /* # channels which have no packet already queued */
+ navail = 0; /* total # of usable channels (not deregistered) */
+ nzero = 0; /* number of channels with zero speed associated*/
+ totfree = 0; /*total # of channels available and
+ *having no queued packets before
+ *starting the fragmentation*/
+
+ hdrlen = (ppp->flags & SC_MP_XSHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN;
+ i = 0;
+ list_for_each_entry(pch, &ppp->channels, clist) {
+ if (pch->chan) {
+ pch->avail = 1;
+ navail++;
+ pch->speed = pch->chan->speed;
+ } else {
+ pch->avail = 0;
+ }
+ if (pch->avail) {
+ if (skb_queue_empty(&pch->file.xq) ||
+ !pch->had_frag) {
+ if (pch->speed == 0)
+ nzero++;
+ else
+ totspeed += pch->speed;
+
+ pch->avail = 2;
+ ++nfree;
+ ++totfree;
+ }
+ if (!pch->had_frag && i < ppp->nxchan)
+ ppp->nxchan = i;
+ }
+ ++i;
+ }
+ /*
+ * Don't start sending this packet unless at least half of
+ * the channels are free. This gives much better TCP
+ * performance if we have a lot of channels.
+ */
+ if (nfree == 0 || nfree < navail / 2)
+ return 0; /* can't take now, leave it in xmit_pending */
+
+ /* Do protocol field compression */
+ p = skb->data;
+ len = skb->len;
+ if (*p == 0 && mp_protocol_compress) {
+ ++p;
+ --len;
+ }
+
+ totlen = len;
+ nbigger = len % nfree;
+
+ /* skip to the channel after the one we last used
+ and start at that one */
+ list = &ppp->channels;
+ for (i = 0; i < ppp->nxchan; ++i) {
+ list = list->next;
+ if (list == &ppp->channels) {
+ i = 0;
+ break;
+ }
+ }
+
+ /* create a fragment for each channel */
+ bits = B;
+ while (len > 0) {
+ list = list->next;
+ if (list == &ppp->channels) {
+ i = 0;
+ continue;
+ }
+ pch = list_entry(list, struct channel, clist);
+ ++i;
+ if (!pch->avail)
+ continue;
+
+ /*
+ * Skip this channel if it has a fragment pending already and
+ * we haven't given a fragment to all of the free channels.
+ */
+ if (pch->avail == 1) {
+ if (nfree > 0)
+ continue;
+ } else {
+ pch->avail = 1;
+ }
+
+ /* check the channel's mtu and whether it is still attached. */
+ spin_lock_bh(&pch->downl);
+ if (pch->chan == NULL) {
+ /* can't use this channel, it's being deregistered */
+ if (pch->speed == 0)
+ nzero--;
+ else
+ totspeed -= pch->speed;
+
+ spin_unlock_bh(&pch->downl);
+ pch->avail = 0;
+ totlen = len;
+ totfree--;
+ nfree--;
+ if (--navail == 0)
+ break;
+ continue;
+ }
+
+ /*
+ *if the channel speed is not set divide
+ *the packet evenly among the free channels;
+ *otherwise divide it according to the speed
+ *of the channel we are going to transmit on
+ */
+ flen = len;
+ if (nfree > 0) {
+ if (pch->speed == 0) {
+ flen = len/nfree;
+ if (nbigger > 0) {
+ flen++;
+ nbigger--;
+ }
+ } else {
+ flen = (((totfree - nzero)*(totlen + hdrlen*totfree)) /
+ ((totspeed*totfree)/pch->speed)) - hdrlen;
+ if (nbigger > 0) {
+ flen += ((totfree - nzero)*pch->speed)/totspeed;
+ nbigger -= ((totfree - nzero)*pch->speed)/
+ totspeed;
+ }
+ }
+ nfree--;
+ }
+
+ /*
+ *check if we are on the last channel or
+ *we exceded the length of the data to
+ *fragment
+ */
+ if ((nfree <= 0) || (flen > len))
+ flen = len;
+ /*
+ *it is not worth to tx on slow channels:
+ *in that case from the resulting flen according to the
+ *above formula will be equal or less than zero.
+ *Skip the channel in this case
+ */
+ if (flen <= 0) {
+ pch->avail = 2;
+ spin_unlock_bh(&pch->downl);
+ continue;
+ }
+
+ mtu = pch->chan->mtu - hdrlen;
+ if (mtu < 4)
+ mtu = 4;
+ if (flen > mtu)
+ flen = mtu;
+ if (flen == len)
+ bits |= E;
+ frag = alloc_skb(flen + hdrlen + (flen == 0), GFP_ATOMIC);
+ if (!frag)
+ goto noskb;
+ q = skb_put(frag, flen + hdrlen);
+
+ /* make the MP header */
+ put_unaligned_be16(PPP_MP, q);
+ if (ppp->flags & SC_MP_XSHORTSEQ) {
+ q[2] = bits + ((ppp->nxseq >> 8) & 0xf);
+ q[3] = ppp->nxseq;
+ } else {
+ q[2] = bits;
+ q[3] = ppp->nxseq >> 16;
+ q[4] = ppp->nxseq >> 8;
+ q[5] = ppp->nxseq;
+ }
+
+ memcpy(q + hdrlen, p, flen);
+
+ /* try to send it down the channel */
+ chan = pch->chan;
+ if (!skb_queue_empty(&pch->file.xq) ||
+ !chan->ops->start_xmit(chan, frag))
+ skb_queue_tail(&pch->file.xq, frag);
+ pch->had_frag = 1;
+ p += flen;
+ len -= flen;
+ ++ppp->nxseq;
+ bits = 0;
+ spin_unlock_bh(&pch->downl);
+ }
+ ppp->nxchan = i;
+
+ return 1;
+
+ noskb:
+ spin_unlock_bh(&pch->downl);
+ if (ppp->debug & 1)
+ netdev_err(ppp->dev, "PPP: no memory (fragment)\n");
+ ++ppp->dev->stats.tx_errors;
+ ++ppp->nxseq;
+ return 1; /* abandon the frame */
+}
+#endif /* CONFIG_PPP_MULTILINK */
+
+/*
+ * Try to send data out on a channel.
+ */
+static void
+ppp_channel_push(struct channel *pch)
+{
+ struct sk_buff *skb;
+ struct ppp *ppp;
+
+ spin_lock_bh(&pch->downl);
+ if (pch->chan) {
+ while (!skb_queue_empty(&pch->file.xq)) {
+ skb = skb_dequeue(&pch->file.xq);
+ if (!pch->chan->ops->start_xmit(pch->chan, skb)) {
+ /* put the packet back and try again later */
+ skb_queue_head(&pch->file.xq, skb);
+ break;
+ }
+ }
+ } else {
+ /* channel got deregistered */
+ skb_queue_purge(&pch->file.xq);
+ }
+ spin_unlock_bh(&pch->downl);
+ /* see if there is anything from the attached unit to be sent */
+ if (skb_queue_empty(&pch->file.xq)) {
+ read_lock_bh(&pch->upl);
+ ppp = pch->ppp;
+ if (ppp)
+ ppp_xmit_process(ppp);
+ read_unlock_bh(&pch->upl);
+ }
+}
+
+/*
+ * Receive-side routines.
+ */
+
+struct ppp_mp_skb_parm {
+ u32 sequence;
+ u8 BEbits;
+};
+#define PPP_MP_CB(skb) ((struct ppp_mp_skb_parm *)((skb)->cb))
+
+static inline void
+ppp_do_recv(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+{
+ ppp_recv_lock(ppp);
+ if (!ppp->closing)
+ ppp_receive_frame(ppp, skb, pch);
+ else
+ kfree_skb(skb);
+ ppp_recv_unlock(ppp);
+}
+
+void
+ppp_input(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct channel *pch = chan->ppp;
+ int proto;
+
+ if (!pch) {
+ kfree_skb(skb);
+ return;
+ }
+
+ read_lock_bh(&pch->upl);
+ if (!pskb_may_pull(skb, 2)) {
+ kfree_skb(skb);
+ if (pch->ppp) {
+ ++pch->ppp->dev->stats.rx_length_errors;
+ ppp_receive_error(pch->ppp);
+ }
+ goto done;
+ }
+
+ proto = PPP_PROTO(skb);
+ if (!pch->ppp || proto >= 0xc000 || proto == PPP_CCPFRAG) {
+ /* put it on the channel queue */
+ skb_queue_tail(&pch->file.rq, skb);
+ /* drop old frames if queue too long */
+ while (pch->file.rq.qlen > PPP_MAX_RQLEN &&
+ (skb = skb_dequeue(&pch->file.rq)))
+ kfree_skb(skb);
+ wake_up_interruptible(&pch->file.rwait);
+ } else {
+ ppp_do_recv(pch->ppp, skb, pch);
+ }
+
+done:
+ read_unlock_bh(&pch->upl);
+}
+
+/* Put a 0-length skb in the receive queue as an error indication */
+void
+ppp_input_error(struct ppp_channel *chan, int code)
+{
+ struct channel *pch = chan->ppp;
+ struct sk_buff *skb;
+
+ if (!pch)
+ return;
+
+ read_lock_bh(&pch->upl);
+ if (pch->ppp) {
+ skb = alloc_skb(0, GFP_ATOMIC);
+ if (skb) {
+ skb->len = 0; /* probably unnecessary */
+ skb->cb[0] = code;
+ ppp_do_recv(pch->ppp, skb, pch);
+ }
+ }
+ read_unlock_bh(&pch->upl);
+}
+
+/*
+ * We come in here to process a received frame.
+ * The receive side of the ppp unit is locked.
+ */
+static void
+ppp_receive_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+{
+ /* note: a 0-length skb is used as an error indication */
+ if (skb->len > 0) {
+#ifdef CONFIG_PPP_MULTILINK
+ /* XXX do channel-level decompression here */
+ if (PPP_PROTO(skb) == PPP_MP)
+ ppp_receive_mp_frame(ppp, skb, pch);
+ else
+#endif /* CONFIG_PPP_MULTILINK */
+ ppp_receive_nonmp_frame(ppp, skb);
+ } else {
+ kfree_skb(skb);
+ ppp_receive_error(ppp);
+ }
+}
+
+static void
+ppp_receive_error(struct ppp *ppp)
+{
+ ++ppp->dev->stats.rx_errors;
+ if (ppp->vj)
+ slhc_toss(ppp->vj);
+}
+
+static void
+ppp_receive_nonmp_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ struct sk_buff *ns;
+ int proto, len, npi;
+
+ /*
+ * Decompress the frame, if compressed.
+ * Note that some decompressors need to see uncompressed frames
+ * that come in as well as compressed frames.
+ */
+ if (ppp->rc_state && (ppp->rstate & SC_DECOMP_RUN) &&
+ (ppp->rstate & (SC_DC_FERROR | SC_DC_ERROR)) == 0)
+ skb = ppp_decompress_frame(ppp, skb);
+
+ if (ppp->flags & SC_MUST_COMP && ppp->rstate & SC_DC_FERROR)
+ goto err;
+
+ proto = PPP_PROTO(skb);
+ switch (proto) {
+ case PPP_VJC_COMP:
+ /* decompress VJ compressed packets */
+ if (!ppp->vj || (ppp->flags & SC_REJ_COMP_TCP))
+ goto err;
+
+ if (skb_tailroom(skb) < 124 || skb_cloned(skb)) {
+ /* copy to a new sk_buff with more tailroom */
+ ns = dev_alloc_skb(skb->len + 128);
+ if (!ns) {
+ netdev_err(ppp->dev, "PPP: no memory "
+ "(VJ decomp)\n");
+ goto err;
+ }
+ skb_reserve(ns, 2);
+ skb_copy_bits(skb, 0, skb_put(ns, skb->len), skb->len);
+ kfree_skb(skb);
+ skb = ns;
+ }
+ else
+ skb->ip_summed = CHECKSUM_NONE;
+
+ len = slhc_uncompress(ppp->vj, skb->data + 2, skb->len - 2);
+ if (len <= 0) {
+ netdev_printk(KERN_DEBUG, ppp->dev,
+ "PPP: VJ decompression error\n");
+ goto err;
+ }
+ len += 2;
+ if (len > skb->len)
+ skb_put(skb, len - skb->len);
+ else if (len < skb->len)
+ skb_trim(skb, len);
+ proto = PPP_IP;
+ break;
+
+ case PPP_VJC_UNCOMP:
+ if (!ppp->vj || (ppp->flags & SC_REJ_COMP_TCP))
+ goto err;
+
+ /* Until we fix the decompressor need to make sure
+ * data portion is linear.
+ */
+ if (!pskb_may_pull(skb, skb->len))
+ goto err;
+
+ if (slhc_remember(ppp->vj, skb->data + 2, skb->len - 2) <= 0) {
+ netdev_err(ppp->dev, "PPP: VJ uncompressed error\n");
+ goto err;
+ }
+ proto = PPP_IP;
+ break;
+
+ case PPP_CCP:
+ ppp_ccp_peek(ppp, skb, 1);
+ break;
+ }
+
+ ++ppp->dev->stats.rx_packets;
+ ppp->dev->stats.rx_bytes += skb->len - 2;
+
+ npi = proto_to_npindex(proto);
+ if (npi < 0) {
+ /* control or unknown frame - pass it to pppd */
+ skb_queue_tail(&ppp->file.rq, skb);
+ /* limit queue length by dropping old frames */
+ while (ppp->file.rq.qlen > PPP_MAX_RQLEN &&
+ (skb = skb_dequeue(&ppp->file.rq)))
+ kfree_skb(skb);
+ /* wake up any process polling or blocking on read */
+ wake_up_interruptible(&ppp->file.rwait);
+
+ } else {
+ /* network protocol frame - give it to the kernel */
+
+#ifdef CONFIG_PPP_FILTER
+ /* check if the packet passes the pass and active filters */
+ /* the filter instructions are constructed assuming
+ a four-byte PPP header on each packet */
+ if (ppp->pass_filter || ppp->active_filter) {
+ if (skb_cloned(skb) &&
+ pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
+ goto err;
+
+ *skb_push(skb, 2) = 0;
+ if (ppp->pass_filter &&
+ sk_run_filter(skb, ppp->pass_filter) == 0) {
+ if (ppp->debug & 1)
+ netdev_printk(KERN_DEBUG, ppp->dev,
+ "PPP: inbound frame "
+ "not passed\n");
+ kfree_skb(skb);
+ return;
+ }
+ if (!(ppp->active_filter &&
+ sk_run_filter(skb, ppp->active_filter) == 0))
+ ppp->last_recv = jiffies;
+ __skb_pull(skb, 2);
+ } else
+#endif /* CONFIG_PPP_FILTER */
+ ppp->last_recv = jiffies;
+
+ if ((ppp->dev->flags & IFF_UP) == 0 ||
+ ppp->npmode[npi] != NPMODE_PASS) {
+ kfree_skb(skb);
+ } else {
+ /* chop off protocol */
+ skb_pull_rcsum(skb, 2);
+ skb->dev = ppp->dev;
+ skb->protocol = htons(npindex_to_ethertype[npi]);
+ skb_reset_mac_header(skb);
+ netif_rx(skb);
+ }
+ }
+ return;
+
+ err:
+ kfree_skb(skb);
+ ppp_receive_error(ppp);
+}
+
+static struct sk_buff *
+ppp_decompress_frame(struct ppp *ppp, struct sk_buff *skb)
+{
+ int proto = PPP_PROTO(skb);
+ struct sk_buff *ns;
+ int len;
+
+ /* Until we fix all the decompressor's need to make sure
+ * data portion is linear.
+ */
+ if (!pskb_may_pull(skb, skb->len))
+ goto err;
+
+ if (proto == PPP_COMP) {
+ int obuff_size;
+
+ switch(ppp->rcomp->compress_proto) {
+ case CI_MPPE:
+ obuff_size = ppp->mru + PPP_HDRLEN + 1;
+ break;
+ default:
+ obuff_size = ppp->mru + PPP_HDRLEN;
+ break;
+ }
+
+ ns = dev_alloc_skb(obuff_size);
+ if (!ns) {
+ netdev_err(ppp->dev, "ppp_decompress_frame: "
+ "no memory\n");
+ goto err;
+ }
+ /* the decompressor still expects the A/C bytes in the hdr */
+ len = ppp->rcomp->decompress(ppp->rc_state, skb->data - 2,
+ skb->len + 2, ns->data, obuff_size);
+ if (len < 0) {
+ /* Pass the compressed frame to pppd as an
+ error indication. */
+ if (len == DECOMP_FATALERROR)
+ ppp->rstate |= SC_DC_FERROR;
+ kfree_skb(ns);
+ goto err;
+ }
+
+ kfree_skb(skb);
+ skb = ns;
+ skb_put(skb, len);
+ skb_pull(skb, 2); /* pull off the A/C bytes */
+
+ } else {
+ /* Uncompressed frame - pass to decompressor so it
+ can update its dictionary if necessary. */
+ if (ppp->rcomp->incomp)
+ ppp->rcomp->incomp(ppp->rc_state, skb->data - 2,
+ skb->len + 2);
+ }
+
+ return skb;
+
+ err:
+ ppp->rstate |= SC_DC_ERROR;
+ ppp_receive_error(ppp);
+ return skb;
+}
+
+#ifdef CONFIG_PPP_MULTILINK
+/*
+ * Receive a multilink frame.
+ * We put it on the reconstruction queue and then pull off
+ * as many completed frames as we can.
+ */
+static void
+ppp_receive_mp_frame(struct ppp *ppp, struct sk_buff *skb, struct channel *pch)
+{
+ u32 mask, seq;
+ struct channel *ch;
+ int mphdrlen = (ppp->flags & SC_MP_SHORTSEQ)? MPHDRLEN_SSN: MPHDRLEN;
+
+ if (!pskb_may_pull(skb, mphdrlen + 1) || ppp->mrru == 0)
+ goto err; /* no good, throw it away */
+
+ /* Decode sequence number and begin/end bits */
+ if (ppp->flags & SC_MP_SHORTSEQ) {
+ seq = ((skb->data[2] & 0x0f) << 8) | skb->data[3];
+ mask = 0xfff;
+ } else {
+ seq = (skb->data[3] << 16) | (skb->data[4] << 8)| skb->data[5];
+ mask = 0xffffff;
+ }
+ PPP_MP_CB(skb)->BEbits = skb->data[2];
+ skb_pull(skb, mphdrlen); /* pull off PPP and MP headers */
+
+ /*
+ * Do protocol ID decompression on the first fragment of each packet.
+ */
+ if ((PPP_MP_CB(skb)->BEbits & B) && (skb->data[0] & 1))
+ *skb_push(skb, 1) = 0;
+
+ /*
+ * Expand sequence number to 32 bits, making it as close
+ * as possible to ppp->minseq.
+ */
+ seq |= ppp->minseq & ~mask;
+ if ((int)(ppp->minseq - seq) > (int)(mask >> 1))
+ seq += mask + 1;
+ else if ((int)(seq - ppp->minseq) > (int)(mask >> 1))
+ seq -= mask + 1; /* should never happen */
+ PPP_MP_CB(skb)->sequence = seq;
+ pch->lastseq = seq;
+
+ /*
+ * If this packet comes before the next one we were expecting,
+ * drop it.
+ */
+ if (seq_before(seq, ppp->nextseq)) {
+ kfree_skb(skb);
+ ++ppp->dev->stats.rx_dropped;
+ ppp_receive_error(ppp);
+ return;
+ }
+
+ /*
+ * Reevaluate minseq, the minimum over all channels of the
+ * last sequence number received on each channel. Because of
+ * the increasing sequence number rule, we know that any fragment
+ * before `minseq' which hasn't arrived is never going to arrive.
+ * The list of channels can't change because we have the receive
+ * side of the ppp unit locked.
+ */
+ list_for_each_entry(ch, &ppp->channels, clist) {
+ if (seq_before(ch->lastseq, seq))
+ seq = ch->lastseq;
+ }
+ if (seq_before(ppp->minseq, seq))
+ ppp->minseq = seq;
+
+ /* Put the fragment on the reconstruction queue */
+ ppp_mp_insert(ppp, skb);
+
+ /* If the queue is getting long, don't wait any longer for packets
+ before the start of the queue. */
+ if (skb_queue_len(&ppp->mrq) >= PPP_MP_MAX_QLEN) {
+ struct sk_buff *mskb = skb_peek(&ppp->mrq);
+ if (seq_before(ppp->minseq, PPP_MP_CB(mskb)->sequence))
+ ppp->minseq = PPP_MP_CB(mskb)->sequence;
+ }
+
+ /* Pull completed packets off the queue and receive them. */
+ while ((skb = ppp_mp_reconstruct(ppp))) {
+ if (pskb_may_pull(skb, 2))
+ ppp_receive_nonmp_frame(ppp, skb);
+ else {
+ ++ppp->dev->stats.rx_length_errors;
+ kfree_skb(skb);
+ ppp_receive_error(ppp);
+ }
+ }
+
+ return;
+
+ err:
+ kfree_skb(skb);
+ ppp_receive_error(ppp);
+}
+
+/*
+ * Insert a fragment on the MP reconstruction queue.
+ * The queue is ordered by increasing sequence number.
+ */
+static void
+ppp_mp_insert(struct ppp *ppp, struct sk_buff *skb)
+{
+ struct sk_buff *p;
+ struct sk_buff_head *list = &ppp->mrq;
+ u32 seq = PPP_MP_CB(skb)->sequence;
+
+ /* N.B. we don't need to lock the list lock because we have the
+ ppp unit receive-side lock. */
+ skb_queue_walk(list, p) {
+ if (seq_before(seq, PPP_MP_CB(p)->sequence))
+ break;
+ }
+ __skb_queue_before(list, p, skb);
+}
+
+/*
+ * Reconstruct a packet from the MP fragment queue.
+ * We go through increasing sequence numbers until we find a
+ * complete packet, or we get to the sequence number for a fragment
+ * which hasn't arrived but might still do so.
+ */
+static struct sk_buff *
+ppp_mp_reconstruct(struct ppp *ppp)
+{
+ u32 seq = ppp->nextseq;
+ u32 minseq = ppp->minseq;
+ struct sk_buff_head *list = &ppp->mrq;
+ struct sk_buff *p, *tmp;
+ struct sk_buff *head, *tail;
+ struct sk_buff *skb = NULL;
+ int lost = 0, len = 0;
+
+ if (ppp->mrru == 0) /* do nothing until mrru is set */
+ return NULL;
+ head = list->next;
+ tail = NULL;
+ skb_queue_walk_safe(list, p, tmp) {
+ again:
+ if (seq_before(PPP_MP_CB(p)->sequence, seq)) {
+ /* this can't happen, anyway ignore the skb */
+ netdev_err(ppp->dev, "ppp_mp_reconstruct bad "
+ "seq %u < %u\n",
+ PPP_MP_CB(p)->sequence, seq);
+ __skb_unlink(p, list);
+ kfree_skb(p);
+ continue;
+ }
+ if (PPP_MP_CB(p)->sequence != seq) {
+ /* Fragment `seq' is missing. If it is after
+ minseq, it might arrive later, so stop here. */
+ if (seq_after(seq, minseq))
+ break;
+ /* Fragment `seq' is lost, keep going. */
+ lost = 1;
+ seq = seq_before(minseq, PPP_MP_CB(p)->sequence)?
+ minseq + 1: PPP_MP_CB(p)->sequence;
+ goto again;
+ }
+
+ /*
+ * At this point we know that all the fragments from
+ * ppp->nextseq to seq are either present or lost.
+ * Also, there are no complete packets in the queue
+ * that have no missing fragments and end before this
+ * fragment.
+ */
+
+ /* B bit set indicates this fragment starts a packet */
+ if (PPP_MP_CB(p)->BEbits & B) {
+ head = p;
+ lost = 0;
+ len = 0;
+ }
+
+ len += p->len;
+
+ /* Got a complete packet yet? */
+ if (lost == 0 && (PPP_MP_CB(p)->BEbits & E) &&
+ (PPP_MP_CB(head)->BEbits & B)) {
+ if (len > ppp->mrru + 2) {
+ ++ppp->dev->stats.rx_length_errors;
+ netdev_printk(KERN_DEBUG, ppp->dev,
+ "PPP: reconstructed packet"
+ " is too long (%d)\n", len);
+ } else {
+ tail = p;
+ break;
+ }
+ ppp->nextseq = seq + 1;
+ }
+
+ /*
+ * If this is the ending fragment of a packet,
+ * and we haven't found a complete valid packet yet,
+ * we can discard up to and including this fragment.
+ */
+ if (PPP_MP_CB(p)->BEbits & E) {
+ struct sk_buff *tmp2;
+
+ skb_queue_reverse_walk_from_safe(list, p, tmp2) {
+ __skb_unlink(p, list);
+ kfree_skb(p);
+ }
+ head = skb_peek(list);
+ if (!head)
+ break;
+ }
+ ++seq;
+ }
+
+ /* If we have a complete packet, copy it all into one skb. */
+ if (tail != NULL) {
+ /* If we have discarded any fragments,
+ signal a receive error. */
+ if (PPP_MP_CB(head)->sequence != ppp->nextseq) {
+ if (ppp->debug & 1)
+ netdev_printk(KERN_DEBUG, ppp->dev,
+ " missed pkts %u..%u\n",
+ ppp->nextseq,
+ PPP_MP_CB(head)->sequence-1);
+ ++ppp->dev->stats.rx_dropped;
+ ppp_receive_error(ppp);
+ }
+
+ skb = head;
+ if (head != tail) {
+ struct sk_buff **fragpp = &skb_shinfo(skb)->frag_list;
+ p = skb_queue_next(list, head);
+ __skb_unlink(skb, list);
+ skb_queue_walk_from_safe(list, p, tmp) {
+ __skb_unlink(p, list);
+ *fragpp = p;
+ p->next = NULL;
+ fragpp = &p->next;
+
+ skb->len += p->len;
+ skb->data_len += p->len;
+ skb->truesize += p->len;
+
+ if (p == tail)
+ break;
+ }
+ } else {
+ __skb_unlink(skb, list);
+ }
+
+ ppp->nextseq = PPP_MP_CB(tail)->sequence + 1;
+ }
+
+ return skb;
+}
+#endif /* CONFIG_PPP_MULTILINK */
+
+/*
+ * Channel interface.
+ */
+
+/* Create a new, unattached ppp channel. */
+int ppp_register_channel(struct ppp_channel *chan)
+{
+ return ppp_register_net_channel(current->nsproxy->net_ns, chan);
+}
+
+/* Create a new, unattached ppp channel for specified net. */
+int ppp_register_net_channel(struct net *net, struct ppp_channel *chan)
+{
+ struct channel *pch;
+ struct ppp_net *pn;
+
+ pch = kzalloc(sizeof(struct channel), GFP_KERNEL);
+ if (!pch)
+ return -ENOMEM;
+
+ pn = ppp_pernet(net);
+
+ pch->ppp = NULL;
+ pch->chan = chan;
+ pch->chan_net = net;
+ chan->ppp = pch;
+ init_ppp_file(&pch->file, CHANNEL);
+ pch->file.hdrlen = chan->hdrlen;
+#ifdef CONFIG_PPP_MULTILINK
+ pch->lastseq = -1;
+#endif /* CONFIG_PPP_MULTILINK */
+ init_rwsem(&pch->chan_sem);
+ spin_lock_init(&pch->downl);
+ rwlock_init(&pch->upl);
+
+ spin_lock_bh(&pn->all_channels_lock);
+ pch->file.index = ++pn->last_channel_index;
+ list_add(&pch->list, &pn->new_channels);
+ atomic_inc(&channel_count);
+ spin_unlock_bh(&pn->all_channels_lock);
+
+ return 0;
+}
+
+/*
+ * Return the index of a channel.
+ */
+int ppp_channel_index(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+
+ if (pch)
+ return pch->file.index;
+ return -1;
+}
+
+/*
+ * Return the PPP unit number to which a channel is connected.
+ */
+int ppp_unit_number(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+ int unit = -1;
+
+ if (pch) {
+ read_lock_bh(&pch->upl);
+ if (pch->ppp)
+ unit = pch->ppp->file.index;
+ read_unlock_bh(&pch->upl);
+ }
+ return unit;
+}
+
+/*
+ * Return the PPP device interface name of a channel.
+ */
+char *ppp_dev_name(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+ char *name = NULL;
+
+ if (pch) {
+ read_lock_bh(&pch->upl);
+ if (pch->ppp && pch->ppp->dev)
+ name = pch->ppp->dev->name;
+ read_unlock_bh(&pch->upl);
+ }
+ return name;
+}
+
+
+/*
+ * Disconnect a channel from the generic layer.
+ * This must be called in process context.
+ */
+void
+ppp_unregister_channel(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+ struct ppp_net *pn;
+
+ if (!pch)
+ return; /* should never happen */
+
+ chan->ppp = NULL;
+
+ /*
+ * This ensures that we have returned from any calls into the
+ * the channel's start_xmit or ioctl routine before we proceed.
+ */
+ down_write(&pch->chan_sem);
+ spin_lock_bh(&pch->downl);
+ pch->chan = NULL;
+ spin_unlock_bh(&pch->downl);
+ up_write(&pch->chan_sem);
+ ppp_disconnect_channel(pch);
+
+ pn = ppp_pernet(pch->chan_net);
+ spin_lock_bh(&pn->all_channels_lock);
+ list_del(&pch->list);
+ spin_unlock_bh(&pn->all_channels_lock);
+
+ pch->file.dead = 1;
+ wake_up_interruptible(&pch->file.rwait);
+ if (atomic_dec_and_test(&pch->file.refcnt))
+ ppp_destroy_channel(pch);
+}
+
+/*
+ * Callback from a channel when it can accept more to transmit.
+ * This should be called at BH/softirq level, not interrupt level.
+ */
+void
+ppp_output_wakeup(struct ppp_channel *chan)
+{
+ struct channel *pch = chan->ppp;
+
+ if (!pch)
+ return;
+ ppp_channel_push(pch);
+}
+
+/*
+ * Compression control.
+ */
+
+/* Process the PPPIOCSCOMPRESS ioctl. */
+static int
+ppp_set_compress(struct ppp *ppp, unsigned long arg)
+{
+ int err;
+ struct compressor *cp, *ocomp;
+ struct ppp_option_data data;
+ void *state, *ostate;
+ unsigned char ccp_option[CCP_MAX_OPTION_LENGTH];
+
+ err = -EFAULT;
+ if (copy_from_user(&data, (void __user *) arg, sizeof(data)) ||
+ (data.length <= CCP_MAX_OPTION_LENGTH &&
+ copy_from_user(ccp_option, (void __user *) data.ptr, data.length)))
+ goto out;
+ err = -EINVAL;
+ if (data.length > CCP_MAX_OPTION_LENGTH ||
+ ccp_option[1] < 2 || ccp_option[1] > data.length)
+ goto out;
+
+ cp = try_then_request_module(
+ find_compressor(ccp_option[0]),
+ "ppp-compress-%d", ccp_option[0]);
+ if (!cp)
+ goto out;
+
+ err = -ENOBUFS;
+ if (data.transmit) {
+ state = cp->comp_alloc(ccp_option, data.length);
+ if (state) {
+ ppp_xmit_lock(ppp);
+ ppp->xstate &= ~SC_COMP_RUN;
+ ocomp = ppp->xcomp;
+ ostate = ppp->xc_state;
+ ppp->xcomp = cp;
+ ppp->xc_state = state;
+ ppp_xmit_unlock(ppp);
+ if (ostate) {
+ ocomp->comp_free(ostate);
+ module_put(ocomp->owner);
+ }
+ err = 0;
+ } else
+ module_put(cp->owner);
+
+ } else {
+ state = cp->decomp_alloc(ccp_option, data.length);
+ if (state) {
+ ppp_recv_lock(ppp);
+ ppp->rstate &= ~SC_DECOMP_RUN;
+ ocomp = ppp->rcomp;
+ ostate = ppp->rc_state;
+ ppp->rcomp = cp;
+ ppp->rc_state = state;
+ ppp_recv_unlock(ppp);
+ if (ostate) {
+ ocomp->decomp_free(ostate);
+ module_put(ocomp->owner);
+ }
+ err = 0;
+ } else
+ module_put(cp->owner);
+ }
+
+ out:
+ return err;
+}
+
+/*
+ * Look at a CCP packet and update our state accordingly.
+ * We assume the caller has the xmit or recv path locked.
+ */
+static void
+ppp_ccp_peek(struct ppp *ppp, struct sk_buff *skb, int inbound)
+{
+ unsigned char *dp;
+ int len;
+
+ if (!pskb_may_pull(skb, CCP_HDRLEN + 2))
+ return; /* no header */
+ dp = skb->data + 2;
+
+ switch (CCP_CODE(dp)) {
+ case CCP_CONFREQ:
+
+ /* A ConfReq starts negotiation of compression
+ * in one direction of transmission,
+ * and hence brings it down...but which way?
+ *
+ * Remember:
+ * A ConfReq indicates what the sender would like to receive
+ */
+ if(inbound)
+ /* He is proposing what I should send */
+ ppp->xstate &= ~SC_COMP_RUN;
+ else
+ /* I am proposing to what he should send */
+ ppp->rstate &= ~SC_DECOMP_RUN;
+
+ break;
+
+ case CCP_TERMREQ:
+ case CCP_TERMACK:
+ /*
+ * CCP is going down, both directions of transmission
+ */
+ ppp->rstate &= ~SC_DECOMP_RUN;
+ ppp->xstate &= ~SC_COMP_RUN;
+ break;
+
+ case CCP_CONFACK:
+ if ((ppp->flags & (SC_CCP_OPEN | SC_CCP_UP)) != SC_CCP_OPEN)
+ break;
+ len = CCP_LENGTH(dp);
+ if (!pskb_may_pull(skb, len + 2))
+ return; /* too short */
+ dp += CCP_HDRLEN;
+ len -= CCP_HDRLEN;
+ if (len < CCP_OPT_MINLEN || len < CCP_OPT_LENGTH(dp))
+ break;
+ if (inbound) {
+ /* we will start receiving compressed packets */
+ if (!ppp->rc_state)
+ break;
+ if (ppp->rcomp->decomp_init(ppp->rc_state, dp, len,
+ ppp->file.index, 0, ppp->mru, ppp->debug)) {
+ ppp->rstate |= SC_DECOMP_RUN;
+ ppp->rstate &= ~(SC_DC_ERROR | SC_DC_FERROR);
+ }
+ } else {
+ /* we will soon start sending compressed packets */
+ if (!ppp->xc_state)
+ break;
+ if (ppp->xcomp->comp_init(ppp->xc_state, dp, len,
+ ppp->file.index, 0, ppp->debug))
+ ppp->xstate |= SC_COMP_RUN;
+ }
+ break;
+
+ case CCP_RESETACK:
+ /* reset the [de]compressor */
+ if ((ppp->flags & SC_CCP_UP) == 0)
+ break;
+ if (inbound) {
+ if (ppp->rc_state && (ppp->rstate & SC_DECOMP_RUN)) {
+ ppp->rcomp->decomp_reset(ppp->rc_state);
+ ppp->rstate &= ~SC_DC_ERROR;
+ }
+ } else {
+ if (ppp->xc_state && (ppp->xstate & SC_COMP_RUN))
+ ppp->xcomp->comp_reset(ppp->xc_state);
+ }
+ break;
+ }
+}
+
+/* Free up compression resources. */
+static void
+ppp_ccp_closed(struct ppp *ppp)
+{
+ void *xstate, *rstate;
+ struct compressor *xcomp, *rcomp;
+
+ ppp_lock(ppp);
+ ppp->flags &= ~(SC_CCP_OPEN | SC_CCP_UP);
+ ppp->xstate = 0;
+ xcomp = ppp->xcomp;
+ xstate = ppp->xc_state;
+ ppp->xc_state = NULL;
+ ppp->rstate = 0;
+ rcomp = ppp->rcomp;
+ rstate = ppp->rc_state;
+ ppp->rc_state = NULL;
+ ppp_unlock(ppp);
+
+ if (xstate) {
+ xcomp->comp_free(xstate);
+ module_put(xcomp->owner);
+ }
+ if (rstate) {
+ rcomp->decomp_free(rstate);
+ module_put(rcomp->owner);
+ }
+}
+
+/* List of compressors. */
+static LIST_HEAD(compressor_list);
+static DEFINE_SPINLOCK(compressor_list_lock);
+
+struct compressor_entry {
+ struct list_head list;
+ struct compressor *comp;
+};
+
+static struct compressor_entry *
+find_comp_entry(int proto)
+{
+ struct compressor_entry *ce;
+
+ list_for_each_entry(ce, &compressor_list, list) {
+ if (ce->comp->compress_proto == proto)
+ return ce;
+ }
+ return NULL;
+}
+
+/* Register a compressor */
+int
+ppp_register_compressor(struct compressor *cp)
+{
+ struct compressor_entry *ce;
+ int ret;
+ spin_lock(&compressor_list_lock);
+ ret = -EEXIST;
+ if (find_comp_entry(cp->compress_proto))
+ goto out;
+ ret = -ENOMEM;
+ ce = kmalloc(sizeof(struct compressor_entry), GFP_ATOMIC);
+ if (!ce)
+ goto out;
+ ret = 0;
+ ce->comp = cp;
+ list_add(&ce->list, &compressor_list);
+ out:
+ spin_unlock(&compressor_list_lock);
+ return ret;
+}
+
+/* Unregister a compressor */
+void
+ppp_unregister_compressor(struct compressor *cp)
+{
+ struct compressor_entry *ce;
+
+ spin_lock(&compressor_list_lock);
+ ce = find_comp_entry(cp->compress_proto);
+ if (ce && ce->comp == cp) {
+ list_del(&ce->list);
+ kfree(ce);
+ }
+ spin_unlock(&compressor_list_lock);
+}
+
+/* Find a compressor. */
+static struct compressor *
+find_compressor(int type)
+{
+ struct compressor_entry *ce;
+ struct compressor *cp = NULL;
+
+ spin_lock(&compressor_list_lock);
+ ce = find_comp_entry(type);
+ if (ce) {
+ cp = ce->comp;
+ if (!try_module_get(cp->owner))
+ cp = NULL;
+ }
+ spin_unlock(&compressor_list_lock);
+ return cp;
+}
+
+/*
+ * Miscelleneous stuff.
+ */
+
+static void
+ppp_get_stats(struct ppp *ppp, struct ppp_stats *st)
+{
+ struct slcompress *vj = ppp->vj;
+
+ memset(st, 0, sizeof(*st));
+ st->p.ppp_ipackets = ppp->dev->stats.rx_packets;
+ st->p.ppp_ierrors = ppp->dev->stats.rx_errors;
+ st->p.ppp_ibytes = ppp->dev->stats.rx_bytes;
+ st->p.ppp_opackets = ppp->dev->stats.tx_packets;
+ st->p.ppp_oerrors = ppp->dev->stats.tx_errors;
+ st->p.ppp_obytes = ppp->dev->stats.tx_bytes;
+ if (!vj)
+ return;
+ st->vj.vjs_packets = vj->sls_o_compressed + vj->sls_o_uncompressed;
+ st->vj.vjs_compressed = vj->sls_o_compressed;
+ st->vj.vjs_searches = vj->sls_o_searches;
+ st->vj.vjs_misses = vj->sls_o_misses;
+ st->vj.vjs_errorin = vj->sls_i_error;
+ st->vj.vjs_tossed = vj->sls_i_tossed;
+ st->vj.vjs_uncompressedin = vj->sls_i_uncompressed;
+ st->vj.vjs_compressedin = vj->sls_i_compressed;
+}
+
+/*
+ * Stuff for handling the lists of ppp units and channels
+ * and for initialization.
+ */
+
+/*
+ * Create a new ppp interface unit. Fails if it can't allocate memory
+ * or if there is already a unit with the requested number.
+ * unit == -1 means allocate a new number.
+ */
+static struct ppp *
+ppp_create_interface(struct net *net, int unit, int *retp)
+{
+ struct ppp *ppp;
+ struct ppp_net *pn;
+ struct net_device *dev = NULL;
+ int ret = -ENOMEM;
+ int i;
+
+ dev = alloc_netdev(sizeof(struct ppp), "", ppp_setup);
+ if (!dev)
+ goto out1;
+
+ pn = ppp_pernet(net);
+
+ ppp = netdev_priv(dev);
+ ppp->dev = dev;
+ ppp->mru = PPP_MRU;
+ init_ppp_file(&ppp->file, INTERFACE);
+ ppp->file.hdrlen = PPP_HDRLEN - 2; /* don't count proto bytes */
+ for (i = 0; i < NUM_NP; ++i)
+ ppp->npmode[i] = NPMODE_PASS;
+ INIT_LIST_HEAD(&ppp->channels);
+ spin_lock_init(&ppp->rlock);
+ spin_lock_init(&ppp->wlock);
+#ifdef CONFIG_PPP_MULTILINK
+ ppp->minseq = -1;
+ skb_queue_head_init(&ppp->mrq);
+#endif /* CONFIG_PPP_MULTILINK */
+
+ /*
+ * drum roll: don't forget to set
+ * the net device is belong to
+ */
+ dev_net_set(dev, net);
+
+ mutex_lock(&pn->all_ppp_mutex);
+
+ if (unit < 0) {
+ unit = unit_get(&pn->units_idr, ppp);
+ if (unit < 0) {
+ ret = unit;
+ goto out2;
+ }
+ } else {
+ ret = -EEXIST;
+ if (unit_find(&pn->units_idr, unit))
+ goto out2; /* unit already exists */
+ /*
+ * if caller need a specified unit number
+ * lets try to satisfy him, otherwise --
+ * he should better ask us for new unit number
+ *
+ * NOTE: yes I know that returning EEXIST it's not
+ * fair but at least pppd will ask us to allocate
+ * new unit in this case so user is happy :)
+ */
+ unit = unit_set(&pn->units_idr, ppp, unit);
+ if (unit < 0)
+ goto out2;
+ }
+
+ /* Initialize the new ppp unit */
+ ppp->file.index = unit;
+ sprintf(dev->name, "ppp%d", unit);
+
+ ret = register_netdev(dev);
+ if (ret != 0) {
+ unit_put(&pn->units_idr, unit);
+ netdev_err(ppp->dev, "PPP: couldn't register device %s (%d)\n",
+ dev->name, ret);
+ goto out2;
+ }
+
+ ppp->ppp_net = net;
+
+ atomic_inc(&ppp_unit_count);
+ mutex_unlock(&pn->all_ppp_mutex);
+
+ *retp = 0;
+ return ppp;
+
+out2:
+ mutex_unlock(&pn->all_ppp_mutex);
+ free_netdev(dev);
+out1:
+ *retp = ret;
+ return NULL;
+}
+
+/*
+ * Initialize a ppp_file structure.
+ */
+static void
+init_ppp_file(struct ppp_file *pf, int kind)
+{
+ pf->kind = kind;
+ skb_queue_head_init(&pf->xq);
+ skb_queue_head_init(&pf->rq);
+ atomic_set(&pf->refcnt, 1);
+ init_waitqueue_head(&pf->rwait);
+}
+
+/*
+ * Take down a ppp interface unit - called when the owning file
+ * (the one that created the unit) is closed or detached.
+ */
+static void ppp_shutdown_interface(struct ppp *ppp)
+{
+ struct ppp_net *pn;
+
+ pn = ppp_pernet(ppp->ppp_net);
+ mutex_lock(&pn->all_ppp_mutex);
+
+ /* This will call dev_close() for us. */
+ ppp_lock(ppp);
+ if (!ppp->closing) {
+ ppp->closing = 1;
+ ppp_unlock(ppp);
+ unregister_netdev(ppp->dev);
+ unit_put(&pn->units_idr, ppp->file.index);
+ } else
+ ppp_unlock(ppp);
+
+ ppp->file.dead = 1;
+ ppp->owner = NULL;
+ wake_up_interruptible(&ppp->file.rwait);
+
+ mutex_unlock(&pn->all_ppp_mutex);
+}
+
+/*
+ * Free the memory used by a ppp unit. This is only called once
+ * there are no channels connected to the unit and no file structs
+ * that reference the unit.
+ */
+static void ppp_destroy_interface(struct ppp *ppp)
+{
+ atomic_dec(&ppp_unit_count);
+
+ if (!ppp->file.dead || ppp->n_channels) {
+ /* "can't happen" */
+ netdev_err(ppp->dev, "ppp: destroying ppp struct %p "
+ "but dead=%d n_channels=%d !\n",
+ ppp, ppp->file.dead, ppp->n_channels);
+ return;
+ }
+
+ ppp_ccp_closed(ppp);
+ if (ppp->vj) {
+ slhc_free(ppp->vj);
+ ppp->vj = NULL;
+ }
+ skb_queue_purge(&ppp->file.xq);
+ skb_queue_purge(&ppp->file.rq);
+#ifdef CONFIG_PPP_MULTILINK
+ skb_queue_purge(&ppp->mrq);
+#endif /* CONFIG_PPP_MULTILINK */
+#ifdef CONFIG_PPP_FILTER
+ kfree(ppp->pass_filter);
+ ppp->pass_filter = NULL;
+ kfree(ppp->active_filter);
+ ppp->active_filter = NULL;
+#endif /* CONFIG_PPP_FILTER */
+
+ kfree_skb(ppp->xmit_pending);
+
+ free_netdev(ppp->dev);
+}
+
+/*
+ * Locate an existing ppp unit.
+ * The caller should have locked the all_ppp_mutex.
+ */
+static struct ppp *
+ppp_find_unit(struct ppp_net *pn, int unit)
+{
+ return unit_find(&pn->units_idr, unit);
+}
+
+/*
+ * Locate an existing ppp channel.
+ * The caller should have locked the all_channels_lock.
+ * First we look in the new_channels list, then in the
+ * all_channels list. If found in the new_channels list,
+ * we move it to the all_channels list. This is for speed
+ * when we have a lot of channels in use.
+ */
+static struct channel *
+ppp_find_channel(struct ppp_net *pn, int unit)
+{
+ struct channel *pch;
+
+ list_for_each_entry(pch, &pn->new_channels, list) {
+ if (pch->file.index == unit) {
+ list_move(&pch->list, &pn->all_channels);
+ return pch;
+ }
+ }
+
+ list_for_each_entry(pch, &pn->all_channels, list) {
+ if (pch->file.index == unit)
+ return pch;
+ }
+
+ return NULL;
+}
+
+/*
+ * Connect a PPP channel to a PPP interface unit.
+ */
+static int
+ppp_connect_channel(struct channel *pch, int unit)
+{
+ struct ppp *ppp;
+ struct ppp_net *pn;
+ int ret = -ENXIO;
+ int hdrlen;
+
+ pn = ppp_pernet(pch->chan_net);
+
+ mutex_lock(&pn->all_ppp_mutex);
+ ppp = ppp_find_unit(pn, unit);
+ if (!ppp)
+ goto out;
+ write_lock_bh(&pch->upl);
+ ret = -EINVAL;
+ if (pch->ppp)
+ goto outl;
+
+ ppp_lock(ppp);
+ if (pch->file.hdrlen > ppp->file.hdrlen)
+ ppp->file.hdrlen = pch->file.hdrlen;
+ hdrlen = pch->file.hdrlen + 2; /* for protocol bytes */
+ if (hdrlen > ppp->dev->hard_header_len)
+ ppp->dev->hard_header_len = hdrlen;
+ list_add_tail(&pch->clist, &ppp->channels);
+ ++ppp->n_channels;
+ pch->ppp = ppp;
+ atomic_inc(&ppp->file.refcnt);
+ ppp_unlock(ppp);
+ ret = 0;
+
+ outl:
+ write_unlock_bh(&pch->upl);
+ out:
+ mutex_unlock(&pn->all_ppp_mutex);
+ return ret;
+}
+
+/*
+ * Disconnect a channel from its ppp unit.
+ */
+static int
+ppp_disconnect_channel(struct channel *pch)
+{
+ struct ppp *ppp;
+ int err = -EINVAL;
+
+ write_lock_bh(&pch->upl);
+ ppp = pch->ppp;
+ pch->ppp = NULL;
+ write_unlock_bh(&pch->upl);
+ if (ppp) {
+ /* remove it from the ppp unit's list */
+ ppp_lock(ppp);
+ list_del(&pch->clist);
+ if (--ppp->n_channels == 0)
+ wake_up_interruptible(&ppp->file.rwait);
+ ppp_unlock(ppp);
+ if (atomic_dec_and_test(&ppp->file.refcnt))
+ ppp_destroy_interface(ppp);
+ err = 0;
+ }
+ return err;
+}
+
+/*
+ * Free up the resources used by a ppp channel.
+ */
+static void ppp_destroy_channel(struct channel *pch)
+{
+ atomic_dec(&channel_count);
+
+ if (!pch->file.dead) {
+ /* "can't happen" */
+ pr_err("ppp: destroying undead channel %p !\n", pch);
+ return;
+ }
+ skb_queue_purge(&pch->file.xq);
+ skb_queue_purge(&pch->file.rq);
+ kfree(pch);
+}
+
+static void __exit ppp_cleanup(void)
+{
+ /* should never happen */
+ if (atomic_read(&ppp_unit_count) || atomic_read(&channel_count))
+ pr_err("PPP: removing module but units remain!\n");
+ unregister_chrdev(PPP_MAJOR, "ppp");
+ device_destroy(ppp_class, MKDEV(PPP_MAJOR, 0));
+ class_destroy(ppp_class);
+ unregister_pernet_device(&ppp_net_ops);
+}
+
+/*
+ * Units handling. Caller must protect concurrent access
+ * by holding all_ppp_mutex
+ */
+
+static int __unit_alloc(struct idr *p, void *ptr, int n)
+{
+ int unit, err;
+
+again:
+ if (!idr_pre_get(p, GFP_KERNEL)) {
+ pr_err("PPP: No free memory for idr\n");
+ return -ENOMEM;
+ }
+
+ err = idr_get_new_above(p, ptr, n, &unit);
+ if (err < 0) {
+ if (err == -EAGAIN)
+ goto again;
+ return err;
+ }
+
+ return unit;
+}
+
+/* associate pointer with specified number */
+static int unit_set(struct idr *p, void *ptr, int n)
+{
+ int unit;
+
+ unit = __unit_alloc(p, ptr, n);
+ if (unit < 0)
+ return unit;
+ else if (unit != n) {
+ idr_remove(p, unit);
+ return -EINVAL;
+ }
+
+ return unit;
+}
+
+/* get new free unit number and associate pointer with it */
+static int unit_get(struct idr *p, void *ptr)
+{
+ return __unit_alloc(p, ptr, 0);
+}
+
+/* put unit number back to a pool */
+static void unit_put(struct idr *p, int n)
+{
+ idr_remove(p, n);
+}
+
+/* get pointer associated with the number */
+static void *unit_find(struct idr *p, int n)
+{
+ return idr_find(p, n);
+}
+
+/* Module/initialization stuff */
+
+module_init(ppp_init);
+module_exit(ppp_cleanup);
+
+EXPORT_SYMBOL(ppp_register_net_channel);
+EXPORT_SYMBOL(ppp_register_channel);
+EXPORT_SYMBOL(ppp_unregister_channel);
+EXPORT_SYMBOL(ppp_channel_index);
+EXPORT_SYMBOL(ppp_unit_number);
+EXPORT_SYMBOL(ppp_dev_name);
+EXPORT_SYMBOL(ppp_input);
+EXPORT_SYMBOL(ppp_input_error);
+EXPORT_SYMBOL(ppp_output_wakeup);
+EXPORT_SYMBOL(ppp_register_compressor);
+EXPORT_SYMBOL(ppp_unregister_compressor);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_CHARDEV(PPP_MAJOR, 0);
+MODULE_ALIAS("devname:ppp");
diff --git a/drivers/net/ppp/ppp_mppe.c b/drivers/net/ppp/ppp_mppe.c
new file mode 100644
index 0000000..9a1849a
--- /dev/null
+++ b/drivers/net/ppp/ppp_mppe.c
@@ -0,0 +1,740 @@
+/*
+ * ppp_mppe.c - interface MPPE to the PPP code.
+ * This version is for use with Linux kernel 2.6.14+
+ *
+ * By Frank Cusack <fcusack@fcusack.com>.
+ * Copyright (c) 2002,2003,2004 Google, Inc.
+ * All rights reserved.
+ *
+ * License:
+ * Permission to use, copy, modify, and distribute this software and its
+ * documentation is hereby granted, provided that the above copyright
+ * notice appears in all copies. This software is provided without any
+ * warranty, express or implied.
+ *
+ * ALTERNATIVELY, provided that this notice is retained in full, this product
+ * may be distributed under the terms of the GNU General Public License (GPL),
+ * in which case the provisions of the GPL apply INSTEAD OF those given above.
+ *
+ * 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
+ *
+ *
+ * Changelog:
+ * 08/12/05 - Matt Domsch <Matt_Domsch@dell.com>
+ * Only need extra skb padding on transmit, not receive.
+ * 06/18/04 - Matt Domsch <Matt_Domsch@dell.com>, Oleg Makarenko <mole@quadra.ru>
+ * Use Linux kernel 2.6 arc4 and sha1 routines rather than
+ * providing our own.
+ * 2/15/04 - TS: added #include <version.h> and testing for Kernel
+ * version before using
+ * MOD_DEC_USAGE_COUNT/MOD_INC_USAGE_COUNT which are
+ * deprecated in 2.6
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/crypto.h>
+#include <linux/mm.h>
+#include <linux/ppp_defs.h>
+#include <linux/ppp-comp.h>
+#include <linux/scatterlist.h>
+#include <asm/unaligned.h>
+
+#include "ppp_mppe.h"
+
+MODULE_AUTHOR("Frank Cusack <fcusack@fcusack.com>");
+MODULE_DESCRIPTION("Point-to-Point Protocol Microsoft Point-to-Point Encryption support");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("ppp-compress-" __stringify(CI_MPPE));
+MODULE_VERSION("1.0.2");
+
+static unsigned int
+setup_sg(struct scatterlist *sg, const void *address, unsigned int length)
+{
+ sg_set_buf(sg, address, length);
+ return length;
+}
+
+#define SHA1_PAD_SIZE 40
+
+/*
+ * kernel crypto API needs its arguments to be in kmalloc'd memory, not in the module
+ * static data area. That means sha_pad needs to be kmalloc'd.
+ */
+
+struct sha_pad {
+ unsigned char sha_pad1[SHA1_PAD_SIZE];
+ unsigned char sha_pad2[SHA1_PAD_SIZE];
+};
+static struct sha_pad *sha_pad;
+
+static inline void sha_pad_init(struct sha_pad *shapad)
+{
+ memset(shapad->sha_pad1, 0x00, sizeof(shapad->sha_pad1));
+ memset(shapad->sha_pad2, 0xF2, sizeof(shapad->sha_pad2));
+}
+
+/*
+ * State for an MPPE (de)compressor.
+ */
+struct ppp_mppe_state {
+ struct crypto_blkcipher *arc4;
+ struct crypto_hash *sha1;
+ unsigned char *sha1_digest;
+ unsigned char master_key[MPPE_MAX_KEY_LEN];
+ unsigned char session_key[MPPE_MAX_KEY_LEN];
+ unsigned keylen; /* key length in bytes */
+ /* NB: 128-bit == 16, 40-bit == 8! */
+ /* If we want to support 56-bit, */
+ /* the unit has to change to bits */
+ unsigned char bits; /* MPPE control bits */
+ unsigned ccount; /* 12-bit coherency count (seqno) */
+ unsigned stateful; /* stateful mode flag */
+ int discard; /* stateful mode packet loss flag */
+ int sanity_errors; /* take down LCP if too many */
+ int unit;
+ int debug;
+ struct compstat stats;
+};
+
+/* struct ppp_mppe_state.bits definitions */
+#define MPPE_BIT_A 0x80 /* Encryption table were (re)inititalized */
+#define MPPE_BIT_B 0x40 /* MPPC only (not implemented) */
+#define MPPE_BIT_C 0x20 /* MPPC only (not implemented) */
+#define MPPE_BIT_D 0x10 /* This is an encrypted frame */
+
+#define MPPE_BIT_FLUSHED MPPE_BIT_A
+#define MPPE_BIT_ENCRYPTED MPPE_BIT_D
+
+#define MPPE_BITS(p) ((p)[4] & 0xf0)
+#define MPPE_CCOUNT(p) ((((p)[4] & 0x0f) << 8) + (p)[5])
+#define MPPE_CCOUNT_SPACE 0x1000 /* The size of the ccount space */
+
+#define MPPE_OVHD 2 /* MPPE overhead/packet */
+#define SANITY_MAX 1600 /* Max bogon factor we will tolerate */
+
+/*
+ * Key Derivation, from RFC 3078, RFC 3079.
+ * Equivalent to Get_Key() for MS-CHAP as described in RFC 3079.
+ */
+static void get_new_key_from_sha(struct ppp_mppe_state * state)
+{
+ struct hash_desc desc;
+ struct scatterlist sg[4];
+ unsigned int nbytes;
+
+ sg_init_table(sg, 4);
+
+ nbytes = setup_sg(&sg[0], state->master_key, state->keylen);
+ nbytes += setup_sg(&sg[1], sha_pad->sha_pad1,
+ sizeof(sha_pad->sha_pad1));
+ nbytes += setup_sg(&sg[2], state->session_key, state->keylen);
+ nbytes += setup_sg(&sg[3], sha_pad->sha_pad2,
+ sizeof(sha_pad->sha_pad2));
+
+ desc.tfm = state->sha1;
+ desc.flags = 0;
+
+ crypto_hash_digest(&desc, sg, nbytes, state->sha1_digest);
+}
+
+/*
+ * Perform the MPPE rekey algorithm, from RFC 3078, sec. 7.3.
+ * Well, not what's written there, but rather what they meant.
+ */
+static void mppe_rekey(struct ppp_mppe_state * state, int initial_key)
+{
+ struct scatterlist sg_in[1], sg_out[1];
+ struct blkcipher_desc desc = { .tfm = state->arc4 };
+
+ get_new_key_from_sha(state);
+ if (!initial_key) {
+ crypto_blkcipher_setkey(state->arc4, state->sha1_digest,
+ state->keylen);
+ sg_init_table(sg_in, 1);
+ sg_init_table(sg_out, 1);
+ setup_sg(sg_in, state->sha1_digest, state->keylen);
+ setup_sg(sg_out, state->session_key, state->keylen);
+ if (crypto_blkcipher_encrypt(&desc, sg_out, sg_in,
+ state->keylen) != 0) {
+ printk(KERN_WARNING "mppe_rekey: cipher_encrypt failed\n");
+ }
+ } else {
+ memcpy(state->session_key, state->sha1_digest, state->keylen);
+ }
+ if (state->keylen == 8) {
+ /* See RFC 3078 */
+ state->session_key[0] = 0xd1;
+ state->session_key[1] = 0x26;
+ state->session_key[2] = 0x9e;
+ }
+ crypto_blkcipher_setkey(state->arc4, state->session_key, state->keylen);
+}
+
+/*
+ * Allocate space for a (de)compressor.
+ */
+static void *mppe_alloc(unsigned char *options, int optlen)
+{
+ struct ppp_mppe_state *state;
+ unsigned int digestsize;
+
+ if (optlen != CILEN_MPPE + sizeof(state->master_key) ||
+ options[0] != CI_MPPE || options[1] != CILEN_MPPE)
+ goto out;
+
+ state = kzalloc(sizeof(*state), GFP_KERNEL);
+ if (state == NULL)
+ goto out;
+
+
+ state->arc4 = crypto_alloc_blkcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(state->arc4)) {
+ state->arc4 = NULL;
+ goto out_free;
+ }
+
+ state->sha1 = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
+ if (IS_ERR(state->sha1)) {
+ state->sha1 = NULL;
+ goto out_free;
+ }
+
+ digestsize = crypto_hash_digestsize(state->sha1);
+ if (digestsize < MPPE_MAX_KEY_LEN)
+ goto out_free;
+
+ state->sha1_digest = kmalloc(digestsize, GFP_KERNEL);
+ if (!state->sha1_digest)
+ goto out_free;
+
+ /* Save keys. */
+ memcpy(state->master_key, &options[CILEN_MPPE],
+ sizeof(state->master_key));
+ memcpy(state->session_key, state->master_key,
+ sizeof(state->master_key));
+
+ /*
+ * We defer initial key generation until mppe_init(), as mppe_alloc()
+ * is called frequently during negotiation.
+ */
+
+ return (void *)state;
+
+ out_free:
+ if (state->sha1_digest)
+ kfree(state->sha1_digest);
+ if (state->sha1)
+ crypto_free_hash(state->sha1);
+ if (state->arc4)
+ crypto_free_blkcipher(state->arc4);
+ kfree(state);
+ out:
+ return NULL;
+}
+
+/*
+ * Deallocate space for a (de)compressor.
+ */
+static void mppe_free(void *arg)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+ if (state) {
+ if (state->sha1_digest)
+ kfree(state->sha1_digest);
+ if (state->sha1)
+ crypto_free_hash(state->sha1);
+ if (state->arc4)
+ crypto_free_blkcipher(state->arc4);
+ kfree(state);
+ }
+}
+
+/*
+ * Initialize (de)compressor state.
+ */
+static int
+mppe_init(void *arg, unsigned char *options, int optlen, int unit, int debug,
+ const char *debugstr)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+ unsigned char mppe_opts;
+
+ if (optlen != CILEN_MPPE ||
+ options[0] != CI_MPPE || options[1] != CILEN_MPPE)
+ return 0;
+
+ MPPE_CI_TO_OPTS(&options[2], mppe_opts);
+ if (mppe_opts & MPPE_OPT_128)
+ state->keylen = 16;
+ else if (mppe_opts & MPPE_OPT_40)
+ state->keylen = 8;
+ else {
+ printk(KERN_WARNING "%s[%d]: unknown key length\n", debugstr,
+ unit);
+ return 0;
+ }
+ if (mppe_opts & MPPE_OPT_STATEFUL)
+ state->stateful = 1;
+
+ /* Generate the initial session key. */
+ mppe_rekey(state, 1);
+
+ if (debug) {
+ int i;
+ char mkey[sizeof(state->master_key) * 2 + 1];
+ char skey[sizeof(state->session_key) * 2 + 1];
+
+ printk(KERN_DEBUG "%s[%d]: initialized with %d-bit %s mode\n",
+ debugstr, unit, (state->keylen == 16) ? 128 : 40,
+ (state->stateful) ? "stateful" : "stateless");
+
+ for (i = 0; i < sizeof(state->master_key); i++)
+ sprintf(mkey + i * 2, "%02x", state->master_key[i]);
+ for (i = 0; i < sizeof(state->session_key); i++)
+ sprintf(skey + i * 2, "%02x", state->session_key[i]);
+ printk(KERN_DEBUG
+ "%s[%d]: keys: master: %s initial session: %s\n",
+ debugstr, unit, mkey, skey);
+ }
+
+ /*
+ * Initialize the coherency count. The initial value is not specified
+ * in RFC 3078, but we can make a reasonable assumption that it will
+ * start at 0. Setting it to the max here makes the comp/decomp code
+ * do the right thing (determined through experiment).
+ */
+ state->ccount = MPPE_CCOUNT_SPACE - 1;
+
+ /*
+ * Note that even though we have initialized the key table, we don't
+ * set the FLUSHED bit. This is contrary to RFC 3078, sec. 3.1.
+ */
+ state->bits = MPPE_BIT_ENCRYPTED;
+
+ state->unit = unit;
+ state->debug = debug;
+
+ return 1;
+}
+
+static int
+mppe_comp_init(void *arg, unsigned char *options, int optlen, int unit,
+ int hdrlen, int debug)
+{
+ /* ARGSUSED */
+ return mppe_init(arg, options, optlen, unit, debug, "mppe_comp_init");
+}
+
+/*
+ * We received a CCP Reset-Request (actually, we are sending a Reset-Ack),
+ * tell the compressor to rekey. Note that we MUST NOT rekey for
+ * every CCP Reset-Request; we only rekey on the next xmit packet.
+ * We might get multiple CCP Reset-Requests if our CCP Reset-Ack is lost.
+ * So, rekeying for every CCP Reset-Request is broken as the peer will not
+ * know how many times we've rekeyed. (If we rekey and THEN get another
+ * CCP Reset-Request, we must rekey again.)
+ */
+static void mppe_comp_reset(void *arg)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+
+ state->bits |= MPPE_BIT_FLUSHED;
+}
+
+/*
+ * Compress (encrypt) a packet.
+ * It's strange to call this a compressor, since the output is always
+ * MPPE_OVHD + 2 bytes larger than the input.
+ */
+static int
+mppe_compress(void *arg, unsigned char *ibuf, unsigned char *obuf,
+ int isize, int osize)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+ struct blkcipher_desc desc = { .tfm = state->arc4 };
+ int proto;
+ struct scatterlist sg_in[1], sg_out[1];
+
+ /*
+ * Check that the protocol is in the range we handle.
+ */
+ proto = PPP_PROTOCOL(ibuf);
+ if (proto < 0x0021 || proto > 0x00fa)
+ return 0;
+
+ /* Make sure we have enough room to generate an encrypted packet. */
+ if (osize < isize + MPPE_OVHD + 2) {
+ /* Drop the packet if we should encrypt it, but can't. */
+ printk(KERN_DEBUG "mppe_compress[%d]: osize too small! "
+ "(have: %d need: %d)\n", state->unit,
+ osize, osize + MPPE_OVHD + 2);
+ return -1;
+ }
+
+ osize = isize + MPPE_OVHD + 2;
+
+ /*
+ * Copy over the PPP header and set control bits.
+ */
+ obuf[0] = PPP_ADDRESS(ibuf);
+ obuf[1] = PPP_CONTROL(ibuf);
+ put_unaligned_be16(PPP_COMP, obuf + 2);
+ obuf += PPP_HDRLEN;
+
+ state->ccount = (state->ccount + 1) % MPPE_CCOUNT_SPACE;
+ if (state->debug >= 7)
+ printk(KERN_DEBUG "mppe_compress[%d]: ccount %d\n", state->unit,
+ state->ccount);
+ put_unaligned_be16(state->ccount, obuf);
+
+ if (!state->stateful || /* stateless mode */
+ ((state->ccount & 0xff) == 0xff) || /* "flag" packet */
+ (state->bits & MPPE_BIT_FLUSHED)) { /* CCP Reset-Request */
+ /* We must rekey */
+ if (state->debug && state->stateful)
+ printk(KERN_DEBUG "mppe_compress[%d]: rekeying\n",
+ state->unit);
+ mppe_rekey(state, 0);
+ state->bits |= MPPE_BIT_FLUSHED;
+ }
+ obuf[0] |= state->bits;
+ state->bits &= ~MPPE_BIT_FLUSHED; /* reset for next xmit */
+
+ obuf += MPPE_OVHD;
+ ibuf += 2; /* skip to proto field */
+ isize -= 2;
+
+ /* Encrypt packet */
+ sg_init_table(sg_in, 1);
+ sg_init_table(sg_out, 1);
+ setup_sg(sg_in, ibuf, isize);
+ setup_sg(sg_out, obuf, osize);
+ if (crypto_blkcipher_encrypt(&desc, sg_out, sg_in, isize) != 0) {
+ printk(KERN_DEBUG "crypto_cypher_encrypt failed\n");
+ return -1;
+ }
+
+ state->stats.unc_bytes += isize;
+ state->stats.unc_packets++;
+ state->stats.comp_bytes += osize;
+ state->stats.comp_packets++;
+
+ return osize;
+}
+
+/*
+ * Since every frame grows by MPPE_OVHD + 2 bytes, this is always going
+ * to look bad ... and the longer the link is up the worse it will get.
+ */
+static void mppe_comp_stats(void *arg, struct compstat *stats)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+
+ *stats = state->stats;
+}
+
+static int
+mppe_decomp_init(void *arg, unsigned char *options, int optlen, int unit,
+ int hdrlen, int mru, int debug)
+{
+ /* ARGSUSED */
+ return mppe_init(arg, options, optlen, unit, debug, "mppe_decomp_init");
+}
+
+/*
+ * We received a CCP Reset-Ack. Just ignore it.
+ */
+static void mppe_decomp_reset(void *arg)
+{
+ /* ARGSUSED */
+ return;
+}
+
+/*
+ * Decompress (decrypt) an MPPE packet.
+ */
+static int
+mppe_decompress(void *arg, unsigned char *ibuf, int isize, unsigned char *obuf,
+ int osize)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+ struct blkcipher_desc desc = { .tfm = state->arc4 };
+ unsigned ccount;
+ int flushed = MPPE_BITS(ibuf) & MPPE_BIT_FLUSHED;
+ int sanity = 0;
+ struct scatterlist sg_in[1], sg_out[1];
+
+ if (isize <= PPP_HDRLEN + MPPE_OVHD) {
+ if (state->debug)
+ printk(KERN_DEBUG
+ "mppe_decompress[%d]: short pkt (%d)\n",
+ state->unit, isize);
+ return DECOMP_ERROR;
+ }
+
+ /*
+ * Make sure we have enough room to decrypt the packet.
+ * Note that for our test we only subtract 1 byte whereas in
+ * mppe_compress() we added 2 bytes (+MPPE_OVHD);
+ * this is to account for possible PFC.
+ */
+ if (osize < isize - MPPE_OVHD - 1) {
+ printk(KERN_DEBUG "mppe_decompress[%d]: osize too small! "
+ "(have: %d need: %d)\n", state->unit,
+ osize, isize - MPPE_OVHD - 1);
+ return DECOMP_ERROR;
+ }
+ osize = isize - MPPE_OVHD - 2; /* assume no PFC */
+
+ ccount = MPPE_CCOUNT(ibuf);
+ if (state->debug >= 7)
+ printk(KERN_DEBUG "mppe_decompress[%d]: ccount %d\n",
+ state->unit, ccount);
+
+ /* sanity checks -- terminate with extreme prejudice */
+ if (!(MPPE_BITS(ibuf) & MPPE_BIT_ENCRYPTED)) {
+ printk(KERN_DEBUG
+ "mppe_decompress[%d]: ENCRYPTED bit not set!\n",
+ state->unit);
+ state->sanity_errors += 100;
+ sanity = 1;
+ }
+ if (!state->stateful && !flushed) {
+ printk(KERN_DEBUG "mppe_decompress[%d]: FLUSHED bit not set in "
+ "stateless mode!\n", state->unit);
+ state->sanity_errors += 100;
+ sanity = 1;
+ }
+ if (state->stateful && ((ccount & 0xff) == 0xff) && !flushed) {
+ printk(KERN_DEBUG "mppe_decompress[%d]: FLUSHED bit not set on "
+ "flag packet!\n", state->unit);
+ state->sanity_errors += 100;
+ sanity = 1;
+ }
+
+ if (sanity) {
+ if (state->sanity_errors < SANITY_MAX)
+ return DECOMP_ERROR;
+ else
+ /*
+ * Take LCP down if the peer is sending too many bogons.
+ * We don't want to do this for a single or just a few
+ * instances since it could just be due to packet corruption.
+ */
+ return DECOMP_FATALERROR;
+ }
+
+ /*
+ * Check the coherency count.
+ */
+
+ if (!state->stateful) {
+ /* RFC 3078, sec 8.1. Rekey for every packet. */
+ while (state->ccount != ccount) {
+ mppe_rekey(state, 0);
+ state->ccount = (state->ccount + 1) % MPPE_CCOUNT_SPACE;
+ }
+ } else {
+ /* RFC 3078, sec 8.2. */
+ if (!state->discard) {
+ /* normal state */
+ state->ccount = (state->ccount + 1) % MPPE_CCOUNT_SPACE;
+ if (ccount != state->ccount) {
+ /*
+ * (ccount > state->ccount)
+ * Packet loss detected, enter the discard state.
+ * Signal the peer to rekey (by sending a CCP Reset-Request).
+ */
+ state->discard = 1;
+ return DECOMP_ERROR;
+ }
+ } else {
+ /* discard state */
+ if (!flushed) {
+ /* ccp.c will be silent (no additional CCP Reset-Requests). */
+ return DECOMP_ERROR;
+ } else {
+ /* Rekey for every missed "flag" packet. */
+ while ((ccount & ~0xff) !=
+ (state->ccount & ~0xff)) {
+ mppe_rekey(state, 0);
+ state->ccount =
+ (state->ccount +
+ 256) % MPPE_CCOUNT_SPACE;
+ }
+
+ /* reset */
+ state->discard = 0;
+ state->ccount = ccount;
+ /*
+ * Another problem with RFC 3078 here. It implies that the
+ * peer need not send a Reset-Ack packet. But RFC 1962
+ * requires it. Hopefully, M$ does send a Reset-Ack; even
+ * though it isn't required for MPPE synchronization, it is
+ * required to reset CCP state.
+ */
+ }
+ }
+ if (flushed)
+ mppe_rekey(state, 0);
+ }
+
+ /*
+ * Fill in the first part of the PPP header. The protocol field
+ * comes from the decrypted data.
+ */
+ obuf[0] = PPP_ADDRESS(ibuf); /* +1 */
+ obuf[1] = PPP_CONTROL(ibuf); /* +1 */
+ obuf += 2;
+ ibuf += PPP_HDRLEN + MPPE_OVHD;
+ isize -= PPP_HDRLEN + MPPE_OVHD; /* -6 */
+ /* net osize: isize-4 */
+
+ /*
+ * Decrypt the first byte in order to check if it is
+ * a compressed or uncompressed protocol field.
+ */
+ sg_init_table(sg_in, 1);
+ sg_init_table(sg_out, 1);
+ setup_sg(sg_in, ibuf, 1);
+ setup_sg(sg_out, obuf, 1);
+ if (crypto_blkcipher_decrypt(&desc, sg_out, sg_in, 1) != 0) {
+ printk(KERN_DEBUG "crypto_cypher_decrypt failed\n");
+ return DECOMP_ERROR;
+ }
+
+ /*
+ * Do PFC decompression.
+ * This would be nicer if we were given the actual sk_buff
+ * instead of a char *.
+ */
+ if ((obuf[0] & 0x01) != 0) {
+ obuf[1] = obuf[0];
+ obuf[0] = 0;
+ obuf++;
+ osize++;
+ }
+
+ /* And finally, decrypt the rest of the packet. */
+ setup_sg(sg_in, ibuf + 1, isize - 1);
+ setup_sg(sg_out, obuf + 1, osize - 1);
+ if (crypto_blkcipher_decrypt(&desc, sg_out, sg_in, isize - 1)) {
+ printk(KERN_DEBUG "crypto_cypher_decrypt failed\n");
+ return DECOMP_ERROR;
+ }
+
+ state->stats.unc_bytes += osize;
+ state->stats.unc_packets++;
+ state->stats.comp_bytes += isize;
+ state->stats.comp_packets++;
+
+ /* good packet credit */
+ state->sanity_errors >>= 1;
+
+ return osize;
+}
+
+/*
+ * Incompressible data has arrived (this should never happen!).
+ * We should probably drop the link if the protocol is in the range
+ * of what should be encrypted. At the least, we should drop this
+ * packet. (How to do this?)
+ */
+static void mppe_incomp(void *arg, unsigned char *ibuf, int icnt)
+{
+ struct ppp_mppe_state *state = (struct ppp_mppe_state *) arg;
+
+ if (state->debug &&
+ (PPP_PROTOCOL(ibuf) >= 0x0021 && PPP_PROTOCOL(ibuf) <= 0x00fa))
+ printk(KERN_DEBUG
+ "mppe_incomp[%d]: incompressible (unencrypted) data! "
+ "(proto %04x)\n", state->unit, PPP_PROTOCOL(ibuf));
+
+ state->stats.inc_bytes += icnt;
+ state->stats.inc_packets++;
+ state->stats.unc_bytes += icnt;
+ state->stats.unc_packets++;
+}
+
+/*************************************************************
+ * Module interface table
+ *************************************************************/
+
+/*
+ * Procedures exported to if_ppp.c.
+ */
+static struct compressor ppp_mppe = {
+ .compress_proto = CI_MPPE,
+ .comp_alloc = mppe_alloc,
+ .comp_free = mppe_free,
+ .comp_init = mppe_comp_init,
+ .comp_reset = mppe_comp_reset,
+ .compress = mppe_compress,
+ .comp_stat = mppe_comp_stats,
+ .decomp_alloc = mppe_alloc,
+ .decomp_free = mppe_free,
+ .decomp_init = mppe_decomp_init,
+ .decomp_reset = mppe_decomp_reset,
+ .decompress = mppe_decompress,
+ .incomp = mppe_incomp,
+ .decomp_stat = mppe_comp_stats,
+ .owner = THIS_MODULE,
+ .comp_extra = MPPE_PAD,
+};
+
+/*
+ * ppp_mppe_init()
+ *
+ * Prior to allowing load, try to load the arc4 and sha1 crypto
+ * libraries. The actual use will be allocated later, but
+ * this way the module will fail to insmod if they aren't available.
+ */
+
+static int __init ppp_mppe_init(void)
+{
+ int answer;
+ if (!(crypto_has_blkcipher("ecb(arc4)", 0, CRYPTO_ALG_ASYNC) &&
+ crypto_has_hash("sha1", 0, CRYPTO_ALG_ASYNC)))
+ return -ENODEV;
+
+ sha_pad = kmalloc(sizeof(struct sha_pad), GFP_KERNEL);
+ if (!sha_pad)
+ return -ENOMEM;
+ sha_pad_init(sha_pad);
+
+ answer = ppp_register_compressor(&ppp_mppe);
+
+ if (answer == 0)
+ printk(KERN_INFO "PPP MPPE Compression module registered\n");
+ else
+ kfree(sha_pad);
+
+ return answer;
+}
+
+static void __exit ppp_mppe_cleanup(void)
+{
+ ppp_unregister_compressor(&ppp_mppe);
+ kfree(sha_pad);
+}
+
+module_init(ppp_mppe_init);
+module_exit(ppp_mppe_cleanup);
diff --git a/drivers/net/ppp/ppp_mppe.h b/drivers/net/ppp/ppp_mppe.h
new file mode 100644
index 0000000..7a14e05
--- /dev/null
+++ b/drivers/net/ppp/ppp_mppe.h
@@ -0,0 +1,86 @@
+#define MPPE_PAD 4 /* MPPE growth per frame */
+#define MPPE_MAX_KEY_LEN 16 /* largest key length (128-bit) */
+
+/* option bits for ccp_options.mppe */
+#define MPPE_OPT_40 0x01 /* 40 bit */
+#define MPPE_OPT_128 0x02 /* 128 bit */
+#define MPPE_OPT_STATEFUL 0x04 /* stateful mode */
+/* unsupported opts */
+#define MPPE_OPT_56 0x08 /* 56 bit */
+#define MPPE_OPT_MPPC 0x10 /* MPPC compression */
+#define MPPE_OPT_D 0x20 /* Unknown */
+#define MPPE_OPT_UNSUPPORTED (MPPE_OPT_56|MPPE_OPT_MPPC|MPPE_OPT_D)
+#define MPPE_OPT_UNKNOWN 0x40 /* Bits !defined in RFC 3078 were set */
+
+/*
+ * This is not nice ... the alternative is a bitfield struct though.
+ * And unfortunately, we cannot share the same bits for the option
+ * names above since C and H are the same bit. We could do a u_int32
+ * but then we have to do a htonl() all the time and/or we still need
+ * to know which octet is which.
+ */
+#define MPPE_C_BIT 0x01 /* MPPC */
+#define MPPE_D_BIT 0x10 /* Obsolete, usage unknown */
+#define MPPE_L_BIT 0x20 /* 40-bit */
+#define MPPE_S_BIT 0x40 /* 128-bit */
+#define MPPE_M_BIT 0x80 /* 56-bit, not supported */
+#define MPPE_H_BIT 0x01 /* Stateless (in a different byte) */
+
+/* Does not include H bit; used for least significant octet only. */
+#define MPPE_ALL_BITS (MPPE_D_BIT|MPPE_L_BIT|MPPE_S_BIT|MPPE_M_BIT|MPPE_H_BIT)
+
+/* Build a CI from mppe opts (see RFC 3078) */
+#define MPPE_OPTS_TO_CI(opts, ci) \
+ do { \
+ u_char *ptr = ci; /* u_char[4] */ \
+ \
+ /* H bit */ \
+ if (opts & MPPE_OPT_STATEFUL) \
+ *ptr++ = 0x0; \
+ else \
+ *ptr++ = MPPE_H_BIT; \
+ *ptr++ = 0; \
+ *ptr++ = 0; \
+ \
+ /* S,L bits */ \
+ *ptr = 0; \
+ if (opts & MPPE_OPT_128) \
+ *ptr |= MPPE_S_BIT; \
+ if (opts & MPPE_OPT_40) \
+ *ptr |= MPPE_L_BIT; \
+ /* M,D,C bits not supported */ \
+ } while (/* CONSTCOND */ 0)
+
+/* The reverse of the above */
+#define MPPE_CI_TO_OPTS(ci, opts) \
+ do { \
+ u_char *ptr = ci; /* u_char[4] */ \
+ \
+ opts = 0; \
+ \
+ /* H bit */ \
+ if (!(ptr[0] & MPPE_H_BIT)) \
+ opts |= MPPE_OPT_STATEFUL; \
+ \
+ /* S,L bits */ \
+ if (ptr[3] & MPPE_S_BIT) \
+ opts |= MPPE_OPT_128; \
+ if (ptr[3] & MPPE_L_BIT) \
+ opts |= MPPE_OPT_40; \
+ \
+ /* M,D,C bits */ \
+ if (ptr[3] & MPPE_M_BIT) \
+ opts |= MPPE_OPT_56; \
+ if (ptr[3] & MPPE_D_BIT) \
+ opts |= MPPE_OPT_D; \
+ if (ptr[3] & MPPE_C_BIT) \
+ opts |= MPPE_OPT_MPPC; \
+ \
+ /* Other bits */ \
+ if (ptr[0] & ~MPPE_H_BIT) \
+ opts |= MPPE_OPT_UNKNOWN; \
+ if (ptr[1] || ptr[2]) \
+ opts |= MPPE_OPT_UNKNOWN; \
+ if (ptr[3] & ~MPPE_ALL_BITS) \
+ opts |= MPPE_OPT_UNKNOWN; \
+ } while (/* CONSTCOND */ 0)
diff --git a/drivers/net/ppp/ppp_synctty.c b/drivers/net/ppp/ppp_synctty.c
new file mode 100644
index 0000000..736a39e
--- /dev/null
+++ b/drivers/net/ppp/ppp_synctty.c
@@ -0,0 +1,790 @@
+/*
+ * PPP synchronous tty channel driver for Linux.
+ *
+ * This is a ppp channel driver that can be used with tty device drivers
+ * that are frame oriented, such as synchronous HDLC devices.
+ *
+ * Complete PPP frames without encoding/decoding are exchanged between
+ * the channel driver and the device driver.
+ *
+ * The async map IOCTL codes are implemented to keep the user mode
+ * applications happy if they call them. Synchronous PPP does not use
+ * the async maps.
+ *
+ * Copyright 1999 Paul Mackerras.
+ *
+ * Also touched by the grubby hands of Paul Fulghum paulkf@microgate.com
+ *
+ * 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 driver provides the encapsulation and framing for sending
+ * and receiving PPP frames over sync serial lines. It relies on
+ * the generic PPP layer to give it frames to send and to process
+ * received frames. It implements the PPP line discipline.
+ *
+ * Part of the code in this driver was inspired by the old async-only
+ * PPP driver, written by Michael Callahan and Al Longyear, and
+ * subsequently hacked by Paul Mackerras.
+ *
+ * ==FILEVERSION 20040616==
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/tty.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/spinlock.h>
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <asm/uaccess.h>
+
+#define PPP_VERSION "2.4.2"
+
+/* Structure for storing local state. */
+struct syncppp {
+ struct tty_struct *tty;
+ unsigned int flags;
+ unsigned int rbits;
+ int mru;
+ spinlock_t xmit_lock;
+ spinlock_t recv_lock;
+ unsigned long xmit_flags;
+ u32 xaccm[8];
+ u32 raccm;
+ unsigned int bytes_sent;
+ unsigned int bytes_rcvd;
+
+ struct sk_buff *tpkt;
+ unsigned long last_xmit;
+
+ struct sk_buff_head rqueue;
+
+ struct tasklet_struct tsk;
+
+ atomic_t refcnt;
+ struct completion dead_cmp;
+ struct ppp_channel chan; /* interface to generic ppp layer */
+};
+
+/* Bit numbers in xmit_flags */
+#define XMIT_WAKEUP 0
+#define XMIT_FULL 1
+
+/* Bits in rbits */
+#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+
+#define PPPSYNC_MAX_RQLEN 32 /* arbitrary */
+
+/*
+ * Prototypes.
+ */
+static struct sk_buff* ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *);
+static int ppp_sync_send(struct ppp_channel *chan, struct sk_buff *skb);
+static int ppp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd,
+ unsigned long arg);
+static void ppp_sync_process(unsigned long arg);
+static int ppp_sync_push(struct syncppp *ap);
+static void ppp_sync_flush_output(struct syncppp *ap);
+static void ppp_sync_input(struct syncppp *ap, const unsigned char *buf,
+ char *flags, int count);
+
+static const struct ppp_channel_ops sync_ops = {
+ .start_xmit = ppp_sync_send,
+ .ioctl = ppp_sync_ioctl,
+};
+
+/*
+ * Utility procedures to print a buffer in hex/ascii
+ */
+static void
+ppp_print_hex (register __u8 * out, const __u8 * in, int count)
+{
+ register __u8 next_ch;
+ static const char hex[] = "0123456789ABCDEF";
+
+ while (count-- > 0) {
+ next_ch = *in++;
+ *out++ = hex[(next_ch >> 4) & 0x0F];
+ *out++ = hex[next_ch & 0x0F];
+ ++out;
+ }
+}
+
+static void
+ppp_print_char (register __u8 * out, const __u8 * in, int count)
+{
+ register __u8 next_ch;
+
+ while (count-- > 0) {
+ next_ch = *in++;
+
+ if (next_ch < 0x20 || next_ch > 0x7e)
+ *out++ = '.';
+ else {
+ *out++ = next_ch;
+ if (next_ch == '%') /* printk/syslogd has a bug !! */
+ *out++ = '%';
+ }
+ }
+ *out = '\0';
+}
+
+static void
+ppp_print_buffer (const char *name, const __u8 *buf, int count)
+{
+ __u8 line[44];
+
+ if (name != NULL)
+ printk(KERN_DEBUG "ppp_synctty: %s, count = %d\n", name, count);
+
+ while (count > 8) {
+ memset (line, 32, 44);
+ ppp_print_hex (line, buf, 8);
+ ppp_print_char (&line[8 * 3], buf, 8);
+ printk(KERN_DEBUG "%s\n", line);
+ count -= 8;
+ buf += 8;
+ }
+
+ if (count > 0) {
+ memset (line, 32, 44);
+ ppp_print_hex (line, buf, count);
+ ppp_print_char (&line[8 * 3], buf, count);
+ printk(KERN_DEBUG "%s\n", line);
+ }
+}
+
+
+/*
+ * Routines implementing the synchronous PPP line discipline.
+ */
+
+/*
+ * We have a potential race on dereferencing tty->disc_data,
+ * because the tty layer provides no locking at all - thus one
+ * cpu could be running ppp_synctty_receive while another
+ * calls ppp_synctty_close, which zeroes tty->disc_data and
+ * frees the memory that ppp_synctty_receive is using. The best
+ * way to fix this is to use a rwlock in the tty struct, but for now
+ * we use a single global rwlock for all ttys in ppp line discipline.
+ *
+ * FIXME: Fixed in tty_io nowadays.
+ */
+static DEFINE_RWLOCK(disc_data_lock);
+
+static struct syncppp *sp_get(struct tty_struct *tty)
+{
+ struct syncppp *ap;
+
+ read_lock(&disc_data_lock);
+ ap = tty->disc_data;
+ if (ap != NULL)
+ atomic_inc(&ap->refcnt);
+ read_unlock(&disc_data_lock);
+ return ap;
+}
+
+static void sp_put(struct syncppp *ap)
+{
+ if (atomic_dec_and_test(&ap->refcnt))
+ complete(&ap->dead_cmp);
+}
+
+/*
+ * Called when a tty is put into sync-PPP line discipline.
+ */
+static int
+ppp_sync_open(struct tty_struct *tty)
+{
+ struct syncppp *ap;
+ int err;
+ int speed;
+
+ if (tty->ops->write == NULL)
+ return -EOPNOTSUPP;
+
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ err = -ENOMEM;
+ if (!ap)
+ goto out;
+
+ /* initialize the syncppp structure */
+ ap->tty = tty;
+ ap->mru = PPP_MRU;
+ spin_lock_init(&ap->xmit_lock);
+ spin_lock_init(&ap->recv_lock);
+ ap->xaccm[0] = ~0U;
+ ap->xaccm[3] = 0x60000000U;
+ ap->raccm = ~0U;
+
+ skb_queue_head_init(&ap->rqueue);
+ tasklet_init(&ap->tsk, ppp_sync_process, (unsigned long) ap);
+
+ atomic_set(&ap->refcnt, 1);
+ init_completion(&ap->dead_cmp);
+
+ ap->chan.private = ap;
+ ap->chan.ops = &sync_ops;
+ ap->chan.mtu = PPP_MRU;
+ ap->chan.hdrlen = 2; /* for A/C bytes */
+ speed = tty_get_baud_rate(tty);
+ ap->chan.speed = speed;
+ err = ppp_register_channel(&ap->chan);
+ if (err)
+ goto out_free;
+
+ tty->disc_data = ap;
+ tty->receive_room = 65536;
+ return 0;
+
+ out_free:
+ kfree(ap);
+ out:
+ return err;
+}
+
+/*
+ * Called when the tty is put into another line discipline
+ * or it hangs up. We have to wait for any cpu currently
+ * executing in any of the other ppp_synctty_* routines to
+ * finish before we can call ppp_unregister_channel and free
+ * the syncppp struct. This routine must be called from
+ * process context, not interrupt or softirq context.
+ */
+static void
+ppp_sync_close(struct tty_struct *tty)
+{
+ struct syncppp *ap;
+
+ write_lock_irq(&disc_data_lock);
+ ap = tty->disc_data;
+ tty->disc_data = NULL;
+ write_unlock_irq(&disc_data_lock);
+ if (!ap)
+ return;
+
+ /*
+ * We have now ensured that nobody can start using ap from now
+ * on, but we have to wait for all existing users to finish.
+ * Note that ppp_unregister_channel ensures that no calls to
+ * our channel ops (i.e. ppp_sync_send/ioctl) are in progress
+ * by the time it returns.
+ */
+ if (!atomic_dec_and_test(&ap->refcnt))
+ wait_for_completion(&ap->dead_cmp);
+ tasklet_kill(&ap->tsk);
+
+ ppp_unregister_channel(&ap->chan);
+ skb_queue_purge(&ap->rqueue);
+ kfree_skb(ap->tpkt);
+ kfree(ap);
+}
+
+/*
+ * Called on tty hangup in process context.
+ *
+ * Wait for I/O to driver to complete and unregister PPP channel.
+ * This is already done by the close routine, so just call that.
+ */
+static int ppp_sync_hangup(struct tty_struct *tty)
+{
+ ppp_sync_close(tty);
+ return 0;
+}
+
+/*
+ * Read does nothing - no data is ever available this way.
+ * Pppd reads and writes packets via /dev/ppp instead.
+ */
+static ssize_t
+ppp_sync_read(struct tty_struct *tty, struct file *file,
+ unsigned char __user *buf, size_t count)
+{
+ return -EAGAIN;
+}
+
+/*
+ * Write on the tty does nothing, the packets all come in
+ * from the ppp generic stuff.
+ */
+static ssize_t
+ppp_sync_write(struct tty_struct *tty, struct file *file,
+ const unsigned char *buf, size_t count)
+{
+ return -EAGAIN;
+}
+
+static int
+ppp_synctty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct syncppp *ap = sp_get(tty);
+ int __user *p = (int __user *)arg;
+ int err, val;
+
+ if (!ap)
+ return -ENXIO;
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGCHAN:
+ err = -EFAULT;
+ if (put_user(ppp_channel_index(&ap->chan), p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGUNIT:
+ err = -EFAULT;
+ if (put_user(ppp_unit_number(&ap->chan), p))
+ break;
+ err = 0;
+ break;
+
+ case TCFLSH:
+ /* flush our buffers and the serial port's buffer */
+ if (arg == TCIOFLUSH || arg == TCOFLUSH)
+ ppp_sync_flush_output(ap);
+ err = tty_perform_flush(tty, arg);
+ break;
+
+ case FIONREAD:
+ val = 0;
+ if (put_user(val, p))
+ break;
+ err = 0;
+ break;
+
+ default:
+ err = tty_mode_ioctl(tty, file, cmd, arg);
+ break;
+ }
+
+ sp_put(ap);
+ return err;
+}
+
+/* No kernel lock - fine */
+static unsigned int
+ppp_sync_poll(struct tty_struct *tty, struct file *file, poll_table *wait)
+{
+ return 0;
+}
+
+/* May sleep, don't call from interrupt level or with interrupts disabled */
+static void
+ppp_sync_receive(struct tty_struct *tty, const unsigned char *buf,
+ char *cflags, int count)
+{
+ struct syncppp *ap = sp_get(tty);
+ unsigned long flags;
+
+ if (!ap)
+ return;
+ spin_lock_irqsave(&ap->recv_lock, flags);
+ ppp_sync_input(ap, buf, cflags, count);
+ spin_unlock_irqrestore(&ap->recv_lock, flags);
+ if (!skb_queue_empty(&ap->rqueue))
+ tasklet_schedule(&ap->tsk);
+ sp_put(ap);
+ tty_unthrottle(tty);
+}
+
+static void
+ppp_sync_wakeup(struct tty_struct *tty)
+{
+ struct syncppp *ap = sp_get(tty);
+
+ clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ if (!ap)
+ return;
+ set_bit(XMIT_WAKEUP, &ap->xmit_flags);
+ tasklet_schedule(&ap->tsk);
+ sp_put(ap);
+}
+
+
+static struct tty_ldisc_ops ppp_sync_ldisc = {
+ .owner = THIS_MODULE,
+ .magic = TTY_LDISC_MAGIC,
+ .name = "pppsync",
+ .open = ppp_sync_open,
+ .close = ppp_sync_close,
+ .hangup = ppp_sync_hangup,
+ .read = ppp_sync_read,
+ .write = ppp_sync_write,
+ .ioctl = ppp_synctty_ioctl,
+ .poll = ppp_sync_poll,
+ .receive_buf = ppp_sync_receive,
+ .write_wakeup = ppp_sync_wakeup,
+};
+
+static int __init
+ppp_sync_init(void)
+{
+ int err;
+
+ err = tty_register_ldisc(N_SYNC_PPP, &ppp_sync_ldisc);
+ if (err != 0)
+ printk(KERN_ERR "PPP_sync: error %d registering line disc.\n",
+ err);
+ return err;
+}
+
+/*
+ * The following routines provide the PPP channel interface.
+ */
+static int
+ppp_sync_ioctl(struct ppp_channel *chan, unsigned int cmd, unsigned long arg)
+{
+ struct syncppp *ap = chan->private;
+ int err, val;
+ u32 accm[8];
+ void __user *argp = (void __user *)arg;
+ u32 __user *p = argp;
+
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGFLAGS:
+ val = ap->flags | ap->rbits;
+ if (put_user(val, (int __user *) argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSFLAGS:
+ if (get_user(val, (int __user *) argp))
+ break;
+ ap->flags = val & ~SC_RCV_BITS;
+ spin_lock_irq(&ap->recv_lock);
+ ap->rbits = val & SC_RCV_BITS;
+ spin_unlock_irq(&ap->recv_lock);
+ err = 0;
+ break;
+
+ case PPPIOCGASYNCMAP:
+ if (put_user(ap->xaccm[0], p))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSASYNCMAP:
+ if (get_user(ap->xaccm[0], p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGRASYNCMAP:
+ if (put_user(ap->raccm, p))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSRASYNCMAP:
+ if (get_user(ap->raccm, p))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCGXASYNCMAP:
+ if (copy_to_user(argp, ap->xaccm, sizeof(ap->xaccm)))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSXASYNCMAP:
+ if (copy_from_user(accm, argp, sizeof(accm)))
+ break;
+ accm[2] &= ~0x40000000U; /* can't escape 0x5e */
+ accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */
+ memcpy(ap->xaccm, accm, sizeof(ap->xaccm));
+ err = 0;
+ break;
+
+ case PPPIOCGMRU:
+ if (put_user(ap->mru, (int __user *) argp))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSMRU:
+ if (get_user(val, (int __user *) argp))
+ break;
+ if (val < PPP_MRU)
+ val = PPP_MRU;
+ ap->mru = val;
+ err = 0;
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+ return err;
+}
+
+/*
+ * This is called at softirq level to deliver received packets
+ * to the ppp_generic code, and to tell the ppp_generic code
+ * if we can accept more output now.
+ */
+static void ppp_sync_process(unsigned long arg)
+{
+ struct syncppp *ap = (struct syncppp *) arg;
+ struct sk_buff *skb;
+
+ /* process received packets */
+ while ((skb = skb_dequeue(&ap->rqueue)) != NULL) {
+ if (skb->len == 0) {
+ /* zero length buffers indicate error */
+ ppp_input_error(&ap->chan, 0);
+ kfree_skb(skb);
+ }
+ else
+ ppp_input(&ap->chan, skb);
+ }
+
+ /* try to push more stuff out */
+ if (test_bit(XMIT_WAKEUP, &ap->xmit_flags) && ppp_sync_push(ap))
+ ppp_output_wakeup(&ap->chan);
+}
+
+/*
+ * Procedures for encapsulation and framing.
+ */
+
+static struct sk_buff*
+ppp_sync_txmunge(struct syncppp *ap, struct sk_buff *skb)
+{
+ int proto;
+ unsigned char *data;
+ int islcp;
+
+ data = skb->data;
+ proto = get_unaligned_be16(data);
+
+ /* LCP packets with codes between 1 (configure-request)
+ * and 7 (code-reject) must be sent as though no options
+ * have been negotiated.
+ */
+ islcp = proto == PPP_LCP && 1 <= data[2] && data[2] <= 7;
+
+ /* compress protocol field if option enabled */
+ if (data[0] == 0 && (ap->flags & SC_COMP_PROT) && !islcp)
+ skb_pull(skb,1);
+
+ /* prepend address/control fields if necessary */
+ if ((ap->flags & SC_COMP_AC) == 0 || islcp) {
+ if (skb_headroom(skb) < 2) {
+ struct sk_buff *npkt = dev_alloc_skb(skb->len + 2);
+ if (npkt == NULL) {
+ kfree_skb(skb);
+ return NULL;
+ }
+ skb_reserve(npkt,2);
+ skb_copy_from_linear_data(skb,
+ skb_put(npkt, skb->len), skb->len);
+ kfree_skb(skb);
+ skb = npkt;
+ }
+ skb_push(skb,2);
+ skb->data[0] = PPP_ALLSTATIONS;
+ skb->data[1] = PPP_UI;
+ }
+
+ ap->last_xmit = jiffies;
+
+ if (skb && ap->flags & SC_LOG_OUTPKT)
+ ppp_print_buffer ("send buffer", skb->data, skb->len);
+
+ return skb;
+}
+
+/*
+ * Transmit-side routines.
+ */
+
+/*
+ * Send a packet to the peer over an sync tty line.
+ * Returns 1 iff the packet was accepted.
+ * If the packet was not accepted, we will call ppp_output_wakeup
+ * at some later time.
+ */
+static int
+ppp_sync_send(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct syncppp *ap = chan->private;
+
+ ppp_sync_push(ap);
+
+ if (test_and_set_bit(XMIT_FULL, &ap->xmit_flags))
+ return 0; /* already full */
+ skb = ppp_sync_txmunge(ap, skb);
+ if (skb != NULL)
+ ap->tpkt = skb;
+ else
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+
+ ppp_sync_push(ap);
+ return 1;
+}
+
+/*
+ * Push as much data as possible out to the tty.
+ */
+static int
+ppp_sync_push(struct syncppp *ap)
+{
+ int sent, done = 0;
+ struct tty_struct *tty = ap->tty;
+ int tty_stuffed = 0;
+
+ if (!spin_trylock_bh(&ap->xmit_lock))
+ return 0;
+ for (;;) {
+ if (test_and_clear_bit(XMIT_WAKEUP, &ap->xmit_flags))
+ tty_stuffed = 0;
+ if (!tty_stuffed && ap->tpkt) {
+ set_bit(TTY_DO_WRITE_WAKEUP, &tty->flags);
+ sent = tty->ops->write(tty, ap->tpkt->data, ap->tpkt->len);
+ if (sent < 0)
+ goto flush; /* error, e.g. loss of CD */
+ if (sent < ap->tpkt->len) {
+ tty_stuffed = 1;
+ } else {
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ continue;
+ }
+ /* haven't made any progress */
+ spin_unlock_bh(&ap->xmit_lock);
+ if (!(test_bit(XMIT_WAKEUP, &ap->xmit_flags) ||
+ (!tty_stuffed && ap->tpkt)))
+ break;
+ if (!spin_trylock_bh(&ap->xmit_lock))
+ break;
+ }
+ return done;
+
+flush:
+ if (ap->tpkt) {
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ spin_unlock_bh(&ap->xmit_lock);
+ return done;
+}
+
+/*
+ * Flush output from our internal buffers.
+ * Called for the TCFLSH ioctl.
+ */
+static void
+ppp_sync_flush_output(struct syncppp *ap)
+{
+ int done = 0;
+
+ spin_lock_bh(&ap->xmit_lock);
+ if (ap->tpkt != NULL) {
+ kfree_skb(ap->tpkt);
+ ap->tpkt = NULL;
+ clear_bit(XMIT_FULL, &ap->xmit_flags);
+ done = 1;
+ }
+ spin_unlock_bh(&ap->xmit_lock);
+ if (done)
+ ppp_output_wakeup(&ap->chan);
+}
+
+/*
+ * Receive-side routines.
+ */
+
+/* called when the tty driver has data for us.
+ *
+ * Data is frame oriented: each call to ppp_sync_input is considered
+ * a whole frame. If the 1st flag byte is non-zero then the whole
+ * frame is considered to be in error and is tossed.
+ */
+static void
+ppp_sync_input(struct syncppp *ap, const unsigned char *buf,
+ char *flags, int count)
+{
+ struct sk_buff *skb;
+ unsigned char *p;
+
+ if (count == 0)
+ return;
+
+ if (ap->flags & SC_LOG_INPKT)
+ ppp_print_buffer ("receive buffer", buf, count);
+
+ /* stuff the chars in the skb */
+ skb = dev_alloc_skb(ap->mru + PPP_HDRLEN + 2);
+ if (!skb) {
+ printk(KERN_ERR "PPPsync: no memory (input pkt)\n");
+ goto err;
+ }
+ /* Try to get the payload 4-byte aligned */
+ if (buf[0] != PPP_ALLSTATIONS)
+ skb_reserve(skb, 2 + (buf[0] & 1));
+
+ if (flags && *flags) {
+ /* error flag set, ignore frame */
+ goto err;
+ } else if (count > skb_tailroom(skb)) {
+ /* packet overflowed MRU */
+ goto err;
+ }
+
+ p = skb_put(skb, count);
+ memcpy(p, buf, count);
+
+ /* strip address/control field if present */
+ p = skb->data;
+ if (p[0] == PPP_ALLSTATIONS && p[1] == PPP_UI) {
+ /* chop off address/control */
+ if (skb->len < 3)
+ goto err;
+ p = skb_pull(skb, 2);
+ }
+
+ /* decompress protocol field if compressed */
+ if (p[0] & 1) {
+ /* protocol is compressed */
+ skb_push(skb, 1)[0] = 0;
+ } else if (skb->len < 2)
+ goto err;
+
+ /* queue the frame to be processed */
+ skb_queue_tail(&ap->rqueue, skb);
+ return;
+
+err:
+ /* queue zero length packet as error indication */
+ if (skb || (skb = dev_alloc_skb(0))) {
+ skb_trim(skb, 0);
+ skb_queue_tail(&ap->rqueue, skb);
+ }
+}
+
+static void __exit
+ppp_sync_cleanup(void)
+{
+ if (tty_unregister_ldisc(N_SYNC_PPP) != 0)
+ printk(KERN_ERR "failed to unregister Sync PPP line discipline\n");
+}
+
+module_init(ppp_sync_init);
+module_exit(ppp_sync_cleanup);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_LDISC(N_SYNC_PPP);
diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
new file mode 100644
index 0000000..bc9a4bb
--- /dev/null
+++ b/drivers/net/ppp/pppoe.c
@@ -0,0 +1,1208 @@
+/** -*- linux-c -*- ***********************************************************
+ * Linux PPP over Ethernet (PPPoX/PPPoE) Sockets
+ *
+ * PPPoX --- Generic PPP encapsulation socket family
+ * PPPoE --- PPP over Ethernet (RFC 2516)
+ *
+ *
+ * Version: 0.7.0
+ *
+ * 070228 : Fix to allow multiple sessions with same remote MAC and same
+ * session id by including the local device ifindex in the
+ * tuple identifying a session. This also ensures packets can't
+ * be injected into a session from interfaces other than the one
+ * specified by userspace. Florian Zumbiehl <florz@florz.de>
+ * (Oh, BTW, this one is YYMMDD, in case you were wondering ...)
+ * 220102 : Fix module use count on failure in pppoe_create, pppox_sk -acme
+ * 030700 : Fixed connect logic to allow for disconnect.
+ * 270700 : Fixed potential SMP problems; we must protect against
+ * simultaneous invocation of ppp_input
+ * and ppp_unregister_channel.
+ * 040800 : Respect reference count mechanisms on net-devices.
+ * 200800 : fix kfree(skb) in pppoe_rcv (acme)
+ * Module reference count is decremented in the right spot now,
+ * guards against sock_put not actually freeing the sk
+ * in pppoe_release.
+ * 051000 : Initialization cleanup.
+ * 111100 : Fix recvmsg.
+ * 050101 : Fix PADT procesing.
+ * 140501 : Use pppoe_rcv_core to handle all backlog. (Alexey)
+ * 170701 : Do not lock_sock with rwlock held. (DaveM)
+ * Ignore discovery frames if user has socket
+ * locked. (DaveM)
+ * Ignore return value of dev_queue_xmit in __pppoe_xmit
+ * or else we may kfree an SKB twice. (DaveM)
+ * 190701 : When doing copies of skb's in __pppoe_xmit, always delete
+ * the original skb that was passed in on success, never on
+ * failure. Delete the copy of the skb on failure to avoid
+ * a memory leak.
+ * 081001 : Misc. cleanup (licence string, non-blocking, prevent
+ * reference of device on close).
+ * 121301 : New ppp channels interface; cannot unregister a channel
+ * from interrupts. Thus, we mark the socket as a ZOMBIE
+ * and do the unregistration later.
+ * 081002 : seq_file support for proc stuff -acme
+ * 111602 : Merge all 2.4 fixes into 2.5/2.6 tree. Label 2.5/2.6
+ * as version 0.7. Spacing cleanup.
+ * Author: Michal Ostrowski <mostrows@speakeasy.net>
+ * Contributors:
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * David S. Miller (davem@redhat.com)
+ *
+ * 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.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/net.h>
+#include <linux/inetdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/if_ether.h>
+#include <linux/if_pppox.h>
+#include <linux/ppp_channel.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/notifier.h>
+#include <linux/file.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+
+#include <linux/nsproxy.h>
+#include <net/net_namespace.h>
+#include <net/netns/generic.h>
+#include <net/sock.h>
+
+#include <asm/uaccess.h>
+
+#define PPPOE_HASH_BITS 4
+#define PPPOE_HASH_SIZE (1 << PPPOE_HASH_BITS)
+#define PPPOE_HASH_MASK (PPPOE_HASH_SIZE - 1)
+
+static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb);
+
+static const struct proto_ops pppoe_ops;
+static const struct ppp_channel_ops pppoe_chan_ops;
+
+/* per-net private data for this module */
+static int pppoe_net_id __read_mostly;
+struct pppoe_net {
+ /*
+ * we could use _single_ hash table for all
+ * nets by injecting net id into the hash but
+ * it would increase hash chains and add
+ * a few additional math comparations messy
+ * as well, moreover in case of SMP less locking
+ * controversy here
+ */
+ struct pppox_sock *hash_table[PPPOE_HASH_SIZE];
+ rwlock_t hash_lock;
+};
+
+/*
+ * PPPoE could be in the following stages:
+ * 1) Discovery stage (to obtain remote MAC and Session ID)
+ * 2) Session stage (MAC and SID are known)
+ *
+ * Ethernet frames have a special tag for this but
+ * we use simpler approach based on session id
+ */
+static inline bool stage_session(__be16 sid)
+{
+ return sid != 0;
+}
+
+static inline struct pppoe_net *pppoe_pernet(struct net *net)
+{
+ BUG_ON(!net);
+
+ return net_generic(net, pppoe_net_id);
+}
+
+static inline int cmp_2_addr(struct pppoe_addr *a, struct pppoe_addr *b)
+{
+ return a->sid == b->sid && !memcmp(a->remote, b->remote, ETH_ALEN);
+}
+
+static inline int cmp_addr(struct pppoe_addr *a, __be16 sid, char *addr)
+{
+ return a->sid == sid && !memcmp(a->remote, addr, ETH_ALEN);
+}
+
+#if 8 % PPPOE_HASH_BITS
+#error 8 must be a multiple of PPPOE_HASH_BITS
+#endif
+
+static int hash_item(__be16 sid, unsigned char *addr)
+{
+ unsigned char hash = 0;
+ unsigned int i;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ hash ^= addr[i];
+ for (i = 0; i < sizeof(sid_t) * 8; i += 8)
+ hash ^= (__force __u32)sid >> i;
+ for (i = 8; (i >>= 1) >= PPPOE_HASH_BITS;)
+ hash ^= hash >> i;
+
+ return hash & PPPOE_HASH_MASK;
+}
+
+/**********************************************************************
+ *
+ * Set/get/delete/rehash items (internal versions)
+ *
+ **********************************************************************/
+static struct pppox_sock *__get_item(struct pppoe_net *pn, __be16 sid,
+ unsigned char *addr, int ifindex)
+{
+ int hash = hash_item(sid, addr);
+ struct pppox_sock *ret;
+
+ ret = pn->hash_table[hash];
+ while (ret) {
+ if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+ ret->pppoe_ifindex == ifindex)
+ return ret;
+
+ ret = ret->next;
+ }
+
+ return NULL;
+}
+
+static int __set_item(struct pppoe_net *pn, struct pppox_sock *po)
+{
+ int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+ struct pppox_sock *ret;
+
+ ret = pn->hash_table[hash];
+ while (ret) {
+ if (cmp_2_addr(&ret->pppoe_pa, &po->pppoe_pa) &&
+ ret->pppoe_ifindex == po->pppoe_ifindex)
+ return -EALREADY;
+
+ ret = ret->next;
+ }
+
+ po->next = pn->hash_table[hash];
+ pn->hash_table[hash] = po;
+
+ return 0;
+}
+
+static struct pppox_sock *__delete_item(struct pppoe_net *pn, __be16 sid,
+ char *addr, int ifindex)
+{
+ int hash = hash_item(sid, addr);
+ struct pppox_sock *ret, **src;
+
+ ret = pn->hash_table[hash];
+ src = &pn->hash_table[hash];
+
+ while (ret) {
+ if (cmp_addr(&ret->pppoe_pa, sid, addr) &&
+ ret->pppoe_ifindex == ifindex) {
+ *src = ret->next;
+ break;
+ }
+
+ src = &ret->next;
+ ret = ret->next;
+ }
+
+ return ret;
+}
+
+/**********************************************************************
+ *
+ * Set/get/delete/rehash items
+ *
+ **********************************************************************/
+static inline struct pppox_sock *get_item(struct pppoe_net *pn, __be16 sid,
+ unsigned char *addr, int ifindex)
+{
+ struct pppox_sock *po;
+
+ read_lock_bh(&pn->hash_lock);
+ po = __get_item(pn, sid, addr, ifindex);
+ if (po)
+ sock_hold(sk_pppox(po));
+ read_unlock_bh(&pn->hash_lock);
+
+ return po;
+}
+
+static inline struct pppox_sock *get_item_by_addr(struct net *net,
+ struct sockaddr_pppox *sp)
+{
+ struct net_device *dev;
+ struct pppoe_net *pn;
+ struct pppox_sock *pppox_sock = NULL;
+
+ int ifindex;
+
+ rcu_read_lock();
+ dev = dev_get_by_name_rcu(net, sp->sa_addr.pppoe.dev);
+ if (dev) {
+ ifindex = dev->ifindex;
+ pn = pppoe_pernet(net);
+ pppox_sock = get_item(pn, sp->sa_addr.pppoe.sid,
+ sp->sa_addr.pppoe.remote, ifindex);
+ }
+ rcu_read_unlock();
+ return pppox_sock;
+}
+
+static inline struct pppox_sock *delete_item(struct pppoe_net *pn, __be16 sid,
+ char *addr, int ifindex)
+{
+ struct pppox_sock *ret;
+
+ write_lock_bh(&pn->hash_lock);
+ ret = __delete_item(pn, sid, addr, ifindex);
+ write_unlock_bh(&pn->hash_lock);
+
+ return ret;
+}
+
+/***************************************************************************
+ *
+ * Handler for device events.
+ * Certain device events require that sockets be unconnected.
+ *
+ **************************************************************************/
+
+static void pppoe_flush_dev(struct net_device *dev)
+{
+ struct pppoe_net *pn;
+ int i;
+
+ pn = pppoe_pernet(dev_net(dev));
+ write_lock_bh(&pn->hash_lock);
+ for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+ struct pppox_sock *po = pn->hash_table[i];
+ struct sock *sk;
+
+ while (po) {
+ while (po && po->pppoe_dev != dev) {
+ po = po->next;
+ }
+
+ if (!po)
+ break;
+
+ sk = sk_pppox(po);
+
+ /* We always grab the socket lock, followed by the
+ * hash_lock, in that order. Since we should hold the
+ * sock lock while doing any unbinding, we need to
+ * release the lock we're holding. Hold a reference to
+ * the sock so it doesn't disappear as we're jumping
+ * between locks.
+ */
+
+ sock_hold(sk);
+ write_unlock_bh(&pn->hash_lock);
+ lock_sock(sk);
+
+ if (po->pppoe_dev == dev &&
+ sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND | PPPOX_ZOMBIE)) {
+ pppox_unbind_sock(sk);
+ sk->sk_state = PPPOX_ZOMBIE;
+ sk->sk_state_change(sk);
+ po->pppoe_dev = NULL;
+ dev_put(dev);
+ }
+
+ release_sock(sk);
+ sock_put(sk);
+
+ /* Restart the process from the start of the current
+ * hash chain. We dropped locks so the world may have
+ * change from underneath us.
+ */
+
+ BUG_ON(pppoe_pernet(dev_net(dev)) == NULL);
+ write_lock_bh(&pn->hash_lock);
+ po = pn->hash_table[i];
+ }
+ }
+ write_unlock_bh(&pn->hash_lock);
+}
+
+static int pppoe_device_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+
+ /* Only look at sockets that are using this specific device. */
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ case NETDEV_CHANGEMTU:
+ /* A change in mtu or address is a bad thing, requiring
+ * LCP re-negotiation.
+ */
+
+ case NETDEV_GOING_DOWN:
+ case NETDEV_DOWN:
+ /* Find every socket on this device and kill it. */
+ pppoe_flush_dev(dev);
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block pppoe_notifier = {
+ .notifier_call = pppoe_device_event,
+};
+
+/************************************************************************
+ *
+ * Do the real work of receiving a PPPoE Session frame.
+ *
+ ***********************************************************************/
+static int pppoe_rcv_core(struct sock *sk, struct sk_buff *skb)
+{
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pppox_sock *relay_po;
+
+ /* Backlog receive. Semantics of backlog rcv preclude any code from
+ * executing in lock_sock()/release_sock() bounds; meaning sk->sk_state
+ * can't change.
+ */
+
+ if (sk->sk_state & PPPOX_BOUND) {
+ ppp_input(&po->chan, skb);
+ } else if (sk->sk_state & PPPOX_RELAY) {
+ relay_po = get_item_by_addr(sock_net(sk),
+ &po->pppoe_relay);
+ if (relay_po == NULL)
+ goto abort_kfree;
+
+ if ((sk_pppox(relay_po)->sk_state & PPPOX_CONNECTED) == 0)
+ goto abort_put;
+
+ if (!__pppoe_xmit(sk_pppox(relay_po), skb))
+ goto abort_put;
+ } else {
+ if (sock_queue_rcv_skb(sk, skb))
+ goto abort_kfree;
+ }
+
+ return NET_RX_SUCCESS;
+
+abort_put:
+ sock_put(sk_pppox(relay_po));
+
+abort_kfree:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+/************************************************************************
+ *
+ * Receive wrapper called in BH context.
+ *
+ ***********************************************************************/
+static int pppoe_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev)
+{
+ struct pppoe_hdr *ph;
+ struct pppox_sock *po;
+ struct pppoe_net *pn;
+ int len;
+
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (!skb)
+ goto out;
+
+ if (!pskb_may_pull(skb, sizeof(struct pppoe_hdr)))
+ goto drop;
+
+ ph = pppoe_hdr(skb);
+ len = ntohs(ph->length);
+
+ skb_pull_rcsum(skb, sizeof(*ph));
+ if (skb->len < len)
+ goto drop;
+
+ if (pskb_trim_rcsum(skb, len))
+ goto drop;
+
+ pn = pppoe_pernet(dev_net(dev));
+
+ /* Note that get_item does a sock_hold(), so sk_pppox(po)
+ * is known to be safe.
+ */
+ po = get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
+ if (!po)
+ goto drop;
+
+ return sk_receive_skb(sk_pppox(po), skb, 0);
+
+drop:
+ kfree_skb(skb);
+out:
+ return NET_RX_DROP;
+}
+
+/************************************************************************
+ *
+ * Receive a PPPoE Discovery frame.
+ * This is solely for detection of PADT frames
+ *
+ ***********************************************************************/
+static int pppoe_disc_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt, struct net_device *orig_dev)
+
+{
+ struct pppoe_hdr *ph;
+ struct pppox_sock *po;
+ struct pppoe_net *pn;
+
+ skb = skb_share_check(skb, GFP_ATOMIC);
+ if (!skb)
+ goto out;
+
+ if (!pskb_may_pull(skb, sizeof(struct pppoe_hdr)))
+ goto abort;
+
+ ph = pppoe_hdr(skb);
+ if (ph->code != PADT_CODE)
+ goto abort;
+
+ pn = pppoe_pernet(dev_net(dev));
+ po = get_item(pn, ph->sid, eth_hdr(skb)->h_source, dev->ifindex);
+ if (po) {
+ struct sock *sk = sk_pppox(po);
+
+ bh_lock_sock(sk);
+
+ /* If the user has locked the socket, just ignore
+ * the packet. With the way two rcv protocols hook into
+ * one socket family type, we cannot (easily) distinguish
+ * what kind of SKB it is during backlog rcv.
+ */
+ if (sock_owned_by_user(sk) == 0) {
+ /* We're no longer connect at the PPPOE layer,
+ * and must wait for ppp channel to disconnect us.
+ */
+ sk->sk_state = PPPOX_ZOMBIE;
+ }
+
+ bh_unlock_sock(sk);
+ sock_put(sk);
+ }
+
+abort:
+ kfree_skb(skb);
+out:
+ return NET_RX_SUCCESS; /* Lies... :-) */
+}
+
+static struct packet_type pppoes_ptype __read_mostly = {
+ .type = cpu_to_be16(ETH_P_PPP_SES),
+ .func = pppoe_rcv,
+};
+
+static struct packet_type pppoed_ptype __read_mostly = {
+ .type = cpu_to_be16(ETH_P_PPP_DISC),
+ .func = pppoe_disc_rcv,
+};
+
+static struct proto pppoe_sk_proto __read_mostly = {
+ .name = "PPPOE",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct pppox_sock),
+};
+
+/***********************************************************************
+ *
+ * Initialize a new struct sock.
+ *
+ **********************************************************************/
+static int pppoe_create(struct net *net, struct socket *sock)
+{
+ struct sock *sk;
+
+ sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pppoe_sk_proto);
+ if (!sk)
+ return -ENOMEM;
+
+ sock_init_data(sock, sk);
+
+ sock->state = SS_UNCONNECTED;
+ sock->ops = &pppoe_ops;
+
+ sk->sk_backlog_rcv = pppoe_rcv_core;
+ sk->sk_state = PPPOX_NONE;
+ sk->sk_type = SOCK_STREAM;
+ sk->sk_family = PF_PPPOX;
+ sk->sk_protocol = PX_PROTO_OE;
+
+ return 0;
+}
+
+static int pppoe_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_sock *po;
+ struct pppoe_net *pn;
+ struct net *net = NULL;
+
+ if (!sk)
+ return 0;
+
+ lock_sock(sk);
+ if (sock_flag(sk, SOCK_DEAD)) {
+ release_sock(sk);
+ return -EBADF;
+ }
+
+ po = pppox_sk(sk);
+
+ if (sk->sk_state & (PPPOX_CONNECTED | PPPOX_BOUND)) {
+ dev_put(po->pppoe_dev);
+ po->pppoe_dev = NULL;
+ }
+
+ pppox_unbind_sock(sk);
+
+ /* Signal the death of the socket. */
+ sk->sk_state = PPPOX_DEAD;
+
+ net = sock_net(sk);
+ pn = pppoe_pernet(net);
+
+ /*
+ * protect "po" from concurrent updates
+ * on pppoe_flush_dev
+ */
+ delete_item(pn, po->pppoe_pa.sid, po->pppoe_pa.remote,
+ po->pppoe_ifindex);
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ skb_queue_purge(&sk->sk_receive_queue);
+ release_sock(sk);
+ sock_put(sk);
+
+ return 0;
+}
+
+static int pppoe_connect(struct socket *sock, struct sockaddr *uservaddr,
+ int sockaddr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_pppox *sp = (struct sockaddr_pppox *)uservaddr;
+ struct pppox_sock *po = pppox_sk(sk);
+ struct net_device *dev = NULL;
+ struct pppoe_net *pn;
+ struct net *net = NULL;
+ int error;
+
+ lock_sock(sk);
+
+ error = -EINVAL;
+ if (sp->sa_protocol != PX_PROTO_OE)
+ goto end;
+
+ /* Check for already bound sockets */
+ error = -EBUSY;
+ if ((sk->sk_state & PPPOX_CONNECTED) &&
+ stage_session(sp->sa_addr.pppoe.sid))
+ goto end;
+
+ /* Check for already disconnected sockets, on attempts to disconnect */
+ error = -EALREADY;
+ if ((sk->sk_state & PPPOX_DEAD) &&
+ !stage_session(sp->sa_addr.pppoe.sid))
+ goto end;
+
+ error = 0;
+
+ /* Delete the old binding */
+ if (stage_session(po->pppoe_pa.sid)) {
+ pppox_unbind_sock(sk);
+ pn = pppoe_pernet(sock_net(sk));
+ delete_item(pn, po->pppoe_pa.sid,
+ po->pppoe_pa.remote, po->pppoe_ifindex);
+ if (po->pppoe_dev) {
+ dev_put(po->pppoe_dev);
+ po->pppoe_dev = NULL;
+ }
+
+ memset(sk_pppox(po) + 1, 0,
+ sizeof(struct pppox_sock) - sizeof(struct sock));
+ sk->sk_state = PPPOX_NONE;
+ }
+
+ /* Re-bind in session stage only */
+ if (stage_session(sp->sa_addr.pppoe.sid)) {
+ error = -ENODEV;
+ net = sock_net(sk);
+ dev = dev_get_by_name(net, sp->sa_addr.pppoe.dev);
+ if (!dev)
+ goto err_put;
+
+ po->pppoe_dev = dev;
+ po->pppoe_ifindex = dev->ifindex;
+ pn = pppoe_pernet(net);
+ if (!(dev->flags & IFF_UP)) {
+ goto err_put;
+ }
+
+ memcpy(&po->pppoe_pa,
+ &sp->sa_addr.pppoe,
+ sizeof(struct pppoe_addr));
+
+ write_lock_bh(&pn->hash_lock);
+ error = __set_item(pn, po);
+ write_unlock_bh(&pn->hash_lock);
+ if (error < 0)
+ goto err_put;
+
+ po->chan.hdrlen = (sizeof(struct pppoe_hdr) +
+ dev->hard_header_len);
+
+ po->chan.mtu = dev->mtu - sizeof(struct pppoe_hdr);
+ po->chan.private = sk;
+ po->chan.ops = &pppoe_chan_ops;
+
+ error = ppp_register_net_channel(dev_net(dev), &po->chan);
+ if (error) {
+ delete_item(pn, po->pppoe_pa.sid,
+ po->pppoe_pa.remote, po->pppoe_ifindex);
+ goto err_put;
+ }
+
+ sk->sk_state = PPPOX_CONNECTED;
+ }
+
+ po->num = sp->sa_addr.pppoe.sid;
+
+end:
+ release_sock(sk);
+ return error;
+err_put:
+ if (po->pppoe_dev) {
+ dev_put(po->pppoe_dev);
+ po->pppoe_dev = NULL;
+ }
+ goto end;
+}
+
+static int pppoe_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *usockaddr_len, int peer)
+{
+ int len = sizeof(struct sockaddr_pppox);
+ struct sockaddr_pppox sp;
+
+ sp.sa_family = AF_PPPOX;
+ sp.sa_protocol = PX_PROTO_OE;
+ memcpy(&sp.sa_addr.pppoe, &pppox_sk(sock->sk)->pppoe_pa,
+ sizeof(struct pppoe_addr));
+
+ memcpy(uaddr, &sp, len);
+
+ *usockaddr_len = len;
+
+ return 0;
+}
+
+static int pppoe_ioctl(struct socket *sock, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_sock *po = pppox_sk(sk);
+ int val;
+ int err;
+
+ switch (cmd) {
+ case PPPIOCGMRU:
+ err = -ENXIO;
+ if (!(sk->sk_state & PPPOX_CONNECTED))
+ break;
+
+ err = -EFAULT;
+ if (put_user(po->pppoe_dev->mtu -
+ sizeof(struct pppoe_hdr) -
+ PPP_HDRLEN,
+ (int __user *)arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPIOCSMRU:
+ err = -ENXIO;
+ if (!(sk->sk_state & PPPOX_CONNECTED))
+ break;
+
+ err = -EFAULT;
+ if (get_user(val, (int __user *)arg))
+ break;
+
+ if (val < (po->pppoe_dev->mtu
+ - sizeof(struct pppoe_hdr)
+ - PPP_HDRLEN))
+ err = 0;
+ else
+ err = -EINVAL;
+ break;
+
+ case PPPIOCSFLAGS:
+ err = -EFAULT;
+ if (get_user(val, (int __user *)arg))
+ break;
+ err = 0;
+ break;
+
+ case PPPOEIOCSFWD:
+ {
+ struct pppox_sock *relay_po;
+
+ err = -EBUSY;
+ if (sk->sk_state & (PPPOX_BOUND | PPPOX_ZOMBIE | PPPOX_DEAD))
+ break;
+
+ err = -ENOTCONN;
+ if (!(sk->sk_state & PPPOX_CONNECTED))
+ break;
+
+ /* PPPoE address from the user specifies an outbound
+ PPPoE address which frames are forwarded to */
+ err = -EFAULT;
+ if (copy_from_user(&po->pppoe_relay,
+ (void __user *)arg,
+ sizeof(struct sockaddr_pppox)))
+ break;
+
+ err = -EINVAL;
+ if (po->pppoe_relay.sa_family != AF_PPPOX ||
+ po->pppoe_relay.sa_protocol != PX_PROTO_OE)
+ break;
+
+ /* Check that the socket referenced by the address
+ actually exists. */
+ relay_po = get_item_by_addr(sock_net(sk), &po->pppoe_relay);
+ if (!relay_po)
+ break;
+
+ sock_put(sk_pppox(relay_po));
+ sk->sk_state |= PPPOX_RELAY;
+ err = 0;
+ break;
+ }
+
+ case PPPOEIOCDFWD:
+ err = -EALREADY;
+ if (!(sk->sk_state & PPPOX_RELAY))
+ break;
+
+ sk->sk_state &= ~PPPOX_RELAY;
+ err = 0;
+ break;
+
+ default:
+ err = -ENOTTY;
+ }
+
+ return err;
+}
+
+static int pppoe_sendmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *m, size_t total_len)
+{
+ struct sk_buff *skb;
+ struct sock *sk = sock->sk;
+ struct pppox_sock *po = pppox_sk(sk);
+ int error;
+ struct pppoe_hdr hdr;
+ struct pppoe_hdr *ph;
+ struct net_device *dev;
+ char *start;
+
+ lock_sock(sk);
+ if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED)) {
+ error = -ENOTCONN;
+ goto end;
+ }
+
+ hdr.ver = 1;
+ hdr.type = 1;
+ hdr.code = 0;
+ hdr.sid = po->num;
+
+ dev = po->pppoe_dev;
+
+ error = -EMSGSIZE;
+ if (total_len > (dev->mtu + dev->hard_header_len))
+ goto end;
+
+
+ skb = sock_wmalloc(sk, total_len + dev->hard_header_len + 32,
+ 0, GFP_KERNEL);
+ if (!skb) {
+ error = -ENOMEM;
+ goto end;
+ }
+
+ /* Reserve space for headers. */
+ skb_reserve(skb, dev->hard_header_len);
+ skb_reset_network_header(skb);
+
+ skb->dev = dev;
+
+ skb->priority = sk->sk_priority;
+ skb->protocol = cpu_to_be16(ETH_P_PPP_SES);
+
+ ph = (struct pppoe_hdr *)skb_put(skb, total_len + sizeof(struct pppoe_hdr));
+ start = (char *)&ph->tag[0];
+
+ error = memcpy_fromiovec(start, m->msg_iov, total_len);
+ if (error < 0) {
+ kfree_skb(skb);
+ goto end;
+ }
+
+ error = total_len;
+ dev_hard_header(skb, dev, ETH_P_PPP_SES,
+ po->pppoe_pa.remote, NULL, total_len);
+
+ memcpy(ph, &hdr, sizeof(struct pppoe_hdr));
+
+ ph->length = htons(total_len);
+
+ dev_queue_xmit(skb);
+
+end:
+ release_sock(sk);
+ return error;
+}
+
+/************************************************************************
+ *
+ * xmit function for internal use.
+ *
+ ***********************************************************************/
+static int __pppoe_xmit(struct sock *sk, struct sk_buff *skb)
+{
+ struct pppox_sock *po = pppox_sk(sk);
+ struct net_device *dev = po->pppoe_dev;
+ struct pppoe_hdr *ph;
+ int data_len = skb->len;
+
+ /* The higher-level PPP code (ppp_unregister_channel()) ensures the PPP
+ * xmit operations conclude prior to an unregistration call. Thus
+ * sk->sk_state cannot change, so we don't need to do lock_sock().
+ * But, we also can't do a lock_sock since that introduces a potential
+ * deadlock as we'd reverse the lock ordering used when calling
+ * ppp_unregister_channel().
+ */
+
+ if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED))
+ goto abort;
+
+ if (!dev)
+ goto abort;
+
+ /* Copy the data if there is no space for the header or if it's
+ * read-only.
+ */
+ if (skb_cow_head(skb, sizeof(*ph) + dev->hard_header_len))
+ goto abort;
+
+ __skb_push(skb, sizeof(*ph));
+ skb_reset_network_header(skb);
+
+ ph = pppoe_hdr(skb);
+ ph->ver = 1;
+ ph->type = 1;
+ ph->code = 0;
+ ph->sid = po->num;
+ ph->length = htons(data_len);
+
+ skb->protocol = cpu_to_be16(ETH_P_PPP_SES);
+ skb->dev = dev;
+
+ dev_hard_header(skb, dev, ETH_P_PPP_SES,
+ po->pppoe_pa.remote, NULL, data_len);
+
+ dev_queue_xmit(skb);
+ return 1;
+
+abort:
+ kfree_skb(skb);
+ return 1;
+}
+
+/************************************************************************
+ *
+ * xmit function called by generic PPP driver
+ * sends PPP frame over PPPoE socket
+ *
+ ***********************************************************************/
+static int pppoe_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct sock *sk = (struct sock *)chan->private;
+ return __pppoe_xmit(sk, skb);
+}
+
+static const struct ppp_channel_ops pppoe_chan_ops = {
+ .start_xmit = pppoe_xmit,
+};
+
+static int pppoe_recvmsg(struct kiocb *iocb, struct socket *sock,
+ struct msghdr *m, size_t total_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int error = 0;
+
+ if (sk->sk_state & PPPOX_BOUND) {
+ error = -EIO;
+ goto end;
+ }
+
+ skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
+ flags & MSG_DONTWAIT, &error);
+ if (error < 0)
+ goto end;
+
+ m->msg_namelen = 0;
+
+ if (skb) {
+ total_len = min_t(size_t, total_len, skb->len);
+ error = skb_copy_datagram_iovec(skb, 0, m->msg_iov, total_len);
+ if (error == 0)
+ error = total_len;
+ }
+
+ kfree_skb(skb);
+end:
+ return error;
+}
+
+#ifdef CONFIG_PROC_FS
+static int pppoe_seq_show(struct seq_file *seq, void *v)
+{
+ struct pppox_sock *po;
+ char *dev_name;
+
+ if (v == SEQ_START_TOKEN) {
+ seq_puts(seq, "Id Address Device\n");
+ goto out;
+ }
+
+ po = v;
+ dev_name = po->pppoe_pa.dev;
+
+ seq_printf(seq, "%08X %pM %8s\n",
+ po->pppoe_pa.sid, po->pppoe_pa.remote, dev_name);
+out:
+ return 0;
+}
+
+static inline struct pppox_sock *pppoe_get_idx(struct pppoe_net *pn, loff_t pos)
+{
+ struct pppox_sock *po;
+ int i;
+
+ for (i = 0; i < PPPOE_HASH_SIZE; i++) {
+ po = pn->hash_table[i];
+ while (po) {
+ if (!pos--)
+ goto out;
+ po = po->next;
+ }
+ }
+
+out:
+ return po;
+}
+
+static void *pppoe_seq_start(struct seq_file *seq, loff_t *pos)
+ __acquires(pn->hash_lock)
+{
+ struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+ loff_t l = *pos;
+
+ read_lock_bh(&pn->hash_lock);
+ return l ? pppoe_get_idx(pn, --l) : SEQ_START_TOKEN;
+}
+
+static void *pppoe_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+ struct pppox_sock *po;
+
+ ++*pos;
+ if (v == SEQ_START_TOKEN) {
+ po = pppoe_get_idx(pn, 0);
+ goto out;
+ }
+ po = v;
+ if (po->next)
+ po = po->next;
+ else {
+ int hash = hash_item(po->pppoe_pa.sid, po->pppoe_pa.remote);
+
+ po = NULL;
+ while (++hash < PPPOE_HASH_SIZE) {
+ po = pn->hash_table[hash];
+ if (po)
+ break;
+ }
+ }
+
+out:
+ return po;
+}
+
+static void pppoe_seq_stop(struct seq_file *seq, void *v)
+ __releases(pn->hash_lock)
+{
+ struct pppoe_net *pn = pppoe_pernet(seq_file_net(seq));
+ read_unlock_bh(&pn->hash_lock);
+}
+
+static const struct seq_operations pppoe_seq_ops = {
+ .start = pppoe_seq_start,
+ .next = pppoe_seq_next,
+ .stop = pppoe_seq_stop,
+ .show = pppoe_seq_show,
+};
+
+static int pppoe_seq_open(struct inode *inode, struct file *file)
+{
+ return seq_open_net(inode, file, &pppoe_seq_ops,
+ sizeof(struct seq_net_private));
+}
+
+static const struct file_operations pppoe_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = pppoe_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release_net,
+};
+
+#endif /* CONFIG_PROC_FS */
+
+static const struct proto_ops pppoe_ops = {
+ .family = AF_PPPOX,
+ .owner = THIS_MODULE,
+ .release = pppoe_release,
+ .bind = sock_no_bind,
+ .connect = pppoe_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = pppoe_getname,
+ .poll = datagram_poll,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ .setsockopt = sock_no_setsockopt,
+ .getsockopt = sock_no_getsockopt,
+ .sendmsg = pppoe_sendmsg,
+ .recvmsg = pppoe_recvmsg,
+ .mmap = sock_no_mmap,
+ .ioctl = pppox_ioctl,
+};
+
+static const struct pppox_proto pppoe_proto = {
+ .create = pppoe_create,
+ .ioctl = pppoe_ioctl,
+ .owner = THIS_MODULE,
+};
+
+static __net_init int pppoe_init_net(struct net *net)
+{
+ struct pppoe_net *pn = pppoe_pernet(net);
+ struct proc_dir_entry *pde;
+
+ rwlock_init(&pn->hash_lock);
+
+ pde = proc_net_fops_create(net, "pppoe", S_IRUGO, &pppoe_seq_fops);
+#ifdef CONFIG_PROC_FS
+ if (!pde)
+ return -ENOMEM;
+#endif
+
+ return 0;
+}
+
+static __net_exit void pppoe_exit_net(struct net *net)
+{
+ proc_net_remove(net, "pppoe");
+}
+
+static struct pernet_operations pppoe_net_ops = {
+ .init = pppoe_init_net,
+ .exit = pppoe_exit_net,
+ .id = &pppoe_net_id,
+ .size = sizeof(struct pppoe_net),
+};
+
+static int __init pppoe_init(void)
+{
+ int err;
+
+ err = register_pernet_device(&pppoe_net_ops);
+ if (err)
+ goto out;
+
+ err = proto_register(&pppoe_sk_proto, 0);
+ if (err)
+ goto out_unregister_net_ops;
+
+ err = register_pppox_proto(PX_PROTO_OE, &pppoe_proto);
+ if (err)
+ goto out_unregister_pppoe_proto;
+
+ dev_add_pack(&pppoes_ptype);
+ dev_add_pack(&pppoed_ptype);
+ register_netdevice_notifier(&pppoe_notifier);
+
+ return 0;
+
+out_unregister_pppoe_proto:
+ proto_unregister(&pppoe_sk_proto);
+out_unregister_net_ops:
+ unregister_pernet_device(&pppoe_net_ops);
+out:
+ return err;
+}
+
+static void __exit pppoe_exit(void)
+{
+ unregister_netdevice_notifier(&pppoe_notifier);
+ dev_remove_pack(&pppoed_ptype);
+ dev_remove_pack(&pppoes_ptype);
+ unregister_pppox_proto(PX_PROTO_OE);
+ proto_unregister(&pppoe_sk_proto);
+ unregister_pernet_device(&pppoe_net_ops);
+}
+
+module_init(pppoe_init);
+module_exit(pppoe_exit);
+
+MODULE_AUTHOR("Michal Ostrowski <mostrows@speakeasy.net>");
+MODULE_DESCRIPTION("PPP over Ethernet driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_NETPROTO(PF_PPPOX);
diff --git a/drivers/net/ppp/pppox.c b/drivers/net/ppp/pppox.c
new file mode 100644
index 0000000..8c0d170
--- /dev/null
+++ b/drivers/net/ppp/pppox.c
@@ -0,0 +1,149 @@
+/** -*- linux-c -*- ***********************************************************
+ * Linux PPP over X/Ethernet (PPPoX/PPPoE) Sockets
+ *
+ * PPPoX --- Generic PPP encapsulation socket family
+ * PPPoE --- PPP over Ethernet (RFC 2516)
+ *
+ *
+ * Version: 0.5.2
+ *
+ * Author: Michal Ostrowski <mostrows@speakeasy.net>
+ *
+ * 051000 : Initialization cleanup
+ *
+ * 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.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/net.h>
+#include <linux/init.h>
+#include <linux/if_pppox.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/kmod.h>
+
+#include <net/sock.h>
+
+#include <asm/uaccess.h>
+
+static const struct pppox_proto *pppox_protos[PX_MAX_PROTO + 1];
+
+int register_pppox_proto(int proto_num, const struct pppox_proto *pp)
+{
+ if (proto_num < 0 || proto_num > PX_MAX_PROTO)
+ return -EINVAL;
+ if (pppox_protos[proto_num])
+ return -EALREADY;
+ pppox_protos[proto_num] = pp;
+ return 0;
+}
+
+void unregister_pppox_proto(int proto_num)
+{
+ if (proto_num >= 0 && proto_num <= PX_MAX_PROTO)
+ pppox_protos[proto_num] = NULL;
+}
+
+void pppox_unbind_sock(struct sock *sk)
+{
+ /* Clear connection to ppp device, if attached. */
+
+ if (sk->sk_state & (PPPOX_BOUND | PPPOX_CONNECTED | PPPOX_ZOMBIE)) {
+ ppp_unregister_channel(&pppox_sk(sk)->chan);
+ sk->sk_state = PPPOX_DEAD;
+ }
+}
+
+EXPORT_SYMBOL(register_pppox_proto);
+EXPORT_SYMBOL(unregister_pppox_proto);
+EXPORT_SYMBOL(pppox_unbind_sock);
+
+int pppox_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_sock *po = pppox_sk(sk);
+ int rc;
+
+ lock_sock(sk);
+
+ switch (cmd) {
+ case PPPIOCGCHAN: {
+ int index;
+ rc = -ENOTCONN;
+ if (!(sk->sk_state & PPPOX_CONNECTED))
+ break;
+
+ rc = -EINVAL;
+ index = ppp_channel_index(&po->chan);
+ if (put_user(index , (int __user *) arg))
+ break;
+
+ rc = 0;
+ sk->sk_state |= PPPOX_BOUND;
+ break;
+ }
+ default:
+ rc = pppox_protos[sk->sk_protocol]->ioctl ?
+ pppox_protos[sk->sk_protocol]->ioctl(sock, cmd, arg) : -ENOTTY;
+ }
+
+ release_sock(sk);
+ return rc;
+}
+
+EXPORT_SYMBOL(pppox_ioctl);
+
+static int pppox_create(struct net *net, struct socket *sock, int protocol,
+ int kern)
+{
+ int rc = -EPROTOTYPE;
+
+ if (protocol < 0 || protocol > PX_MAX_PROTO)
+ goto out;
+
+ rc = -EPROTONOSUPPORT;
+ if (!pppox_protos[protocol])
+ request_module("pppox-proto-%d", protocol);
+ if (!pppox_protos[protocol] ||
+ !try_module_get(pppox_protos[protocol]->owner))
+ goto out;
+
+ rc = pppox_protos[protocol]->create(net, sock);
+
+ module_put(pppox_protos[protocol]->owner);
+out:
+ return rc;
+}
+
+static const struct net_proto_family pppox_proto_family = {
+ .family = PF_PPPOX,
+ .create = pppox_create,
+ .owner = THIS_MODULE,
+};
+
+static int __init pppox_init(void)
+{
+ return sock_register(&pppox_proto_family);
+}
+
+static void __exit pppox_exit(void)
+{
+ sock_unregister(PF_PPPOX);
+}
+
+module_init(pppox_init);
+module_exit(pppox_exit);
+
+MODULE_AUTHOR("Michal Ostrowski <mostrows@speakeasy.net>");
+MODULE_DESCRIPTION("PPP over Ethernet driver (generic socket layer)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/ppp/pptp.c b/drivers/net/ppp/pptp.c
new file mode 100644
index 0000000..eae542a
--- /dev/null
+++ b/drivers/net/ppp/pptp.c
@@ -0,0 +1,717 @@
+/*
+ * Point-to-Point Tunneling Protocol for Linux
+ *
+ * Authors: Dmitry Kozlov <xeb@mail.ru>
+ *
+ * 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 <linux/string.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/vmalloc.h>
+#include <linux/init.h>
+#include <linux/ppp_channel.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_pppox.h>
+#include <linux/if_ppp.h>
+#include <linux/notifier.h>
+#include <linux/file.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/rcupdate.h>
+#include <linux/spinlock.h>
+
+#include <net/sock.h>
+#include <net/protocol.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/route.h>
+#include <net/gre.h>
+
+#include <linux/uaccess.h>
+
+#define PPTP_DRIVER_VERSION "0.8.5"
+
+#define MAX_CALLID 65535
+
+static DECLARE_BITMAP(callid_bitmap, MAX_CALLID + 1);
+static struct pppox_sock **callid_sock;
+
+static DEFINE_SPINLOCK(chan_lock);
+
+static struct proto pptp_sk_proto __read_mostly;
+static const struct ppp_channel_ops pptp_chan_ops;
+static const struct proto_ops pptp_ops;
+
+#define PPP_LCP_ECHOREQ 0x09
+#define PPP_LCP_ECHOREP 0x0A
+#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+
+#define MISSING_WINDOW 20
+#define WRAPPED(curseq, lastseq)\
+ ((((curseq) & 0xffffff00) == 0) &&\
+ (((lastseq) & 0xffffff00) == 0xffffff00))
+
+#define PPTP_GRE_PROTO 0x880B
+#define PPTP_GRE_VER 0x1
+
+#define PPTP_GRE_FLAG_C 0x80
+#define PPTP_GRE_FLAG_R 0x40
+#define PPTP_GRE_FLAG_K 0x20
+#define PPTP_GRE_FLAG_S 0x10
+#define PPTP_GRE_FLAG_A 0x80
+
+#define PPTP_GRE_IS_C(f) ((f)&PPTP_GRE_FLAG_C)
+#define PPTP_GRE_IS_R(f) ((f)&PPTP_GRE_FLAG_R)
+#define PPTP_GRE_IS_K(f) ((f)&PPTP_GRE_FLAG_K)
+#define PPTP_GRE_IS_S(f) ((f)&PPTP_GRE_FLAG_S)
+#define PPTP_GRE_IS_A(f) ((f)&PPTP_GRE_FLAG_A)
+
+#define PPTP_HEADER_OVERHEAD (2+sizeof(struct pptp_gre_header))
+struct pptp_gre_header {
+ u8 flags;
+ u8 ver;
+ u16 protocol;
+ u16 payload_len;
+ u16 call_id;
+ u32 seq;
+ u32 ack;
+} __packed;
+
+static struct pppox_sock *lookup_chan(u16 call_id, __be32 s_addr)
+{
+ struct pppox_sock *sock;
+ struct pptp_opt *opt;
+
+ rcu_read_lock();
+ sock = rcu_dereference(callid_sock[call_id]);
+ if (sock) {
+ opt = &sock->proto.pptp;
+ if (opt->dst_addr.sin_addr.s_addr != s_addr)
+ sock = NULL;
+ else
+ sock_hold(sk_pppox(sock));
+ }
+ rcu_read_unlock();
+
+ return sock;
+}
+
+static int lookup_chan_dst(u16 call_id, __be32 d_addr)
+{
+ struct pppox_sock *sock;
+ struct pptp_opt *opt;
+ int i;
+
+ rcu_read_lock();
+ for (i = find_next_bit(callid_bitmap, MAX_CALLID, 1); i < MAX_CALLID;
+ i = find_next_bit(callid_bitmap, MAX_CALLID, i + 1)) {
+ sock = rcu_dereference(callid_sock[i]);
+ if (!sock)
+ continue;
+ opt = &sock->proto.pptp;
+ if (opt->dst_addr.call_id == call_id &&
+ opt->dst_addr.sin_addr.s_addr == d_addr)
+ break;
+ }
+ rcu_read_unlock();
+
+ return i < MAX_CALLID;
+}
+
+static int add_chan(struct pppox_sock *sock)
+{
+ static int call_id;
+
+ spin_lock(&chan_lock);
+ if (!sock->proto.pptp.src_addr.call_id) {
+ call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, call_id + 1);
+ if (call_id == MAX_CALLID) {
+ call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, 1);
+ if (call_id == MAX_CALLID)
+ goto out_err;
+ }
+ sock->proto.pptp.src_addr.call_id = call_id;
+ } else if (test_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap))
+ goto out_err;
+
+ set_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap);
+ rcu_assign_pointer(callid_sock[sock->proto.pptp.src_addr.call_id], sock);
+ spin_unlock(&chan_lock);
+
+ return 0;
+
+out_err:
+ spin_unlock(&chan_lock);
+ return -1;
+}
+
+static void del_chan(struct pppox_sock *sock)
+{
+ spin_lock(&chan_lock);
+ clear_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap);
+ rcu_assign_pointer(callid_sock[sock->proto.pptp.src_addr.call_id], NULL);
+ spin_unlock(&chan_lock);
+ synchronize_rcu();
+}
+
+static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct sock *sk = (struct sock *) chan->private;
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pptp_opt *opt = &po->proto.pptp;
+ struct pptp_gre_header *hdr;
+ unsigned int header_len = sizeof(*hdr);
+ struct flowi4 fl4;
+ int islcp;
+ int len;
+ unsigned char *data;
+ __u32 seq_recv;
+
+
+ struct rtable *rt;
+ struct net_device *tdev;
+ struct iphdr *iph;
+ int max_headroom;
+
+ if (sk_pppox(po)->sk_state & PPPOX_DEAD)
+ goto tx_error;
+
+ rt = ip_route_output_ports(&init_net, &fl4, NULL,
+ opt->dst_addr.sin_addr.s_addr,
+ opt->src_addr.sin_addr.s_addr,
+ 0, 0, IPPROTO_GRE,
+ RT_TOS(0), 0);
+ if (IS_ERR(rt))
+ goto tx_error;
+
+ tdev = rt->dst.dev;
+
+ max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(*iph) + sizeof(*hdr) + 2;
+
+ if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
+ struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+ if (!new_skb) {
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+ if (skb->sk)
+ skb_set_owner_w(new_skb, skb->sk);
+ kfree_skb(skb);
+ skb = new_skb;
+ }
+
+ data = skb->data;
+ islcp = ((data[0] << 8) + data[1]) == PPP_LCP && 1 <= data[2] && data[2] <= 7;
+
+ /* compress protocol field */
+ if ((opt->ppp_flags & SC_COMP_PROT) && data[0] == 0 && !islcp)
+ skb_pull(skb, 1);
+
+ /* Put in the address/control bytes if necessary */
+ if ((opt->ppp_flags & SC_COMP_AC) == 0 || islcp) {
+ data = skb_push(skb, 2);
+ data[0] = PPP_ALLSTATIONS;
+ data[1] = PPP_UI;
+ }
+
+ len = skb->len;
+
+ seq_recv = opt->seq_recv;
+
+ if (opt->ack_sent == seq_recv)
+ header_len -= sizeof(hdr->ack);
+
+ /* Push down and install GRE header */
+ skb_push(skb, header_len);
+ hdr = (struct pptp_gre_header *)(skb->data);
+
+ hdr->flags = PPTP_GRE_FLAG_K;
+ hdr->ver = PPTP_GRE_VER;
+ hdr->protocol = htons(PPTP_GRE_PROTO);
+ hdr->call_id = htons(opt->dst_addr.call_id);
+
+ hdr->flags |= PPTP_GRE_FLAG_S;
+ hdr->seq = htonl(++opt->seq_sent);
+ if (opt->ack_sent != seq_recv) {
+ /* send ack with this message */
+ hdr->ver |= PPTP_GRE_FLAG_A;
+ hdr->ack = htonl(seq_recv);
+ opt->ack_sent = seq_recv;
+ }
+ hdr->payload_len = htons(len);
+
+ /* Push down and install the IP header. */
+
+ skb_reset_transport_header(skb);
+ skb_push(skb, sizeof(*iph));
+ skb_reset_network_header(skb);
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+ IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED | IPSKB_REROUTED);
+
+ iph = ip_hdr(skb);
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr) >> 2;
+ if (ip_dont_fragment(sk, &rt->dst))
+ iph->frag_off = htons(IP_DF);
+ else
+ iph->frag_off = 0;
+ iph->protocol = IPPROTO_GRE;
+ iph->tos = 0;
+ iph->daddr = fl4.daddr;
+ iph->saddr = fl4.saddr;
+ iph->ttl = ip4_dst_hoplimit(&rt->dst);
+ iph->tot_len = htons(skb->len);
+
+ skb_dst_drop(skb);
+ skb_dst_set(skb, &rt->dst);
+
+ nf_reset(skb);
+
+ skb->ip_summed = CHECKSUM_NONE;
+ ip_select_ident(iph, &rt->dst, NULL);
+ ip_send_check(iph);
+
+ ip_local_out(skb);
+
+tx_error:
+ return 1;
+}
+
+static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb)
+{
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pptp_opt *opt = &po->proto.pptp;
+ int headersize, payload_len, seq;
+ __u8 *payload;
+ struct pptp_gre_header *header;
+
+ if (!(sk->sk_state & PPPOX_CONNECTED)) {
+ if (sock_queue_rcv_skb(sk, skb))
+ goto drop;
+ return NET_RX_SUCCESS;
+ }
+
+ header = (struct pptp_gre_header *)(skb->data);
+
+ /* test if acknowledgement present */
+ if (PPTP_GRE_IS_A(header->ver)) {
+ __u32 ack = (PPTP_GRE_IS_S(header->flags)) ?
+ header->ack : header->seq; /* ack in different place if S = 0 */
+
+ ack = ntohl(ack);
+
+ if (ack > opt->ack_recv)
+ opt->ack_recv = ack;
+ /* also handle sequence number wrap-around */
+ if (WRAPPED(ack, opt->ack_recv))
+ opt->ack_recv = ack;
+ }
+
+ /* test if payload present */
+ if (!PPTP_GRE_IS_S(header->flags))
+ goto drop;
+
+ headersize = sizeof(*header);
+ payload_len = ntohs(header->payload_len);
+ seq = ntohl(header->seq);
+
+ /* no ack present? */
+ if (!PPTP_GRE_IS_A(header->ver))
+ headersize -= sizeof(header->ack);
+ /* check for incomplete packet (length smaller than expected) */
+ if (skb->len - headersize < payload_len)
+ goto drop;
+
+ payload = skb->data + headersize;
+ /* check for expected sequence number */
+ if (seq < opt->seq_recv + 1 || WRAPPED(opt->seq_recv, seq)) {
+ if ((payload[0] == PPP_ALLSTATIONS) && (payload[1] == PPP_UI) &&
+ (PPP_PROTOCOL(payload) == PPP_LCP) &&
+ ((payload[4] == PPP_LCP_ECHOREQ) || (payload[4] == PPP_LCP_ECHOREP)))
+ goto allow_packet;
+ } else {
+ opt->seq_recv = seq;
+allow_packet:
+ skb_pull(skb, headersize);
+
+ if (payload[0] == PPP_ALLSTATIONS && payload[1] == PPP_UI) {
+ /* chop off address/control */
+ if (skb->len < 3)
+ goto drop;
+ skb_pull(skb, 2);
+ }
+
+ if ((*skb->data) & 1) {
+ /* protocol is compressed */
+ skb_push(skb, 1)[0] = 0;
+ }
+
+ skb->ip_summed = CHECKSUM_NONE;
+ skb_set_network_header(skb, skb->head-skb->data);
+ ppp_input(&po->chan, skb);
+
+ return NET_RX_SUCCESS;
+ }
+drop:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+static int pptp_rcv(struct sk_buff *skb)
+{
+ struct pppox_sock *po;
+ struct pptp_gre_header *header;
+ struct iphdr *iph;
+
+ if (skb->pkt_type != PACKET_HOST)
+ goto drop;
+
+ if (!pskb_may_pull(skb, 12))
+ goto drop;
+
+ iph = ip_hdr(skb);
+
+ header = (struct pptp_gre_header *)skb->data;
+
+ if (ntohs(header->protocol) != PPTP_GRE_PROTO || /* PPTP-GRE protocol for PPTP */
+ PPTP_GRE_IS_C(header->flags) || /* flag C should be clear */
+ PPTP_GRE_IS_R(header->flags) || /* flag R should be clear */
+ !PPTP_GRE_IS_K(header->flags) || /* flag K should be set */
+ (header->flags&0xF) != 0) /* routing and recursion ctrl = 0 */
+ /* if invalid, discard this packet */
+ goto drop;
+
+ po = lookup_chan(htons(header->call_id), iph->saddr);
+ if (po) {
+ skb_dst_drop(skb);
+ nf_reset(skb);
+ return sk_receive_skb(sk_pppox(po), skb, 0);
+ }
+drop:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+static int pptp_bind(struct socket *sock, struct sockaddr *uservaddr,
+ int sockaddr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pptp_opt *opt = &po->proto.pptp;
+ int error = 0;
+
+ lock_sock(sk);
+
+ opt->src_addr = sp->sa_addr.pptp;
+ if (add_chan(po)) {
+ release_sock(sk);
+ error = -EBUSY;
+ }
+
+ release_sock(sk);
+ return error;
+}
+
+static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr,
+ int sockaddr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr;
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pptp_opt *opt = &po->proto.pptp;
+ struct rtable *rt;
+ struct flowi4 fl4;
+ int error = 0;
+
+ if (sp->sa_protocol != PX_PROTO_PPTP)
+ return -EINVAL;
+
+ if (lookup_chan_dst(sp->sa_addr.pptp.call_id, sp->sa_addr.pptp.sin_addr.s_addr))
+ return -EALREADY;
+
+ lock_sock(sk);
+ /* Check for already bound sockets */
+ if (sk->sk_state & PPPOX_CONNECTED) {
+ error = -EBUSY;
+ goto end;
+ }
+
+ /* Check for already disconnected sockets, on attempts to disconnect */
+ if (sk->sk_state & PPPOX_DEAD) {
+ error = -EALREADY;
+ goto end;
+ }
+
+ if (!opt->src_addr.sin_addr.s_addr || !sp->sa_addr.pptp.sin_addr.s_addr) {
+ error = -EINVAL;
+ goto end;
+ }
+
+ po->chan.private = sk;
+ po->chan.ops = &pptp_chan_ops;
+
+ rt = ip_route_output_ports(&init_net, &fl4, sk,
+ opt->dst_addr.sin_addr.s_addr,
+ opt->src_addr.sin_addr.s_addr,
+ 0, 0,
+ IPPROTO_GRE, RT_CONN_FLAGS(sk), 0);
+ if (IS_ERR(rt)) {
+ error = -EHOSTUNREACH;
+ goto end;
+ }
+ sk_setup_caps(sk, &rt->dst);
+
+ po->chan.mtu = dst_mtu(&rt->dst);
+ if (!po->chan.mtu)
+ po->chan.mtu = PPP_MTU;
+ ip_rt_put(rt);
+ po->chan.mtu -= PPTP_HEADER_OVERHEAD;
+
+ po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header);
+ error = ppp_register_channel(&po->chan);
+ if (error) {
+ pr_err("PPTP: failed to register PPP channel (%d)\n", error);
+ goto end;
+ }
+
+ opt->dst_addr = sp->sa_addr.pptp;
+ sk->sk_state = PPPOX_CONNECTED;
+
+ end:
+ release_sock(sk);
+ return error;
+}
+
+static int pptp_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *usockaddr_len, int peer)
+{
+ int len = sizeof(struct sockaddr_pppox);
+ struct sockaddr_pppox sp;
+
+ sp.sa_family = AF_PPPOX;
+ sp.sa_protocol = PX_PROTO_PPTP;
+ sp.sa_addr.pptp = pppox_sk(sock->sk)->proto.pptp.src_addr;
+
+ memcpy(uaddr, &sp, len);
+
+ *usockaddr_len = len;
+
+ return 0;
+}
+
+static int pptp_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct pppox_sock *po;
+ struct pptp_opt *opt;
+ int error = 0;
+
+ if (!sk)
+ return 0;
+
+ lock_sock(sk);
+
+ if (sock_flag(sk, SOCK_DEAD)) {
+ release_sock(sk);
+ return -EBADF;
+ }
+
+ po = pppox_sk(sk);
+ opt = &po->proto.pptp;
+ del_chan(po);
+
+ pppox_unbind_sock(sk);
+ sk->sk_state = PPPOX_DEAD;
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ release_sock(sk);
+ sock_put(sk);
+
+ return error;
+}
+
+static void pptp_sock_destruct(struct sock *sk)
+{
+ if (!(sk->sk_state & PPPOX_DEAD)) {
+ del_chan(pppox_sk(sk));
+ pppox_unbind_sock(sk);
+ }
+ skb_queue_purge(&sk->sk_receive_queue);
+}
+
+static int pptp_create(struct net *net, struct socket *sock)
+{
+ int error = -ENOMEM;
+ struct sock *sk;
+ struct pppox_sock *po;
+ struct pptp_opt *opt;
+
+ sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pptp_sk_proto);
+ if (!sk)
+ goto out;
+
+ sock_init_data(sock, sk);
+
+ sock->state = SS_UNCONNECTED;
+ sock->ops = &pptp_ops;
+
+ sk->sk_backlog_rcv = pptp_rcv_core;
+ sk->sk_state = PPPOX_NONE;
+ sk->sk_type = SOCK_STREAM;
+ sk->sk_family = PF_PPPOX;
+ sk->sk_protocol = PX_PROTO_PPTP;
+ sk->sk_destruct = pptp_sock_destruct;
+
+ po = pppox_sk(sk);
+ opt = &po->proto.pptp;
+
+ opt->seq_sent = 0; opt->seq_recv = 0;
+ opt->ack_recv = 0; opt->ack_sent = 0;
+
+ error = 0;
+out:
+ return error;
+}
+
+static int pptp_ppp_ioctl(struct ppp_channel *chan, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sock *sk = (struct sock *) chan->private;
+ struct pppox_sock *po = pppox_sk(sk);
+ struct pptp_opt *opt = &po->proto.pptp;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+ int err, val;
+
+ err = -EFAULT;
+ switch (cmd) {
+ case PPPIOCGFLAGS:
+ val = opt->ppp_flags;
+ if (put_user(val, p))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSFLAGS:
+ if (get_user(val, p))
+ break;
+ opt->ppp_flags = val & ~SC_RCV_BITS;
+ err = 0;
+ break;
+ default:
+ err = -ENOTTY;
+ }
+
+ return err;
+}
+
+static const struct ppp_channel_ops pptp_chan_ops = {
+ .start_xmit = pptp_xmit,
+ .ioctl = pptp_ppp_ioctl,
+};
+
+static struct proto pptp_sk_proto __read_mostly = {
+ .name = "PPTP",
+ .owner = THIS_MODULE,
+ .obj_size = sizeof(struct pppox_sock),
+};
+
+static const struct proto_ops pptp_ops = {
+ .family = AF_PPPOX,
+ .owner = THIS_MODULE,
+ .release = pptp_release,
+ .bind = pptp_bind,
+ .connect = pptp_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = pptp_getname,
+ .poll = sock_no_poll,
+ .listen = sock_no_listen,
+ .shutdown = sock_no_shutdown,
+ .setsockopt = sock_no_setsockopt,
+ .getsockopt = sock_no_getsockopt,
+ .sendmsg = sock_no_sendmsg,
+ .recvmsg = sock_no_recvmsg,
+ .mmap = sock_no_mmap,
+ .ioctl = pppox_ioctl,
+};
+
+static const struct pppox_proto pppox_pptp_proto = {
+ .create = pptp_create,
+ .owner = THIS_MODULE,
+};
+
+static const struct gre_protocol gre_pptp_protocol = {
+ .handler = pptp_rcv,
+};
+
+static int __init pptp_init_module(void)
+{
+ int err = 0;
+ pr_info("PPTP driver version " PPTP_DRIVER_VERSION "\n");
+
+ callid_sock = vzalloc((MAX_CALLID + 1) * sizeof(void *));
+ if (!callid_sock) {
+ pr_err("PPTP: cann't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ err = gre_add_protocol(&gre_pptp_protocol, GREPROTO_PPTP);
+ if (err) {
+ pr_err("PPTP: can't add gre protocol\n");
+ goto out_mem_free;
+ }
+
+ err = proto_register(&pptp_sk_proto, 0);
+ if (err) {
+ pr_err("PPTP: can't register sk_proto\n");
+ goto out_gre_del_protocol;
+ }
+
+ err = register_pppox_proto(PX_PROTO_PPTP, &pppox_pptp_proto);
+ if (err) {
+ pr_err("PPTP: can't register pppox_proto\n");
+ goto out_unregister_sk_proto;
+ }
+
+ return 0;
+
+out_unregister_sk_proto:
+ proto_unregister(&pptp_sk_proto);
+out_gre_del_protocol:
+ gre_del_protocol(&gre_pptp_protocol, GREPROTO_PPTP);
+out_mem_free:
+ vfree(callid_sock);
+
+ return err;
+}
+
+static void __exit pptp_exit_module(void)
+{
+ unregister_pppox_proto(PX_PROTO_PPTP);
+ proto_unregister(&pptp_sk_proto);
+ gre_del_protocol(&gre_pptp_protocol, GREPROTO_PPTP);
+ vfree(callid_sock);
+}
+
+module_init(pptp_init_module);
+module_exit(pptp_exit_module);
+
+MODULE_DESCRIPTION("Point-to-Point Tunneling Protocol");
+MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)");
+MODULE_LICENSE("GPL");