shithub: purgatorio

ref: a411870ee4640241e3c494367d922847da84f972
dir: /os/ip/ppp.c/

View raw version
#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"
#include	<libcrypt.h>
#include	<kernel.h>
#include	"ip.h"
#include	"ppp.h"

int	nocompress;
Ipaddr	pppdns[2];

/*
 * Calculate FCS - rfc 1331
 */
ushort fcstab[256] =
{
      0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
      0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
      0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
      0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
      0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
      0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
      0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
      0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
      0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
      0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
      0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
      0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
      0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
      0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
      0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
      0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
      0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
      0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
      0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
      0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
      0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
      0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
      0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
      0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
      0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
      0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
      0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
      0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
      0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
      0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
      0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
      0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
};

static char *snames[] =
{
	"Sclosed",
	"Sclosing",
	"Sreqsent",
	"Sackrcvd",
	"Sacksent",
	"Sopened",
};

static void	init(PPP*);
static void	setphase(PPP*, int);
static void	pinit(PPP*, Pstate*);
static void	ppptimer(void*);
static void	ptimer(PPP*, Pstate*);
static int	getframe(PPP*, Block**);
static Block*	putframe(PPP*, int, Block*);
static uchar*	escapebyte(PPP*, ulong, uchar*, ushort*);
static void	config(PPP*, Pstate*, int);
static int	getopts(PPP*, Pstate*, Block*);
static void	rejopts(PPP*, Pstate*, Block*, int);
static void	newstate(PPP*, Pstate*, int);
static void	rcv(PPP*, Pstate*, Block*);
static void	getchap(PPP*, Block*);
static void	getpap(PPP*, Block*);
static void	sendpap(PPP*);
static void	getlqm(PPP*, Block*);
static void	putlqm(PPP*);
static void	hangup(PPP*);
static void	remove(PPP*);

static	int		validv4(Ipaddr);
static	void		invalidate(Ipaddr);
static	void		ipconnect(PPP *);
static	void		setdefroute(PPP *, Ipaddr);
static	void		printopts(PPP *, Pstate*, Block*, int);
static	void		sendtermreq(PPP*, Pstate*);

static void
errlog(PPP *ppp, char *err)
{
	int n;
	char msg[64];

	n = snprint(msg, sizeof(msg), "%s\n", err);
	qproduce(ppp->ifc->conv->eq, msg, n);
}

static void
init(PPP* ppp)
{
	if(ppp->inbuf == nil){
		ppp->inbuf = allocb(4096);
		ppp->outbuf = allocb(4096);

		ppp->lcp = malloc(sizeof(Pstate));
		ppp->ipcp = malloc(sizeof(Pstate));
		if(ppp->lcp == nil || ppp->ipcp == nil)
			error("ppp init: malloc");

		ppp->lcp->proto = Plcp;
		ppp->lcp->state = Sclosed;
		ppp->ipcp->proto = Pipcp;
		ppp->ipcp->state = Sclosed;

		kproc("ppptimer", ppptimer, ppp, KPDUPPG|KPDUPFDG);
	}

	pinit(ppp, ppp->lcp);
	setphase(ppp, Plink);
}

static void
setphase(PPP *ppp, int phase)
{
	int oldphase;

	oldphase = ppp->phase;

	ppp->phase = phase;
	switch(phase){
	default:
		panic("ppp: unknown phase %d", phase);
	case Pdead:
		/* restart or exit? */
		pinit(ppp, ppp->lcp);
		setphase(ppp, Plink);
		break;
	case Plink:
		/* link down */
		switch(oldphase) {
		case Pnet:
			newstate(ppp, ppp->ipcp, Sclosed);
		}
		break;
	case Pauth:
		if(ppp->usepap)
			sendpap(ppp);
		else if(!ppp->usechap)
			setphase(ppp, Pnet);
		break;
	case Pnet:
		pinit(ppp, ppp->ipcp);
		break;
	case Pterm:
		/* what? */
		break;
	}
}

static void
pinit(PPP *ppp, Pstate *p)
{
	p->timeout = 0;

	switch(p->proto){
	case Plcp:
		ppp->magic = TK2MS(MACHP(0)->ticks);
		ppp->xctlmap = 0xffffffff;
		ppp->period = 0;
		p->optmask = 0xffffffff;
		ppp->rctlmap = 0;
		ppp->ipcp->state = Sclosed;
		ppp->ipcp->optmask = 0xffffffff;

		/* quality goo */
		ppp->timeout = 0;
		memset(&ppp->in, 0, sizeof(ppp->in));
		memset(&ppp->out, 0, sizeof(ppp->out));
		memset(&ppp->pin, 0, sizeof(ppp->pin));
		memset(&ppp->pout, 0, sizeof(ppp->pout));
		memset(&ppp->sin, 0, sizeof(ppp->sin));
		break;
	case Pipcp:
		if(ppp->localfrozen == 0)
			invalidate(ppp->local);
		if(ppp->remotefrozen == 0)
			invalidate(ppp->remote);
		p->optmask = 0xffffffff;
		ppp->ctcp = compress_init(ppp->ctcp);
		ppp->usedns = 3;
		invalidate(ppp->dns1);
		invalidate(ppp->dns2);
		break;
	}
	p->confid = p->rcvdconfid = -1;
	config(ppp, p, 1);
	newstate(ppp, p, Sreqsent);
}

/*
 *  change protocol to a new state.
 */
static void
newstate(PPP *ppp, Pstate *p, int state)
{
	netlog(ppp->f, Logppp, "%ux %ux %s->%s ctlmap %lux/%lux flags %ux mtu %d mru %d\n", ppp, p->proto,
		snames[p->state], snames[state], ppp->rctlmap, ppp->xctlmap, p->flags,
		ppp->mtu, ppp->mru);

	if(p->proto == Plcp) {
		if(state == Sopened)
			setphase(ppp, Pauth);
		else if(state == Sclosed)
			setphase(ppp, Pdead);
		else if(p->state == Sopened)
			setphase(ppp, Plink);
	}

	if(p->proto == Pipcp && state == Sopened && validv4(ppp->local) && validv4(ppp->remote)){
		netlog(ppp->f, Logppp, "pppnewstate: local %I remote %I\n", ppp->local, ppp->remote);
		ipmove(pppdns[0], ppp->dns1);
		ipmove(pppdns[1], ppp->dns2);
		ipconnect(ppp);
		/* if this is the only network, set up a default route */
//		if(ppp->ifc->link==nil)		/* how??? */
			setdefroute(ppp, ppp->remote);
		errlog(ppp, Enoerror);
	}

	p->state = state;
}

