Fix a bunch of %lud and %lux
[akaros.git] / kern / src / net / ipmux.c
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, "0x%2.2x", 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, "0x%2.2x", *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 }