Subversion Repositories planix.SVN

Rev

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

/*
 * 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] [-k keyspec] [-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);
}