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