/*
* Copyright (c) 2001-2002 Packet Design, LLC.
* All rights reserved.
*
* Subject to the following obligations and disclaimer of warranty,
* use and redistribution of this software, in source or object code
* forms, with or without modifications are expressly permitted by
* Packet Design; provided, however, that:
*
* (i) Any and all reproductions of the source or object code
* must include the copyright notice above and the following
* disclaimer of warranties; and
* (ii) No rights are granted, in any manner or form, to use
* Packet Design trademarks, including the mark "PACKET DESIGN"
* on advertising, endorsements, or otherwise except as such
* appears in the above copyright notice or in the software.
*
* THIS SOFTWARE IS BEING PROVIDED BY PACKET DESIGN "AS IS", AND
* TO THE MAXIMUM EXTENT PERMITTED BY LAW, PACKET DESIGN MAKES NO
* REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, REGARDING
* THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY AND ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
* OR NON-INFRINGEMENT. PACKET DESIGN DOES NOT WARRANT, GUARANTEE,
* OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF, OR THE RESULTS
* OF THE USE OF THIS SOFTWARE IN TERMS OF ITS CORRECTNESS, ACCURACY,
* RELIABILITY OR OTHERWISE. IN NO EVENT SHALL PACKET DESIGN BE
* LIABLE FOR ANY DAMAGES RESULTING FROM OR ARISING OUT OF ANY USE
* OF THIS SOFTWARE, INCLUDING WITHOUT LIMITATION, ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE, OR CONSEQUENTIAL
* DAMAGES, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF
* USE, DATA OR PROFITS, HOWEVER CAUSED AND UNDER 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 PACKET DESIGN IS ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Archie Cobbs <archie@freebsd.org>
*/
#include "ppp/ppp_defs.h"
#include "ppp/ppp_log.h"
#include "ppp/ppp_util.h"
#include "ppp/ppp_fsm_option.h"
#include "ppp/ppp_fsm.h"
/* FSM defaults */
#define FSM_MAX_CONFIGURE 10
#define FSM_MAX_TERMINATE 3
#define FSM_MAX_FAILURE 8
#define FSM_TIMEOUT 2
/* Memory type */
#define FSM_MTYPE "ppp_fsm"
/* Max amount of packet data to copy & send back */
#define MAX_PKTCOPY 200
/* FSM events */
enum fsm_event {
UP =0,
DOWN =1,
OPEN =2,
CLOSE =3,
TO_P =4,
TO_M =5,
RCR_P =6,
RCR_M =7,
RCA =8,
RCN =9,
RTR =10,
RTA =11,
RXJ_P =12,
RXJ_M =13
};
#define FSM_EVENT_MAX 14
/* Actions to take on events */
#define TLU 0x0010 /* this layer up */
#define TLD 0x0020 /* this layer down */
#define TLS 0x0040 /* this layer started */
#define TLF 0x0080 /* this layer finished */
#define IRC 0x0100 /* init restart counter */
#define ZRC 0x0200 /* zero restart counter */
#define SCR 0x0400 /* send config request */
#define SCA 0x0800 /* send config ack */
#define SCN 0x1000 /* send config nak/rej */
#define STR 0x2000 /* send terminate request */
#define STA 0x4000 /* send terminate ack */
#define STMASK 0x000f /* next state mask */
#define NA 0xffff /* impossible event */
/* Configurations we keep */
#define FSM_CONF_SELF 0 /* my requested config */
#define FSM_CONF_PEER 1 /* peer requested config */
#define FSM_CONF_NAK 2 /* nak'd peer config */
#define FSM_CONF_REJ 3 /* rejected peer config */
#define FSM_CONF_MAX 4
/* Information describing an instance of an FSM */
struct ppp_fsm {
struct ppp_fsm_instance *inst; /* fsm instance object */
enum ppp_fsm_state state; /* fsm state */
struct ppp_log *log; /* log object */
struct ppp_fsm_options *config[FSM_CONF_MAX]; /* config options */
u_char ids[FSM_CODE_MAX]; /* packet ids */
u_char rejcode[FSM_CODE_MAX]; /* rejected codes */
short restart; /* restart counter */
short failure[2]; /* failure counter */
time_t last_heard; /* time last heard from */
struct ppp_fsm_output dead; /* if dead and reason why */
struct pevent_ctx *ev_ctx; /* event context */
pthread_mutex_t *mutex; /* mutex */
struct pevent *timer; /* restart timer */
struct mesg_port *outport; /* where output goes */
};
#define FSM_TIMER_STATE(state) \
((state) >= FSM_STATE_CLOSING && (state) != FSM_STATE_OPENED)
#define FSM_DEAD(fsm) \
(((fsm)->state == FSM_STATE_INITIAL \
|| (fsm)->state == FSM_STATE_CLOSED) \
&& (fsm)->dead.u.down.reason != 0)
/* Macro for logging */
#define LOG(sev, fmt, args...) PPP_LOG(fsm->log, sev, fmt , ## args)
/*
* RFC 1661 PPP state transition table
*
* Differences from RFC 1661:
* TLS added in [OPEN, CLOSED]
* TLF added in [DOWN, CLOSING]
* TLS added in [RCR+, STOPPED]
* TLS added in [RCR-, STOPPED]
* RUC and RXR events removed (handled directly)
*
* The extra TLS/TLF actions are to keep intention of lower layer in sync
* with the intention of this layer (i.e., if TLS -> lower layer OPEN and
* TLF -> lower layer CLOSE).
*/
static const u_int16_t fsm_actions[FSM_EVENT_MAX][FSM_STATE_MAX] = {
/*INITL STARTNG CLOSED STOPPED CLOSING STOPPNG REQ-SNT ACK-RCD ACK-SNT OPENED*/
/* Up */
{ 2, IRC|SCR|6,
NA, NA, NA, NA, NA, NA, NA, NA },
/* Down */
{ NA, NA, 0, TLS|1, TLF|0, 1, 1, 1, 1, TLD|1 },
/* Open */
{ TLS|1,1, TLS|IRC|SCR|6,
3, 5, 5, 6, 7, 8, 9 },
/* Close */
{ 0, TLF|0, 2, 2, 4, 4, IRC|STR|4,
IRC|STR|4,
IRC|STR|4,
TLD|IRC|STR|4},
/* TO+ */
{ NA, NA, NA, NA, STR|4, STR|5, SCR|6, SCR|6, SCR|8, NA },
/* TO- */
{ NA, NA, NA, NA, TLF|2, TLF|3, TLF|3, TLF|3, TLF|3, NA },
/* RCR+ */
{ NA, NA, STA|2, TLS|IRC|SCR|SCA|8,
4, 5, SCA|8, SCA|TLU|9,
SCA|8,
TLD|SCR|SCA|8},
/* RCR- */
{ NA, NA, STA|2, TLS|IRC|SCR|SCN|6,
4, 5, SCN|6, SCN|7, SCN|6,
TLD|SCR|SCN|6},
/* RCA */
{ NA, NA, STA|2, STA|3, 4, 5, IRC|7,
SCR|6, IRC|TLU|9,
TLD|SCR|6 },
/* RCN */
{ NA, NA, STA|2, STA|3, 4, 5, IRC|SCR|6,
SCR|6, IRC|SCR|8,
TLD|SCR|6 },
/* RTR */
{ NA, NA, STA|2, STA|3, STA|4, STA|5, STA|6, STA|6, STA|6,
TLD|ZRC|STA|5},
/* RTA */
{ NA, NA, 2, 3, TLF|2, TLF|3, 6, 6, 8,
TLD|SCR|6 },
/* RXJ+ */
{ NA, NA, 2, 3, 4, 5, 6, 6, 8, 9 },
/* RXJ- */
{ NA, NA, TLF|2, TLF|3, TLF|2, TLF|3, TLF|3, TLF|3, TLF|3,
TLD|IRC|STR|5},
};
/* Minimum packet data lengths */
static const u_char fsm_minlen[FSM_CODE_MAX] = {
0, /* FSM_CODE_VENDOR */
0, /* FSM_CODE_CONFIGREQ */
0, /* FSM_CODE_CONFIGACK */
0, /* FSM_CODE_CONFIGNAK */
0, /* FSM_CODE_CONFIGREJ */
0, /* FSM_CODE_TERMREQ */
0, /* FSM_CODE_TERMACK */
1, /* FSM_CODE_CODEREJ */
2, /* FSM_CODE_PROTOREJ */
4, /* FSM_CODE_ECHOREQ */
4, /* FSM_CODE_ECHOREP */
4, /* FSM_CODE_DISCREQ */
4, /* FSM_CODE_IDENT */
8, /* FSM_CODE_TIMEREM */
0, /* FSM_CODE_RESETREQ */
0, /* FSM_CODE_RESETACK */
};
/*
* Internal functions
*/
static void ppp_fsm_input_packet(struct ppp_fsm *fsm,
const u_char *data, u_int dlen);
static void ppp_fsm_event(struct ppp_fsm *fsm, int event);
static void ppp_fsm_send_config(struct ppp_fsm *fsm,
enum ppp_fsm_code code);
static void ppp_fsm_send_packet(struct ppp_fsm *fsm, u_char code,
const void *data, u_int len);
static void ppp_fsm_record(struct ppp_fsm *fsm,
/* enum ppp_fsm_reason reason, */ ...);
static int ppp_fsm_output(struct ppp_fsm *fsm,
enum ppp_fsmoutput type, ...);
static void ppp_fsm_output_build(struct ppp_fsm_output *output,
enum ppp_fsmoutput type, va_list args);
static void ppp_fsm_output_dead(struct ppp_fsm *fsm);
static void ppp_fsm_syserr(struct ppp_fsm *fsm, const char *func);
static void ppp_fsm_log_pkt(struct ppp_fsm *fsm, int sev,
const char *prefix, const u_char *pkt);
static pevent_handler_t ppp_fsm_timeout;
static const char *st2str(u_int state);
static const char *ev2str(u_int event);
static const char *cd2str(u_int code);
/***********************************************************************
PUBLIC API FUNCTIONS
***********************************************************************/
/*
* Create a new FSM.
*
* "inst" is freed when the FSM is destroyed.
*/
struct ppp_fsm *
ppp_fsm_create(struct pevent_ctx *ev_ctx, pthread_mutex_t *mutex,
struct ppp_fsm_instance *inst, struct ppp_log *log)
{
struct ppp_fsm *fsm;
int i;
/* Get new FSM object */
if ((fsm = MALLOC(FSM_MTYPE, sizeof(*fsm))) == NULL)
return (NULL);
memset(fsm, 0, sizeof(*fsm));
fsm->ev_ctx = ev_ctx;
fsm->mutex = mutex;
fsm->inst = inst;
fsm->state = FSM_STATE_INITIAL;
/* Prefix log with FSM name */
if ((fsm->log = ppp_log_prefix(log,
"%s: ", inst->type->name)) == NULL) {
FREE(FSM_MTYPE, fsm);
return (NULL);
}
/* Get message port */
if ((fsm->outport = mesg_port_create(inst->type->name)) == NULL) {
FREE(FSM_MTYPE, fsm);
return (NULL);
}
/* For 'shell' FSM's like MP LCP, no config required */
if ((inst->type->sup_codes & (1 << FSM_CODE_CONFIGREQ)) == 0) {
fsm->state = FSM_STATE_OPENED;
return (fsm);
}
/* Initialize configuration options */
for (i = 0; i < FSM_CONF_MAX; i++) {
if ((fsm->config[i] = ppp_fsm_option_create()) == NULL) {
while (i-- > 0)
ppp_fsm_option_destroy(&fsm->config[i]);
mesg_port_destroy(&fsm->outport);
FREE(FSM_MTYPE, fsm);
return (NULL);
}
}
/* Done */
inst->fsm = fsm;
return (fsm);
}
/*
* Destroy an FSM
*/
void
ppp_fsm_destroy(struct ppp_fsm **fsmp)
{
struct ppp_fsm *const fsm = *fsmp;
struct ppp_fsm_output *output;
int i;
if (fsm == NULL)
return;
*fsmp = NULL;
pevent_unregister(&fsm->timer);
for (i = 0; i < FSM_CONF_MAX; i++)
ppp_fsm_option_destroy(&fsm->config[i]);
while ((output = mesg_port_get(fsm->outport, 0)) != NULL)
ppp_fsm_free_output(output);
mesg_port_destroy(&fsm->outport);
(*fsm->inst->type->destroy)(fsm->inst);
ppp_log_close(&fsm->log);
FREE(FSM_MTYPE, fsm);
}
/*
* Get output port.
*/
struct mesg_port *
ppp_fsm_get_outport(struct ppp_fsm *fsm)
{
return (fsm->outport);
}
/*
* Free an FSM output structure.
*/
void
ppp_fsm_free_output(struct ppp_fsm_output *output)
{
switch (output->type) {
case FSM_OUTPUT_DATA:
FREE(FSM_MTYPE, output->u.data.data);
break;
default:
break;
}
FREE(FSM_MTYPE, output);
}
/*
* Input something to the FSM.
*/
void
ppp_fsm_input(struct ppp_fsm *fsm, enum ppp_fsm_input input, ...)
{
const u_char *data;
va_list args;
u_int dlen;
/* If we're dead, ignore it */
if (FSM_DEAD(fsm))
return;
/* Handle input */
va_start(args, input);
switch (input) {
case FSM_INPUT_OPEN:
ppp_fsm_event(fsm, OPEN);
break;
case FSM_INPUT_CLOSE:
ppp_fsm_record(fsm, FSM_REASON_CLOSE);
ppp_fsm_event(fsm, CLOSE);
break;
case FSM_INPUT_UP:
ppp_fsm_event(fsm, UP);
break;
case FSM_INPUT_DOWN_FATAL:
ppp_fsm_record(fsm, FSM_REASON_DOWN_FATAL);
ppp_fsm_event(fsm, DOWN);
ppp_fsm_event(fsm, CLOSE);
break;
case FSM_INPUT_DOWN_NONFATAL:
ppp_fsm_record(fsm, FSM_REASON_DOWN_NONFATAL);
ppp_fsm_event(fsm, DOWN);
break;
case FSM_INPUT_RECD_PROTOREJ:
{
u_int16_t proto;
proto = va_arg(args, int);
ppp_fsm_record(fsm, FSM_REASON_PROTOREJ, proto);
ppp_fsm_event(fsm, RXJ_M);
break;
}
case FSM_INPUT_DATA:
data = va_arg(args, const u_char *);
dlen = va_arg(args, u_int);
ppp_fsm_input_packet(fsm, data, dlen);
break;
case FSM_INPUT_XMIT_PROTOREJ:
{
u_int16_t proto;
u_int16_t *prj;
u_int prlen;
proto = va_arg(args, int);
data = va_arg(args, const u_char *);
dlen = va_arg(args, u_int);
prlen = 2 + MIN(dlen, MAX_PKTCOPY);
if ((prj = MALLOC(TYPED_MEM_TEMP, prlen)) == NULL)
break;
*prj = htons(proto);
memcpy((char *)prj + 2, data, MIN(dlen, MAX_PKTCOPY));
ppp_fsm_send_packet(fsm, FSM_CODE_PROTOREJ, prj, prlen);
FREE(TYPED_MEM_TEMP, prj);
break;
}
default:
LOG(LOG_ERR, "invalid input %d", input);
break;
}
va_end(args);
}
/*
* Get FSM state.
*/
enum ppp_fsm_state
ppp_fsm_get_state(struct ppp_fsm *fsm)
{
return (fsm->state);
}
/*
* Get time we last heard from the peer.
*/
time_t
ppp_fsm_last_heard(struct ppp_fsm *fsm)
{
return (fsm->last_heard);
}
/*
* Get underlying FSM instance.
*/
struct ppp_fsm_instance *
ppp_fsm_get_instance(struct ppp_fsm *fsm)
{
return (fsm->inst);
}
/*
* Send a reset-request.
*/
void
ppp_fsm_send_reset_req(struct ppp_fsm *fsm, const void *data, size_t dlen)
{
fsm->ids[FSM_CODE_RESETREQ]++;
ppp_fsm_send_packet(fsm, FSM_CODE_RESETREQ, data, dlen);
}
/*
* Send a reset-ack.
*/
void
ppp_fsm_send_reset_ack(struct ppp_fsm *fsm, const void *data, size_t dlen)
{
ppp_fsm_send_packet(fsm, FSM_CODE_RESETACK, data, dlen);
}
/***********************************************************************
INTERNAL FUNCTIONS
***********************************************************************/
/*
* Handle an incoming packet
*/
static void
ppp_fsm_input_packet(struct ppp_fsm *fsm, const u_char *pkt, u_int dlen)
{
const u_char *const payload = pkt + sizeof(struct ppp_fsm_pkt);
const struct ppp_fsm_type *const ftyp = fsm->inst->type;
struct ppp_fsm_options *opts = NULL;
struct ppp_fsm_pkt hdr;
u_int len;
/* Drop packet if it's unexpected */
if (fsm->state == FSM_STATE_INITIAL
|| fsm->state == FSM_STATE_STARTING)
goto done;
/* Check packet length */
if (dlen < sizeof(hdr)) {
LOG(LOG_NOTICE, "rec'd %s packet (%u bytes)", "runt", dlen);
goto done;
}
/* Copy packet header into aligned memory */
memcpy(&hdr, pkt, sizeof(hdr));
/* Check packet length again */
if ((len = ntohs(hdr.length)) < sizeof(hdr)) {
LOG(LOG_NOTICE, "rec'd %s packet (%u bytes)", "runt", dlen);
goto done;
}
if (len > dlen) {
LOG(LOG_NOTICE, "rec'd %s packet (%u bytes)",
"truncated", dlen);
goto done;
}
len -= sizeof(hdr); /* get length of just the data part */
/* Check code; send code-reject if not supported */
if (hdr.code >= FSM_CODE_MAX
|| (ftyp->sup_codes & (1 << hdr.code)) == 0) {
code_reject: LOG(LOG_DEBUG, "rejecting unsupported code %u", hdr.code);
ppp_fsm_send_packet(fsm, FSM_CODE_CODEREJ,
pkt, MIN(dlen, MAX_PKTCOPY));
goto done;
}
/* Check data length */
if (len < fsm_minlen[hdr.code]) {
LOG(LOG_DEBUG + 1, "ignoring truncated packet");
goto done;
}
/* Reset peer's idle time */
fsm->last_heard = time(NULL);
/* Initialize failure counters if appropriate */
if (fsm->state < FSM_STATE_REQSENT) {
fsm->failure[PPP_SELF] = FSM_MAX_FAILURE;
fsm->failure[PPP_PEER] = FSM_MAX_FAILURE;
}
/* Logging */
ppp_fsm_log_pkt(fsm, (hdr.code == FSM_CODE_ECHOREQ
|| hdr.code == FSM_CODE_ECHOREP) ? LOG_DEBUG : LOG_INFO,
"recv", pkt);
/* Extract encoded config options */
switch (hdr.code) {
case FSM_CODE_CONFIGREQ:
case FSM_CODE_CONFIGACK:
case FSM_CODE_CONFIGNAK:
case FSM_CODE_CONFIGREJ:
ppp_fsm_record(fsm, FSM_REASON_CONF, hdr.code);
if ((opts = ppp_fsm_option_unpack(payload, len)) == NULL) {
ppp_fsm_syserr(fsm, "malloc");
goto close;
}
break;
default:
break;
}
/* Check magic number */
switch (hdr.code) {
case FSM_CODE_ECHOREQ:
case FSM_CODE_ECHOREP:
case FSM_CODE_IDENT:
case FSM_CODE_DISCREQ:
case FSM_CODE_TIMEREM:
{
u_int32_t pkt_magic;
u_int32_t req_magic;
/* Get what peer's magic number ought to be */
if (ftyp->get_magic == NULL)
break;
req_magic = (*ftyp->get_magic)(fsm->inst, PPP_PEER);
/* Get actual magic number received */
memcpy(&pkt_magic, payload, 4);
pkt_magic = ntohl(pkt_magic);
/*
* Only check if both magic numbers are non-zero and
* the FSM has reached the opened state.
*/
if (req_magic == 0
|| pkt_magic == 0
|| fsm->state != FSM_STATE_OPENED)
break;
/* If wrong, bail out */
if (pkt_magic != req_magic) {
LOG(LOG_NOTICE,
"rec'd %s with invalid magic# 0x%08x != 0x%08x",
cd2str(hdr.code), pkt_magic, req_magic);
ppp_fsm_record(fsm, FSM_REASON_BADMAGIC);
goto close;
}
break;
}
default:
break;
}
/* Deal with packet */
switch (hdr.code) {
case FSM_CODE_VENDOR:
if (ftyp->recv_vendor == NULL)
goto code_reject;
(*ftyp->recv_vendor)(fsm->inst, payload, len);
break;
case FSM_CODE_CONFIGREQ:
{
int ack;
int i;
/* Update reply id's */
fsm->ids[FSM_CODE_CONFIGACK] = hdr.id;
fsm->ids[FSM_CODE_CONFIGNAK] = hdr.id;
fsm->ids[FSM_CODE_CONFIGREJ] = hdr.id;
/* Update peer's requested options with new info */
ppp_fsm_option_destroy(&fsm->config[FSM_CONF_PEER]);
fsm->config[FSM_CONF_PEER] = opts;
opts = NULL; /* avoid double free */
/* Reset nak and rej reply options */
ppp_fsm_option_zero(fsm->config[FSM_CONF_NAK]);
ppp_fsm_option_zero(fsm->config[FSM_CONF_REJ]);
/* Examine peer's options for basic validity */
for (i = 0; i < fsm->config[FSM_CONF_PEER]->num; i++) {
const struct ppp_fsm_option *const opt
= &fsm->config[FSM_CONF_PEER]->opts[i];
const struct ppp_fsm_optdesc *const desc
= ppp_fsm_option_desc(ftyp->options, opt);
/* If not supported or invalid, reject it */
if (desc == NULL
|| !desc->supported
|| opt->len < desc->min
|| opt->len > desc->max) {
/* Add to reject list */
if (ppp_fsm_option_add(
fsm->config[FSM_CONF_REJ],
opt->type, opt->len, opt->data) == -1) {
ppp_fsm_syserr(fsm, "malloc");
goto close;
}
/* Remove from request list */
ppp_fsm_option_del(
fsm->config[FSM_CONF_PEER], i--);
}
}
/* Call FSM type method to deal with remaining options */
if ((*ftyp->recv_conf_req)(fsm->inst,
fsm->config[FSM_CONF_PEER],
fsm->config[FSM_CONF_NAK],
fsm->config[FSM_CONF_REJ]) == -1)
goto config_error;
/* Check if not converging */
if (fsm->config[FSM_CONF_NAK]->num > 0
&& --fsm->failure[PPP_PEER] <= 0) {
LOG(LOG_NOTICE, "negotiation failed to converge:"
" configuration not accepted by %s", "me");
ppp_fsm_record(fsm, FSM_REASON_NEGOT);
goto close;
}
/* Evoke RCR+ or RCR- event */
ack = (fsm->config[FSM_CONF_NAK]->num
+ fsm->config[FSM_CONF_REJ]->num == 0);
if (ack)
fsm->failure[PPP_PEER] = FSM_MAX_FAILURE;
ppp_fsm_event(fsm, ack ? RCR_P : RCR_M);
break;
}
case FSM_CODE_CONFIGACK:
/* Validate id and contents */
if (hdr.id != fsm->ids[FSM_CODE_CONFIGREQ]) {
LOG(LOG_DEBUG + 1, "ignoring id #%u", hdr.id);
goto done;
}
if (!ppp_fsm_option_equal(opts, -1,
fsm->config[FSM_CONF_SELF], -1)) {
LOG(LOG_DEBUG + 1, "ignoring altered contents");
goto done;
}
/* Generate RCA event */
fsm->ids[FSM_CODE_CONFIGREQ]++;
fsm->failure[PPP_SELF] = FSM_MAX_FAILURE;
ppp_fsm_event(fsm, RCA);
break;
case FSM_CODE_CONFIGNAK:
case FSM_CODE_CONFIGREJ:
{
int (*func)(struct ppp_fsm_instance *inst,
struct ppp_fsm_options *rej);
/* Validate id */
if (hdr.id != fsm->ids[FSM_CODE_CONFIGREQ]) {
LOG(LOG_DEBUG + 1, "ignoring id #%u", hdr.id);
goto done;
}
/* Check if not converging */
if (hdr.code == FSM_CODE_CONFIGNAK
&& --fsm->failure[PPP_SELF] <= 0) {
LOG(LOG_NOTICE, "negotiation failed to converge:"
" configuration not accepted by %s", "peer");
ppp_fsm_record(fsm, FSM_REASON_NEGOT);
goto close;
}
/* Call implementation to deal with options */
func = (hdr.code == FSM_CODE_CONFIGNAK) ?
ftyp->recv_conf_nak : ftyp->recv_conf_rej;
if ((*func)(fsm->inst, opts) == -1) {
config_error: if (errno == ELOOP)
ppp_fsm_record(fsm, FSM_REASON_LOOPBACK);
else if (errno == EINVAL)
ppp_fsm_record(fsm, FSM_REASON_NEGOT);
else
ppp_fsm_record(fsm, FSM_REASON_SYSERR, errno);
goto close;
}
/* Generate RCN event */
fsm->ids[FSM_CODE_CONFIGREQ]++;
ppp_fsm_event(fsm, RCN);
break;
}
case FSM_CODE_TERMREQ:
/* Generate RTR event */
fsm->ids[FSM_CODE_TERMACK] = hdr.id;
ppp_fsm_record(fsm, FSM_REASON_TERM);
ppp_fsm_event(fsm, RTR);
goto close;
case FSM_CODE_TERMACK:
/* Validate id */
if (hdr.id != fsm->ids[FSM_CODE_TERMREQ]) {
LOG(LOG_DEBUG + 1, "ignoring id #%u", hdr.id);
goto done;
}
fsm->ids[FSM_CODE_TERMREQ]++;
/* Generate RTA event */
fsm->ids[FSM_CODE_TERMACK] = hdr.id;
ppp_fsm_event(fsm, RTA);
break;
case FSM_CODE_CODEREJ:
{
const u_char code = payload[0];
/* See if rejected code is required */
if (code >= FSM_CODE_MAX
|| (ftyp->req_codes & (1 << code)) == 0) {
fsm->rejcode[code] = 1;
ppp_fsm_event(fsm, RXJ_P);
} else {
ppp_fsm_record(fsm, FSM_REASON_CODEREJ, code);
ppp_fsm_event(fsm, RXJ_M);
goto close;
}
break;
}
case FSM_CODE_PROTOREJ:
{
u_int16_t proto;
/* Get rejected protocol */
memcpy(&proto, payload, 2);
proto = ntohs(proto);
if (proto == ftyp->proto) {
ppp_fsm_record(fsm, FSM_REASON_PROTOREJ, proto);
ppp_fsm_event(fsm, RXJ_M);
goto close;
}
ppp_fsm_output(fsm, FSM_OUTPUT_PROTOREJ, proto);
ppp_fsm_event(fsm, RXJ_P); /* assume RXJ+ until hear o/w */
break;
}
case FSM_CODE_ECHOREQ:
{
u_char buf[MAX_PKTCOPY];
u_int32_t magic;
/* Update reply id */
fsm->ids[FSM_CODE_ECHOREP] = hdr.id;
/* Only reply when in opened state (except MP LCP) */
if (fsm->state != FSM_STATE_OPENED) {
LOG(LOG_DEBUG + 1, "ignoring: not in %s yet",
st2str(FSM_STATE_OPENED));
goto done;
}
/* Insert my magic number and reply */
memcpy(buf, payload, MIN(len, sizeof(buf)));
magic = (ftyp->get_magic != NULL) ?
(*ftyp->get_magic)(fsm->inst, PPP_SELF) : 0;
magic = htonl(magic);
memcpy(buf, &magic, 4);
ppp_fsm_send_packet(fsm, FSM_CODE_ECHOREP,
buf, MIN(len, sizeof(buf)));
break;
}
case FSM_CODE_RESETREQ:
if (ftyp->recv_reset_req == NULL)
goto code_reject;
fsm->ids[FSM_CODE_RESETACK] = hdr.id;
(*ftyp->recv_reset_req)(fsm->inst, payload, len);
break;
case FSM_CODE_RESETACK:
if (ftyp->recv_reset_ack == NULL)
goto code_reject;
if (hdr.id != fsm->ids[FSM_CODE_RESETREQ])
break;
(*ftyp->recv_reset_ack)(fsm->inst, payload, len);
break;
case FSM_CODE_ECHOREP: /* ignore these */
case FSM_CODE_IDENT:
case FSM_CODE_DISCREQ:
case FSM_CODE_TIMEREM:
break;
default: /* already handled above */
break;
}
/* Done */
goto done;
close:
/* Handle failure by closing up shop */
ppp_fsm_event(fsm, CLOSE);
done:
/* Clean up */
ppp_fsm_option_destroy(&opts);
}
/*
* Handle an event
*/
static void
ppp_fsm_event(struct ppp_fsm *fsm, int event)
{
const int ostate = fsm->state;
const int action = fsm_actions[event][fsm->state];
/* Debugging */
if (action == NA) {
LOG(LOG_ERR, "%s event %s in state %s",
"invalid", ev2str(event), st2str(fsm->state));
return;
}
LOG(LOG_DEBUG, "event %s in state %s",
ev2str(event), st2str(fsm->state));
/* Perform actions */
if ((action & TLU) != 0)
ppp_fsm_output(fsm, FSM_OUTPUT_UP);
if ((action & TLD) != 0)
ppp_fsm_output(fsm, FSM_OUTPUT_DOWN, fsm->dead.u.down.reason);
if ((action & TLS) != 0)
ppp_fsm_output(fsm, FSM_OUTPUT_OPEN);
if ((action & TLF) != 0)
ppp_fsm_output(fsm, FSM_OUTPUT_CLOSE);
if ((action & IRC) != 0) {
fsm->restart = (event == CLOSE || event == RXJ_M) ?
FSM_MAX_TERMINATE : FSM_MAX_CONFIGURE;
}
if ((action & SCR) != 0)
ppp_fsm_send_config(fsm, FSM_CODE_CONFIGREQ);
if ((action & SCA) != 0)
ppp_fsm_send_config(fsm, FSM_CODE_CONFIGACK);
if ((action & SCN) != 0) {
if (fsm->config[FSM_CONF_REJ]->num != 0)
ppp_fsm_send_config(fsm, FSM_CODE_CONFIGREJ);
if (fsm->config[FSM_CONF_NAK]->num != 0)
ppp_fsm_send_config(fsm, FSM_CODE_CONFIGNAK);
}
if ((action & STR) != 0)
ppp_fsm_send_packet(fsm, FSM_CODE_TERMREQ, NULL, 0);
if ((action & STA) != 0)
ppp_fsm_send_packet(fsm, FSM_CODE_TERMACK, NULL, 0);
/* Transition to new state */
fsm->state = (action & STMASK);
if (fsm->state != ostate)
LOG(LOG_DEBUG, "%s -> %s", st2str(ostate), st2str(fsm->state));
/* Initialize failure counter if appropriate */
if (ostate < FSM_STATE_REQSENT && fsm->state >= FSM_STATE_REQSENT) {
fsm->failure[PPP_SELF] = FSM_MAX_FAILURE;
fsm->failure[PPP_PEER] = FSM_MAX_FAILURE;
}
/* Stop the restart timer if it's not supposed to be running */
if (!FSM_TIMER_STATE(fsm->state)) {
pevent_unregister(&fsm->timer);
goto no_timer;
}
/* Check if timer is already running and doesn't need to be restarted */
if (FSM_TIMER_STATE(ostate) && (action & (SCR|STR)) == 0)
goto no_timer;
/* (Re)start restart timer */
pevent_unregister(&fsm->timer);
if (pevent_register(fsm->ev_ctx, &fsm->timer, 0, fsm->mutex,
ppp_fsm_timeout, fsm, PEVENT_TIME, FSM_TIMEOUT * 1000) == -1) {
ppp_fsm_syserr(fsm, "pevent_register");
return;
}
no_timer:
/* Emit 'dead' output if we're dead */
if (FSM_DEAD(fsm))
ppp_fsm_output_dead(fsm);
}
/*
* Send a config req, ack, nak, or rej packet
*/
static void
ppp_fsm_send_config(struct ppp_fsm *fsm, enum ppp_fsm_code code)
{
const struct ppp_fsm_type *const ftyp = fsm->inst->type;
struct ppp_fsm_options *opts;
u_char *buf;
u_int len;
/* Get the corresponding config option data */
switch (code) {
case FSM_CODE_CONFIGREQ:
{
u_int i;
u_int j;
/* Regenerate my requested config options */
opts = fsm->config[FSM_CONF_SELF];
ppp_fsm_option_zero(opts);
if ((*ftyp->build_conf_req)(fsm->inst, opts) == -1) {
if (errno == EINVAL)
ppp_fsm_record(fsm, FSM_REASON_NEGOT);
else
ppp_fsm_syserr(fsm, "build_conf_req");
return;
}
/* Elide default values from within config-request */
if (fsm->inst->type->defaults == NULL)
break;
for (i = 0; i < fsm->inst->type->defaults->num; i++) {
for (j = 0; j < opts->num; j++) {
if (ppp_fsm_option_equal(
fsm->inst->type->defaults, i, opts, j))
ppp_fsm_option_del(opts, j--);
}
}
break;
}
case FSM_CODE_CONFIGACK:
opts = fsm->config[FSM_CONF_PEER];
break;
case FSM_CODE_CONFIGNAK:
opts = fsm->config[FSM_CONF_NAK];
break;
case FSM_CODE_CONFIGREJ:
opts = fsm->config[FSM_CONF_REJ];
break;
default:
assert (0);
return;
}
/* Construct packet payload */
len = ppp_fsm_option_packlen(opts);
if ((buf = MALLOC(TYPED_MEM_TEMP, len)) == NULL) {
LOG(LOG_ERR, "%s: %m", "malloc");
ppp_fsm_syserr(fsm, "malloc");
return;
}
/* Send packet */
ppp_fsm_option_pack(opts, buf);
ppp_fsm_send_packet(fsm, code, buf, len);
FREE(TYPED_MEM_TEMP, buf);
}
/*
* Handler for an FSM timeout event.
*/
static void
ppp_fsm_timeout(void *arg)
{
struct ppp_fsm *const fsm = arg;
/* Cancel event */
pevent_unregister(&fsm->timer);
/* If we're dead, ignore it */
if (FSM_DEAD(fsm))
return;
/* Send timeout event */
if (--fsm->restart <= 0)
ppp_fsm_event(fsm, TO_M);
else
ppp_fsm_event(fsm, TO_P);
}
/*
* Send a packet.
*
* Handle any errors by shutting down the FSM.
*/
static void
ppp_fsm_send_packet(struct ppp_fsm *fsm,
u_char code, const void *data, u_int len)
{
struct ppp_fsm_pkt *pkt;
/* If peer rejected code, don't bother */
if (fsm->rejcode[code])
return;
/* Build packet */
if ((pkt = MALLOC(FSM_MTYPE, sizeof(*pkt) + len)) == NULL) {
ppp_fsm_syserr(fsm, "malloc");
return;
}
pkt->code = code;
pkt->id = fsm->ids[code];
pkt->length = htons(sizeof(*pkt) + len);
memcpy(pkt->data, data, len);
/* Logging */
ppp_fsm_log_pkt(fsm, (pkt->code == FSM_CODE_ECHOREQ
|| pkt->code == FSM_CODE_ECHOREP) ? LOG_DEBUG : LOG_INFO,
"xmit", (u_char *)pkt);
/* Send packet */
if (ppp_fsm_output(fsm, FSM_OUTPUT_DATA, pkt, sizeof(*pkt) + len) == -1)
FREE(FSM_MTYPE, pkt);
}
/*
* Record the reason for the FSM going down or dying.
*
* This information is saved until it can be output later.
*/
static void
ppp_fsm_record(struct ppp_fsm *fsm, ...)
{
struct ppp_fsm_output *const output = &fsm->dead;
va_list args;
/* Prioritize reason first on severity, then first come, first serve */
if (fsm->dead.u.down.reason == 0)
goto record;
switch (fsm->dead.u.down.reason) {
case FSM_REASON_CONF:
case FSM_REASON_DOWN_NONFATAL:
break;
default:
return;
}
record:
/* Build output message */
va_start(args, fsm);
ppp_fsm_output_build(output, FSM_OUTPUT_DEAD, args);
va_end(args);
}
/*
* Emit some sort of output from the FSM.
*
* Any errors are handled internally by shutting down the FSM.
*/
static int
ppp_fsm_output(struct ppp_fsm *fsm, enum ppp_fsmoutput type, ...)
{
struct ppp_fsm_output *output;
va_list args;
/* Allocate new output message */
if ((output = MALLOC(FSM_MTYPE, sizeof(*output))) == NULL) {
ppp_fsm_syserr(fsm, "malloc");
return (-1);
}
memset(output, 0, sizeof(*output));
/* Build output message */
va_start(args, type);
ppp_fsm_output_build(output, type, args);
va_end(args);
/* Send it */
if (mesg_port_put(fsm->outport, output) == -1) {
ppp_fsm_syserr(fsm, "mesg_port_put");
FREE(FSM_MTYPE, output);
return (-1);
}
/* Done */
return (0);
}
/*
* Output FSM_OUTPUT_DEAD message because we're dead.
*
* This is called when we reach the initial state after a fatal error.
* We copy the reason information previously recorded via ppp_fsm_record().
*/
static void
ppp_fsm_output_dead(struct ppp_fsm *fsm)
{
struct ppp_fsm_output *output;
/* Create new output message */
if ((output = MALLOC(FSM_MTYPE, sizeof(*output))) == NULL) {
ppp_fsm_syserr(fsm, "malloc");
return;
}
memset(output, 0, sizeof(*output));
/* Copy previously recorded output message, changing DOWN to DEAD */
*output = fsm->dead;
/* Send it */
if (mesg_port_put(fsm->outport, output) == -1) {
ppp_fsm_syserr(fsm, "mesg_port_put");
FREE(FSM_MTYPE, output);
}
}
/*
* Build an FSM output structure.
*/
static void
ppp_fsm_output_build(struct ppp_fsm_output *output,
enum ppp_fsmoutput type, va_list args)
{
output->type = type;
switch (type) {
case FSM_OUTPUT_OPEN:
case FSM_OUTPUT_CLOSE:
case FSM_OUTPUT_UP:
break;
case FSM_OUTPUT_DOWN:
case FSM_OUTPUT_DEAD:
output->u.down.reason = va_arg(args, int);
switch (output->u.down.reason) {
case FSM_REASON_SYSERR:
output->u.down.u.error = va_arg(args, int);
break;
case FSM_REASON_CONF:
case FSM_REASON_CODEREJ:
output->u.down.u.code = va_arg(args, int);
break;
case FSM_REASON_PROTOREJ:
output->u.down.u.proto = va_arg(args, int);
break;
default:
break;
}
break;
case FSM_OUTPUT_DATA:
output->u.data.data = va_arg(args, u_char *);
output->u.data.length = va_arg(args, u_int);
break;
case FSM_OUTPUT_PROTOREJ:
output->u.proto = va_arg(args, int);
break;
default:
assert(0);
}
}
/*
* Handle system error
*/
static void
ppp_fsm_syserr(struct ppp_fsm *fsm, const char *func)
{
LOG(LOG_ERR, "%s: %m", func);
ppp_fsm_record(fsm, FSM_REASON_SYSERR, errno);
}
/*
* Decode and log contents of an FSM packet
*/
static void
ppp_fsm_log_pkt(struct ppp_fsm *fsm, int sev,
const char *prefix, const u_char *pkt)
{
const u_char *const payload = pkt + sizeof(struct ppp_fsm_pkt);
struct ppp_fsm_pkt hdr;
char buf[512] = { '\0' };
u_int16_t dlen;
memcpy(&hdr, pkt, sizeof(hdr));
dlen = ntohs(hdr.length) - sizeof(hdr);
switch (hdr.code) {
case FSM_CODE_CONFIGREQ:
case FSM_CODE_CONFIGNAK:
case FSM_CODE_CONFIGREJ:
case FSM_CODE_CONFIGACK:
ppp_fsm_options_decode(fsm->inst->type->options,
payload, dlen, buf, sizeof(buf));
break;
case FSM_CODE_CODEREJ:
snprintf(buf, sizeof(buf), "code=%u", payload[0]);
break;
case FSM_CODE_PROTOREJ:
snprintf(buf, sizeof(buf),
"proto=0x%02x%02x", payload[0], payload[1]);
break;
case FSM_CODE_IDENT:
case FSM_CODE_TERMREQ:
case FSM_CODE_TERMACK:
if (dlen > 0) {
strlcpy(buf, "msg=\"", sizeof(buf));
ppp_util_ascify(buf + strlen(buf),
sizeof(buf) - strlen(buf), payload + 4, dlen - 4);
strlcat(buf, "\"", sizeof(buf));
}
break;
case FSM_CODE_TIMEREM:
{
u_int32_t remain;
char tbuf[32];
memcpy(&remain, payload + 4, 4);
remain = ntohl(remain);
if (remain == ~0)
strlcpy(tbuf, "unlimited", sizeof(tbuf));
else {
snprintf(tbuf, sizeof(tbuf),
"%lu seconds", (u_long)remain);
}
strlcpy(buf, "remaining=", sizeof(buf));
strlcat(buf, tbuf, sizeof(buf));
if (dlen > 4) {
strlcat(buf, " msg=\"", sizeof(buf));
ppp_util_ascify(buf + strlen(buf),
sizeof(buf) - strlen(buf), payload + 8, dlen - 8);
strlcat(buf, "\"", sizeof(buf));
}
break;
}
default:
break;
}
/* Finally, log it */
if (*buf == '\0')
LOG(sev, "%s %s #%u", prefix, cd2str(hdr.code), hdr.id);
else {
LOG(sev, "%s %s #%u: %s", prefix,
cd2str(hdr.code), hdr.id, buf);
}
}
static const char *
st2str(u_int state)
{
static const char *const snames[FSM_STATE_MAX] = {
"INITIAL", "STARTING", "CLOSED", "STOPPED", "CLOSING",
"STOPPING", "REQ-SENT", "ACK-RCVD", "ACK-SENT", "OPENED"
};
static char buf[16];
if (state >= sizeof(snames) / sizeof(*snames)) {
snprintf(buf, sizeof(buf), "?[%u]", state);
return (buf);
}
return (snames[state]);
}
static const char *
ev2str(u_int event)
{
static const char *const enames[FSM_EVENT_MAX] = {
"UP", "DOWN", "OPEN", "CLOSE", "TO+", "TO-", "RCR+",
"RCR-", "RCA", "RCN", "RTR", "RTA", "RXJ+", "RXJ-"
};
static char buf[16];
if (event >= sizeof(enames) / sizeof(*enames)) {
snprintf(buf, sizeof(buf), "?[%u]", event);
return (buf);
}
return (enames[event]);
}
static const char *
cd2str(u_int code)
{
static const char *const cnames[FSM_CODE_MAX] = {
"Vendor", "Conf-Req", "Conf-Ack", "Conf-Nak", "Conf-Rej",
"Term-Req", "Term-Ack", "Code-Rej", "Proto-Rej", "Echo-Req",
"Echo-Rsp", "Disc-Req", "Ident", "Time-Rem", "Reset-Req",
"Reset-Ack"
};
static char buf[16];
if (code >= sizeof(cnames) / sizeof(*cnames)) {
snprintf(buf, sizeof(buf), "?[%u]", code);
return (buf);
}
return (cnames[code]);
}
/***********************************************************************
PUBLIC DEBUGGING FUNCTIONS
***********************************************************************/
/*
* Return a string describing FSM output.
*/
const char *
ppp_fsm_output_str(struct ppp_fsm_output *output)
{
static char buf[256];
switch (output->type) {
case FSM_OUTPUT_OPEN:
snprintf(buf, sizeof(buf), "OPEN");
break;
case FSM_OUTPUT_CLOSE:
snprintf(buf, sizeof(buf), "CLOSE");
break;
case FSM_OUTPUT_UP:
snprintf(buf, sizeof(buf), "UP");
break;
case FSM_OUTPUT_DOWN:
snprintf(buf, sizeof(buf), "DOWN reason=%s",
ppp_fsm_reason_str(output));
break;
case FSM_OUTPUT_DATA:
snprintf(buf, sizeof(buf), "DATA type=%s",
cd2str(output->u.data.data[0]));
break;
case FSM_OUTPUT_PROTOREJ:
snprintf(buf, sizeof(buf), "PROTOREJ proto=0x%04x",
output->u.proto);
break;
case FSM_OUTPUT_DEAD:
snprintf(buf, sizeof(buf), "DEAD reason=%s",
ppp_fsm_reason_str(output));
break;
default:
snprintf(buf, sizeof(buf), "?[%u]?", output->type);
break;
}
return (buf);
}
/*
* Return a string describing a FSM_OUTPUT_DOWN or FSM_OUTPUT_DEAD output.
*/
const char *
ppp_fsm_reason_str(struct ppp_fsm_output *output)
{
static char buf[64];
/* Sanity check */
if (output->type != FSM_OUTPUT_DEAD
&& output->type != FSM_OUTPUT_DOWN)
return ("?not FSM_OUTPUT_DOWN or FSM_OUTPUT_DEAD");
/* Describe reason */
switch (output->u.down.reason) {
case FSM_REASON_CLOSE:
strlcpy(buf, "administratively closed", sizeof(buf));
break;
case FSM_REASON_DOWN_FATAL:
strlcpy(buf, "the underlying packet delivery"
" layer failed (fatal)", sizeof(buf));
break;
case FSM_REASON_DOWN_NONFATAL:
strlcpy(buf, "the underlying packet delivery"
" layer failed (non-fatal)", sizeof(buf));
break;
case FSM_REASON_CONF:
snprintf(buf, sizeof(buf),
"rec'd %s after reaching opened state",
cd2str(output->u.down.u.code));
break;
case FSM_REASON_TERM:
snprintf(buf, sizeof(buf),
"rec'd a Terminate-Request from peer");
break;
case FSM_REASON_CODEREJ:
snprintf(buf, sizeof(buf),
"rec'd a fatal Code-Reject (code=%s)",
cd2str(output->u.down.u.code));
break;
case FSM_REASON_PROTOREJ:
snprintf(buf, sizeof(buf),
"rec'd a fatal Protocol-Reject (proto=0x%04x)",
output->u.down.u.proto);
break;
case FSM_REASON_NEGOT:
strlcpy(buf, "local and remote configurations"
" are not compatible", sizeof(buf));
break;
case FSM_REASON_BADMAGIC:
strlcpy(buf, "rec'd a packet with an invalid magic number",
sizeof(buf));
break;
case FSM_REASON_LOOPBACK:
strlcpy(buf, "a loopback condition was detected", sizeof(buf));
break;
case FSM_REASON_TIMEOUT:
strlcpy(buf, "timed out waiting for an echo response",
sizeof(buf));
break;
case FSM_REASON_SYSERR:
snprintf(buf, sizeof(buf), "internal system error, error=%s",
strerror(output->u.down.u.error));
break;
default:
snprintf(buf, sizeof(buf), "?[%u]", output->u.down.reason);
break;
}
return (buf);
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>