Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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: %r", 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 0xa1b2c3d4
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
        uvlong	ts;	/* time stamp */
290
        ulong	caplen;	/* length of portion present */
291
        ulong	len;	/* length this packet (off wire) */
292
};
293
 
294
/*
295
 *  pcap trace header 
296
 */
297
void
298
pcaphdr(void)
299
{
300
	struct pcap_file_header hdr;
301
 
302
	hdr.magic = TCPDUMP_MAGIC;
303
	hdr.version_major = PCAP_VERSION_MAJOR;
304
	hdr.version_minor = PCAP_VERSION_MINOR;
305
 
306
	hdr.thiszone = 0;
307
	hdr.snaplen = 1500;
308
	hdr.sigfigs = 0;
309
	hdr.linktype = 1;
310
 
311
	write(1, &hdr, sizeof(hdr));
312
}
313
 
314
/*
315
 *  write out a packet trace
316
 */
317
void
318
tracepkt(uchar *ps, int len)
319
{
320
	struct pcap_pkthdr *goo;
321
 
322
	if(Mflag && len > Mflag)
323
		len = Mflag;
324
	if(pcap){
325
		goo = (struct pcap_pkthdr*)(ps-16);
326
		goo->ts = pkttime;
327
		goo->caplen = len;
328
		goo->len = len;
329
		write(1, goo, len+16);
330
	} else {
331
		hnputs(ps-10, len);
332
		hnputl(ps-8, pkttime>>32);
333
		hnputl(ps-4, pkttime);
334
		write(1, ps-10, len+10);
335
	}
336
}
337
 
338
/*
339
 *  format and print a packet
340
 */
341
void
342
printpkt(char *p, char *e, uchar *ps, uchar *pe)
343
{
344
	Msg m;
345
	ulong dt;
346
 
347
	dt = (pkttime-starttime)/1000000LL;
348
	m.p = seprint(p, e, "%6.6uld ms ", dt);
349
	m.ps = ps;
350
	m.pe = pe;
351
	m.e = e;
352
	m.pr = root;
353
	while(m.p < m.e){
354
		if(!sflag)
355
			m.p = seprint(m.p, m.e, "\n\t");
356
		m.p = seprint(m.p, m.e, "%s(", m.pr->name);
357
		if((*m.pr->seprint)(&m) < 0){
358
			m.p = seprint(m.p, m.e, "TOO SHORT");
359
			m.ps = m.pe;
360
		}
361
		m.p = seprint(m.p, m.e, ")");
362
		if(m.pr == nil || m.ps >= m.pe)
363
			break;
364
	}
365
	*m.p++ = '\n';
366
 
367
	if(write(1, p, m.p - p) < 0)
368
		sysfatal("stdout: %r");
369
}
370
 
371
Proto **xprotos;
372
int nprotos;
373
 
374
/* look up a protocol by its name */
375
Proto*
376
findproto(char *name)
377
{
378
	int i;
379
 
380
	for(i = 0; i < nprotos; i++)
381
		if(strcmp(xprotos[i]->name, name) == 0)
382
			return xprotos[i];
383
	return nil;
384
}
385
 
386
/*
387
 *  add an undefined protocol to protos[]
388
 */
389
Proto*
390
addproto(char *name)
391
{
392
	Proto *pr;
393
 
394
	xprotos = realloc(xprotos, (nprotos+1)*sizeof(Proto*));
395
	pr = malloc(sizeof *pr);
396
	*pr = dump;
397
	pr->name = name;
398
	xprotos[nprotos++] = pr;
399
	return pr;
400
}
401
 
402
/*
403
 *  build a graph of protocols, this could easily be circular.  This
404
 *  links together all the multiplexing in the protocol modules.
405
 */
406
void
407
mkprotograph(void)
408
{
409
	Proto **l;
410
	Proto *pr;
411
	Mux *m;
412
 
413
	/* copy protos into a reallocable area */
414
	for(nprotos = 0; protos[nprotos] != nil; nprotos++)
415
		;
416
	xprotos = malloc(nprotos*sizeof(Proto*));
417
	memmove(xprotos, protos, nprotos*sizeof(Proto*));
418
 
419
	for(l = protos; *l != nil; l++){
420
		pr = *l;
421
		for(m = pr->mux; m != nil && m->name != nil; m++){
422
			m->pr = findproto(m->name);
423
			if(m->pr == nil)
424
				m->pr = addproto(m->name);
425
		}
426
	}
427
}
428
 
429
/*
430
 *  add in a protocol node
431
 */
432
static Filter*
433
addnode(Filter *f, Proto *pr)
434
{
435
	Filter *nf;
436
	nf = newfilter();
437
	nf->pr = pr;
438
	nf->s = pr->name;
439
	nf->l = f;
440
	nf->op = WORD;
441
	return nf;
442
}
443
 