static void
remove(PPP *ppp)
{
	free(ppp->ipcp);
	ppp->ipcp = 0;
	free(ppp->ctcp);
	ppp->ctcp = 0;
	free(ppp->lcp);
	ppp->lcp = 0;
	if (ppp->inbuf) {
		freeb(ppp->inbuf);
		ppp->inbuf = nil;
	}
	if (ppp->outbuf) {
		freeb(ppp->outbuf);
		ppp->outbuf = nil;
	}
	free(ppp);
}

void
pppclose(PPP *ppp)
{
	hangup(ppp);
	remove(ppp);
}

static void
dumpblock(Block *b)
{
	char x[256];
	int i;

	for(i = 0; i < (sizeof(x)-1)/3 && b->rp+i < b->wp; i++)
		sprint(&x[3*i], "%2.2ux ", b->rp[i]);
	print("%s\n", x);
}

/* returns (protocol, information) */
static int
getframe(PPP *ppp, Block **info)
{
	uchar *p, *from, *to;
	int n, len, proto;
	ulong c;
	ushort fcs;
	Block *buf, *b;

	buf = ppp->inbuf;
	for(;;){
		/* read till we hit a frame byte or run out of room */
		for(p = buf->rp; buf->wp < buf->lim;){
			for(; p < buf->wp; p++)
				if(*p == HDLC_frame)
					goto break2;

			len = buf->lim - buf->wp;
			n = 0;
			if(ppp->dchan != nil)
				n = kchanio(ppp->dchan, buf->wp, len, OREAD);
				netlog(ppp->f, Logppp, "ppp kchanio %d bytes\n", n);
			if(n <= 0){
				buf->wp = buf->rp;
//				if(n < 0)
//					print("ppp kchanio(%s) returned %d: %r",
//						ppp->dchan->path->elem, n);
				*info = nil;
				return 0;
			}
			buf->wp += n;
		}
break2:

		/* copy into block, undoing escapes, and caculating fcs */
		fcs = PPP_initfcs;
		b = allocb(p - buf->rp);
		to = b->wp;
		for(from = buf->rp; from != p;){
			c = *from++;
			if(c == HDLC_esc){
				if(from == p)
					break;
				c = *from++ ^ 0x20;
			} else if((c < 0x20) && (ppp->rctlmap & (1 << c)))
				continue;
			*to++ = c;
			fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
		}

		/* copy down what's left in buffer */
		p++;
		memmove(buf->rp, p, buf->wp - p);
		n = p - buf->rp;
		buf->wp -= n;
		b->wp = to - 2;

		/* return to caller if checksum matches */
		if(fcs == PPP_goodfcs){
			if(b->rp[0] == PPP_addr && b->rp[1] == PPP_ctl)
				b->rp += 2;
			proto = *b->rp++;
			if((proto & 0x1) == 0)
				proto = (proto<<8) | *b->rp++;
			if(b->rp < b->wp){
				ppp->in.bytes += n;
				ppp->in.packets++;
				*info = b;
				return proto;
			}
		} else if(BLEN(b) > 0){
			ppp->ifc->inerr++;
			ppp->in.discards++;
			netlog(ppp->f, Logppp, "len %d/%d cksum %ux (%ux %ux %ux %ux)\n",
				BLEN(b), BLEN(buf), fcs, b->rp[0],
				b->rp[1], b->rp[2], b->rp[3]);
		}

		freeblist(b);
	}
	*info = nil;
	return 0;
}

/* send a PPP frame */
static Block *
putframe(PPP *ppp, int proto, Block *b)
{
	Block *buf;
	uchar *to, *from;
	ushort fcs;
	ulong ctlmap;
	int c;
	Block *bp;

	if(ppp->dchan == nil){
		netlog(ppp->f, Logppp, "putframe: dchan down\n");
		errlog(ppp, Ehungup);
		return b;
	}
	netlog(ppp->f, Logppp, "putframe %ux %d %d (%d bytes)\n", proto, b->rp[0], b->rp[1], BLEN(b));

	ppp->out.packets++;

	if(proto == Plcp)
		ctlmap = 0xffffffff;
	else
		ctlmap = ppp->xctlmap;

	/* make sure we have head room */
	if(b->rp - b->base < 4){
		b = padblock(b, 4);
		b->rp += 4;
	}

	/* add in the protocol and address, we'd better have left room */
	from = b->rp;
	*--from = proto;
	if(!(ppp->lcp->flags&Fpc) || proto > 0x100 || proto == Plcp)
		*--from = proto>>8;
	if(!(ppp->lcp->flags&Fac) || proto == Plcp){
		*--from = PPP_ctl;
		*--from = PPP_addr;
	}

	qlock(&ppp->outlock);
	buf = ppp->outbuf;

	/* escape and checksum the body */
	fcs = PPP_initfcs;
	to = buf->rp;

	*to++ = HDLC_frame;

	for(bp = b; bp; bp = bp->next){
		if(bp != b)
			from = bp->rp;
		for(; from < bp->wp; from++){
			c = *from;
			if(c == HDLC_frame || c == HDLC_esc
			   || (c < 0x20 && ((1<<c) & ctlmap))){
				*to++ = HDLC_esc;
				*to++ = c ^ 0x20;
			} else 
				*to++ = c;
			fcs = (fcs >> 8) ^ fcstab[(fcs ^ c) & 0xff];
		}
	}

	/* add on and escape the checksum */
	fcs = ~fcs;
	c = fcs;
	if(c == HDLC_frame || c == HDLC_esc
	   || (c < 0x20 && ((1<<c) & ctlmap))){
		*to++ = HDLC_esc;
		*to++ = c ^ 0x20;
	} else 
		*to++ = c;
	c = fcs>>8;
	if(c == HDLC_frame || c == HDLC_esc
	   || (c < 0x20 && ((1<<c) & ctlmap))){
		*to++ = HDLC_esc;
		*to++ = c ^ 0x20;
	} else 
		*to++ = c;

	/* add frame marker and send */
	*to++ = HDLC_frame;
	buf->wp = to;
	if(ppp->dchan == nil){
		netlog(ppp->f, Logppp, "putframe: dchan down\n");
		errlog(ppp, Ehungup);
	}else{
		kchanio(ppp->dchan, buf->rp, BLEN(buf), OWRITE);
		ppp->out.bytes += BLEN(buf);
	}

	qunlock(&ppp->outlock);
	return b;
}

