Subversion Repositories planix.SVN

Rev

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

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>

#define LOG     "pptpd"

typedef struct Call     Call;
typedef struct Event Event;

#define SDB     if(debug) fprint(2, 
#define EDB     );

enum {
        Magic   = 0x1a2b3c4d,
        Nhash   = 17,
        Nchan   = 10,           /* maximum number of channels */
        Window  = 8,            /* default window size */
        Timeout = 60,           /* timeout in seconds for control channel */
        Pktsize = 2000,         /* maximum packet size */
        Tick    = 500,          /* tick length in milliseconds */
        Sendtimeout = 4,        /* in ticks */
};

enum {
        Syncframe       = 0x1,
        Asyncframe      = 0x2,
        Analog          = 0x1,
        Digital         = 0x2,
        Version         = 0x100,
};

enum {
        Tstart          = 1,
        Rstart          = 2,
        Tstop           = 3,
        Rstop           = 4,
        Techo           = 5,
        Recho           = 6,
        Tcallout        = 7,
        Rcallout        = 8,
        Tcallreq        = 9,
        Rcallreq        = 10,
        Acallcon        = 11,
        Tcallclear      = 12,
        Acalldis        = 13,
        Awaninfo        = 14,
        Alinkinfo       = 15,
};


struct Event {
        QLock;
        QLock waitlk;
        int     wait;
        int ready;
};

struct Call {
        int     ref;
        QLock   lk;
        int     id;
        int     serial;
        int     pppfd;

        int     closed;

        int     pac;    /* server is acting as a PAC */

        int     recvwindow;     /* recv windows */
        int     sendwindow;     /* send windows */
        int     delay;

        int     sendaccm;
        int     recvaccm;

        uint    seq;            /* current seq number - for send */
        uint    ack;            /* current acked mesg - for send */
        uint    rseq;           /* highest recv seq number for in order packet  */
        uint    rack;           /* highest ack sent */

        Event   eack;           /* recved ack - for send */
        ulong   tick;

        uchar   remoteip[IPaddrlen];    /* remote ip address */
        int     dhcpfd[2];      /* pipe to dhcpclient */

        /* error stats */
        struct {
                int     crc;
                int     frame;
                int     hardware;
                int     overrun;
                int     timeout;
                int     align;
        } err;

        struct {
                int     send;
                int     sendack;
                int     recv;
                int     recvack;
                int     dropped;
                int     missing;
                int     sendwait;
                int     sendtimeout;
        } stat;

        Call    *next;
};

struct {
        QLock   lk;
        int     start;
        int     grefd;
        int     grecfd;
        uchar   local[IPaddrlen];
        uchar   remote[IPaddrlen];
        char    *tcpdir;
        uchar   ipaddr[IPaddrlen];              /* starting ip addresss to allocate */

        int     recvwindow;

        char    *pppdir;
        char    *pppexec;

        double  rcvtime;        /* time at which last request was received */
        int     echoid;         /* id of last echo request */

        Call    *hash[Nhash];
} srv;

/* GRE flag bits */
enum {
        GRE_chksum      = (1<<15),
        GRE_routing     = (1<<14),
        GRE_key         = (1<<13),
        GRE_seq         = (1<<12),
        GRE_srcrt       = (1<<11),
        GRE_recur       = (7<<8),
        GRE_ack         = (1<<7),
        GRE_ver         = 0x7,
};

/* GRE protocols */
enum {
        GRE_ppp         = 0x880b,
};

int     debug;
double  drop;

void    myfatal(char *fmt, ...);

#define PSHORT(p, v)            ((p)[0]=((v)>>8), (p)[1]=(v))
#define PLONG(p, v)             (PSHORT(p, (v)>>16), PSHORT(p+2, (v)))
#define PSTRING(d,s,n)          strncpy((char*)(d), s, n)
#define GSHORT(p)               (((p)[0]<<8) | ((p)[1]<<0))
#define GLONG(p)                ((GSHORT((p))<<16) | ((GSHORT((p)+2))<<0))
#define GSTRING(d,s,n)          strncpy(d, (char*)(s), n), d[(n)-1] = 0

void    serve(void);

