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