a11922a31df12448e8b3729614ca155c9b257f8a
[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.5 1994/09/21 06:47:37 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 <syslog.h>
33
34 #include "pppd.h"
35 #include "fsm.h"
36
37 extern char *proto_name();
38
39 static void fsm_timeout __P((caddr_t));
40 static void fsm_rconfreq __P((fsm *, int, u_char *, int));
41 static void fsm_rconfack __P((fsm *, int, u_char *, int));
42 static void fsm_rconfnakrej __P((fsm *, int, int, u_char *, int));
43 static void fsm_rtermreq __P((fsm *, int));
44 static void fsm_rtermack __P((fsm *));
45 static void fsm_rcoderej __P((fsm *, u_char *, int));
46 static void fsm_sconfreq __P((fsm *, int));
47
48 #define PROTO_NAME(f)   ((f)->callbacks->proto_name)
49
50 int peer_mru[N_PPP];
51
52
53 /*
54  * fsm_init - Initialize fsm.
55  *
56  * Initialize fsm state.
57  */
58 void
59 fsm_init(f)
60     fsm *f;
61 {
62     f->state = INITIAL;
63     f->flags = 0;
64     f->id = 0;                          /* XXX Start with random id? */
65     f->timeouttime = DEFTIMEOUT;
66     f->maxconfreqtransmits = DEFMAXCONFREQS;
67     f->maxtermtransmits = DEFMAXTERMREQS;
68     f->maxnakloops = DEFMAXNAKLOOPS;
69 }
70
71
72 /*
73  * fsm_lowerup - The lower layer is up.
74  */
75 void
76 fsm_lowerup(f)
77     fsm *f;
78 {
79     switch( f->state ){
80     case INITIAL:
81         f->state = CLOSED;
82         break;
83
84     case STARTING:
85         if( f->flags & OPT_SILENT )
86             f->state = STOPPED;
87         else {
88             /* Send an initial configure-request */
89             fsm_sconfreq(f, 0);
90             f->state = REQSENT;
91         }
92         break;
93
94     default:
95         FSMDEBUG((LOG_INFO, "%s: Up event in state %d!",
96                   PROTO_NAME(f), f->state));
97     }
98 }
99
100
101 /*
102  * fsm_lowerdown - The lower layer is down.
103  *
104  * Cancel all timeouts and inform upper layers.
105  */
106 void
107 fsm_lowerdown(f)
108     fsm *f;
109 {
110     switch( f->state ){
111     case CLOSED:
112         f->state = INITIAL;
113         break;
114
115     case STOPPED:
116         f->state = STARTING;
117         if( f->callbacks->starting )
118             (*f->callbacks->starting)(f);
119         break;
120
121     case CLOSING:
122         f->state = INITIAL;
123         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
124         break;
125
126     case STOPPING:
127     case REQSENT:
128     case ACKRCVD:
129     case ACKSENT:
130         f->state = STARTING;
131         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
132         break;
133
134     case OPENED:
135         if( f->callbacks->down )
136             (*f->callbacks->down)(f);
137         f->state = STARTING;
138         break;
139
140     default:
141         FSMDEBUG((LOG_INFO, "%s: Down event in state %d!",
142                   PROTO_NAME(f), f->state));
143     }
144 }
145
146
147 /*
148  * fsm_open - Link is allowed to come up.
149  */
150 void
151 fsm_open(f)
152     fsm *f;
153 {
154     switch( f->state ){
155     case INITIAL:
156         f->state = STARTING;
157         if( f->callbacks->starting )
158             (*f->callbacks->starting)(f);
159         break;
160
161     case CLOSED:
162         if( f->flags & OPT_SILENT )
163             f->state = STOPPED;
164         else {
165             /* Send an initial configure-request */
166             fsm_sconfreq(f, 0);
167             f->state = REQSENT;
168         }
169         break;
170
171     case CLOSING:
172         f->state = STOPPING;
173         /* fall through */
174     case STOPPED:
175     case OPENED:
176         if( f->flags & OPT_RESTART ){
177             fsm_lowerdown(f);
178             fsm_lowerup(f);
179         }
180         break;
181     }
182 }
183
184
185 /*
186  * fsm_close - Start closing connection.
187  *
188  * Cancel timeouts and either initiate close or possibly go directly to
189  * the CLOSED state.
190  */
191 void
192 fsm_close(f)
193     fsm *f;
194 {
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, (caddr_t) 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, NULL, 0);
218         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
219         --f->retransmits;
220
221         f->state = CLOSING;
222         break;
223     }
224 }
225
226
227 /*
228  * fsm_timeout - Timeout expired.
229  */
230 static void
231 fsm_timeout(arg)
232     caddr_t arg;
233 {
234     fsm *f = (fsm *) arg;
235
236     switch (f->state) {
237     case CLOSING:
238     case STOPPING:
239         if( f->retransmits <= 0 ){
240             /*
241              * We've waited for an ack long enough.  Peer probably heard us.
242              */
243             f->state = (f->state == CLOSING)? CLOSED: STOPPED;
244             if( f->callbacks->finished )
245                 (*f->callbacks->finished)(f);
246         } else {
247             /* Send Terminate-Request */
248             fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
249             TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
250             --f->retransmits;
251         }
252         break;
253
254     case REQSENT:
255     case ACKRCVD:
256     case ACKSENT:
257         if (f->retransmits <= 0) {
258             syslog(LOG_WARNING, "%s: timeout sending Config-Requests",
259                    PROTO_NAME(f));
260             f->state = STOPPED;
261             if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
262                 (*f->callbacks->finished)(f);
263
264         } else {
265             /* Retransmit the configure-request */
266             if (f->callbacks->retransmit)
267                 (*f->callbacks->retransmit)(f);
268             fsm_sconfreq(f, 1);         /* Re-send Configure-Request */
269             if( f->state == ACKRCVD )
270                 f->state = REQSENT;
271         }
272         break;
273
274     default:
275         FSMDEBUG((LOG_INFO, "%s: Timeout event in state %d!",
276                   PROTO_NAME(f), f->state));
277     }
278 }
279
280
281 /*
282  * fsm_input - Input packet.
283  */
284 void
285 fsm_input(f, inpacket, l)
286     fsm *f;
287     u_char *inpacket;
288     int l;
289 {
290     u_char *inp, *outp;
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((LOG_WARNING, "fsm_input(%x): Rcvd short header.",
301                   f->protocol));
302         return;
303     }
304     GETCHAR(code, inp);
305     GETCHAR(id, inp);
306     GETSHORT(len, inp);
307     if (len < HEADERLEN) {
308         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd illegal length.",
309                   f->protocol));
310         return;
311     }
312     if (len > l) {
313         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd short packet.",
314                   f->protocol));
315         return;
316     }
317     len -= HEADERLEN;           /* subtract header length */
318
319     if( f->state == INITIAL || f->state == STARTING ){
320         FSMDEBUG((LOG_INFO, "fsm_input(%x): Rcvd packet in state %d.",
321                   f->protocol, f->state));
322         return;
323     }
324
325     /*
326      * Action depends on code.
327      */
328     switch (code) {
329     case CONFREQ:
330         fsm_rconfreq(f, id, inp, len);
331         break;
332     
333     case CONFACK:
334         fsm_rconfack(f, id, inp, len);
335         break;
336     
337     case CONFNAK:
338     case CONFREJ:
339         fsm_rconfnakrej(f, code, id, inp, len);
340         break;
341     
342     case TERMREQ:
343         fsm_rtermreq(f, id);
344         break;
345     
346     case TERMACK:
347         fsm_rtermack(f);
348         break;
349     
350     case CODEREJ:
351         fsm_rcoderej(f, inp, len);
352         break;
353     
354     default:
355         if( !f->callbacks->extcode
356            || !(*f->callbacks->extcode)(f, code, id, inp, len) )
357             fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
358         break;
359     }
360 }
361
362
363 /*
364  * fsm_rconfreq - Receive Configure-Request.
365  */
366 static void
367 fsm_rconfreq(f, id, inp, len)
368     fsm *f;
369     u_char id;
370     u_char *inp;
371     int len;
372 {
373     u_char *outp;
374     int code, reject_if_disagree;
375
376     FSMDEBUG((LOG_INFO, "fsm_rconfreq(%s): Rcvd id %d.", PROTO_NAME(f), id));
377     switch( f->state ){
378     case CLOSED:
379         /* Go away, we're closed */
380         fsm_sdata(f, TERMACK, id, NULL, 0);
381         return;
382     case CLOSING:
383     case STOPPING:
384         return;
385
386     case OPENED:
387         /* Go down and restart negotiation */
388         if( f->callbacks->down )
389             (*f->callbacks->down)(f);   /* Inform upper layers */
390         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
391         break;
392
393     case STOPPED:
394         /* Negotiation started by our peer */
395         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
396         f->state = REQSENT;
397         break;
398     }
399
400     /*
401      * Pass the requested configuration options
402      * to protocol-specific code for checking.
403      */
404     if (f->callbacks->reqci){           /* Check CI */
405         reject_if_disagree = (f->nakloops >= f->maxnakloops);
406         code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
407     } else if (len)
408         code = CONFREJ;                 /* Reject all CI */
409     else
410         code = CONFACK;
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 || f->seen_ack)          /* Expected id? */
449         return;                                 /* Nope, toss... */
450     if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
451           (len == 0)) ){
452         /* Ack is bad - ignore it */
453         FSMDEBUG((LOG_INFO, "%s: received bad Ack (length %d)",
454                   PROTO_NAME(f), len));
455         return;
456     }
457     f->seen_ack = 1;
458
459     switch (f->state) {
460     case CLOSED:
461     case STOPPED:
462         fsm_sdata(f, TERMACK, id, NULL, 0);
463         break;
464
465     case REQSENT:
466         f->state = ACKRCVD;
467         f->retransmits = f->maxconfreqtransmits;
468         break;
469
470     case ACKRCVD:
471         /* Huh? an extra valid Ack? oh well... */
472         fsm_sconfreq(f, 0);
473         f->state = REQSENT;
474         break;
475
476     case ACKSENT:
477         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
478         f->state = OPENED;
479         f->retransmits = f->maxconfreqtransmits;
480         if (f->callbacks->up)
481             (*f->callbacks->up)(f);     /* Inform upper layers */
482         break;
483
484     case OPENED:
485         /* Go down and restart negotiation */
486         if (f->callbacks->down)
487             (*f->callbacks->down)(f);   /* Inform upper layers */
488         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
489         f->state = REQSENT;
490         break;
491     }
492 }
493
494
495 /*
496  * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
497  */
498 static void
499 fsm_rconfnakrej(f, code, id, inp, len)
500     fsm *f;
501     int code, id;
502     u_char *inp;
503     int len;
504 {
505     int (*proc)();
506
507     FSMDEBUG((LOG_INFO, "fsm_rconfnakrej(%s): Rcvd id %d.",
508               PROTO_NAME(f), id));
509
510     if (id != f->reqid || f->seen_ack)  /* Expected id? */
511         return;                         /* Nope, toss... */
512     proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
513     if (!proc || !proc(f, inp, len)) {
514         /* Nak/reject is bad - ignore it */
515         FSMDEBUG((LOG_INFO, "%s: received bad %s (length %d)",
516                   PROTO_NAME(f), (code==CONFNAK? "Nak": "reject"), len));
517         return;
518     }
519     f->seen_ack = 1;
520
521     switch (f->state) {
522     case CLOSED:
523     case STOPPED:
524         fsm_sdata(f, TERMACK, id, NULL, 0);
525         break;
526
527     case REQSENT:
528     case ACKSENT:
529         /* They didn't agree to what we wanted - try another request */
530         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
531         fsm_sconfreq(f, 0);             /* Send Configure-Request */
532         break;
533
534     case ACKRCVD:
535         /* Got a Nak/reject when we had already had an Ack?? oh well... */
536         fsm_sconfreq(f, 0);
537         f->state = REQSENT;
538         break;
539
540     case OPENED:
541         /* Go down and restart negotiation */
542         if (f->callbacks->down)
543             (*f->callbacks->down)(f);   /* Inform upper layers */
544         fsm_sconfreq(f, 0);             /* Send initial Configure-Request */
545         f->state = REQSENT;
546         break;
547     }
548 }
549
550
551 /*
552  * fsm_rtermreq - Receive Terminate-Req.
553  */
554 static void
555 fsm_rtermreq(f, id)
556     fsm *f;
557     int id;
558 {
559     FSMDEBUG((LOG_INFO, "fsm_rtermreq(%s): Rcvd id %d.",
560               PROTO_NAME(f), id));
561
562     switch (f->state) {
563     case ACKRCVD:
564     case ACKSENT:
565         f->state = REQSENT;             /* Start over but keep trying */
566         break;
567
568     case OPENED:
569         syslog(LOG_INFO, "%s terminated at peer's request", PROTO_NAME(f));
570         if (f->callbacks->down)
571             (*f->callbacks->down)(f);   /* Inform upper layers */
572         f->retransmits = 0;
573         f->state = STOPPING;
574         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
575         break;
576     }
577
578     fsm_sdata(f, TERMACK, id, NULL, 0);
579 }
580
581
582 /*
583  * fsm_rtermack - Receive Terminate-Ack.
584  */
585 static void
586 fsm_rtermack(f)
587     fsm *f;
588 {
589     FSMDEBUG((LOG_INFO, "fsm_rtermack(%s).", PROTO_NAME(f)));
590
591     switch (f->state) {
592     case CLOSING:
593         f->state = CLOSED;
594         if( f->callbacks->finished )
595             (*f->callbacks->finished)(f);
596         break;
597     case STOPPING:
598         f->state = STOPPED;
599         if( f->callbacks->finished )
600             (*f->callbacks->finished)(f);
601         break;
602
603     case ACKRCVD:
604         f->state = REQSENT;
605         break;
606
607     case OPENED:
608         if (f->callbacks->down)
609             (*f->callbacks->down)(f);   /* Inform upper layers */
610         fsm_sconfreq(f, 0);
611         break;
612     }
613 }
614
615
616 /*
617  * fsm_rcoderej - Receive an Code-Reject.
618  */
619 static void
620 fsm_rcoderej(f, inp, len)
621     fsm *f;
622     u_char *inp;
623     int len;
624 {
625     u_char code, id;
626
627     FSMDEBUG((LOG_INFO, "fsm_rcoderej(%s).", PROTO_NAME(f)));
628
629     if (len < HEADERLEN) {
630         FSMDEBUG((LOG_INFO, "fsm_rcoderej: Rcvd short Code-Reject packet!"));
631         return;
632     }
633     GETCHAR(code, inp);
634     GETCHAR(id, inp);
635     syslog(LOG_WARNING, "%s: Rcvd Code-Reject for code %d, id %d",
636            PROTO_NAME(f), code, id);
637
638     if( f->state == ACKRCVD )
639         f->state = REQSENT;
640 }
641
642
643 /*
644  * fsm_protreject - Peer doesn't speak this protocol.
645  *
646  * Treat this as a catastrophic error (RXJ-).
647  */
648 void
649 fsm_protreject(f)
650     fsm *f;
651 {
652     switch( f->state ){
653     case CLOSING:
654         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
655         /* fall through */
656     case CLOSED:
657         f->state = CLOSED;
658         if( f->callbacks->finished )
659             (*f->callbacks->finished)(f);
660         break;
661
662     case STOPPING:
663     case REQSENT:
664     case ACKRCVD:
665     case ACKSENT:
666         UNTIMEOUT(fsm_timeout, (caddr_t) f);    /* Cancel timeout */
667         /* fall through */
668     case STOPPED:
669         f->state = STOPPED;
670         if( f->callbacks->finished )
671             (*f->callbacks->finished)(f);
672         break;
673
674     case OPENED:
675         if( f->callbacks->down )
676             (*f->callbacks->down)(f);
677
678         /* Init restart counter, send Terminate-Request */
679         f->retransmits = f->maxtermtransmits;
680         fsm_sdata(f, TERMREQ, f->reqid = ++f->id, NULL, 0);
681         TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
682         --f->retransmits;
683
684         f->state = STOPPING;
685         break;
686
687     default:
688         FSMDEBUG((LOG_INFO, "%s: Protocol-reject event in state %d!",
689                   PROTO_NAME(f), f->state));
690     }
691 }
692
693
694 /*
695  * fsm_sconfreq - Send a Configure-Request.
696  */
697 static void
698 fsm_sconfreq(f, retransmit)
699     fsm *f;
700     int retransmit;
701 {
702     u_char *outp;
703     int outlen, cilen;
704
705     if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
706         /* Not currently negotiating - reset options */
707         if( f->callbacks->resetci )
708             (*f->callbacks->resetci)(f);
709         f->nakloops = 0;
710     }
711
712     if( !retransmit ){
713         /* New request - reset retransmission counter, use new ID */
714         f->retransmits = f->maxconfreqtransmits;
715         f->reqid = ++f->id;
716     }
717
718     f->seen_ack = 0;
719
720     /*
721      * Make up the request packet
722      */
723     outp = outpacket_buf + PPP_HDRLEN + HEADERLEN;
724     if( f->callbacks->cilen && f->callbacks->addci ){
725         cilen = (*f->callbacks->cilen)(f);
726         if( cilen > peer_mru[f->unit] - HEADERLEN )
727             cilen = peer_mru[f->unit] - HEADERLEN;
728         if (f->callbacks->addci)
729             (*f->callbacks->addci)(f, outp, &cilen);
730     } else
731         cilen = 0;
732
733     /* send the request to our peer */
734     fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
735
736     /* start the retransmit timer */
737     --f->retransmits;
738     TIMEOUT(fsm_timeout, (caddr_t) f, f->timeouttime);
739
740     FSMDEBUG((LOG_INFO, "%s: sending Configure-Request, id %d",
741               PROTO_NAME(f), f->reqid));
742 }
743
744
745 /*
746  * fsm_sdata - Send some data.
747  *
748  * Used for all packets sent to our peer by this module.
749  */
750 void
751 fsm_sdata(f, code, id, data, datalen)
752     fsm *f;
753     u_char code, id;
754     u_char *data;
755     int datalen;
756 {
757     u_char *outp;
758     int outlen;
759
760     /* Adjust length to be smaller than MTU */
761     outp = outpacket_buf;
762     if (datalen > peer_mru[f->unit] - HEADERLEN)
763         datalen = peer_mru[f->unit] - HEADERLEN;
764     if (datalen && data != outp + PPP_HDRLEN + HEADERLEN)
765         BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen);
766     outlen = datalen + HEADERLEN;
767     MAKEHEADER(outp, f->protocol);
768     PUTCHAR(code, outp);
769     PUTCHAR(id, outp);
770     PUTSHORT(outlen, outp);
771     output(f->unit, outpacket_buf, outlen + PPP_HDRLEN);
772
773     FSMDEBUG((LOG_INFO, "fsm_sdata(%s): Sent code %d, id %d.",
774               PROTO_NAME(f), code, id));
775 }