#define IPB2LCP(b) ((Lcpmsg*)((b)->wp-4))

static Block*
alloclcp(int code, int id, int len)
{
	Block *b;
	Lcpmsg *m;

	/*
	 *  leave room for header
	 */
	b = allocb(len);

	m = (Lcpmsg*)b->wp;
	m->code = code;
	m->id = id;
	b->wp += 4;

	return b;
}

static void
putao(Block *b, int type, int aproto, int alg)
{
	*b->wp++ = type;
	*b->wp++ = 5;
	hnputs(b->wp, aproto);
	b->wp += 2;
	*b->wp++ = alg;
}

static void
putlo(Block *b, int type, ulong val)
{
	*b->wp++ = type;
	*b->wp++ = 6;
	hnputl(b->wp, val);
	b->wp += 4;
}

static void
putv4o(Block *b, int type, Ipaddr val)
{
	*b->wp++ = type;
	*b->wp++ = 6;
	if(v6tov4(b->wp, val) < 0){
		/*panic("putv4o")*/;
	}
	b->wp += 4;
}

static void
putso(Block *b, int type, ulong val)
{
	*b->wp++ = type;
	*b->wp++ = 4;
	hnputs(b->wp, val);
	b->wp += 2;
}

static void
puto(Block *b, int type)
{
	*b->wp++ = type;
	*b->wp++ = 2;
}

/*
 *  send configuration request
 */
static void
config(PPP *ppp, Pstate *p, int newid)
{
	Block *b;
	Lcpmsg *m;
	int id;

	if(newid){
		id = ++(p->id);
		p->confid = id;
		p->timeout = Timeout;
	} else
		id = p->confid;
	b = alloclcp(Lconfreq, id, 256);
	m = IPB2LCP(b);
	USED(m);

	switch(p->proto){
	case Plcp:
		if(p->optmask & Fmagic)
			putlo(b, Omagic, ppp->magic);
		if(p->optmask & Fmtu)
			putso(b, Omtu, ppp->mru);
		if(p->optmask & Fac)
			puto(b, Oac);
		if(p->optmask & Fpc)
			puto(b, Opc);
		if(p->optmask & Fctlmap)
			putlo(b, Octlmap, 0);	/* we don't want anything escaped */
		break;
	case Pipcp:
		if((p->optmask & Fipaddr) /*&& validv4(ppp->local)*/)
			putv4o(b, Oipaddr, ppp->local);
		if(!nocompress && (p->optmask & Fipcompress)){
			*b->wp++ = Oipcompress;
			*b->wp++ = 6;
			hnputs(b->wp, Pvjctcp);
			b->wp += 2;
			*b->wp++ = MAX_STATES-1;
			*b->wp++ = 1;
		}
		if(ppp->usedns & 1)
			putlo(b, Oipdns, 0);
		if(ppp->usedns & 2)
			putlo(b, Oipdns2, 0);
		break;
	}

	hnputs(m->len, BLEN(b));
	b = putframe(ppp, p->proto, b);
	freeblist(b);
}

/*
 *  parse configuration request, sends an ack or reject packet
 *
 *	returns:	-1 if request was syntacticly incorrect
 *			 0 if packet was accepted
 *			 1 if packet was rejected
 */