int     sstart(uchar*, int);
int     sstop(uchar*, int);
int     secho(uchar*, int);
int     scallout(uchar*, int);
int     scallreq(uchar*, int);
int     scallcon(uchar*, int);
int     scallclear(uchar*, int);
int     scalldis(uchar*, int);
int     swaninfo(uchar*, int);
int     slinkinfo(uchar*, int);

Call    *callalloc(int id);
void    callclose(Call*);
void    callfree(Call*);
Call    *calllookup(int id);

void    gretimeout(void*);
void    pppread(void*);

void    srvinit(void);
void    greinit(void);
void    greread(void*);
void    greack(Call *c);

void    timeoutthread(void*);

int     argatoi(char *p);
void    usage(void);
int     ipaddralloc(Call *c);

void    *emallocz(int size);
void    esignal(Event *e);
void    ewait(Event *e);
int     proc(char **argv, int fd0, int fd1, int fd2);
double  realtime(void);
ulong   thread(void(*f)(void*), void *a);

void
main(int argc, char *argv[])
{
        ARGBEGIN{
        case 'd': debug++; break;
        case 'p': srv.pppdir = ARGF(); break;
        case 'P': srv.pppexec = ARGF(); break;
        case 'w': srv.recvwindow = argatoi(ARGF()); break;
        case 'D': drop = atof(ARGF()); break;
        default:
                usage();
        }ARGEND

        fmtinstall('I', eipfmt);
        fmtinstall('E', eipfmt);
        fmtinstall('V', eipfmt);
        fmtinstall('M', eipfmt);

        rfork(RFNOTEG|RFREND);

        if(argc != 1)
                usage();

        srv.tcpdir = argv[0];

        srvinit();

        syslog(0, LOG, ": src=%I: pptp started: %d", srv.remote, getpid());

        SDB  "\n\n\n%I: pptp started\n", srv.remote EDB

        greinit();

        thread(timeoutthread, 0);

        serve();

        syslog(0, LOG, ": src=%I: server exits", srv.remote);

        exits(0);
}

void
usage(void)
{
        fprint(2, "usage: pptpd [-dD] [-p ppp-net] [-w window] tcpdir\n");
        exits("usage");
}

void
serve(void)
{
        uchar buf[2000], *p;
        int n, n2, len;
        int magic;
        int op, type;

        n = 0;
        for(;;) {
                n2 = read(0, buf+n, sizeof(buf)-n);
                if(n2 < 0)
                        myfatal("bad read on ctl channel: %r");
                if(n2 == 0)
                        break;
                n += n2;
                p = buf;
                for(;;) {
                        if(n < 12)
                                break;

                        qlock(&srv.lk);
                        srv.rcvtime = realtime();
                        qunlock(&srv.lk);

                        len = GSHORT(p);
                        type = GSHORT(p+2);
                        magic = GLONG(p+4);
                        op = GSHORT(p+8);
                        if(magic != Magic)
                                myfatal("bad magic number: got %x", magic);
                        if(type != 1)
                                myfatal("bad message type: %d", type);
                        switch(op) {
                        default:
                                myfatal("unknown control op: %d", op);
                        case Tstart:            /* start-control-connection-request */
                                n2 = sstart(p, n);
                                break;
                        case Tstop:
                                n2 = sstop(p, n);
                                if(n2 > 0)
                                        return;
                                break;
                        case Techo:
                                n2 = secho(p, n);
                                break;
                        case Tcallout:
                                n2 = scallout(p, n);
                                break;
                        case Tcallreq:
                                n2 = scallreq(p, n);
                                break;
                        case Acallcon:
                                n2 = scallcon(p, n);
                                break;
                        case Tcallclear:
                                n2 = scallclear(p, n);
                                break;
                        case Acalldis:
                                n2 = scalldis(p, n);
                                break;
                        case Awaninfo:
                                n2 = swaninfo(p, n);
                                break;
                        case Alinkinfo:
                                n2 = slinkinfo(p, n);
                                break;
                        }       
                        if(n2 == 0)
                                break;
                        if(n2 != len)
                                myfatal("op=%d: bad length: got %d expected %d", op, len, n2);
                        n -= n2;
                        p += n2;
                        
                }

                /* move down partial message */
                if(p != buf && n != 0)
                        memmove(buf, p, n);
        }

}

