xref: /openbsd/usr.sbin/npppd/npppd/fsm.c (revision 76d0caae)
1 /*	$OpenBSD: fsm.c,v 1.10 2019/02/27 04:52:19 denis Exp $ */
2 
3 /**@file
4  * This file was adapted from NetBSD:/usr/src/usr.sbin/pppd/pppd/fsm.c
5  */
6 /*
7  * fsm.c is simple and it can be use without modifications.  So keep the
8  * original as much as possible.  (2005/04 yasuoka)
9  *
10  * XXX: I think the same message for initial configure-request is a bad idea
11  * XXX: on resending configure-request.(yasuoka)
12  */
13 /*	$NetBSD: fsm.c,v 1.13 2000/09/23 22:39:35 christos Exp $	*/
14 
15 /*
16  * fsm.c - {Link, IP} Control Protocol Finite State Machine.
17  *
18  * Copyright (c) 1989 Carnegie Mellon University.
19  * All rights reserved.
20  *
21  * Redistribution and use in source and binary forms are permitted
22  * provided that the above copyright notice and this paragraph are
23  * duplicated in all such forms and that any documentation,
24  * advertising materials, and other materials related to such
25  * distribution and use acknowledge that the software was developed
26  * by Carnegie Mellon University.  The name of the
27  * University may not be used to endorse or promote products derived
28  * from this software without specific prior written permission.
29  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
30  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
31  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
32  */
33 
34 /*
35  * TODO:
36  * Randomize fsm id on link/init.
37  * Deal with variable outgoing MTU.
38  */
39 
40 #include <stdio.h>
41 #include <string.h>
42 #include <sys/types.h>
43 #include <stdarg.h>
44 #include <syslog.h>
45 #include <stdlib.h>
46 
47 /* npppd related headers below */
48 #include <sys/time.h>
49 #include <sys/socket.h>
50 #include <netinet/in.h>
51 #include <net/if_dl.h>
52 #include <time.h>
53 #include <event.h>
54 #include "debugutil.h"
55 #include "npppd.h"
56 #include "fsm.h"
57 
58 #ifdef	FSM_DEBUG
59 #define	FSMDEBUG(x)	fsm_log x
60 #define	FSM_ASSERT(x)	ASSERT(x)
61 #else
62 #define	FSMDEBUG(x)
63 #define	FSM_ASSERT(x)
64 #endif
65 
66 #define	HEADERLEN	4
67 
68 #ifdef RCSID
69 static const char rcsid[] = RCSID;
70 #endif
71 
72 static void fsm_timeout(void *);
73 static void fsm_rconfreq(fsm *, int, u_char *, int);
74 static void fsm_rconfack(fsm *, int, u_char *, int);
75 static void fsm_rconfnakrej(fsm *, int, int, u_char *, int);
76 static void fsm_rtermreq(fsm *, int, u_char *, int);
77 static void fsm_rtermack(fsm *);
78 static void fsm_rcoderej(fsm *, u_char *, int);
79 static void fsm_sconfreq(fsm *, int);
80 
81 #define PROTO_NAME(f)	((f)->callbacks->proto_name)
82 
83 void
84 fsm_evtimer_timeout(int fd, short evtype, void *ctx)
85 {
86 	struct evtimer_wrap *wrap;
87 
88 	wrap = ctx;
89 	wrap->func(wrap->ctx);
90 }
91 
92 
93 /*
94  * fsm_init - Initialize fsm.
95  *
96  * Initialize fsm state.
97  */
98 void
99 fsm_init(f)
100     fsm *f;
101 {
102     f->state = INITIAL;
103     f->flags = 0;
104     f->id = 0;				/* XXX Start with random id? */
105     f->timeouttime = DEFTIMEOUT;
106     f->maxconfreqtransmits = DEFMAXCONFREQS;
107     f->maxtermtransmits = DEFMAXTERMREQS;
108     f->maxnakloops = DEFMAXNAKLOOPS;
109     f->term_reason_len = 0;
110     memset(&f->timerctx, 0, sizeof(f->timerctx));
111     f->timerctx.ctx = f;
112 }
113 
114 
115 /*
116  * fsm_lowerup - The lower layer is up.
117  */
118 void
119 fsm_lowerup(f)
120     fsm *f;
121 {
122     switch( f->state ){
123     case INITIAL:
124 	f->state = CLOSED;
125 	break;
126 
127     case STARTING:
128 	if( f->flags & OPT_SILENT )
129 	    f->state = STOPPED;
130 	else {
131 	    /* Send an initial configure-request */
132 	    fsm_sconfreq(f, 0);
133 	    f->state = REQSENT;
134 	}
135 	break;
136 
137     default:
138 	FSMDEBUG((f, LOG_DEBUG, "Up event in state %d!", f->state));
139     }
140 }
141 
142 
143 /*
144  * fsm_lowerdown - The lower layer is down.
145  *
146  * Cancel all timeouts and inform upper layers.
147  */
148 void
149 fsm_lowerdown(f)
150     fsm *f;
151 {
152     switch( f->state ){
153     case CLOSED:
154 	f->state = INITIAL;
155 	break;
156 
157     case STOPPED:
158 	f->state = STARTING;
159 	if( f->callbacks->starting )
160 	    (*f->callbacks->starting)(f);
161 	break;
162 
163     case CLOSING:
164 	f->state = INITIAL;
165 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
166 	break;
167 
168     case STOPPING:
169     case REQSENT:
170     case ACKRCVD:
171     case ACKSENT:
172 	f->state = STARTING;
173 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
174 	break;
175 
176     case OPENED:
177 	if( f->callbacks->down )
178 	    (*f->callbacks->down)(f);
179 	f->state = STARTING;
180 	break;
181 
182     default:
183 	FSMDEBUG((f, LOG_DEBUG, "Down event in state %d!", f->state));
184     }
185 }
186 
187 
188 /*
189  * fsm_open - Link is allowed to come up.
190  */
191 void
192 fsm_open(f)
193     fsm *f;
194 {
195     switch( f->state ){
196     case INITIAL:
197 	f->state = STARTING;
198 	if( f->callbacks->starting )
199 	    (*f->callbacks->starting)(f);
200 	break;
201 
202     case CLOSED:
203 	if( f->flags & OPT_SILENT )
204 	    f->state = STOPPED;
205 	else {
206 	    /* Send an initial configure-request */
207 	    fsm_sconfreq(f, 0);
208 	    f->state = REQSENT;
209 	}
210 	break;
211 
212     case CLOSING:
213 	f->state = STOPPING;
214 	/* fall through */
215     case STOPPED:
216     case OPENED:
217 	if( f->flags & OPT_RESTART ){
218 	    fsm_lowerdown(f);
219 	    fsm_lowerup(f);
220 	}
221 	break;
222     }
223 }
224 
225 
226 /*
227  * fsm_close - Start closing connection.
228  *
229  * Cancel timeouts and either initiate close or possibly go directly to
230  * the CLOSED state.
231  */
232 void
233 fsm_close(f, reason)
234     fsm *f;
235     const char *reason;
236 {
237     f->term_reason = (char *)reason;
238     f->term_reason_len = (reason == NULL? 0: strlen(reason));
239     switch( f->state ){
240     case STARTING:
241 	f->state = INITIAL;
242 	break;
243     case STOPPED:
244 	f->state = CLOSED;
245 	break;
246     case STOPPING:
247 	f->state = CLOSING;
248 	break;
249 
250     case REQSENT:
251     case ACKRCVD:
252     case ACKSENT:
253     case OPENED:
254 	if( f->state != OPENED )
255 	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
256 	else if( f->callbacks->down )
257 	    (*f->callbacks->down)(f);	/* Inform upper layers we're down */
258 
259 	/* Init restart counter, send Terminate-Request */
260 	f->retransmits = f->maxtermtransmits;
261 	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
262 		  (u_char *) f->term_reason, f->term_reason_len);
263 	TIMEOUT(fsm_timeout, f, f->timeouttime);
264 	--f->retransmits;
265 
266 	f->state = CLOSING;
267 	break;
268     }
269 }
270 
271 
272 /*
273  * fsm_timeout - Timeout expired.
274  */
275 static void
276 fsm_timeout(arg)
277     void *arg;
278 {
279     fsm *f = (fsm *) arg;
280 
281     switch (f->state) {
282     case CLOSING:
283     case STOPPING:
284 	if( f->retransmits <= 0 ){
285 	    /*
286 	     * We've waited for an ack long enough.  Peer probably heard us.
287 	     */
288 	    f->state = (f->state == CLOSING)? CLOSED: STOPPED;
289 	    if( f->callbacks->finished )
290 		(*f->callbacks->finished)(f);
291 	} else {
292 	    /* Send Terminate-Request */
293 	    fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
294 		      (u_char *) f->term_reason, f->term_reason_len);
295 	    TIMEOUT(fsm_timeout, f, f->timeouttime);
296 	    --f->retransmits;
297 	}
298 	break;
299 
300     case REQSENT:
301     case ACKRCVD:
302     case ACKSENT:
303 	if (f->retransmits <= 0) {
304 	    fsm_log(f, LOG_WARNING, "timeout sending Config-Requests\n");
305 	    f->state = STOPPED;
306 	    if( (f->flags & OPT_PASSIVE) == 0 && f->callbacks->finished )
307 		(*f->callbacks->finished)(f);
308 
309 	} else {
310 	    /* Retransmit the configure-request */
311 	    if (f->callbacks->retransmit)
312 		(*f->callbacks->retransmit)(f);
313 	    fsm_sconfreq(f, 1);		/* Re-send Configure-Request */
314 	    if( f->state == ACKRCVD )
315 		f->state = REQSENT;
316 	}
317 	break;
318 
319     default:
320 	FSMDEBUG((f, LOG_DEBUG, "Timeout event in state %d!", f->state));
321     }
322 }
323 
324 
325 /*
326  * fsm_input - Input packet.
327  */
328 void
329 fsm_input(f, inpacket, l)
330     fsm *f;
331     u_char *inpacket;
332     int l;
333 {
334     u_char *inp;
335     u_char code, id;
336     int len;
337 
338     /*
339      * Parse header (code, id and length).
340      * If packet too short, drop it.
341      */
342     inp = inpacket;
343     if (l < HEADERLEN) {
344 	FSMDEBUG((f, LOG_DEBUG, "fsm_input(): Rcvd short header."));
345 	return;
346     }
347     GETCHAR(code, inp);
348     GETCHAR(id, inp);
349     GETSHORT(len, inp);
350     if (len < HEADERLEN) {
351 	FSMDEBUG((f, LOG_DEBUG, "fsm_input(): Rcvd illegal length."));
352 	return;
353     }
354     if (len > l) {
355 	FSMDEBUG((f, LOG_DEBUG, "fsm_input(): Rcvd short packet."));
356 	return;
357     }
358     len -= HEADERLEN;		/* subtract header length */
359 
360     if( f->state == INITIAL || f->state == STARTING ){
361 	FSMDEBUG((f, LOG_DEBUG, "fsm_input(): Rcvd packet in state %d.",
362 	    f->state));
363 	return;
364     }
365 
366     /*
367      * Action depends on code.
368      */
369     switch (code) {
370     case CONFREQ:
371 	fsm_rconfreq(f, id, inp, len);
372 	break;
373 
374     case CONFACK:
375 	fsm_rconfack(f, id, inp, len);
376 	break;
377 
378     case CONFNAK:
379     case CONFREJ:
380 	fsm_rconfnakrej(f, code, id, inp, len);
381 	break;
382 
383     case TERMREQ:
384 	fsm_rtermreq(f, id, inp, len);
385 	break;
386 
387     case TERMACK:
388 	fsm_rtermack(f);
389 	break;
390 
391     case CODEREJ:
392 	fsm_rcoderej(f, inp, len);
393 	break;
394 
395     default:
396 	if( !f->callbacks->extcode
397 	   || !(*f->callbacks->extcode)(f, code, id, inp, len) )
398 	    fsm_sdata(f, CODEREJ, ++f->id, inpacket, len + HEADERLEN);
399 	break;
400     }
401 }
402 
403 
404 /*
405  * fsm_rconfreq - Receive Configure-Request.
406  */
407 static void
408 fsm_rconfreq(f, id, inp, len)
409     fsm *f;
410     u_char id;
411     u_char *inp;
412     int len;
413 {
414     int code, reject_if_disagree;
415 
416     switch( f->state ){
417     case CLOSED:
418 	/* Go away, we're closed */
419 	fsm_sdata(f, TERMACK, id, NULL, 0);
420 	return;
421     case CLOSING:
422     case STOPPING:
423 	return;
424 
425     case OPENED:
426 	/* Go down and restart negotiation */
427 	if( f->callbacks->down )
428 	    (*f->callbacks->down)(f);	/* Inform upper layers */
429 	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
430 	break;
431 
432     case STOPPED:
433 	/* Negotiation started by our peer */
434 	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
435 	f->state = REQSENT;
436 	break;
437     }
438 
439     /*
440      * Pass the requested configuration options
441      * to protocol-specific code for checking.
442      */
443     if (f->callbacks->reqci){		/* Check CI */
444 	reject_if_disagree = (f->nakloops >= f->maxnakloops);
445 	code = (*f->callbacks->reqci)(f, inp, &len, reject_if_disagree);
446     } else if (len)
447 	code = CONFREJ;			/* Reject all CI */
448     else
449 	code = CONFACK;
450 
451     /* send the Ack, Nak or Rej to the peer */
452     fsm_sdata(f, code, id, inp, len);
453 
454     if (code == CONFACK) {
455 	if (f->state == ACKRCVD) {
456 	    UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
457 	    f->state = OPENED;
458 	    if (f->callbacks->up)
459 		(*f->callbacks->up)(f);	/* Inform upper layers */
460 	} else
461 	    f->state = ACKSENT;
462 	f->nakloops = 0;
463 
464     } else {
465 	/* we sent CONFNAK or CONFREJ */
466 	if (f->state != ACKRCVD)
467 	    f->state = REQSENT;
468 	if( code == CONFNAK )
469 	    ++f->nakloops;
470     }
471 }
472 
473 
474 /*
475  * fsm_rconfack - Receive Configure-Ack.
476  */
477 static void
478 fsm_rconfack(f, id, inp, len)
479     fsm *f;
480     int id;
481     u_char *inp;
482     int len;
483 {
484     if (id != f->reqid || f->seen_ack)		/* Expected id? */
485 	return;					/* Nope, toss... */
486     if( !(f->callbacks->ackci? (*f->callbacks->ackci)(f, inp, len):
487 	  (len == 0)) ){
488 	/* Ack is bad - ignore it */
489 	fsm_log(f, LOG_ERR, "Received bad configure-ack: %p(%d)", inp, len);
490 	return;
491     }
492     f->seen_ack = 1;
493 
494     switch (f->state) {
495     case CLOSED:
496     case STOPPED:
497 	fsm_sdata(f, TERMACK, id, NULL, 0);
498 	break;
499 
500     case REQSENT:
501 	f->state = ACKRCVD;
502 	f->retransmits = f->maxconfreqtransmits;
503 	break;
504 
505     case ACKRCVD:
506 	/* Huh? an extra valid Ack? oh well... */
507 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
508 	fsm_sconfreq(f, 0);
509 	f->state = REQSENT;
510 	break;
511 
512     case ACKSENT:
513 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
514 	f->state = OPENED;
515 	f->retransmits = f->maxconfreqtransmits;
516 	if (f->callbacks->up)
517 	    (*f->callbacks->up)(f);	/* Inform upper layers */
518 	break;
519 
520     case OPENED:
521 	/* Go down and restart negotiation */
522 	if (f->callbacks->down)
523 	    (*f->callbacks->down)(f);	/* Inform upper layers */
524 	fsm_sconfreq(f, 0);		/* Send initial Configure-Request */
525 	f->state = REQSENT;
526 	break;
527     }
528 }
529 
530 
531 /*
532  * fsm_rconfnakrej - Receive Configure-Nak or Configure-Reject.
533  */
534 static void
535 fsm_rconfnakrej(f, code, id, inp, len)
536     fsm *f;
537     int code, id;
538     u_char *inp;
539     int len;
540 {
541     int (*proc)(fsm *, u_char *, int);
542     int ret;
543 
544     if (id != f->reqid || f->seen_ack)	/* Expected id? */
545 	return;				/* Nope, toss... */
546     proc = (code == CONFNAK)? f->callbacks->nakci: f->callbacks->rejci;
547     if (!proc || !(ret = proc(f, inp, len))) {
548 	/* Nak/reject is bad - ignore it */
549 	fsm_log(f, LOG_INFO, "Received bad configure-%s: %p(%d)",
550 	    (code == CONFNAK)? "nak" : "rej", inp, len);
551 	return;
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(f, id, p, len)
594     fsm *f;
595     int id;
596     u_char *p;
597     int len;
598 {
599     switch (f->state) {
600     case ACKRCVD:
601     case ACKSENT:
602 	f->state = REQSENT;		/* Start over but keep trying */
603 	break;
604 
605     case OPENED:
606 	fsm_log(f, LOG_INFO, "terminated by peer");
607 	if (f->callbacks->down)
608 	    (*f->callbacks->down)(f);	/* Inform upper layers */
609 	f->retransmits = 0;
610 	f->state = STOPPING;
611 	TIMEOUT(fsm_timeout, f, f->timeouttime);
612 	break;
613     }
614 
615     fsm_sdata(f, TERMACK, id, NULL, 0);
616 }
617 
618 
619 /*
620  * fsm_rtermack - Receive Terminate-Ack.
621  */
622 static void
623 fsm_rtermack(f)
624     fsm *f;
625 {
626     switch (f->state) {
627     case CLOSING:
628 	UNTIMEOUT(fsm_timeout, f);
629 	f->state = CLOSED;
630 	if( f->callbacks->finished )
631 	    (*f->callbacks->finished)(f);
632 	break;
633     case STOPPING:
634 	UNTIMEOUT(fsm_timeout, f);
635 	f->state = STOPPED;
636 	if( f->callbacks->finished )
637 	    (*f->callbacks->finished)(f);
638 	break;
639 
640     case ACKRCVD:
641 	f->state = REQSENT;
642 	break;
643 
644     case OPENED:
645 	if (f->callbacks->down)
646 	    (*f->callbacks->down)(f);	/* Inform upper layers */
647 	fsm_sconfreq(f, 0);
648 	f->state = REQSENT;
649 	break;
650     }
651 }
652 
653 
654 /*
655  * fsm_rcoderej - Receive an Code-Reject.
656  */
657 static void
658 fsm_rcoderej(f, inp, len)
659     fsm *f;
660     u_char *inp;
661     int len;
662 {
663     u_char code, id;
664 
665     if (len < HEADERLEN) {
666 	FSMDEBUG((f, LOG_DEBUG,
667 	    "fsm_rcoderej: Rcvd short Code-Reject packet!"));
668 	return;
669     }
670     GETCHAR(code, inp);
671     GETCHAR(id, inp);
672     fsm_log(f, LOG_INFO,
673 	"%s: Rcvd Code-Reject for code %d, id %d", PROTO_NAME(f), code, id);
674 
675     if( f->state == ACKRCVD )
676 	f->state = REQSENT;
677 }
678 
679 
680 /*
681  * fsm_protreject - Peer doesn't speak this protocol.
682  *
683  * Treat this as a catastrophic error (RXJ-).
684  */
685 void
686 fsm_protreject(f)
687     fsm *f;
688 {
689     switch( f->state ){
690     case CLOSING:
691 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
692 	/* fall through */
693     case CLOSED:
694 	f->state = CLOSED;
695 	if( f->callbacks->finished )
696 	    (*f->callbacks->finished)(f);
697 	break;
698 
699     case STOPPING:
700     case REQSENT:
701     case ACKRCVD:
702     case ACKSENT:
703 	UNTIMEOUT(fsm_timeout, f);	/* Cancel timeout */
704 	/* fall through */
705     case STOPPED:
706 	f->state = STOPPED;
707 	if( f->callbacks->finished )
708 	    (*f->callbacks->finished)(f);
709 	break;
710 
711     case OPENED:
712 	if( f->callbacks->down )
713 	    (*f->callbacks->down)(f);
714 
715 	/* Init restart counter, send Terminate-Request */
716 	f->retransmits = f->maxtermtransmits;
717 	fsm_sdata(f, TERMREQ, f->reqid = ++f->id,
718 		  (u_char *) f->term_reason, f->term_reason_len);
719 	TIMEOUT(fsm_timeout, f, f->timeouttime);
720 	--f->retransmits;
721 
722 	f->state = STOPPING;
723 	break;
724 
725     default:
726 	FSMDEBUG((f, LOG_DEBUG,
727 	    "Protocol-reject event in state %d!", f->state));
728     }
729 }
730 
731 
732 /*
733  * fsm_sconfreq - Send a Configure-Request.
734  */
735 static void
736 fsm_sconfreq(f, retransmit)
737     fsm *f;
738     int retransmit;
739 {
740     u_char *outp;
741     int cilen;
742 
743     if( f->state != REQSENT && f->state != ACKRCVD && f->state != ACKSENT ){
744 	/* Not currently negotiating - reset options */
745 	if( f->callbacks->resetci )
746 	    (*f->callbacks->resetci)(f);
747 	f->nakloops = 0;
748     }
749 
750     if( !retransmit ){
751 	/* New request - reset retransmission counter, use new ID */
752 	f->retransmits = f->maxconfreqtransmits;
753 	f->reqid = ++f->id;
754     }
755 
756     f->seen_ack = 0;
757 
758     /*
759      * Make up the request packet
760      */
761     outp = f->ppp->outpacket_buf + PPP_HDRLEN + HEADERLEN;
762     if( f->callbacks->cilen && f->callbacks->addci ){
763 	cilen = (*f->callbacks->cilen)(f);
764 	if( cilen > f->ppp->mru - HEADERLEN )
765 	    cilen = f->ppp->mru - HEADERLEN;
766 	if (f->callbacks->addci)
767 	    (*f->callbacks->addci)(f, outp, &cilen);
768     } else
769 	cilen = 0;
770 
771     /* send the request to our peer */
772     fsm_sdata(f, CONFREQ, f->reqid, outp, cilen);
773 
774     /* start the retransmit timer */
775     --f->retransmits;
776     TIMEOUT(fsm_timeout, f, f->timeouttime);
777 }
778 
779 
780 /*
781  * fsm_sdata - Send some data.
782  *
783  * Used for all packets sent to our peer by this module.
784  */
785 void
786 fsm_sdata(f, code, id, data, datalen)
787     fsm *f;
788     u_char code, id;
789     u_char *data;
790     int datalen;
791 {
792     ppp_output(f->ppp, f->protocol, code, id, data, datalen);
793 }
794 
795 
796 void
797 fsm_log(fsm *f, uint32_t prio, const char *fmt, ...)
798 {
799     char logbuf[BUFSIZ];
800     va_list ap;
801 
802     FSM_ASSERT(f != NULL);
803     FSM_ASSERT(f->callbacks != NULL);
804 
805     va_start(ap, fmt);
806     snprintf(logbuf, sizeof(logbuf), "ppp id=%u layer=%s %s", f->ppp->id,
807 	PROTO_NAME(f), fmt);
808     vlog_printf(prio, logbuf, ap);
809     va_end(ap);
810 }
811