changes to linux interface for new kernel driver,
authorPaul Mackerras <paulus@samba.org>
Mon, 27 Mar 2000 06:03:07 +0000 (06:03 +0000)
committerPaul Mackerras <paulus@samba.org>
Mon, 27 Mar 2000 06:03:07 +0000 (06:03 +0000)
plus the start of multilink support

pppd/Makefile.linux
pppd/auth.c
pppd/lcp.c
pppd/lcp.h
pppd/main.c
pppd/options.c
pppd/pppd.h
pppd/sys-linux.c

index 0e404fa4e6d2762aa55b9d1a3e98b11a258c8b5b..f36d841686276b3fb9c4589d4403c3df23e84f50 100644 (file)
@@ -1,6 +1,6 @@
 #
 # pppd makefile for Linux
-# $Id: Makefile.linux,v 1.35 1999/12/23 01:23:07 paulus Exp $
+# $Id: Makefile.linux,v 1.36 2000/03/27 06:02:59 paulus Exp $
 #
 
 # Default installation locations
@@ -50,7 +50,7 @@ PLUGIN=y
 
 INCLUDE_DIRS= -I../include
 
-COMPILE_FLAGS= -D_linux_=1 -DHAVE_PATHS_H -DIPX_CHANGE
+COMPILE_FLAGS= -D_linux_=1 -DHAVE_PATHS_H -DIPX_CHANGE -DHAVE_MULTILINK
 
 CFLAGS= $(COPTS) $(COMPILE_FLAGS) $(INCLUDE_DIRS)
 
index 4dfcb4382bdb2801e141ae6116bb84beeb3d956f..3d466e2e52fa097f95b72dac09ab50d4d71a9ea1 100644 (file)
@@ -32,7 +32,7 @@
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#define RCSID  "$Id: auth.c,v 1.61 2000/03/13 23:25:46 paulus Exp $"
+#define RCSID  "$Id: auth.c,v 1.62 2000/03/27 06:02:59 paulus Exp $"
 
 #include <stdio.h>
 #include <stddef.h>
@@ -509,6 +509,7 @@ start_networks()
     struct protent *protp;
 
     new_phase(PHASE_NETWORK);
+
 #if 0
     if (!demand)
        set_filters(&pass_filter, &active_filter);
index 2d90432f337ab68fa1c473f90ef35eb645f92126..231b7e4552861ea828cb361e9923bccf83c061d3 100644 (file)
@@ -17,7 +17,7 @@
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#define RCSID  "$Id: lcp.c,v 1.47 1999/12/23 01:27:28 paulus Exp $";
+#define RCSID  "$Id: lcp.c,v 1.48 2000/03/27 06:02:59 paulus Exp $";
 
 /*
  * TODO:
@@ -107,6 +107,14 @@ static option_t lcp_option_list[] = {
       "Set limit on number of LCP configure-naks" },
     { "receive-all", o_bool, &lax_recv,
       "Accept all received control characters", 1 },
+#ifdef HAVE_MULTILINK
+    { "mpshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
+      "Use short sequence numbers in multilink headers",
+      OPT_A2COPY, &lcp_allowoptions[0].neg_ssnhf },
+    { "nompshortseq", o_bool, &lcp_wantoptions[0].neg_ssnhf,
+      "Don't use short sequence numbers in multilink headers",
+      OPT_A2COPY, &lcp_allowoptions[0].neg_ssnhf },
+#endif /* HAVE_MULTILINK */
     {NULL}
 };
 
@@ -530,9 +538,13 @@ static void
 lcp_resetci(f)
     fsm *f;
 {
-    lcp_wantoptions[f->unit].magicnumber = magic();
-    lcp_wantoptions[f->unit].numloops = 0;
-    lcp_gotoptions[f->unit] = lcp_wantoptions[f->unit];
+    lcp_options *wo = &lcp_wantoptions[f->unit];
+
+    wo->magicnumber = magic();
+    wo->numloops = 0;
+    if (!wo->neg_multilink)
+       wo->neg_ssnhf = 0;
+    lcp_gotoptions[f->unit] = *wo;
     peer_mru[f->unit] = PPP_MRU;
     auth_reset(f->unit);
 }
@@ -565,7 +577,10 @@ lcp_cilen(f)
            LENCICBCP(go->neg_cbcp) +
            LENCILONG(go->neg_magicnumber) +
            LENCIVOID(go->neg_pcompression) +
-           LENCIVOID(go->neg_accompression));
+           LENCIVOID(go->neg_accompression) +
+           LENCISHORT(go->neg_multilink) +
+           LENCIVOID(go->neg_ssnhf) +
+           (go->neg_endpoint? CILEN_CHAR + go->endp_len: 0));
 }
 
 
@@ -618,6 +633,15 @@ lcp_addci(f, ucp, lenp)
        PUTCHAR(CILEN_CHAR, ucp); \
        PUTCHAR(val, ucp); \
     }
