]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/snoopy/main.c
etheriwl: don't break controller on command flush timeout
[plan9front.git] / sys / src / cmd / ip / snoopy / main.c
1 /*
2  * snoopy - network sniffer
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <ip.h>
7 #include <bio.h>
8 #include <fcall.h>
9 #include <libsec.h>
10 #include <ndb.h>
11 #include "dat.h"
12 #include "protos.h"
13 #include "y.tab.h"
14
15 int Cflag;
16 int pflag;
17 int Nflag;
18 int Mflag;
19 int sflag;
20 int tiflag;
21 int toflag;
22
23 char *prom = "promiscuous";
24
25 enum
26 {
27         Pktlen= 64*1024,
28         Blen=   16*1024,
29 };
30
31 Filter *filter;
32 Proto *root;
33 Biobuf out;
34 vlong starttime, pkttime;
35 int pcap;
36
37 int     filterpkt(Filter *f, uchar *ps, uchar *pe, Proto *pr, int);
38 void    printpkt(char *p, char *e, uchar *ps, uchar *pe);
39 void    mkprotograph(void);
40 Proto*  findproto(char *name);
41 Filter* compile(Filter *f);
42 void    printfilter(Filter *f, char *tag);
43 void    printhelp(char*);
44 void    tracepkt(uchar*, int);
45 void    pcaphdr(void);
46
47 void
48 printusage(void)
49 {
50         fprint(2, "usage: %s [-CDdpst] [-N n] [-f filter] [-h first-header] path\n", argv0);
51         fprint(2, "  for protocol help: %s -? [proto]\n", argv0);
52 }
53
54 void
55 usage(void)
56 {
57         printusage();
58         exits("usage");
59 }
60
61 void
62 main(int argc, char **argv)
63 {
64         uchar *pkt;
65         char *buf, *file, *p, *e;
66         int fd, cfd;
67         int n;
68
69         Binit(&out, 1, OWRITE);
70
71         fmtinstall('E', eipfmt);
72         fmtinstall('V', eipfmt);
73         fmtinstall('I', eipfmt);
74         fmtinstall('H', encodefmt);
75         fmtinstall('F', fcallfmt);
76
77         pkt = malloc(Pktlen+16);
78         pkt += 16;
79         buf = malloc(Blen);
80         e = buf+Blen-1;
81
82         pflag = 1;
83         Nflag = 32;
84         sflag = 0;
85
86         mkprotograph();
87
88         ARGBEGIN{
89         default:
90                 usage();
91         case '?':
92                 printusage();
93                 printhelp(ARGF());
94                 exits(0);
95                 break;
96         case 'M':
97                 p = EARGF(usage());
98                 Mflag = atoi(p);
99                 break;
100         case 'N':
101                 p = EARGF(usage());
102                 Nflag = atoi(p);
103                 break;
104         case 'f':
105                 p = EARGF(usage());
106                 yyinit(p);
107                 yyparse();
108                 break;
109         case 's':
110                 sflag = 1;
111                 break;
112         case 'h':
113                 p = EARGF(usage());
114                 root = findproto(p);
115                 if(root == nil)
116                         sysfatal("unknown protocol: %s", p);
117                 break;
118         case 'd':
119                 toflag = 1;
120                 break;
121         case 'D':
122                 toflag = 1;
123                 pcap = 1;
124                 break;
125         case 't':
126                 tiflag = 1;
127                 break;
128         case 'C':
129                 Cflag = 1;
130                 break;
131         case 'p':
132                 pflag = 0;
133                 break;
134         }ARGEND;
135
136         if(pcap)
137                 pcaphdr();
138
139         if(argc == 0){
140                 file = "/net/ether0";
141                 if(root != nil)
142                         root = &ether;
143         } else
144                 file = argv[0];
145
146         if((!tiflag) && strstr(file, "ether")){
147                 if(root == nil)
148                         root = &ether;
149                 snprint(buf, Blen, "%s!-1", file);
150                 fd = dial(buf, 0, 0, &cfd);
151                 if(fd < 0)
152                         sysfatal("dialing %s", buf);
153                 if(pflag && fprint(cfd, prom, strlen(prom)) < 0)
154                         sysfatal("setting %s", prom);
155         } else if((!tiflag) && strstr(file, "ipifc")){
156                 if(root == nil)
157                         root = &ip;
158                 snprint(buf, Blen, "%s/snoop", file);
159                 fd = open(buf, OREAD);
160                 if(fd < 0)
161                         sysfatal("opening %s: %r", buf);
162         } else {
163                 if(root == nil)
164                         root = &ether;
165                 fd = open(file, OREAD);
166                 if(fd < 0)
167                         sysfatal("opening %s: %r", file);
168         }
169         filter = compile(filter);
170
171         if(tiflag){
172                 /* read a trace file */
173                 for(;;){
174                         n = read(fd, pkt, 10);
175                         if(n != 10)
176                                 break;
177                         pkttime = NetL(pkt+2);
178                         pkttime = (pkttime<<32) | NetL(pkt+6);
179                         if(starttime == 0LL)
180                                 starttime = pkttime;
181                         n = NetS(pkt);
182                         if(readn(fd, pkt, n) != n)
183                                 break;
184                         if(filterpkt(filter, pkt, pkt+n, root, 1))
185                                 if(toflag)
186                                         tracepkt(pkt, n);
187                                 else
188                                         printpkt(buf, e, pkt, pkt+n);
189                 }
190         } else {
191                 /* read a real time stream */
192                 starttime = nsec();
193                 for(;;){
194                         n = root->framer(fd, pkt, Pktlen);
195                         if(n <= 0)
196                                 break;
197                         pkttime = nsec();
198                         if(filterpkt(filter, pkt, pkt+n, root, 1))
199                                 if(toflag)
200                                         tracepkt(pkt, n);
201                                 else
202                                         printpkt(buf, e, pkt, pkt+n);
203                 }
204         }
205 }
206
207 /* create a new filter node */
208 Filter*
209 newfilter(void)
210 {
211         Filter *f;
212
213         f = mallocz(sizeof(*f), 1);
214         if(f == nil)
215                 sysfatal("newfilter: %r");
216         return f;
217 }
218
219 /*
220  *  apply filter to packet
221  */
222 int
223 _filterpkt(Filter *f, Msg *m)
224 {
225         Msg ma;
226
227         if(f == nil)
228                 return 1;
229
230         switch(f->op){
231         case '!':
232                 return !_filterpkt(f->l, m);
233         case LAND:
234                 ma = *m;
235                 return _filterpkt(f->l, &ma) && _filterpkt(f->r, m);
236         case LOR:
237                 ma = *m;
238                 return _filterpkt(f->l, &ma) || _filterpkt(f->r, m);
239         case WORD:
240                 if(m->needroot){
241                         if(m->pr != f->pr)
242                                 return 0;
243                         m->needroot = 0;
244                 }else{
245                         if(m->pr && (m->pr->filter==nil || !(m->pr->filter)(f, m)))
246                                 return 0;
247                 }
248                 if(f->l == nil)
249                         return 1;
250                 m->pr = f->pr;
251                 return _filterpkt(f->l, m);
252         }
253         sysfatal("internal error: filterpkt op: %d", f->op);
254         return 0;
255 }
256 int
257 filterpkt(Filter *f, uchar *ps, uchar *pe, Proto *pr, int needroot)
258 {
259         Msg m;
260
261         if(f == nil)
262                 return 1;
263
264         m.needroot = needroot;
265         m.ps = ps;
266         m.pe = pe;
267         m.pr = pr;
268         return _filterpkt(f, &m);
269 }
270
271 /*
272  *  from the Unix world
273  */
274 #define PCAP_VERSION_MAJOR 2
275 #define PCAP_VERSION_MINOR 4
276 #define TCPDUMP_MAGIC 0xa1b23c4d
277
278 struct pcap_file_header {
279         ulong           magic;
280         ushort          version_major;
281         ushort          version_minor;
282         long            thiszone;    /* gmt to local correction */
283         ulong           sigfigs;    /* accuracy of timestamps */
284         ulong           snaplen;    /* max length saved portion of each pkt */
285         ulong           linktype;   /* data link type (DLT_*) */
286 };
287
288 struct pcap_pkthdr {
289         ulong   ts_sec;
290         ulong   ts_nsec;
291         ulong   caplen; /* length of portion present */
292         ulong   len;    /* length this packet (off wire) */
293 };
294
295 /*
296  *  pcap trace header 
297  */
298 void
299 pcaphdr(void)
300 {
301         struct pcap_file_header hdr;
302
303         hdr.magic = TCPDUMP_MAGIC;
304         hdr.version_major = PCAP_VERSION_MAJOR;
305         hdr.version_minor = PCAP_VERSION_MINOR;
306   
307         hdr.thiszone = 0;
308         hdr.snaplen = 1500;
309         hdr.sigfigs = 0;
310         hdr.linktype = 1;
311
312         write(1, &hdr, sizeof(hdr));
313 }
314
315 /*
316  *  write out a packet trace
317  */
318 void
319 tracepkt(uchar *ps, int len)
320 {
321         struct pcap_pkthdr *goo;
322
323         if(Mflag && len > Mflag)
324                 len = Mflag;
325         if(pcap){
326                 goo = (struct pcap_pkthdr*)(ps-16);
327                 goo->ts_sec  = (uvlong)pkttime / 1000000000;
328                 goo->ts_nsec = (uvlong)pkttime % 1000000000;
329                 goo->caplen = len;
330                 goo->len = len;
331                 write(1, goo, len+16);
332         } else {
333                 hnputs(ps-10, len);
334                 hnputl(ps-8, pkttime>>32);
335                 hnputl(ps-4, pkttime);
336                 write(1, ps-10, len+10);
337         }
338 }
339
340 /*
341  *  format and print a packet
342  */
343 void
344 printpkt(char *p, char *e, uchar *ps, uchar *pe)
345 {
346         Msg m;
347         ulong dt;
348
349         dt = (pkttime-starttime)/1000000LL;
350         m.p = seprint(p, e, "%6.6uld ms ", dt);
351         m.ps = ps;
352         m.pe = pe;
353         m.e = e;
354         m.pr = root;
355         while(m.p < m.e){
356                 if(!sflag)
357                         m.p = seprint(m.p, m.e, "\n\t");
358                 m.p = seprint(m.p, m.e, "%s(", m.pr->name);
359                 if((*m.pr->seprint)(&m) < 0){
360                         m.p = seprint(m.p, m.e, "TOO SHORT");
361                         m.ps = m.pe;
362                 }
363                 m.p = seprint(m.p, m.e, ")");
364                 if(m.pr == nil || m.ps >= m.pe)
365                         break;
366         }
367         *m.p++ = '\n';
368
369         if(write(1, p, m.p - p) < 0)
370                 sysfatal("stdout: %r");
371 }
372
373 Proto **xprotos;
374 int nprotos;
375
376 /* look up a protocol by its name */
377 Proto*
378 findproto(char *name)
379 {
380         int i;
381
382         for(i = 0; i < nprotos; i++)
383                 if(strcmp(xprotos[i]->name, name) == 0)
384                         return xprotos[i];
385         return nil;
386 }
387
388 /*
389  *  add an undefined protocol to protos[]
390  */
391 Proto*
392 addproto(char *name)
393 {
394         Proto *pr;
395
396         xprotos = realloc(xprotos, (nprotos+1)*sizeof(Proto*));
397         pr = malloc(sizeof *pr);
398         *pr = dump;
399         pr->name = name;
400         xprotos[nprotos++] = pr;
401         return pr;
402 }
403
404 /*
405  *  build a graph of protocols, this could easily be circular.  This
406  *  links together all the multiplexing in the protocol modules.
407  */
408 void
409 mkprotograph(void)
410 {
411         Proto **l;
412         Proto *pr;
413         Mux *m;
414
415         /* copy protos into a reallocable area */
416         for(nprotos = 0; protos[nprotos] != nil; nprotos++)
417                 ;
418         xprotos = malloc(nprotos*sizeof(Proto*));
419         memmove(xprotos, protos, nprotos*sizeof(Proto*));
420
421         for(l = protos; *l != nil; l++){
422                 pr = *l;
423                 for(m = pr->mux; m != nil && m->name != nil; m++){
424                         m->pr = findproto(m->name);
425                         if(m->pr == nil)
426                                 m->pr = addproto(m->name);
427                 }
428         }
429 }
430
431 /*
432  *  add in a protocol node
433  */
434 static Filter*
435 addnode(Filter *f, Proto *pr)
436 {
437         Filter *nf;
438         nf = newfilter();
439         nf->pr = pr;
440         nf->s = pr->name;
441         nf->l = f;
442         nf->op = WORD;
443         return nf;
444 }
445
446 /*
447  *  recurse through the protocol graph adding missing nodes
448  *  to the filter if we reach the filter's protocol
449  */
450 static Filter*
451 _fillin(Filter *f, Proto *last, int depth)
452 {
453         Mux *m;
454         Filter *nf;
455
456         if(depth-- <= 0)
457                 return nil;
458
459         for(m = last->mux; m != nil && m->name != nil; m++){
460                 if(m->pr == nil)
461                         continue;
462                 if(f->pr == m->pr)
463                         return f;
464                 nf = _fillin(f, m->pr, depth);
465                 if(nf != nil)
466                         return addnode(nf, m->pr);
467         }
468         return nil;
469 }
470
471 static Filter*
472 fillin(Filter *f, Proto *last)
473 {
474         int i;
475         Filter *nf;
476
477         /* hack to make sure top level node is the root */
478         if(last == nil){
479                 if(f->pr == root)
480                         return f;
481                 f = fillin(f, root);
482                 if(f == nil)
483                         return nil;
484                 return addnode(f, root);
485         }
486
487         /* breadth first search though the protocol graph */
488         nf = f;
489         for(i = 1; i < 20; i++){
490                 nf = _fillin(f, last, i);
491                 if(nf != nil)
492                         break;
493         }
494         return nf;
495 }
496
497 /*
498  *  massage tree so that all paths from the root to a leaf
499  *  contain a filter node for each header.
500  *
501  *  also, set f->pr where possible
502  */
503 Filter*
504 complete(Filter *f, Proto *last)
505 {
506         Proto *pr;
507
508         if(f == nil)
509                 return f;
510
511         /* do a depth first traversal of the filter tree */
512         switch(f->op){
513         case '!':
514                 f->l = complete(f->l, last);
515                 break;
516         case LAND:
517         case LOR:
518                 f->l = complete(f->l, last);
519                 f->r = complete(f->r, last);
520                 break;
521         case '=':
522                 break;
523         case WORD:
524                 pr = findproto(f->s);
525                 f->pr = pr;
526                 if(pr == nil){
527                         if(f->l != nil){
528                                 fprint(2, "%s unknown proto, ignoring params\n",
529                                         f->s);
530                                 f->l = nil;
531                         }
532                 } else {
533                         f->l = complete(f->l, pr);
534                         f = fillin(f, last);
535                         if(f == nil)
536                                 sysfatal("internal error: can't get to %s", pr->name);
537                 }
538                 break;
539         }
540         return f;
541 }
542
543 /*
544  *  merge common nodes under | and & moving the merged node
545  *  above the | or &.
546  *
547  *  do some constant foldong, e.g. `true & x' becomes x and
548  *  'true | x' becomes true.
549  */
550 static int changed;
551
552 static Filter*
553 _optimize(Filter *f)
554 {
555         Filter *l;
556
557         if(f == nil)
558                 return f;
559
560         switch(f->op){
561         case '!':
562                 /* is child also a not */
563                 if(f->l->op == '!'){
564                         changed = 1;
565                         return f->l->l;
566                 }
567                 break;
568         case LOR:
569                 /* are two children the same protocol? */
570                 if(f->l->op != f->r->op || f->r->op != WORD
571                 || f->l->pr != f->r->pr || f->l->pr == nil)
572                         break;  /* no optimization */
573
574                 changed = 1;
575
576                 /* constant folding */
577                 /* if either child is childless, just return that */
578                 if(f->l->l == nil)
579                         return f->l;
580                 else if(f->r->l == nil)
581                         return f->r;
582
583                 /* move the common node up, thow away one node */
584                 l = f->l;
585                 f->l = l->l;
586                 f->r = f->r->l;
587                 l->l = f;
588                 return l;
589         case LAND:
590                 /* are two children the same protocol? */
591                 if(f->l->op != f->r->op || f->r->op != WORD
592                 || f->l->pr != f->r->pr || f->l->pr == nil)
593                         break;  /* no optimization */
594
595                 changed = 1;
596
597                 /* constant folding */
598                 /* if either child is childless, ignore it */
599                 if(f->l->l == nil)
600                         return f->r;
601                 else if(f->r->l == nil)
602                         return f->l;
603
604                 /* move the common node up, thow away one node */
605                 l = f->l;
606                 f->l = _optimize(l->l);
607                 f->r = _optimize(f->r->l);
608                 l->l = f;
609                 return l;
610         }
611         f->l = _optimize(f->l);
612         f->r = _optimize(f->r);
613         return f;
614 }
615
616 Filter*
617 optimize(Filter *f)
618 {
619         do{
620                 changed = 0;
621                 f = _optimize(f);
622         }while(changed);
623
624         return f;
625 }
626
627 /*
628  *  find any top level nodes that aren't the root
629  */
630 int
631 findbogus(Filter *f)
632 {
633         int rv;
634
635         if(f->op != WORD){
636                 rv = findbogus(f->l);
637                 if(f->r)
638                         rv |= findbogus(f->r);
639                 return rv;
640         } else if(f->pr != root){
641                 fprint(2, "bad top-level protocol: %s\n", f->s);
642                 return 1;
643         }
644         return 0;
645 }
646
647 /*
648  *  compile the filter
649  */
650 static void
651 _compile(Filter *f, Proto *last)
652 {
653         if(f == nil)
654                 return;
655
656         switch(f->op){
657         case '!':
658                 _compile(f->l, last);
659                 break;
660         case LOR:
661         case LAND:
662                 _compile(f->l, last);
663                 _compile(f->r, last);
664                 break;
665         case WORD:
666                 if(last != nil){
667                         if(last->compile == nil)
668                                 sysfatal("unknown %s subprotocol: %s", f->pr->name, f->s);
669                         (*last->compile)(f);
670                 }
671                 if(f->l)
672                         _compile(f->l, f->pr);
673                 break;
674         case '=':
675                 if(last == nil)
676                         sysfatal("internal error: compilewalk: badly formed tree");
677                 
678                 if(last->compile == nil)
679                         sysfatal("unknown %s field: %s", f->pr->name, f->s);
680                 (*last->compile)(f);
681                 break;
682         default:
683                 sysfatal("internal error: compilewalk op: %d", f->op);
684         }
685 }
686
687 Filter*
688 compile(Filter *f)
689 {
690         if(f == nil)
691                 return f;
692
693         /* fill in the missing header filters */
694         f = complete(f, nil);
695
696         /* constant folding */
697         f = optimize(f);
698         if(!toflag)
699                 printfilter(f, "after optimize");
700
701         /* protocol specific compilations */
702         _compile(f, nil);
703
704         /* at this point, the root had better be the root proto */
705         if(findbogus(f)){
706                 fprint(2, "bogus filter\n");
707                 exits("bad filter");
708         }
709
710         return f;
711 }
712
713 /*
714  *  parse a byte array
715  */
716 int
717 parseba(uchar *to, char *from)
718 {
719         char nip[4];
720         char *p;
721         int i;
722
723         p = from;
724         for(i = 0; i < 16; i++){
725                 if(*p == 0)
726                         return -1;
727                 nip[0] = *p++;
728                 if(*p == 0)
729                         return -1;
730                 nip[1] = *p++;
731                 nip[2] = 0;
732                 to[i] = strtoul(nip, 0, 16);
733         }
734         return i;
735 }
736
737 /*
738  *  compile WORD = WORD, becomes a single node with a subop
739  */
740 void
741 compile_cmp(char *proto, Filter *f, Field *fld)
742 {
743         uchar x[IPaddrlen];
744         char *v;
745
746         if(f->op != '=')
747                 sysfatal("internal error: compile_cmp %s: not a cmp", proto);
748
749         for(; fld->name != nil; fld++){
750                 if(strcmp(f->l->s, fld->name) == 0){
751                         f->op = WORD;
752                         f->subop = fld->subop;
753                         switch(fld->ftype){
754                         case Fnum:
755                                 f->ulv = strtoul(f->r->s, 0, 0);
756                                 break;
757                         case Fether:
758                                 v = csgetvalue(nil, "sys", (char*)f->r->s,
759                                         "ether", 0);
760                                 if(v){
761                                         parseether(f->a, v);
762                                         free(v);
763                                 } else
764                                         parseether(f->a, f->r->s);
765                                 break;
766                         case Fv4ip:
767                                 v = csgetvalue(nil, "sys", (char*)f->r->s,
768                                         "ip", 0);
769                                 if(v){
770                                         f->ulv = parseip(x, v);
771                                         free(v);
772                                 }else
773                                         f->ulv = parseip(x, f->r->s);
774                                 break;
775                         case Fv6ip:
776                                 v = csgetvalue(nil, "sys", (char*)f->r->s,
777                                         "ipv6", 0);
778                                 if(v){
779                                         parseip(f->a, v);
780                                         free(v);
781                                 }else
782                                         parseip(f->a, f->r->s);
783                                 break;
784                         case Fba:
785                                 parseba(f->a, f->r->s);
786                                 break;
787                         default:
788                                 sysfatal("internal error: compile_cmp %s: %d",
789                                         proto, fld->ftype);
790                         }
791                         f->l = f->r = nil;
792                         return;
793                 }
794         }
795         sysfatal("unknown %s field in: %s = %s", proto, f->l->s, f->r->s);
796 }
797
798 void
799 _pf(Filter *f)
800 {
801         char *s;
802
803         if(f == nil)
804                 return;
805
806         s = nil;
807         switch(f->op){
808         case '!':
809                 fprint(2, "!");
810                 _pf(f->l);
811                 break;
812         case WORD:
813                 fprint(2, "%s", f->s);
814                 if(f->l != nil){
815                         fprint(2, "(");
816                         _pf(f->l);
817                         fprint(2, ")");
818                 }
819                 break;
820         case LAND:
821                 s = "&&";
822                 goto print;
823         case LOR:
824                 s = "||";
825                 goto print;
826         case '=':
827         print:
828                 _pf(f->l);
829                 if(s)
830                         fprint(2, " %s ", s);
831                 else
832                         fprint(2, " %c ", f->op);
833                 _pf(f->r);
834                 break;
835         default:
836                 fprint(2, "???");
837                 break;
838         }
839 }
840
841 void
842 printfilter(Filter *f, char *tag)
843 {
844         fprint(2, "%s: ", tag);
845         _pf(f);
846         fprint(2, "\n");
847 }
848
849 void
850 cat(void)
851 {
852         char buf[1024];
853         int n;
854         
855         while((n = read(0, buf, sizeof buf)) > 0)
856                 write(1, buf, n);
857 }
858
859 static int fd1 = -1;
860 void
861 startmc(void)
862 {
863         int p[2];
864         
865         if(fd1 == -1)
866                 fd1 = dup(1, -1);
867         
868         if(pipe(p) < 0)
869                 return;
870         switch(fork()){
871         case -1:
872                 return;
873         default:
874                 close(p[0]);
875                 dup(p[1], 1);
876                 if(p[1] != 1)
877                         close(p[1]);
878                 return;
879         case 0:
880                 close(p[1]);
881                 dup(p[0], 0);
882                 if(p[0] != 0)
883                         close(p[0]);
884                 execl("/bin/mc", "mc", nil);
885                 cat();
886                 _exits(0);
887         }
888 }
889
890 void
891 stopmc(void)
892 {
893         close(1);
894         dup(fd1, 1);
895         waitpid();
896 }
897
898 void
899 printhelp(char *name)
900 {
901         int len;
902         Proto *pr, **l;
903         Mux *m;
904         Field *f;
905         char fmt[40];
906         
907         if(name == nil){
908                 print("protocols:\n");
909                 startmc();
910                 for(l=protos; (pr=*l) != nil; l++)
911                         print("  %s\n", pr->name);
912                 stopmc();
913                 return;
914         }
915         
916         pr = findproto(name);
917         if(pr == nil){
918                 print("unknown protocol %s\n", name);
919                 return;
920         }
921         
922         if(pr->field){
923                 print("%s's filter attributes:\n", pr->name);
924                 len = 0;
925                 for(f=pr->field; f->name; f++)
926                         if(len < strlen(f->name))
927                                 len = strlen(f->name);
928                 startmc();
929                 for(f=pr->field; f->name; f++)
930                         print("  %-*s - %s\n", len, f->name, f->help);
931                 stopmc();
932         }
933         if(pr->mux){
934                 print("%s's subprotos:\n", pr->name);
935                 startmc();
936                 snprint(fmt, sizeof fmt, "  %s %%s\n", pr->valfmt);
937                 for(m=pr->mux; m->name != nil; m++)
938                         print(fmt, m->val, m->name);
939                 stopmc();
940         }
941 }
942
943 /*
944  *  demultiplex to next prototol header
945  */
946 void
947 demux(Mux *mx, ulong val1, ulong val2, Msg *m, Proto *def)
948 {
949         m->pr = def;
950         for(mx = mx; mx->name != nil; mx++){
951                 if(val1 == mx->val || val2 == mx->val){
952                         m->pr = mx->pr;
953                         break;
954                 }
955         }
956 }
957
958 /*
959  *  default framer just assumes the input packet is
960  *  a single read
961  */
962 int
963 defaultframer(int fd, uchar *pkt, int pktlen)
964 {
965         return read(fd, pkt, pktlen);
966 }