Initial revision
[ppp.git] / pppd / fsm.c
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 #ifndef lint
21 static char rcsid[] = "$Id: fsm.c,v 1.1 1993/11/11 03:54:25 paulus Exp $";
22 #endif
23
24 /*
25  * TODO:
26  * Randomize fsm id on link/init.
27  * Deal with variable outgoing MTU.
28  */
29
30 #include <stdio.h>
31 #include <sys/types.h>
32 /*#include <malloc.h>*/
33 #include <syslog.h>
34
35 #include "ppp.h"
36 #include "pppd.h"
37 #include "fsm.h"
38
39 extern char *proto_name();
40
41 static void fsm_timeout __ARGS((caddr_t));
42 static void fsm_rconfreq __ARGS((fsm *, int, u_char *, int));
43 static void fsm_rconfack __ARGS((fsm *, int, u_char *, int));
44 static void fsm_rconfnakrej __ARGS((fsm *, int, int, u_char *, int));
45 static void fsm_rtermreq __ARGS((fsm *, int));
46 static void fsm_rtermack __ARGS((fsm *));
47 static void fsm_rcoderej __ARGS((fsm *, u_char *, int));
48 static void fsm_sconfreq __ARGS((fsm *, int));
49
50 #define PROTO_NAME(f)   ((f)->callbacks->proto_name)
51
52 int peer_mru[NPPP];
53
54
55 /*
56  * fsm_init - Initialize fsm.
57  *
58  * Initialize fsm state.
59  */
60 void
61 fsm_init(f)
62     fsm *f;
63 {
64     f->state = INITIAL;
65     f->flags = 0;
66     f->id = 0;                          /* XXX Start with random id? */
67     f->timeouttime = DEFTIMEOUT;
68     f->maxconfreqtransmits = DEFMAXCONFREQS;
69     f->maxtermtransmits = DEFMAXTERMREQS;
70     f->maxnakloops = DEFMAXNAKLOOPS;
71 }
72
73
74 /*
75  * fsm_lowerup - The lower layer is up.
76  */
77 void
78 fsm_lowerup(f)
79     fsm *f;
80 {
81     switch( f->state ){
82     case INITIAL:
83         f->state = CLOSED;
84         break;
85
86     case STARTING:
87         if( f->flags & OPT_SILENT )
88             f->state = STOPPED;
89         else {
90             /* Send an initial configure-request */
91             fsm_sconfreq(f, 0);
92             f->state = REQSENT;
93         }
94         break;
95
96     default:
97         FSMDEBUG((LOG_INFO, "%s: Up event in state %d!",
98                   PROTO_NAME(f), f->state));
99     }
100 }
101
102
103 /*
104  * fsm_lowerdown - The lower layer is down.
105  *
106  * Cancel all timeouts and inform upper layers.
107  */
108 void
109 fsm_lowerdown(f)
110     fsm *f;
111 {
112     switch( f->state ){
113     case CLOSED:
114         f->state = INITIAL;
115         break;
116
117     case STOPPED:
118         f->state = STARTING;
119         if( f->callbacks->starting )
120             (*f->callbacks->starting)(f);
121         break;
122
123     case CLOSING:
124         f->state = INITIAL;
125         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
126         break;
127
128     case STOPPING:
129     case REQSENT:
130     case ACKRCVD:
131     case ACKSENT:
132         f->state = STARTING;
133         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
134         break;
135
136     case OPENED:
137         if( f->callbacks->down )
138             (*f->callbacks->down)(f);
139         f->state = STARTING;
140         break;
141
142     default:
143         FSMDEBUG((LOG_INFO, "%s: Down event in state %d!",
144                   PROTO_NAME(f), f->state));
145     }
146 }
147
148
149 /*
150  * fsm_open - Link is allowed to come up.
151  */
152 void
153 fsm_open(f)
154     fsm *f;
155 {
156     switch( f->state ){
157     case INITIAL:
158         f->state = STARTING;
159         if( f->callbacks->starting )
160             (*f->callbacks->starting)(f);
161         break;
162
163     case CLOSED:
164         if( f->flags & OPT_SILENT )
165             f->state = STOPPED;
166         else {
167             /* Send an initial configure-request */
168             fsm_sconfreq(f, 0);
169             f->state = REQSENT;
170         }
171         break;
172
173     case CLOSING:
174         f->state = STOPPING;
175         /* fall through */
176     case STOPPED:
177     case OPENED:
178         if( f->flags & OPT_RESTART ){
179             fsm_lowerdown(f);
180             fsm_lowerup(f);
181         }
182         break;
183     }
184 }
185
186
187 /*
188  * fsm_close - Start closing connection.
189  *
190  * Cancel timeouts and either initiate close or possibly go directly to
191  * the CLOSED state.
192  */
193 void
194 fsm_close(f)
195     fsm *f;
196 {
197     switch( f->state ){
198     case STARTING:
199         f->state = INITIAL;
200         break;
201     case STOPPED:
202         f->state = CLOSED;
203         break;
204     case STOPPING:
205         f->state = CLOSING;
206         break;
207
208     case REQSENT:
209     case ACKRCVD:
210     case ACKSENT:
211     case OPENED:
212         if( f->state != OPENED )
213             UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
214         else if( f->callbacks->down )
215             (*f->callbacks->down)(f);   /* Inform upper layers we're down */
216
217         /* Init restart counter, send Terminate-Request */
218         f->retransmits = f->maxtermtransmits;
219         fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
220         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
221         --f->retransmits;
222
223         f->state = CLOSING;
224         break;
225     }
226 }
227
228
229 /*
230  * fsm_timeout - Timeout expired.
231  */
232 static void
233 fsm_timeout(arg)
234     caddr_t 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, NULL, 0);
251             TIMEOUT(fsm_timeout, (caddr_t) 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             syslog(LOG_WARNING, "%s: timeout sending Config-Requests",
261                    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((LOG_INFO, "%s: Timeout event in state %d!",
278                   PROTO_NAME(f), f->state));
279     }
280 }
281
282
283 /*
284  * fsm_input - Input packet.
285  */
286 void
287 fsm_input(f, inpacket, l)
288     fsm *f;
289     u_char *inpacket;
290     int l;
291 {
292     u_char *inp, *outp;
293     u_char code, id;
294     int len;
295
296     /*
297      * Parse header (code, id and length).
298      * If packet too short, drop it.
299      */
300     inp = inpacket;
301     if (l < HEADERLEN) {
302         FSMDEBUG((LOG_WARNING, "fsm_input(%x): Rcvd short header.",
303                   f->protocol));
304         return;
305     }
306     GETCHAR(code, inp);
307     GETCHAR(id, inp);
308     GETSHORT(len, inp);
309     if (len < HEADERLEN) {
310         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd illegal length.",
311                   f->protocol));
312         return;
313     }
314     if (len > l) {
315         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd short packet.",
316                   f->protocol));
317         return;
318     }
319     len -= HEADERLEN;           /* subtract header length */
320
321     if( f->state == INITIAL || f->state == STARTING ){
322         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd packet in state %d.",
323                   f->protocol, f->state));
324         return;
325     }
326
327     /*
328      * Action depends on code.
329      */
330     switch (code) {
331     case CONFREQ:
332         fsm_rconfreq(f, id, inp, len);
333         break;
334     
335     case CONFACK:
336         fsm_rconfack(f, id, inp, len);
337         break;
338     
339     case CONFNAK:
340     case CONFREJ:
341         fsm_rconfnakrej(f, code, id, inp, len);
342         break;
343     
344     case TERMREQ:
345         fsm_rtermreq(f, id);
346         break;
347     
348     case TERMACK:
349         fsm_rtermack(f);
350         break;
351     
352     case CODEREJ:
353         fsm_rcoderej(f, inp, len);
354         break;
355     
356     default:
357         if( !f->callbacks->extcode
358            || !(*f->callbacks->extcode)(f, code, id, inp, len) )
359             fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
360         break;
361     }
362 }
363
364
365 /*
366  * fsm_rconfreq - Receive Configure-Request.
367  */
368 static void
369 fsm_rconfreq(f, id, inp, len)
370     fsm *f;
371     u_char id;
372     u_char *inp;
373     int len;
374 {
375     u_char *outp;
376     int code, reject_if_disagree;
377
378     FSMDEBUG((LOG_INFO, "fsm_rconfreq(%s): Rcvd id %d.", PROTO_NAME(f), id));
379     switch( f->state ){
380     case CLOSED:
381         /* Go away, we're closed */
382         fsm_sdata(f, TERMACK, id, NULL, 0);
383         return;
384     case CLOSING:
385     case STOPPING:
386         return;
387
388     case OPENED:
389         /* Go down and restart negotiation */
390         if( f->callbacks->down )
391             (*f->callbacks->down)(f);   /* Inform upper layers */
392         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
393         break;
394
395     case STOPPED:
396         /* Negotiation started by our peer */
397         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
398         f->state = REQSENT;
399         break;
400     }
401
402     /*
403      * Pass the requested configuration options
404      * to protocol-specific code for checking.
405      */
406     if (f->callbacks->reqci){           /* Check CI */
407         reject_if_disagree = (f->nakloops >= f->maxnakloops);
408         code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
409     } else if (len)
410         code = CONFREJ;                 /* Reject all CI */
411
412     /* send the Ack, Nak or Rej to the peer */
413     fsm_sdata(f, code, id, inp, len);
414
415     if (code == CONFACK) {
416         if (f->state == ACKRCVD) {
417             UNTIMEOUT(fsm_timeout, (caddr_t) f);        /* Cancel timeout */
418             f->state = OPENED;
419             if (f->callbacks->up)
420                 (*f->callbacks->up)(f); /* Inform upper layers */
421         } else
422             f->state = ACKSENT;
423         f->nakloops = 0;
424
425     } else {
426         /* we sent CONFACK or CONFREJ */
427         if (f->state != ACKRCVD)
428             f->state = REQSENT;
429         if( code == CONFNAK )
430             ++f->nakloops;
431     }
432 }
433
434
435 /*
436  * fsm_rconfack - Receive Configure-Ack.
437  */
438 static void
439 fsm_rconfack(f, id, inp, len)
440     fsm *f;
441     int id;
442     u_char *inp;
443     int len;
444 {
445     FSMDEBUG((LOG_INFO, "fsm_rconfack(%s): Rcvd id %d.",
446               PROTO_NAME(f), id));
447
448     if (id != f->reqid)         /* Expected id? */
449         return;                 /* Nope, toss... */
450     if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len): (len == 0)) ){
451         /* Ack is bad - ignore it */
452         FSMDEBUG((LOG_INFO, "%s: received bad Ack (length %d)",
453                   PROTO_NAME(f), len));
454         return;
455     }
456
457     switch (f->state) {
458     case CLOSED:
459     case STOPPED:
460         fsm_sdata(f, TERMACK, id, NULL, 0);
461         break;
462
463     case REQSENT:
464         f->state = ACKRCVD;
465         f->retransmits = f->maxconfreqtransmits;
466         break;
467
468     case ACKRCVD:
469         /* Huh? an extra Ack? oh well... */
470         fsm_sconfreq(f, 0);
471         f->state = REQSENT;
472         break;
473
474     case ACKSENT:
475         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
476         f->state = OPENED;
477         f->retransmits = f->maxconfreqtransmits;
478         if (f->callbacks->up)
479             (*f->callbacks->up)(f);     /* Inform upper layers */
480         break;
481
482     case OPENED:
483         /* Go down and restart negotiation */
484         if (f->callbacks->down)
485             (*f->callbacks->down)(f);   /* Inform upper layers */
486         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
487         f->state = REQSENT;
488         break;
489     }
490 }
491
492
493 /*
494  * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
495  */
496 static void
497 fsm_rconfnakrej(f, code, id, inp, len)
498     fsm *f;
499     int code, id;
500     u_char *inp;
501     int len;
502 {
503     int (*proc)();
504
505     FSMDEBUG((LOG_INFO, "fsm_rconfnakrej(%s): Rcvd id %d.",
506               PROTO_NAME(f), id));
507
508     if (id != f->reqid)         /* Expected id? */
509         return;                 /* Nope, toss... */
510     proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
511     if( !proc || !proc(f, inp, len) ){
512         /* Nak/reject is bad - ignore it */
513         FSMDEBUG((LOG_INFO, "%s: received bad %s (length %d)",
514                   PROTO_NAME(f), (code==CONFNAK? "Nak": "reject"), len));
515         return;
516     }
517
518     switch (f->state) {
519     case CLOSED:
520     case STOPPED:
521         fsm_sdata(f, TERMACK, id, NULL, 0);
522         break;
523
524     case REQSENT:
525     case ACKSENT:
526         /* They didn't agree to what we wanted - try another request */
527         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
528         fsm_sconfreq(f, 0);             /* Send Configure-Request */
529         break;
530
531     case ACKRCVD:
532         /* Got a Nak/reject when we had already had an Ack?? oh well... */
533         fsm_sconfreq(f, 0);
534         f->state = REQSENT;
535         break;
536
537     case OPENED:
538         /* Go down and restart negotiation */
539         if (f->callbacks->down)
540             (*f->callbacks->down)(f);   /* Inform upper layers */
541         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
542         f->state = REQSENT;
543         break;
544     }
545 }
546
547
548 /*
549  * fsm_rtermreq - Receive Terminate-Req.
550  */
551 static void
552 fsm_rtermreq(f, id)
553     fsm *f;
554     int id;
555 {
556     FSMDEBUG((LOG_INFO, "fsm_rtermreq(%s): Rcvd id %d.",
557               PROTO_NAME(f), id));
558
559     fsm_sdata(f, TERMACK, id, NULL, 0);
560     switch (f->state) {
561     case ACKRCVD:
562     case ACKSENT:
563         f->state = REQSENT;             /* Start over but keep trying */
564         break;
565
566     case OPENED:
567         syslog(LOG_INFO, "%s terminated at peer's request", PROTO_NAME(f));
568         if (f->callbacks->down)
569             (*f->callbacks->down)(f);   /* Inform upper layers */
570         f->retransmits = 0;
571         f->state = STOPPING;
572         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
573         break;
574     }
575 }
576
577
578 /*
579  * fsm_rtermack - Receive Terminate-Ack.
580  */
581 static void
582 fsm_rtermack(f)
583     fsm *f;
584 {
585     FSMDEBUG((LOG_INFO, "fsm_rtermack(%s).", PROTO_NAME(f)));
586
587     switch (f->state) {
588     case CLOSING:
589         f->state = CLOSED;
590         if( f->callbacks->finished )
591             (*f->callbacks->finished)(f);
592         break;
593     case STOPPING:
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  */
615 static void
616 fsm_rcoderej(f, inp, len)
617     fsm *f;
618     u_char *inp;
619     int len;
620 {
621     u_char code, id;
622
623     FSMDEBUG((LOG_INFO, "fsm_rcoderej(%s).", PROTO_NAME(f)));
624
625     if (len < HEADERLEN) {
626         FSMDEBUG((LOG_INFO, "fsm_rcoderej: Rcvd short Code-Reject packet!"));
627         return;
628     }
629     GETCHAR(code, inp);
630     GETCHAR(id, inp);
631     syslog(LOG_WARNING, "%s: Rcvd Code-Reject for code %d, id %d",
632            PROTO_NAME(f), code, id);
633
634     if( f->state == ACKRCVD )
635         f->state = REQSENT;
636 }
637
638
639 /*
640  * fsm_protreject - Peer doesn't speak this protocol.
641  *
642  * Treat this as a catastrophic error (RXJ-).
643  */
644 void
645 fsm_protreject(f)
646     fsm *f;
647 {
648     switch( f->state ){
649     case CLOSING:
650         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
651         /* fall through */
652     case CLOSED:
653         f->state = CLOSED;
654         if( f->callbacks->finished )
655             (*f->callbacks->finished)(f);
656         break;
657
658     case STOPPING:
659     case REQSENT:
660     case ACKRCVD:
661     case ACKSENT:
662         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
663         /* fall through */
664     case STOPPED:
665         f->state = STOPPED;
666         if( f->callbacks->finished )
667             (*f->callbacks->finished)(f);
668         break;
669
670     case OPENED:
671         if( f->callbacks->down )
672             (*f->callbacks->down)(f);
673
674         /* Init restart counter, send Terminate-Request */
675         f->retransmits = f->maxtermtransmits;
676         fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
677         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
678         --f->retransmits;
679
680         f->state = STOPPING;
681         break;
682
683     default:
684         FSMDEBUG((LOG_INFO, "%s: Protocol-reject event in state %d!",
685                   PROTO_NAME(f), f->state));
686     }
687 }
688
689
690 /*
691  * fsm_sconfreq - Send a Configure-Request.
692  */
693 static void
694 fsm_sconfreq(f, retransmit)
695     fsm *f;
696     int retransmit;
697 {
698     u_char *outp;
699     int outlen, cilen;
700
701     if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
702         /* Not currently negotiating - reset options */
703         if( f->callbacks->resetci )
704             (*f->callbacks->resetci)(f);
705         f->nakloops = 0;
706     }
707
708     if( !retransmit ){
709         /* New request - reset retransmission counter, use new ID */
710         f->retransmits = f->maxconfreqtransmits;
711         f->reqid = ++f->id;
712     }
713
714     /*
715      * Make up the request packet
716      */
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         outp = outpacket_buf + DLLHEADERLEN + 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, (caddr_t) f, f->timeouttime);
733
734     FSMDEBUG((LOG_INFO, "%s: sending Configure-Request, id %d",
735               PROTO_NAME(f), f->reqid));
736 }
737
738
739 /*
740  * fsm_sdata - Send some data.
741  *
742  * Used for all packets sent to our peer by this module.
743  */
744 void
745 fsm_sdata(f, code, id, data, datalen)
746     fsm *f;
747     u_char code, id;
748     u_char *data;
749     int datalen;
750 {
751     u_char *outp;
752     int outlen;
753
754     /* Adjust length to be smaller than MTU */
755     outp = outpacket_buf;
756     if (datalen > peer_mru[f->unit] - HEADERLEN)
757         datalen = peer_mru[f->unit] - HEADERLEN;
758     if (datalen && data != outp + DLLHEADERLEN + HEADERLEN)
759         BCOPY(data, outp + DLLHEADERLEN + HEADERLEN, datalen);
760     outlen = datalen + HEADERLEN;
761     MAKEHEADER(outp, f->protocol);
762     PUTCHAR(code, outp);
763     PUTCHAR(id, outp);
764     PUTSHORT(outlen, outp);
765     output(f->unit, outpacket_buf, outlen + DLLHEADERLEN);
766
767     FSMDEBUG((LOG_INFO, "fsm_sdata(%s): Sent code %d, id %d.",
768               PROTO_NAME(f), code, id));
769 }