444
/*
445
 *  recurse through the protocol graph adding missing nodes
446
 *  to the filter if we reach the filter's protocol
447
 */
448
static Filter*
449
_fillin(Filter *f, Proto *last, int depth)
450
{
451
	Mux *m;
452
	Filter *nf;
453
 
454
	if(depth-- <= 0)
455
		return nil;
456
 
457
	for(m = last->mux; m != nil && m->name != nil; m++){
458
		if(m->pr == nil)
459
			continue;
460
		if(f->pr == m->pr)
461
			return f;
462
		nf = _fillin(f, m->pr, depth);
463
		if(nf != nil)
464
			return addnode(nf, m->pr);
465
	}
466
	return nil;
467
}
468
 
469
static Filter*
470
fillin(Filter *f, Proto *last)
471
{
472
	int i;
473
	Filter *nf;
474
 
475
	/* hack to make sure top level node is the root */
476
	if(last == nil){
477
		if(f->pr == root)
478
			return f;
479
		f = fillin(f, root);
480
		if(f == nil)
481
			return nil;
482
		return addnode(f, root);
483
	}
484
 
485
	/* breadth first search though the protocol graph */
486
	nf = f;
487
	for(i = 1; i < 20; i++){
488
		nf = _fillin(f, last, i);
489
		if(nf != nil)
490
			break;
491
	}
492
	return nf;
493
}
494
 
495
/*
496
 *  massage tree so that all paths from the root to a leaf
497
 *  contain a filter node for each header.
498
 *
499
 *  also, set f->pr where possible
500
 */
501
Filter*
502
complete(Filter *f, Proto *last)
503
{
504
	Proto *pr;
505
 
506
	if(f == nil)
507
		return f;
508
 
509
	/* do a depth first traversal of the filter tree */
510
	switch(f->op){
511
	case '!':
512
		f->l = complete(f->l, last);
513
		break;
514
	case LAND:
515
	case LOR:
516
		f->l = complete(f->l, last);
517
		f->r = complete(f->r, last);
518
		break;
519
	case '=':
520
		break;
521
	case WORD:
522
		pr = findproto(f->s);
523
		f->pr = pr;
524
		if(pr == nil){
525
			if(f->l != nil){
526
				fprint(2, "%s unknown proto, ignoring params\n",
527
					f->s);
528
				f->l = nil;
529
			}
530
		} else {
531
			f->l = complete(f->l, pr);
532
			f = fillin(f, last);
533
			if(f == nil)
534
				sysfatal("internal error: can't get to %s", pr->name);
535
		}
536
		break;
537
	}
538
	return f;
539
}
540
 
541
/*
542
 *  merge common nodes under | and & moving the merged node
543
 *  above the | or &.
544
 *
545
 *  do some constant foldong, e.g. `true & x' becomes x and
546
 *  'true | x' becomes true.
547
 */
548
static int changed;
549
 
550
static Filter*
551
_optimize(Filter *f)
552
{
553
	Filter *l;
554
 
555
	if(f == nil)
556
		return f;
557
 
558
	switch(f->op){
559
	case '!':
560
		/* is child also a not */
561
		if(f->l->op == '!'){
562
			changed = 1;
563
			return f->l->l;
564
		}
565
		break;
566
	case LOR:
567
		/* are two children the same protocol? */
568
		if(f->l->op != f->r->op || f->r->op != WORD
569
		|| f->l->pr != f->r->pr || f->l->pr == nil)
570
			break;	/* no optimization */
571
 
572
		changed = 1;
573
 
574
		/* constant folding */
575
		/* if either child is childless, just return that */
576
		if(f->l->l == nil)
577
			return f->l;
578
		else if(f->r->l == nil)
579
			return f->r;
580
 
581
		/* move the common node up, thow away one node */
582
		l = f->l;
583
		f->l = l->l;
584
		f->r = f->r->l;
585
		l->l = f;
586
		return l;
587
	case LAND:
588
		/* are two children the same protocol? */
589
		if(f->l->op != f->r->op || f->r->op != WORD
590
		|| f->l->pr != f->r->pr || f->l->pr == nil)
591
			break;	/* no optimization */
592
 
593
		changed = 1;
594
 
595
		/* constant folding */
596
		/* if either child is childless, ignore it */
597
		if(f->l->l == nil)
598
			return f->r;
599
		else if(f->r->l == nil)
600
			return f->l;
601
 
602
		/* move the common node up, thow away one node */
603
		l = f->l;
604
		f->l = _optimize(l->l);
605
		f->r = _optimize(f->r->l);
606
		l->l = f;
607
		return l;
608
	}
609
	f->l = _optimize(f->l);
610
	f->r = _optimize(f->r);
611
	return f;
612
}
613
 