+#define ADDCIENDP(opt, neg, class, val, len) \
+    if (neg) { \
+       int i; \
+       PUTCHAR(opt, ucp); \
+       PUTCHAR(CILEN_CHAR + len, ucp); \
+       PUTCHAR(class, ucp); \
+       for (i = 0; i < len; ++i) \
+           PUTCHAR(val[i], ucp); \
+    }
 
     ADDCISHORT(CI_MRU, go->neg_mru && go->mru != DEFMRU, go->mru);
     ADDCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
@@ -629,6 +653,10 @@ lcp_addci(f, ucp, lenp)
     ADDCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
     ADDCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
     ADDCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
+    ADDCISHORT(CI_MRRU, go->neg_multilink, go->mrru);
+    ADDCIVOID(CI_SSNHF, go->neg_ssnhf);
+    ADDCIENDP(CI_EPDISC, go->neg_endpoint, go->endp_class, go->endpoint,
+             go->endp_len);
 
     if (ucp - start_ucp != *lenp) {
        /* this should never happen, because peer_mtu should be 1500 */
@@ -742,6 +770,25 @@ lcp_ackci(f, p, len)
        if (cilong != val) \
          goto bad; \
     }
+#define ACKCIENDP(opt, neg, class, val, vlen) \
+    if (neg) { \
+       int i; \
+       if ((len -= CILEN_CHAR + vlen) < 0) \
+           goto bad; \
+       GETCHAR(citype, p); \
+       GETCHAR(cilen, p); \
+       if (cilen != CILEN_CHAR + vlen || \
+           citype != opt) \
+           goto bad; \
+       GETCHAR(cichar, p); \
+       if (cichar != class) \
+           goto bad; \
+       for (i = 0; i < vlen; ++i) { \
+           GETCHAR(cichar, p); \
+           if (cichar != val[i]) \
+               goto bad; \
+       } \
+    }
 
     ACKCISHORT(CI_MRU, go->neg_mru && go->mru != DEFMRU, go->mru);
     ACKCILONG(CI_ASYNCMAP, go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF,
@@ -753,6 +800,10 @@ lcp_ackci(f, p, len)
     ACKCILONG(CI_MAGICNUMBER, go->neg_magicnumber, go->magicnumber);
     ACKCIVOID(CI_PCOMPRESSION, go->neg_pcompression);
     ACKCIVOID(CI_ACCOMPRESSION, go->neg_accompression);
+    ACKCISHORT(CI_MRRU, go->neg_multilink, go->mrru);
+    ACKCIVOID(CI_SSNHF, go->neg_ssnhf);
+    ACKCIENDP(CI_EPDISC, go->neg_endpoint, go->endp_class, go->endpoint,
+             go->endp_len);
 
     /*
      * If there are any remaining CIs, then this packet is bad.
@@ -799,7 +850,7 @@ lcp_nakci(f, p, len)
      * Check packet length and CI length at each step.
      * If we find any deviations, then this packet is bad.
      */
-#define NAKCIVOID(opt, neg, code) \
+#define NAKCIVOID(opt, neg) \
     if (go->neg && \
        len >= CILEN_VOID && \
        p[1] == CILEN_VOID && \
@@ -807,7 +858,7 @@ lcp_nakci(f, p, len)
        len -= CILEN_VOID; \
        INCPTR(CILEN_VOID, p); \
        no.neg = 1; \
-       code \
+       try.neg = 0; \
     }
 #define NAKCICHAP(opt, neg, code) \
     if (go->neg && \
@@ -866,6 +917,17 @@ lcp_nakci(f, p, len)
        no.neg = 1; \
        code \
     }
+#define NAKCIENDP(opt, neg) \
+    if (go->neg && \
+       len >= CILEN_CHAR && \
+       p[0] == opt && \
+       p[1] >= CILEN_CHAR && \
+       p[1] <= len) { \
+       len -= p[1]; \
+       INCPTR(p[1], p); \
+       no.neg = 1; \
+       try.neg = 0; \
+    }
 
     /*
      * We don't care if they want to send us smaller packets than
@@ -983,12 +1045,31 @@ lcp_nakci(f, p, len)
      * address/control compression requests; they should send
      * a Reject instead.  If they send a Nak, treat it as a Reject.
      */
-    NAKCIVOID(CI_PCOMPRESSION, neg_pcompression,
-             try.neg_pcompression = 0;
-             );
-    NAKCIVOID(CI_ACCOMPRESSION, neg_accompression,
-             try.neg_accompression = 0;
-             );
+    NAKCIVOID(CI_PCOMPRESSION, neg_pcompression);
+    NAKCIVOID(CI_ACCOMPRESSION, neg_accompression);
+
+    /*
+     * Nak for MRRU option - accept their value if it is smaller
+     * than the one we want.
+     */
+    if (go->neg_multilink) {
+       NAKCISHORT(CI_MRRU, neg_multilink,
+                  if (cishort <= wo->mrru)
+                      try.mrru = cishort;
+                  );
+    }
+
+    /*
+     * Nak for short sequence numbers shouldn't be sent, treat it
+     * like a reject.
+     */
+    NAKCIVOID(CI_SSNHF, neg_ssnhf);
+
+    /*
+     * Nak of the endpoint discriminator option is not permitted,
+     * treat it like a reject.
+     */
+    NAKCIENDP(CI_EPDISC, neg_endpoint);
 
     /*
      * There may be remaining CIs, if the peer is requesting negotiation
@@ -1019,8 +1100,10 @@ lcp_nakci(f, p, len)
                || no.neg_mru || cilen != CILEN_SHORT)
                goto bad;
            GETSHORT(cishort, p);
-           if (cishort < DEFMRU)
+           if (cishort < DEFMRU) {
+               try.neg_mru = 1;
                try.mru = cishort;
+           }
            break;
        case CI_ASYNCMAP:
            if ((go->neg_asyncmap && go->asyncmap != 0xFFFFFFFF)
@@ -1050,6 +1133,19 @@ lcp_nakci(f, p, len)
            if (go->neg_lqr || no.neg_lqr || cilen != CILEN_LQR)
                goto bad;
            break;
+       case CI_MRRU:
+           if (go->neg_multilink || no.neg_multilink || cilen != CILEN_SHORT)
+               goto bad;
+           break;
+       case CI_SSNHF:
+           if (go->neg_ssnhf || no.neg_ssnhf || cilen != CILEN_VOID)
+               goto bad;
+           try.neg_ssnhf = 1;
+           break;
+       case CI_EPDISC:
+           if (go->neg_endpoint || no.neg_endpoint || cilen < CILEN_CHAR)
+               goto bad;
+           break;
        }
        p = next;
     }
@@ -1183,6 +1279,24 @@ lcp_rejci(f, p, len)
            goto bad; \
        try.neg = 0; \
     }
+#define REJCIENDP(opt, neg, class, val, vlen) \
+    if (go->neg && \
+       len >= CILEN_CHAR + vlen && \
+       p[0] == opt && \
+       p[1] == CILEN_CHAR + vlen) { \
+       int i; \
+       len -= CILEN_CHAR + vlen; \
+       INCPTR(p[1], p); \
+       GETCHAR(cichar, p); \
+       if (cichar != class) \
+           goto bad; \
+       for (i = 0; i < vlen; ++i) { \
+           GETCHAR(cichar, p); \
+           if (cichar != val[i]) \
+               goto bad; \
+       } \
+       try.neg = 0; \
+    }
 
     REJCISHORT(CI_MRU, neg_mru, go->mru);
     REJCILONG(CI_ASYNCMAP, neg_asyncmap, go->asyncmap);
@@ -1195,6 +1309,10 @@ lcp_rejci(f, p, len)
     REJCILONG(CI_MAGICNUMBER, neg_magicnumber, go->magicnumber);
     REJCIVOID(CI_PCOMPRESSION, neg_pcompression);
     REJCIVOID(CI_ACCOMPRESSION, neg_accompression);
+    REJCISHORT(CI_MRRU, neg_multilink, go->mrru);
+    REJCIVOID(CI_SSNHF, neg_ssnhf);
+    REJCIENDP(CI_EPDISC, neg_endpoint, go->endp_class, go->endpoint,
+             go->endp_len);
 
     /*
      * If there are any remaining CIs, then this packet is bad.
@@ -1477,6 +1595,44 @@ lcp_reqci(f, inp, lenp, reject_if_disagree)
            ho->neg_accompression = 1;
            break;
 
+       case CI_MRRU:
+           if (!ao->neg_multilink ||
+               cilen != CILEN_SHORT) {
+               orc = CONFREJ;
+               break;
+           }
+
+           GETSHORT(cishort, p);
+           /* possibly should insist on a minimum/maximum MRRU here */
+           ho->neg_multilink = 1;
+           ho->mrru = cishort;
+           break;
+
+       case CI_SSNHF:
+           if (!ao->neg_ssnhf ||
+               cilen != CILEN_VOID) {
+               orc = CONFREJ;
+               break;
+           }
+           ho->neg_ssnhf = 1;
+           break;
+
+       case CI_EPDISC:
+           if (!ao->neg_endpoint ||
+               cilen < CILEN_CHAR ||
+               cilen > CILEN_CHAR + MAX_ENDP_LEN) {
+               orc = CONFREJ;
+               break;
+           }
+           GETCHAR(cichar, p);
+           cilen -= CILEN_CHAR;
+           ho->neg_endpoint = 1;
+           ho->endp_class = cichar;
+           ho->endp_len = cilen;
+           BCOPY(p, ho->endpoint, cilen);
+           INCPTR(cilen, p);
+           break;
+
        default:
            LCPDEBUG(("lcp_reqci: rcvd unknown option %d", citype));
            orc = CONFREJ;
@@ -1627,6 +1783,10 @@ static char *lcp_codenames[] = {
     "EchoReq", "EchoRep", "DiscReq"
 };
 
+static char *endp_class_names[] = {
+    "null", "local", "IP", "MAC", "magic", "phone"
+};
+
 static int
 lcp_printpkt(p, plen, printer, arg)
     u_char *p;
@@ -1634,8 +1794,9 @@ lcp_printpkt(p, plen, printer, arg)
     void (*printer) __P((void *, char *, ...));
     void *arg;
 {
-    int code, id, len, olen;
+    int code, id, len, olen, i;
     u_char *pstart, *optend;
+    u_char cichar;
     u_short cishort;
     u_int32_t cilong;
 
@@ -1763,6 +1924,30 @@ lcp_printpkt(p, plen, printer, arg)
                    printer(arg, "accomp");
                }
                break;
+           case CI_MRRU:
+               if (olen == CILEN_SHORT) {
+                   p += 2;
+                   GETSHORT(cishort, p);
+                   printer(arg, "mrru %d", cishort);
+               }
+               break;
+           case CI_SSNHF:
+               if (olen == CILEN_VOID) {
+                   p += 2;
+                   printer(arg, "ssnhf");
+               }
+               break;
+           case CI_EPDISC:
+               if (olen >= CILEN_CHAR) {
+                   p += 2;
+                   GETCHAR(cichar, p);
+                   if (cichar <= 5)
+                       printer(arg, "endpoint [%s]:",
+                               endp_class_names[cichar]);
+                   else
+                       printer(arg, "endpoint [%d]:");
+               }
+               break;
            }
            while (p < optend) {
                GETCHAR(code, p);
@@ -1795,10 +1980,14 @@ lcp_printpkt(p, plen, printer, arg)
     }
 
     /* print the rest of the bytes in the packet */
