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