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