-    for (; len > 0; --len) {
+    for (i = 0; i < len && i < 32; ++i) {
        GETCHAR(code, p);
        printer(arg, " %.2x", code);
     }
+    if (i < len) {
+       printer(arg, " ...");
+       p += len - i;
+    }
 
     return p - pstart;
 }
index a7851f671c10f15374024f15942abcade0526acb..f2e7c983e80a0ed8cc1d7b21dde9bbf283f483c5 100644 (file)
@@ -16,7 +16,7 @@
  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  *
- * $Id: lcp.h,v 1.13 1998/11/07 06:59:27 paulus Exp $
+ * $Id: lcp.h,v 1.14 2000/03/27 06:03:00 paulus Exp $
  */
 
 /*
@@ -30,6 +30,9 @@
 #define CI_PCOMPRESSION        7       /* Protocol Field Compression */
 #define CI_ACCOMPRESSION 8     /* Address/Control Field Compression */
 #define CI_CALLBACK    13      /* callback */
+#define CI_MRRU                17      /* max reconstructed receive unit; multilink */
+#define CI_SSNHF       18      /* short sequence numbers for multilink */
+#define CI_EPDISC      19      /* endpoint discriminator */
 
 /*
  * LCP-specific packet types.
@@ -40,6 +43,9 @@
 #define DISCREQ                11      /* Discard Request */
 #define CBCP_OPT       6       /* Use callback control protocol */
 
