/*
* Point-to-point Tunneling Protocol (PPTP)
* See RFC 2637, pptpd.c
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <thread.h>
int ack;
int alarmed;
int ctlechotime;
int ctlfd;
int ctlrcvtime;
int debug;
int grefd;
uchar localip[IPaddrlen];
int localwin;
char *keyspec;
int now;
char *pppnetmntpt;
int pid;
Channel *pidchan;
int pppfd;
int primary;
int rack;
Channel *rdchan;
int rdexpect;
int remid;
uchar remoteip[IPaddrlen];
int remwin;
int rseq;
int seq;
char tcpdir[40];
Channel *tickchan;
int topppfd;
int aread(int, int, void*, int);
int catchalarm(void*, char*);
void dumpctlpkt(uchar*);
void getaddrs(void);
void *emalloc(long);
void ewrite(int, void*, int);
void myfatal(char*, ...);
#pragma varargck argpos myfatal 1
int pptp(char*);
void pushppp(int);
void recordack(int);
int schedack(int, uchar*, int);
void waitacks(void);
void
usage(void)
{
fprint(2, "usage: ip/pptp [-Pd] [-s user:secret] [-x pppnetmntpt] [-w window] server\n");
exits("usage");
}
void
threadmain(int argc, char **argv)
{
int fd;
ARGBEGIN{
case 'P':
primary = 1;
break;
case 'd':
debug++;
break;
case 'k':
keyspec = EARGF(usage());
break;
case 'w':
localwin = atoi(EARGF(usage()));
break;
case 'x':
pppnetmntpt = EARGF(usage());
break;
default:
usage();
}ARGEND
if(argc != 1)
usage();
fmtinstall('E', eipfmt);
fmtinstall('I', eipfmt);
rfork(RFNOTEG);
atnotify(catchalarm, 1);
fd = pptp(argv[0]);
pushppp(fd);
exits(nil);
}
int
catchalarm(void *a, char *msg)
{
USED(a);
if(strstr(msg, "alarm")){
alarmed = 1;
return 1;
}
if(debug)
fprint(2, "note rcved: %s\n", msg);
return 0;
}
enum {
Stack = 8192,
PptpProto = 0x0100,
Magic = 0x1a2b3c4d,
Window = 16, /* 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 */
Servertimeout = 5*60*1000/Tick,
Echointerval = 60*1000/Tick,
};
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,
};
void
recho(uchar *in)
{
uchar out[20];
if(nhgets(in) < 16)
return;
memset(out, 0, sizeof out);
hnputs(out, sizeof out);
hnputs(out+2, 1);
hnputl(out+4, Magic);
hnputs(out+8, Recho);
memmove(out+12, in+12, 4);
out[16] = 1;
ewrite(ctlfd, out, sizeof out);
}
void
sendecho(void)
{
uchar out[16];
ctlechotime = now;
memset(out, 0, sizeof out);
hnputs(out, sizeof out);
hnputs(out+2, 1);
hnputl(out+4, Magic);
hnputs(out+8, Techo);
ewrite(ctlfd, out, sizeof out);
}
void
pptpctlproc(void*)
{
uchar pkt[1600], *p;
int len;
for(;;){
if(readn(ctlfd, pkt, 2) != 2)
myfatal("pptpread: %r");
len = nhgets(pkt);
if(len < 12 || len+2 >= sizeof pkt)
myfatal("pptpread: bad length %d", len);
if(readn(ctlfd, pkt+2, len-2) != len-2)
myfatal("pptpread: %r");
if(nhgetl(pkt+4) != Magic)
myfatal("pptpread bad magic");
if(nhgets(pkt+2) != 1)
myfatal("pptpread bad message type");
if(debug)
dumpctlpkt(pkt);
ctlrcvtime = now;
switch(nhgets(pkt+8)){
case Tstart:
case Tstop:
case Tcallout:
case Tcallreq:
case Tcallclear:
case Acallcon:
case Acalldis:
case Awaninfo:
myfatal("unexpected msg type %d", nhgets(pkt+8));
case Techo:
recho(pkt);
break;
case Recho:
break;
case Rstart:
case Rstop:
case Rcallout:
case Rcallreq:
if(rdexpect != nhgets(pkt+8))
continue;
p = emalloc(len);
memmove(p, pkt, len);
sendp(rdchan, p);
break;
case Alinkinfo:
myfatal("cannot change ppp params on the fly");
}
}
}
enum {
Seqnum = 0x1000,
Acknum = 0x0080,
GrePPP = 0x880B,
};
void
grereadproc(void*)
{
int datoff, flags, len, n, pass;
uchar pkt[1600];
uchar src[IPaddrlen], dst[IPaddrlen];
rfork(RFFDG);
close(pppfd);
sendul(pidchan, getpid());
while((n = read(grefd, pkt, sizeof pkt)) > 0){
if(n == sizeof pkt)
myfatal("gre pkt buffer too small");
if(n < 16){
if(debug)
fprint(2, "small pkt len %d ignored\n", n);
continue;
}
v4tov6(src, pkt);
v4tov6(dst, pkt+4);
if(ipcmp(src, remoteip) != 0 || ipcmp(dst, localip) != 0)
myfatal("%I: gre read bad address src=%I dst=%I",
remoteip, src, dst);
if(nhgets(pkt+10) != GrePPP)
myfatal("%I: gre read bad protocol 0x%x",
remoteip, nhgets(pkt+10));
flags = nhgets(pkt+8);
if((flags&0xEF7F) != 0x2001){
if(debug)
fprint(2, "bad flags in gre hdr 0x%x\n", flags);
continue;
}
datoff = 8+8;
pass = 0;
len = nhgets(pkt+8+4);
if(len > n-datoff){
fprint(2, "bad payload length %d > %d\n",
len, n-datoff);
continue;
}
if(flags&Seqnum)
datoff += 4;
if(flags&Acknum){
recordack(nhgetl(pkt+datoff));
datoff += 4;
}
if(flags&Seqnum)
pass = schedack(nhgetl(pkt+8+8), pkt+datoff, len);
if(debug)
fprint(2, "got gre callid %d len %d flag 0x%x pass %d seq %d rseq %d\n", nhgets(pkt+8+6),
len, flags, pass, nhgetl(pkt+8+8), rseq);
}
threadexits(nil);
}
void
pppreadproc(void*)
{
int n, myrseq;
uchar pkt[1600];
enum {
Hdr = 8+16,
};
rfork(RFFDG);
close(pppfd);
sendul(pidchan, getpid());
while((n = read(topppfd, pkt+Hdr, sizeof pkt-Hdr)) > 0){
if(n == sizeof pkt-Hdr)
myfatal("ppp pkt buffer too small");
v6tov4(pkt+0, localip);
v6tov4(pkt+4, remoteip);
hnputs(pkt+8, 0x2001 | Seqnum | Acknum);
hnputs(pkt+10, GrePPP);
hnputs(pkt+12, n);
hnputs(pkt+14, remid);
hnputl(pkt+16, ++seq);
myrseq = rseq;
hnputl(pkt+20, myrseq);
rack = myrseq;
if(debug)
fprint(2, "wrote gre callid %d len %d flag 0x%x seq %d rseq %d\n", nhgets(pkt+8+6),
n, nhgets(pkt+8), nhgetl(pkt+16), nhgetl(pkt+20));
if(write(grefd, pkt, n+Hdr) != n+Hdr)
myfatal("gre write: %r");
waitacks();
}
threadexits(nil);
}
void
sendack(void)
{
int myrseq;
uchar pkt[20];
v6tov4(pkt+0, localip);
v6tov4(pkt+4, remoteip);
hnputs(pkt+8, 0x2001 | Acknum);
hnputs(pkt+10, GrePPP);
hnputs(pkt+12, 0);
hnputs(pkt+14, remid);
myrseq = rseq;
rack = myrseq;
hnputs(pkt+16, myrseq);
if(write(grefd, pkt, sizeof pkt) != sizeof pkt)
myfatal("gre write: %r");
}
int
schedack(int n, uchar *dat, int len)
{
static uchar sdat[1600];
static int srseq, slen;
if(n-rseq <= 0){
fprint(2, "skipping pkt %d len %d, have %d\n", n, len, rseq);
return 0;
}
/* missed one pkt, maybe a swap happened, save pkt */
if(n==rseq+2){
memmove(sdat, dat, len);
slen = len;
srseq = n;
return 0;
}
if(n-rseq > 1){
if(slen && srseq == n-1){
fprint(2, "reswapped pkts %d and %d\n", srseq, n);
write(topppfd, sdat, slen);
slen = 0;
}else
fprint(2, "missed pkts %d-%d, got %d len %d\n", rseq+1, n-1, n, len);
}
write(topppfd, dat, len);
rseq = n;
/* send ack if we haven't recently */
if((int)(rseq-rack) > (localwin>>1))
sendack();
return 1;
}
void
gretimeoutproc(void*)
{
for(;;){
sleep(Tick);
now++;
nbsendul(tickchan, now);
if(now - ctlrcvtime > Servertimeout)
myfatal("server timeout");
if(now - ctlechotime > Echointerval)
sendecho();
}
}
void
recordack(int n)
{
ack = n;
}
void
waitacks(void)
{
/*
int start;
start = now;
while(seq-ack > remwin && now-start < Sendtimeout){
print("seq %d ack %d remwin %d now %d start %d\n",
seq, ack, remwin, now, start);
recvul(tickchan);
}
*/
}
void
tstart(void)
{
char *name;
uchar pkt[200], *rpkt;
memset(pkt, 0, sizeof pkt);
hnputs(pkt+0, 156);
hnputs(pkt+2, 1);
hnputl(pkt+4, Magic);
hnputs(pkt+8, Tstart);
hnputs(pkt+12, PptpProto);
hnputl(pkt+16, 1);
hnputl(pkt+20, 1);
hnputs(pkt+24, 1);
name = sysname();
if(name == nil)
name = "gnot";
strcpy((char*)pkt+28, name);
strcpy((char*)pkt+92, "plan 9");
if(debug)
dumpctlpkt(pkt);
rdexpect = Rstart;
ewrite(ctlfd, pkt, 156);
rpkt = recvp(rdchan);
if(rpkt == nil)
myfatal("recvp: %r");
if(nhgets(rpkt) != 156)
myfatal("Rstart wrong length %d != 156", nhgets(rpkt));
if(rpkt[14] != 1)
myfatal("Rstart error %d", rpkt[15]);
free(rpkt);
}
void
tcallout(void)
{
uchar pkt[200], *rpkt;
pid = getpid();
memset(pkt, 0, sizeof pkt);
hnputs(pkt+0, 168);
hnputs(pkt+2, 1);
hnputl(pkt+4, Magic);
hnputs(pkt+8, Tcallout);
hnputl(pkt+16, 56000);
hnputl(pkt+20, 768000);
hnputl(pkt+24, 3);
hnputl(pkt+28, 3);
if(localwin == 0)
localwin = Window;
hnputs(pkt+32, localwin);
if(debug)
dumpctlpkt(pkt);
rdexpect = Rcallout;
ewrite(ctlfd, pkt, 168);
rpkt = recvp(rdchan);
if(rpkt == nil)
myfatal("recvp: %r");
if(nhgets(rpkt) != 32)
myfatal("Rcallreq wrong length %d != 32", nhgets(rpkt));
if(rpkt[16] != 1)
myfatal("Rcallreq error %d", rpkt[17]);
remid = nhgets(pkt+12);
remwin = nhgets(pkt+24);
free(rpkt);
}
/*
void
tcallreq(void)
{
uchar pkt[200], *rpkt;
pid = getpid();
memset(pkt, 0, sizeof pkt);
hnputs(pkt+0, 220);
hnputs(pkt+2, 1);
hnputl(pkt+4, Magic);
hnputs(pkt+8, Tcallreq);
if(debug)
dumpctlpkt(pkt);
rdexpect = Rcallreq;
ewrite(ctlfd, pkt, 220);
rpkt = recvp(rdchan);
if(rpkt == nil)
myfatal("recvp: %r");
if(nhgets(rpkt) != 24)
myfatal("Rcallreq wrong length %d != 24", nhgets(rpkt));
if(rpkt[16] != 1)
myfatal("Rcallreq error %d", rpkt[17]);
remid = nhgets(pkt+12);
remwin = nhgets(pkt+18);
free(rpkt);
}
void
acallcon(void)
{
uchar pkt[200];
memset(pkt, 0, sizeof pkt);
hnputs(pkt+0, 28);
hnputs(pkt+2, 1);
hnputl(pkt+4, Magic);
hnputs(pkt+8, Acallcon);
hnputs(pkt+12, remid);
if(localwin == 0)
localwin = Window;
hnputs(pkt+20, localwin);
hnputl(pkt+24, 1);
if(debug)
dumpctlpkt(pkt);
ewrite(ctlfd, pkt, 28);
}
*/
int
pptp(char *addr)
{
int p[2];
char greaddr[128];
addr = netmkaddr(addr, "net", "pptp");
ctlfd = dial(addr, nil, tcpdir, nil);
if(ctlfd < 0)
myfatal("dial %s: %r", addr);
getaddrs();
rdchan = chancreate(sizeof(void*), 0);
proccreate(pptpctlproc, nil, Stack);
tstart();
tcallout();
if(pipe(p) < 0)
myfatal("pipe: %r");
pppfd = p[0];
topppfd = p[1];
strcpy(greaddr, tcpdir);
*strrchr(greaddr, '/') = '\0';
sprint(strrchr(greaddr, '/')+1, "gre!%I!%d", remoteip, GrePPP);
print("local %I remote %I gre %s remid %d remwin %d\n",
localip, remoteip, greaddr, remid, remwin);
grefd = dial(greaddr, nil, nil, nil);
if(grefd < 0)
myfatal("dial gre: %r");
tickchan = chancreate(sizeof(int), 0);
proccreate(gretimeoutproc, nil, Stack);
pidchan = chancreate(sizeof(int), 0);
proccreate(grereadproc, nil, Stack);
recvul(pidchan);
proccreate(pppreadproc, nil, Stack);
recvul(pidchan);
close(topppfd);
return pppfd;
}
void
pushppp(int fd)
{
char *argv[16];
int argc;
argc = 0;
argv[argc++] = "/bin/ip/ppp";
argv[argc++] = "-C";
argv[argc++] = "-m1450";
if(debug)
argv[argc++] = "-d";
if(primary)
argv[argc++] = "-P";
if(pppnetmntpt){
argv[argc++] = "-x";
argv[argc++] = pppnetmntpt;
}
if(keyspec){
argv[argc++] = "-k";
argv[argc++] = keyspec;
}
argv[argc] = nil;
switch(fork()){
case -1:
myfatal("fork: %r");
default:
return;
case 0:
dup(fd, 0);
dup(fd, 1);
exec(argv[0], argv);
myfatal("exec: %r");
}
}
int
aread(int timeout, int fd, void *buf, int nbuf)
{
int n;
alarmed = 0;
alarm(timeout);
n = read(fd, buf, nbuf);
alarm(0);
if(alarmed)
return -1;
if(n < 0)
myfatal("read: %r");
if(n == 0)
myfatal("short read");
return n;
}
void
ewrite(int fd, void *buf, int nbuf)
{
char e[ERRMAX], path[64];
if(write(fd, buf, nbuf) != nbuf){
rerrstr(e, sizeof e);
strcpy(path, "unknown");
fd2path(fd, path, sizeof path);
myfatal("write %d to %s: %s", nbuf, path, e);
}
}
void*
emalloc(long n)
{
void *v;
v = malloc(n);
if(v == nil)
myfatal("out of memory");
return v;
}
int
thread(void(*f)(void*), void *a)
{
int pid;
pid=rfork(RFNOWAIT|RFMEM|RFPROC);
if(pid < 0)
myfatal("rfork: %r");
if(pid != 0)
return pid;
(*f)(a);
_exits(nil);
return 0; // never reaches here
}
void
dumpctlpkt(uchar *pkt)
{
fprint(2, "pkt len %d mtype %d cookie 0x%.8ux type %d\n",
nhgets(pkt), nhgets(pkt+2),
nhgetl(pkt+4), nhgets(pkt+8));
switch(nhgets(pkt+8)){
default:
fprint(2, "\tunknown type\n");
break;
case Tstart:
fprint(2, "\tTstart proto %d framing %d bearer %d maxchan %d firmware %d\n",
nhgets(pkt+12), nhgetl(pkt+16),
nhgetl(pkt+20), nhgets(pkt+24),
nhgets(pkt+26));
fprint(2, "\thost %.64s\n", (char*)pkt+28);
fprint(2, "\tvendor %.64s\n", (char*)pkt+92);
break;
case Rstart:
fprint(2, "\tRstart proto %d res %d err %d framing %d bearer %d maxchan %d firmware %d\n",
nhgets(pkt+12), pkt[14], pkt[15],
nhgetl(pkt+16),
nhgetl(pkt+20), nhgets(pkt+24),
nhgets(pkt+26));
fprint(2, "\thost %.64s\n", (char*)pkt+28);
fprint(2, "\tvendor %.64s\n", (char*)pkt+92);
break;
case Tstop:
fprint(2, "\tTstop reason %d\n", pkt[12]);
break;
case Rstop:
fprint(2, "\tRstop res %d err %d\n", pkt[12], pkt[13]);
break;
case Techo:
fprint(2, "\tTecho id %.8ux\n", nhgetl(pkt+12));
break;
case Recho:
fprint(2, "\tRecho id %.8ux res %d err %d\n", nhgetl(pkt+12), pkt[16], pkt[17]);
break;
case Tcallout:
fprint(2, "\tTcallout id %d serno %d bps %d-%d\n",
nhgets(pkt+12), nhgets(pkt+14),
nhgetl(pkt+16), nhgetl(pkt+20));
fprint(2, "\tbearer 0x%x framing 0x%x recvwin %d delay %d\n",
nhgetl(pkt+24), nhgetl(pkt+28),
nhgets(pkt+32), nhgets(pkt+34));
fprint(2, "\tphone len %d num %.64s\n",
nhgets(pkt+36), (char*)pkt+40);
fprint(2, "\tsubaddr %.64s\n", (char*)pkt+104);
break;
case Rcallout:
fprint(2, "\tRcallout id %d peerid %d res %d err %d cause %d\n",
nhgets(pkt+12), nhgets(pkt+14),
pkt[16], pkt[17], nhgets(pkt+18));
fprint(2, "\tconnect %d recvwin %d delay %d chan 0x%.8ux\n",
nhgetl(pkt+20), nhgets(pkt+24),
nhgets(pkt+26), nhgetl(pkt+28));
break;
case Tcallreq:
fprint(2, "\tTcallreq id %d serno %d bearer 0x%x id 0x%x\n",
nhgets(pkt+12), nhgets(pkt+14),
nhgetl(pkt+16), nhgetl(pkt+20));
fprint(2, "\tdialed len %d num %.64s\n",
nhgets(pkt+24), (char*)pkt+28);
fprint(2, "\tdialing len %d num %.64s\n",
nhgets(pkt+26), (char*)pkt+92);
fprint(2, "\tsubaddr %.64s\n", (char*)pkt+156);
break;
case Rcallreq:
fprint(2, "\tRcallout id %d peerid %d res %d err %d recvwin %d delay %d\n",
nhgets(pkt+12), nhgets(pkt+14),
pkt[16], pkt[17], nhgets(pkt+18),
nhgets(pkt+20));
break;
case Acallcon:
fprint(2, "\tAcallcon peerid %d connect %d recvwin %d delay %d framing 0x%x\n",
nhgets(pkt+12), nhgetl(pkt+16),
nhgets(pkt+20), nhgets(pkt+22),
nhgetl(pkt+24));
break;
case Tcallclear:
fprint(2, "\tTcallclear callid %d\n",
nhgets(pkt+12));
break;
case Acalldis:
fprint(2, "\tAcalldis callid %d res %d err %d cause %d\n",
nhgets(pkt+12), pkt[14], pkt[15],
nhgets(pkt+16));
fprint(2, "\tstats %.128s\n", (char*)pkt+20);
break;
case Awaninfo:
fprint(2, "\tAwaninfo peerid %d\n", nhgets(pkt+12));
fprint(2, "\tcrc errors %d\n", nhgetl(pkt+16));
fprint(2, "\tframe errors %d\n", nhgetl(pkt+20));
fprint(2, "\thardware overruns %d\n", nhgetl(pkt+24));
fprint(2, "\tbuffer overruns %d\n", nhgetl(pkt+28));
fprint(2, "\ttime-out errors %d\n", nhgetl(pkt+32));
fprint(2, "\talignment errors %d\n", nhgetl(pkt+36));
break;
case Alinkinfo:
fprint(2, "\tAlinkinfo peerid %d sendaccm 0x%ux recvaccm 0x%ux\n",
nhgets(pkt+12), nhgetl(pkt+16),
nhgetl(pkt+20));
break;
}
}
void
getaddrs(void)
{
char buf[128];
int fd, n;
sprint(buf, "%s/local", 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(localip, buf);
close(fd);
sprint(buf, "%s/remote", 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(remoteip, buf);
close(fd);
}
void
myfatal(char *fmt, ...)
{
char sbuf[512];
va_list arg;
uchar buf[16];
memset(buf, 0, sizeof(buf));
hnputs(buf+0, sizeof(buf)); /* length */
hnputs(buf+2, 1); /* message type */
hnputl(buf+4, Magic); /* magic */
hnputs(buf+8, Tstop); /* op */
buf[12] = 3; /* local shutdown */
write(ctlfd, buf, sizeof(buf));
va_start(arg, fmt);
vseprint(sbuf, sbuf+sizeof(sbuf), fmt, arg);
va_end(arg);
fprint(2, "fatal: %s\n", sbuf);
threadexitsall(nil);
}
|