static int
getopts(PPP *ppp, Pstate *p, Block *b)
{
	Lcpmsg *m, *repm;	
	Lcpopt *o;
	uchar *cp;
	ulong rejecting, nacking, flags, proto;
	ulong mtu, ctlmap, period;
	ulong x;
	Block *repb;
	Ipaddr ipaddr;

	rejecting = 0;
	nacking = 0;
	flags = 0;

	/* defaults */
	invalidate(ipaddr);
	mtu = ppp->mtu;

	ctlmap = 0xffffffff;
	period = 0;

	m = (Lcpmsg*)b->rp;
	repb = alloclcp(Lconfack, m->id, BLEN(b));
	repm = IPB2LCP(repb);

	/* copy options into ack packet */
	memmove(repm->data, m->data, b->wp - m->data);
	repb->wp += b->wp - m->data;

	/* look for options we don't recognize or like */
	for(cp = m->data; cp < b->wp; cp += o->len){
		o = (Lcpopt*)cp;
		if(cp + o->len > b->wp || o->len == 0){
			freeblist(repb);
			netlog(ppp->f, Logppp, "ppp %s: bad option length %ux\n", ppp->ifc->dev,
				o->type);
			return -1;
		}

		switch(p->proto){
		case Plcp:
			switch(o->type){
			case Oac:
				flags |= Fac;
				continue;
			case Opc:
				flags |= Fpc;
				continue;
			case Omtu:
				mtu = nhgets(o->data);
				if(mtu < ppp->ifc->m->mintu){
					netlog(ppp->f, Logppp, "bogus mtu %d\n", mtu);
					mtu = ppp->ifc->m->mintu;
				}
				continue;
			case Omagic:
				if(ppp->magic == nhgetl(o->data))
					netlog(ppp->f, Logppp, "ppp: possible loop\n");
				continue;
			case Octlmap:
				ctlmap = nhgetl(o->data);
				continue;
			case Oquality:
				proto = nhgets(o->data);
				if(proto != Plqm)
					break;
				x = nhgetl(o->data+2)*10;
				period = (x+Period-1)/Period;
				continue;
			case Oauth:
				proto = nhgets(o->data);
				if(proto == Ppap && ppp->chapname[0] && ppp->secret[0]){
					ppp->usepap = 1;
					netlog(ppp->f, Logppp, "PPP %s: select PAP\n", ppp->ifc->dev);
					continue;
				}
				if(proto != Pchap || o->data[2] != APmd5){
					if(!nacking){
						nacking = 1;
						repb->wp = repm->data;
						repm->code = Lconfnak;
					}
					putao(repb, Oauth, Pchap, APmd5);
				}
				else
					ppp->usechap = 1;
				ppp->usepap = 0;
				continue;
			}
			break;
		case Pipcp:
			switch(o->type){
			case Oipaddr:	
				v4tov6(ipaddr, o->data);
				if(!validv4(ppp->remote))
					continue;
				if(!validv4(ipaddr) && !rejecting){
					/* other side requesting an address */
					if(!nacking){
						nacking = 1;
						repb->wp = repm->data;
						repm->code = Lconfnak;
					}
					putv4o(repb, Oipaddr, ppp->remote);
				}
				continue;
			case Oipcompress:
				proto = nhgets(o->data);
				if(nocompress || proto != Pvjctcp || compress_negotiate(ppp->ctcp, o->data+2) < 0)
					break;
				flags |= Fipcompress;
				continue;
			}
			break;
		}

		/* come here if option is not recognized */
		if(!rejecting){
			rejecting = 1;
			repb->wp = repm->data;
			repm->code = Lconfrej;
		}
		netlog(ppp->f, Logppp, "ppp %s: bad %ux option %d\n", ppp->ifc->dev, p->proto, o->type);
		memmove(repb->wp, o, o->len);
		repb->wp += o->len;
	}

	/* permanent changes only after we know that we liked the packet */
	if(!rejecting && !nacking){
		switch(p->proto){
		case Plcp:
			netlog(ppp->f, Logppp, "Plcp: mtu: %d %d x:%lux/r:%lux %lux\n", mtu, ppp->mtu, ppp->xctlmap, ppp->rctlmap, ctlmap);
			ppp->period = period;
			ppp->xctlmap = ctlmap;
			if(mtu > Maxmtu)
				mtu = Maxmtu;
			if(mtu < Minmtu)
				mtu = Minmtu;
			ppp->mtu = mtu;
			break;
		case Pipcp:
			if(validv4(ipaddr) && ppp->remotefrozen == 0)
 				ipmove(ppp->remote, ipaddr);
			break;
		}
		p->flags = flags;
	}

	hnputs(repm->len, BLEN(repb));
	repb = putframe(ppp, p->proto, repb);
	freeblist(repb);

	return rejecting || nacking;
}

/*
 *  parse configuration rejection, just stop sending anything that they
 *  don't like (except for ipcp address nak).
 */
static void
rejopts(PPP *ppp, Pstate *p, Block *b, int code)
{
	Lcpmsg *m;
	Lcpopt *o;

	/* just give up trying what the other side doesn't like */
	m = (Lcpmsg*)b->rp;
	for(b->rp = m->data; b->rp < b->wp; b->rp += o->len){
		o = (Lcpopt*)b->rp;
		if(b->rp + o->len > b->wp || o->len == 0){
			netlog(ppp->f, Logppp, "ppp %s: bad roption length %ux\n", ppp->ifc->dev,
				o->type);
			return;
		}

		if(code == Lconfrej){
			if(o->type < 8*sizeof(p->optmask))
				p->optmask &= ~(1<<o->type);
			if(o->type == Oipdns)
				ppp->usedns &= ~1;
			else if(o->type == Oipdns2)
				ppp->usedns &= ~2;
			netlog(ppp->f, Logppp, "ppp %s: %ux rejecting %d\n", ppp->ifc->dev, p->proto,
				o->type);
			continue;
		}

		switch(p->proto){
		case Plcp:
			switch(o->type){
			case Octlmap:
				ppp->rctlmap = nhgetl(o->data);
				break;
			default:
				if(o->type < 8*sizeof(p->optmask))
					p->optmask &= ~(1<<o->type);
				break;
			};
		case Pipcp:
			switch(o->type){
			case Oipaddr:
				if(!validv4(ppp->local))
					v4tov6(ppp->local, o->data);
//				if(o->type < 8*sizeof(p->optmask))
//					p->optmask &= ~(1<<o->type);
				break;
			case Oipdns:
				if(!validv4(ppp->dns1))
					v4tov6(ppp->dns1, o->data);
				ppp->usedns &= ~1;
				break;
			case Oipdns2:
				if(!validv4(ppp->dns2))
					v4tov6(ppp->dns2, o->data);
				ppp->usedns &= ~2;
				break;
			default:
				if(o->type < 8*sizeof(p->optmask))
					p->optmask &= ~(1<<o->type);
				break;
			}
			break;
		}
	}
}


/*
 *  put a messages through the lcp or ipcp state machine.  They are
 *  very similar.
 */
