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