+/* maximum length of endpoint discriminator value */
+#define MAX_ENDP_LEN   20
+
 /*
  * The state of options is described by an lcp_options structure.
  */
@@ -56,12 +62,19 @@ typedef struct lcp_options {
     bool neg_accompression;    /* HDLC Address/Control Field Compression? */
     bool neg_lqr;              /* Negotiate use of Link Quality Reports */
     bool neg_cbcp;             /* Negotiate use of CBCP */
+    bool neg_multilink;                /* negotiate multilink (MRRU) */
+    bool neg_ssnhf;            /* negotiate short sequence numbers */
+    bool neg_endpoint;         /* negotiate endpoint discriminator */
     int  mru;                  /* Value of MRU */
+    int         mrru;                  /* Value of MRRU, and multilink enable */
     u_char chap_mdtype;                /* which MD type (hashing algorithm) */
     u_int32_t asyncmap;                /* Value of async map */
     u_int32_t magicnumber;
-    int numloops;              /* Number of loops during magic number neg. */
+    int  numloops;             /* Number of loops during magic number neg. */
     u_int32_t lqr_period;      /* Reporting period for LQR 1/100ths second */
+    int  endp_class;           /* endpoint discriminator class */
+    int  endp_len;             /* endpoint discriminator length */
+    u_char endpoint[MAX_ENDP_LEN];     /* endpoint discriminator value */
 } lcp_options;
 
 extern fsm lcp_fsm[];