static void
rcv(PPP *ppp, Pstate *p, Block *b)
{
	ulong len;
	int err;
	Lcpmsg *m;

	if(BLEN(b) < 4){
		netlog(ppp->f, Logppp, "ppp %s: short lcp message\n", ppp->ifc->dev);
		freeblist(b);
		return;
	}
	m = (Lcpmsg*)b->rp;
	len = nhgets(m->len);
	if(BLEN(b) < len){
		netlog(ppp->f, Logppp, "ppp %s: short lcp message\n", ppp->ifc->dev);
		freeblist(b);
		return;
	}

	netlog(ppp->f, Logppp, "ppp: %ux rcv %d len %d id %d/%d/%d\n",
		p->proto, m->code, len, m->id, p->confid, p->id);

	if(p->proto != Plcp && ppp->lcp->state != Sopened){
		netlog(ppp->f, Logppp, "ppp: non-lcp with lcp not open\n");
		freeb(b);
		return;
	}

	qlock(ppp);
	switch(m->code){
	case Lconfreq:
		/* flush the output queue */
		if(p->state == Sopened && p->proto == Plcp)
			kchanio(ppp->cchan, "f", 1, OWRITE);

		printopts(ppp, p, b, 0);
		err = getopts(ppp, p, b);
		if(err < 0)
			break;

		if(m->id == p->rcvdconfid)
			break;			/* don't change state for duplicates */
		p->rcvdconfid = m->id;

		switch(p->state){
		case Sackrcvd:
			if(err)
				break;
			newstate(ppp, p, Sopened);
			break;
		case Sclosed:
		case Sopened:
			config(ppp, p, 1);
			if(err == 0)
				newstate(ppp, p, Sacksent);
			else
				newstate(ppp, p, Sreqsent);
			break;
			break;
		case Sreqsent:
		case Sacksent:
			if(err == 0)
				newstate(ppp, p, Sacksent);
			else
				newstate(ppp, p, Sreqsent);
			break;
		}
		break;
	case Lconfack:
		if(p->confid != m->id){
			/* ignore if it isn't the message we're sending */
			netlog(ppp->f, Logppp, "ppp: dropping confack\n");
			break;
		}
		p->confid = -1;		/* ignore duplicates */
		p->id++;		/* avoid sending duplicates */

		switch(p->state){
		case Sopened:
		case Sackrcvd:
			config(ppp, p, 1);
			newstate(ppp, p, Sreqsent);
			break;
		case Sreqsent:
			newstate(ppp, p, Sackrcvd);
			break;
		case Sacksent:
			newstate(ppp, p, Sopened);
			break;
		}
		break;
	case Lconfrej:
	case Lconfnak:
		if(p->confid != m->id) {
			/* ignore if it isn't the message we're sending */
			netlog(ppp->f, Logppp, "ppp: dropping confrej or confnak\n");
			break;
		}
		p->confid = -1;		/* ignore duplicates */
		p->id++;		/* avoid sending duplicates */

		switch(p->state){
		case Sopened:
		case Sackrcvd:
			config(ppp, p, 1);
			newstate(ppp, p, Sreqsent);
			break;
		case Sreqsent:
		case Sacksent:
			printopts(ppp, p, b, 0);
			rejopts(ppp, p, b, m->code);
			config(ppp, p, 1);
			break;
		}
		break;
	case Ltermreq:
		m->code = Ltermack;
		b = putframe(ppp, p->proto, b);

		switch(p->state){
		case Sackrcvd:
		case Sacksent:
			newstate(ppp, p, Sreqsent);
			break;
		case Sopened:
			newstate(ppp, p, Sclosing);
			break;
		}
		break;
	case Ltermack:
		if(p->termid != m->id)	/* ignore if it isn't the message we're sending */
			break;

		if(p->proto == Plcp)
			ppp->ipcp->state = Sclosed;
		switch(p->state){
		case Sclosing:
			newstate(ppp, p, Sclosed);
			break;
		case Sackrcvd:
			newstate(ppp, p, Sreqsent);
			break;
		case Sopened:
			config(ppp, p, 0);
			newstate(ppp, p, Sreqsent);
			break;
		}
		break;
	case Lcoderej:
		netlog(ppp->f, Logppp, "ppp %s: code reject %d\n", ppp->ifc->dev, m->data[0]);
		break;
	case Lprotorej:
		netlog(ppp->f, Logppp, "ppp %s: proto reject %lux\n", ppp->ifc->dev, nhgets(m->data));
		break;
	case Lechoreq:
		m->code = Lechoack;
		b = putframe(ppp, p->proto, b);
		break;
	case Lechoack:
	case Ldiscard:
		/* nothing to do */
		break;
	}

	qunlock(ppp);
	freeblist(b);
}

/*
 *  timer for protocol state machine
 */
static void
ptimer(PPP *ppp, Pstate *p)
{
	if(p->state == Sopened || p->state == Sclosed)
		return;

	p->timeout--;
	switch(p->state){
	case Sclosing:
		sendtermreq(ppp, p);
		break;
	case Sreqsent:
	case Sacksent:
		if(p->timeout <= 0){
			if(p->proto && ppp->cchan != nil)
				kchanio(ppp->cchan, "f", 1, OWRITE); /* flush output queue */
			newstate(ppp, p, Sclosed);
		} else {
			config(ppp, p, 0);
		}
		break;
	case Sackrcvd:
		if(p->timeout <= 0){
			if(p->proto && ppp->cchan != nil)
				kchanio(ppp->cchan, "f", 1, OWRITE); /* flush output queue */
			newstate(ppp, p, Sclosed);
		}
		else {
			config(ppp, p, 0);
			newstate(ppp, p, Sreqsent);
		}
		break;
	}
}

/*
 *  timer for ppp
 */
static void
ppptimer(void *arg)
{
	PPP *ppp;

	ppp = arg;
	ppp->timep = up;
	if(waserror()){
		netlog(ppp->f, Logppp, "ppptimer: %I: %s\n", ppp->local, up->env->errstr);
		ppp->timep = 0;
		pexit("hangup", 1);
	}
	for(;;){
		tsleep(&up->sleep, return0, nil, Period);
		if(ppp->pppup){
			qlock(ppp);

			ptimer(ppp, ppp->lcp);
			if(ppp->lcp->state == Sopened)
				ptimer(ppp, ppp->ipcp);

			if(ppp->period && --(ppp->timeout) <= 0){
				ppp->timeout = ppp->period;
				putlqm(ppp);
			}

			qunlock(ppp);
		}
	}
}

static void
setdefroute(PPP *ppp, Ipaddr gate)
{
	int fd, n;
	char path[128], msg[128];

	snprint(path, sizeof path, "#I%d/iproute", ppp->f->dev);
	fd = kopen(path, ORDWR);
	if(fd < 0)
		return;
	n = snprint(msg, sizeof(msg), "add 0 0 %I", gate);
	kwrite(fd, msg, n);
	kclose(fd);
}