614
Filter*
615
optimize(Filter *f)
616
{
617
	do{
618
		changed = 0;
619
		f = _optimize(f);
620
	}while(changed);
621
 
622
	return f;
623
}
624
 
625
/*
626
 *  find any top level nodes that aren't the root
627
 */
628
int
629
findbogus(Filter *f)
630
{
631
	int rv;
632
 
633
	if(f->op != WORD){
634
		rv = findbogus(f->l);
635
		if(f->r)
636
			rv |= findbogus(f->r);
637
		return rv;
638
	} else if(f->pr != root){
639
		fprint(2, "bad top-level protocol: %s\n", f->s);
640
		return 1;
641
	}
642
	return 0;
643
}
644
 
645
/*
646
 *  compile the filter
647
 */
648
static void
649
_compile(Filter *f, Proto *last)
650
{
651
	if(f == nil)
652
		return;
653
 
654
	switch(f->op){
655
	case '!':
656
		_compile(f->l, last);
657
		break;
658
	case LOR:
659
	case LAND:
660
		_compile(f->l, last);
661
		_compile(f->r, last);
662
		break;
663
	case WORD:
664
		if(last != nil){
665
			if(last->compile == nil)
666
				sysfatal("unknown %s subprotocol: %s", f->pr->name, f->s);
667
			(*last->compile)(f);
668
		}
669
		if(f->l)
670
			_compile(f->l, f->pr);
671
		break;
672
	case '=':
673
		if(last == nil)
674
			sysfatal("internal error: compilewalk: badly formed tree");
675
 
676
		if(last->compile == nil)
677
			sysfatal("unknown %s field: %s", f->pr->name, f->s);
678
		(*last->compile)(f);
679
		break;
680
	default:
681
		sysfatal("internal error: compilewalk op: %d", f->op);
682
	}
683
}
684
 
685
Filter*
686
compile(Filter *f)
687
{
688
	if(f == nil)
689
		return f;
690
 
691
	/* fill in the missing header filters */
692
	f = complete(f, nil);
693
 
694
	/* constant folding */
695
	f = optimize(f);
696
	if(!toflag)
697
		printfilter(f, "after optimize");
698
 
699
	/* protocol specific compilations */
700
	_compile(f, nil);
701
 
702
	/* at this point, the root had better be the root proto */
703
	if(findbogus(f)){
704
		fprint(2, "bogus filter\n");
705
		exits("bad filter");
706
	}
707
 
708
	return f;
709
}
710
 
711
/*
712
 *  parse a byte array
713
 */
714
int
715
parseba(uchar *to, char *from)
716
{
717
	char nip[4];
718
	char *p;
719
	int i;
720
 
721
	p = from;
722
	for(i = 0; i < 16; i++){
723
		if(*p == 0)
724
			return -1;
725
		nip[0] = *p++;
726
		if(*p == 0)
727
			return -1;
728
		nip[1] = *p++;
729
		nip[2] = 0;
730
		to[i] = strtoul(nip, 0, 16);
731
	}
732
	return i;
733
}
734
 
735
/*
736
 *  compile WORD = WORD, becomes a single node with a subop
737
 */
738
void
739
compile_cmp(char *proto, Filter *f, Field *fld)
740
{
741
	uchar x[IPaddrlen];
742
	char *v;
743
 
744
	if(f->op != '=')
745
		sysfatal("internal error: compile_cmp %s: not a cmp", proto);
746
 
747
	for(; fld->name != nil; fld++){
748
		if(strcmp(f->l->s, fld->name) == 0){
749
			f->op = WORD;
750
			f->subop = fld->subop;
751
			switch(fld->ftype){
752
			case Fnum:
753
				f->ulv = atoi(f->r->s);
754
				break;
755
			case Fether:
756
				v = csgetvalue(nil, "sys", (char*)f->r->s,
757
					"ether", 0);
758
				if(v){
759
					parseether(f->a, v);
760
					free(v);
761
				} else
762
					parseether(f->a, f->r->s);
763
				break;
764
			case Fv4ip:
765
				v = csgetvalue(nil, "sys", (char*)f->r->s,
766
					"ip", 0);
767
				if(v){
768
					f->ulv = parseip(x, v);
769
					free(v);
770
				}else
771
					f->ulv = parseip(x, f->r->s);
772
				break;
773
			case Fv6ip:
774
				v = csgetvalue(nil, "sys", (char*)f->r->s,
775
					"ipv6", 0);
776
				if(v){
777
					parseip(f->a, v);
778
					free(v);
779
				}else
780
					parseip(f->a, f->r->s);
781
				break;
782
			case Fba:
783
				parseba(f->a, f->r->s);
784
				break;
785
			default:
786
				sysfatal("internal error: compile_cmp %s: %d",
787
					proto, fld->ftype);
788
			}
789
			f->l = f->r = nil;
790
			return;
791
		}
792
	}
793
	sysfatal("unknown %s field in: %s = %s", proto, f->l->s, f->r->s);
794
}
795
 