index 2e0604b4e55be95dd97da6aa648f549fcf2a90cd..982ab55c4e4198ab401793a79f47ff72337e151e 100644 (file)
@@ -17,7 +17,7 @@
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#define RCSID  "$Id: main.c,v 1.90 2000/03/13 23:39:58 paulus Exp $"
+#define RCSID  "$Id: main.c,v 1.91 2000/03/27 06:03:01 paulus Exp $"
 
 #include <stdio.h>
 #include <ctype.h>
@@ -781,7 +781,7 @@ main(argc, argv)
            goto disconnect;
        }
 
-       if (!demand) {
+       if (!demand && ifunit >= 0) {
            
            info("Using interface ppp%d", ifunit);
            slprintf(ifname, sizeof(ifname), "ppp%d", ifunit);
index d9e794b23ac5d7be0b5d734c220dfb89c77bb2dd..7ee1e07f46e5dc5952ff4d676d1474d8ca023468 100644 (file)
@@ -17,7 +17,7 @@
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
-#define RCSID  "$Id: options.c,v 1.70 2000/03/13 23:39:58 paulus Exp $"
+#define RCSID  "$Id: options.c,v 1.71 2000/03/27 06:03:03 paulus Exp $"
 
 #include <ctype.h>
 #include <stdio.h>
@@ -103,6 +103,8 @@ char        linkname[MAXPATHLEN];   /* logical name for link */
 bool   tune_kernel;            /* may alter kernel settings */
 int    connect_delay = 1000;   /* wait this many ms after connect script */
 int    max_data_rate;          /* max bytes/sec through charshunt */
+int    req_unit = -1;          /* requested interface unit */
+bool   multilink = 0;          /* Enable multilink operation */
 
 extern option_t auth_options[];
 extern struct stat devstat;
@@ -276,6 +278,18 @@ option_t general_options[] = {
       "Maximum time (in ms) to wait after connect script finishes" },
     { "datarate", o_int, &max_data_rate,
       "Maximum data rate in bytes/sec (with pty, notty or record option)" },
+    { "unit", o_int, &req_unit,
+      "PPP interface unit number to use if possible", OPT_LLIMIT, 0, 0 },
+#ifdef HAVE_MULTILINK
+    { "multilink", o_bool, &multilink,
+      "Enable multilink operation", 1 },
+    { "nomultilink", o_bool, &multilink,
+      "Disable multilink operation", 0 },
+    { "mp", o_bool, &multilink,
+      "Enable multilink operation", 1 },
+    { "nomp", o_bool, &multilink,
+      "Disable multilink operation", 0 },
+#endif /* HAVE_MULTILINK */
 #ifdef PLUGIN
     { "plugin", o_special, loadplugin,
       "Load a plug-in module into pppd", OPT_PRIV },
index 5dfe83b5dec6b639cafdf6a01819b65cb80e079e..538b8cda2bc02d38761cf2d1ff926266d71f4f73 100644 (file)
@@ -16,7 +16,7 @@
  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  *
- * $Id: pppd.h,v 1.50 2000/03/13 23:39:58 paulus Exp $
+ * $Id: pppd.h,v 1.51 2000/03/27 06:03:06 paulus Exp $
  */
 
 /*
@@ -217,6 +217,8 @@ extern char linkname[MAXPATHLEN]; /* logical name for link */
 extern bool    tune_kernel;    /* May alter kernel settings as necessary */
 extern int     connect_delay;  /* Time to delay after connect script */
 extern int     max_data_rate;  /* max bytes/sec through charshunt */
+extern int     req_unit;       /* interface unit number to use */
+extern bool    multilink;      /* enable multilink operation */
 
 #ifdef PPP_FILTER
 extern struct  bpf_program pass_filter;   /* Filter for pkts to pass */
index 928a7207f806af13225107064536e87e549a1e17..1e52e3bddf8ab4bd53fb26855c42c0f3c8d20c62 100644 (file)
@@ -124,6 +124,7 @@ static int master_fd = -1;
 static int sock6_fd = -1;
 #endif /* INET6 */
 static int ppp_dev_fd = -1;    /* fd for /dev/ppp (new style driver) */
+static int chindex;            /* channel index (new style driver) */
 
 static fd_set in_fds;          /* set of fds that wait_input waits for */
 static int max_in_fd;          /* highest fd set in in_fds */
@@ -337,6 +338,8 @@ sys_close(void)
 
 static int set_kdebugflag (int requested_level)
 {
+    if (new_style_driver && ifunit < 0)
+       return 1;
     if (ioctl(ppp_dev_fd, PPPIOCSDEBUG, &requested_level) < 0) {
        if ( ! ok_error (errno) )
            error("ioctl(PPPIOCSDEBUG): %m");
@@ -355,26 +358,23 @@ static int set_kdebugflag (int requested_level)
 int establish_ppp (int tty_fd)
 {
     int x;
+    int fd = -1;
 
-/*
- * The current PPP device will be the tty file.
- */
-    set_ppp_fd (tty_fd);
-    if (new_style_driver)
-       add_fd(tty_fd);
 /*
  * Ensure that the tty device is in exclusive mode.
  */
     if (ioctl(tty_fd, TIOCEXCL, 0) < 0) {
        if ( ! ok_error ( errno ))
-           warn("ioctl(TIOCEXCL): %m");
+           warn("Couldn't make tty exclusive: %m");
     }
 /*
  * Demand mode - prime the old ppp device to relinquish the unit.
  */
     if (!new_style_driver && looped
-       && ioctl(slave_fd, PPPIOCXFERUNIT, 0) < 0)
-       fatal("ioctl(transfer ppp unit): %m(%d)", errno);
+       && ioctl(slave_fd, PPPIOCXFERUNIT, 0) < 0) {
+       error("ioctl(transfer ppp unit): %m");
+       return -1;
+    }
 /*
  * Set the current tty to the PPP discpline
  */
@@ -382,32 +382,71 @@ int establish_ppp (int tty_fd)
 #ifndef N_SYNC_PPP
 #define N_SYNC_PPP 14
 #endif
-    if (new_style_driver)
-           ppp_disc = sync_serial ? N_SYNC_PPP:N_PPP;
-
+    ppp_disc = (new_style_driver && sync_serial)? N_SYNC_PPP: N_PPP;
     if (ioctl(tty_fd, TIOCSETD, &ppp_disc) < 0) {
-       if ( ! ok_error (errno) )
-           fatal("ioctl(TIOCSETD): %m(%d)", errno);
+       if ( ! ok_error (errno) ) {
+           error("Couldn't set tty to PPP discipline: %m");
+           return -1;
+       }
     }
-/*
- * Find out which interface we were given.
- */
+
     if (new_style_driver) {
-       if (!looped) {
-           /* allocate ourselves a ppp unit */
-           ifunit = -1;
-           if (ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit) < 0)
-               fatal("Couldn't create new ppp unit: %m");
-           set_kdebugflag(kdebugflag);
-       } else {
-           set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) & ~SC_LOOP_TRAFFIC);
+       /* Open another instance of /dev/ppp and connect the channel to it */
+       int flags;
+
+       if (ioctl(tty_fd, PPPIOCGCHAN, &chindex) == -1) {
+           error("Couldn't get channel number: %m");
+           goto err;
+       }
+       dbglog("using channel %d", chindex);
+       fd = open("/dev/ppp", O_RDWR);
+       if (fd < 0) {
+           error("Couldn't reopen /dev/ppp: %m");
+           goto err;
+       }
+       if (ioctl(fd, PPPIOCATTCHAN, &chindex) < 0) {
+           error("Couldn't attach to channel %d: %m", chindex);
+           goto err_close;
        }
-       if (ioctl(tty_fd, PPPIOCATTACH, &ifunit) < 0) {
-           if (errno == EIO)
-               return -1;
-           fatal("Couldn't attach tty to PPP unit %d: %m", ifunit);
+       flags = fcntl(fd, F_GETFL);
+       if (flags == -1 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+           warn("Couldn't set /dev/ppp (channel) to nonblock: %m");
+       set_ppp_fd(fd);
+
+       ifunit = -1;
+       if (!looped && !multilink) {
+           /*
+            * Create a new PPP unit.
+            */
+           ifunit = req_unit;
+           x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
+           if (x < 0 && req_unit >= 0 && errno == EEXIST) {
+               warn("Couldn't allocate PPP unit %d as it is already in use");
+               ifunit = -1;
+               x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
+           }
+           if (x < 0) {
+               error("Couldn't create new ppp unit: %m");
+               goto err_close;
+           }
+       }
+
+       if (looped)
+           set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) & ~SC_LOOP_TRAFFIC);
+
+       if (!multilink) {
+           add_fd(ppp_dev_fd);
+           if (ioctl(fd, PPPIOCCONNECT, &ifunit) < 0) {
+               error("Couldn't attach to PPP unit %d: %m", ifunit);
+               goto err_close;
+           }
        }
+
     } else {
+       /*
+        * Old-style driver: find out which interface we were given.
+        */
+       set_ppp_fd (tty_fd);
        if (ioctl(tty_fd, PPPIOCGUNIT, &x) < 0) {       
            if ( ! ok_error (errno))
                fatal("ioctl(PPPIOCGUNIT): %m(%d)", errno);
@@ -416,32 +455,40 @@ int establish_ppp (int tty_fd)
        if (looped && x != ifunit)
            fatal("transfer_ppp failed: wanted unit %d, got %d", ifunit, x);
        ifunit = x;
+
+       /*
+        * Fetch the initial file flags and reset blocking mode on the file.
+        */
+       initfdflags = fcntl(tty_fd, F_GETFL);
+       if (initfdflags == -1 ||
+           fcntl(tty_fd, F_SETFL, initfdflags | O_NONBLOCK) == -1) {
+           if ( ! ok_error (errno))
+               warn("Couldn't set device to non-blocking mode: %m");
+       }
     }
 
-/*
- * Enable debug in the driver if requested.
- */
+    looped = 0;
+
+    /*
+     * Enable debug in the driver if requested.
+     */
     if (!looped)
        set_kdebugflag (kdebugflag);
-    looped = 0;
 
-    set_flags(tty_fd, get_flags(tty_fd) & ~(SC_RCV_B7_0 | SC_RCV_B7_1 |
+    set_flags(ppp_fd, get_flags(ppp_fd) & ~(SC_RCV_B7_0 | SC_RCV_B7_1 |
                                            SC_RCV_EVNP | SC_RCV_ODDP));
 
     SYSDEBUG ((LOG_NOTICE, "Using version %d.%d.%d of PPP driver",
            driver_version, driver_modification, driver_patch));
 
-/*
- * Fetch the initial file flags and reset blocking mode on the file.
- */
-    initfdflags = fcntl(tty_fd, F_GETFL);
-    if (initfdflags == -1 ||
-       fcntl(tty_fd, F_SETFL, initfdflags | O_NONBLOCK) == -1) {
-       if ( ! ok_error (errno))
-           warn("Couldn't set device to non-blocking mode: %m");
-    }
+    return ppp_fd;
 
-    return ppp_dev_fd;
+ err_close:
+    close(fd);
+ err:
+    if (ioctl(tty_fd, TIOCSETD, &tty_disc) < 0 && !ok_error(errno))
+       warn("Couldn't reset tty to normal line discipline: %m");
+    return -1;
 }
 
 /********************************************************************
@@ -452,8 +499,6 @@ int establish_ppp (int tty_fd)
 
 void disestablish_ppp(int tty_fd)
 {
-    if (new_style_driver)
-       remove_fd(tty_fd);
     if (!hungup) {
 /*
  * Flush the tty output buffer so that the TIOCSETD doesn't hang.
@@ -480,25 +525,42 @@ void disestablish_ppp(int tty_fd)
        }
     }
     initfdflags = -1;
-    if (new_style_driver && !looped) {
-       if (ioctl(ppp_dev_fd, PPPIOCDETACH) < 0) {
-           if (errno == ENOTTY) {
-               /* first version of new driver didn't have PPPIOCDETACH */
-               int flags;
-
-               close(ppp_dev_fd);
-               ppp_dev_fd = open("/dev/ppp", O_RDWR);
-               if (ppp_dev_fd < 0)
-                   fatal("Couldn't reopen /dev/ppp: %m");
-               flags = fcntl(ppp_dev_fd, F_GETFL);
-               if (flags == -1
-                   || fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1)
-                   warn("Couldn't set /dev/ppp to nonblock: %m");
-           } else
-               error("Couldn't release PPP unit: %m");
-       }
-       set_ppp_fd(-1);
+
+    if (new_style_driver) {
+       close(ppp_fd);
+       ppp_fd = -1;
+       if (!looped && ioctl(ppp_dev_fd, PPPIOCDETACH) < 0)
+           error("Couldn't release PPP unit: %m");
+       if (!multilink)
+           remove_fd(ppp_dev_fd);
+    }
+}
+
+/*
+ * bundle_attach - attach our link to a given PPP unit.
+ */
+int bundle_attach(int ifnum)
+{
+    int mrru = 1500;
+
+    if (!new_style_driver)
+       return -1;
+
+    if (ioctl(ppp_dev_fd, PPPIOCATTACH, &ifnum) < 0) {
+       error("Couldn't attach to interface unit %d: %m\n", ifnum);
+       return -1;
+    }
+    set_flags(ppp_dev_fd, get_flags(ppp_dev_fd) | SC_MULTILINK);
+    if (ioctl(ppp_dev_fd, PPPIOCSMRRU, &mrru) < 0)
+       error("Couldn't set interface MRRU: %m");
+
+    if (ioctl(ppp_fd, PPPIOCCONNECT, &ifnum) < 0) {
+       error("Couldn't connect to interface unit %d: %m", ifnum);
+       return -1;
     }
+
+    dbglog("bundle_attach succeeded");
+    return 0;
 }
 
 /********************************************************************
@@ -781,6 +843,9 @@ void restore_tty (int tty_fd)
 
 void output (int unit, unsigned char *p, int len)
 {
+    int fd = ppp_fd;
+    int proto;
+
     if (debug)
        dbglog("sent %P", p, len);
 
@@ -789,8 +854,11 @@ void output (int unit, unsigned char *p, int len)
     if (new_style_driver) {
        p += 2;
        len -= 2;
+       proto = (p[0] << 8) + p[1];
+       if (!multilink && proto != PPP_LCP)
+           fd = ppp_dev_fd;
     }
-    if (write(ppp_dev_fd, p, len) < 0) {
+    if (write(fd, p, len) < 0) {
        if (errno == EWOULDBLOCK || errno == ENOBUFS
            || errno == ENXIO || errno == EIO || errno == EINTR)
            warn("write: warning: %m (%d)", errno);
@@ -808,11 +876,12 @@ void output (int unit, unsigned char *p, int len)
 
 void wait_input(struct timeval *timo)
 {
-    fd_set ready;
+    fd_set ready, exc;
     int n;
 
     ready = in_fds;
-    n = select(max_in_fd + 1, &ready, NULL, &ready, timo);
+    exc = in_fds;
+    n = select(max_in_fd + 1, &ready, NULL, &exc, timo);
     if (n < 0 && errno != EINTR)
        fatal("select: %m(%d)", errno);
 }
@@ -852,16 +921,17 @@ int read_packet (unsigned char *buf)
        len -= 2;
     }
     nr = -1;
-    if (new_style_driver) {
-       nr = read(ppp_dev_fd, buf, len);
-       if (nr < 0 && errno != EWOULDBLOCK && errno != EIO)
-           error("read /dev/ppp: %m");
-    }
-    if (nr < 0 && ppp_fd >= 0) {
+    if (ppp_fd >= 0) {
        nr = read(ppp_fd, buf, len);
        if (nr < 0 && errno != EWOULDBLOCK && errno != EIO)
            error("read: %m");
     }
+    if (nr < 0 && new_style_driver && !multilink) {
+       /* N.B. we read ppp_fd first since LCP packets come in there. */
+       nr = read(ppp_dev_fd, buf, len);
+       if (nr < 0 && errno != EWOULDBLOCK && errno != EIO)
+           error("read /dev/ppp: %m");
+    }
     return (new_style_driver && nr > 0)? nr+2: nr;
 }
 