int
sstart(uchar *p, int n)
{
        int ver, frame, bearer, maxchan, firm;
        char host[64], vendor[64], *sysname;
        uchar buf[156];

        if(n < 156)
                return 0;
        ver = GSHORT(p+12);
        frame = GLONG(p+16);
        bearer = GLONG(p+20);
        maxchan = GSHORT(p+24);
        firm = GSHORT(p+26);
        GSTRING(host, p+28, 64);
        GSTRING(vendor, p+92, 64);

        SDB "%I: start ver = %x f = %d b = %d maxchan = %d firm = %d host = %s vendor = %s\n",
                srv.remote, ver, frame, bearer, maxchan, firm, host, vendor EDB

        if(ver != Version)
                myfatal("bad version: got %x expected %x", ver, Version);

        if(srv.start)
                myfatal("multiple start messages");

        srv.start = 1;

        sysname = getenv("sysname");
        if(sysname == 0)
                strcpy(host, "gnot");
        else
                strncpy(host, sysname, 64);
        free(sysname);

        memset(buf, 0, sizeof(buf));

        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Rstart);          /* op */
        PSHORT(buf+12, Version);        /* version */
        buf[14] = 1;                    /* result = ok */
        PLONG(buf+16, Syncframe|Asyncframe);    /* frameing */
        PLONG(buf+20, Digital|Analog);  /* berear capabilities */
        PSHORT(buf+24, Nchan);          /* max channels */
        PSHORT(buf+26, 1);              /* driver version */
        PSTRING(buf+28, host, 64);      /* host name */
        PSTRING(buf+92, "plan 9", 64);  /* vendor */

        if(write(1, buf, sizeof(buf)) < sizeof(buf))
                myfatal("write failed: %r");

        return 156;
}

int
sstop(uchar *p, int n)
{
        int reason;
        uchar buf[16];

        if(n < 16)
                return 0;
        reason = p[12];
        
        SDB "%I: stop %d\n", srv.remote, reason EDB

        memset(buf, 0, sizeof(buf));
        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Rstop);           /* op */
        buf[12] = 1;                    /* ok */

        if(write(1, buf, sizeof(buf)) < sizeof(buf))
                myfatal("write failed: %r");

        return 16;
}

int
secho(uchar *p, int n)
{
        int id;
        uchar buf[20];

        if(n < 16)
                return 0;
        id = GLONG(p+12);
        
        SDB "%I: echo %d\n", srv.remote, id EDB

        memset(buf, 0, sizeof(buf));
        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Recho);           /* op */
        PLONG(buf+12, id);              /* id */
        p[16] = 1;                      /* ok */

        if(write(1, buf, sizeof(buf)) < sizeof(buf))
                myfatal("write failed: %r");

        return 16;
}

int
scallout(uchar *p, int n)
{
        int id, serial;
        int minbps, maxbps, bearer, frame;
        int window, delay;
        int nphone;
        char phone[64], sub[64], buf[32];
        Call *c;

        if(n < 168)
                return 0;

        if(!srv.start)
                myfatal("%I: did not recieve start message", srv.remote);
        
        id = GSHORT(p+12);
        serial = GSHORT(p+14);
        minbps = GLONG(p+16);
        maxbps = GLONG(p+20);
        bearer = GLONG(p+24);
        frame = GLONG(p+28);
        window = GSHORT(p+32);
        delay = GSHORT(p+34);
        nphone = GSHORT(p+36);
        GSTRING(phone, p+40, 64);
        GSTRING(sub, p+104, 64);

        SDB "%I: callout id = %d serial = %d bps=[%d,%d] b=%x f=%x win = %d delay = %d np=%d phone=%s sub=%s\n",
                srv.remote, id, serial, minbps, maxbps, bearer, frame, window, delay, nphone, phone, sub EDB

        c = callalloc(id);
        c->sendwindow = window;
        c->delay = delay;
        c->pac = 1;
        c->recvwindow = srv.recvwindow;

        memset(buf, 0, sizeof(buf));
        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Rcallout);        /* op */
        PSHORT(buf+12, id);             /* call id */
        PSHORT(buf+14, id);             /* peer id */
        buf[16] = 1;                    /* ok */
        PLONG(buf+20, 10000000);        /* speed */
        PSHORT(buf+24, c->recvwindow);  /* window size */
        PSHORT(buf+26, 0);              /* delay */
        PLONG(buf+28, 0);               /* channel id */
        
        if(write(1, buf, sizeof(buf)) < sizeof(buf))
                myfatal("write failed: %r");

        return 168;
}

