/* * User-level PPP over Ethernet (PPPoE) client. * See RFC 2516 */ #include #include #include void dumppkt(uchar*); uchar *findtag(uchar*, int, int*, int); void hexdump(uchar*, int); int malformed(uchar*, int, int); int pppoe(char*); void execppp(int); int alarmed; int debug; int sessid; char *keyspec; int primary; char *pppnetmtpt; char *acname; char *pppname = "/bin/ip/ppp"; char *srvname = ""; char *wantac; uchar *cookie; int cookielen; uchar etherdst[6]; int mtu = 1492; void usage(void) { fprint(2, "usage: pppoe [-Pd] [-A acname] [-S srvname] [-k keyspec] [-m mtu] [-x pppnet] [ether0]\n"); exits("usage"); } 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; } void main(int argc, char **argv) { int fd; char *dev; ARGBEGIN{ case 'A': wantac = EARGF(usage()); break; case 'P': primary = 1; break; case 'S': srvname = EARGF(usage()); break; case 'd': debug++; break; case 'm': mtu = atoi(EARGF(usage())); break; case 'k': keyspec = EARGF(usage()); break; case 'x': pppnetmtpt = EARGF(usage()); break; default: usage(); }ARGEND switch(argc){ default: usage(); case 0: dev = "ether0"; break; case 1: dev = argv[0]; break; } fmtinstall('E', eipfmt); atnotify(catchalarm, 1); fd = pppoe(dev); execppp(fd); } typedef struct Etherhdr Etherhdr; struct Etherhdr { uchar dst[6]; uchar src[6]; uchar type[2]; }; enum { EtherHdrSz = 6+6+2, EtherMintu = 60, EtherPppoeDiscovery = 0x8863, EtherPppoeSession = 0x8864, }; uchar etherbcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; int etherhdr(uchar *pkt, uchar *dst, int type) { Etherhdr *eh; eh = (Etherhdr*)pkt; memmove(eh->dst, dst, sizeof(eh->dst)); hnputs(eh->type, type); return EtherHdrSz; } typedef struct Pppoehdr Pppoehdr; struct Pppoehdr { uchar verstype; uchar code; uchar sessid[2]; uchar length[2]; /* of payload */ }; enum { PppoeHdrSz = 1+1+2+2, Hdr = EtherHdrSz+PppoeHdrSz, }; enum { VersType = 0x11, /* Discovery codes */ CodeDiscInit = 0x09, /* discovery init */ CodeDiscOffer = 0x07, /* discovery offer */ CodeDiscReq = 0x19, /* discovery request */ CodeDiscSess = 0x65, /* session confirmation */ /* Session codes */ CodeSession = 0x00, }; int pppoehdr(uchar *pkt, int code, int sessid) { Pppoehdr *ph; ph = (Pppoehdr*)pkt; ph->verstype = VersType; ph->code = code; hnputs(ph->sessid, sessid); return PppoeHdrSz; } typedef struct Taghdr Taghdr; struct Taghdr { uchar type[2]; uchar length[2]; /* of value */ }; enum { TagEnd = 0x0000, /* end of tag list */ TagSrvName = 0x0101, /* service name */ TagAcName = 0x0102, /* access concentrator name */ TagHostUniq = 0x0103, /* nonce */ TagAcCookie = 0x0104, /* a.c. cookie */ TagVendSpec = 0x0105, /* vendor specific */ TagRelaySessId = 0x0110, /* relay session id */ TagSrvNameErr = 0x0201, /* service name error (ascii) */ TagAcSysErr = 0x0202, /* a.c. system error */ }; int tag(uchar *pkt, int type, void *value, int nvalue) { Taghdr *h; h = (Taghdr*)pkt; hnputs(h->type, type); hnputs(h->length, nvalue); memmove(pkt+4, value, nvalue); return 4+nvalue; } /* PPPoE Active Discovery Initiation */ int padi(uchar *pkt) { int sz, tagoff; uchar *length; sz = 0; sz += etherhdr(pkt+sz, etherbcast, EtherPppoeDiscovery); sz += pppoehdr(pkt+sz, CodeDiscInit, 0x0000); length = pkt+sz-2; tagoff = sz; sz += tag(pkt+sz, TagSrvName, srvname, strlen(srvname)); hnputs(length, sz-tagoff); return sz; } /* PPPoE Active Discovery Request */ int padr(uchar *pkt) { int sz, tagoff; uchar *length; sz = 0; sz += etherhdr(pkt+sz, etherdst, EtherPppoeDiscovery); sz += pppoehdr(pkt+sz, CodeDiscReq, 0x0000); length = pkt+sz-2; tagoff = sz; sz += tag(pkt+sz, TagSrvName, srvname, strlen(srvname)); sz += tag(pkt+sz, TagAcName, acname, strlen(acname)); if(cookie) sz += tag(pkt+sz, TagAcCookie, cookie, cookielen); hnputs(length, sz-tagoff); return sz; } 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); sysfatal("write %d to %s: %s", nbuf, path, e); } } void* emalloc(long n) { void *v; v = malloc(n); if(v == nil) sysfatal("out of memory"); return v; } 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) sysfatal("read: %r"); if(n == 0) sysfatal("short read"); return n; } int pktread(int timeout, int fd, void *buf, int nbuf, int (*want)(uchar*)) { int n, t2; n = -1; for(t2=timeout; t2<16000; t2*=2){ while((n = aread(t2, fd, buf, nbuf)) > 0){ if(malformed(buf, n, EtherPppoeDiscovery)){ if(debug) fprint(2, "dropping pkt: %r\n"); continue; } if(debug) dumppkt(buf); if(!want(buf)){ if(debug) fprint(2, "dropping unwanted pkt: %r\n"); continue; } break; } if(n > 0) break; } return n; } int bad(char *reason) { werrstr(reason); return 0; } void* copy(uchar *s, int len) { uchar *v; v = emalloc(len+1); memmove(v, s, len); v[len] = '\0'; return v; } void clearstate(void) { sessid = -1; free(acname); acname = nil; free(cookie); cookie = nil; } int wantoffer(uchar *pkt) { int i, len; uchar *s; Etherhdr *eh; Pppoehdr *ph; eh = (Etherhdr*)pkt; ph = (Pppoehdr*)(pkt+EtherHdrSz); if(ph->code != CodeDiscOffer) return bad("not an offer"); if(nhgets(ph->sessid) != 0x0000) return bad("bad session id"); for(i=0;; i++){ if((s = findtag(pkt, TagSrvName, &len, i)) == nil) return bad("no matching service name"); if(len == strlen(srvname) && memcmp(s, srvname, len) == 0) break; } if((s = findtag(pkt, TagAcName, &len, 0)) == nil) return bad("no ac name"); acname = copy(s, len); if(wantac && strcmp(acname, wantac) != 0){ free(acname); return bad("wrong ac name"); } if(s = findtag(pkt, TagAcCookie, &len, 0)){ cookie = copy(s, len); cookielen = len; } memmove(etherdst, eh->src, sizeof etherdst); return 1; } int wantsession(uchar *pkt) { int len; uchar *s; Pppoehdr *ph; ph = (Pppoehdr*)(pkt+EtherHdrSz); if(ph->code != CodeDiscSess) return bad("not a session confirmation"); if(nhgets(ph->sessid) == 0x0000) return bad("bad session id"); if(findtag(pkt, TagSrvName, &len, 0) == nil) return bad("no service name"); if(findtag(pkt, TagSrvNameErr, &len, 0)) return bad("service name error"); if(findtag(pkt, TagAcSysErr, &len, 0)) return bad("ac system error"); if((s = findtag(pkt, TagSrvName, &len, 0)) == nil) return bad("no matching service name"); if(len != strlen(srvname) || memcmp(s, srvname, len) != 0) return bad("no matching service name"); sessid = nhgets(ph->sessid); return 1; } int pppoe(char *ether) { char buf[64]; uchar pkt[1520]; int dfd, p[2], n, sfd, sz, timeout; Pppoehdr *ph; ph = (Pppoehdr*)(pkt+EtherHdrSz); snprint(buf, sizeof buf, "%s!%d", ether, EtherPppoeDiscovery); if((dfd = dial(buf, nil, nil, nil)) < 0) sysfatal("dial %s: %r", buf); snprint(buf, sizeof buf, "%s!%d", ether, EtherPppoeSession); if((sfd = dial(buf, nil, nil, nil)) < 0) sysfatal("dial %s: %r", buf); for(timeout=250; timeout<16000; timeout*=2){ clearstate(); memset(pkt, 0, sizeof pkt); sz = padi(pkt); if(debug) dumppkt(pkt); if(sz < EtherMintu) sz = EtherMintu; ewrite(dfd, pkt, sz); if(pktread(timeout, dfd, pkt, sizeof pkt, wantoffer) < 0) continue; memset(pkt, 0, sizeof pkt); sz = padr(pkt); if(debug) dumppkt(pkt); if(sz < EtherMintu) sz = EtherMintu; ewrite(dfd, pkt, sz); if(pktread(timeout, dfd, pkt, sizeof pkt, wantsession) < 0) continue; break; } if(sessid < 0) sysfatal("could not establish session"); rfork(RFNOTEG); if(pipe(p) < 0) sysfatal("pipe: %r"); switch(fork()){ case -1: sysfatal("fork: %r"); default: break; case 0: close(p[1]); while((n = read(p[0], pkt+Hdr, sizeof pkt-Hdr)) > 0){ etherhdr(pkt, etherdst, EtherPppoeSession); pppoehdr(pkt+EtherHdrSz, 0x00, sessid); hnputs(pkt+Hdr-2, n); sz = Hdr+n; if(debug > 1){ dumppkt(pkt); hexdump(pkt, sz); } if(sz < EtherMintu) sz = EtherMintu; if(write(sfd, pkt, sz) < 0){ if(debug) fprint(2, "write to ether failed: %r"); _exits(nil); } } _exits(nil); } switch(fork()){ case -1: sysfatal("fork: %r"); default: break; case 0: close(p[1]); while((n = read(sfd, pkt, sizeof pkt)) > 0){ if(malformed(pkt, n, EtherPppoeSession) || ph->code != 0x00 || nhgets(ph->sessid) != sessid){ if(debug) fprint(2, "malformed session pkt: %r\n"); if(debug) dumppkt(pkt); continue; } if(write(p[0], pkt+Hdr, nhgets(ph->length)) < 0){ if(debug) fprint(2, "write to ppp failed: %r\n"); _exits(nil); } } _exits(nil); } close(p[0]); return p[1]; } void execppp(int fd) { char *argv[16]; int argc; char smtu[10]; argc = 0; argv[argc++] = pppname; snprint(smtu, sizeof(smtu), "-m%d", mtu); argv[argc++] = smtu; argv[argc++] = "-F"; if(debug) argv[argc++] = "-d"; if(primary) argv[argc++] = "-P"; if(pppnetmtpt){ argv[argc++] = "-x"; argv[argc++] = pppnetmtpt; } if(keyspec){ argv[argc++] = "-k"; argv[argc++] = keyspec; } argv[argc] = nil; dup(fd, 0); dup(fd, 1); exec(pppname, argv); sysfatal("exec: %r"); } uchar* findtag(uchar *pkt, int tagtype, int *plen, int skip) { int len, sz, totlen; uchar *tagdat, *v; Etherhdr *eh; Pppoehdr *ph; Taghdr *t; eh = (Etherhdr*)pkt; ph = (Pppoehdr*)(pkt+EtherHdrSz); tagdat = pkt+Hdr; if(nhgets(eh->type) != EtherPppoeDiscovery) return nil; totlen = nhgets(ph->length); sz = 0; while(sz+4 <= totlen){ t = (Taghdr*)(tagdat+sz); v = tagdat+sz+4; len = nhgets(t->length); if(sz+4+len > totlen) break; if(nhgets(t->type) == tagtype && skip-- == 0){ *plen = len; return v; } sz += 2+2+len; } return nil; } void dumptags(uchar *tagdat, int ntagdat) { int i,len, sz; uchar *v; Taghdr *t; sz = 0; while(sz+4 <= ntagdat){ t = (Taghdr*)(tagdat+sz); v = tagdat+sz+2+2; len = nhgets(t->length); if(sz+4+len > ntagdat) break; fprint(2, "\t0x%x %d: ", nhgets(t->type), len); switch(nhgets(t->type)){ case TagEnd: fprint(2, "end of tag list\n"); break; case TagSrvName: fprint(2, "service '%.*s'\n", len, (char*)v); break; case TagAcName: fprint(2, "ac '%.*s'\n", len, (char*)v); break; case TagHostUniq: fprint(2, "nonce "); Hex: for(i=0; itype); fprint(2, "%E -> %E type 0x%x\n", eh->src, eh->dst, et); switch(et){ case EtherPppoeDiscovery: case EtherPppoeSession: fprint(2, "\tvers %d type %d code 0x%x sessid 0x%x length %d\n", ph->verstype>>4, ph->verstype&15, ph->code, nhgets(ph->sessid), nhgets(ph->length)); if(et == EtherPppoeDiscovery) dumptags(pkt+Hdr, nhgets(ph->length)); } } int malformed(uchar *pkt, int n, int wantet) { int et; Etherhdr *eh; Pppoehdr *ph; eh = (Etherhdr*)pkt; ph = (Pppoehdr*)(pkt+EtherHdrSz); if(n < Hdr || n < Hdr+nhgets(ph->length)){ werrstr("packet too short %d != %d", n, Hdr+nhgets(ph->length)); return 1; } et = nhgets(eh->type); if(et != wantet){ werrstr("wrong ethernet packet type 0x%x != 0x%x", et, wantet); return 1; } return 0; } void hexdump(uchar *a, int na) { int i; char buf[80]; buf[0] = '\0'; for(i=0; i