@@ -916,7 +986,7 @@ void ppp_send_config (int unit,int mtu,u_int32_t asyncmap,int pcomp,int accomp)
     strlcpy(ifr.ifr_name, ifname, sizeof (ifr.ifr_name));
     ifr.ifr_mtu = mtu;
        
-    if (ioctl(sock_fd, SIOCSIFMTU, (caddr_t) &ifr) < 0)
+    if (ifunit >= 0 && ioctl(sock_fd, SIOCSIFMTU, (caddr_t) &ifr) < 0)
        fatal("ioctl(SIOCSIFMTU): %m(%d)", errno);
        
     if (!still_ppp())
@@ -975,7 +1045,8 @@ void ppp_recv_config (int unit,int mru,u_int32_t asyncmap,int pcomp,int accomp)
        if ( ! ok_error (errno))
            error("ioctl(PPPIOCSMRU): %m(%d)", errno);
     }
-    if (new_style_driver && ioctl(ppp_dev_fd, PPPIOCSMRU, (caddr_t) &mru) < 0)
+    if (new_style_driver && ifunit >= 0
+       && ioctl(ppp_dev_fd, PPPIOCSMRU, (caddr_t) &mru) < 0)
        error("Couldn't set MRU in generic PPP layer: %m");
 
     SYSDEBUG ((LOG_DEBUG, "recv_config: asyncmap = %lx\n", asyncmap));