int
scallreq(uchar *p, int n)
{
        USED(p);
        USED(n);

        myfatal("callreq: not done yet");
        return 0;
}

int
scallcon(uchar *p, int n)
{
        USED(p);
        USED(n);

        myfatal("callcon: not done yet");
        return 0;
}

int
scallclear(uchar *p, int n)
{
        Call *c;
        int id;
        uchar buf[148];

        if(n < 16)
                return 0;
        id = GSHORT(p+12);
        
        SDB "%I: callclear id=%d\n", srv.remote, id EDB
        
        if(c = calllookup(id)) {
                callclose(c);
                callfree(c);
        }

        memset(buf, 0, sizeof(buf));
        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Acalldis);        /* op */
        PSHORT(buf+12, id);             /* id */
        buf[14] = 3;                    /* reply to callclear */

        if(write(1, buf, sizeof(buf)) < sizeof(buf))
                myfatal("write failed: %r");

        return 16;
}

int
scalldis(uchar *p, int n)
{
        Call *c;
        int id, res;

        if(n < 148)
                return 0;
        id = GSHORT(p+12);
        res = p[14];

        SDB "%I: calldis id=%d res=%d\n", srv.remote, id, res EDB

        if(c = calllookup(id)) {
                callclose(c);
                callfree(c);
        }

        return 148;
}

int
swaninfo(uchar *p, int n)
{
        Call *c;
        int id;

        if(n < 40)
                return 0;
        
        id = GSHORT(p+12);
        SDB "%I: waninfo id = %d\n", srv.remote, id EDB
        
        c = calllookup(id);
        if(c != 0) {
                c->err.crc = GLONG(p+16);
                c->err.frame = GLONG(p+20);
                c->err.hardware = GLONG(p+24);
                c->err.overrun = GLONG(p+28);
                c->err.timeout = GLONG(p+32);
                c->err.align = GLONG(p+36);

                callfree(c);
        }

        
        return 40;
}

int
slinkinfo(uchar *p, int n)
{
        Call *c;
        int id;
        int sendaccm, recvaccm;

        if(n < 24)
                return 0;
        id = GSHORT(p+12);
        sendaccm = GLONG(p+16);
        recvaccm = GLONG(p+20);

        SDB "%I: linkinfo id=%d saccm=%ux raccm=%ux\n", srv.remote, id, sendaccm, recvaccm EDB

        if(c = calllookup(id)) {
                c->sendaccm = sendaccm;
                c->recvaccm = recvaccm;

                callfree(c);
        }
        
        return 24;
}

Call*
callalloc(int id)
{
        uint h;
        Call *c;
        char buf[300], *argv[30], local[20], remote[20], **p;
        int fd, pfd[2], n;

        h = id%Nhash;

        qlock(&srv.lk);
        for(c=srv.hash[h]; c; c=c->next)
                if(c->id == id)
                        myfatal("callalloc: duplicate id: %d", id);
        c = emallocz(sizeof(Call));
        c->ref = 1;
        c->id = id;
        c->sendaccm = ~0;
        c->recvaccm = ~0;

        if(!ipaddralloc(c))
                myfatal("callalloc: could not alloc remote ip address");

        if(pipe(pfd) < 0)
                myfatal("callalloc: pipe failed: %r");

        sprint(buf, "%s/ipifc/clone", srv.pppdir);
        fd = open(buf, OWRITE);
        if(fd < 0)
                myfatal("callalloc: could not open %s: %r", buf);

        n = sprint(buf, "iprouting");
        if(write(fd, buf, n) < n)
                myfatal("callalloc: write to ifc failed: %r");
        close(fd);

        p = argv;
        *p++ = srv.pppexec;
        *p++ = "-SC";
        *p++ = "-x";
        *p++ = srv.pppdir;
        if(debug)
                *p++ = "-d";
        sprint(local, "%I", srv.ipaddr);
        *p++ = local;
        sprint(remote, "%I", c->remoteip);
        *p++ = remote;
        *p = 0;

        proc(argv, pfd[0], pfd[0], 2);

        close(pfd[0]);

        c->pppfd = pfd[1];

        c->next = srv.hash[h];
        srv.hash[h] = c;

        qunlock(&srv.lk);

        c->ref++;
        thread(pppread, c);
        c->ref++;
        thread(gretimeout, c);

        syslog(0, LOG, ": src=%I: call started: id=%d: remote ip=%I", srv.remote, id, c->remoteip);

        return c;
}

