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