1 /* $NetBSD: fsm.c,v 1.6 2025/01/08 19:59:39 christos Exp $ */ 2 3 /* 4 * fsm.c - {Link, IP} Control Protocol Finite State Machine. 5 * 6 * Copyright (c) 1984-2000 Carnegie Mellon University. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in 17 * the documentation and/or other materials provided with the 18 * distribution. 19 * 20 * 3. The name "Carnegie Mellon University" must not be used to 21 * endorse or promote products derived from this software without 22 * prior written permission. For permission or any legal 23 * details, please contact 24 * Office of Technology Transfer 25 * Carnegie Mellon University 26 * 5000 Forbes Avenue 27 * Pittsburgh, PA 15213-3890 28 * (412) 268-4387, fax: (412) 268-7395 29 * tech-transfer@andrew.cmu.edu 30 * 31 * 4. Redistributions of any form whatsoever must retain the following 32 * acknowledgment: 33 * "This product includes software developed by Computing Services 34 * at Carnegie Mellon University (http://www.cmu.edu/computing/)." 35 * 36 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO 37 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 38 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE 39 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 40 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 41 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 42 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 43 */ 44 45 #include <sys/cdefs.h> 46 __RCSID("$NetBSD: fsm.c,v 1.6 2025/01/08 19:59:39 christos Exp $"); 47 48 #ifdef HAVE_CONFIG_H 49 #include "config.h" 50 #endif 51 52 /* 53 * TODO: 54 * Randomize fsm id on link/init. 55 * Deal with variable outgoing MTU. 56 */ 57 58 #include <stdio.h> 59 #include <string.h> 60 #include <sys/types.h> 61 62 #include "pppd-private.h" 63 #include "fsm.h" 64 65 66 static void fsm_timeout (void *); 67 static void fsm_rconfreq (fsm *, int, u_char *, int); 68 static void fsm_rconfack (fsm *, int, u_char *, int); 69 static void fsm_rconfnakrej (fsm *, int, int, u_char *, int); 70 static void fsm_rtermreq (fsm *, int, u_char *, int); 71 static void fsm_rtermack (fsm *); 72 static void fsm_rcoderej (fsm *, u_char *, int); 73 static void fsm_sconfreq (fsm *, int); 74 75 #define PROTO_NAME(f) ((f)->callbacks->proto_name) 76 77 int peer_mru[NUM_PPP]; 78 79 80 /* 81 * fsm_init - Initialize fsm. 82 * 83 * Initialize fsm state. 84 */ 85 void 86 fsm_init(fsm *f) 87 { 88 f->state = INITIAL; 89 f->flags = 0; 90 f->id = 0; /* XXX Start with random id? */ 91 f->timeouttime = DEFTIMEOUT; 92 f->maxconfreqtransmits = DEFMAXCONFREQS; 93 f->maxtermtransmits = DEFMAXTERMREQS; 94 f->maxnakloops = DEFMAXNAKLOOPS; 95 f->term_reason_len = 0; 96 } 97 98 99 /* 100 * fsm_lowerup - The lower layer is up. 101 */ 102 void 103 fsm_lowerup(fsm *f) 104 { 105 switch( f->state ){ 106 case INITIAL: 107 f->state = CLOSED; 108 break; 109 110 case STARTING: 111 if( f->flags & OPT_SILENT ) 112 f->state = STOPPED; 113 else { 114 /* Send an initial configure-request */ 115 fsm_sconfreq(f, 0); 116 f->state = REQSENT; 117 } 118 break; 119 120 default: 121 FSMDEBUG(("%s: Up event in state %d!", PROTO_NAME(f), f->state)); 122 } 123 } 124 125 126 /* 127 * fsm_lowerdown - The lower layer is down. 128 * 129 * Cancel all timeouts and inform upper layers. 130 */ 131 void 132 fsm_lowerdown(fsm *f) 133 { 134 switch( f->state ){ 135 case CLOSED: 136 f->state = INITIAL; 137 break; 138 139 case STOPPED: 140 f->state = STARTING; 141 if( f->callbacks->starting ) 142 (*f->callbacks->starting)(f); 143 break; 144 145 case CLOSING: 146 f->state = INITIAL; 147 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 148 break; 149 150 case STOPPING: 151 case REQSENT: 152 case ACKRCVD: 153 case ACKSENT: 154 f->state = STARTING; 155 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 156 break; 157 158 case OPENED: 159 if( f->callbacks->down ) 160 (*f->callbacks->down)(f); 161 f->state = STARTING; 162 break; 163 164 default: 165 FSMDEBUG(("%s: Down event in state %d!", PROTO_NAME(f), f->state)); 166 } 167 } 168 169 170 /* 171 * fsm_open - Link is allowed to come up. 172 */ 173 void 174 fsm_open(fsm *f) 175 { 176 switch( f->state ){ 177 case INITIAL: 178 f->state = STARTING; 179 if( f->callbacks->starting ) 180 (*f->callbacks->starting)(f); 181 break; 182 183 case CLOSED: 184 if( f->flags & OPT_SILENT ) 185 f->state = STOPPED; 186 else { 187 /* Send an initial configure-request */ 188 fsm_sconfreq(f, 0); 189 f->state = REQSENT; 190 } 191 break; 192 193 case CLOSING: 194 f->state = STOPPING; 195 /* fall through */ 196 case STOPPED: 197 case OPENED: 198 if( f->flags & OPT_RESTART ){ 199 fsm_lowerdown(f); 200 fsm_lowerup(f); 201 } 202 break; 203 } 204 } 205 206 /* 207 * terminate_layer - Start process of shutting down the FSM 208 * 209 * Cancel any timeout running, notify upper layers we're done, and 210 * send a terminate-request message as configured. 211 */ 212 static void 213 terminate_layer(fsm *f, int nextstate) 214 { 215 if( f->state != OPENED ) 216 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 217 else if( f->callbacks->down ) 218 (*f->callbacks->down)(f); /* Inform upper layers we're down */ 219 220 /* Init restart counter and send Terminate-Request */ 221 f->retransmits = f->maxtermtransmits; 222 fsm_sdata(f, TERMREQ, f->reqid = ++f->id, 223 (u_char *) f->term_reason, f->term_reason_len); 224 225 if (f->retransmits == 0) { 226 /* 227 * User asked for no terminate requests at all; just close it. 228 * We've already fired off one Terminate-Request just to be nice 229 * to the peer, but we're not going to wait for a reply. 230 */ 231 f->state = nextstate == CLOSING ? CLOSED : STOPPED; 232 if( f->callbacks->finished ) 233 (*f->callbacks->finished)(f); 234 return; 235 } 236 237 TIMEOUT(fsm_timeout, f, f->timeouttime); 238 --f->retransmits; 239 240 f->state = nextstate; 241 } 242 243 /* 244 * fsm_close - Start closing connection. 245 * 246 * Cancel timeouts and either initiate close or possibly go directly to 247 * the CLOSED state. 248 */ 249 void 250 fsm_close(fsm *f, char *reason) 251 { 252 f->term_reason = reason; 253 f->term_reason_len = (reason == NULL? 0: strlen(reason)); 254 switch( f->state ){ 255 case STARTING: 256 f->state = INITIAL; 257 break; 258 case STOPPED: 259 f->state = CLOSED; 260 break; 261 case STOPPING: 262 f->state = CLOSING; 263 break; 264 265 case REQSENT: 266 case ACKRCVD: 267 case ACKSENT: 268 case OPENED: 269 terminate_layer(f, CLOSING); 270 break; 271 } 272 } 273 274 275 /* 276 * fsm_timeout - Timeout expired. 277 */ 278 static void 279 fsm_timeout(void *arg) 280 { 281 fsm *f = (fsm *) arg; 282 283 switch (f->state) { 284 case CLOSING: 285 case STOPPING: 286 if( f->retransmits <= 0 ){ 287 /* 288 * We've waited for an ack long enough. Peer probably heard us. 289 */ 290 f->state = (f->state == CLOSING)? CLOSED: STOPPED; 291 if( f->callbacks->finished ) 292 (*f->callbacks->finished)(f); 293 } else { 294 /* Send Terminate-Request */ 295 fsm_sdata(f, TERMREQ, f->reqid = ++f->id, 296 (u_char *) f->term_reason, f->term_reason_len); 297 TIMEOUT(fsm_timeout, f, f->timeouttime); 298 --f->retransmits; 299 } 300 break; 301 302 case REQSENT: 303 case ACKRCVD: 304 case ACKSENT: 305 if (f->retransmits <= 0) { 306 warn("%s: timeout sending Config-Requests", PROTO_NAME(f)); 307 f->state = STOPPED; 308 if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished ) 309 (*f->callbacks->finished)(f); 310 311 } else { 312 /* Retransmit the configure-request */ 313 if (f->callbacks->retransmit) 314 (*f->callbacks->retransmit)(f); 315 fsm_sconfreq(f, 1); /* Re-send Configure-Request */ 316 if( f->state == ACKRCVD ) 317 f->state = REQSENT; 318 } 319 break; 320 321 default: 322 FSMDEBUG(("%s: Timeout event in state %d!", PROTO_NAME(f), f->state)); 323 } 324 } 325 326 327 /* 328 * fsm_input - Input packet. 329 */ 330 void 331 fsm_input(fsm *f, u_char *inpacket, int l) 332 { 333 u_char *inp; 334 u_char code, id; 335 int len; 336 337 /* 338 * Parse header (code, id and length). 339 * If packet too short, drop it. 340 */ 341 inp = inpacket; 342 if (l < HEADERLEN) { 343 FSMDEBUG(("fsm_input(%x): Rcvd short header.", f->protocol)); 344 return; 345 } 346 GETCHAR(code, inp); 347 GETCHAR(id, inp); 348 GETSHORT(len, inp); 349 if (len < HEADERLEN) { 350 FSMDEBUG(("fsm_input(%x): Rcvd illegal length.", f->protocol)); 351 return; 352 } 353 if (len > l) { 354 FSMDEBUG(("fsm_input(%x): Rcvd short packet.", f->protocol)); 355 return; 356 } 357 len -= HEADERLEN; /* subtract header length */ 358 359 if( f->state == INITIAL || f->state == STARTING ){ 360 FSMDEBUG(("fsm_input(%x): Rcvd packet in state %d.", 361 f->protocol, f->state)); 362 return; 363 } 364 365 /* 366 * Action depends on code. 367 */ 368 switch (code) { 369 case CONFREQ: 370 fsm_rconfreq(f, id, inp, len); 371 break; 372 373 case CONFACK: 374 fsm_rconfack(f, id, inp, len); 375 break; 376 377 case CONFNAK: 378 case CONFREJ: 379 fsm_rconfnakrej(f, code, id, inp, len); 380 break; 381 382 case TERMREQ: 383 fsm_rtermreq(f, id, inp, len); 384 break; 385 386 case TERMACK: 387 fsm_rtermack(f); 388 break; 389 390 case CODEREJ: 391 fsm_rcoderej(f, inp, len); 392 break; 393 394 default: 395 if( !f->callbacks->extcode 396 || !(*f->callbacks->extcode)(f, code, id, inp, len) ) 397 fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN); 398 break; 399 } 400 } 401 402 403 /* 404 * fsm_rconfreq - Receive Configure-Request. 405 */ 406 static void 407 fsm_rconfreq(fsm *f, int id, u_char *inp, int len) 408 { 409 int code, reject_if_disagree; 410 411 switch( f->state ){ 412 case CLOSED: 413 /* Go away, we're closed */ 414 fsm_sdata(f, TERMACK, id, NULL, 0); 415 return; 416 case CLOSING: 417 case STOPPING: 418 return; 419 420 case OPENED: 421 /* Go down and restart negotiation */ 422 if( f->callbacks->down ) 423 (*f->callbacks->down)(f); /* Inform upper layers */ 424 fsm_sconfreq(f, 0); /* Send initial Configure-Request */ 425 f->state = REQSENT; 426 break; 427 428 case STOPPED: 429 /* Negotiation started by our peer */ 430 fsm_sconfreq(f, 0); /* Send initial Configure-Request */ 431 f->state = REQSENT; 432 break; 433 } 434 435 /* 436 * Pass the requested configuration options 437 * to protocol-specific code for checking. 438 */ 439 if (f->callbacks->reqci){ /* Check CI */ 440 reject_if_disagree = (f->nakloops >= f->maxnakloops); 441 code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree); 442 } else if (len) 443 code = CONFREJ; /* Reject all CI */ 444 else 445 code = CONFACK; 446 447 /* send the Ack, Nak or Rej to the peer */ 448 fsm_sdata(f, code, id, inp, len); 449 450 if (code == CONFACK) { 451 if (f->state == ACKRCVD) { 452 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 453 f->state = OPENED; 454 if (f->callbacks->up) 455 (*f->callbacks->up)(f); /* Inform upper layers */ 456 } else 457 f->state = ACKSENT; 458 f->nakloops = 0; 459 460 } else { 461 /* we sent CONFNAK or CONFREJ */ 462 if (f->state != ACKRCVD) 463 f->state = REQSENT; 464 if( code == CONFNAK ) 465 ++f->nakloops; 466 } 467 } 468 469 470 /* 471 * fsm_rconfack - Receive Configure-Ack. 472 */ 473 static void 474 fsm_rconfack(fsm *f, int id, u_char *inp, int len) 475 { 476 if (id != f->reqid || f->seen_ack) /* Expected id? */ 477 return; /* Nope, toss... */ 478 if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len): 479 (len == 0)) ){ 480 /* Ack is bad - ignore it */ 481 error("Received bad configure-ack: %P", inp, len); 482 return; 483 } 484 f->seen_ack = 1; 485 f->rnakloops = 0; 486 487 switch (f->state) { 488 case CLOSED: 489 case STOPPED: 490 fsm_sdata(f, TERMACK, id, NULL, 0); 491 break; 492 493 case REQSENT: 494 f->state = ACKRCVD; 495 f->retransmits = f->maxconfreqtransmits; 496 break; 497 498 case ACKRCVD: 499 /* Huh? an extra valid Ack? oh well... */ 500 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 501 fsm_sconfreq(f, 0); 502 f->state = REQSENT; 503 break; 504 505 case ACKSENT: 506 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 507 f->state = OPENED; 508 f->retransmits = f->maxconfreqtransmits; 509 if (f->callbacks->up) 510 (*f->callbacks->up)(f); /* Inform upper layers */ 511 break; 512 513 case OPENED: 514 /* Go down and restart negotiation */ 515 if (f->callbacks->down) 516 (*f->callbacks->down)(f); /* Inform upper layers */ 517 fsm_sconfreq(f, 0); /* Send initial Configure-Request */ 518 f->state = REQSENT; 519 break; 520 } 521 } 522 523 524 /* 525 * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject. 526 */ 527 static void 528 fsm_rconfnakrej(fsm *f, int code, int id, u_char *inp, int len) 529 { 530 int ret; 531 int treat_as_reject; 532 533 if (id != f->reqid || f->seen_ack) /* Expected id? */ 534 return; /* Nope, toss... */ 535 536 if (code == CONFNAK) { 537 ++f->rnakloops; 538 treat_as_reject = (f->rnakloops >= f->maxnakloops); 539 if (f->callbacks->nakci == NULL 540 || !(ret = f->callbacks->nakci(f, inp, len, treat_as_reject))) { 541 error("Received bad configure-nak: %P", inp, len); 542 return; 543 } 544 } else { 545 f->rnakloops = 0; 546 if (f->callbacks->rejci == NULL 547 || !(ret = f->callbacks->rejci(f, inp, len))) { 548 error("Received bad configure-rej: %P", inp, len); 549 return; 550 } 551 } 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(fsm *f, int id, u_char *p, int len) 594 { 595 switch (f->state) { 596 case ACKRCVD: 597 case ACKSENT: 598 f->state = REQSENT; /* Start over but keep trying */ 599 break; 600 601 case OPENED: 602 if (len > 0) { 603 info("%s terminated by peer (%0.*v)", PROTO_NAME(f), len, p); 604 } else 605 info("%s terminated by peer", PROTO_NAME(f)); 606 f->retransmits = 0; 607 f->state = STOPPING; 608 if (f->callbacks->down) 609 (*f->callbacks->down)(f); /* Inform upper layers */ 610 TIMEOUT(fsm_timeout, f, f->timeouttime); 611 break; 612 } 613 614 fsm_sdata(f, TERMACK, id, NULL, 0); 615 } 616 617 618 /* 619 * fsm_rtermack - Receive Terminate-Ack. 620 */ 621 static void 622 fsm_rtermack(fsm *f) 623 { 624 switch (f->state) { 625 case CLOSING: 626 UNTIMEOUT(fsm_timeout, f); 627 f->state = CLOSED; 628 if( f->callbacks->finished ) 629 (*f->callbacks->finished)(f); 630 break; 631 case STOPPING: 632 UNTIMEOUT(fsm_timeout, f); 633 f->state = STOPPED; 634 if( f->callbacks->finished ) 635 (*f->callbacks->finished)(f); 636 break; 637 638 case ACKRCVD: 639 f->state = REQSENT; 640 break; 641 642 case OPENED: 643 if (f->callbacks->down) 644 (*f->callbacks->down)(f); /* Inform upper layers */ 645 fsm_sconfreq(f, 0); 646 f->state = REQSENT; 647 break; 648 } 649 } 650 651 652 /* 653 * fsm_rcoderej - Receive an Code-Reject. 654 */ 655 static void 656 fsm_rcoderej(fsm *f, u_char *inp, int len) 657 { 658 u_char code, id; 659 660 if (len < HEADERLEN) { 661 FSMDEBUG(("fsm_rcoderej: Rcvd short Code-Reject packet!")); 662 return; 663 } 664 GETCHAR(code, inp); 665 GETCHAR(id, inp); 666 warn("%s: Rcvd Code-Reject for code %d, id %d", PROTO_NAME(f), code, id); 667 668 if( f->state == ACKRCVD ) 669 f->state = REQSENT; 670 } 671 672 673 /* 674 * fsm_protreject - Peer doesn't speak this protocol. 675 * 676 * Treat this as a catastrophic error (RXJ-). 677 */ 678 void 679 fsm_protreject(fsm *f) 680 { 681 switch( f->state ){ 682 case CLOSING: 683 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 684 /* fall through */ 685 case CLOSED: 686 f->state = CLOSED; 687 if( f->callbacks->finished ) 688 (*f->callbacks->finished)(f); 689 break; 690 691 case STOPPING: 692 case REQSENT: 693 case ACKRCVD: 694 case ACKSENT: 695 UNTIMEOUT(fsm_timeout, f); /* Cancel timeout */ 696 /* fall through */ 697 case STOPPED: 698 f->state = STOPPED; 699 if( f->callbacks->finished ) 700 (*f->callbacks->finished)(f); 701 break; 702 703 case OPENED: 704 terminate_layer(f, STOPPING); 705 break; 706 707 default: 708 FSMDEBUG(("%s: Protocol-reject event in state %d!", 709 PROTO_NAME(f), f->state)); 710 } 711 } 712 713 714 /* 715 * fsm_sconfreq - Send a Configure-Request. 716 */ 717 static void 718 fsm_sconfreq(fsm *f, int retransmit) 719 { 720 u_char *outp; 721 int cilen; 722 723 if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){ 724 /* Not currently negotiating - reset options */ 725 if( f->callbacks->resetci ) 726 (*f->callbacks->resetci)(f); 727 f->nakloops = 0; 728 f->rnakloops = 0; 729 } 730 731 if( !retransmit ){ 732 /* New request - reset retransmission counter, use new ID */ 733 f->retransmits = f->maxconfreqtransmits; 734 f->reqid = ++f->id; 735 } 736 737 f->seen_ack = 0; 738 739 /* 740 * Make up the request packet 741 */ 742 outp = outpacket_buf + PPP_HDRLEN + HEADERLEN; 743 if( f->callbacks->cilen && f->callbacks->addci ){ 744 cilen = (*f->callbacks->cilen)(f); 745 if( cilen > peer_mru[f->unit] - HEADERLEN ) 746 cilen = peer_mru[f->unit] - HEADERLEN; 747 if (f->callbacks->addci) 748 (*f->callbacks->addci)(f, outp, &cilen); 749 } else 750 cilen = 0; 751 752 /* send the request to our peer */ 753 fsm_sdata(f, CONFREQ, f->reqid, outp, cilen); 754 755 /* start the retransmit timer */ 756 --f->retransmits; 757 TIMEOUT(fsm_timeout, f, f->timeouttime); 758 } 759 760 761 /* 762 * fsm_sdata - Send some data. 763 * 764 * Used for all packets sent to our peer by this module. 765 */ 766 void 767 fsm_sdata(fsm *f, int code, int id, u_char *data, int datalen) 768 { 769 u_char *outp; 770 int outlen; 771 772 /* Adjust length to be smaller than MTU */ 773 outp = outpacket_buf; 774 if (datalen > peer_mru[f->unit] - HEADERLEN) 775 datalen = peer_mru[f->unit] - HEADERLEN; 776 if (datalen && data != outp + PPP_HDRLEN + HEADERLEN) 777 BCOPY(data, outp + PPP_HDRLEN + HEADERLEN, datalen); 778 outlen = datalen + HEADERLEN; 779 MAKEHEADER(outp, f->protocol); 780 PUTCHAR(code, outp); 781 PUTCHAR(id, outp); 782 PUTSHORT(outlen, outp); 783 output(f->unit, outpacket_buf, outlen + PPP_HDRLEN); 784 } 785