void
callclose(Call *c)
{
        Call *oc;
        int id;
        uint h;

        syslog(0, LOG, ": src=%I: call closed: id=%d: send=%d sendack=%d recv=%d recvack=%d dropped=%d missing=%d sendwait=%d sendtimeout=%d",
                srv.remote, c->id, c->stat.send, c->stat.sendack, c->stat.recv, c->stat.recvack,
                c->stat.dropped, c->stat.missing, c->stat.sendwait, c->stat.sendtimeout);

        qlock(&srv.lk);
        if(c->closed) {
                qunlock(&srv.lk);
                return;
        }
        c->closed = 1;

        close(c->dhcpfd[0]);
        close(c->dhcpfd[1]);
        close(c->pppfd);
        c->pppfd = -1;

        h = c->id%Nhash;
        id = c->id;
        for(c=srv.hash[h],oc=0; c; oc=c,c=c->next)
                if(c->id == id)
                        break;
        if(oc == 0)
                srv.hash[h] = c->next;
        else
                oc->next = c->next;
        c->next = 0;
        qunlock(&srv.lk);

        callfree(c);
}

void
callfree(Call *c)
{       
        int ref;
        
        qlock(&srv.lk);
        ref = --c->ref;
        qunlock(&srv.lk);
        if(ref > 0)
                return;
        
        /* already unhooked from hash list - see callclose */
        assert(c->closed == 1);
        assert(ref == 0);
        assert(c->next == 0);

SDB "call free\n" EDB   
        free(c);
}

Call*
calllookup(int id)
{
        uint h;
        Call *c;

        h = id%Nhash;

        qlock(&srv.lk);
        for(c=srv.hash[h]; c; c=c->next)
                if(c->id == id)
                        break;
        if(c != 0)
                c->ref++;
        qunlock(&srv.lk);

        return c;
}


void
srvinit(void)
{
        char buf[100];
        int fd, n;

        sprint(buf, "%s/local", srv.tcpdir);
        if((fd = open(buf, OREAD)) < 0)
                myfatal("could not open %s: %r", buf);
        if((n = read(fd, buf, sizeof(buf))) < 0)
                myfatal("could not read %s: %r", buf);
        buf[n] = 0;
        parseip(srv.local, buf);
        close(fd);

        sprint(buf, "%s/remote", srv.tcpdir);
        if((fd = open(buf, OREAD)) < 0)
                myfatal("could not open %s: %r", buf);
        if((n = read(fd, buf, sizeof(buf))) < 0)
                myfatal("could not read %s: %r", buf);
        buf[n] = 0;
        parseip(srv.remote, buf);
        close(fd);

        if(srv.pppdir == 0)
                srv.pppdir = "/net";

        if(srv.pppexec == 0)
                srv.pppexec = "/bin/ip/ppp";

        if(myipaddr(srv.ipaddr, srv.pppdir) < 0)
                myfatal("could not read local ip addr: %r");
        if(srv.recvwindow == 0)
                srv.recvwindow = Window;
}

void
greinit(void)
{
        char addr[100], *p;
        int fd, cfd;

        SDB "srv.tcpdir = %s\n", srv.tcpdir EDB
        strcpy(addr, srv.tcpdir);
        p = strrchr(addr, '/');
        if(p == 0)
                myfatal("bad tcp dir: %s", srv.tcpdir);
        *p = 0;
        p = strrchr(addr, '/');
        if(p == 0)
                myfatal("bad tcp dir: %s", srv.tcpdir);
        sprint(p, "/gre!%I!34827", srv.remote);

        SDB "addr = %s\n", addr EDB

        fd = dial(addr, 0, 0, &cfd);

        if(fd < 0)
                myfatal("%I: dial %s failed: %r", srv.remote, addr);

        srv.grefd = fd;
        srv.grecfd = cfd;

        thread(greread, 0);
}

