This commit was generated by cvs2svn to track changes on a CVS vendor
[unix-history] / libexec / pppd / fsm.c
index 19bfb97..2582fb8 100644 (file)
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
+#ifndef lint
+static char rcsid[] = "$Id: fsm.c,v 1.2 1994/03/30 09:31:25 jkh Exp $";
+#endif
+
 /*
  * TODO:
 /*
  * TODO:
- * Mechanism to exit() and/or drop DTR.
- * Hold-down on open?
  * Randomize fsm id on link/init.
  * Deal with variable outgoing MTU.
  */
  * Randomize fsm id on link/init.
  * Deal with variable outgoing MTU.
  */
 /*#include <malloc.h>*/
 #include <syslog.h>
 
 /*#include <malloc.h>*/
 #include <syslog.h>
 
-#ifdef STREAMS
-#include       <sys/stream.h>
-#include       <sys/socket.h>
-#include       <net/if.h>
-#endif
-
-#include <net/ppp.h>
+#include "ppp.h"
 #include "pppd.h"
 #include "fsm.h"
 
 extern char *proto_name();
 
 static void fsm_timeout __ARGS((caddr_t));
 #include "pppd.h"
 #include "fsm.h"
 
 extern char *proto_name();
 
 static void fsm_timeout __ARGS((caddr_t));
-static void fsm_rconfack __ARGS((fsm *, u_char *, int, int));
-static void fsm_rconfnak __ARGS((fsm *, u_char *, int, int));
-static void fsm_rconfrej __ARGS((fsm *, u_char *, int, int));
+static void fsm_rconfreq __ARGS((fsm *, int, u_char *, int));
+static void fsm_rconfack __ARGS((fsm *, int, u_char *, int));
+static void fsm_rconfnakrej __ARGS((fsm *, int, int, u_char *, int));
 static void fsm_rtermreq __ARGS((fsm *, int));
 static void fsm_rtermack __ARGS((fsm *));
 static void fsm_rcoderej __ARGS((fsm *, u_char *, int));
 static void fsm_rtermreq __ARGS((fsm *, int));
 static void fsm_rtermack __ARGS((fsm *));
 static void fsm_rcoderej __ARGS((fsm *, u_char *, int));
-static void fsm_rprotrej __ARGS((fsm *, u_char *, int));
-static void fsm_sconfreq __ARGS((fsm *));
+static void fsm_sconfreq __ARGS((fsm *, int));
+
+#define PROTO_NAME(f)  ((f)->callbacks->proto_name)
+
+int peer_mru[_NPPP];
 
 
 /*
 
 
 /*
@@ -59,226 +58,225 @@ static void fsm_sconfreq __ARGS((fsm *));
  * Initialize fsm state.
  */
 void
  * Initialize fsm state.
  */
 void
