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