void
greread(void *)
{
        uchar buf[Pktsize], *p;
        int n, i;
        int flag, prot, len, callid;
        uchar src[IPaddrlen], dst[IPaddrlen];
        uint rseq, ack;
        Call *c;
        static double t, last;

        for(;;) {
                n = read(srv.grefd, buf, sizeof(buf));
                if(n < 0)
                        myfatal("%I: bad read on gre: %r", srv.remote);
                if(n == sizeof(buf))
                        myfatal("%I: gre read: buf too small", srv.remote);

                p = buf;
                v4tov6(src, p);
                v4tov6(dst, p+4);
                flag = GSHORT(p+8);
                prot = GSHORT(p+10);
                p += 12; n -= 12;

                if(ipcmp(src, srv.remote) != 0 || ipcmp(dst, srv.local) != 0)
                        myfatal("%I: gre read bad address src=%I dst=%I", srv.remote, src, dst);

                if(prot != GRE_ppp)
                        myfatal("%I: gre read gave bad protocol", srv.remote);

                if(flag & (GRE_chksum|GRE_routing)){
                        p += 4; n -= 4;
                }

                if(!(flag&GRE_key))
                        myfatal("%I: gre packet does not contain a key: f=%ux",
                                srv.remote, flag);

                len = GSHORT(p);
                callid = GSHORT(p+2);
                p += 4; n -= 4;

                c = calllookup(callid);
                if(c == 0) {
                        SDB "%I: unknown callid: %d\n", srv.remote, callid EDB
                        continue;
                }

                qlock(&c->lk);

                c->stat.recv++;

                if(flag&GRE_seq) {
                        rseq = GLONG(p);
                        p += 4; n -= 4;
                } else
                        rseq = c->rseq;

                if(flag&GRE_ack){
                        ack = GLONG(p);
                        p += 4; n -= 4;
                } else
                        ack = c->ack;

                /* skip routing if present */
                if(flag&GRE_routing) {
                        while((i=p[3]) != 0) {
                                n -= i;
                                p += i;
                        }
                }

                if(len > n)
                        myfatal("%I: bad len in gre packet", srv.remote);

                if((int)(ack-c->ack) > 0) {
                        c->ack = ack;
                        esignal(&c->eack);
                }

                if(debug)
                        t = realtime();

                if(len == 0) {
                        /* ack packet */
                        c->stat.recvack++;

SDB "%I: %.3f (%.3f): gre %d: recv ack a=%ux n=%d flag=%ux\n", srv.remote, t, t-last,
        c->id, ack, n, flag EDB

                } else {

SDB "%I: %.3f (%.3f): gre %d: recv s=%ux a=%ux len=%d\n", srv.remote, t, t-last,
        c->id, rseq, ack, len EDB

                        /*
                         * the following handles the case of a single pair of packets
                         * received out of order
                         */
                        n = rseq-c->rseq;
                        if(n > 0 && (drop == 0. || frand() > drop)) {
                                c->stat.missing += n-1;
                                /* current packet */
                                write(c->pppfd, p, len);
                        } else {
                                /* out of sequence - drop on the floor */
                                c->stat.dropped++;

SDB "%I: %.3f: gre %d: recv out of order or dup packet: seq=%ux len=%d\n",
srv.remote, realtime(), c->id, rseq, len EDB

                        }
                }

                if((int)(rseq-c->rseq) > 0)
                        c->rseq = rseq;

                if(debug)
                        last=t;

                /* open up client window */
                if((int)(c->rseq-c->rack) > (c->recvwindow>>1))
                        greack(c);

                qunlock(&c->lk);


                callfree(c);
        }
}

void
greack(Call *c)
{
        uchar buf[20];

        c->stat.sendack++;

SDB "%I: %.3f: gre %d: send ack %ux\n", srv.remote, realtime(), c->id, c->rseq EDB

        v6tov4(buf+0, srv.local);               /* source */
        v6tov4(buf+4, srv.remote);              /* source */
        PSHORT(buf+8, GRE_key|GRE_ack|1);
        PSHORT(buf+10, GRE_ppp);
        PSHORT(buf+12, 0);
        PSHORT(buf+14, c->id);
        PLONG(buf+16, c->rseq);

        write(srv.grefd, buf, sizeof(buf));

        c->rack = c->rseq;

}

