summaryrefslogtreecommitdiff
path: root/sys/src/9/ip/gre.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/ip/gre.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/ip/gre.c')
-rwxr-xr-xsys/src/9/ip/gre.c971
1 files changed, 971 insertions, 0 deletions
diff --git a/sys/src/9/ip/gre.c b/sys/src/9/ip/gre.c
new file mode 100755
index 000000000..b7982dc33
--- /dev/null
+++ b/sys/src/9/ip/gre.c
@@ -0,0 +1,971 @@
+/*
+ * 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);
+}