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