void
gretimeout(void *a)
{
        Call *c;

        c = a;

        while(!c->closed) {
                sleep(Tick);
                qlock(&c->lk);
                c->tick++;
                qunlock(&c->lk);
                esignal(&c->eack);
        }
        callfree(c);
        exits(0);
}


void
pppread(void *a)
{
        Call *c;
        uchar buf[2000], *p;
        int n;
        ulong tick;

        c = a;
        for(;;) {
                p = buf+24;
                n = read(c->pppfd, p, sizeof(buf)-24);
                if(n <= 0)
                        break;
                
                qlock(&c->lk);

                /* add gre header */
                c->seq++;
                tick = c->tick;

                while(c->seq-c->ack>c->sendwindow && c->tick-tick<Sendtimeout && !c->closed) {
                        c->stat.sendwait++;
SDB "window full seq = %d ack = %ux window = %ux\n", c->seq, c->ack, c->sendwindow EDB
                        qunlock(&c->lk);
                        ewait(&c->eack);
                        qlock(&c->lk);
                }
                
                if(c->tick-tick >= Sendtimeout) {
                        c->stat.sendtimeout++;
SDB "send timeout = %d ack = %ux window = %ux\n", c->seq, c->ack, c->sendwindow EDB
                }

                v6tov4(buf+0, srv.local);               /* source */
                v6tov4(buf+4, srv.remote);              /* source */
                PSHORT(buf+8, GRE_key|GRE_seq|GRE_ack|1);
                PSHORT(buf+10, GRE_ppp);
                PSHORT(buf+12, n);
                PSHORT(buf+14, c->id);
                PLONG(buf+16, c->seq);
                PLONG(buf+20, c->rseq);

                c->stat.send++;
                c->rack = c->rseq;

SDB "%I: %.3f: gre %d: send s=%ux a=%ux len=%d\n", srv.remote, realtime(),
        c->id,  c->seq, c->rseq, n EDB

                if(drop == 0. || frand() > drop)
                        if(write(srv.grefd, buf, n+24)<n+24)
                                myfatal("pppread: write failed: %r");

                qunlock(&c->lk);
        }

        SDB "pppread exit: %d\n", c->id);
        
        callfree(c);
        exits(0);
}

void
timeoutthread(void*)
{
        for(;;) {
                sleep(30*1000);

                qlock(&srv.lk);
                if(realtime() - srv.rcvtime > 5*60)
                        myfatal("server timedout");
                qunlock(&srv.lk);
        }
}


/* use syslog() rather than fprint(2, ...) */
void
myfatal(char *fmt, ...)
{
        char sbuf[512];
        va_list arg;
        uchar buf[16];

        /* NT don't seem to like us just going away */
        memset(buf, 0, sizeof(buf));
        PSHORT(buf+0, sizeof(buf));     /* length */
        PSHORT(buf+2, 1);               /* message type */
        PLONG(buf+4, Magic);            /* magic */
        PSHORT(buf+8, Tstop);           /* op */
        buf[12] = 3;                    /* local shutdown */

        write(1, buf, sizeof(buf));

        va_start(arg, fmt);
        vseprint(sbuf, sbuf+sizeof(sbuf), fmt, arg);
        va_end(arg);

        SDB "%I: fatal: %s\n", srv.remote, sbuf EDB
        syslog(0, LOG, ": src=%I: fatal: %s", srv.remote, sbuf);

        close(0);
        close(1);
        close(srv.grefd);
        close(srv.grecfd);

        postnote(PNGROUP, getpid(), "die");
        exits(sbuf);
}

int
argatoi(char *p)
{
        char *q;
        int i;

        if(p == 0)
                usage();

        i = strtol(p, &q, 0);
        if(q == p)
                usage();
        return i;
}

void
dhcpclientwatch(void *a)
{
        Call *c = a;
        uchar buf[1];

        for(;;) {
                if(read(c->dhcpfd[0], buf, sizeof(buf)) <= 0)
                        break;
        }
        if(!c->closed)
                myfatal("dhcpclient terminated");
        callfree(c);
        exits(0);
}

