diff options
Diffstat (limited to 'usr.sbin/ppp/fsm.c')
| -rw-r--r-- | usr.sbin/ppp/fsm.c | 1213 | 
1 files changed, 1213 insertions, 0 deletions
| diff --git a/usr.sbin/ppp/fsm.c b/usr.sbin/ppp/fsm.c new file mode 100644 index 000000000000..5c011bef7f4f --- /dev/null +++ b/usr.sbin/ppp/fsm.c @@ -0,0 +1,1213 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 1996 - 2001 Brian Somers <brian@Awfulhak.org> + *          based on work by Toshiharu OHNO <tony-o@iij.ad.jp> + *                           Internet Initiative Japan, Inc (IIJ) + * All rights reserved. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#include <sys/param.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <string.h> +#include <termios.h> + +#include "layer.h" +#include "ua.h" +#include "mbuf.h" +#include "log.h" +#include "defs.h" +#include "timer.h" +#include "fsm.h" +#include "iplist.h" +#include "lqr.h" +#include "hdlc.h" +#include "throughput.h" +#include "slcompress.h" +#include "ncpaddr.h" +#include "ipcp.h" +#include "filter.h" +#include "descriptor.h" +#include "lcp.h" +#include "ccp.h" +#include "link.h" +#include "mp.h" +#ifndef NORADIUS +#include "radius.h" +#endif +#include "ipv6cp.h" +#include "ncp.h" +#include "bundle.h" +#include "async.h" +#include "physical.h" +#include "proto.h" + +static void FsmSendConfigReq(struct fsm *); +static void FsmSendTerminateReq(struct fsm *); +static void FsmInitRestartCounter(struct fsm *, int); + +typedef void (recvfn)(struct fsm *, struct fsmheader *, struct mbuf *); +static recvfn FsmRecvConfigReq, FsmRecvConfigAck, FsmRecvConfigNak, +              FsmRecvConfigRej, FsmRecvTermReq, FsmRecvTermAck, +              FsmRecvCodeRej, FsmRecvProtoRej, FsmRecvEchoReq, +              FsmRecvEchoRep, FsmRecvDiscReq, FsmRecvIdent, +              FsmRecvTimeRemain, FsmRecvResetReq, FsmRecvResetAck; + +static const struct fsmcodedesc { +  recvfn *recv; +  unsigned check_reqid : 1; +  unsigned inc_reqid : 1; +  const char *name; +} FsmCodes[] = { +  { FsmRecvConfigReq, 0, 0, "ConfigReq"    }, +  { FsmRecvConfigAck, 1, 1, "ConfigAck"    }, +  { FsmRecvConfigNak, 1, 1, "ConfigNak"    }, +  { FsmRecvConfigRej, 1, 1, "ConfigRej"    }, +  { FsmRecvTermReq,   0, 0, "TerminateReq" }, +  { FsmRecvTermAck,   1, 1, "TerminateAck" }, +  { FsmRecvCodeRej,   0, 0, "CodeRej"      }, +  { FsmRecvProtoRej,  0, 0, "ProtocolRej"  }, +  { FsmRecvEchoReq,   0, 0, "EchoRequest"  }, +  { FsmRecvEchoRep,   0, 0, "EchoReply"    }, +  { FsmRecvDiscReq,   0, 0, "DiscardReq"   }, +  { FsmRecvIdent,     0, 1, "Ident"        }, +  { FsmRecvTimeRemain,0, 0, "TimeRemain"   }, +  { FsmRecvResetReq,  0, 0, "ResetReq"     }, +  { FsmRecvResetAck,  0, 1, "ResetAck"     } +}; + +static const char * +Code2Nam(u_int code) +{ +  if (code == 0 || code > sizeof FsmCodes / sizeof FsmCodes[0]) +    return "Unknown"; +  return FsmCodes[code-1].name; +} + +const char * +State2Nam(u_int state) +{ +  static const char * const StateNames[] = { +    "Initial", "Starting", "Closed", "Stopped", "Closing", "Stopping", +    "Req-Sent", "Ack-Rcvd", "Ack-Sent", "Opened", +  }; + +  if (state >= sizeof StateNames / sizeof StateNames[0]) +    return "unknown"; +  return StateNames[state]; +} + +static void +StoppedTimeout(void *v) +{ +  struct fsm *fp = (struct fsm *)v; + +  log_Printf(fp->LogLevel, "%s: Stopped timer expired\n", fp->link->name); +  if (fp->OpenTimer.state == TIMER_RUNNING) { +    log_Printf(LogWARN, "%s: %s: aborting open delay due to stopped timer\n", +              fp->link->name, fp->name); +    timer_Stop(&fp->OpenTimer); +  } +  if (fp->state == ST_STOPPED) +    fsm2initial(fp); +} + +void +fsm_Init(struct fsm *fp, const char *name, u_short proto, int mincode, +         int maxcode, int LogLevel, struct bundle *bundle, +         struct link *l, const struct fsm_parent *parent, +         struct fsm_callbacks *fn, const char * const timer_names[3]) +{ +  fp->name = name; +  fp->proto = proto; +  fp->min_code = mincode; +  fp->max_code = maxcode; +  fp->state = fp->min_code > CODE_TERMACK ? ST_OPENED : ST_INITIAL; +  fp->reqid = 1; +  fp->restart = 1; +  fp->more.reqs = fp->more.naks = fp->more.rejs = 3; +  memset(&fp->FsmTimer, '\0', sizeof fp->FsmTimer); +  memset(&fp->OpenTimer, '\0', sizeof fp->OpenTimer); +  memset(&fp->StoppedTimer, '\0', sizeof fp->StoppedTimer); +  fp->LogLevel = LogLevel; +  fp->link = l; +  fp->bundle = bundle; +  fp->parent = parent; +  fp->fn = fn; +  fp->FsmTimer.name = timer_names[0]; +  fp->OpenTimer.name = timer_names[1]; +  fp->StoppedTimer.name = timer_names[2]; +} + +static void +NewState(struct fsm *fp, int new) +{ +  log_Printf(fp->LogLevel, "%s: State change %s --> %s\n", +             fp->link->name, State2Nam(fp->state), State2Nam(new)); +  if (fp->state == ST_STOPPED && fp->StoppedTimer.state == TIMER_RUNNING) +    timer_Stop(&fp->StoppedTimer); +  fp->state = new; +  if ((new >= ST_INITIAL && new <= ST_STOPPED) || (new == ST_OPENED)) { +    timer_Stop(&fp->FsmTimer); +    if (new == ST_STOPPED && fp->StoppedTimer.load) { +      timer_Stop(&fp->StoppedTimer); +      fp->StoppedTimer.func = StoppedTimeout; +      fp->StoppedTimer.arg = (void *) fp; +      timer_Start(&fp->StoppedTimer); +    } +  } +} + +void +fsm_Output(struct fsm *fp, u_int code, u_int id, u_char *ptr, unsigned count, +           int mtype) +{ +  int plen; +  struct fsmheader lh; +  struct mbuf *bp; + +  if (log_IsKept(fp->LogLevel)) { +    log_Printf(fp->LogLevel, "%s: Send%s(%d) state = %s\n", +              fp->link->name, Code2Nam(code), id, State2Nam(fp->state)); +    switch (code) { +      case CODE_CONFIGREQ: +      case CODE_CONFIGACK: +      case CODE_CONFIGREJ: +      case CODE_CONFIGNAK: +        (*fp->fn->DecodeConfig)(fp, ptr, ptr + count, MODE_NOP, NULL); +        if (count < sizeof(struct fsm_opt_hdr)) +          log_Printf(fp->LogLevel, "  [EMPTY]\n"); +        break; +    } +  } + +  plen = sizeof(struct fsmheader) + count; +  lh.code = code; +  lh.id = id; +  lh.length = htons(plen); +  bp = m_get(plen, mtype); +  memcpy(MBUF_CTOP(bp), &lh, sizeof(struct fsmheader)); +  if (count) +    memcpy(MBUF_CTOP(bp) + sizeof(struct fsmheader), ptr, count); +  log_DumpBp(LogDEBUG, "fsm_Output", bp); +  link_PushPacket(fp->link, bp, fp->bundle, LINK_QUEUES(fp->link) - 1, +                  fp->proto); + +  if (code == CODE_CONFIGREJ) +    lcp_SendIdentification(&fp->link->lcp); +} + +static void +FsmOpenNow(void *v) +{ +  struct fsm *fp = (struct fsm *)v; + +  timer_Stop(&fp->OpenTimer); +  if (fp->state <= ST_STOPPED) { +    if (fp->state != ST_STARTING) { +      /* +       * In practice, we're only here in ST_STOPPED (when delaying the +       * first config request) or ST_CLOSED (when openmode == 0). +       * +       * The ST_STOPPED bit is breaking the RFC already :-( +       * +       * According to the RFC (1661) state transition table, a TLS isn't +       * required for an Open event when state == Closed, but the RFC +       * must be wrong as TLS hasn't yet been called (since the last TLF) +       * ie, Initial gets an `Up' event, Closing gets a RTA etc. +       */ +      (*fp->fn->LayerStart)(fp); +      (*fp->parent->LayerStart)(fp->parent->object, fp); +    } +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +  } +} + +void +fsm_Open(struct fsm *fp) +{ +  switch (fp->state) { +  case ST_INITIAL: +    NewState(fp, ST_STARTING); +    (*fp->fn->LayerStart)(fp); +    (*fp->parent->LayerStart)(fp->parent->object, fp); +    break; +  case ST_CLOSED: +    if (fp->open_mode == OPEN_PASSIVE) { +      NewState(fp, ST_STOPPED);		/* XXX: This is a hack ! */ +    } else if (fp->open_mode > 0) { +      if (fp->open_mode > 1) +        log_Printf(LogPHASE, "%s: Entering STOPPED state for %d seconds\n", +                  fp->link->name, fp->open_mode); +      NewState(fp, ST_STOPPED);		/* XXX: This is a not-so-bad hack ! */ +      timer_Stop(&fp->OpenTimer); +      fp->OpenTimer.load = fp->open_mode * SECTICKS; +      fp->OpenTimer.func = FsmOpenNow; +      fp->OpenTimer.arg = (void *)fp; +      timer_Start(&fp->OpenTimer); +    } else +      FsmOpenNow(fp); +    break; +  case ST_STOPPED:		/* XXX: restart option */ +  case ST_REQSENT: +  case ST_ACKRCVD: +  case ST_ACKSENT: +  case ST_OPENED:		/* XXX: restart option */ +    break; +  case ST_CLOSING:		/* XXX: restart option */ +  case ST_STOPPING:		/* XXX: restart option */ +    NewState(fp, ST_STOPPING); +    break; +  } +} + +void +fsm_Up(struct fsm *fp) +{ +  switch (fp->state) { +  case ST_INITIAL: +    log_Printf(fp->LogLevel, "FSM: Using \"%s\" as a transport\n", +              fp->link->name); +    NewState(fp, ST_CLOSED); +    break; +  case ST_STARTING: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    break; +  default: +    log_Printf(fp->LogLevel, "%s: Oops, Up at %s\n", +              fp->link->name, State2Nam(fp->state)); +    break; +  } +} + +void +fsm_Down(struct fsm *fp) +{ +  switch (fp->state) { +  case ST_CLOSED: +    NewState(fp, ST_INITIAL); +    break; +  case ST_CLOSING: +    /* This TLF contradicts the RFC (1661), which ``misses it out'' ! */ +    (*fp->fn->LayerFinish)(fp); +    NewState(fp, ST_INITIAL); +    (*fp->parent->LayerFinish)(fp->parent->object, fp); +    break; +  case ST_STOPPED: +    NewState(fp, ST_STARTING); +    (*fp->fn->LayerStart)(fp); +    (*fp->parent->LayerStart)(fp->parent->object, fp); +    break; +  case ST_STOPPING: +  case ST_REQSENT: +  case ST_ACKRCVD: +  case ST_ACKSENT: +    NewState(fp, ST_STARTING); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    NewState(fp, ST_STARTING); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  } +} + +void +fsm_Close(struct fsm *fp) +{ +  switch (fp->state) { +  case ST_STARTING: +    (*fp->fn->LayerFinish)(fp); +    NewState(fp, ST_INITIAL); +    (*fp->parent->LayerFinish)(fp->parent->object, fp); +    break; +  case ST_STOPPED: +    NewState(fp, ST_CLOSED); +    break; +  case ST_STOPPING: +    NewState(fp, ST_CLOSING); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    if (fp->state == ST_OPENED) { +      FsmInitRestartCounter(fp, FSM_TRM_TIMER); +      FsmSendTerminateReq(fp); +      NewState(fp, ST_CLOSING); +      (*fp->parent->LayerDown)(fp->parent->object, fp); +    } +    break; +  case ST_REQSENT: +  case ST_ACKRCVD: +  case ST_ACKSENT: +    FsmInitRestartCounter(fp, FSM_TRM_TIMER); +    FsmSendTerminateReq(fp); +    NewState(fp, ST_CLOSING); +    break; +  } +} + +/* + *	Send functions + */ +static void +FsmSendConfigReq(struct fsm *fp) +{ +  if (fp->more.reqs-- > 0 && fp->restart-- > 0) { +    (*fp->fn->SendConfigReq)(fp); +    timer_Start(&fp->FsmTimer);		/* Start restart timer */ +  } else { +    if (fp->more.reqs < 0) +      log_Printf(LogPHASE, "%s: Too many %s REQs sent - abandoning " +                 "negotiation\n", fp->link->name, fp->name); +    lcp_SendIdentification(&fp->link->lcp); +    fsm_Close(fp); +  } +} + +static void +FsmSendTerminateReq(struct fsm *fp) +{ +  fsm_Output(fp, CODE_TERMREQ, fp->reqid, NULL, 0, MB_UNKNOWN); +  (*fp->fn->SentTerminateReq)(fp); +  timer_Start(&fp->FsmTimer);	/* Start restart timer */ +  fp->restart--;		/* Decrement restart counter */ +} + +/* + *	Timeout actions + */ +static void +FsmTimeout(void *v) +{ +  struct fsm *fp = (struct fsm *)v; + +  if (fp->restart) { +    switch (fp->state) { +    case ST_CLOSING: +    case ST_STOPPING: +      FsmSendTerminateReq(fp); +      break; +    case ST_REQSENT: +    case ST_ACKSENT: +      FsmSendConfigReq(fp); +      break; +    case ST_ACKRCVD: +      FsmSendConfigReq(fp); +      NewState(fp, ST_REQSENT); +      break; +    } +    timer_Start(&fp->FsmTimer); +  } else { +    switch (fp->state) { +    case ST_CLOSING: +      (*fp->fn->LayerFinish)(fp); +      NewState(fp, ST_CLOSED); +      (*fp->parent->LayerFinish)(fp->parent->object, fp); +      break; +    case ST_STOPPING: +      (*fp->fn->LayerFinish)(fp); +      NewState(fp, ST_STOPPED); +      (*fp->parent->LayerFinish)(fp->parent->object, fp); +      break; +    case ST_REQSENT:		/* XXX: 3p */ +    case ST_ACKSENT: +    case ST_ACKRCVD: +      (*fp->fn->LayerFinish)(fp); +      NewState(fp, ST_STOPPED); +      (*fp->parent->LayerFinish)(fp->parent->object, fp); +      break; +    } +  } +} + +static void +FsmInitRestartCounter(struct fsm *fp, int what) +{ +  timer_Stop(&fp->FsmTimer); +  fp->FsmTimer.func = FsmTimeout; +  fp->FsmTimer.arg = (void *)fp; +  (*fp->fn->InitRestartCounter)(fp, what); +} + +/* + * Actions when receive packets + */ +static void +FsmRecvConfigReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +/* RCR */ +{ +  struct fsm_decode dec; +  int plen, flen; +  int ackaction = 0; +  u_char *cp; + +  bp = m_pullup(bp); +  plen = m_length(bp); +  flen = ntohs(lhp->length) - sizeof *lhp; +  if (plen < flen) { +    log_Printf(LogWARN, "%s: FsmRecvConfigReq: plen (%d) < flen (%d)\n", +               fp->link->name, plen, flen); +    m_freem(bp); +    return; +  } + +  /* Some things must be done before we Decode the packet */ +  switch (fp->state) { +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +  } + +  dec.ackend = dec.ack; +  dec.nakend = dec.nak; +  dec.rejend = dec.rej; +  cp = MBUF_CTOP(bp); +  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REQ, &dec); +  if (flen < (int)sizeof(struct fsm_opt_hdr)) +    log_Printf(fp->LogLevel, "  [EMPTY]\n"); + +  if (dec.nakend == dec.nak && dec.rejend == dec.rej) +    ackaction = 1; + +  /* Check and process easy case */ +  switch (fp->state) { +  case ST_INITIAL: +    if (fp->proto == PROTO_CCP && fp->link->lcp.fsm.state == ST_OPENED) { +      /* +       * ccp_SetOpenMode() leaves us in initial if we're disabling +       * & denying everything. +       */ +      bp = m_prepend(bp, lhp, sizeof *lhp, 2); +      bp = proto_Prepend(bp, fp->proto, 0, 0); +      bp = m_pullup(bp); +      lcp_SendProtoRej(&fp->link->lcp, MBUF_CTOP(bp), bp->m_len); +      m_freem(bp); +      return; +    } +    /* Drop through */ +  case ST_STARTING: +    log_Printf(fp->LogLevel, "%s: Oops, RCR in %s.\n", +              fp->link->name, State2Nam(fp->state)); +    m_freem(bp); +    return; +  case ST_CLOSED: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    m_freem(bp); +    return; +  case ST_CLOSING: +    log_Printf(fp->LogLevel, "%s: Error: Got ConfigReq while state = %s\n", +              fp->link->name, State2Nam(fp->state)); +  case ST_STOPPING: +    m_freem(bp); +    return; +  case ST_STOPPED: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    /* Drop through */ +  case ST_OPENED: +    FsmSendConfigReq(fp); +    break; +  } + +  if (dec.rejend != dec.rej) +    fsm_Output(fp, CODE_CONFIGREJ, lhp->id, dec.rej, dec.rejend - dec.rej, +               MB_UNKNOWN); +  if (dec.nakend != dec.nak) +    fsm_Output(fp, CODE_CONFIGNAK, lhp->id, dec.nak, dec.nakend - dec.nak, +               MB_UNKNOWN); +  if (ackaction) +    fsm_Output(fp, CODE_CONFIGACK, lhp->id, dec.ack, dec.ackend - dec.ack, +               MB_UNKNOWN); + +  switch (fp->state) { +  case ST_STOPPED: +      /* +       * According to the RFC (1661) state transition table, a TLS isn't +       * required for a RCR when state == ST_STOPPED, but the RFC +       * must be wrong as TLS hasn't yet been called (since the last TLF) +       */ +    (*fp->fn->LayerStart)(fp); +    (*fp->parent->LayerStart)(fp->parent->object, fp); +    /* FALLTHROUGH */ + +  case ST_OPENED: +    if (ackaction) +      NewState(fp, ST_ACKSENT); +    else +      NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  case ST_REQSENT: +    if (ackaction) +      NewState(fp, ST_ACKSENT); +    break; +  case ST_ACKRCVD: +    if (ackaction) { +      NewState(fp, ST_OPENED); +      if ((*fp->fn->LayerUp)(fp)) +        (*fp->parent->LayerUp)(fp->parent->object, fp); +      else { +        (*fp->fn->LayerDown)(fp); +        FsmInitRestartCounter(fp, FSM_TRM_TIMER); +        FsmSendTerminateReq(fp); +        NewState(fp, ST_CLOSING); +        lcp_SendIdentification(&fp->link->lcp); +      } +    } +    break; +  case ST_ACKSENT: +    if (!ackaction) +      NewState(fp, ST_REQSENT); +    break; +  } +  m_freem(bp); + +  if (dec.rejend != dec.rej && --fp->more.rejs <= 0) { +    log_Printf(LogPHASE, "%s: Too many %s REJs sent - abandoning negotiation\n", +               fp->link->name, fp->name); +    lcp_SendIdentification(&fp->link->lcp); +    fsm_Close(fp); +  } + +  if (dec.nakend != dec.nak && --fp->more.naks <= 0) { +    log_Printf(LogPHASE, "%s: Too many %s NAKs sent - abandoning negotiation\n", +               fp->link->name, fp->name); +    lcp_SendIdentification(&fp->link->lcp); +    fsm_Close(fp); +  } +} + +static void +FsmRecvConfigAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +/* RCA */ +{ +  struct fsm_decode dec; +  int plen, flen; +  u_char *cp; + +  plen = m_length(bp); +  flen = ntohs(lhp->length) - sizeof *lhp; +  if (plen < flen) { +    m_freem(bp); +    return; +  } + +  bp = m_pullup(bp); +  dec.ackend = dec.ack; +  dec.nakend = dec.nak; +  dec.rejend = dec.rej; +  cp = MBUF_CTOP(bp); +  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_ACK, &dec); +  if (flen < (int)sizeof(struct fsm_opt_hdr)) +    log_Printf(fp->LogLevel, "  [EMPTY]\n"); + +  switch (fp->state) { +    case ST_CLOSED: +    case ST_STOPPED: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    break; +  case ST_CLOSING: +  case ST_STOPPING: +    break; +  case ST_REQSENT: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    NewState(fp, ST_ACKRCVD); +    break; +  case ST_ACKRCVD: +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    break; +  case ST_ACKSENT: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    NewState(fp, ST_OPENED); +    if ((*fp->fn->LayerUp)(fp)) +      (*fp->parent->LayerUp)(fp->parent->object, fp); +    else { +      (*fp->fn->LayerDown)(fp); +      FsmInitRestartCounter(fp, FSM_TRM_TIMER); +      FsmSendTerminateReq(fp); +      NewState(fp, ST_CLOSING); +      lcp_SendIdentification(&fp->link->lcp); +    } +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  } +  m_freem(bp); +} + +static void +FsmRecvConfigNak(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +/* RCN */ +{ +  struct fsm_decode dec; +  int plen, flen; +  u_char *cp; + +  plen = m_length(bp); +  flen = ntohs(lhp->length) - sizeof *lhp; +  if (plen < flen) { +    m_freem(bp); +    return; +  } + +  /* +   * Check and process easy case +   */ +  switch (fp->state) { +  case ST_INITIAL: +  case ST_STARTING: +    log_Printf(fp->LogLevel, "%s: Oops, RCN in %s.\n", +              fp->link->name, State2Nam(fp->state)); +    m_freem(bp); +    return; +  case ST_CLOSED: +  case ST_STOPPED: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    m_freem(bp); +    return; +  case ST_CLOSING: +  case ST_STOPPING: +    m_freem(bp); +    return; +  } + +  bp = m_pullup(bp); +  dec.ackend = dec.ack; +  dec.nakend = dec.nak; +  dec.rejend = dec.rej; +  cp = MBUF_CTOP(bp); +  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_NAK, &dec); +  if (flen < (int)sizeof(struct fsm_opt_hdr)) +    log_Printf(fp->LogLevel, "  [EMPTY]\n"); + +  switch (fp->state) { +  case ST_REQSENT: +  case ST_ACKSENT: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    FsmSendConfigReq(fp); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  case ST_ACKRCVD: +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    break; +  } + +  m_freem(bp); +} + +static void +FsmRecvTermReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +/* RTR */ +{ +  switch (fp->state) { +  case ST_INITIAL: +  case ST_STARTING: +    log_Printf(fp->LogLevel, "%s: Oops, RTR in %s\n", +              fp->link->name, State2Nam(fp->state)); +    break; +  case ST_CLOSED: +  case ST_STOPPED: +  case ST_CLOSING: +  case ST_STOPPING: +  case ST_REQSENT: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    break; +  case ST_ACKRCVD: +  case ST_ACKSENT: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    NewState(fp, ST_REQSENT); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    FsmInitRestartCounter(fp, FSM_TRM_TIMER); +    timer_Start(&fp->FsmTimer);			/* Start restart timer */ +    fp->restart = 0; +    NewState(fp, ST_STOPPING); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    /* A delayed ST_STOPPED is now scheduled */ +    break; +  } +  m_freem(bp); +} + +static void +FsmRecvTermAck(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp) +/* RTA */ +{ +  switch (fp->state) { +  case ST_CLOSING: +    (*fp->fn->LayerFinish)(fp); +    NewState(fp, ST_CLOSED); +    (*fp->parent->LayerFinish)(fp->parent->object, fp); +    break; +  case ST_STOPPING: +    (*fp->fn->LayerFinish)(fp); +    NewState(fp, ST_STOPPED); +    (*fp->parent->LayerFinish)(fp->parent->object, fp); +    break; +  case ST_ACKRCVD: +    NewState(fp, ST_REQSENT); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  } +  m_freem(bp); +} + +static void +FsmRecvConfigRej(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +/* RCJ */ +{ +  struct fsm_decode dec; +  size_t plen; +  int flen; +  u_char *cp; + +  plen = m_length(bp); +  flen = ntohs(lhp->length) - sizeof *lhp; +  if ((int)plen < flen) { +    m_freem(bp); +    return; +  } + +  lcp_SendIdentification(&fp->link->lcp); + +  /* +   * Check and process easy case +   */ +  switch (fp->state) { +  case ST_INITIAL: +  case ST_STARTING: +    log_Printf(fp->LogLevel, "%s: Oops, RCJ in %s.\n", +              fp->link->name, State2Nam(fp->state)); +    m_freem(bp); +    return; +  case ST_CLOSED: +  case ST_STOPPED: +    (*fp->fn->SendTerminateAck)(fp, lhp->id); +    m_freem(bp); +    return; +  case ST_CLOSING: +  case ST_STOPPING: +    m_freem(bp); +    return; +  } + +  bp = m_pullup(bp); +  dec.ackend = dec.ack; +  dec.nakend = dec.nak; +  dec.rejend = dec.rej; +  cp = MBUF_CTOP(bp); +  (*fp->fn->DecodeConfig)(fp, cp, cp + flen, MODE_REJ, &dec); +  if (flen < (int)sizeof(struct fsm_opt_hdr)) +    log_Printf(fp->LogLevel, "  [EMPTY]\n"); + +  switch (fp->state) { +  case ST_REQSENT: +  case ST_ACKSENT: +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    FsmSendConfigReq(fp); +    break; +  case ST_OPENED: +    (*fp->fn->LayerDown)(fp); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +    break; +  case ST_ACKRCVD: +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    break; +  } +  m_freem(bp); +} + +static void +FsmRecvCodeRej(struct fsm *fp __unused, struct fsmheader *lhp __unused, +	       struct mbuf *bp) +{ +  m_freem(bp); +} + +static void +FsmRecvProtoRej(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp) +{ +  struct physical *p = link2physical(fp->link); +  u_short proto; + +  if (m_length(bp) < 2) { +    m_freem(bp); +    return; +  } +  bp = mbuf_Read(bp, &proto, 2); +  proto = ntohs(proto); +  log_Printf(fp->LogLevel, "%s: -- Protocol 0x%04x (%s) was rejected!\n", +            fp->link->name, proto, hdlc_Protocol2Nam(proto)); + +  switch (proto) { +  case PROTO_LQR: +    if (p) +      lqr_Stop(p, LQM_LQR); +    else +      log_Printf(LogERROR, "%s: FsmRecvProtoRej: Not a physical link !\n", +                fp->link->name); +    break; +  case PROTO_CCP: +    if (fp->proto == PROTO_LCP) { +      fp = &fp->link->ccp.fsm; +      /* Despite the RFC (1661), don't do an out-of-place TLF */ +      /* (*fp->fn->LayerFinish)(fp); */ +      switch (fp->state) { +      case ST_CLOSED: +      case ST_CLOSING: +        NewState(fp, ST_CLOSED); +        break; +      default: +        NewState(fp, ST_STOPPED); +        break; +      } +      /* See above */ +      /* (*fp->parent->LayerFinish)(fp->parent->object, fp); */ +    } +    break; +  case PROTO_IPCP: +    if (fp->proto == PROTO_LCP) { +      log_Printf(LogPHASE, "%s: IPCP protocol reject closes IPCP !\n", +                fp->link->name); +      fsm_Close(&fp->bundle->ncp.ipcp.fsm); +    } +    break; +#ifndef NOINET6 +  case PROTO_IPV6CP: +    if (fp->proto == PROTO_LCP) { +      log_Printf(LogPHASE, "%s: IPV6CP protocol reject closes IPV6CP !\n", +                fp->link->name); +      fsm_Close(&fp->bundle->ncp.ipv6cp.fsm); +    } +    break; +#endif +  case PROTO_MP: +    if (fp->proto == PROTO_LCP) { +      struct lcp *lcp = fsm2lcp(fp); + +      if (lcp->want_mrru && lcp->his_mrru) { +        log_Printf(LogPHASE, "%s: MP protocol reject is fatal !\n", +                  fp->link->name); +        fsm_Close(fp); +      } +    } +    break; +  } +  m_freem(bp); +} + +static void +FsmRecvEchoReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +{ +  struct lcp *lcp = fsm2lcp(fp); +  u_char *cp; +  u_int32_t magic; + +  bp = m_pullup(bp); +  m_settype(bp, MB_ECHOIN); + +  if (lcp && ntohs(lhp->length) - sizeof *lhp >= 4) { +    cp = MBUF_CTOP(bp); +    ua_ntohl(cp, &magic); +    if (magic != lcp->his_magic) { +      log_Printf(fp->LogLevel, "%s: RecvEchoReq: magic 0x%08lx is wrong," +                 " expecting 0x%08lx\n", fp->link->name, (u_long)magic, +                 (u_long)lcp->his_magic); +      /* XXX: We should send terminate request */ +    } +    if (fp->state == ST_OPENED) { +      ua_htonl(&lcp->want_magic, cp);		/* local magic */ +      fsm_Output(fp, CODE_ECHOREP, lhp->id, cp, +                 ntohs(lhp->length) - sizeof *lhp, MB_ECHOOUT); +    } +  } +  m_freem(bp); +} + +static void +FsmRecvEchoRep(struct fsm *fp, struct fsmheader *lhp __unused, struct mbuf *bp) +{ +  if (fsm2lcp(fp)) +    bp = lqr_RecvEcho(fp, bp); + +  m_freem(bp); +} + +static void +FsmRecvDiscReq(struct fsm *fp __unused, struct fsmheader *lhp __unused, +	       struct mbuf *bp) +{ +  m_freem(bp); +} + +static void +FsmRecvIdent(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +{ +  u_int32_t magic; +  u_short len; +  u_char *cp; + +  len = ntohs(lhp->length) - sizeof *lhp; +  if (len >= 4) { +    bp = m_pullup(m_append(bp, "", 1)); +    cp = MBUF_CTOP(bp); +    ua_ntohl(cp, &magic); +    if (magic != fp->link->lcp.his_magic) +      log_Printf(fp->LogLevel, "%s: RecvIdent: magic 0x%08lx is wrong," +                 " expecting 0x%08lx\n", fp->link->name, (u_long)magic, +                 (u_long)fp->link->lcp.his_magic); +    cp[len] = '\0'; +    lcp_RecvIdentification(&fp->link->lcp, cp + 4); +  } +  m_freem(bp); +} + +static void +FsmRecvTimeRemain(struct fsm *fp __unused, struct fsmheader *lhp __unused, +		  struct mbuf *bp) +{ +  m_freem(bp); +} + +static void +FsmRecvResetReq(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +{ +  if ((*fp->fn->RecvResetReq)(fp)) { +    /* +     * All sendable compressed packets are queued in the first (lowest +     * priority) modem output queue.... dump 'em to the priority queue +     * so that they arrive at the peer before our ResetAck. +     */ +    link_SequenceQueue(fp->link); +    fsm_Output(fp, CODE_RESETACK, lhp->id, NULL, 0, MB_CCPOUT); +  } +  m_freem(bp); +} + +static void +FsmRecvResetAck(struct fsm *fp, struct fsmheader *lhp, struct mbuf *bp) +{ +  (*fp->fn->RecvResetAck)(fp, lhp->id); +  m_freem(bp); +} + +void +fsm_Input(struct fsm *fp, struct mbuf *bp) +{ +  size_t len; +  struct fsmheader lh; +  const struct fsmcodedesc *codep; + +  len = m_length(bp); +  if (len < sizeof(struct fsmheader)) { +    m_freem(bp); +    return; +  } +  bp = mbuf_Read(bp, &lh, sizeof lh); + +  if (ntohs(lh.length) > len) { +    log_Printf(LogWARN, "%s: Oops: Got %zu bytes but %d byte payload " +               "- dropped\n", fp->link->name, len, (int)ntohs(lh.length)); +    m_freem(bp); +    return; +  } + +  if (lh.code < fp->min_code || lh.code > fp->max_code || +      lh.code > sizeof FsmCodes / sizeof *FsmCodes) { +    /* +     * Use a private id.  This is really a response-type packet, but we +     * MUST send a unique id for each REQ.... +     */ +    static u_char id; + +    bp = m_prepend(bp, &lh, sizeof lh, 0); +    bp = m_pullup(bp); +    fsm_Output(fp, CODE_CODEREJ, id++, MBUF_CTOP(bp), bp->m_len, MB_UNKNOWN); +    m_freem(bp); +    return; +  } + +  codep = FsmCodes + lh.code - 1; +  if (lh.id != fp->reqid && codep->check_reqid && +      Enabled(fp->bundle, OPT_IDCHECK)) { +    log_Printf(fp->LogLevel, "%s: Recv%s(%d), dropped (expected %d)\n", +               fp->link->name, codep->name, lh.id, fp->reqid); +    return; +  } + +  log_Printf(fp->LogLevel, "%s: Recv%s(%d) state = %s\n", +             fp->link->name, codep->name, lh.id, State2Nam(fp->state)); + +  if (codep->inc_reqid && (lh.id == fp->reqid || +      (!Enabled(fp->bundle, OPT_IDCHECK) && codep->check_reqid))) +    fp->reqid++;	/* That's the end of that ``exchange''.... */ + +  (*codep->recv)(fp, &lh, bp); +} + +int +fsm_NullRecvResetReq(struct fsm *fp) +{ +  log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset req\n", +            fp->link->name); +  return 1; +} + +void +fsm_NullRecvResetAck(struct fsm *fp, u_char id __unused) +{ +  log_Printf(fp->LogLevel, "%s: Oops - received unexpected reset ack\n", +            fp->link->name); +} + +void +fsm_Reopen(struct fsm *fp) +{ +  if (fp->state == ST_OPENED) { +    (*fp->fn->LayerDown)(fp); +    FsmInitRestartCounter(fp, FSM_REQ_TIMER); +    FsmSendConfigReq(fp); +    NewState(fp, ST_REQSENT); +    (*fp->parent->LayerDown)(fp->parent->object, fp); +  } +} + +void +fsm2initial(struct fsm *fp) +{ +  timer_Stop(&fp->FsmTimer); +  timer_Stop(&fp->OpenTimer); +  timer_Stop(&fp->StoppedTimer); +  if (fp->state == ST_STOPPED) +    fsm_Close(fp); +  if (fp->state > ST_INITIAL) +    fsm_Down(fp); +  if (fp->state > ST_INITIAL) +    fsm_Close(fp); +} + +struct fsm_opt * +fsm_readopt(u_char **cp) +{ +  struct fsm_opt *o = (struct fsm_opt *)*cp; + +  if (o->hdr.len < sizeof(struct fsm_opt_hdr)) { +    log_Printf(LogERROR, "Bad option length %d (out of phase?)\n", o->hdr.len); +    return NULL; +  } + +  *cp += o->hdr.len; + +  if (o->hdr.len > sizeof(struct fsm_opt)) { +    log_Printf(LogERROR, "Warning: Truncating option length from %d to %d\n", +               o->hdr.len, (int)sizeof(struct fsm_opt)); +    o->hdr.len = sizeof(struct fsm_opt); +  } + +  return o; +} + +static int +fsm_opt(u_char *opt, int optlen, const struct fsm_opt *o) +{ +  unsigned cplen = o->hdr.len; + +  if (optlen < (int)sizeof(struct fsm_opt_hdr)) +    optlen = 0; + +  if ((int)cplen > optlen) { +    log_Printf(LogERROR, "Can't REJ length %d - trunating to %d\n", +      cplen, optlen); +    cplen = optlen; +  } +  memcpy(opt, o, cplen); +  if (cplen) +    opt[1] = cplen; + +  return cplen; +} + +void +fsm_rej(struct fsm_decode *dec, const struct fsm_opt *o) +{ +  if (!dec) +    return; +  dec->rejend += fsm_opt(dec->rejend, FSM_OPTLEN - (dec->rejend - dec->rej), o); +} + +void +fsm_ack(struct fsm_decode *dec, const struct fsm_opt *o) +{ +  if (!dec) +    return; +  dec->ackend += fsm_opt(dec->ackend, FSM_OPTLEN - (dec->ackend - dec->ack), o); +} + +void +fsm_nak(struct fsm_decode *dec, const struct fsm_opt *o) +{ +  if (!dec) +    return; +  dec->nakend += fsm_opt(dec->nakend, FSM_OPTLEN - (dec->nakend - dec->nak), o); +} + +void +fsm_opt_normalise(struct fsm_decode *dec) +{ +  if (dec->rejend != dec->rej) { +    /* rejects are preferred */ +    dec->ackend = dec->ack; +    dec->nakend = dec->nak; +  } else if (dec->nakend != dec->nak) +    /* then NAKs */ +    dec->ackend = dec->ack; +} | 
