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
fsm_init(fsm * f)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
fsm_lowerup(fsm * f)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
fsm_lowerdown(fsm * f)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
fsm_open(fsm * f)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
terminate_layer(fsm * f,int nextstate)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
fsm_close(fsm * f,char * reason)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
fsm_timeout(void * arg)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
fsm_input(fsm * f,u_char * inpacket,int l)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
fsm_rconfreq(fsm * f,int id,u_char * inp,int len)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
fsm_rconfack(fsm * f,int id,u_char * inp,int len)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
fsm_rconfnakrej(fsm * f,int code,int id,u_char * inp,int len)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
fsm_rtermreq(fsm * f,int id,u_char * p,int len)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
fsm_rtermack(fsm * f)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
fsm_rcoderej(fsm * f,u_char * inp,int len)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
fsm_protreject(fsm * f)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
fsm_sconfreq(fsm * f,int retransmit)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
fsm_sdata(fsm * f,int code,int id,u_char * data,int datalen)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