1341 lines
23 KiB
C
1341 lines
23 KiB
C
#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;
|
|
}
|
|
|