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