static void
ipconnect(PPP *ppp)
{
	int fd, n;
	char path[128], msg[128];

	snprint(path, sizeof path, "#I%d/ipifc/%d/ctl", ppp->f->dev, ppp->ifc->conv->x);
	fd = kopen(path, ORDWR);
	if(fd < 0)
		return;
	n = snprint(msg, sizeof(msg), "connect %I 255.255.255.255 %I", ppp->local, ppp->remote);
	if (kwrite(fd, msg, n) != n)
		print("ppp ipconnect: %s: %r\n", msg);
	kclose(fd);
}

PPP*
pppopen(PPP *ppp, char *dev,
	Ipaddr ipaddr, Ipaddr remip,
	int mtu, int framing,
	char *chapname, char *secret)
{
	int fd, cfd;
	char ctl[Maxpath];

	invalidate(ppp->remote);
	invalidate(ppp->local);
	invalidate(ppp->dns1);
	invalidate(ppp->dns2);
	ppp->mtu = Defmtu;
	ppp->mru = mtu;
	ppp->framing = framing;

	if(remip != nil && validv4(remip)){
		ipmove(ppp->remote, remip);
		ppp->remotefrozen = 1;
	}
	if(ipaddr != nil && validv4(ipaddr)){
		ipmove(ppp->local, ipaddr);
		ppp->localfrozen = 1;
	}

	/* authentication goo */
	ppp->secret[0] = 0;
	if(secret != nil)
		strncpy(ppp->secret, secret, sizeof(ppp->secret));
	ppp->chapname[0] = 0;
	if(chapname != nil)
		strncpy(ppp->chapname, chapname, sizeof(ppp->chapname));

	if(strchr(dev, '!'))
		fd = kdial(dev, nil, nil, nil);
	else
		fd = kopen(dev, ORDWR);
	if(fd < 0){
		netlog(ppp->f, Logppp, "ppp: can't open %s\n", dev);
		return nil;
	}
	ppp->dchan = fdtochan(up->env->fgrp, fd, ORDWR, 0, 1);
	kclose(fd);

	/* set up serial line */
/* XXX this stuff belongs in application, not driver */
	sprint(ctl, "%sctl", dev);
	cfd = kopen(ctl, ORDWR);
	if(cfd >= 0){
		ppp->cchan = fdtochan(up->env->fgrp, cfd, ORDWR, 0, 1);
		kclose(cfd);
		kchanio(ppp->cchan, "m1", 2, OWRITE);	/* cts/rts flow control/fifo's) on */
		kchanio(ppp->cchan, "q64000", 6, OWRITE);/* increas q size to 64k */
		kchanio(ppp->cchan, "n1", 2, OWRITE);	/* nonblocking writes on */
		kchanio(ppp->cchan, "r1", 2, OWRITE);	/* rts on */
		kchanio(ppp->cchan, "d1", 2, OWRITE);	/* dtr on */
	}

	ppp->pppup = 1;
	init(ppp);
	return ppp;
}

static void
hangup(PPP *ppp)
{
	qlock(ppp);
	if(waserror()){
		qunlock(ppp);
		nexterror();
	}
	netlog(ppp->f, Logppp, "PPP Hangup\n");
	errlog(ppp, Ehungup);
	if(ppp->pppup && ppp->cchan != nil){
		kchanio(ppp->cchan, "f", 1, OWRITE);	/* flush */
		kchanio(ppp->cchan, "h", 1, OWRITE);	/* hangup */
	}
	cclose(ppp->dchan);
	cclose(ppp->cchan);
	ppp->dchan = nil;
	ppp->cchan = nil;
	ppp->pppup = 0;
	qunlock(ppp);
	poperror();
}

/* return next input IP packet */
Block*
pppread(PPP *ppp)
{
	Block *b;
	int proto;
	Lcpmsg *m;

	for(;;){
		proto = getframe(ppp, &b);
		if(b == nil)
			return nil;
		netlog(ppp->f, Logppp, "ppp: read proto %d len %d\n", proto, blocklen(b));
		switch(proto){
		case Plcp:
			rcv(ppp, ppp->lcp, b);
			break;
		case Pipcp:
			rcv(ppp, ppp->ipcp, b);
			break;
		case Pip:
			if(ppp->ipcp->state == Sopened)
				return b;
			freeblist(b);
			break;
		case Plqm:
			getlqm(ppp, b);
			break;
		case Pchap:
			getchap(ppp, b);
			break;
		case Ppap:
			getpap(ppp, b);
			break;
		case Pvjctcp:
		case Pvjutcp:
			if(ppp->ipcp->state == Sopened){
				b = tcpuncompress(ppp->ctcp, b, proto, ppp->f);
				if(b != nil)
					return b;
			}
			freeblist(b);
			break;
		default:
			netlog(ppp->f, Logppp, "unknown proto %ux\n", proto);
			if(ppp->lcp->state == Sopened){
				/* reject the protocol */
				b->rp -= 6;
				m = (Lcpmsg*)b->rp;
				m->code = Lprotorej;
				m->id = ++ppp->lcp->id;
				hnputs(m->data, proto);
				hnputs(m->len, BLEN(b));
				b = putframe(ppp, Plcp, b);
			}
			freeblist(b);
			break;
		}
	}
	return nil;		/* compiler confused */
}

/* transmit an IP packet */
int
pppwrite(PPP *ppp, Block *b)
{
	ushort proto;
	int r;

	qlock(ppp);

	/* can't send ip packets till we're established */
	if(ppp->ipcp->state != Sopened)
		goto ret;

	/* link hung up */
	if(ppp->dchan == nil)
		goto ret;

	b = concatblock(b);		/* or else compression will barf */

	proto = Pip;
	if(ppp->ipcp->flags & Fipcompress)
		proto = compress(ppp->ctcp, b, ppp->f);
	b = putframe(ppp, proto, b);


ret:
	qunlock(ppp);

	r = blocklen(b);
	netlog(ppp->f, Logppp, "ppp wrt len %d\n", r);

	freeblist(b);
	return r;
}

/*
 *  link quality management
 */
