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