1/*
2 * fsm.c - {Link, IP} Control Protocol Finite State Machine.
3 *
4 * Copyright (c) 1989 Carnegie Mellon University.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms are permitted
8 * provided that the above copyright notice and this paragraph are
9 * duplicated in all such forms and that any documentation,
10 * advertising materials, and other materials related to such
11 * distribution and use acknowledge that the software was developed
12 * by Carnegie Mellon University.  The name of the
13 * University may not be used to endorse or promote products derived
14 * from this software without specific prior written permission.
15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
17 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18 */
19
20#define RCSID	"$Id: fsm.c 241182 2011-02-17 21:50:03Z $"
21
22/*
23 * TODO:
24 * Randomize fsm id on link/init.
25 * Deal with variable outgoing MTU.
26 */
27
28#include <stdio.h>
29#include <string.h>
30#include <sys/types.h>
31
32#include <pppd.h>
33#include "fsm.h"
34
35static const char rcsid[] = RCSID;
36
37static void fsm_timeout __P((void *));
38static void fsm_rconfreq __P((fsm *, int, u_char *, int));
39static void fsm_rconfack __P((fsm *, int, u_char *, int));
40static void fsm_rconfnakrej __P((fsm *, int, int, u_char *, int));
41static void fsm_rtermreq __P((fsm *, int, u_char *, int));
42static void fsm_rtermack __P((fsm *));
43static void fsm_rcoderej __P((fsm *, u_char *, int));
44static void fsm_sconfreq __P((fsm *, int));
45
46#define PROTO_NAME(f)	((f)->callbacks->proto_name)
47
48int peer_mru[NUM_PPP];
49
50
51/*
52 * fsm_init - Initialize fsm.
53 *
54 * Initialize fsm state.
55 */
56void
57fsm_init(f)
58    fsm *f;
59{
60    f->state = INITIAL;
61    f->flags = 0;
62    f->id = 0;
63    f->timeouttime = DEFTIMEOUT;
64    f->maxconfreqtransmits = DEFMAXCONFREQS;
65    f->maxtermtransmits = DEFMAXTERMREQS;
66    f->maxnakloops = DEFMAXNAKLOOPS;
67    f->term_reason_len = 0;
68}
69
70
71/*
72 * fsm_lowerup - The lower layer is up.
73 */
74void
75fsm_lowerup(f)
76    fsm *f;
77{
78    switch( f->state ){
79    case INITIAL:
80	f->state = CLOSED;
81	break;
82
83    case STARTING:
84	if( f->flags & OPT_SILENT )
85	    f->state = STOPPED;
86	else {
87	    /* Send an initial configure-request */
88	    fsm_sconfreq(f, 0);
89	    f->state = REQSENT;
90	}
91	break;
92
93    default:
94	FSMDEBUG(("%s: Up event in state %d!", PROTO_NAME(f), f->state));
95    }
96}
97
98
99/*
100 * fsm_lowerdown - The lower layer is down.
101 *
102 * Cancel all timeouts and inform upper layers.
103 */
104void
105fsm_lowerdown(f)
106    fsm *f;
107{
108    switch( f->state ){
109    case CLOSED:
110	f->state = INITIAL;
111	break;
112
113    case STOPPED:
114	f->state = STARTING;
115	if( f->callbacks->starting )
116	    (*f->callbacks->starting)(f);
117	break;
118
119    case CLOSING:
120	f->state = INITIAL;
121	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
122	break;
123
124    case STOPPING:
125    case REQSENT:
126    case ACKRCVD:
127    case ACKSENT:
128	f->state = STARTING;
129	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
130	break;
131
132    case OPENED:
133	if( f->callbacks->down )
134	    (*f->callbacks->down)(f);
135	f->state = STARTING;
136	break;
137
138    default:
139	FSMDEBUG(("%s: Down event in state %d!", PROTO_NAME(f), f->state));
140    }
141}
142
143
144/*
145 * fsm_open - Link is allowed to come up.
146 */
147void
148fsm_open(f)
149    fsm *f;
150{
151    switch( f->state ){
152    case INITIAL:
153	f->state = STARTING;
154	if( f->callbacks->starting )
155	    (*f->callbacks->starting)(f);
156	break;
157
158    case CLOSED:
159	if( f->flags & OPT_SILENT )
160	    f->state = STOPPED;
161	else {
162	    /* Send an initial configure-request */
163	    fsm_sconfreq(f, 0);
164	    f->state = REQSENT;
165	}
166	break;
167
168    case CLOSING:
169	f->state = STOPPING;
170	/* fall through */
171    case STOPPED:
172    case OPENED:
173	if( f->flags & OPT_RESTART ){
174	    fsm_lowerdown(f);
175	    fsm_lowerup(f);
176	}
177	break;
178    }
179}
180
181
182/*
183 * fsm_close - Start closing connection.
184 *
185 * Cancel timeouts and either initiate close or possibly go directly to
186 * the CLOSED state.
187 */
188void
189fsm_close(f, reason)
190    fsm *f;
191    char *reason;
192{
193    f->term_reason = reason;
194    f->term_reason_len = (reason == NULL? 0: strlen(reason));
195    switch( f->state ){
196    case STARTING:
197	f->state = INITIAL;
198	break;
199    case STOPPED:
200	f->state = CLOSED;
201	break;
202    case STOPPING:
203	f->state = CLOSING;
204	break;
205
206    case REQSENT:
207    case ACKRCVD:
208    case ACKSENT:
209    case OPENED:
210	if( f->state != OPENED )
211	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
212	else if( f->callbacks->down )
213	    (*f->callbacks->down)(f);	/* Inform upper layers we're down */
214
215	/* Init restart counter, send Terminate-Request */
216	f->retransmits = f->maxtermtransmits;
217	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
218		  (u_char *) f->term_reason, f->term_reason_len);
219	TIMEOUT(fsm_timeout, f, f->timeouttime);
220	--f->retransmits;
221
222	f->state = CLOSING;
223	break;
224    }
225}
226
227
228/*
229 * fsm_timeout - Timeout expired.
230 */
231static void
232fsm_timeout(arg)
233    void *arg;
234{
235    fsm *f = (fsm *) arg;
236
237    switch (f->state) {
238    case CLOSING:
239    case STOPPING:
240	if( f->retransmits <= 0 ){
241	    /*
242	     * We've waited for an ack long enough.  Peer probably heard us.
243	     */
244	    f->state = (f->state == CLOSING)? CLOSED: STOPPED;
245	    if( f->callbacks->finished )
246		(*f->callbacks->finished)(f);
247	} else {
248	    /* Send Terminate-Request */
249	    fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
250		      (u_char *) f->term_reason, f->term_reason_len);
251	    TIMEOUT(fsm_timeout, f, f->timeouttime);
252	    --f->retransmits;
253	}
254	break;
255
256    case REQSENT:
257    case ACKRCVD:
258    case ACKSENT:
259	if (f->retransmits <= 0) {
260	    warn("%s: timeout sending Config-Requests\n", PROTO_NAME(f));
261	    f->state = STOPPED;
262	    if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
263		(*f->callbacks->finished)(f);
264
265	} else {
266	    /* Retransmit the configure-request */
267	    if (f->callbacks->retransmit)
268		(*f->callbacks->retransmit)(f);
269	    fsm_sconfreq(f, 1);		/* Re-send Configure-Request */
270	    if( f->state == ACKRCVD )
271		f->state = REQSENT;
272	}
273	break;
274
275    default:
276	FSMDEBUG(("%s: Timeout event in state %d!", PROTO_NAME(f), f->state));
277    }
278}
279
280
281/*
282 * fsm_input - Input packet.
283 */
284void
285fsm_input(f, inpacket, l)
286    fsm *f;
287    u_char *inpacket;
288    int l;
289{
290    u_char *inp;
291    u_char code, id;
292    int len;
293
294    /*
295     * Parse header (code, id and length).
296     * If packet too short, drop it.
297     */
298    inp = inpacket;
299    if (l < HEADERLEN) {
300	FSMDEBUG(("fsm_input(%x): Rcvd short header.", f->protocol));
301	return;
302    }
303    GETCHAR(code, inp);
304    GETCHAR(id, inp);
305    GETSHORT(len, inp);
306    if (len < HEADERLEN) {
307	FSMDEBUG(("fsm_input(%x): Rcvd illegal length.", f->protocol));
308	return;
309    }
310    if (len > l) {
311	FSMDEBUG(("fsm_input(%x): Rcvd short packet.", f->protocol));
312	return;
313    }
314    len -= HEADERLEN;		/* subtract header length */
315
316    if( f->state == INITIAL || f->state == STARTING ){
317	FSMDEBUG(("fsm_input(%x): Rcvd packet in state %d.",
318		  f->protocol, f->state));
319	return;
320    }
321
322    /*
323     * Action depends on code.
324     */
325    switch (code) {
326    case CONFREQ:
327	fsm_rconfreq(f, id, inp, len);
328	break;
329
330    case CONFACK:
331	fsm_rconfack(f, id, inp, len);
332	break;
333
334    case CONFNAK:
335    case CONFREJ:
336	fsm_rconfnakrej(f, code, id, inp, len);
337	break;
338
339    case TERMREQ:
340	fsm_rtermreq(f, id, inp, len);
341	break;
342
343    case TERMACK:
344	fsm_rtermack(f);
345	break;
346
347    case CODEREJ:
348	fsm_rcoderej(f, inp, len);
349	break;
350
351    default:
352	if( !f->callbacks->extcode
353	   || !(*f->callbacks->extcode)(f, code, id, inp, len) )
354	    fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
355	break;
356    }
357}
358
359
360/*
361 * fsm_rconfreq - Receive Configure-Request.
362 */
363static void
364fsm_rconfreq(f, id, inp, len)
365    fsm *f;
366    u_char id;
367    u_char *inp;
368    int len;
369{
370    int code, reject_if_disagree;
371
372    switch( f->state ){
373    case CLOSED:
374	/* Go away, we're closed */
375	fsm_sdata(f, TERMACK, id, NULL, 0);
376	return;
377    case CLOSING:
378    case STOPPING:
379	return;
380
381    case OPENED:
382	/* Go down and restart negotiation */
383	if( f->callbacks->down )
384	    (*f->callbacks->down)(f);	/* Inform upper layers */
385	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
386	break;
387
388    case STOPPED:
389	/* Negotiation started by our peer */
390	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
391	f->state = REQSENT;
392	break;
393    }
394
395    /*
396     * Pass the requested configuration options
397     * to protocol-specific code for checking.
398     */
399    if (f->callbacks->reqci){		/* Check CI */
400	reject_if_disagree = (f->nakloops >= f->maxnakloops);
401	code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
402    } else if (len)
403	code = CONFREJ;			/* Reject all CI */
404    else
405	code = CONFACK;
406
407    /* send the Ack, Nak or Rej to the peer */
408    fsm_sdata(f, code, id, inp, len);
409
410    if (code == CONFACK) {
411	if (f->state == ACKRCVD) {
412	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
413	    f->state = OPENED;
414	    if (f->callbacks->up)
415		(*f->callbacks->up)(f);	/* Inform upper layers */
416	} else
417	    f->state = ACKSENT;
418	f->nakloops = 0;
419
420    } else {
421	/* we sent CONFACK or CONFREJ */
422	if (f->state != ACKRCVD)
423	    f->state = REQSENT;
424	if( code == CONFNAK )
425	    ++f->nakloops;
426    }
427}
428
429
430/*
431 * fsm_rconfack - Receive Configure-Ack.
432 */
433static void
434fsm_rconfack(f, id, inp, len)
435    fsm *f;
436    int id;
437    u_char *inp;
438    int len;
439{
440    if (id != f->reqid || f->seen_ack)		/* Expected id? */
441	return;					/* Nope, toss... */
442    if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
443	  (len == 0)) ){
444	/* Ack is bad - ignore it */
445	error("Received bad configure-ack: %P", inp, len);
446	return;
447    }
448    f->seen_ack = 1;
449
450    switch (f->state) {
451    case CLOSED:
452    case STOPPED:
453	fsm_sdata(f, TERMACK, id, NULL, 0);
454	break;
455
456    case REQSENT:
457	f->state = ACKRCVD;
458	f->retransmits = f->maxconfreqtransmits;
459	break;
460
461    case ACKRCVD:
462	/* Huh? an extra valid Ack? oh well... */
463	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
464	fsm_sconfreq(f, 0);
465	f->state = REQSENT;
466	break;
467
468    case ACKSENT:
469	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
470	f->state = OPENED;
471	f->retransmits = f->maxconfreqtransmits;
472	if (f->callbacks->up)
473	    (*f->callbacks->up)(f);	/* Inform upper layers */
474	break;
475
476    case OPENED:
477	/* Go down and restart negotiation */
478	if (f->callbacks->down)
479	    (*f->callbacks->down)(f);	/* Inform upper layers */
480	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
481	f->state = REQSENT;
482	break;
483    }
484}
485
486
487/*
488 * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
489 */
490static void
491fsm_rconfnakrej(f, code, id, inp, len)
492    fsm *f;
493    int code, id;
494    u_char *inp;
495    int len;
496{
497    int (*proc) __P((fsm *, u_char *, int));
498    int ret;
499
500    if (id != f->reqid || f->seen_ack)	/* Expected id? */
501	return;				/* Nope, toss... */
502    proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
503    if (!proc || !(ret = proc(f, inp, len))) {
504	/* Nak/reject is bad - ignore it */
505	error("Received bad configure-nak/rej: %P", inp, len);
506	return;
507    }
508    f->seen_ack = 1;
509
510    switch (f->state) {
511    case CLOSED:
512    case STOPPED:
513	fsm_sdata(f, TERMACK, id, NULL, 0);
514	break;
515
516    case REQSENT:
517    case ACKSENT:
518	/* They didn't agree to what we wanted - try another request */
519	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
520	if (ret < 0)
521	    f->state = STOPPED;		/* kludge for stopping CCP */
522	else
523	    fsm_sconfreq(f, 0);		/* Send Configure-Request */
524	break;
525
526    case ACKRCVD:
527	/* Got a Nak/reject when we had already had an Ack?? oh well... */
528	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
529	fsm_sconfreq(f, 0);
530	f->state = REQSENT;
531	break;
532
533    case OPENED:
534	/* Go down and restart negotiation */
535	if (f->callbacks->down)
536	    (*f->callbacks->down)(f);	/* Inform upper layers */
537	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
538	f->state = REQSENT;
539	break;
540    }
541}
542
543
544/*
545 * fsm_rtermreq - Receive Terminate-Req.
546 */
547static void
548fsm_rtermreq(f, id, p, len)
549    fsm *f;
550    int id;
551    u_char *p;
552    int len;
553{
554    switch (f->state) {
555    case ACKRCVD:
556    case ACKSENT:
557	f->state = REQSENT;		/* Start over but keep trying */
558	break;
559
560    case OPENED:
561	if (len > 0) {
562	    info("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p);
563	} else
564	    info("%s terminated by peer", PROTO_NAME(f));
565	if (f->callbacks->down)
566	    (*f->callbacks->down)(f);	/* Inform upper layers */
567	f->retransmits = 0;
568	f->state = STOPPING;
569	TIMEOUT(fsm_timeout, f, f->timeouttime);
570	break;
571    }
572
573    fsm_sdata(f, TERMACK, id, NULL, 0);
574}
575
576
577/*
578 * fsm_rtermack - Receive Terminate-Ack.
579 */
580static void
581fsm_rtermack(f)
582    fsm *f;
583{
584    switch (f->state) {
585    case CLOSING:
586	UNTIMEOUT(fsm_timeout, f);
587	f->state = CLOSED;
588	if( f->callbacks->finished )
589	    (*f->callbacks->finished)(f);
590	break;
591    case STOPPING:
592	UNTIMEOUT(fsm_timeout, f);
593	f->state = STOPPED;
594	if( f->callbacks->finished )
595	    (*f->callbacks->finished)(f);
596	break;
597
598    case ACKRCVD:
599	f->state = REQSENT;
600	break;
601
602    case OPENED:
603	if (f->callbacks->down)
604	    (*f->callbacks->down)(f);	/* Inform upper layers */
605	fsm_sconfreq(f, 0);
606	break;
607    }
608}
609
610
611/*
612 * fsm_rcoderej - Receive an Code-Reject.
613 */
614static void
615fsm_rcoderej(f, inp, len)
616    fsm *f;
617    u_char *inp;
618    int len;
619{
620    u_char code, id;
621
622    if (len < HEADERLEN) {
623	FSMDEBUG(("fsm_rcoderej: Rcvd short Code-Reject packet!"));
624	return;
625    }
626    GETCHAR(code, inp);
627    GETCHAR(id, inp);
628    warn("%s: Rcvd Code-Reject for code %d, id %d", PROTO_NAME(f), code, id);
629
630    if( f->state == ACKRCVD )
631	f->state = REQSENT;
632}
633
634
635/*
636 * fsm_protreject - Peer doesn't speak this protocol.
637 *
638 * Treat this as a catastrophic error (RXJ-).
639 */
640void
641fsm_protreject(f)
642    fsm *f;
643{
644    switch( f->state ){
645    case CLOSING:
646	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
647	/* fall through */
648    case CLOSED:
649	f->state = CLOSED;
650	if( f->callbacks->finished )
651	    (*f->callbacks->finished)(f);
652	break;
653
654    case STOPPING:
655    case REQSENT:
656    case ACKRCVD:
657    case ACKSENT:
658	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
659	/* fall through */
660    case STOPPED:
661	f->state = STOPPED;
662	if( f->callbacks->finished )
663	    (*f->callbacks->finished)(f);
664	break;
665
666    case OPENED:
667	if( f->callbacks->down )
668	    (*f->callbacks->down)(f);
669
670	/* Init restart counter, send Terminate-Request */
671	f->retransmits = f->maxtermtransmits;
672	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
673		  (u_char *) f->term_reason, f->term_reason_len);
674	TIMEOUT(fsm_timeout, f, f->timeouttime);
675	--f->retransmits;
676
677	f->state = STOPPING;
678	break;
679
680    default:
681	FSMDEBUG(("%s: Protocol-reject event in state %d!",
682		  PROTO_NAME(f), f->state));
683    }
684}
685
686
687/*
688 * fsm_sconfreq - Send a Configure-Request.
689 */
690static void
691fsm_sconfreq(f, retransmit)
692    fsm *f;
693    int retransmit;
694{
695    u_char *outp;
696    int cilen;
697
698    if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
699	/* Not currently negotiating - reset options */
700	if( f->callbacks->resetci )
701	    (*f->callbacks->resetci)(f);
702	f->nakloops = 0;
703    }
704
705    if( !retransmit ){
706	/* New request - reset retransmission counter, use new ID */
707	f->retransmits = f->maxconfreqtransmits;
708	f->reqid = ++f->id;
709    }
710
711    f->seen_ack = 0;
712
713    /*
714     * Make up the request packet
715     */
716    outp = outpacket_buf + PPP_HDRLEN + HEADERLEN;
717    if( f->callbacks->cilen && f->callbacks->addci ){
718	cilen = (*f->callbacks->cilen)(f);
719	if( cilen > peer_mru[f->unit] - HEADERLEN )
720	    cilen = peer_mru[f->unit] - HEADERLEN;
721	if (f->callbacks->addci)
722	    (*f->callbacks->addci)(f, outp, &cilen);
723    } else
724	cilen = 0;
725
726    /* send the request to our peer */
727    fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
728
729    /* start the retransmit timer */
730    --f->retransmits;
731    TIMEOUT(fsm_timeout, f, f->timeouttime);
732}
733
734
735/*
736 * fsm_sdata - Send some data.
737 *
738 * Used for all packets sent to our peer by this module.
739 */
740void
741fsm_sdata(f, code, id, data, datalen)
742    fsm *f;
743    u_char code, id;
744    u_char *data;
745    int datalen;
746{
747    u_char *outp;
748    int outlen;
749
750    /* Adjust length to be smaller than MTU */
751    outp = outpacket_buf;
752    if (datalen > peer_mru[f->unit] - HEADERLEN)
753	datalen = peer_mru[f->unit] - HEADERLEN;
754    if (datalen && data != outp + PPP_HDRLEN + HEADERLEN)
755	BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);
756    outlen = datalen + HEADERLEN;
757    MAKEHEADER(outp, f->protocol);
758    PUTCHAR(code, outp);
759    PUTCHAR(id, outp);
760    PUTSHORT(outlen, outp);
761    output(f->unit, outpacket_buf, outlen + PPP_HDRLEN);
762}
763