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