-  fsm_init(f)
-fsm *f;
+fsm_init(f)
+    fsm *f;
 {
 {
-    f->state = CLOSED;
+    f->state = INITIAL;
     f->flags = 0;
     f->id = 0;                         /* XXX Start with random id? */
     f->flags = 0;
     f->id = 0;                         /* XXX Start with random id? */
+    f->timeouttime = DEFTIMEOUT;
+    f->maxconfreqtransmits = DEFMAXCONFREQS;
+    f->maxtermtransmits = DEFMAXTERMREQS;
+    f->maxnakloops = DEFMAXNAKLOOPS;
 }
 
 
 /*
 }
 
 
 /*
- * fsm_activeopen - Actively open connection.
- *
- * Set new state, reset desired options and send requests.
+ * fsm_lowerup - The lower layer is up.
  */
 void
  */
 void
-  fsm_activeopen(f)
-fsm *f;
+fsm_lowerup(f)
+    fsm *f;
 {
 {
-    f->flags &= ~(AOPENDING|POPENDING); /* Clear pending flags */
-    if (f->state == REQSENT ||         /* Already actively open(ing)? */
-       f->state == ACKRCVD ||
-       f->state == ACKSENT ||
-       f->state == OPEN)
-       return;
-    if (f->state == TERMSENT ||                /* Closing or */
-       !(f->flags & LOWERUP)) {        /*  lower layer down? */
-       f->flags |= AOPENDING;          /* Wait for desired event */
-       return;
-    }
-    if (f->callbacks->resetci)
-       (*f->callbacks->resetci)(f);    /* Reset options */
-    fsm_sconfreq(f);                   /* Send Configure-Request */
-    TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-    f->state = REQSENT;
-    f->retransmits = 0;                        /* Reset retransmits count */
-    f->nakloops = 0;                   /* Reset nakloops count */
-}
+    switch( f->state ){
+    case INITIAL:
+       f->state = CLOSED;
+       break;
 
 
+    case STARTING:
+       if( f->flags & OPT_SILENT )
+           f->state = STOPPED;
+       else {
+           /* Send an initial configure-request */
+           fsm_sconfreq(f, 0);
+           f->state = REQSENT;
+       }
+       break;
 
 
-/*
- * fsm_passiveopen - Passively open connection.
- *
- * Set new state and reset desired options.
- */
-void
-  fsm_passiveopen(f)
-fsm *f;
-{
-    f->flags &= ~(AOPENDING|POPENDING); /* Clear pending flags */
-    if (f->state == LISTEN ||          /* Already passively open(ing)? */
-       f->state == OPEN)
-       return;
-    if (f->state == REQSENT ||         /* Active-Opening or */
-       f->state == ACKRCVD ||
-       f->state == ACKSENT ||
-       f->state == TERMSENT ||         /*  closing or */
-       !(f->flags & LOWERUP)) {        /*  lower layer down? */
-       f->flags |= POPENDING;          /* Wait for desired event */
-       return;
+    default:
+       FSMDEBUG((LOG_INFO, "%s: Up event in state %d!",
+                 PROTO_NAME(f), f->state));
     }
     }
-    if (f->callbacks->resetci)
-       (*f->callbacks->resetci)(f);    /* Reset options */
-    f->state = LISTEN;
-    f->retransmits = 0;                        /* Reset retransmits count */
-    f->nakloops = 0;                   /* Reset nakloops count */
 }
 
 
 /*
 }
 
 
 /*
- * fsm_close - Start closing connection.
+ * fsm_lowerdown - The lower layer is down.
  *
  *
- * Cancel timeouts and either initiate close or possibly go directly to
- * the CLOSED state.
+ * Cancel all timeouts and inform upper layers.
  */
 void
  */
 void
-  fsm_close(f)
-fsm *f;
+fsm_lowerdown(f)
+    fsm *f;
 {
 {
-    f->flags &= ~(AOPENDING|POPENDING); /* Clear pending flags */
-    if (f->state == CLOSED ||          /* Already CLOSED or Closing? */
-       f->state == TERMSENT)
-       return;
-    if (f->state == REQSENT ||         /* Timeout pending for Open? */
-       f->state == ACKRCVD ||
-       f->state == ACKSENT)
+    switch( f->state ){
+    case CLOSED:
+       f->state = INITIAL;
+       break;
+
+    case STOPPED:
+       f->state = STARTING;
+       if( f->callbacks->starting )
+           (*f->callbacks->starting)(f);
+       break;
+
+    case CLOSING:
+       f->state = INITIAL;
        UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
        UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
-    if (f->state == OPEN &&            /* Open? */
-       f->callbacks->down)
-       (*f->callbacks->down)(f);       /* Inform upper layers we're down */
-    if (f->state == ACKSENT ||         /* Could peer be OPEN? */
-       f->state == OPEN) {
-       fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
-                                       /* Send Terminate-Request */
-       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-       f->state = TERMSENT;
-       f->retransmits = 0;             /* Reset retransmits count */
-    }
-    else {
-       f->state = CLOSED;
-       if (f->callbacks->closed)
-           (*f->callbacks->closed)(f); /* Exit/restart/etc. */
+       break;
+
+    case STOPPING:
+    case REQSENT:
+    case ACKRCVD:
+    case ACKSENT:
+       f->state = STARTING;
+       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
+       break;
+
+    case OPENED:
+       if( f->callbacks->down )
+           (*f->callbacks->down)(f);
+       f->state = STARTING;
+       break;
+
+    default:
+       FSMDEBUG((LOG_INFO, "%s: Down event in state %d!",
+                 PROTO_NAME(f), f->state));
     }
 }
 
 
 /*
     }
 }
 
 
 /*
- * fsm_timeout - Timeout expired.
+ * fsm_open - Link is allowed to come up.
  */
  */
-static void
-  fsm_timeout(arg)
-caddr_t arg;
+void
+fsm_open(f)
+    fsm *f;
 {
 {
-  fsm *f = (fsm *) arg;
-    switch (f->state) {
-      case REQSENT:
-      case ACKRCVD:
-      case ACKSENT:
-       if (f->flags & POPENDING) {     /* Go passive? */
-           f->state = CLOSED;          /* Pretend for a moment... */
-           fsm_passiveopen(f);
-           return;
-       }
-       if (f->retransmits > f->maxconfreqtransmits) {
-           if (f->nakloops > f->maxnakloops) {
-               syslog(LOG_INFO, "%s: timeout sending Config-Requests",
-                      proto_name(f->protocol));
-           } else
-               syslog(LOG_INFO, "%s: timed out. Config-Requests not accepted",
-                      proto_name(f->protocol));
-
-           /* timeout sending config-requests */
-           fsm_close(f);
-
-           return;
-       }
-       if (f->callbacks->retransmit)   /* If there is a retransmit rtn? */
-           (*f->callbacks->retransmit)(f);
-       fsm_sconfreq(f);                /* Send Configure-Request */
-       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-       f->state = REQSENT;
-       ++f->retransmits;
-       f->nakloops = 0;
+    switch( f->state ){
+    case INITIAL:
+       f->state = STARTING;
+       if( f->callbacks->starting )
+           (*f->callbacks->starting)(f);
        break;
 
        break;
 
-      case TERMSENT:
-       if (f->flags & POPENDING) {     /* Go passive? */
-           f->state = CLOSED;          /* Pretend for a moment... */
-           fsm_passiveopen(f);
-           return;
+    case CLOSED:
+       if( f->flags & OPT_SILENT )
+           f->state = STOPPED;
+       else {
+           /* Send an initial configure-request */
+           fsm_sconfreq(f, 0);
+           f->state = REQSENT;
        }
        }
-       if (++f->retransmits > f->maxtermtransmits) {
-           /*
-            * We've waited for an ack long enough.  Peer probably heard us.
-            */
-           f->state = CLOSED;
-           if (f->callbacks->closed)
-               (*f->callbacks->closed)(f); /* Exit/restart/etc. */
-           return;
+       break;
+
+    case CLOSING:
+       f->state = STOPPING;
+       /* fall through */
+    case STOPPED:
+    case OPENED:
+       if( f->flags & OPT_RESTART ){
+           fsm_lowerdown(f);
+           fsm_lowerup(f);
        }
        }
-       if (f->callbacks->retransmit)   /* If there is a retransmit rtn? */
-           (*f->callbacks->retransmit)(f);
-       fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
-                                       /* Send Terminate-Request */
-       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-       ++f->retransmits;
+       break;
     }
 }
 
 
 /*
     }
 }
 
 
 /*
- * fsm_lowerup - The lower layer is up.
+ * fsm_close - Start closing connection.
  *
  *
- * Start Active or Passive Open if pending.
+ * Cancel timeouts and either initiate close or possibly go directly to
+ * the CLOSED state.
  */
 void
  */
 void
-  fsm_lowerup(f)
-fsm *f;
+fsm_close(f)
+    fsm *f;
 {
 {
-    f->flags |= LOWERUP;
-    if (f->flags & AOPENDING)          /* Attempting Active-Open? */
-       fsm_activeopen(f);              /* Try it now */
-    else if (f->flags & POPENDING)     /* Attempting Passive-Open? */
-       fsm_passiveopen(f);             /* Try it now */
-}
+    switch( f->state ){
+    case STARTING:
+       f->state = INITIAL;
+       break;
+    case STOPPED:
+       f->state = CLOSED;
+       break;
+    case STOPPING:
+       f->state = CLOSING;
+       break;
 
 
+    case REQSENT:
+    case ACKRCVD:
+    case ACKSENT:
+    case OPENED:
+       if( f->state != OPENED )
+           UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
+       else if( f->callbacks->down )
+           (*f->callbacks->down)(f);   /* Inform upper layers we're down */
 
 
-/*
- * fsm_lowerdown - The lower layer is down.
- *
- * Cancel all timeouts and inform upper layers.
- */
-void
-  fsm_lowerdown(f)
-fsm *f;
-{
-    f->flags &= ~LOWERUP;
-    if (f->state == REQSENT ||         /* Timeout pending? */
-       f->state == ACKRCVD ||
-       f->state == ACKSENT ||
-       f->state == TERMSENT)
-       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
-    if (f->state == OPEN &&            /* OPEN? */
-       f->callbacks->down)
-       (*f->callbacks->down)(f);       /* Inform upper layers */
-    f->state = CLOSED;
-    if (f->callbacks->closed)
-       (*f->callbacks->closed)(f);     /* Exit/restart/etc. */
+       /* Init restart counter, send Terminate-Request */
+       f->retransmits = f->maxtermtransmits;
+       fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
+       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
+       --f->retransmits;
+
+       f->state = CLOSING;
+       break;
+    }
 }
 
 
 /*
 }
 
 
 /*
- * fsm_protreject - Peer doesn't speak this protocol.
- *
- * Pretend that the lower layer went down.
+ * fsm_timeout - Timeout expired.
  */
  */
-void
-  fsm_protreject(f)
-fsm *f;
+static void
+fsm_timeout(arg)
+    caddr_t arg;
 {
 {
-    fsm_lowerdown(f);
+    fsm *f = (fsm *) arg;
+
+    switch (f->state) {
+    case CLOSING:
+    case STOPPING:
+       if( f->retransmits <= 0 ){
+           /*
+            * We've waited for an ack long enough.  Peer probably heard us.
+            */
+           f->state = (f->state == CLOSING)? CLOSED: STOPPED;
+           if( f->callbacks->finished )
+               (*f->callbacks->finished)(f);
+       } else {
+           /* Send Terminate-Request */
+           fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
+           TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
+           --f->retransmits;
+       }
+       break;
+
+    case REQSENT:
+    case ACKRCVD:
+    case ACKSENT:
+       if (f->retransmits <= 0) {
+           syslog(LOG_WARNING, "%s: timeout sending Config-Requests",
+                  PROTO_NAME(f));
+           f->state = STOPPED;
+           if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
+               (*f->callbacks->finished)(f);
+
+       } else {
+           /* Retransmit the configure-request */
+           if (f->callbacks->retransmit)
+               (*f->callbacks->retransmit)(f);
+           fsm_sconfreq(f, 1);         /* Re-send Configure-Request */
+           if( f->state == ACKRCVD )
+               f->state = REQSENT;
+       }
+       break;
+
+    default:
+       FSMDEBUG((LOG_INFO, "%s: Timeout event in state %d!",
+                 PROTO_NAME(f), f->state));
+    }
 }
 
 
 }
 
 
@@ -286,302 +284,262 @@ fsm *f;
  * fsm_input - Input packet.
  */
 void
  * fsm_input - Input packet.
  */
 void
-  fsm_input(f, inpacket, l)
-fsm *f;
-u_char *inpacket;
-int l;
+fsm_input(f, inpacket, l)
+    fsm *f;
+    u_char *inpacket;
+    int l;
 {
 {
-  u_char *inp, *outp;
-  u_char code, id;
-  int len;
-
-  /*
-   * Parse header (code, id and length).
-   * If packet too short, drop it.
-   */
-  inp = inpacket;
-  if (l < HEADERLEN) {
-    FSMDEBUG((LOG_WARNING, "fsm_input(%x): Rcvd short header.", f->protocol))
-    return;
-  }
-  GETCHAR(code, inp);
-  GETCHAR(id, inp);
-  GETSHORT(len, inp);
-  if (len < HEADERLEN) {
-    FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd illegal length.",
-             f->protocol))
-    return;
-  }
-  if (len > l) {
-    FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd short packet.",
-             f->protocol))
-    return;
-  }
-  len -= HEADERLEN;            /* subtract header length */
-
-  /*
-   * Action depends on code.
-   */
-  switch (code) {
-  case CONFREQ:
-    FSMDEBUG((LOG_INFO, "fsm_rconfreq(%x): Rcvd id %d.",
-             f->protocol, id))
-
-    if (f->state == TERMSENT)
-      return;
-    if (f->state == CLOSED) {
-      fsm_sdata(f, TERMACK, id, NULL, 0);
-      return;
+    u_char *inp, *outp;
+    u_char code, id;
+    int len;
+
+    /*
+     * Parse header (code, id and length).
+     * If packet too short, drop it.
+     */
+    inp = inpacket;
+    if (l < HEADERLEN) {
+       FSMDEBUG((LOG_WARNING, "fsm_input(%x): Rcvd short header.",
+                 f->protocol));
+       return;
     }
     }
-    if (f->state == OPEN && f->callbacks->down)
-      (*f->callbacks->down)(f);        /* Inform upper layers */
-    if (f->state == OPEN || f->state == LISTEN) {
-      /* XXX Possibly need hold-down on OPEN? */
-      fsm_sconfreq(f);         /* Send Configure-Request */
-      TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
+    GETCHAR(code, inp);
+    GETCHAR(id, inp);
+    GETSHORT(len, inp);
+    if (len < HEADERLEN) {
+       FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd illegal length.",
+                 f->protocol));
+       return;
     }
     }
-    
-    if (f->callbacks->reqci)   /* Check CI */
-      code = (*f->callbacks->reqci)(f, inp, &len);
-    else if (len)
-      code = CONFREJ;          /* Reject all CI */
-    
-    len += HEADERLEN;  /* add header length back on */
-    
-    inp = inpacket;                  /* Reset to header */
-    outp = outpacket_buf;      /* get pointer to output buffer */
-    MAKEHEADER(outp, f->protocol); /* paste in DLL header */
-    BCOPY(inp, outp, len);     /* copy input packet */
-    PUTCHAR(code, outp);       /* put in the code, id, and length*/
-    PUTCHAR(id, outp);
-    PUTSHORT(len, outp);
-    output(f->unit, outpacket_buf, len + DLLHEADERLEN);     /* send it out */ 
-    
-    if (code == CONFACK) {
-      if (f->state == ACKRCVD) {
-       UNTIMEOUT(fsm_timeout, (caddr_t) f); /* Cancel timeout */
-       if (f->callbacks->up)
-         (*f->callbacks->up)(f); /* Inform upper layers */
-       f->state = OPEN;
-      }
-      else
-       f->state = ACKSENT;
+    if (len > l) {
+       FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd short packet.",
+                 f->protocol));
+       return;
     }
     }
-    else {
-      if (f->state != ACKRCVD)
-       f->state = REQSENT;
+    len -= HEADERLEN;          /* subtract header length */
+
+    if( f->state == INITIAL || f->state == STARTING ){
+       FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd packet in state %d.",
+                 f->protocol, f->state));
+       return;
     }
     }
