/* * Generic Routing Encapsulation over IPv4, rfc1702 */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "ip.h" enum { GRE_IPONLY = 12, /* size of ip header */ GRE_IPPLUSGRE = 12, /* minimum size of GRE header */ IP_GREPROTO = 47, GRErxms = 200, GREtickms = 100, GREmaxxmit = 10, K = 1024, GREqlen = 256 * K, GRE_cksum = 0x8000, GRE_routing = 0x4000, GRE_key = 0x2000, GRE_seq = 0x1000, Nring = 1 << 10, /* power of two, please */ Ringmask = Nring - 1, GREctlraw = 0, GREctlcooked, GREctlretunnel, GREctlreport, GREctldlsuspend, GREctlulsuspend, GREctldlresume, GREctlulresume, GREctlforward, GREctlulkey, Ncmds, }; typedef struct GREhdr GREhdr; struct GREhdr{ /* ip header */ uchar vihl; /* Version and header length */ uchar tos; /* Type of service */ uchar len[2]; /* packet length (including headers) */ uchar id[2]; /* Identification */ uchar frag[2]; /* Fragment information */ uchar ttl; uchar proto; /* Protocol */ uchar cksum[2]; /* checksum */ uchar src[4]; /* Ip source */ uchar dst[4]; /* Ip destination */ /* gre header */ uchar flags[2]; uchar eproto[2]; /* encapsulation protocol */ }; typedef struct GREpriv GREpriv; struct GREpriv{ /* non-MIB stats */ ulong lenerr; /* short packet */ }; typedef struct Bring Bring; struct Bring{ Block *ring[Nring]; long produced; long consumed; }; typedef struct GREconv GREconv; struct GREconv{ int raw; /* Retunnelling information. v4 only */ uchar north[4]; /* HA */ uchar south[4]; /* Base station */ uchar hoa[4]; /* Home address */ uchar coa[4]; /* Careof address */ ulong seq; /* Current sequence # */ int dlsusp; /* Downlink suspended? */ int ulsusp; /* Uplink suspended? */ ulong ulkey; /* GRE key */ QLock lock; /* Lock for rings */ Bring dlpending; /* Ring of pending packets */ Bring dlbuffered; /* Received while suspended */ Bring ulbuffered; /* Received while suspended */ }; typedef struct Metablock Metablock; struct Metablock{ uchar *rp; ulong seq; }; static char *grectlcooked(Conv *, int, char **); static char *grectldlresume(Conv *, int, char **); static char *grectldlsuspend(Conv *, int, char **); static char *grectlforward(Conv *, int, char **); static char *grectlraw(Conv *, int, char **); static char *grectlreport(Conv *, int, char **); static char *grectlretunnel(Conv *, int, char **); static char *grectlulkey(Conv *, int, char **); static char *grectlulresume(Conv *, int, char **); static char *grectlulsuspend(Conv *, int, char **); static struct{ char *cmd; int argc; char *(*f)(Conv *, int, char **); } grectls[Ncmds] = { [GREctlraw] = { "raw", 1, grectlraw, }, [GREctlcooked] = { "cooked", 1, grectlcooked, }, [GREctlretunnel]= { "retunnel", 5, grectlretunnel, }, [GREctlreport] = { "report", 2, grectlreport, }, [GREctldlsuspend]= { "dlsuspend", 1, grectldlsuspend,}, [GREctlulsuspend]= { "ulsuspend", 1, grectlulsuspend,}, [GREctldlresume]= { "dlresume", 1, grectldlresume, }, [GREctlulresume]= { "ulresume", 1, grectlulresume, }, [GREctlforward] = { "forward", 2, grectlforward, }, [GREctlulkey] = { "ulkey", 2, grectlulkey, }, }; static uchar nulladdr[4]; static char *sessend = "session end"; static void grekick(void *x, Block *bp); static char *gresetup(Conv *, char *, char *, char *); ulong grepdin, grepdout, grebdin, grebdout; ulong grepuin, grepuout, grebuin, grebuout; static Block * getring(Bring *r) { Block *bp; if(r->consumed == r->produced) return nil; bp = r->ring[r->consumed & Ringmask]; r->ring[r->consumed & Ringmask] = nil; r->consumed++; return bp; } static void addring(Bring *r, Block *bp) { Block *tbp; if(r->produced - r->consumed > Ringmask){ /* Full! */ tbp = r->ring[r->produced & Ringmask]; assert(tbp); freeb(tbp); r->consumed++; } r->ring[r->produced & Ringmask] = bp; r->produced++; } static char * greconnect(Conv *c, char **argv, int argc) { Proto *p; char *err; Conv *tc, **cp, **ecp; err = Fsstdconnect(c, argv, argc); if(err != nil) return err; /* make sure noone's already connected to this other sys */ p = c->p; qlock(p); ecp = &p->conv[p->nc]; for(cp = p->conv; cp < ecp; cp++){ tc = *cp; if(tc == nil) break; if(tc == c) continue; if(tc->rport == c->rport && ipcmp(tc->raddr, c->raddr) == 0){ err = "already connected to that addr/proto"; ipmove(c->laddr, IPnoaddr); ipmove(c->raddr, IPnoaddr); break; } } qunlock(p); if(err != nil) return err; Fsconnected(c, nil); return nil; } static void grecreate(Conv *c) { c->rq = qopen(GREqlen, Qmsg, 0, c); c->wq = qbypass(grekick, c); } static int grestate(Conv *c, char *state, int n) { GREconv *grec; char *ep, *p; grec = c->ptcl; p = state; ep = p + n; p = seprint(p, ep, "%s%s%s%shoa %V north %V south %V seq %ulx " "pending %uld %uld buffered dl %uld %uld ul %uld %uld ulkey %.8ulx\n", c->inuse? "Open ": "Closed ", grec->raw? "raw ": "", grec->dlsusp? "DL suspended ": "", grec->ulsusp? "UL suspended ": "", grec->hoa, grec->north, grec->south, grec->seq, grec->dlpending.consumed, grec->dlpending.produced, grec->dlbuffered.consumed, grec->dlbuffered.produced, grec->ulbuffered.consumed, grec->ulbuffered.produced, grec->ulkey); return p - state; } static char* greannounce(Conv*, char**, int) { return "gre does not support announce"; } static void greclose(Conv *c) { GREconv *grec; Block *bp; grec = c->ptcl; /* Make sure we don't forward any more packets */ memset(grec->hoa, 0, sizeof grec->hoa); memset(grec->north, 0, sizeof grec->north); memset(grec->south, 0, sizeof grec->south); qlock(&grec->lock); while((bp = getring(&grec->dlpending)) != nil) freeb(bp); while((bp = getring(&grec->dlbuffered)) != nil) freeb(bp); while((bp = getring(&grec->ulbuffered)) != nil) freeb(bp); grec->dlpending.produced = grec->dlpending.consumed = 0; grec->dlbuffered.produced = grec->dlbuffered.consumed = 0; grec->ulbuffered.produced = grec->ulbuffered.consumed = 0; qunlock(&grec->lock); grec->raw = 0; grec->seq = 0; grec->dlsusp = grec->ulsusp = 1; qhangup(c->rq, sessend); qhangup(c->wq, sessend); qhangup(c->eq, sessend); ipmove(c->laddr, IPnoaddr); ipmove(c->raddr, IPnoaddr); c->lport = c->rport = 0; } static void grekick(void *x, Block *bp) { Conv *c; GREconv *grec; GREhdr *gre; uchar laddr[IPaddrlen], raddr[IPaddrlen]; if(bp == nil) return; c = x; grec = c->ptcl; /* Make space to fit ip header (gre header already there) */ bp = padblock(bp, GRE_IPONLY); if(bp == nil) return; /* make sure the message has a GRE header */ bp = pullupblock(bp, GRE_IPONLY+GRE_IPPLUSGRE); if(bp == nil) return; gre = (GREhdr *)bp->rp; gre->vihl = IP_VER4; if(grec->raw == 0){ v4tov6(raddr, gre->dst); if(ipcmp(raddr, v4prefix) == 0) memmove(gre->dst, c->raddr + IPv4off, IPv4addrlen); v4tov6(laddr, gre->src); if(ipcmp(laddr, v4prefix) == 0){ if(ipcmp(c->laddr, IPnoaddr) == 0) /* pick interface closest to dest */ findlocalip(c->p->f, c->laddr, raddr); memmove(gre->src, c->laddr + IPv4off, sizeof gre->src); } hnputs(gre->eproto, c->rport); } gre->proto = IP_GREPROTO; gre->frag[0] = gre->frag[1] = 0; grepdout++; grebdout += BLEN(bp); ipoput4(c->p->f, bp, 0, c->ttl, c->tos, nil); } static void gredownlink(Conv *c, Block *bp) { Metablock *m; GREconv *grec; GREhdr *gre; int hdrlen, suspended, extra; ushort flags; ulong seq; gre = (GREhdr *)bp->rp; if(gre->ttl == 1){ freeb(bp); return; } /* * We've received a packet with a GRE header and we need to * re-adjust the packet header to strip all unwanted parts * but leave room for only a sequence number. */ grec = c->ptcl; flags = nhgets(gre->flags); hdrlen = 0; if(flags & GRE_cksum) hdrlen += 2; if(flags & GRE_routing){ print("%V routing info present. Discarding packet", gre->src); freeb(bp); return; } if(flags & (GRE_cksum|GRE_routing)) hdrlen += 2; /* Offset field */ if(flags & GRE_key) hdrlen += 4; if(flags & GRE_seq) hdrlen += 4; /* * The outgoing packet only has the sequence number set. Make room * for the sequence number. */ if(hdrlen != sizeof(ulong)){ extra = hdrlen - sizeof(ulong); if(extra < 0 && bp->rp - bp->base < -extra){ print("gredownlink: cannot add sequence number\n"); freeb(bp); return; } memmove(bp->rp + extra, bp->rp, sizeof(GREhdr)); bp->rp += extra; assert(BLEN(bp) >= sizeof(GREhdr) + sizeof(ulong)); gre = (GREhdr *)bp->rp; } seq = grec->seq++; hnputs(gre->flags, GRE_seq); hnputl(bp->rp + sizeof(GREhdr), seq); /* * Keep rp and seq at the base. ipoput4 consumes rp for * refragmentation. */ assert(bp->rp - bp->base >= sizeof(Metablock)); m = (Metablock *)bp->base; m->rp = bp->rp; m->seq = seq; /* * Here we make a decision what we're doing with the packet. We're * doing this w/o holding a lock which means that later on in the * process we may discover we've done the wrong thing. I don't want * to call ipoput with the lock held. */ restart: suspended = grec->dlsusp; if(suspended){ if(!canqlock(&grec->lock)){ /* * just give up. too bad, we lose a packet. this * is just too hard and my brain already hurts. */ freeb(bp); return; } if(!grec->dlsusp){ /* * suspend race. We though we were suspended, but * we really weren't. */ qunlock(&grec->lock); goto restart; } /* Undo the incorrect ref count addition */ addring(&grec->dlbuffered, bp); qunlock(&grec->lock); return; } /* * When we get here, we're not suspended. Proceed to send the * packet. */ memmove(gre->src, grec->coa, sizeof gre->dst); memmove(gre->dst, grec->south, sizeof gre->dst); /* * Make sure the packet does not go away. */ _xinc(&bp->ref); assert(bp->ref == 2); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); grepdout++; grebdout += BLEN(bp); /* * Now make sure we didn't do the wrong thing. */ if(!canqlock(&grec->lock)){ freeb(bp); /* The packet just goes away */ return; } /* We did the right thing */ addring(&grec->dlpending, bp); qunlock(&grec->lock); } static void greuplink(Conv *c, Block *bp) { GREconv *grec; GREhdr *gre; ushort flags; gre = (GREhdr *)bp->rp; if(gre->ttl == 1) return; grec = c->ptcl; memmove(gre->src, grec->coa, sizeof gre->src); memmove(gre->dst, grec->north, sizeof gre->dst); /* * Add a key, if needed. */ if(grec->ulkey){ flags = nhgets(gre->flags); if(flags & (GRE_cksum|GRE_routing)){ print("%V routing info present. Discarding packet\n", gre->src); freeb(bp); return; } if((flags & GRE_key) == 0){ /* Make room for the key */ if(bp->rp - bp->base < sizeof(ulong)){ print("%V can't add key\n", gre->src); freeb(bp); return; } bp->rp -= 4; memmove(bp->rp, bp->rp + 4, sizeof(GREhdr)); gre = (GREhdr *)bp->rp; hnputs(gre->flags, flags | GRE_key); } /* Add the key */ hnputl(bp->rp + sizeof(GREhdr), grec->ulkey); } if(!canqlock(&grec->lock)){ freeb(bp); return; } if(grec->ulsusp) addring(&grec->ulbuffered, bp); else{ ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); grepuout++; grebuout += BLEN(bp); } qunlock(&grec->lock); } static void greiput(Proto *proto, Ipifc *, Block *bp) { int len, hdrlen; ushort eproto, flags; uchar raddr[IPaddrlen]; Conv *c, **p; GREconv *grec; GREhdr *gre; GREpriv *gpriv; Ip4hdr *ip; /* * We don't want to deal with block lists. Ever. The problem is * that when the block is forwarded, devether.c puts the block into * a queue that also uses ->next. Just do not use ->next here! */ if(bp->next){ len = blocklen(bp); bp = pullupblock(bp, len); assert(BLEN(bp) == len && bp->next == nil); } gre = (GREhdr *)bp->rp; if(BLEN(bp) < sizeof(GREhdr) || gre->proto != IP_GREPROTO){ freeb(bp); return; } v4tov6(raddr, gre->src); eproto = nhgets(gre->eproto); flags = nhgets(gre->flags); hdrlen = sizeof(GREhdr); if(flags & GRE_cksum) hdrlen += 2; if(flags & GRE_routing){ print("%I routing info present. Discarding packet\n", raddr); freeb(bp); return; } if(flags & (GRE_cksum|GRE_routing)) hdrlen += 2; /* Offset field */ if(flags & GRE_key) hdrlen += 4; if(flags & GRE_seq) hdrlen += 4; if(BLEN(bp) - hdrlen < sizeof(Ip4hdr)){ print("greretunnel: packet too short (s=%V d=%V)\n", gre->src, gre->dst); freeb(bp); return; } ip = (Ip4hdr *)(bp->rp + hdrlen); qlock(proto); /* * Look for a conversation structure for this port and address, or * match the retunnel part, or match on the raw flag. */ for(p = proto->conv; *p; p++) { c = *p; if(c->inuse == 0) continue; /* * Do not stop this session - blocking here * implies that etherread is blocked. */ grec = c->ptcl; if(memcmp(ip->dst, grec->hoa, sizeof ip->dst) == 0){ grepdin++; grebdin += BLEN(bp); gredownlink(c, bp); qunlock(proto); return; } if(memcmp(ip->src, grec->hoa, sizeof ip->src) == 0){ grepuin++; grebuin += BLEN(bp); greuplink(c, bp); qunlock(proto); return; } } /* * when we get here, none of the forwarding tunnels matched. now * try to match on raw and conversational sessions. */ for(c = nil, p = proto->conv; *p; p++) { c = *p; if(c->inuse == 0) continue; /* * Do not stop this session - blocking here * implies that etherread is blocked. */ grec = c->ptcl; if(c->rport == eproto && (grec->raw || ipcmp(c->raddr, raddr) == 0)) break; } qunlock(proto); if(*p == nil){ freeb(bp); return; } /* * Trim the packet down to data size */ len = nhgets(gre->len) - GRE_IPONLY; if(len < GRE_IPPLUSGRE){ freeb(bp); return; } bp = trimblock(bp, GRE_IPONLY, len); if(bp == nil){ gpriv = proto->priv; gpriv->lenerr++; return; } /* * Can't delimit packet so pull it all into one block. */ if(qlen(c->rq) > GREqlen) freeb(bp); else{ bp = concatblock(bp); if(bp == 0) panic("greiput"); qpass(c->rq, bp); } } int grestats(Proto *gre, char *buf, int len) { GREpriv *gpriv; gpriv = gre->priv; return snprint(buf, len, "gre: %lud %lud %lud %lud %lud %lud %lud %lud, lenerrs %lud\n", grepdin, grepdout, grepuin, grepuout, grebdin, grebdout, grebuin, grebuout, gpriv->lenerr); } static char * grectlraw(Conv *c, int, char **) { GREconv *grec; grec = c->ptcl; grec->raw = 1; return nil; } static char * grectlcooked(Conv *c, int, char **) { GREconv *grec; grec = c->ptcl; grec->raw = 0; return nil; } static char * grectlretunnel(Conv *c, int, char **argv) { GREconv *grec; uchar ipaddr[4]; grec = c->ptcl; if(memcmp(grec->hoa, nulladdr, sizeof grec->hoa)) return "tunnel already set up"; v4parseip(ipaddr, argv[1]); if(memcmp(ipaddr, nulladdr, sizeof ipaddr) == 0) return "bad hoa"; memmove(grec->hoa, ipaddr, sizeof grec->hoa); v4parseip(ipaddr, argv[2]); memmove(grec->north, ipaddr, sizeof grec->north); v4parseip(ipaddr, argv[3]); memmove(grec->south, ipaddr, sizeof grec->south); v4parseip(ipaddr, argv[4]); memmove(grec->coa, ipaddr, sizeof grec->coa); grec->ulsusp = 1; grec->dlsusp = 0; return nil; } static char * grectlreport(Conv *c, int, char **argv) { ulong seq; Block *bp; Bring *r; GREconv *grec; Metablock *m; grec = c->ptcl; seq = strtoul(argv[1], nil, 0); qlock(&grec->lock); r = &grec->dlpending; while(r->produced - r->consumed > 0){ bp = r->ring[r->consumed & Ringmask]; assert(bp && bp->rp - bp->base >= sizeof(Metablock)); m = (Metablock *)bp->base; if((long)(seq - m->seq) <= 0) break; r->ring[r->consumed & Ringmask] = nil; r->consumed++; freeb(bp); } qunlock(&grec->lock); return nil; } static char * grectldlsuspend(Conv *c, int, char **) { GREconv *grec; grec = c->ptcl; if(grec->dlsusp) return "already suspended"; grec->dlsusp = 1; return nil; } static char * grectlulsuspend(Conv *c, int, char **) { GREconv *grec; grec = c->ptcl; if(grec->ulsusp) return "already suspended"; grec->ulsusp = 1; return nil; } static char * grectldlresume(Conv *c, int, char **) { GREconv *grec; GREhdr *gre; Block *bp; grec = c->ptcl; qlock(&grec->lock); if(!grec->dlsusp){ qunlock(&grec->lock); return "not suspended"; } while((bp = getring(&grec->dlbuffered)) != nil){ gre = (GREhdr *)bp->rp; qunlock(&grec->lock); /* * Make sure the packet does not go away. */ _xinc(&bp->ref); assert(bp->ref == 2); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); qlock(&grec->lock); addring(&grec->dlpending, bp); } grec->dlsusp = 0; qunlock(&grec->lock); return nil; } static char * grectlulresume(Conv *c, int, char **) { GREconv *grec; GREhdr *gre; Block *bp; grec = c->ptcl; qlock(&grec->lock); while((bp = getring(&grec->ulbuffered)) != nil){ gre = (GREhdr *)bp->rp; qunlock(&grec->lock); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); qlock(&grec->lock); } grec->ulsusp = 0; qunlock(&grec->lock); return nil; } static char * grectlforward(Conv *c, int, char **argv) { int len; Block *bp, *nbp; GREconv *grec; GREhdr *gre; Metablock *m; grec = c->ptcl; v4parseip(grec->south, argv[1]); memmove(grec->north, grec->south, sizeof grec->north); qlock(&grec->lock); if(!grec->dlsusp){ qunlock(&grec->lock); return "not suspended"; } grec->dlsusp = 0; grec->ulsusp = 0; while((bp = getring(&grec->dlpending)) != nil){ assert(bp->rp - bp->base >= sizeof(Metablock)); m = (Metablock *)bp->base; assert(m->rp >= bp->base && m->rp < bp->lim); /* * If the packet is still held inside the IP transmit * system, make a copy of the packet first. */ if(bp->ref > 1){ len = bp->wp - m->rp; nbp = allocb(len); memmove(nbp->wp, m->rp, len); nbp->wp += len; freeb(bp); bp = nbp; } else{ /* Patch up rp */ bp->rp = m->rp; } gre = (GREhdr *)bp->rp; memmove(gre->src, grec->coa, sizeof gre->dst); memmove(gre->dst, grec->south, sizeof gre->dst); qunlock(&grec->lock); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); qlock(&grec->lock); } while((bp = getring(&grec->dlbuffered)) != nil){ gre = (GREhdr *)bp->rp; memmove(gre->src, grec->coa, sizeof gre->dst); memmove(gre->dst, grec->south, sizeof gre->dst); qunlock(&grec->lock); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); qlock(&grec->lock); } while((bp = getring(&grec->ulbuffered)) != nil){ gre = (GREhdr *)bp->rp; memmove(gre->src, grec->coa, sizeof gre->dst); memmove(gre->dst, grec->south, sizeof gre->dst); qunlock(&grec->lock); ipoput4(c->p->f, bp, 0, gre->ttl - 1, gre->tos, nil); qlock(&grec->lock); } qunlock(&grec->lock); return nil; } static char * grectlulkey(Conv *c, int, char **argv) { GREconv *grec; grec = c->ptcl; grec->ulkey = strtoul(argv[1], nil, 0); return nil; } char * grectl(Conv *c, char **f, int n) { int i; if(n < 1) return "too few arguments"; for(i = 0; i < Ncmds; i++) if(strcmp(f[0], grectls[i].cmd) == 0) break; if(i == Ncmds) return "no such command"; if(grectls[i].argc != 0 && grectls[i].argc != n) return "incorrect number of arguments"; return grectls[i].f(c, n, f); } void greinit(Fs *fs) { Proto *gre; gre = smalloc(sizeof(Proto)); gre->priv = smalloc(sizeof(GREpriv)); gre->name = "gre"; gre->connect = greconnect; gre->announce = greannounce; gre->state = grestate; gre->create = grecreate; gre->close = greclose; gre->rcv = greiput; gre->ctl = grectl; gre->advise = nil; gre->stats = grestats; gre->ipproto = IP_GREPROTO; gre->nc = 64; gre->ptclsize = sizeof(GREconv); Fsproto(fs, gre); }