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