static void
getlqm(PPP *ppp, Block *b)
{
	Qualpkt *p;

	p = (Qualpkt*)b->rp;
	if(BLEN(b) == sizeof(Qualpkt)){
		ppp->in.reports++;
		ppp->pout.reports = nhgetl(p->peeroutreports);
		ppp->pout.packets = nhgetl(p->peeroutpackets);
		ppp->pout.bytes = nhgetl(p->peeroutbytes);
		ppp->pin.reports = nhgetl(p->peerinreports);
		ppp->pin.packets = nhgetl(p->peerinpackets);
		ppp->pin.discards = nhgetl(p->peerindiscards);
		ppp->pin.errors = nhgetl(p->peerinerrors);
		ppp->pin.bytes = nhgetl(p->peerinbytes);

		/* save our numbers at time of reception */
		memmove(&ppp->sin, &ppp->in, sizeof(Qualstats));

	}
	freeblist(b);
	if(ppp->period == 0)
		putlqm(ppp);

}
static void
putlqm(PPP *ppp)
{
	Qualpkt *p;
	Block *b;

	b = allocb(sizeof(Qualpkt));
	b->wp += sizeof(Qualpkt);
	p = (Qualpkt*)b->rp;
	hnputl(p->magic, 0);

	/* heresay (what he last told us) */
	hnputl(p->lastoutreports, ppp->pout.reports);
	hnputl(p->lastoutpackets, ppp->pout.packets);
	hnputl(p->lastoutbytes, ppp->pout.bytes);

	/* our numbers at time of last reception */
	hnputl(p->peerinreports, ppp->sin.reports);
	hnputl(p->peerinpackets, ppp->sin.packets);
	hnputl(p->peerindiscards, ppp->sin.discards);
	hnputl(p->peerinerrors, ppp->sin.errors);
	hnputl(p->peerinbytes, ppp->sin.bytes);

	/* our numbers now */
	hnputl(p->peeroutreports, ppp->out.reports+1);
	hnputl(p->peeroutpackets, ppp->out.packets+1);
	hnputl(p->peeroutbytes, ppp->out.bytes+53/*hack*/);

	b = putframe(ppp, Plqm, b);
	freeblist(b);
	ppp->out.reports++;
}

/*
 *  challenge response dialog
 */
static void
getchap(PPP *ppp, Block *b)
{
	Lcpmsg *m;
	int len, vlen, n;
	char md5buf[512];

	m = (Lcpmsg*)b->rp;
	len = nhgets(m->len);
	if(BLEN(b) < len){
		netlog(ppp->f, Logppp, "ppp %s: short chap message\n", ppp->ifc->dev);
		freeblist(b);
		return;
	}

	switch(m->code){
	case Cchallenge:
		vlen = m->data[0];
		if(vlen > len - 5){
			netlog(ppp->f, Logppp, "PPP %s: bad challenge len\n", ppp->ifc->dev);
			freeblist(b);
			break;
		}

		netlog(ppp->f, Logppp, "PPP %s: CHAP Challenge\n", ppp->ifc->dev);
netlog(ppp->f, Logppp, "(secret %s chapname %s id %d)\n", ppp->secret, ppp->chapname, m->id);
		/* create string to hash */
		md5buf[0] = m->id;
		strcpy(md5buf+1, ppp->secret);
		n = strlen(ppp->secret) + 1;
		memmove(md5buf+n, m->data+1, vlen);
		n += vlen;
		freeblist(b);

		/* send reply */
		len = 4 + 1 + 16 + strlen(ppp->chapname);
		b = alloclcp(2, md5buf[0], len);
		m = IPB2LCP(b);
		m->data[0] = 16;
		md5((uchar*)md5buf, n, m->data+1, 0);
		memmove((char*)m->data+17, ppp->chapname, strlen(ppp->chapname));
		hnputs(m->len, len);
		b->wp += len-4;
		b = putframe(ppp, Pchap, b);
		break;
	case Cresponse:
		netlog(ppp->f, Logppp, "PPP %s: chap response?\n", ppp->ifc->dev);
		break;
	case Csuccess:
		netlog(ppp->f, Logppp, "PPP %s: chap succeeded\n", ppp->ifc->dev);
		setphase(ppp, Pnet);
		break;
	case Cfailure:
		netlog(ppp->f, Logppp, "PPP %s: chap failed: %.*s\n", ppp->ifc->dev, len-4, m->data);
		errlog(ppp, Eperm);
		break;
	default:
		netlog(ppp->f, Logppp, "PPP %s: chap code %d?\n", ppp->ifc->dev, m->code);
		break;
	}
	freeblist(b);
}

/*
 *  password authentication protocol dialog
 *	-- obsolete but all we know how to use with NT just now
 */
static void
sendpap(PPP *ppp)
{
	Lcpmsg *m;
	int clen, slen, len;
	Block *b;
	uchar *p;

	clen = strlen(ppp->chapname);
	slen = strlen(ppp->secret);
	len = 4 + 1 + clen + 1 + slen;
	ppp->papid = ++ppp->lcp->id;
	b = alloclcp(Cpapreq, ppp->papid, len);
	m = IPB2LCP(b);
	p = m->data;
	p[0] = clen;
	memmove(p+1, ppp->chapname, clen);
	p += clen + 1;
	p[0] = slen;
	memmove(p+1, ppp->secret, slen);
	hnputs(m->len, len);
	b->wp += len-4;
	b = putframe(ppp, Ppap, b);
	netlog(ppp->f, Logppp, "PPP %s: sent pap auth req (%d)\n", ppp->ifc->dev, len);
	freeblist(b);
}

static void
getpap(PPP *ppp, Block *b)
{
	Lcpmsg *m;
	int len;

	m = (Lcpmsg*)b->rp;
	len = nhgets(m->len);
	if(BLEN(b) < len){
		netlog(ppp->f, Logppp, "ppp %s: short pap message\n", ppp->ifc->dev);
		freeblist(b);
		return;
	}

	switch(m->code){
	case Cpapreq:
		netlog(ppp->f, Logppp, "PPP %s: pap request?\n", ppp->ifc->dev);
		break;
	case Cpapack:
		netlog(ppp->f, Logppp, "PPP %s: PAP succeeded\n", ppp->ifc->dev);
		setphase(ppp, Pnet);
		break;
	case Cpapnak:
		if(m->data[0])
			netlog(ppp->f, Logppp, "PPP %s: PAP failed: %.*s\n", ppp->ifc->dev, len-5, m->data+1);
		else
			netlog(ppp->f, Logppp, "PPP %s: PAP failed\n", ppp->ifc->dev);
		errlog(ppp, Eperm);
		break;
	default:
		netlog(ppp->f, Logppp, "PPP %s: pap code %d?\n", ppp->ifc->dev, m->code);
		break;
	}
	freeblist(b);
}

