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$"
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    case STARTING: /* foxconn wklin added */
154	f->state = STARTING;
155	if( f->callbacks->starting )
156	    (*f->callbacks->starting)(f);
157	break;
158
159    case CLOSED:
160	if( f->flags & OPT_SILENT )
161	    f->state = STOPPED;
162	else {
163	    /* Send an initial configure-request */
164	    fsm_sconfreq(f, 0);
165	    f->state = REQSENT;
166	}
167	break;
168
169    case CLOSING:
170	f->state = STOPPING;
171	/* fall through */
172    case STOPPED:
173    case OPENED:
174	if( f->flags & OPT_RESTART ){
175	    fsm_lowerdown(f);
176	    fsm_lowerup(f);
177	}
178	break;
179    }
180}
181
182
183/*
184 * fsm_close - Start closing connection.
185 *
186 * Cancel timeouts and either initiate close or possibly go directly to
187 * the CLOSED state.
188 */
189void
190fsm_close(f, reason)
191    fsm *f;
192    char *reason;
193{
194    f->term_reason = reason;
195    f->term_reason_len = (reason == NULL? 0: strlen(reason));
196    switch( f->state ){
197    case STARTING:
198	f->state = INITIAL;
199	break;
200    case STOPPED:
201	f->state = CLOSED;
202	break;
203    case STOPPING:
204	f->state = CLOSING;
205	break;
206
207    case REQSENT:
208    case ACKRCVD:
209    case ACKSENT:
210    case OPENED:
211	if( f->state != OPENED )
212	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
213	else if( f->callbacks->down )
214	    (*f->callbacks->down)(f);	/* Inform upper layers we're down */
215
216	/* Init restart counter, send Terminate-Request */
217	f->retransmits = f->maxtermtransmits;
218	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
219		  (u_char *) f->term_reason, f->term_reason_len);
220	TIMEOUT(fsm_timeout, f, f->timeouttime);
221	--f->retransmits;
222
223	f->state = CLOSING;
224	break;
225    }
226}
227
228
229/*
230 * fsm_timeout - Timeout expired.
231 */
232static void
233fsm_timeout(arg)
234    void *arg;
235{
236    fsm *f = (fsm *) arg;
237
238    switch (f->state) {
239    case CLOSING:
240    case STOPPING:
241	if( f->retransmits <= 0 ){
242	    /*
243	     * We've waited for an ack long enough.  Peer probably heard us.
244	     */
245	    f->state = (f->state == CLOSING)? CLOSED: STOPPED;
246	    if( f->callbacks->finished )
247		(*f->callbacks->finished)(f);
248	} else {
249	    /* Send Terminate-Request */
250	    fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
251		      (u_char *) f->term_reason, f->term_reason_len);
252	    TIMEOUT(fsm_timeout, f, f->timeouttime);
253	    --f->retransmits;
254	}
255	break;
256
257    case REQSENT:
258    case ACKRCVD:
259    case ACKSENT:
260	if (f->retransmits <= 0) {
261	    warn("%s: timeout sending Config-Requests\n", PROTO_NAME(f));
262	    f->state = STOPPED;
263	    if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
264		(*f->callbacks->finished)(f);
265
266	} else {
267	    /* Retransmit the configure-request */
268	    if (f->callbacks->retransmit)
269		(*f->callbacks->retransmit)(f);
270	    fsm_sconfreq(f, 1);		/* Re-send Configure-Request */
271	    if( f->state == ACKRCVD )
272		f->state = REQSENT;
273	}
274	break;
275
276    default:
277	FSMDEBUG(("%s: Timeout event in state %d!", PROTO_NAME(f), f->state));
278    }
279}
280
281
282/*
283 * fsm_input - Input packet.
284 */
285void
286fsm_input(f, inpacket, l)
287    fsm *f;
288    u_char *inpacket;
289    int l;
290{
291    u_char *inp;
292    u_char code, id;
293    int len;
294
295    /*
296     * Parse header (code, id and length).
297     * If packet too short, drop it.
298     */
299    inp = inpacket;
300    if (l < HEADERLEN) {
301	FSMDEBUG(("fsm_input(%x): Rcvd short header.", f->protocol));
302	return;
303    }
304    GETCHAR(code, inp);
305    GETCHAR(id, inp);
306    GETSHORT(len, inp);
307    if (len < HEADERLEN) {
308	FSMDEBUG(("fsm_input(%x): Rcvd illegal length.", f->protocol));
309	return;
310    }
311    if (len > l) {
312	FSMDEBUG(("fsm_input(%x): Rcvd short packet.", f->protocol));
313	return;
314    }
315    len -= HEADERLEN;		/* subtract header length */
316
317    if( f->state == INITIAL || f->state == STARTING ){
318	FSMDEBUG(("fsm_input(%x): Rcvd packet in state %d.",
319		  f->protocol, f->state));
320	return;
321    }
322
323    /*
324     * Action depends on code.
325     */
326    switch (code) {
327    case CONFREQ:
328	fsm_rconfreq(f, id, inp, len);
329	break;
330
331    case CONFACK:
332	fsm_rconfack(f, id, inp, len);
333	break;
334
335    case CONFNAK:
336    case CONFREJ:
337	fsm_rconfnakrej(f, code, id, inp, len);
338	break;
339
340    case TERMREQ:
341	fsm_rtermreq(f, id, inp, len);
342	break;
343
344    case TERMACK:
345	fsm_rtermack(f);
346	break;
347
348    case CODEREJ:
349	fsm_rcoderej(f, inp, len);
350	break;
351
352    default:
353	if( !f->callbacks->extcode
354	   || !(*f->callbacks->extcode)(f, code, id, inp, len) )
355	    fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
356	break;
357    }
358}
359
360
361/*
362 * fsm_rconfreq - Receive Configure-Request.
363 */
364static void
365fsm_rconfreq(f, id, inp, len)
366    fsm *f;
367    u_char id;
368    u_char *inp;
369    int len;
370{
371    int code, reject_if_disagree;
372
373    switch( f->state ){
374    case CLOSED:
375	/* Go away, we're closed */
376	fsm_sdata(f, TERMACK, id, NULL, 0);
377	return;
378    case CLOSING:
379    case STOPPING:
380	return;
381
382    case OPENED:
383	/* Go down and restart negotiation */
384	if( f->callbacks->down )
385	    (*f->callbacks->down)(f);	/* Inform upper layers */
386	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
387	break;
388
389    case STOPPED:
390	/* Negotiation started by our peer */
391	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
392	f->state = REQSENT;
393	break;
394    }
395
396    /*
397     * Pass the requested configuration options
398     * to protocol-specific code for checking.
399     */
400    if (f->callbacks->reqci){		/* Check CI */
401	reject_if_disagree = (f->nakloops >= f->maxnakloops);
402	code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
403    } else if (len)
404	code = CONFREJ;			/* Reject all CI */
405    else
406	code = CONFACK;
407
408    /* send the Ack, Nak or Rej to the peer */
409    fsm_sdata(f, code, id, inp, len);
410
411    if (code == CONFACK) {
412	if (f->state == ACKRCVD) {
413	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
414	    f->state = OPENED;
415	    if (f->callbacks->up)
416		(*f->callbacks->up)(f);	/* Inform upper layers */
417	} else
418	    f->state = ACKSENT;
419	f->nakloops = 0;
420
421    } else {
422	/* we sent CONFACK or CONFREJ */
423	if (f->state != ACKRCVD)
424	    f->state = REQSENT;
425	if( code == CONFNAK )
426	    ++f->nakloops;
427    }
428}
429
430
431/*
432 * fsm_rconfack - Receive Configure-Ack.
433 */
434static void
435fsm_rconfack(f, id, inp, len)
436    fsm *f;
437    int id;
438    u_char *inp;
439    int len;
440{
441    if (id != f->reqid || f->seen_ack)		/* Expected id? */
442	return;					/* Nope, toss... */
443    if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
444	  (len == 0)) ){
445	/* Ack is bad - ignore it */
446	error("Received bad configure-ack: %P", inp, len);
447	return;
448    }
449    f->seen_ack = 1;
450
451    switch (f->state) {
452    case CLOSED:
453    case STOPPED:
454	fsm_sdata(f, TERMACK, id, NULL, 0);
455	break;
456
457    case REQSENT:
458	f->state = ACKRCVD;
459	f->retransmits = f->maxconfreqtransmits;
460	break;
461
462    case ACKRCVD:
463	/* Huh? an extra valid Ack? oh well... */
464	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
465	fsm_sconfreq(f, 0);
466	f->state = REQSENT;
467	break;
468
469    case ACKSENT:
470	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
471	f->state = OPENED;
472	f->retransmits = f->maxconfreqtransmits;
473	if (f->callbacks->up)
474	    (*f->callbacks->up)(f);	/* Inform upper layers */
475	break;
476
477    case OPENED:
478	/* Go down and restart negotiation */
479	if (f->callbacks->down)
480	    (*f->callbacks->down)(f);	/* Inform upper layers */
481	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
482	f->state = REQSENT;
483	break;
484    }
485}
486
487
488/*
489 * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
490 */
491static void
492fsm_rconfnakrej(f, code, id, inp, len)
493    fsm *f;
494    int code, id;
495    u_char *inp;
496    int len;
497{
498    int (*proc) __P((fsm *, u_char *, int));
499    int ret;
500
501    if (id != f->reqid || f->seen_ack)	/* Expected id? */
502	return;				/* Nope, toss... */
503    proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
504    if (!proc || !(ret = proc(f, inp, len))) {
505	/* Nak/reject is bad - ignore it */
506	error("Received bad configure-nak/rej: %P", inp, len);
507	return;
508    }
509    f->seen_ack = 1;
510
511    switch (f->state) {
512    case CLOSED:
513    case STOPPED:
514	fsm_sdata(f, TERMACK, id, NULL, 0);
515	break;
516
517    case REQSENT:
518    case ACKSENT:
519	/* They didn't agree to what we wanted - try another request */
520	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
521	if (ret < 0)
522	    f->state = STOPPED;		/* kludge for stopping CCP */
523	else
524	    fsm_sconfreq(f, 0);		/* Send Configure-Request */
525	break;
526
527    case ACKRCVD:
528	/* Got a Nak/reject when we had already had an Ack?? oh well... */
529	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
530	fsm_sconfreq(f, 0);
531	f->state = REQSENT;
532	break;
533
534    case OPENED:
535	/* Go down and restart negotiation */
536	if (f->callbacks->down)
537	    (*f->callbacks->down)(f);	/* Inform upper layers */
538	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
539	f->state = REQSENT;
540	break;
541    }
542}
543
544
545/*
546 * fsm_rtermreq - Receive Terminate-Req.
547 */
548static void
549fsm_rtermreq(f, id, p, len)
550    fsm *f;
551    int id;
552    u_char *p;
553    int len;
554{
555    switch (f->state) {
556    case ACKRCVD:
557    case ACKSENT:
558	f->state = REQSENT;		/* Start over but keep trying */
559	break;
560
561    case OPENED:
562	if (len > 0) {
563	    info("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p);
564	} else
565	    info("%s terminated by peer", PROTO_NAME(f));
566	if (f->callbacks->down)
567	    (*f->callbacks->down)(f);	/* Inform upper layers */
568	f->retransmits = 0;
569	f->state = STOPPING;
570	TIMEOUT(fsm_timeout, f, f->timeouttime);
571	break;
572    }
573
574    fsm_sdata(f, TERMACK, id, NULL, 0);
575}
576
577
578/*
579 * fsm_rtermack - Receive Terminate-Ack.
580 */
581static void
582fsm_rtermack(f)
583    fsm *f;
584{
585    switch (f->state) {
586    case CLOSING:
587	UNTIMEOUT(fsm_timeout, f);
588	f->state = CLOSED;
589	if( f->callbacks->finished )
590	    (*f->callbacks->finished)(f);
591	break;
592    case STOPPING:
593	UNTIMEOUT(fsm_timeout, f);
594	f->state = STOPPED;
595	if( f->callbacks->finished )
596	    (*f->callbacks->finished)(f);
597	break;
598
599    case ACKRCVD:
600	f->state = REQSENT;
601	break;
602
603    case OPENED:
604	if (f->callbacks->down)
605	    (*f->callbacks->down)(f);	/* Inform upper layers */
606	fsm_sconfreq(f, 0);
607	break;
608    }
609}
610
611
612/*
613 * fsm_rcoderej - Receive an Code-Reject.
614 */
615static void
616fsm_rcoderej(f, inp, len)
617    fsm *f;
618    u_char *inp;
619    int len;
620{
621    u_char code, id;
622
623    if (len < HEADERLEN) {
624	FSMDEBUG(("fsm_rcoderej: Rcvd short Code-Reject packet!"));
625	return;
626    }
627    GETCHAR(code, inp);
628    GETCHAR(id, inp);
629    warn("%s: Rcvd Code-Reject for code %d, id %d", PROTO_NAME(f), code, id);
630
631    if( f->state == ACKRCVD )
632	f->state = REQSENT;
633}
634
635
636/*
637 * fsm_protreject - Peer doesn't speak this protocol.
638 *
639 * Treat this as a catastrophic error (RXJ-).
640 */
641void
642fsm_protreject(f)
643    fsm *f;
644{
645    switch( f->state ){
646    case CLOSING:
647	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
648	/* fall through */
649    case CLOSED:
650	f->state = CLOSED;
651	if( f->callbacks->finished )
652	    (*f->callbacks->finished)(f);
653	break;
654
655    case STOPPING:
656    case REQSENT:
657    case ACKRCVD:
658    case ACKSENT:
659	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
660	/* fall through */
661    case STOPPED:
662	f->state = STOPPED;
663	if( f->callbacks->finished )
664	    (*f->callbacks->finished)(f);
665	break;
666
667    case OPENED:
668	if( f->callbacks->down )
669	    (*f->callbacks->down)(f);
670
671	/* Init restart counter, send Terminate-Request */
672	f->retransmits = f->maxtermtransmits;
673	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
674		  (u_char *) f->term_reason, f->term_reason_len);
675	TIMEOUT(fsm_timeout, f, f->timeouttime);
676	--f->retransmits;
677
678	f->state = STOPPING;
679	break;
680
681    default:
682	FSMDEBUG(("%s: Protocol-reject event in state %d!",
683		  PROTO_NAME(f), f->state));
684    }
685}
686
687
688/*
689 * fsm_sconfreq - Send a Configure-Request.
690 */
691static void
692fsm_sconfreq(f, retransmit)
693    fsm *f;
694    int retransmit;
695{
696    u_char *outp;
697    int cilen;
698
699    if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
700	/* Not currently negotiating - reset options */
701	if( f->callbacks->resetci )
702	    (*f->callbacks->resetci)(f);
703	f->nakloops = 0;
704    }
705
706    if( !retransmit ){
707	/* New request - reset retransmission counter, use new ID */
708	f->retransmits = f->maxconfreqtransmits;
709	f->reqid = ++f->id;
710    }
711
712    f->seen_ack = 0;
713
714    /*
715     * Make up the request packet
716     */
717    outp = outpacket_buf + PPP_HDRLEN + HEADERLEN;
718    if( f->callbacks->cilen && f->callbacks->addci ){
719	cilen = (*f->callbacks->cilen)(f);
720	if( cilen > peer_mru[f->unit] - HEADERLEN )
721	    cilen = peer_mru[f->unit] - HEADERLEN;
722	if (f->callbacks->addci)
723	    (*f->callbacks->addci)(f, outp, &cilen);
724    } else
725	cilen = 0;
726
727    /* send the request to our peer */
728    fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
729
730    /* start the retransmit timer */
731    --f->retransmits;
732    TIMEOUT(fsm_timeout, f, f->timeouttime);
733}
734
735
736/*
737 * fsm_sdata - Send some data.
738 *
739 * Used for all packets sent to our peer by this module.
740 */
741void
742fsm_sdata(f, code, id, data, datalen)
743    fsm *f;
744    u_char code, id;
745    u_char *data;
746    int datalen;
747{
748    u_char *outp;
749    int outlen;
750
751    /* Adjust length to be smaller than MTU */
752    outp = outpacket_buf;
753    if (datalen > peer_mru[f->unit] - HEADERLEN)
754	datalen = peer_mru[f->unit] - HEADERLEN;
755    if (datalen && data != outp + PPP_HDRLEN + HEADERLEN)
756	BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);
757    outlen = datalen + HEADERLEN;
758    MAKEHEADER(outp, f->protocol);
759    PUTCHAR(code, outp);
760    PUTCHAR(id, outp);
761    PUTSHORT(outlen, outp);
762    output(f->unit, outpacket_buf, outlen + PPP_HDRLEN);
763}
764