Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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);
}