-    return;
-    
-  case CONFACK:
-    fsm_rconfack(f, inp, id, len);
-    break;
-    
-  case CONFNAK:
-    fsm_rconfnak(f, inp, id, len);
-    break;
-    
-  case CONFREJ:
-    fsm_rconfrej(f, inp, id, len);
-    break;
+
+    /*
+     * Action depends on code.
+     */
+    switch (code) {
+    case CONFREQ:
+       fsm_rconfreq(f, id, inp, len);
+       break;
     
     
-  case TERMREQ:
-    fsm_rtermreq(f, id);
-    break;
+    case CONFACK:
+       fsm_rconfack(f, id, inp, len);
+       break;
     
     
-  case TERMACK:
-    fsm_rtermack(f);
-    break;
+    case CONFNAK:
+    case CONFREJ:
+       fsm_rconfnakrej(f, code, id, inp, len);
+       break;
     
     
-  case CODEREJ:
-    fsm_rcoderej(f, inp, len);
-    break;
+    case TERMREQ:
+       fsm_rtermreq(f, id);
+       break;
     
     
-  case PROTREJ:
-    fsm_rprotrej(f, inp, len);
-    break;
+    case TERMACK:
+       fsm_rtermack(f);
+       break;
     
     
-  case ECHOREQ:
-    FSMDEBUG((LOG_INFO, "lcp: Echo-Request, Rcvd id %d", id));
+    case CODEREJ:
+       fsm_rcoderej(f, inp, len);
+       break;
     
     
-    switch (f->state) {
-    case CLOSED:
-    case LISTEN:
-      fsm_sdata(f, TERMACK, id, NULL, 0);
-      break;
-      
-    case OPEN:
-      inp = inpacket; /* Reset to header */
-      outp = outpacket_buf;    /* get pointer to output buffer */
-      MAKEHEADER(outp, f->protocol); /* add DLL header */
-      len += HEADERLEN;                /* add header length */
-      BCOPY(inp, outp, len);   /* copy input packet to output buffer */
-      PUTCHAR(ECHOREP, outp);  /* set code to echo reply */
-      PUTCHAR(id, outp);       /* add in id */
-      PUTSHORT(len, outp);     /* and length */
-      output(f->unit, outpacket_buf, len + DLLHEADERLEN); /* send it */
-      return;
+    default:
+       if( !f->callbacks->extcode
+          || !(*f->callbacks->extcode)(f, code, id, inp, len) )
+           fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
+       break;
     }
     }