static void
printopts(PPP *ppp, Pstate *p, Block *b, int send)
{
	Lcpmsg *m;	
	Lcpopt *o;
	int proto, x, period;
	uchar *cp;
	char *code, *dir;

	m = (Lcpmsg*)b->rp;
	switch(m->code) {
	default: code = "<unknown>"; break;
	case Lconfreq: code = "confrequest"; break;
	case Lconfack: code = "confack"; break;
	case Lconfnak: code = "confnak"; break;
	case Lconfrej: code = "confreject"; break;
	}

	if(send)
		dir = "send";
	else
		dir = "recv";

	netlog(ppp->f, Logppp, "ppp: %s %s: id=%d\n", dir, code, m->id);

	for(cp = m->data; cp < b->wp; cp += o->len){
		o = (Lcpopt*)cp;
		if(cp + o->len > b->wp || o->len == 0){
			netlog(ppp->f, Logppp, "\tbad option length %ux\n", o->type);
			return;
		}

		switch(p->proto){
		case Plcp:
			switch(o->type){
			default:
				netlog(ppp->f, Logppp, "\tunknown %d len=%d\n", o->type, o->len);
				break;
			case Omtu:
				netlog(ppp->f, Logppp, "\tmtu = %d\n", nhgets(o->data));
				break;
			case Octlmap:
				netlog(ppp->f, Logppp, "\tctlmap = %ux\n", nhgetl(o->data));
				break;
			case Oauth:
				netlog(ppp->f, Logppp, "\tauth = ", nhgetl(o->data));
				proto = nhgets(o->data);
				switch(proto) {
				default:
					netlog(ppp->f, Logppp, "unknown auth proto %d\n", proto);
					break;
				case Ppap:
					netlog(ppp->f, Logppp, "password\n");
					break;
				case Pchap:
					netlog(ppp->f, Logppp, "chap %ux\n", o->data[2]);
					break;
				}
				break;
			case Oquality:
				proto = nhgets(o->data);
				switch(proto) {
				default:
					netlog(ppp->f, Logppp, "\tunknown quality proto %d\n", proto);
					break;
				case Plqm:
					x = nhgetl(o->data+2)*10;
					period = (x+Period-1)/Period;
					netlog(ppp->f, Logppp, "\tlqm period = %d\n", period);
					break;
				}
			case Omagic:
				netlog(ppp->f, Logppp, "\tmagic = %ux\n", nhgetl(o->data));
				break;
			case Opc:
				netlog(ppp->f, Logppp, "\tprotocol compress\n");
				break;
			case Oac:
				netlog(ppp->f, Logppp, "\taddr compress\n");
				break;
			}
			break;
		case Pccp:
			switch(o->type){
			default:
				netlog(ppp->f, Logppp, "\tunknown %d len=%d\n", o->type, o->len);
				break;
			case Ocoui:	
				netlog(ppp->f, Logppp, "\tOUI\n");
				break;
			case Ocstac:
				netlog(ppp->f, Logppp, "\tstac LZS\n");
				break;
			case Ocmppc:	
				netlog(ppp->f, Logppp, "\tMicrosoft PPC len=%d %ux\n", o->len, nhgetl(o->data));
				break;
			}
			break;
		case Pecp:
			switch(o->type){
			default:
				netlog(ppp->f, Logppp, "\tunknown %d len=%d\n", o->type, o->len);
				break;
			case Oeoui:	
				netlog(ppp->f, Logppp, "\tOUI\n");
				break;
			case Oedese:
				netlog(ppp->f, Logppp, "\tDES\n");
				break;
			}
			break;
		case Pipcp:
			switch(o->type){
			default:
				netlog(ppp->f, Logppp, "\tunknown %d len=%d\n", o->type, o->len);
				break;
			case Oipaddrs:	
				netlog(ppp->f, Logppp, "\tip addrs - deprecated\n");
				break;
			case Oipcompress:
				netlog(ppp->f, Logppp, "\tip compress\n");
				break;
			case Oipaddr:	
				netlog(ppp->f, Logppp, "\tip addr %V\n", o->data);
				break;
			case Oipdns:
				netlog(ppp->f, Logppp, "\tdns addr %V\n", o->data);
				break;
			case Oipwins:	
				netlog(ppp->f, Logppp, "\twins addr %V\n", o->data);
				break;
			case Oipdns2:
				netlog(ppp->f, Logppp, "\tdns2 addr %V\n", o->data);
				break;
			case Oipwins2:	
				netlog(ppp->f, Logppp, "\twins2 addr %V\n", o->data);
				break;
			}
			break;
		}
	}
}

static void
sendtermreq(PPP *ppp, Pstate *p)
{
	Block *b;
	Lcpmsg *m;

	p->termid = ++(p->id);
	b = alloclcp(Ltermreq, p->termid, 4);
	m = IPB2LCP(b);
	hnputs(m->len, 4);
	putframe(ppp, p->proto, b);
	freeb(b);
	newstate(ppp, p, Sclosing);
}

static void
sendechoreq(PPP *ppp, Pstate *p)
{
	Block *b;
	Lcpmsg *m;

	p->termid = ++(p->id);
	b = alloclcp(Lechoreq, p->id, 4);
	m = IPB2LCP(b);
	hnputs(m->len, 4);
	putframe(ppp, p->proto, b);
	freeb(b);
}

/*
 *  return non-zero if this is a valid v4 address
 */
static int
validv4(Ipaddr addr)
{
	return memcmp(addr, v4prefix, IPv4off) == 0;
}

static void
invalidate(Ipaddr addr)
{
	ipmove(addr, IPnoaddr);
}