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