-    break;
-    
-  case ECHOREP:
-  case DISCREQ:
-    /* XXX Deliver to ECHOREQ sender? */
-    break;
-    
-  default:
-    fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
-    break;
-  }
-
 }
 
 
 /*
 }
 
 
 /*
- * fsm_rconfack - Receive Configure-Ack.
+ * fsm_rconfreq - Receive Configure-Request.
  */
 static void
  */
 static void
-  fsm_rconfack(f, inp, id, len)
-fsm *f;
-u_char *inp;
-u_char id;
-int len;
+fsm_rconfreq(f, id, inp, len)
+    fsm *f;
+    u_char id;
+    u_char *inp;
+    int len;
 {
 {
-    FSMDEBUG((LOG_INFO, "fsm_rconfack(%x): Rcvd id %d.",
-             f->protocol, id))
+    u_char *outp;
+    int code, reject_if_disagree;
 
 
-    switch (f->state) {
-      case LISTEN:
-      case CLOSED:
+    FSMDEBUG((LOG_INFO, "fsm_rconfreq(%s): Rcvd id %d.", PROTO_NAME(f), id));
+    switch( f->state ){
+    case CLOSED:
+       /* Go away, we're closed */
        fsm_sdata(f, TERMACK, id, NULL, 0);
        fsm_sdata(f, TERMACK, id, NULL, 0);
+       return;
+    case CLOSING:
+    case STOPPING:
+       return;
+
+    case OPENED:
+       /* Go down and restart negotiation */
+       if( f->callbacks->down )
+           (*f->callbacks->down)(f);   /* Inform upper layers */
+       fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
        break;
 
        break;
 
-      case ACKRCVD:
-      case REQSENT:
-       if (id != f->reqid)             /* Expected id? */
-           break;                      /* Nope, toss... */
-       if (f->callbacks->ackci &&
-           (*f->callbacks->ackci)(f, inp, len)) /* Good ack? */
-           f->state = ACKRCVD;
-       else
-           f->state = REQSENT;         /* Wait for timeout to retransmit */
+    case STOPPED:
+       /* Negotiation started by our peer */
+       fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
+       f->state = REQSENT;
        break;
        break;
+    }
 
 
-      case ACKSENT:
-       if (id != f->reqid)             /* Expected id? */
-           break;                      /* Nope, toss... */
-       if (f->callbacks->ackci &&
-           (*f->callbacks->ackci)(f, inp, len)) { /* Good ack? */
+    /*
+     * Pass the requested configuration options
+     * to protocol-specific code for checking.
+     */
+    if (f->callbacks->reqci){          /* Check CI */
+       reject_if_disagree = (f->nakloops >= f->maxnakloops);
+       code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
+    } else if (len)
+       code = CONFREJ;                 /* Reject all CI */
+
+    /* send the Ack, Nak or Rej to the peer */
+    fsm_sdata(f, code, id, inp, len);
+
+    if (code == CONFACK) {
+       if (f->state == ACKRCVD) {
            UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
            UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
+           f->state = OPENED;
            if (f->callbacks->up)
                (*f->callbacks->up)(f); /* Inform upper layers */
            if (f->callbacks->up)
                (*f->callbacks->up)(f); /* Inform upper layers */
-           f->state = OPEN;
-       }
-       else
-           f->state = REQSENT;         /* Wait for timeout to retransmit */
-       break;
+       } else
+           f->state = ACKSENT;
+       f->nakloops = 0;
 
 
-      case OPEN:
-       if (f->callbacks->down)
-           (*f->callbacks->down)(f);   /* Inform upper layers */
-       f->state = CLOSED;              /* Only for a moment... */
-       fsm_activeopen(f);              /* Restart */
-       break;
+    } else {
+       /* we sent CONFACK or CONFREJ */
+       if (f->state != ACKRCVD)
+           f->state = REQSENT;
+       if( code == CONFNAK )
+           ++f->nakloops;
     }
 }
 
 
 /*
     }
 }
 
 
 /*
- * fsm_rconfnak - Receive Configure-Nak.
+ * fsm_rconfack - Receive Configure-Ack.
  */
 static void
  */
 static void
-  fsm_rconfnak(f, inp, id, len)
-fsm *f;
-u_char *inp;
-u_char id;
-int len;
+fsm_rconfack(f, id, inp, len)
+    fsm *f;
+    int id;
+    u_char *inp;
+    int len;
 {
 {
-    FSMDEBUG((LOG_INFO, "fsm_rconfnak(%x): Rcvd id %d.",
-             f->protocol, id))
+    FSMDEBUG((LOG_INFO, "fsm_rconfack(%s): Rcvd id %d.",
+             PROTO_NAME(f), id));
+
+    if (id != f->reqid)                /* Expected id? */
+       return;                 /* Nope, toss... */
+    if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len): (len == 0)) ){
+       /* Ack is bad - ignore it */
+       FSMDEBUG((LOG_INFO, "%s: received bad Ack (length %d)",
+                 PROTO_NAME(f), len));
+       return;
+    }
 
     switch (f->state) {
 
     switch (f->state) {
-      case LISTEN:
-      case CLOSED:
+    case CLOSED:
+    case STOPPED:
        fsm_sdata(f, TERMACK, id, NULL, 0);
        break;
 
        fsm_sdata(f, TERMACK, id, NULL, 0);
        break;
 
-      case REQSENT:
-      case ACKSENT:
-       if (id != f->reqid)             /* Expected id? */
-           break;                      /* Nope, toss... */
-       if (++f->nakloops > f->maxnakloops) {
-           FSMDEBUG((LOG_INFO,
-                     "fsm_rconfnak(%x): Possible CONFNAK loop!",
-                     f->protocol))
-           break;                      /* Break the loop */
-       }
-       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
-       if (f->callbacks->nakci)
-           (*f->callbacks->nakci)(f, inp, len);
-       fsm_sconfreq(f);                /* Send Configure-Request */
-       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-       ++f->retransmits;
+    case REQSENT:
+       f->state = ACKRCVD;
+       f->retransmits = f->maxconfreqtransmits;
        break;
 
        break;
 
-      case ACKRCVD:
-       f->state = REQSENT;             /* Wait for timeout to retransmit */
+    case ACKRCVD:
+       /* Huh? an extra Ack? oh well... */
+       fsm_sconfreq(f, 0);
+       f->state = REQSENT;
        break;
 
        break;
 
-      case OPEN:
+    case ACKSENT:
+       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
+       f->state = OPENED;
+       f->retransmits = f->maxconfreqtransmits;
+       if (f->callbacks->up)
+           (*f->callbacks->up)(f);     /* Inform upper layers */
+       break;
+
+    case OPENED:
+       /* Go down and restart negotiation */
        if (f->callbacks->down)
            (*f->callbacks->down)(f);   /* Inform upper layers */
        if (f->callbacks->down)
            (*f->callbacks->down)(f);   /* Inform upper layers */
-       f->state = CLOSED;              /* Only for a moment... */
-       fsm_activeopen(f);              /* Restart */
+       fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
+       f->state = REQSENT;
        break;
     }
 }
 
 
 /*
        break;
     }
 }
 
 
 /*
- * fsm_rconfrej - Receive Configure-Rej.
+ * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
  */
 static void
  */
 static void
-  fsm_rconfrej(f, inp, id, len)
-fsm *f;
-u_char *inp;
-u_char id;
-int len;
+fsm_rconfnakrej(f, code, id, inp, len)
+    fsm *f;
+    int code, id;
+    u_char *inp;
+    int len;
 {
 {
-    FSMDEBUG((LOG_INFO, "fsm_rconfrej(%x): Rcvd id %d.",
-             f->protocol, id))
+    int (*proc)();
+
+    FSMDEBUG((LOG_INFO, "fsm_rconfnakrej(%s): Rcvd id %d.",
+             PROTO_NAME(f), id));
+
+    if (id != f->reqid)                /* Expected id? */
+       return;                 /* Nope, toss... */
+    proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
+    if( !proc || !proc(f, inp, len) ){
+       /* Nak/reject is bad - ignore it */
+       FSMDEBUG((LOG_INFO, "%s: received bad %s (length %d)",
+                 PROTO_NAME(f), (code==CONFNAK? "Nak": "reject"), len));
+       return;
+    }
 
     switch (f->state) {
 
     switch (f->state) {
-      case LISTEN:
-      case CLOSED:
+    case CLOSED:
+    case STOPPED:
        fsm_sdata(f, TERMACK, id, NULL, 0);
        break;
 
        fsm_sdata(f, TERMACK, id, NULL, 0);
        break;
 
-      case REQSENT:
-      case ACKSENT:
-       if (id != f->reqid)             /* Expected id? */
-           break;                      /* Nope, toss... */
-       if (++f->nakloops > f->maxnakloops)
-           break;                      /* Break the loop */
+    case REQSENT:
+    case ACKSENT:
+       /* They didn't agree to what we wanted - try another request */
        UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
        UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
-       if (f->callbacks->rejci)
-           (*f->callbacks->rejci)(f, inp, len);
-       fsm_sconfreq(f);                /* Send Configure-Request */
-       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
-       ++f->retransmits;
+       fsm_sconfreq(f, 0);             /* Send Configure-Request */
        break;
 
        break;
 
-      case ACKRCVD:
-       f->state = REQSENT;             /* Wait for timeout to retransmit */
+    case ACKRCVD:
+       /* Got a Nak/reject when we had already had an Ack?? oh well... */
+       fsm_sconfreq(f, 0);
+       f->state = REQSENT;
        break;
 
        break;
 
-      case OPEN:
-       f->state = CLOSED;              /* Only for a moment... */
-       fsm_activeopen(f);              /* Restart */
+    case OPENED:
+       /* Go down and restart negotiation */
+       if (f->callbacks->down)
+           (*f->callbacks->down)(f);   /* Inform upper layers */
+       fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
+       f->state = REQSENT;
        break;
     }
 }
        break;
     }
 }
@@ -591,26 +549,27 @@ int len;
  * fsm_rtermreq - Receive Terminate-Req.
  */
 static void
  * fsm_rtermreq - Receive Terminate-Req.
  */
 static void
-  fsm_rtermreq(f, id)
-fsm *f;
-u_char id;
+fsm_rtermreq(f, id)
+    fsm *f;
+    int id;
 {
 {
-    FSMDEBUG((LOG_INFO, "fsm_rtermreq(%x): Rcvd id %d.",
-             f->protocol, id))
+    FSMDEBUG((LOG_INFO, "fsm_rtermreq(%s): Rcvd id %d.",
+             PROTO_NAME(f), id));
 
     fsm_sdata(f, TERMACK, id, NULL, 0);
     switch (f->state) {
 
     fsm_sdata(f, TERMACK, id, NULL, 0);
     switch (f->state) {
-      case ACKRCVD:
-      case ACKSENT:
+    case ACKRCVD:
+    case ACKSENT:
        f->state = REQSENT;             /* Start over but keep trying */
        break;
 
        f->state = REQSENT;             /* Start over but keep trying */
        break;
 
-      case OPEN:
+    case OPENED:
+       syslog(LOG_INFO, "%s terminated at peer's request", PROTO_NAME(f));
        if (f->callbacks->down)
            (*f->callbacks->down)(f);   /* Inform upper layers */
        if (f->callbacks->down)
            (*f->callbacks->down)(f);   /* Inform upper layers */
-       f->state = CLOSED;
-       if (f->callbacks->closed)
-           (*f->callbacks->closed)(f); /* Exit/restart/etc. */
+       f->retransmits = 0;
+       f->state = STOPPING;
+       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
        break;
     }
 }
        break;
     }
 }
@@ -620,25 +579,31 @@ u_char id;
  * fsm_rtermack - Receive Terminate-Ack.
  */
 static void
  * fsm_rtermack - Receive Terminate-Ack.
  */
 static void
-  fsm_rtermack(f)
-fsm *f;
+fsm_rtermack(f)
+    fsm *f;
 {
 {
-    FSMDEBUG((LOG_INFO, "fsm_rtermack(%x).", f->protocol))
+    FSMDEBUG((LOG_INFO, "fsm_rtermack(%s).", PROTO_NAME(f)));
 
     switch (f->state) {
 
     switch (f->state) {
-      case OPEN:
-       if (f->callbacks->down)
-           (*f->callbacks->down)(f);   /* Inform upper layers */
+    case CLOSING:
        f->state = CLOSED;
        f->state = CLOSED;
-       if (f->callbacks->closed)
-           (*f->callbacks->closed)(f); /* Exit/restart/etc. */
+       if( f->callbacks->finished )
+           (*f->callbacks->finished)(f);
+       break;
+    case STOPPING:
+       f->state = STOPPED;
+       if( f->callbacks->finished )
+           (*f->callbacks->finished)(f);
        break;
 
        break;
 
-      case TERMSENT:
-       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
-       f->state = CLOSED;
-       if (f->callbacks->closed)
-           (*f->callbacks->closed)(f); /* Exit/restart/etc. */
+    case ACKRCVD:
+       f->state = REQSENT;
+       break;
+
+    case OPENED:
+       if (f->callbacks->down)
+           (*f->callbacks->down)(f);   /* Inform upper layers */
+       fsm_sconfreq(f, 0);
        break;
     }
 }
        break;
     }
 }
@@ -648,59 +613,77 @@ fsm *f;
  * fsm_rcoderej - Receive an Code-Reject.
  */
 static void
  * fsm_rcoderej - Receive an Code-Reject.
  */
 static void
-  fsm_rcoderej(f, inp, len)
-fsm *f;
-u_char *inp;
-int len;
+fsm_rcoderej(f, inp, len)
+    fsm *f;
+    u_char *inp;
+    int len;
 {
 {
-    u_char code;
+    u_char code, id;
 
 
-    FSMDEBUG((LOG_INFO, "fsm_rcoderej(%x).", f->protocol))
+    FSMDEBUG((LOG_INFO, "fsm_rcoderej(%s).", PROTO_NAME(f)));
 
 
-    if (len < sizeof (u_char)) {
-       FSMDEBUG((LOG_INFO,
-                 "fsm_rcoderej: Rcvd short Code-Reject packet!"))
+    if (len < HEADERLEN) {
+       FSMDEBUG((LOG_INFO, "fsm_rcoderej: Rcvd short Code-Reject packet!"));
        return;
     }
     GETCHAR(code, inp);
        return;
     }
     GETCHAR(code, inp);
-    FSMDEBUG((LOG_INFO,
-             "fsm_rcoderej: Rcvd Code-Reject for code %d!",
-             code))
+    GETCHAR(id, inp);
+    syslog(LOG_WARNING, "%s: Rcvd Code-Reject for code %d, id %d",
+          PROTO_NAME(f), code, id);
+
+    if( f->state == ACKRCVD )
+       f->state = REQSENT;
 }
 
 
 /*
 }
 
 
 /*
- * fsm_rprotrej - Receive an Protocol-Reject.
+ * fsm_protreject - Peer doesn't speak this protocol.
  *
  *
- * Figure out which protocol is rejected and inform it.
+ * Treat this as a catastrophic error (RXJ-).
  */
  */
-static void
-  fsm_rprotrej(f, inp, len)
-fsm *f;
-u_char *inp;
-int len;
+void
+fsm_protreject(f)
+    fsm *f;
 {
 {
-    u_short prot;
+    switch( f->state ){
+    case CLOSING:
+       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
+       /* fall through */
+    case CLOSED:
+       f->state = CLOSED;
+       if( f->callbacks->finished )
+           (*f->callbacks->finished)(f);
+       break;
 
 
-    FSMDEBUG((LOG_INFO, "fsm_rprotrej."))
+    case STOPPING:
+    case REQSENT:
+    case ACKRCVD:
+    case ACKSENT:
+       UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
+       /* fall through */
+    case STOPPED:
+       f->state = STOPPED;
+       if( f->callbacks->finished )
+           (*f->callbacks->finished)(f);
+       break;
 
 
-    if (len < sizeof (u_short)) {
-       FSMDEBUG((LOG_INFO,
-                 "fsm_rprotrej: Rcvd short Protocol-Reject packet!"))
-       return;
-    }
-    if (f->protocol != LCP) {          /* Only valid for LCP */
-       FSMDEBUG((LOG_INFO,
-                 "fsm_rprotrej: Rcvd non-LCP Protocol-Reject!"))
-       return;
-    }
+    case OPENED:
+       if( f->callbacks->down )
+           (*f->callbacks->down)(f);
 
 
-    GETSHORT(prot, inp);
+       /* Init restart counter, send Terminate-Request */
+       f->retransmits = f->maxtermtransmits;
+       fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
+       TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
+       --f->retransmits;
+
+       f->state = STOPPING;
+       break;
 
 
-    FSMDEBUG((LOG_INFO,
-             "fsm_rprotrej: Rcvd Protocol-Reject packet for %x!",
-             prot))
-    DEMUXPROTREJ(f->unit, prot);       /* Inform protocol */
+    default:
+       FSMDEBUG((LOG_INFO, "%s: Protocol-reject event in state %d!",
+                 PROTO_NAME(f), f->state));
+    }
 }
 
 
 }
 
 
@@ -708,58 +691,79 @@ int len;
  * fsm_sconfreq - Send a Configure-Request.
  */
 static void
  * fsm_sconfreq - Send a Configure-Request.
  */
 static void
-  fsm_sconfreq(f)
-fsm *f;
+fsm_sconfreq(f, retransmit)
+    fsm *f;
+    int retransmit;
 {
     u_char *outp;
 {
     u_char *outp;
-    int outlen;
+    int outlen, cilen;
 
 
-    outlen = HEADERLEN + (f->callbacks->cilen ? (*f->callbacks->cilen)(f) : 0);
-    /* XXX Adjust outlen to MTU */
-    outp = outpacket_buf;
-    MAKEHEADER(outp, f->protocol);
+    if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
+       /* Not currently negotiating - reset options */
+       if( f->callbacks->resetci )
+           (*f->callbacks->resetci)(f);
+       f->nakloops = 0;
+    }
 
 
-    PUTCHAR(CONFREQ, outp);
-    PUTCHAR(f->reqid = ++f->id, outp);
-    PUTSHORT(outlen, outp);
-    if (f->callbacks->cilen && f->callbacks->addci)
-       (*f->callbacks->addci)(f, outp);
-    output(f->unit, outpacket_buf, outlen + DLLHEADERLEN);
+    if( !retransmit ){
+       /* New request - reset retransmission counter, use new ID */
+       f->retransmits = f->maxconfreqtransmits;
+       f->reqid = ++f->id;
+    }
+
+    /*
+     * Make up the request packet
+     */
+    if( f->callbacks->cilen && f->callbacks->addci ){
+       cilen = (*f->callbacks->cilen)(f);
+       if( cilen > peer_mru[f->unit] - HEADERLEN )
+           cilen = peer_mru[f->unit] - HEADERLEN;
+       outp = outpacket_buf + DLLHEADERLEN + HEADERLEN;
+       if (f->callbacks->addci)
+           (*f->callbacks->addci)(f, outp, &cilen);
+    } else
+       cilen = 0;
+
+    /* send the request to our peer */
+    fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
+
+    /* start the retransmit timer */
+    --f->retransmits;
+    TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
 
     FSMDEBUG((LOG_INFO, "%s: sending Configure-Request, id %d",
 
     FSMDEBUG((LOG_INFO, "%s: sending Configure-Request, id %d",
-             proto_name(f->protocol), f->reqid))
+             PROTO_NAME(f), f->reqid));
 }
 
 
 /*
  * fsm_sdata - Send some data.
  *
 }
 
 
 /*
  * fsm_sdata - Send some data.
  *
- * Used for Terminate-Request, Terminate-Ack, Code-Reject, Protocol-Reject,
- * Echo-Request, and Discard-Request.
+ * Used for all packets sent to our peer by this module.
  */
 void
  */
 void
-  fsm_sdata(f, code, id, data, datalen)
-fsm *f;
-u_char code, id;
-u_char *data;
-int datalen;
+fsm_sdata(f, code, id, data, datalen)
+    fsm *f;
+    u_char code, id;
+    u_char *data;
+    int datalen;
 {
     u_char *outp;
     int outlen;
 
     /* Adjust length to be smaller than MTU */
 {
     u_char *outp;
     int outlen;
 
     /* Adjust length to be smaller than MTU */
-    if (datalen > MTU - HEADERLEN)
-       datalen = MTU - HEADERLEN;
-    outlen = datalen + HEADERLEN;
     outp = outpacket_buf;
     outp = outpacket_buf;
+    if (datalen > peer_mru[f->unit] - HEADERLEN)
+       datalen = peer_mru[f->unit] - HEADERLEN;
+    if (datalen && data != outp + DLLHEADERLEN + HEADERLEN)
+       BCOPY(data, outp + DLLHEADERLEN + HEADERLEN, datalen);
+    outlen = datalen + HEADERLEN;
     MAKEHEADER(outp, f->protocol);
     PUTCHAR(code, outp);
     PUTCHAR(id, outp);
     PUTSHORT(outlen, outp);
     MAKEHEADER(outp, f->protocol);
     PUTCHAR(code, outp);
     PUTCHAR(id, outp);
     PUTSHORT(outlen, outp);
-    if (datalen)
-       BCOPY(data, outp, datalen);
     output(f->unit, outpacket_buf, outlen + DLLHEADERLEN);
 
     output(f->unit, outpacket_buf, outlen + DLLHEADERLEN);
 
-    FSMDEBUG((LOG_INFO, "fsm_sdata(%x): Sent code %d, id %d.",
-             f->protocol, code, id))
+    FSMDEBUG((LOG_INFO, "fsm_sdata(%s): Sent code %d, id %d.",
+             PROTO_NAME(f), code, id));
 }
 }