796
void
797
_pf(Filter *f)
798
{
799
	char *s;
800
 
801
	if(f == nil)
802
		return;
803
 
804
	s = nil;
805
	switch(f->op){
806
	case '!':
807
		fprint(2, "!");
808
		_pf(f->l);
809
		break;
810
	case WORD:
811
		fprint(2, "%s", f->s);
812
		if(f->l != nil){
813
			fprint(2, "(");
814
			_pf(f->l);
815
			fprint(2, ")");
816
		}
817
		break;
818
	case LAND:
819
		s = "&&";
820
		goto print;
821
	case LOR:
822
		s = "||";
823
		goto print;
824
	case '=':
825
	print:
826
		_pf(f->l);
827
		if(s)
828
			fprint(2, " %s ", s);
829
		else
830
			fprint(2, " %c ", f->op);
831
		_pf(f->r);
832
		break;
833
	default:
834
		fprint(2, "???");
835
		break;
836
	}
837
}
838
 
839
void
840
printfilter(Filter *f, char *tag)
841
{
842
	fprint(2, "%s: ", tag);
843
	_pf(f);
844
	fprint(2, "\n");
845
}
846
 
847
void
848
cat(void)
849
{
850
	char buf[1024];
851
	int n;
852
 
853
	while((n = read(0, buf, sizeof buf)) > 0)
854
		write(1, buf, n);
855
}
856
 
857
static int fd1 = -1;
858
void
859
startmc(void)
860
{
861
	int p[2];
862
 
863
	if(fd1 == -1)
864
		fd1 = dup(1, -1);
865
 
866
	if(pipe(p) < 0)
867
		return;
868
	switch(fork()){
869
	case -1:
870
		return;
871
	default:
872
		close(p[0]);
873
		dup(p[1], 1);
874
		if(p[1] != 1)
875
			close(p[1]);
876
		return;
877
	case 0:
878
		close(p[1]);
879
		dup(p[0], 0);
880
		if(p[0] != 0)
881
			close(p[0]);
882
		execl("/bin/mc", "mc", nil);
883
		cat();
884
		_exits(0);
885
	}
886
}
887
 
888
void
889
stopmc(void)
890
{
891
	close(1);
892
	dup(fd1, 1);
893
	waitpid();
894
}
895
 
896
void
897
printhelp(char *name)
898
{
899
	int len;
900
	Proto *pr, **l;
901
	Mux *m;
902
	Field *f;
903
	char fmt[40];
904
 
905
	if(name == nil){
906
		print("protocols:\n");
907
		startmc();
908
		for(l=protos; (pr=*l) != nil; l++)
909
			print("  %s\n", pr->name);
910
		stopmc();
911
		return;
912
	}
913
 
914
	pr = findproto(name);
915
	if(pr == nil){
916
		print("unknown protocol %s\n", name);
917
		return;
918
	}
919
 
920
	if(pr->field){
921
		print("%s's filter attributes:\n", pr->name);
922
		len = 0;
923
		for(f=pr->field; f->name; f++)
924
			if(len < strlen(f->name))
925
				len = strlen(f->name);
926
		startmc();
927
		for(f=pr->field; f->name; f++)
928
			print("  %-*s - %s\n", len, f->name, f->help);
929
		stopmc();
930
	}
931
	if(pr->mux){
932
		print("%s's subprotos:\n", pr->name);
933
		startmc();
934
		snprint(fmt, sizeof fmt, "  %s %%s\n", pr->valfmt);
935
		for(m=pr->mux; m->name != nil; m++)
936
			print(fmt, m->val, m->name);
937
		stopmc();
938
	}
939
}
940
 
941
/*
942
 *  demultiplex to next prototol header
943
 */
944
void
945
demux(Mux *mx, ulong val1, ulong val2, Msg *m, Proto *def)
946
{
947
	m->pr = def;
948
	for(mx = mx; mx->name != nil; mx++){
949
		if(val1 == mx->val || val2 == mx->val){
950
			m->pr = mx->pr;
951
			break;
952
		}
953
	}
954
}
955
 
956
/*
957
 *  default framer just assumes the input packet is
958
 *  a single read
959
 */
960
int
961
defaultframer(int fd, uchar *pkt, int pktlen)
962
{
963
	return read(fd, pkt, pktlen);
964
}