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