927 lines
19 KiB
C
927 lines
19 KiB
C
/*
|
|
* 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 */
|
|
uvlong 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 *);
|
|
|
|
uvlong grepdin, grepdout, grebdin, grebdout;
|
|
uvlong 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);
|
|
|
|
/* 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);
|
|
|
|
ipoput4(c->p->f, copyblock(bp, BLEN(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 != nil)
|
|
bp = pullupblock(bp, blocklen(bp));
|
|
|
|
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;
|
|
|
|
qlock(proto);
|
|
|
|
if(eproto != 0x880B && BLEN(bp) - hdrlen >= sizeof(Ip4hdr)){
|
|
ip = (Ip4hdr *)(bp->rp + hdrlen);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
qpass(c->rq, bp);
|
|
}
|
|
|
|
int
|
|
grestats(Proto *gre, char *buf, int len)
|
|
{
|
|
GREpriv *gpriv;
|
|
|
|
gpriv = gre->priv;
|
|
return snprint(buf, len,
|
|
"gre: %llud %llud %llud %llud %llud %llud %llud %llud, lenerrs %llud\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);
|
|
|
|
ipoput4(c->p->f, copyblock(bp, BLEN(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)
|
|
{
|
|
Block *bp;
|
|
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);
|
|
|
|
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);
|
|
}
|