@@ -1694,6 +1765,7 @@ int ppp_available(void)
 
     if (kernel_version >= KVERSION(2,3,13)) {
        fd = open("/dev/ppp", O_RDWR);
+#if 0
        if (fd < 0 && errno == ENOENT) {
            /* try making it and see if that helps. */
            if (mknod("/dev/ppp", S_IFCHR | S_IRUSR | S_IWUSR,
@@ -1707,6 +1779,7 @@ int ppp_available(void)
                fd = open("/dev/ppp", O_RDWR);
            }
        }
+#endif /* 0 */
        if (fd >= 0) {
            new_style_driver = 1;
 
@@ -1717,6 +1790,7 @@ int ppp_available(void)
            close(fd);
            return 1;
        }
+       return 0;
     }
 
 /*
@@ -2281,13 +2355,19 @@ get_pty(master_fdp, slave_fdp, slave_name, uid)
 int
 open_ppp_loopback(void)
 {
-    int flags;
+    int flags, x;
 
     looped = 1;
     if (new_style_driver) {
        /* allocate ourselves a ppp unit */
-       ifunit = -1;
-       if (ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit) < 0)
+       ifunit = req_unit;
+       x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
+       if (x < 0 && req_unit >= 0 && errno == EEXIST) {
+           warn("Couldn't allocate PPP unit %d as it is already in use");
+           ifunit = -1;
+           x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
+       }
+       if (x < 0)
            fatal("Couldn't create PPP unit: %m");
        set_flags(ppp_dev_fd, SC_LOOP_TRAFFIC);
        set_kdebugflag(kdebugflag);
@@ -2514,5 +2594,9 @@ sys_check_options(void)
                     driver_patch);
        return 0;
     }
+    if (multilink && !new_style_driver) {
+       option_error("multilink is not supported by the kernel driver");
+       return 0;
+    }
     return 1;
 }