696 lines · 12.3 KB · C
RawDownload
/* * User-level PPP over Ethernet (PPPoE) client. * See RFC 2516 */ #include <u.h>#include <libc.h>#include <ip.h> 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; voidusage(void){	fprint(2, "usage: pppoe [-Pd] [-A acname] [-S srvname] [-k keyspec] [-m mtu] [-x pppnet] [ether0]\n");	exits("usage");} intcatchalarm(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;} voidmain(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}; intetherhdr(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,}; intpppoehdr(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 */}; inttag(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 */intpadi(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 */intpadr(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;} voidewrite(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;} intaread(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;} intpktread(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;} intbad(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;} voidclearstate(void){	sessid = -1;	free(acname);	acname = nil;	free(cookie);	cookie = nil;} intwantoffer(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;} intwantsession(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(srvname[0] != 0){		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");	}		/* otherwise, first come, first serve */	sessid = nhgets(ph->sessid);	return 1;} intpppoe(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);			}

Lines 1–500 of 696.