xref: /netbsd-src/external/bsd/ppp/dist/pppd/fsm.c (revision f12839c5f795a8def46f685de6698463dbd213a9)
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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