/* * fsm.c * * Written by Toshiharu OHNO * Copyright (c) 1993, Internet Initiative Japan, Inc. All rights reserved. * See ``COPYRIGHT.iij'' * * Rewritten by Archie Cobbs * Copyright (c) 1995-1999 Whistle Communications, Inc. All rights reserved. * See ``COPYRIGHT.whistle'' */ #include "ppp.h" #include "fsm.h" #include "ngfunc.h" /* * DEFINITIONS */ #define FSM_MAXCONFIG 10 #define FSM_MAXNAK 10 #define FSM_MAX_OPTS 100 #define FSM_MAXTERMINATE 2 #define FSM_MAXFAILURE 5 /* FSM restart options */ /* #define RESTART_OPENED */ /* #define RESTART_CLOSING */ /* #define RESTART_STOPPED */ struct fsmcodedesc { void (*action)(Fsm fp, FsmHeader hdr, Mbuf bp); const char *name; }; /* Size of REQ, ACK, NAK, and REJ buffers */ #define REQ_BUFFER_SIZE 256 #define REJ_BUFFER_SIZE 256 #define ACK_BUFFER_SIZE 256 #define NAK_BUFFER_SIZE 256 /* * INTERNAL FUNCTIONS */ static void FsmNewState(Fsm f, int state); static void FsmSendConfigReq(Fsm fp); static void FsmSendTerminateReq(Fsm fp); static void FsmSendTerminateAck(Fsm fp); static void FsmInitRestartCounter(Fsm fp, int value); static void FsmInitMaxFailure(Fsm fp, int value); static void FsmInitMaxConfig(Fsm fp, int value); static void FsmTimeout(void *arg); static void FsmRecvConfigReq(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvConfigAck(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvConfigNak(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvTermReq(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvTermAck(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvConfigRej(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvCodeRej(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvProtoRej(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvEchoReq(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvEchoRep(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvDiscReq(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvIdent(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvTimeRemain(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvResetReq(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvResetAck(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvVendor(Fsm fp, FsmHeader lhp, Mbuf bp); static void FsmRecvRxjPlus(Fsm fp); static void FsmLayerUp(Fsm fp); static void FsmLayerDown(Fsm fp); static void FsmLayerStart(Fsm fp); static void FsmLayerFinish(Fsm fp); static Mbuf FsmCheckMagic(Fsm fp, Mbuf bp); static void FsmEchoTimeout(void *arg); static void FsmDecodeBuffer(Fsm fp, u_char *buf, int size, int mode); static int FsmExtractOptions(Fsm fp, u_char *data, int dlen, FsmOption opts, int max); /* * GLOBAL VARIABLES */ u_int gAckSize, gNakSize, gRejSize; /* * INTERNAL VARIABLES */ /* * It's OK for these to be statically allocated, because we always * do our work with them all in one whole step without stopping. */ static u_char gRejBuf[REJ_BUFFER_SIZE]; static u_char gAckBuf[ACK_BUFFER_SIZE]; static u_char gNakBuf[NAK_BUFFER_SIZE]; static const struct fsmcodedesc FsmCodes[] = { { FsmRecvVendor, "Vendor Packet" }, { FsmRecvConfigReq, "Configure Request" }, { FsmRecvConfigAck, "Configure Ack" }, { FsmRecvConfigNak, "Configure Nak" }, { FsmRecvConfigRej, "Configure Reject" }, { FsmRecvTermReq, "Terminate Request" }, { FsmRecvTermAck, "Terminate Ack" }, { FsmRecvCodeRej, "Code Reject" }, { FsmRecvProtoRej, "Protocol Reject" }, { FsmRecvEchoReq, "Echo Request" }, { FsmRecvEchoRep, "Echo Reply" }, { FsmRecvDiscReq, "Discard Request" }, { FsmRecvIdent, "Ident" }, { FsmRecvTimeRemain, "Time Remain" }, { FsmRecvResetReq, "Reset Request" }, { FsmRecvResetAck, "Reset Ack" }, }; #define NUM_FSM_CODES (sizeof(FsmCodes) / sizeof(*FsmCodes)) /* * FsmInit() * * Initialize FSM structure and install some default values */ void FsmInit(Fsm fp, FsmType type) { memset(fp, 0, sizeof(*fp)); fp->type = type; fp->log = LG_FSM | type->log; fp->conf.maxconfig = FSM_MAXCONFIG; fp->conf.maxterminate = FSM_MAXTERMINATE; fp->conf.maxfailure = FSM_MAXFAILURE; fp->conf.check_magic = FALSE; fp->conf.passive = FALSE; fp->state = ST_INITIAL; fp->reqid = 1; fp->rejid = 1; fp->echoid = 1; } /* * FsmNewState() * * Change state of a FSM. Also, call the configuration routine * if this is an appropriate time. */ void FsmNewState(Fsm fp, int new) { int old; /* Log it */ Log(fp->log, ("%s: state change %s --> %s", Pref(fp), FsmStateName(fp->state), FsmStateName(new))); /* Change state and call protocol's own handler, if any */ old = fp->state; if (fp->type->NewState) (*fp->type->NewState)(fp, old, new); fp->state = new; if ((new >= ST_INITIAL && new <= ST_STOPPED) || (new == ST_OPENED)) TimerStop(&fp->timer); /* Turn on/off keep-alive echo packets (if so configured) */ TimerStop(&fp->echoTimer); if (new == ST_OPENED && fp->conf.echo_int != 0) { fp->quietCount = 0; memset(&fp->idleStats, 0, sizeof(fp->idleStats)); TimerInit(&fp->echoTimer, "FsmKeepAlive", fp->conf.echo_int * SECONDS, FsmEchoTimeout, fp); TimerStart(&fp->echoTimer); } } /* * FsmOutputMbuf() * * Send out a control packet with specified contents. Consumes the mbuf. */ void FsmOutputMbuf(Fsm fp, u_int code, u_int id, Mbuf payload) { FsmOutputMbuf2(fp->type->proto, fp->type->link_layer, code, id, payload); } /* * FsmOutputMbuf2() */ void FsmOutputMbuf2(u_short proto, int linklayer, u_int code, u_int id, Mbuf payload) { Mbuf bp; struct fsmheader hdr; /* Build header */ hdr.id = id; hdr.code = code; hdr.length = htons(sizeof(hdr) + plength(payload)); /* Prepend to payload */ bp = mbwrite(mballoc(MB_FSM, sizeof(hdr)), (u_char *) &hdr, sizeof(hdr)); bp->next = payload; /* Send it out */ NgFuncWritePppFrame(linklayer ? lnk->bundleIndex : NG_PPP_BUNDLE_LINKNUM, proto, bp); } /* * FsmOutput() * * Send out a control packet with specified contents */ void FsmOutput(Fsm fp, u_int code, u_int id, u_char *ptr, int len) { Mbuf bp; bp = (len > 0) ? mbwrite(mballoc(MB_FSM, len), ptr, len) : NULL; FsmOutputMbuf(fp, code, id, bp); } /* * FsmOpen() * * XXX The use of the restart option should probably be * XXX configured per FSM via the initialization structure. * XXX For now, we just make it mandatory via #ifdef's. */ void FsmOpen(Fsm fp) { Log(fp->log, ("%s: Open event", Pref(fp))); switch (fp->state) { case ST_INITIAL: FsmNewState(fp, ST_STARTING); FsmLayerStart(fp); break; case ST_STARTING: break; case ST_CLOSED: if (fp->type->Configure) (*fp->type->Configure)(fp); FsmNewState(fp, ST_REQSENT); FsmLayerStart(fp); /* Missing in RFC 1661 */ FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmInitMaxFailure(fp, fp->conf.maxfailure); FsmInitMaxConfig(fp, fp->conf.maxconfig); FsmSendConfigReq(fp); break; case ST_REQSENT: case ST_ACKRCVD: case ST_ACKSENT: break; case ST_OPENED: #ifdef RESTART_OPENED FsmDown(fp); FsmUp(fp); #endif break; case ST_CLOSING: FsmNewState(fp, ST_STOPPING); case ST_STOPPING: #ifdef RESTART_CLOSING FsmDown(fp); FsmUp(fp); #endif break; case ST_STOPPED: #ifdef RESTART_STOPPED FsmDown(fp); FsmUp(fp); #endif break; } } void FsmUp(Fsm fp) { Log(fp->log, ("%s: Up event", Pref(fp))); switch (fp->state) { case ST_INITIAL: FsmNewState(fp, ST_CLOSED); break; case ST_STARTING: if (fp->type->Configure) (*fp->type->Configure)(fp); FsmNewState(fp, ST_REQSENT); FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmInitMaxFailure(fp, fp->conf.maxfailure); FsmInitMaxConfig(fp, fp->conf.maxconfig); FsmSendConfigReq(fp); break; default: Log(fp->log, ("%s: Oops, UP at %s", Pref(fp), FsmStateName(fp->state))); break; } } void FsmDown(Fsm fp) { Log(fp->log, ("%s: Down event", Pref(fp))); switch (fp->state) { case ST_CLOSING: FsmLayerFinish(fp); /* Missing in RFC 1661 */ /* fall through */ case ST_CLOSED: FsmNewState(fp, ST_INITIAL); break; case ST_STOPPED: FsmNewState(fp, ST_STARTING); FsmLayerStart(fp); break; case ST_STOPPING: case ST_REQSENT: case ST_ACKRCVD: case ST_ACKSENT: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_STARTING); break; case ST_OPENED: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_STARTING); FsmLayerDown(fp); break; } } void FsmClose(Fsm fp) { Log(fp->log, ("%s: Close event", Pref(fp))); switch (fp->state) { case ST_STARTING: FsmNewState(fp, ST_INITIAL); FsmLayerFinish(fp); break; case ST_STOPPED: FsmNewState(fp, ST_CLOSED); break; case ST_STOPPING: FsmNewState(fp, ST_CLOSING); break; case ST_OPENED: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_CLOSING); FsmInitRestartCounter(fp, fp->conf.maxterminate); FsmSendTerminateReq(fp); FsmLayerDown(fp); break; case ST_REQSENT: case ST_ACKRCVD: case ST_ACKSENT: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_CLOSING); FsmInitRestartCounter(fp, fp->conf.maxterminate); FsmSendTerminateReq(fp); break; } } /* * Send functions */ static void FsmSendConfigReq(Fsm fp) { u_char reqBuf[REQ_BUFFER_SIZE]; u_char *cp; /* Build and display config request */ Log(fp->log, ("%s: SendConfigReq #%d", Pref(fp), fp->reqid)); cp = (*fp->type->BuildConfigReq)(fp, reqBuf); FsmDecodeBuffer(fp, reqBuf, cp - reqBuf, MODE_NOP); /* Send it */ FsmOutput(fp, CODE_CONFIGREQ, fp->reqid++, reqBuf, cp - reqBuf); /* Restart restart timer and decrement restart counter */ TimerStart(&fp->timer); fp->restart--; fp->config--; } static void FsmSendTerminateReq(Fsm fp) { Log(fp->log, ("%s: SendTerminateReq #%d", Pref(fp), fp->reqid)); FsmOutput(fp, CODE_TERMREQ, fp->reqid++, NULL, 0); if (fp->type->SendTerminateReq) (*fp->type->SendTerminateReq)(fp); TimerStart(&fp->timer); /* Restart restart timer */ fp->restart--; /* Decrement restart counter */ } static void FsmSendTerminateAck(Fsm fp) { Log(fp->log, ("%s: SendTerminateAck #%d", Pref(fp), fp->reqid)); FsmOutput(fp, CODE_TERMACK, fp->reqid++, NULL, 0); if (fp->type->SendTerminateAck) (*fp->type->SendTerminateAck)(fp); } static void FsmSendConfigAck(Fsm fp, FsmHeader lhp, u_char *option, int count) { Log(fp->log, ("%s: SendConfigAck #%d", Pref(fp), lhp->id)); FsmDecodeBuffer(fp, option, count, MODE_NOP); FsmOutput(fp, CODE_CONFIGACK, lhp->id, option, count); } static void FsmSendConfigRej(Fsm fp, FsmHeader lhp, u_char *option, int count) { Log(fp->log, ("%s: SendConfigRej #%d", Pref(fp), lhp->id)); FsmDecodeBuffer(fp, option, count, MODE_NOP); FsmOutput(fp, CODE_CONFIGREJ, lhp->id, option, count); fp->failure--; } static void FsmSendConfigNak(Fsm fp, FsmHeader lhp, u_char *option, int count) { Log(fp->log, ("%s: SendConfigNak #%d", Pref(fp), lhp->id)); FsmDecodeBuffer(fp, option, count, MODE_NOP); FsmOutput(fp, CODE_CONFIGNAK, lhp->id, option, count); fp->failure--; } /* * Timeout actions */ static void FsmTimeout(void *arg) { Fsm fp = (Fsm) arg; if (fp->restart > 0) { /* TO+ */ switch (fp->state) { case ST_CLOSING: case ST_STOPPING: FsmSendTerminateReq(fp); break; case ST_REQSENT: case ST_ACKSENT: FsmSendConfigReq(fp); break; case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); FsmSendConfigReq(fp); break; } TimerStart(&fp->timer); } else { /* TO- */ switch (fp->state) { case ST_CLOSING: FsmNewState(fp, ST_CLOSED); FsmLayerFinish(fp); break; case ST_STOPPING: FsmNewState(fp, ST_STOPPED); FsmLayerFinish(fp); break; case ST_REQSENT: case ST_ACKSENT: case ST_ACKRCVD: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_STOPPED); if (!fp->conf.passive) FsmLayerFinish(fp); FsmFailure(fp, FAIL_NEGOT_FAILURE); break; } } } void FsmInitRestartCounter(Fsm fp, int value) { const int retry = fp->type->link_layer ? lnk->conf.retry_timeout : bund->conf.retry_timeout; TimerStop(&fp->timer); TimerInit(&fp->timer, fp->type->name, retry * SECONDS, FsmTimeout, (void *) fp); fp->restart = value; } void FsmInitMaxFailure(Fsm fp, int value) { fp->failure = value; } void FsmInitMaxConfig(Fsm fp, int value) { fp->config = value; } /* * Actions that happen when we receive packets */ /* RCR */ static void FsmRecvConfigReq(Fsm fp, FsmHeader lhp, Mbuf bp) { int fullyAcked; /* Check and process easy cases */ switch (fp->state) { case ST_INITIAL: case ST_STARTING: Log(fp->log, ("%s: Oops, RCR in %s", Pref(fp), FsmStateName(fp->state))); PFREE(bp); return; case ST_CLOSED: FsmSendTerminateAck(fp); PFREE(bp); return; case ST_STOPPED: if (fp->type->Configure) (*fp->type->Configure)(fp); break; case ST_CLOSING: case ST_STOPPING: PFREE(bp); return; } /* Decode packet */ bp = mbunify(bp); FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_REQ); /* State specific actions */ switch (fp->state) { case ST_OPENED: FsmLayerDown(fp); FsmSendConfigReq(fp); break; case ST_STOPPED: FsmLayerStart(fp); /* Missing in RFC 1661 */ FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmInitMaxFailure(fp, fp->conf.maxfailure); FsmInitMaxConfig(fp, fp->conf.maxconfig); FsmSendConfigReq(fp); break; } /* What did we think of packet? */ fullyAcked = (gNakSize == 0 && gRejSize == 0); if (fullyAcked) FsmSendConfigAck(fp, lhp, gAckBuf, gAckSize); else { if (fp->failure <= 0) { Log(fp->log, ("%s: not converging", Pref(fp))); FsmFailure(fp, FAIL_NEGOT_FAILURE); PFREE(bp); return; } else { if (gRejSize) FsmSendConfigRej(fp, lhp, gRejBuf, gRejSize); else if (gNakSize) FsmSendConfigNak(fp, lhp, gNakBuf, gNakSize); } } /* Continue with state transition */ switch (fp->state) { case ST_STOPPED: case ST_OPENED: if (fullyAcked) FsmNewState(fp, ST_ACKSENT); else FsmNewState(fp, ST_REQSENT); break; case ST_REQSENT: if (fullyAcked) FsmNewState(fp, ST_ACKSENT); break; case ST_ACKRCVD: if (fullyAcked) { FsmNewState(fp, ST_OPENED); FsmLayerUp(fp); } break; case ST_ACKSENT: if (!fullyAcked) FsmNewState(fp, ST_REQSENT); break; } PFREE(bp); } /* RCA */ static void FsmRecvConfigAck(Fsm fp, FsmHeader lhp, Mbuf bp) { /* Check sequence number */ if (lhp->id != (u_char) (fp->reqid - 1)) { Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1))); PFREE(bp); return; } /* XXX We should verify the contents are equal to our last sent config-req */ /* Decode packet */ bp = mbunify(bp); FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_NOP); /* Do whatever */ switch (fp->state) { case ST_CLOSED: case ST_STOPPED: FsmSendTerminateAck(fp); break; case ST_CLOSING: case ST_STOPPING: break; case ST_REQSENT: FsmNewState(fp, ST_ACKRCVD); FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmInitMaxFailure(fp, fp->conf.maxfailure); FsmInitMaxConfig(fp, fp->conf.maxconfig); break; case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); FsmSendConfigReq(fp); break; case ST_ACKSENT: FsmNewState(fp, ST_OPENED); FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmInitMaxFailure(fp, fp->conf.maxfailure); FsmInitMaxConfig(fp, fp->conf.maxconfig); FsmLayerUp(fp); break; case ST_OPENED: FsmNewState(fp, ST_REQSENT); FsmLayerDown(fp); FsmSendConfigReq(fp); break; } PFREE(bp); } /* RCN */ static void FsmRecvConfigNak(Fsm fp, FsmHeader lhp, Mbuf bp) { /* Check sequence number */ if (lhp->id != (u_char) (fp->reqid - 1)) { Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1))); PFREE(bp); return; } /* Check and process easy cases */ switch (fp->state) { case ST_INITIAL: case ST_STARTING: Log(fp->log, ("%s: Oops, RCN in %s", Pref(fp), FsmStateName(fp->state))); PFREE(bp); return; case ST_CLOSED: case ST_STOPPED: FsmSendTerminateAck(fp); PFREE(bp); return; case ST_CLOSING: case ST_STOPPING: PFREE(bp); return; } /* Decode packet */ bp = mbunify(bp); FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_NAK); /* Not converging? */ if (fp->config <= 0) { Log(fp->log, ("%s: not converging", Pref(fp))); FsmFailure(fp, FAIL_NEGOT_FAILURE); PFREE(bp); return; } /* Do whatever */ switch (fp->state) { case ST_REQSENT: case ST_ACKSENT: FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmSendConfigReq(fp); break; case ST_OPENED: FsmLayerDown(fp); /* fall through */ case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); FsmSendConfigReq(fp); break; } PFREE(bp); } /* RCJ */ static void FsmRecvConfigRej(Fsm fp, FsmHeader lhp, Mbuf bp) { /* Check sequence number */ if (lhp->id != (u_char) (fp->reqid - 1)) { Log(fp->log, (" Wrong id#, expecting %d", (u_char) (fp->reqid - 1))); PFREE(bp); return; } /* XXX should verify contents are a subset of previously sent config-req */ /* Check and process easy cases */ switch (fp->state) { case ST_INITIAL: case ST_STARTING: Log(fp->log, ("%s: Oops, RCJ in %s", Pref(fp), FsmStateName(fp->state))); PFREE(bp); return; case ST_CLOSED: case ST_STOPPED: FsmSendTerminateAck(fp); PFREE(bp); return; case ST_CLOSING: case ST_STOPPING: PFREE(bp); return; } /* Decode packet */ bp = mbunify(bp); FsmDecodeBuffer(fp, MBDATA(bp), MBLEN(bp), MODE_REJ); /* Not converging? */ if (fp->config <= 0) { Log(fp->log, ("%s: not converging", Pref(fp))); FsmFailure(fp, FAIL_NEGOT_FAILURE); PFREE(bp); return; } /* Do whatever */ switch (fp->state) { case ST_REQSENT: case ST_ACKSENT: FsmInitRestartCounter(fp, fp->conf.maxconfig); FsmSendConfigReq(fp); break; case ST_OPENED: FsmNewState(fp, ST_REQSENT); FsmLayerDown(fp); FsmSendConfigReq(fp); break; case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); FsmSendConfigReq(fp); break; } PFREE(bp); } /* RTR */ static void FsmRecvTermReq(Fsm fp, FsmHeader lhp, Mbuf bp) { RecordLinkUpDownReason(fp->type->link_layer ? lnk : NULL, 0, STR_PEER_DISC, lcats(STR_PEER_DISCONNECT)); switch (fp->state) { case ST_INITIAL: case ST_STARTING: Log(fp->log, ("%s: Oops, RTR in %s", Pref(fp), FsmStateName(fp->state))); break; case ST_CLOSED: case ST_STOPPED: case ST_CLOSING: case ST_STOPPING: case ST_REQSENT: FsmSendTerminateAck(fp); break; case ST_ACKRCVD: case ST_ACKSENT: FsmNewState(fp, ST_REQSENT); FsmSendTerminateAck(fp); break; case ST_OPENED: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_STOPPING); FsmSendTerminateAck(fp); FsmLayerDown(fp); FsmInitRestartCounter(fp, 0); /* Zero restart counter */ TimerStart(&fp->timer); /* Start restart timer */ break; } PFREE(bp); } /* RTA */ static void FsmRecvTermAck(Fsm fp, FsmHeader lhp, Mbuf bp) { switch (fp->state) { case ST_CLOSING: FsmNewState(fp, ST_CLOSED); FsmLayerFinish(fp); break; case ST_STOPPING: FsmNewState(fp, ST_STOPPED); FsmLayerFinish(fp); break; case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); break; case ST_OPENED: FsmNewState(fp, ST_REQSENT); FsmLayerDown(fp); FsmSendConfigReq(fp); break; } PFREE(bp); } /* * FsmRecvCodeRej() * * By default, this is fatal for most codes */ static void FsmRecvCodeRej(Fsm fp, FsmHeader lhp, Mbuf bp) { u_char code = 0; int fatal; /* Get code and log it */ bp = mbread(bp, &code, sizeof(code), NULL); Log(fp->log, ("%s: code %s was rejected", Pref(fp), FsmCodeName(code))); /* Determine fatalness */ if (fp->type->RecvCodeRej) fatal = (*fp->type->RecvCodeRej)(fp, code, bp); else { switch (code) { case CODE_CONFIGREQ: case CODE_CONFIGACK: case CODE_CONFIGNAK: case CODE_CONFIGREJ: case CODE_TERMREQ: case CODE_TERMACK: case CODE_CODEREJ: case CODE_PROTOREJ: case CODE_ECHOREQ: case CODE_ECHOREP: case CODE_RESETREQ: case CODE_RESETACK: fatal = TRUE; break; case CODE_VENDOR: case CODE_DISCREQ: case CODE_IDENT: case CODE_TIMEREM: default: /* if we don't know it, that makes two of us */ fatal = FALSE; break; } } /* Possibly shut down as a result */ if (fatal) FsmFailure(fp, FAIL_RECD_CODEREJ); /* RXJ- */ else FsmRecvRxjPlus(fp); PFREE(bp); } /* * FsmRecvProtoRej() * * By default, this is non-fatal */ static void FsmRecvProtoRej(Fsm fp, FsmHeader lhp, Mbuf bp) { u_short proto = 0; int fatal = FALSE; bp = mbread(bp, (u_char *) &proto, sizeof(proto), NULL); proto = ntohs(proto); Log(fp->log, ("%s: protocol %s was rejected", Pref(fp), ProtoName(proto))); if (fp->state == ST_OPENED && fp->type->RecvProtoRej) fatal = (*fp->type->RecvProtoRej)(fp, proto, bp); if (fatal) FsmFailure(fp, FAIL_RECD_PROTREJ); /* RXJ- */ else FsmRecvRxjPlus(fp); PFREE(bp); } /* * FsmRecvRxjPlus() */ static void FsmRecvRxjPlus(Fsm fp) /* RXJ+ */ { switch (fp->state) { case ST_ACKRCVD: FsmNewState(fp, ST_REQSENT); break; } } /* * FsmFailure() * * Call this at any point if something fatal happens that is inherent * to the FSM itself, like a RXJ- event or negotiation failed to converge. * The action taken is like the RXJ- event. */ void FsmFailure(Fsm fp, enum fsmfail reason) { Log(fp->log, ("%s: %s", Pref(fp), FsmFailureStr(reason))); /* Let layer do any special handling of error code */ if (fp->type->Failure) (*fp->type->Failure)(fp, reason); /* Shut this state machine down */ switch (fp->state) { case ST_CLOSING: FsmNewState(fp, ST_CLOSED); /* fall through */ case ST_CLOSED: FsmLayerFinish(fp); break; case ST_STOPPING: FsmNewState(fp, ST_STOPPED); FsmLayerFinish(fp); break; case ST_ACKRCVD: case ST_ACKSENT: case ST_REQSENT: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmNewState(fp, ST_STOPPED); if (!fp->conf.passive) FsmLayerFinish(fp); break; /* * In the opened state, the peer FSM has somehow died or did something * horribly wrong to us. So the common sense action in this case is to * bring the whole link down, rather than restarting just the FSM. * We do this by telling the lower layer to restart using tlf and tls * while also pretending that we got a DOWN event. */ case ST_OPENED: if (fp->type->UnConfigure) (*fp->type->UnConfigure)(fp); FsmLayerFinish(fp); FsmLayerStart(fp); FsmNewState(fp, ST_STARTING); /* Pretend we got a DOWN event */ FsmLayerDown(fp); break; /* * In the STOPPED state, then most likely the negotiation timed out * or didn't converge. Just wait for the DOWN event from the previously * issued tlf action. */ case ST_STOPPED: if (!fp->conf.passive) FsmLayerFinish(fp); break; } } /* * FsmFailureStr() */ const char * FsmFailureStr(enum fsmfail reason) { const char *string = NULL; switch (reason) { case FAIL_NEGOT_FAILURE: string = lcats(STR_FAIL_NEGOT_FAILURE); break; case FAIL_RECD_BADMAGIC: string = lcats(STR_FAIL_RECD_BADMAGIC); break; case FAIL_RECD_CODEREJ: string = lcats(STR_FAIL_RECD_CODEREJ); break; case FAIL_RECD_PROTREJ: string = lcats(STR_FAIL_RECD_PROTREJ); break; case FAIL_WAS_PROTREJ: string = lcats(STR_FAIL_WAS_PROTREJ); break; case FAIL_ECHO_TIMEOUT: string = lcats(STR_FAIL_ECHO_TIMEOUT); break; case FAIL_CANT_ENCRYPT: string = lcats(STR_FAIL_CANT_ENCRYPT); break; default: assert(0); } return(string); } /* * FsmSendEchoReq() * * Send an echo request containing the supplied payload data. * Consumes the mbuf. */ void FsmSendEchoReq(Fsm fp, Mbuf payload) { u_int32_t const self_magic = htonl(lnk->lcp.want_magic); Mbuf bp; if (fp->state != ST_OPENED) return; /* Prepend my magic number */ bp = mbwrite(mballoc(MB_ECHO, sizeof(self_magic)), (u_char *) &self_magic, sizeof(self_magic)); bp->next = payload; /* Send it */ Log(LG_ECHO, ("%s: SendEchoReq #%d", Pref(fp), fp->echoid)); FsmOutputMbuf(fp, CODE_ECHOREQ, fp->echoid++, bp); } /* * FsmSendIdent() * * Send an LCP ident packet. Consumes the mbuf. */ void FsmSendIdent(Fsm fp, const char *ident) { u_int32_t self_magic = htonl(lnk->lcp.want_magic); const int len = strlen(ident) + 1; /* include NUL to be nice */ Mbuf bp; /* Leave magic zero unless fully opened, as IDENT can be sent anytime */ if (fp->state != ST_OPENED) self_magic = 0; /* Prepend my magic number */ bp = mbwrite(mballoc(MB_FSM, sizeof(self_magic)), (u_char *) &self_magic, sizeof(self_magic)); bp->next = mbwrite(mballoc(MB_FSM, len), (u_char *) ident, len); /* Send it */ Log(LG_FSM, ("%s: SendIdent #%d", Pref(fp), fp->echoid)); ShowMesg(LG_FSM, ident, len); FsmOutputMbuf(fp, CODE_IDENT, fp->echoid++, bp); } /* * FsmRecvEchoReq() */ static void FsmRecvEchoReq(Fsm fp, FsmHeader lhp, Mbuf bp) { u_int32_t self_magic; Mbuf mbp; /* Validate magic number */ bp = FsmCheckMagic(fp, bp); /* If not opened, do nothing */ if (fp->state != ST_OPENED) { PFREE(bp); return; } /* Stick my magic number in there instead */ self_magic = htonl(lnk->lcp.want_magic); mbp = mbwrite(mballoc(MB_FSM, sizeof(self_magic)), (u_char *) &self_magic, sizeof(self_magic)); mbp->next = bp; bp = mbp; /* Send it back, preserving everything else */ Log(LG_ECHO, ("%s: SendEchoRep #%d", Pref(fp), lhp->id)); FsmOutputMbuf(fp, CODE_ECHOREP, lhp->id, bp); } /* * FsmRecvEchoRep() */ static void FsmRecvEchoRep(Fsm fp, FsmHeader lhp, Mbuf bp) { bp = FsmCheckMagic(fp, bp); PFREE(bp); } /* * FsmRecvDiscReq() */ static void FsmRecvDiscReq(Fsm fp, FsmHeader lhp, Mbuf bp) { bp = FsmCheckMagic(fp, bp); if (fp->type->RecvDiscReq) (*fp->type->RecvDiscReq)(fp, bp); PFREE(bp); } /* * FsmRecvIdent() */ static void FsmRecvIdent(Fsm fp, FsmHeader lhp, Mbuf bp) { bp = FsmCheckMagic(fp, bp); if (fp->type->RecvIdent) (*fp->type->RecvIdent)(fp, bp); else if (bp) ShowMesg(fp->log, MBDATA(bp), MBLEN(bp)); PFREE(bp); } /* * FsmRecvVendor() */ static void FsmRecvVendor(Fsm fp, FsmHeader lhp, Mbuf bp) { bp = FsmCheckMagic(fp, bp); if (fp->type->RecvVendor) (*fp->type->RecvVendor)(fp, bp); PFREE(bp); } /* * FsmRecvTimeRemain() */ static void FsmRecvTimeRemain(Fsm fp, FsmHeader lhp, Mbuf bp) { bp = FsmCheckMagic(fp, bp); if (fp->type->RecvTimeRemain) (*fp->type->RecvTimeRemain)(fp, bp); else { u_int32_t remain = 0; bp = mbread(bp, (u_char *) &remain, sizeof(remain), NULL); remain = ntohl(remain); Log(fp->log, (" %u seconds remain", remain)); if (bp) ShowMesg(fp->log, MBDATA(bp), MBLEN(bp)); } PFREE(bp); } /* * FsmRecvResetReq() */ static void FsmRecvResetReq(Fsm fp, FsmHeader lhp, Mbuf bp) { if (fp->type->RecvResetReq) (*fp->type->RecvResetReq)(fp, lhp->id, bp); PFREE(bp); } /* * FsmRecvResetAck() */ static void FsmRecvResetAck(Fsm fp, FsmHeader lhp, Mbuf bp) { if (fp->type->RecvResetAck) (*fp->type->RecvResetAck)(fp, lhp->id, bp); PFREE(bp); } /* * FsmLayerUp() */ static void FsmLayerUp(Fsm fp) { Log(fp->log, ("%s: LayerUp", Pref(fp))); if (fp->type->LayerUp) (*fp->type->LayerUp)(fp); } /* * FsmLayerDown() */ static void FsmLayerDown(Fsm fp) { Log(fp->log, ("%s: LayerDown", Pref(fp))); if (fp->type->LayerDown) (*fp->type->LayerDown)(fp); } /* * FsmLayerStart() */ static void FsmLayerStart(Fsm fp) { Log(fp->log, ("%s: LayerStart", Pref(fp))); if (fp->type->LayerStart) (*fp->type->LayerStart)(fp); } /* * FsmLayerFinish() */ static void FsmLayerFinish(Fsm fp) { Log(fp->log, ("%s: LayerFinish", Pref(fp))); if (fp->type->LayerFinish) (*fp->type->LayerFinish)(fp); } /* * FsmCheckMagic() */ static Mbuf FsmCheckMagic(Fsm fp, Mbuf bp) { u_int32_t peer_magic; u_int32_t peer_magic_ought; /* Read magic number */ bp = mbread(bp, (u_char *) &peer_magic, sizeof(peer_magic), NULL); peer_magic = ntohl(peer_magic); /* What should magic number be? */ if (PROT_LINK_LAYER(fp->type->proto)) peer_magic_ought = lnk->lcp.peer_magic; else peer_magic_ought = 0; /* Verify */ if (fp->conf.check_magic && peer_magic != 0 && peer_magic != peer_magic_ought) { Log(fp->log, ("%s: magic number is wrong: 0x%08x != 0x%08x", Pref(fp), peer_magic, peer_magic_ought)); FsmFailure(fp, FAIL_RECD_BADMAGIC); } return(bp); } /* * FsmEchoTimeout() */ static void FsmEchoTimeout(void *arg) { Fsm const fp = (Fsm) arg; struct ng_ppp_link_stat oldStats; /* See if there was any traffic since last time */ oldStats = fp->idleStats; NgFuncGetStats(fp->type->link_layer ? lnk->bundleIndex : NG_PPP_BUNDLE_LINKNUM, FALSE, &fp->idleStats); if (fp->idleStats.recvFrames > oldStats.recvFrames) fp->quietCount = 0; else fp->quietCount++; /* See if peer hasn't responded for too many requests */ TimerStop(&fp->echoTimer); switch (fp->quietCount) { /* Peer failed to reply to previous echo request */ default: Log(LG_ECHO|fp->log, ("%s: no reply to %d echo request(s)", Pref(fp), fp->quietCount - 1)); /* Has peer failed to reply for maximum allowable interval? */ if (fp->quietCount * fp->conf.echo_int >= fp->conf.echo_max) { FsmFailure(fp, FAIL_ECHO_TIMEOUT); break; } /* fall through */ case 1: /* one interval of silence elapsed; send first echo request */ FsmSendEchoReq(fp, NULL); /* fall through */ case 0: TimerStart(&fp->echoTimer); break; } } /* * FsmInput() * * Deal with an incoming packet for FSM pointed to by "fp" */ void FsmInput(Fsm fp, Mbuf bp, int linkNum) { int log, recd_len, length; struct fsmheader hdr; /* Check for runt frames; discard them */ if ((recd_len = plength(bp)) < sizeof(hdr)) { Log(fp->log, ("%s: runt packet: %d bytes", Pref(fp), recd_len)); PFREE(bp); return; } /* Read in the header */ bp = mbread(bp, (u_char *) &hdr, sizeof(hdr), NULL); length = ntohs(hdr.length); /* Make sure length is sensible; discard otherwise */ if (length < sizeof(hdr) || length > recd_len) { Log(fp->log, ("%s: bad length: says %d, rec'd %d", Pref(fp), length, recd_len)); PFREE(bp); return; } /* Truncate off any padding bytes */ if (length < recd_len) bp = mbtrunc(bp, length - sizeof(hdr)); /* Check for a valid code byte -- if not, send code-reject */ if (!((1 << hdr.code) & fp->type->known_codes)) { /* RUC */ Log(fp->log, ("%s: unknown code %d", Pref(fp), hdr.code)); bp = mbunify(bp); FsmOutput(fp, CODE_CODEREJ, fp->rejid++, MBDATA(bp), MBLEN(bp)); PFREE(bp); return; } /* Log it */ if (hdr.code == CODE_ECHOREQ || hdr.code == CODE_ECHOREP) log = LG_ECHO; else if (hdr.code == CODE_RESETREQ || hdr.code == CODE_RESETACK) log = fp->type->log2; else log = fp->log; Log(log, ("%s: rec'd %s #%d link %d (%s)", Pref(fp), FsmCodeName(hdr.code), (int) hdr.id, (int16_t)linkNum, FsmStateName(fp->state))); /* Do whatever */ (*FsmCodes[hdr.code].action)(fp, &hdr, bp); } /* * FsmConfValue() * * Write in a configuration item with "len" bytes of information. * Special cases: length -2 or -4 means convert to network byte order. */ u_char * FsmConfValue(u_char *cp, int type, int len, const void *data) { u_char *bytes = (u_char *) data; u_short sv; u_long lv; /* Special cases */ switch (len) { case -2: len = 2; sv = htons(*((u_short *) data)); bytes = (u_char *) &sv; break; case -4: len = 4; lv = htonl(*((u_long *) data)); bytes = (u_char *) &lv; break; default: break; } /* Add header and data */ *cp++ = type; *cp++ = len + 2; while (len-- > 0) *cp++ = *bytes++; /* Done */ return(cp); } /* * FsmExtractOptions() * * Extract options from a config packet */ static int FsmExtractOptions(Fsm fp, u_char *data, int dlen, FsmOption opts, int max) { int num; for (num = 0; dlen >= 2 && num < max; num++) { FsmOption const opt = &opts[num]; opt->type = data[0]; opt->len = data[1]; opt->data = &data[2]; if (opt->len < 2 || opt->len > dlen) break; data += opt->len; dlen -= opt->len; } if (dlen != 0) LogDumpBuf(LG_ERR, data, dlen, "%s: %d extra garbage bytes in config packet", Pref(fp), dlen); return(num); } /* * FsmFindOption() */ FsmOptInfo FsmFindOptInfo(FsmOptInfo list, u_char type) { for (; list->name; list++) if (list->type == type) return(list); return(NULL); } /* * FsmDecodeBuffer() */ static void FsmDecodeBuffer(Fsm fp, u_char *buf, int size, int mode) { struct fsmoption opts[FSM_MAX_OPTS]; int num; if (mode == MODE_REQ) gAckSize = gNakSize = gRejSize = 0; num = FsmExtractOptions(fp, buf, size, opts, FSM_MAX_OPTS); (*fp->type->DecodeConfig)(fp, opts, num, mode); } /* * FsmAck() */ void FsmAck(Fsm fp, const struct fsmoption *opt) { if (gAckSize + opt->len > sizeof(gAckBuf)) { Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "ack")); return; } memcpy(&gAckBuf[gAckSize], opt, 2); memcpy(&gAckBuf[gAckSize + 2], opt->data, opt->len - 2); gAckSize += opt->len; } /* * FsmNak() */ void FsmNak(Fsm fp, const struct fsmoption *opt) { if (gNakSize + opt->len > sizeof(gNakBuf)) { Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "nak")); return; } memcpy(&gNakBuf[gNakSize], opt, 2); memcpy(&gNakBuf[gNakSize + 2], opt->data, opt->len - 2); gNakSize += opt->len; } /* * FsmRej() */ void FsmRej(Fsm fp, const struct fsmoption *opt) { if (gRejSize + opt->len > sizeof(gRejBuf)) { Log(LG_ERR, ("%s: %s buffer full", Pref(fp), "rej")); return; } memcpy(&gRejBuf[gRejSize], opt, 2); memcpy(&gRejBuf[gRejSize + 2], opt->data, opt->len - 2); gRejSize += opt->len; } /* * Pref() * * Cook up a little prefix for printing stuff to the log. * This is statically allocated so must be printed or * whatever immediately. */ char * Pref(Fsm fp) { static char buf[100]; snprintf(buf, sizeof(buf), "[%s] %s", fp->type->link_layer ? lnk->name : bund->name, fp->type->name); return(buf); } /* * FsmCodeName() * * This is statically allocated so must be printed or * whatever immediately. */ const char * FsmCodeName(int code) { static char buf[20]; if (code >= 0 && code < NUM_FSM_CODES) return(FsmCodes[code].name); snprintf(buf, sizeof(buf), "%d", code); return(buf); } /* * FsmStateName() */ const char * FsmStateName(int state) { switch (state) { case ST_INITIAL: return "Initial"; case ST_STARTING: return "Starting"; case ST_CLOSED: return "Closed"; case ST_STOPPED: return "Stopped"; case ST_CLOSING: return "Closing"; case ST_STOPPING: return "Stopping"; case ST_REQSENT: return "Req-Sent"; case ST_ACKRCVD: return "Ack-Rcvd"; case ST_ACKSENT: return "Ack-Sent"; case ST_OPENED: return "Opened"; } return "???"; }