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