vx32

Local 9vx git repository for patches.
git clone git://r-36.net/vx32
Log | Files | Refs

ipmux.c (15010B)


      1 /*
      2  * IP packet filter
      3  */
      4 #include "u.h"
      5 #include "lib.h"
      6 #include "mem.h"
      7 #include "dat.h"
      8 #include "fns.h"
      9 #include "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*
     99 skipwhite(char *p)
    100 {
    101 	while(*p == ' ' || *p == '\t')
    102 		p++;
    103 	return p;
    104 }
    105 
    106 static char*
    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*
    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 = (ulong)(ipoff->dst);
    132 		len = IPv4addrlen;
    133 		p += 3;
    134 	}
    135 	else if(strncmp(p, "src", 3) == 0){
    136 		type = Tsrc;
    137 		off = (ulong)(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 = (ulong)&(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
    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
    219 hextoi(char *p)
    220 {
    221 	return (htoi(p[0])<<4) | htoi(p[1]);
    222 }
    223 
    224 static void
    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*
    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
    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)*(ulong)ipoff->data) - 
    350 		(b->off+((int)b->skiphdr)*(ulong)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
    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
    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*
    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
    427 ipmuxfree(Ipmux *f)
    428 {
    429 	if(f->val != nil)
    430 		free(f->val);
    431 	free(f);
    432 }
    433 
    434 static void
    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*
    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
    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*
    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
    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
    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*
    610 ipmuxannounce(Conv* _, char** __, int ___)
    611 {
    612 	return "ipmux does not support announce";
    613 }
    614 
    615 static void
    616 ipmuxclose(Conv *c)
    617 {
    618 	Ipmux *i;
    619 	Ipmuxrock *r;
    620 	Fs *f = c->p->f;
    621 
    622 	r = (Ipmuxrock*)(c->ptcl);
    623 
    624 	qclose(c->rq);
    625 	qclose(c->wq);
    626 	qclose(c->eq);
    627 	ipmove(c->laddr, IPnoaddr);
    628 	ipmove(c->raddr, IPnoaddr);
    629 	c->lport = 0;
    630 	c->rport = 0;
    631 
    632 	WLOCK(f);
    633 	i = (Ipmux *)c->p->priv;
    634 	ipmuxremove(&i, r->chain);
    635 	WUNLOCK(f);
    636 	ipmuxtreefree(r->chain);
    637 	r->chain = nil;
    638 }
    639 
    640 /*
    641  *  takes a fully formed ip packet and just passes it down
    642  *  the stack
    643  */
    644 static void
    645 ipmuxkick(void *x)
    646 {
    647 	Conv *c = x;
    648 	Block *bp;
    649 
    650 	bp = qget(c->wq);
    651 	if(bp != nil) {
    652 		Myip4hdr *ih4 = (Myip4hdr*)(bp->rp);
    653 
    654 		if((ih4->vihl & 0xF0) != IP_VER6)
    655 			ipoput4(c->p->f, bp, 0, ih4->ttl, ih4->tos, nil);
    656 		else
    657 			ipoput6(c->p->f, bp, 0, ((Ip6hdr*)ih4)->ttl, 0, nil);
    658 	}
    659 }
    660 
    661 static void
    662 ipmuxiput(Proto *p, Ipifc *ifc, Block *bp)
    663 {
    664 	int len, hl;
    665 	Fs *f = p->f;
    666 	uchar *m, *h, *v, *e, *ve, *hp;
    667 	Conv *c;
    668 	Ipmux *mux;
    669 	Myip4hdr *ip;
    670 	Ip6hdr *ip6;
    671 
    672 	ip = (Myip4hdr*)bp->rp;
    673 	hl = (ip->vihl&0x0F)<<2;
    674 
    675 	if(p->priv == nil)
    676 		goto nomatch;
    677 
    678 	h = bp->rp;
    679 	len = BLEN(bp);
    680 
    681 	/* run the v4 filter */
    682 	RLOCK(f);
    683 	c = nil;
    684 	mux = f->ipmux->priv;
    685 	while(mux != nil){
    686 		if(mux->eoff > len){
    687 			mux = mux->no;
    688 			continue;
    689 		}
    690 		hp = h + mux->off + ((int)mux->skiphdr)*hl;
    691 		switch(mux->ctype){
    692 		case Cbyte:
    693 			if(*mux->val == *hp)
    694 				goto yes;
    695 			break;
    696 		case Cmbyte:
    697 			if((*hp & *mux->mask) == *mux->val)
    698 				goto yes;
    699 			break;
    700 		case Cshort:
    701 			if(*((ushort*)mux->val) == *(ushort*)hp)
    702 				goto yes;
    703 			break;
    704 		case Cmshort:
    705 			if((*(ushort*)hp & (*((ushort*)mux->mask))) == *((ushort*)mux->val))
    706 				goto yes;
    707 			break;
    708 		case Clong:
    709 			if(*((ulong*)mux->val) == *(ulong*)hp)
    710 				goto yes;
    711 			break;
    712 		case Cmlong:
    713 			if((*(ulong*)hp & (*((ulong*)mux->mask))) == *((ulong*)mux->val))
    714 				goto yes;
    715 			break;
    716 		case Cifc:
    717 			if(*((ulong*)mux->val) == *(ulong*)(ifc->lifc->local + IPv4off))
    718 				goto yes;
    719 			break;
    720 		case Cmifc:
    721 			if((*(ulong*)(ifc->lifc->local + IPv4off) & (*((ulong*)mux->mask))) == *((ulong*)mux->val))
    722 				goto yes;
    723 			break;
    724 		default:
    725 			v = mux->val;
    726 			for(e = mux->e; v < e; v = ve){
    727 				m = mux->mask;
    728 				hp = h + mux->off;
    729 				for(ve = v + mux->len; v < ve; v++){
    730 					if((*hp++ & *m++) != *v)
    731 						break;
    732 				}
    733 				if(v == ve)
    734 					goto yes;
    735 			}
    736 		}
    737 		mux = mux->no;
    738 		continue;
    739 yes:
    740 		if(mux->conv != nil)
    741 			c = mux->conv;
    742 		mux = mux->yes;
    743 	}
    744 	RUNLOCK(f);
    745 
    746 	if(c != nil){
    747 		/* tack on interface address */
    748 		bp = padblock(bp, IPaddrlen);
    749 		ipmove(bp->rp, ifc->lifc->local);
    750 		bp = concatblock(bp);
    751 		if(bp != nil)
    752 			if(qpass(c->rq, bp) < 0)
    753 				print("Q");
    754 		return;
    755 	}
    756 
    757 nomatch:
    758 	/* doesn't match any filter, hand it to the specific protocol handler */
    759 	ip = (Myip4hdr*)bp->rp;
    760 	if((ip->vihl & 0xF0) == IP_VER4) {
    761 		p = f->t2p[ip->proto];
    762 	} else {
    763 		ip6 = (Ip6hdr*)bp->rp;
    764 		p = f->t2p[ip6->proto];
    765 	}
    766 	if(p && p->rcv)
    767 		(*p->rcv)(p, ifc, bp);
    768 	else
    769 		freeblist(bp);
    770 	return;
    771 }
    772 
    773 static int
    774 ipmuxsprint(Ipmux *mux, int level, char *buf, int len)
    775 {
    776 	int i, j, n;
    777 	uchar *v;
    778 
    779 	n = 0;
    780 	for(i = 0; i < level; i++)
    781 		n += snprint(buf+n, len-n, " ");
    782 	if(mux == nil){
    783 		n += snprint(buf+n, len-n, "\n");
    784 		return n;
    785 	}
    786 	n += snprint(buf+n, len-n, "h[%d:%d]&", 
    787                mux->off+((int)mux->skiphdr)*((int)ipoff->data), 
    788                mux->off+(((int)mux->skiphdr)*((int)ipoff->data))+mux->len-1);
    789 	for(i = 0; i < mux->len; i++)
    790 		n += snprint(buf+n, len - n, "%2.2ux", mux->mask[i]);
    791 	n += snprint(buf+n, len-n, "=");
    792 	v = mux->val;
    793 	for(j = 0; j < mux->n; j++){
    794 		for(i = 0; i < mux->len; i++)
    795 			n += snprint(buf+n, len - n, "%2.2ux", *v++);
    796 		n += snprint(buf+n, len-n, "|");
    797 	}
    798 	n += snprint(buf+n, len-n, "\n");
    799 	level++;
    800 	n += ipmuxsprint(mux->no, level, buf+n, len-n);
    801 	n += ipmuxsprint(mux->yes, level, buf+n, len-n);
    802 	return n;
    803 }
    804 
    805 static int
    806 ipmuxstats(Proto *p, char *buf, int len)
    807 {
    808 	int n;
    809 	Fs *f = p->f;
    810 
    811 	RLOCK(f);
    812 	n = ipmuxsprint(p->priv, 0, buf, len);
    813 	RUNLOCK(f);
    814 
    815 	return n;
    816 }
    817 
    818 void
    819 ipmuxinit(Fs *f)
    820 {
    821 	Proto *ipmux;
    822 
    823 	ipmux = smalloc(sizeof(Proto));
    824 	ipmux->priv = nil;
    825 	ipmux->name = "ipmux";
    826 	ipmux->connect = ipmuxconnect;
    827 	ipmux->announce = ipmuxannounce;
    828 	ipmux->state = ipmuxstate;
    829 	ipmux->create = ipmuxcreate;
    830 	ipmux->close = ipmuxclose;
    831 	ipmux->rcv = ipmuxiput;
    832 	ipmux->ctl = nil;
    833 	ipmux->advise = nil;
    834 	ipmux->stats = ipmuxstats;
    835 	ipmux->ipproto = -1;
    836 	ipmux->nc = 64;
    837 	ipmux->ptclsize = sizeof(Ipmuxrock);
    838 
    839 	f->ipmux = ipmux;			/* hack for Fsrcvpcol */
    840 
    841 	Fsproto(f, ipmux);
    842 }