int
ipaddralloc(Call *c)
{
        int pfd[2][2];
        char *argv[4], *p;
        Biobuf bio;

        argv[0] = "/bin/ip/dhcpclient";
        argv[1] = "-x";
        argv[2] = srv.pppdir;
        argv[3] = 0;

        if(pipe(pfd[0])<0)
                myfatal("ipaddralloc: pipe failed: %r");
        if(pipe(pfd[1])<0)
                myfatal("ipaddralloc: pipe failed: %r");

        if(proc(argv, pfd[0][0], pfd[1][1], 2) < 0)
                myfatal("ipaddralloc: proc failed: %r");

        close(pfd[0][0]);
        close(pfd[1][1]);
        c->dhcpfd[0] = pfd[1][0];
        c->dhcpfd[1] = pfd[0][1];

        Binit(&bio, pfd[1][0], OREAD);
        for(;;) {
                p = Brdline(&bio, '\n');
                if(p == 0)
                        break;
                if(strncmp(p, "ip=", 3) == 0) {
                        p += 3;
                        parseip(c->remoteip, p);
                } else if(strncmp(p, "end\n", 4) == 0)
                        break;
        }

        Bterm(&bio);

        c->ref++;

        thread(dhcpclientwatch, c);

        return ipcmp(c->remoteip, IPnoaddr) != 0;
}


void
esignal(Event *e)
{       
        qlock(e);
        if(e->wait == 0) {
                e->ready = 1;
                qunlock(e);
                return;
        }
        assert(e->ready == 0);
        e->wait = 0;
        rendezvous(e, (void*)1);
        qunlock(e);
}

void
ewait(Event *e)
{
        qlock(&e->waitlk);
        qlock(e);
        assert(e->wait == 0);
        if(e->ready) {
                e->ready = 0;
        } else {        
                e->wait = 1;
                qunlock(e);
                rendezvous(e, (void*)2);
                qlock(e);
        }
        qunlock(e);
        qunlock(&e->waitlk);
}

ulong
thread(void(*f)(void*), void *a)
{
        int pid;
        pid=rfork(RFNOWAIT|RFMEM|RFPROC);
        if(pid < 0)
                myfatal("rfork failed: %r");
        if(pid != 0)
                return pid;
        (*f)(a);
        return 0; // never reaches here
}

double
realtime(void)
{
        long times(long*);

        return times(0) / 1000.0;
}

void *
emallocz(int size)
{
        void *p;
        p = malloc(size);
        if(p == 0)
                myfatal("malloc failed: %r");
        memset(p, 0, size);
        return p;
}

static void
fdclose(void)
{
        int fd, n, i;
        Dir *d, *p;

        if((fd = open("#d", OREAD)) < 0)
                return;

        n = dirreadall(fd, &d);
        for(p = d; n > 0; n--, p++) {
                i = atoi(p->name);
                if(i > 2)
                        close(i);
        }
        free(d);
}

int
proc(char **argv, int fd0, int fd1, int fd2)
{
        int r, flag;
        char *arg0, file[200];

        arg0 = argv[0];

        strcpy(file, arg0);

        if(access(file, 1) < 0) {
                if(strncmp(arg0, "/", 1)==0
                || strncmp(arg0, "#", 1)==0
                || strncmp(arg0, "./", 2)==0
                || strncmp(arg0, "../", 3)==0)
                        return 0;
                sprint(file, "/bin/%s", arg0);
                if(access(file, 1) < 0)
                        return 0;
        }

        flag = RFPROC|RFFDG|RFENVG|RFNOWAIT;
        if((r = rfork(flag)) != 0) {
                if(r < 0)
                        return 0;
                return r;
        }

        if(fd0 != 0) {
                if(fd1 == 0)
                        fd1 = dup(0, -1);
                if(fd2 == 0)
                        fd2 = dup(0, -1);
                close(0);
                if(fd0 >= 0)
                        dup(fd0, 0);
        }

        if(fd1 != 1) {
                if(fd2 == 1)
                        fd2 = dup(1, -1);
                close(1);
                if(fd1 >= 0)
                        dup(fd1, 1);
        }

        if(fd2 != 2) {
                close(2);
                if(fd2 >= 0)
                        dup(fd2, 2);
        }

        fdclose();

        exec(file, argv);
        myfatal("proc: exec failed: %r");
        return 0;
}