xref: /plan9-contrib/sys/src/9k/ip/ipmux.c (revision 9ef1f84b659abcb917c5c090acbce0772e494f21)
1 /*
2  * IP packet filter
3  */
4 #include "u.h"
5 #include "../port/lib.h"
6 #include "mem.h"
7 #include "dat.h"
8 #include "fns.h"
9 #include "../port/error.h"
10 
11 #include "ip.h"
12 #include "ipv6.h"
13 
14 typedef struct Ipmuxrock  Ipmuxrock;
15 typedef struct Ipmux      Ipmux;
16 
17 typedef struct Myip4hdr Myip4hdr;
18 struct Myip4hdr
19 {
20 	uchar	vihl;		/* Version and header length */
21 	uchar	tos;		/* Type of service */
22 	uchar	length[2];	/* packet length */
23 	uchar	id[2];		/* ip->identification */
24 	uchar	frag[2];	/* Fragment information */
25 	uchar	ttl;		/* Time to live */
26 	uchar	proto;		/* Protocol */
27 	uchar	cksum[2];	/* Header checksum */
28 	uchar	src[4];		/* IP source */
29 	uchar	dst[4];		/* IP destination */
30 
31 	uchar	data[1];	/* start of data */
32 };
33 Myip4hdr *ipoff = 0;
34 
35 enum
36 {
37 	Tproto,
38 	Tdata,
39 	Tiph,
40 	Tdst,
41 	Tsrc,
42 	Tifc,
43 
44 	Cother = 0,
45 	Cbyte,		/* single byte */
46 	Cmbyte,		/* single byte with mask */
47 	Cshort,		/* single short */
48 	Cmshort,	/* single short with mask */
49 	Clong,		/* single long */
50 	Cmlong,		/* single long with mask */
51 	Cifc,
52 	Cmifc,
53 };
54 
55 char *ftname[] =
56 {
57 [Tproto]	"proto",
58 [Tdata]		"data",
59 [Tiph]	 	"iph",
60 [Tdst]		"dst",
61 [Tsrc]		"src",
62 [Tifc]		"ifc",
63 };
64 
65 /*
66  *  a node in the decision tree
67  */
68 struct Ipmux
69 {
70 	Ipmux	*yes;
71 	Ipmux	*no;
72 	uchar	type;		/* type of field(Txxxx) */
73 	uchar	ctype;		/* tupe of comparison(Cxxxx) */
74 	uchar	len;		/* length in bytes of item to compare */
75 	uchar	n;		/* number of items val points to */
76 	short	off;		/* offset of comparison */
77 	short	eoff;		/* end offset of comparison */
78 	uchar	skiphdr;	/* should offset start after ipheader */
79 	uchar	*val;
80 	uchar	*mask;
81 	uchar	*e;		/* val+n*len*/
82 
83 	int	ref;		/* so we can garbage collect */
84 	Conv	*conv;
85 };
86 
87 /*
88  *  someplace to hold per conversation data
89  */
90 struct Ipmuxrock
91 {
92 	Ipmux	*chain;
93 };
94 
95 static int	ipmuxsprint(Ipmux*, int, char*, int);
96 static void	ipmuxkick(void *x);
97 
98 static char*
skipwhite(char * p)99 skipwhite(char *p)
100 {
101 	while(*p == ' ' || *p == '\t')
102 		p++;
103 	return p;
104 }
105 
106 static char*
follows(char * p,char c)107 follows(char *p, char c)
108 {
109 	char *f;
110 
111 	f = strchr(p, c);
112 	if(f == nil)
113 		return nil;
114 	*f++ = 0;
115 	f = skipwhite(f);
116 	if(*f == 0)
117 		return nil;
118 	return f;
119 }
120 
121 static Ipmux*
parseop(char ** pp)122 parseop(char **pp)
123 {
124 	char *p = *pp;
125 	int type, off, end, len;
126 	Ipmux *f;
127 
128 	p = skipwhite(p);
129 	if(strncmp(p, "dst", 3) == 0){
130 		type = Tdst;
131 		off = (uintptr)(ipoff->dst);
132 		len = IPv4addrlen;
133 		p += 3;
134 	}
135 	else if(strncmp(p, "src", 3) == 0){
136 		type = Tsrc;
137 		off = (uintptr)(ipoff->src);
138 		len = IPv4addrlen;
139 		p += 3;
140 	}
141 	else if(strncmp(p, "ifc", 3) == 0){
142 		type = Tifc;
143 		off = -IPv4addrlen;
144 		len = IPv4addrlen;
145 		p += 3;
146 	}
147 	else if(strncmp(p, "proto", 5) == 0){
148 		type = Tproto;
149 		off = (uintptr)&(ipoff->proto);
150 		len = 1;
151 		p += 5;
152 	}
153 	else if(strncmp(p, "data", 4) == 0 || strncmp(p, "iph", 3) == 0){
154 		if(strncmp(p, "data", 4) == 0) {
155 			type = Tdata;
156 			p += 4;
157 		}
158 		else {
159 			type = Tiph;
160 			p += 3;
161 		}
162 		p = skipwhite(p);
163 		if(*p != '[')
164 			return nil;
165 		p++;
166 		off = strtoul(p, &p, 0);
167 		if(off < 0 || off > (64-IP4HDR))
168 			return nil;
169 		p = skipwhite(p);
170 		if(*p != ':')
171 			end = off;
172 		else {
173 			p++;
174 			p = skipwhite(p);
175 			end = strtoul(p, &p, 0);
176 			if(end < off)
177 				return nil;
178 			p = skipwhite(p);
179 		}
180 		if(*p != ']')
181 			return nil;
182 		p++;
183 		len = end - off + 1;
184 	}
185 	else
186 		return nil;
187 
188 	f = smalloc(sizeof(*f));
189 	f->type = type;
190 	f->len = len;
191 	f->off = off;
192 	f->val = nil;
193 	f->mask = nil;
194 	f->n = 1;
195 	f->ref = 1;
196 	if(type == Tdata)
197 		f->skiphdr = 1;
198 	else
199 		f->skiphdr = 0;
200 
201 	return f;
202 }
203 
204 static int
htoi(char x)205 htoi(char x)
206 {
207 	if(x >= '0' && x <= '9')
208 		x -= '0';
209 	else if(x >= 'a' && x <= 'f')
210 		x -= 'a' - 10;
211 	else if(x >= 'A' && x <= 'F')
212 		x -= 'A' - 10;
213 	else
214 		x = 0;
215 	return x;
216 }
217 
218 static int
hextoi(char * p)219 hextoi(char *p)
220 {
221 	return (htoi(p[0])<<4) | htoi(p[1]);
222 }
223 
224 static void
parseval(uchar * v,char * p,int len)225 parseval(uchar *v, char *p, int len)
226 {
227 	while(*p && len-- > 0){
228 		*v++ = hextoi(p);
229 		p += 2;
230 	}
231 }
232 
233 static Ipmux*
parsemux(char * p)234 parsemux(char *p)
235 {
236 	int n, nomask;
237 	Ipmux *f;
238 	char *val;
239 	char *mask;
240 	char *vals[20];
241 	uchar *v;
242 
243 	/* parse operand */
244 	f = parseop(&p);
245 	if(f == nil)
246 		return nil;
247 
248 	/* find value */
249 	val = follows(p, '=');
250 	if(val == nil)
251 		goto parseerror;
252 
253 	/* parse mask */
254 	mask = follows(p, '&');
255 	if(mask != nil){
256 		switch(f->type){
257 		case Tsrc:
258 		case Tdst:
259 		case Tifc:
260 			f->mask = smalloc(f->len);
261 			v4parseip(f->mask, mask);
262 			break;
263 		case Tdata:
264 		case Tiph:
265 			f->mask = smalloc(f->len);
266 			parseval(f->mask, mask, f->len);
267 			break;
268 		default:
269 			goto parseerror;
270 		}
271 		nomask = 0;
272 	} else {
273 		nomask = 1;
274 		f->mask = smalloc(f->len);
275 		memset(f->mask, 0xff, f->len);
276 	}
277 
278 	/* parse vals */
279 	f->n = getfields(val, vals, sizeof(vals)/sizeof(char*), 1, "|");
280 	if(f->n == 0)
281 		goto parseerror;
282 	f->val = smalloc(f->n*f->len);
283 	v = f->val;
284 	for(n = 0; n < f->n; n++){
285 		switch(f->type){
286 		case Tsrc:
287 		case Tdst:
288 		case Tifc:
289 			v4parseip(v, vals[n]);
290 			break;
291 		case Tproto:
292 		case Tdata:
293 		case Tiph:
294 			parseval(v, vals[n], f->len);
295 			break;
296 		}
297 		v += f->len;
298 	}
299 
300 	f->eoff = f->off + f->len;
301 	f->e = f->val + f->n*f->len;
302 	f->ctype = Cother;
303 	if(f->n == 1){
304 		switch(f->len){
305 		case 1:
306 			f->ctype = nomask ? Cbyte : Cmbyte;
307 			break;
308 		case 2:
309 			f->ctype = nomask ? Cshort : Cmshort;
310 			break;
311 		case 4:
312 			if(f->type == Tifc)
313 				f->ctype = nomask ? Cifc : Cmifc;
314 			else
315 				f->ctype = nomask ? Clong : Cmlong;
316 			break;
317 		}
318 	}
319 	return f;
320 
321 parseerror:
322 	if(f->mask)
323 		free(f->mask);
324 	if(f->val)
325 		free(f->val);
326 	free(f);
327 	return nil;
328 }
329 
330 /*
331  *  Compare relative ordering of two ipmuxs.  This doesn't compare the
332  *  values, just the fields being looked at.
333  *
334  *  returns:	<0 if a is a more specific match
335  *		 0 if a and b are matching on the same fields
336  *		>0 if b is a more specific match
337  */
338 static int
ipmuxcmp(Ipmux * a,Ipmux * b)339 ipmuxcmp(Ipmux *a, Ipmux *b)
340 {
341 	int n;
342 
343 	/* compare types, lesser ones are more important */
344 	n = a->type - b->type;
345 	if(n != 0)
346 		return n;
347 
348 	/* compare offsets, call earlier ones more specific */
349 	n = (a->off+((int)a->skiphdr)*(uintptr)ipoff->data) -
350 		(b->off+((int)b->skiphdr)*(uintptr)ipoff->data);
351 	if(n != 0)
352 		return n;
353 
354 	/* compare match lengths, longer ones are more specific */
355 	n = b->len - a->len;
356 	if(n != 0)
357 		return n;
358 
359 	/*
360 	 *  if we get here we have two entries matching
361 	 *  the same bytes of the record.  Now check
362 	 *  the mask for equality.  Longer masks are
363 	 *  more specific.
364 	 */
365 	if(a->mask != nil && b->mask == nil)
366 		return -1;
367 	if(a->mask == nil && b->mask != nil)
368 		return 1;
369 	if(a->mask != nil && b->mask != nil){
370 		n = memcmp(b->mask, a->mask, a->len);
371 		if(n != 0)
372 			return n;
373 	}
374 	return 0;
375 }
376 
377 /*
378  *  Compare the values of two ipmuxs.  We're assuming that ipmuxcmp
379  *  returned 0 comparing them.
380  */
381 static int
ipmuxvalcmp(Ipmux * a,Ipmux * b)382 ipmuxvalcmp(Ipmux *a, Ipmux *b)
383 {
384 	int n;
385 
386 	n = b->len*b->n - a->len*a->n;
387 	if(n != 0)
388 		return n;
389 	return memcmp(a->val, b->val, a->len*a->n);
390 }
391 
392 /*
393  *  add onto an existing ipmux chain in the canonical comparison
394  *  order
395  */
396 static void
ipmuxchain(Ipmux ** l,Ipmux * f)397 ipmuxchain(Ipmux **l, Ipmux *f)
398 {
399 	for(; *l; l = &(*l)->yes)
400 		if(ipmuxcmp(f, *l) < 0)
401 			break;
402 	f->yes = *l;
403 	*l = f;
404 }
405 
406 /*
407  *  copy a tree
408  */
409 static Ipmux*
ipmuxcopy(Ipmux * f)410 ipmuxcopy(Ipmux *f)
411 {
412 	Ipmux *nf;
413 
414 	if(f == nil)
415 		return nil;
416 	nf = smalloc(sizeof *nf);
417 	*nf = *f;
418 	nf->no = ipmuxcopy(f->no);
419 	nf->yes = ipmuxcopy(f->yes);
420 	nf->val = smalloc(f->n*f->len);
421 	nf->e = nf->val + f->len*f->n;
422 	memmove(nf->val, f->val, f->n*f->len);
423 	return nf;
424 }
425 
426 static void
ipmuxfree(Ipmux * f)427 ipmuxfree(Ipmux *f)
428 {
429 	if(f->val != nil)
430 		free(f->val);
431 	free(f);
432 }
433 
434 static void
ipmuxtreefree(Ipmux * f)435 ipmuxtreefree(Ipmux *f)
436 {
437 	if(f == nil)
438 		return;
439 	if(f->no != nil)
440 		ipmuxfree(f->no);
441 	if(f->yes != nil)
442 		ipmuxfree(f->yes);
443 	ipmuxfree(f);
444 }
445 
446 /*
447  *  merge two trees
448  */
449 static Ipmux*
ipmuxmerge(Ipmux * a,Ipmux * b)450 ipmuxmerge(Ipmux *a, Ipmux *b)
451 {
452 	int n;
453 	Ipmux *f;
454 
455 	if(a == nil)
456 		return b;
457 	if(b == nil)
458 		return a;
459 	n = ipmuxcmp(a, b);
460 	if(n < 0){
461 		f = ipmuxcopy(b);
462 		a->yes = ipmuxmerge(a->yes, b);
463 		a->no = ipmuxmerge(a->no, f);
464 		return a;
465 	}
466 	if(n > 0){
467 		f = ipmuxcopy(a);
468 		b->yes = ipmuxmerge(b->yes, a);
469 		b->no = ipmuxmerge(b->no, f);
470 		return b;
471 	}
472 	if(ipmuxvalcmp(a, b) == 0){
473 		a->yes = ipmuxmerge(a->yes, b->yes);
474 		a->no = ipmuxmerge(a->no, b->no);
475 		a->ref++;
476 		ipmuxfree(b);
477 		return a;
478 	}
479 	a->no = ipmuxmerge(a->no, b);
480 	return a;
481 }
482 
483 /*
484  *  remove a chain from a demux tree.  This is like merging accept that
485  *  we remove instead of insert.
486  */
487 static int
ipmuxremove(Ipmux ** l,Ipmux * f)488 ipmuxremove(Ipmux **l, Ipmux *f)
489 {
490 	int n, rv;
491 	Ipmux *ft;
492 
493 	if(f == nil)
494 		return 0;		/* we've removed it all */
495 	if(*l == nil)
496 		return -1;
497 
498 	ft = *l;
499 	n = ipmuxcmp(ft, f);
500 	if(n < 0){
501 		/* *l is maching an earlier field, descend both paths */
502 		rv = ipmuxremove(&ft->yes, f);
503 		rv += ipmuxremove(&ft->no, f);
504 		return rv;
505 	}
506 	if(n > 0){
507 		/* f represents an earlier field than *l, this should be impossible */
508 		return -1;
509 	}
510 
511 	/* if we get here f and *l are comparing the same fields */
512 	if(ipmuxvalcmp(ft, f) != 0){
513 		/* different values mean mutually exclusive */
514 		return ipmuxremove(&ft->no, f);
515 	}
516 
517 	/* we found a match */
518 	if(--(ft->ref) == 0){
519 		/*
520 		 *  a dead node implies the whole yes side is also dead.
521 		 *  since our chain is constrained to be on that side,
522 		 *  we're done.
523 		 */
524 		ipmuxtreefree(ft->yes);
525 		*l = ft->no;
526 		ipmuxfree(ft);
527 		return 0;
528 	}
529 
530 	/*
531 	 *  free the rest of the chain.  it is constrained to match the
532 	 *  yes side.
533 	 */
534 	return ipmuxremove(&ft->yes, f->yes);
535 }
536 
537 /*
538  *  connection request is a semi separated list of filters
539  *  e.g. proto=17;data[0:4]=11aa22bb;ifc=135.104.9.2&255.255.255.0
540  *
541  *  there's no protection against overlapping specs.
542  */
543 static char*
ipmuxconnect(Conv * c,char ** argv,int argc)544 ipmuxconnect(Conv *c, char **argv, int argc)
545 {
546 	int i, n;
547 	char *field[10];
548 	Ipmux *mux, *chain;
549 	Ipmuxrock *r;
550 	Fs *f;
551 
552 	f = c->p->f;
553 
554 	if(argc != 2)
555 		return Ebadarg;
556 
557 	n = getfields(argv[1], field, nelem(field), 1, ";");
558 	if(n <= 0)
559 		return Ebadarg;
560 
561 	chain = nil;
562 	mux = nil;
563 	for(i = 0; i < n; i++){
564 		mux = parsemux(field[i]);
565 		if(mux == nil){
566 			ipmuxtreefree(chain);
567 			return Ebadarg;
568 		}
569 		ipmuxchain(&chain, mux);
570 	}
571 	if(chain == nil)
572 		return Ebadarg;
573 	mux->conv = c;
574 
575 	/* save a copy of the chain so we can later remove it */
576 	mux = ipmuxcopy(chain);
577 	r = (Ipmuxrock*)(c->ptcl);
578 	r->chain = chain;
579 
580 	/* add the chain to the protocol demultiplexor tree */
581 	wlock(f);
582 	f->ipmux->priv = ipmuxmerge(f->ipmux->priv, mux);
583 	wunlock(f);
584 
585 	Fsconnected(c, nil);
586 	return nil;
587 }
588 
589 static int
ipmuxstate(Conv * c,char * state,int n)590 ipmuxstate(Conv *c, char *state, int n)
591 {
592 	Ipmuxrock *r;
593 
594 	r = (Ipmuxrock*)(c->ptcl);
595 	return ipmuxsprint(r->chain, 0, state, n);
596 }
597 
598 static void
ipmuxcreate(Conv * c)599 ipmuxcreate(Conv *c)
600 {
601 	Ipmuxrock *r;
602 
603 	c->rq = qopen(64*1024, Qmsg, 0, c);
604 	c->wq = qopen(64*1024, Qkick, ipmuxkick, c);
605 	r = (Ipmuxrock*)(c->ptcl);
606 	r->chain = nil;
607 }
608 
609 static char*
ipmuxannounce(Conv *,char **,int)610 ipmuxannounce(Conv*, char**, int)
611 {
612 	return "ipmux does not support announce";
613 }
614 
615 static void
ipmuxclose(Conv * c)616 ipmuxclose(Conv *c)
617 {
618 	Ipmuxrock *r;
619 	Fs *f = c->p->f;
620 
621 	r = (Ipmuxrock*)(c->ptcl);
622 
623 	qclose(c->rq);
624 	qclose(c->wq);
625 	qclose(c->eq);
626 	ipmove(c->laddr, IPnoaddr);
627 	ipmove(c->raddr, IPnoaddr);
628 	c->lport = 0;
629 	c->rport = 0;
630 
631 	wlock(f);
632 	ipmuxremove(&(c->p->priv), r->chain);
633 	wunlock(f);
634 	ipmuxtreefree(r->chain);
635 	r->chain = nil;
636 }
637 
638 /*
639  *  takes a fully formed ip packet and just passes it down
640  *  the stack
641  */
642 static void
ipmuxkick(void * x)643 ipmuxkick(void *x)
644 {
645 	Conv *c = x;
646 	Block *bp;
647 
648 	bp = qget(c->wq);
649 	if(bp != nil) {
650 		Myip4hdr *ih4 = (Myip4hdr*)(bp->rp);
651 
652 		if((ih4->vihl & 0xF0) != IP_VER6)
653 			ipoput4(c->p->f, bp, 0, ih4->ttl, ih4->tos, nil);
654 		else
655 			ipoput6(c->p->f, bp, 0, ((Ip6hdr*)ih4)->ttl, 0, nil);
656 	}
657 }
658 
659 static void
ipmuxiput(Proto * p,Ipifc * ifc,Block * bp)660 ipmuxiput(Proto *p, Ipifc *ifc, Block *bp)
661 {
662 	int len, hl;
663 	Fs *f = p->f;
664 	uchar *m, *h, *v, *e, *ve, *hp;
665 	Conv *c;
666 	Ipmux *mux;
667 	Myip4hdr *ip;
668 	Ip6hdr *ip6;
669 
670 	ip = (Myip4hdr*)bp->rp;
671 	hl = (ip->vihl&0x0F)<<2;
672 
673 	if(p->priv == nil)
674 		goto nomatch;
675 
676 	h = bp->rp;
677 	len = BLEN(bp);
678 
679 	/* run the v4 filter */
680 	rlock(f);
681 	c = nil;
682 	mux = f->ipmux->priv;
683 	while(mux != nil){
684 		if(mux->eoff > len){
685 			mux = mux->no;
686 			continue;
687 		}
688 		hp = h + mux->off + ((int)mux->skiphdr)*hl;
689 		switch(mux->ctype){
690 		case Cbyte:
691 			if(*mux->val == *hp)
692 				goto yes;
693 			break;
694 		case Cmbyte:
695 			if((*hp & *mux->mask) == *mux->val)
696 				goto yes;
697 			break;
698 		case Cshort:
699 			if(*((ushort*)mux->val) == *(ushort*)hp)
700 				goto yes;
701 			break;
702 		case Cmshort:
703 			if((*(ushort*)hp & (*((ushort*)mux->mask))) == *((ushort*)mux->val))
704 				goto yes;
705 			break;
706 		case Clong:
707 			if(*((ulong*)mux->val) == *(ulong*)hp)
708 				goto yes;
709 			break;
710 		case Cmlong:
711 			if((*(ulong*)hp & (*((ulong*)mux->mask))) == *((ulong*)mux->val))
712 				goto yes;
713 			break;
714 		case Cifc:
715 			if(*((ulong*)mux->val) == *(ulong*)(ifc->lifc->local + IPv4off))
716 				goto yes;
717 			break;
718 		case Cmifc:
719 			if((*(ulong*)(ifc->lifc->local + IPv4off) & (*((ulong*)mux->mask))) == *((ulong*)mux->val))
720 				goto yes;
721 			break;
722 		default:
723 			v = mux->val;
724 			for(e = mux->e; v < e; v = ve){
725 				m = mux->mask;
726 				hp = h + mux->off;
727 				for(ve = v + mux->len; v < ve; v++){
728 					if((*hp++ & *m++) != *v)
729 						break;
730 				}
731 				if(v == ve)
732 					goto yes;
733 			}
734 		}
735 		mux = mux->no;
736 		continue;
737 yes:
738 		if(mux->conv != nil)
739 			c = mux->conv;
740 		mux = mux->yes;
741 	}
742 	runlock(f);
743 
744 	if(c != nil){
745 		/* tack on interface address */
746 		bp = padblock(bp, IPaddrlen);
747 		ipmove(bp->rp, ifc->lifc->local);
748 		bp = concatblock(bp);
749 		if(bp != nil)
750 			if(qpass(c->rq, bp) < 0)
751 				print("Q");
752 		return;
753 	}
754 
755 nomatch:
756 	/* doesn't match any filter, hand it to the specific protocol handler */
757 	ip = (Myip4hdr*)bp->rp;
758 	if((ip->vihl & 0xF0) == IP_VER4) {
759 		p = f->t2p[ip->proto];
760 	} else {
761 		ip6 = (Ip6hdr*)bp->rp;
762 		p = f->t2p[ip6->proto];
763 	}
764 	if(p && p->rcv)
765 		(*p->rcv)(p, ifc, bp);
766 	else
767 		freeblist(bp);
768 	return;
769 }
770 
771 static int
ipmuxsprint(Ipmux * mux,int level,char * buf,int len)772 ipmuxsprint(Ipmux *mux, int level, char *buf, int len)
773 {
774 	int i, j, n;
775 	uchar *v;
776 
777 	n = 0;
778 	for(i = 0; i < level; i++)
779 		n += snprint(buf+n, len-n, " ");
780 	if(mux == nil){
781 		n += snprint(buf+n, len-n, "\n");
782 		return n;
783 	}
784 	n += snprint(buf+n, len-n, "h[%lld:%lld]&",
785                mux->off+((int)mux->skiphdr)*((uintptr)ipoff->data),
786                mux->off+(((int)mux->skiphdr)*((uintptr)ipoff->data))+mux->len-1);
787 	for(i = 0; i < mux->len; i++)
788 		n += snprint(buf+n, len - n, "%2.2ux", mux->mask[i]);
789 	n += snprint(buf+n, len-n, "=");
790 	v = mux->val;
791 	for(j = 0; j < mux->n; j++){
792 		for(i = 0; i < mux->len; i++)
793 			n += snprint(buf+n, len - n, "%2.2ux", *v++);
794 		n += snprint(buf+n, len-n, "|");
795 	}
796 	n += snprint(buf+n, len-n, "\n");
797 	level++;
798 	n += ipmuxsprint(mux->no, level, buf+n, len-n);
799 	n += ipmuxsprint(mux->yes, level, buf+n, len-n);
800 	return n;
801 }
802 
803 static int
ipmuxstats(Proto * p,char * buf,int len)804 ipmuxstats(Proto *p, char *buf, int len)
805 {
806 	int n;
807 	Fs *f = p->f;
808 
809 	rlock(f);
810 	n = ipmuxsprint(p->priv, 0, buf, len);
811 	runlock(f);
812 
813 	return n;
814 }
815 
816 void
ipmuxinit(Fs * f)817 ipmuxinit(Fs *f)
818 {
819 	Proto *ipmux;
820 
821 	ipmux = smalloc(sizeof(Proto));
822 	ipmux->priv = nil;
823 	ipmux->name = "ipmux";
824 	ipmux->connect = ipmuxconnect;
825 	ipmux->announce = ipmuxannounce;
826 	ipmux->state = ipmuxstate;
827 	ipmux->create = ipmuxcreate;
828 	ipmux->close = ipmuxclose;
829 	ipmux->rcv = ipmuxiput;
830 	ipmux->ctl = nil;
831 	ipmux->advise = nil;
832 	ipmux->stats = ipmuxstats;
833 	ipmux->ipproto = -1;
834 	ipmux->nc = 64;
835 	ipmux->ptclsize = sizeof(Ipmuxrock);
836 
837 	f->ipmux = ipmux;			/* hack for Fsrcvpcol */
838 
839 	Fsproto(f, ipmux);
840 }
841