/*
 * ipconfig for IPv6
 *	RS means Router Solicitation
 *	RA means Router Advertisement
 */

#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include "ipconfig.h"
#include "../icmp.h"

#pragma varargck argpos ralog 1

#define RALOG "v6routeradv"

#define NetS(x) (((uchar*)x)[0]<< 8 | ((uchar*)x)[1])
#define NetL(x) (((uchar*)x)[0]<<24 | ((uchar*)x)[1]<<16 | \
		 ((uchar*)x)[2]<< 8 | ((uchar*)x)[3])

enum {
	ICMP6LEN=	4,
};

typedef struct Hdr Hdr;
struct Hdr			/* ICMP v4 & v6 header */
{
	uchar	type;
	uchar	code;
	uchar	cksum[2];	/* Checksum */
	uchar	data[];
};

char *icmpmsg6[Maxtype6+1] =
{
[EchoReply]		"EchoReply",
[UnreachableV6]		"UnreachableV6",
[PacketTooBigV6]	"PacketTooBigV6",
[TimeExceedV6]		"TimeExceedV6",
[Redirect]		"Redirect",
[EchoRequest]		"EchoRequest",
[TimeExceed]		"TimeExceed",
[InParmProblem]		"InParmProblem",
[Timestamp]		"Timestamp",
[TimestampReply]	"TimestampReply",
[InfoRequest]		"InfoRequest",
[InfoReply]		"InfoReply",
[AddrMaskRequest]	"AddrMaskRequest",
[AddrMaskReply]		"AddrMaskReply",
[EchoRequestV6]		"EchoRequestV6",
[EchoReplyV6]		"EchoReplyV6",
[RouterSolicit]		"RouterSolicit",
[RouterAdvert]		"RouterAdvert",
[NbrSolicit]		"NbrSolicit",
[NbrAdvert]		"NbrAdvert",
[RedirectV6]		"RedirectV6",
};

static char *icmp6opts[] =
{
[0]			"unknown option",
[V6nd_srclladdr]	"srcll_addr",
[V6nd_targlladdr]	"targll_addr",
[V6nd_pfxinfo]		"prefix",
[V6nd_redirhdr]		"redirect",
[V6nd_mtu]		"mtu",
[V6nd_home]		"home",
[V6nd_srcaddrs]		"src_addrs",
[V6nd_ip]		"ip",
[V6nd_rdns]		"rdns",
[V6nd_9fs]		"9fs",
[V6nd_9auth]		"9auth",
};

uchar v6allroutersL[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x02
};

uchar v6allnodesL[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0x01
};

uchar v6Unspecified[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};

uchar v6loopback[IPaddrlen] = {
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 1
};

uchar v6glunicast[IPaddrlen] = {
	0x08, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};

uchar v6linklocal[IPaddrlen] = {
	0xfe, 0x80, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 0
};

uchar v6solpfx[IPaddrlen] = {
	0xff, 0x02, 0, 0,
	0, 0, 0, 0,
	0, 0, 0, 1,
	/* last 3 bytes filled with low-order bytes of addr being solicited */
	0xff, 0, 0, 0,
};

uchar v6defmask[IPaddrlen] = {
	0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff,
	0, 0, 0, 0,
	0, 0, 0, 0
};

enum
{
	Vadd,
	Vremove,
	Vunbind,
	Vaddpref6,
	Vra6,
};

static void
ralog(char *fmt, ...)
{
	char msg[512];
	va_list arg;

	va_start(arg, fmt);
	vseprint(msg, msg+sizeof msg, fmt, arg);
	va_end(arg);
	syslog(debug, RALOG, msg);
}

extern void
ea2lla(uchar *lla, uchar *ea)
{
	assert(IPaddrlen == 16);
	memset(lla, 0, IPaddrlen);
	lla[0]  = 0xFE;
	lla[1]  = 0x80;
	lla[8]  = ea[0] | 0x2;
	lla[9]  = ea[1];
	lla[10] = ea[2];
	lla[11] = 0xFF;
	lla[12] = 0xFE;
	lla[13] = ea[3];
	lla[14] = ea[4];
	lla[15] = ea[5];
}

extern void
ipv62smcast(uchar *smcast, uchar *a)
{
	assert(IPaddrlen == 16);
	memset(smcast, 0, IPaddrlen);
	smcast[0]  = 0xFF;
	smcast[1]  = 0x02;
	smcast[11] = 0x1;
	smcast[12] = 0xFF;
	smcast[13] = a[13];
	smcast[14] = a[14];
	smcast[15] = a[15];
}

void
v6paraminit(Conf *cf)
{
	cf->sendra = cf->recvra = 0;
	cf->mflag = 0;
	cf->oflag = 0;
	cf->maxraint = Maxv6initraintvl;
	cf->minraint = Maxv6initraintvl / 4;
	cf->linkmtu = 1500;
	cf->reachtime = V6reachabletime;
	cf->rxmitra = V6retranstimer;
	cf->ttl = MAXTTL;

	cf->routerlt = 0;

	cf->prefixlen = 64;
	cf->onlink = 0;
	cf->autoflag = 0;
	cf->validlt = cf->preflt = ~0L;
}

static char *
optname(unsigned opt)
{
	static char buf[32];

	if (opt >= nelem(icmp6opts) || icmp6opts[opt] == nil) {
		snprint(buf, sizeof buf, "unknown option %d", opt);
		return buf;
	} else
		return icmp6opts[opt];
}

static char*
opt_seprint(uchar *ps, uchar *pe, char *sps, char *spe)
{
	int otype, osz, pktsz;
	uchar *a;
	char *p = sps, *e = spe;

	a = ps;
	for (pktsz = pe - ps; pktsz > 0; pktsz -= osz) {
		otype = a[0];
		osz = a[1] * 8;

		switch (otype) {
		default:
			return seprint(p, e, " option=%s ", optname(otype));
		case V6nd_srclladdr:
		case V6nd_targlladdr:
			if (pktsz < osz || osz != 8)
				return seprint(p, e, " option=%s bad size=%d",
					optname(otype), osz);
			p = seprint(p, e, " option=%s maddr=%E", optname(otype),
				a+2);
			break;
		case V6nd_pfxinfo:
			if (pktsz < osz || osz != 32)
				return seprint(p, e, " option=%s: bad size=%d",
					optname(otype), osz);

			p = seprint(p, e, " option=%s pref=%I preflen=%3.3d"
				" lflag=%1.1d aflag=%1.1d unused1=%1.1d"
				" validlt=%ud preflt=%ud unused2=%1.1d",
				optname(otype), a+16, (int)(*(a+2)),
				(*(a+3) & (1 << 7)) != 0,
				(*(a+3) & (1 << 6)) != 0,
				(*(a+3) & 63) != 0,
				NetL(a+4), NetL(a+8), NetL(a+12)!=0);
			break;
		}
		a += osz;
	}
	return p;
}

static void
pkt2str(uchar *ps, uchar *pe, char *sps, char *spe)
{
	int pktlen;
	char *tn, *p, *e;
	uchar *a;
	Hdr *h;

	h = (Hdr*)ps;
	a = ps + 4;
	p = sps;
	e = spe;

	pktlen = pe - ps;
	if(pktlen < ICMP6LEN) {
		seprint(sps, spe, "short pkt");
		return;
	}

	tn = icmpmsg6[h->type];
	if(tn == nil)
		p = seprint(p, e, "t=%ud c=%d ck=%4.4ux", h->type,
			h->code, (ushort)NetS(h->cksum));
	else
		p = seprint(p, e, "t=%s c=%d ck=%4.4ux", tn,
			h->code, (ushort)NetS(h->cksum));

	switch(h->type){
	case RouterSolicit:
		ps += 8;
		p = seprint(p, e, " unused=%1.1d ", NetL(a)!=0);
		opt_seprint(ps, pe, p, e);
		break;
	case RouterAdvert:
		ps += 16;
		p = seprint(p, e, " hoplim=%3.3d mflag=%1.1d oflag=%1.1d"
			" unused=%1.1d routerlt=%d reachtime=%d rxmtimer=%d",
			a[0],
			(*(a+1) & (1 << 7)) != 0,
			(*(a+1) & (1 << 6)) != 0,
			(*(a+1) & 63) != 0,
			NetS(a+2), NetL(a+4), NetL(a+8));
		opt_seprint(ps, pe, p, e);
		break;
	default:
		seprint(p, e, " unexpected icmp6 pkt type");
		break;
	}
}

static void
catch(void *a, char *msg)
{
	USED(a);
	if(strstr(msg, "alarm"))
		noted(NCONT);
	else
		noted(NDFLT);
}

/*
 * based on libthread's threadsetname, but drags in less library code.
 * actually just sets the arguments displayed.
 */
void
procsetname(char *fmt, ...)
{
	int fd;
	char *cmdname;
	char buf[128];
	va_list arg;

	va_start(arg, fmt);
	cmdname = vsmprint(fmt, arg);
	va_end(arg);
	if (cmdname == nil)
		return;
	snprint(buf, sizeof buf, "#p/%d/args", getpid());
	if((fd = open(buf, OWRITE)) >= 0){
		write(fd, cmdname, strlen(cmdname)+1);
		close(fd);
	}
	free(cmdname);
}

int
dialicmp(uchar *dst, int dport, int *ctlfd)
{
	int fd, cfd, n, m;
	char cmsg[100], name[128], connind[40];
	char hdrs[] = "headers";

	snprint(name, sizeof name, "%s/icmpv6/clone", conf.mpoint);
	cfd = open(name, ORDWR);
	if(cfd < 0)
		sysfatal("dialicmp: can't open %s: %r", name);

	n = snprint(cmsg, sizeof cmsg, "connect %I!%d!r %d", dst, dport, dport);
	m = write(cfd, cmsg, n);
	if (m < n)
		sysfatal("dialicmp: can't write %s to %s: %r", cmsg, name);

	seek(cfd, 0, 0);
	n = read(cfd, connind, sizeof connind);
	if (n < 0)
		connind[0] = 0;
	else if (n < sizeof connind)
		connind[n] = 0;
	else
		connind[sizeof connind - 1] = 0;

	snprint(name, sizeof name, "%s/icmpv6/%s/data", conf.mpoint, connind);
	fd = open(name, ORDWR);
	if(fd < 0)
		sysfatal("dialicmp: can't open %s: %r", name);

	n = sizeof hdrs - 1;
	if(write(cfd, hdrs, n) < n)
		sysfatal("dialicmp: can't write `%s' to %s: %r", hdrs, name);
	*ctlfd = cfd;
	return fd;
}

/* add ipv6 addr to an interface */
int
ip6cfg(int autoconf)
{
	int dupfound = 0, n;
	char *p;
	char buf[256];
	uchar ethaddr[6];
	Biobuf *bp;

	if (autoconf) {			/* create link-local addr */
		if (myetheraddr(ethaddr, conf.dev) < 0)
			sysfatal("myetheraddr w/ %s failed: %r", conf.dev);
		ea2lla(conf.laddr, ethaddr);
	}

	if (dupl_disc)
		n = sprint(buf, "try");
	else
		n = sprint(buf, "add");

	n += snprint(buf+n, sizeof buf-n, " %I", conf.laddr);
	if(!validip(conf.mask))
		ipmove(conf.mask, v6defmask);
	n += snprint(buf+n, sizeof buf-n, " %M", conf.mask);
	if(validip(conf.raddr)){
		n += snprint(buf+n, sizeof buf-n, " %I", conf.raddr);
		if(conf.mtu != 0)
			n += snprint(buf+n, sizeof buf-n, " %d", conf.mtu);
	}

	if(write(conf.cfd, buf, n) < 0){
		warning("write(%s): %r", buf);
		return -1;
	}

	if (!dupl_disc)
		return 0;

	sleep(3000);

	/* read arp table, look for addr duplication */
	snprint(buf, sizeof buf, "%s/arp", conf.mpoint);
	bp = Bopen(buf, OREAD);
	if (bp == 0) {
		warning("couldn't open %s: %r", buf);
		return -1;
	}

	snprint(buf, sizeof buf, "%I", conf.laddr);
	while(p = Brdline(bp, '\n')){
		p[Blinelen(bp)-1] = 0;
		if(cistrstr(p, buf) != 0) {
			warning("found dup entry in arp cache");
			dupfound = 1;
			break;
		}
	}
	Bterm(bp);

	if (dupfound)
		doremove();
	else {
		n = sprint(buf, "add %I %M", conf.laddr, conf.mask);
		if(validip(conf.raddr)){
			n += snprint(buf+n, sizeof buf-n, " %I", conf.raddr);
			if(conf.mtu != 0)
				n += snprint(buf+n, sizeof buf-n, " %d",
					conf.mtu);
		}
		write(conf.cfd, buf, n);
	}
	return 0;
}

static int
recvra6on(char *net, int conn)
{
	Ipifc* ifc;

	ifc = readipifc(net, nil, conn);
	if (ifc == nil)
		return 0;
	else if (ifc->sendra6 > 0)
		return IsRouter;
	else if (ifc->recvra6 > 0)
		return IsHostRecv;
	else
		return IsHostNoRecv;
}

/* send icmpv6 router solicitation to multicast address for all routers */
static void
sendrs(int fd)
{
	Routersol *rs;
	uchar buff[sizeof *rs];

	memset(buff, 0, sizeof buff);
	rs = (Routersol *)buff;
	memmove(rs->dst, v6allroutersL, IPaddrlen);
	memmove(rs->src, v6Unspecified, IPaddrlen);
	rs->type = ICMP6_RS;

	if(write(fd, rs, sizeof buff) < sizeof buff)
		ralog("sendrs: write failed, pkt size %d", sizeof buff);
	else
		ralog("sendrs: sent solicitation to %I from %I on %s",
			rs->dst, rs->src, conf.dev);
}

/*
 * a router receiving a router adv from another
 * router calls this; it is basically supposed to
 * log the information in the ra and raise a flag
 * if any parameter value is different from its configured values.
 *
 * doing nothing for now since I don't know where to log this yet.
 */
static void
recvrarouter(uchar buf[], int pktlen)
{
	USED(buf, pktlen);
	ralog("i am a router and got a router advert");
}

/* host receiving a router advertisement calls this */

static void
ewrite(int fd, char *str)
{
	int n;

	n = strlen(str);
	if (write(fd, str, n) != n)
		ralog("write(%s) failed: %r", str);
}

static void
issuebasera6(Conf *cf)
{
	char *cfg;

	cfg = smprint("ra6 mflag %d oflag %d reachtime %d rxmitra %d "
		"ttl %d routerlt %d",
		cf->mflag, cf->oflag, cf->reachtime, cf->rxmitra,
		cf->ttl, cf->routerlt);
	ewrite(cf->cfd, cfg);
	free(cfg);
}

static void
issuerara6(Conf *cf)
{
	char *cfg;

	cfg = smprint("ra6 sendra %d recvra %d maxraint %d minraint %d "
		"linkmtu %d",
		cf->sendra, cf->recvra, cf->maxraint, cf->minraint,
		cf->linkmtu);
	ewrite(cf->cfd, cfg);
	free(cfg);
}

static void
issueadd6(Conf *cf)
{
	char *cfg;

	cfg = smprint("add6 %I %d %d %d %lud %lud", cf->v6pref, cf->prefixlen,
		cf->onlink, cf->autoflag, cf->validlt, cf->preflt);
	ewrite(cf->cfd, cfg);
	free(cfg);
}

static void
recvrahost(uchar buf[], int pktlen)
{
	int arpfd, m, n;
	char abuf[100];
	uchar optype;
	Lladdropt *llao;
	Mtuopt *mtuo;
	Prefixopt *prfo;
	Routeradv *ra;
	static int first = 1;

	ra = (Routeradv*)buf;
//	memmove(conf.v6gaddr, ra->src, IPaddrlen);
	conf.ttl = ra->cttl;
	conf.mflag = (MFMASK & ra->mor);
	conf.oflag = (OCMASK & ra->mor);
	conf.routerlt =  nhgets(ra->routerlt);
	conf.reachtime = nhgetl(ra->rchbltime);
	conf.rxmitra =   nhgetl(ra->rxmtimer);

//	issueadd6(&conf);		/* for conf.v6gaddr? */
	if (fprint(conf.cfd, "ra6 recvra 1") < 0)
		ralog("write(ra6 recvra 1) failed: %r");
	issuebasera6(&conf);

	m = sizeof *ra;
	while (pktlen - m > 0) {
		optype = buf[m];
		switch (optype) {
		case V6nd_srclladdr:
			llao = (Lladdropt *)&buf[m];
			m += 8 * buf[m+1];
			if (llao->len != 1) {
				ralog("recvrahost: illegal len (%d) for source "
					"link layer address option", llao->len);
				return;
			}
			if (!ISIPV6LINKLOCAL(ra->src)) {
				ralog("recvrahost: non-link-local src addr for "
					"router adv %I", ra->src);
				return;
			}

			snprint(abuf, sizeof abuf, "%s/arp", conf.mpoint);
			arpfd = open(abuf, OWRITE);
			if (arpfd < 0) {
				ralog("recvrahost: couldn't open %s to write: %r",
					abuf);
				return;
			}

			n = snprint(abuf, sizeof abuf, "add ether %I %E",
				ra->src, llao->lladdr);
			if (write(arpfd, abuf, n) < n)
				ralog("recvrahost: couldn't write to %s/arp",
					conf.mpoint);
			close(arpfd);
			break;
		case V6nd_targlladdr:
		case V6nd_redirhdr:
			m += 8 * buf[m+1];
			ralog("ignoring unexpected option type `%s' in Routeradv",
				optname(optype));
			break;
		case V6nd_mtu:
			mtuo = (Mtuopt*)&buf[m];
			m += 8 * mtuo->len;
			conf.linkmtu = nhgetl(mtuo->mtu);
			break;
		case V6nd_pfxinfo:
			prfo = (Prefixopt*)&buf[m];
			m += 8 * prfo->len;
			if (prfo->len != 4) {
				ralog("illegal len (%d) for prefix option",
					prfo->len);
				return;
			}
			memmove(conf.v6pref, prfo->pref, IPaddrlen);
			conf.prefixlen = prfo->plen;
			conf.onlink =   ((prfo->lar & OLMASK) != 0);
			conf.autoflag = ((prfo->lar & AFMASK) != 0);
			conf.validlt = nhgetl(prfo->validlt);
			conf.preflt =  nhgetl(prfo->preflt);
			issueadd6(&conf);
			if (first) {
				first = 0;
				ralog("got initial RA from %I on %s; pfx %I",
					ra->src, conf.dev, prfo->pref);
			}
			break;
		default:
			if (debug)
				ralog("ignoring optype %d in Routeradv from %I",
					optype, ra->src);
			/* fall through */
		case V6nd_srcaddrs:
			/* netsbd sends this, so quietly ignore it for now */
			m += 8 * buf[m+1];
			break;
		}
	}
}

/*
 * daemon to receive router advertisements from routers
 */
void
recvra6(void)
{
	int fd, cfd, n, sendrscnt, sleepfor;
	uchar buf[4096];

	/* TODO: why not v6allroutersL? */
	fd = dialicmp(v6allnodesL, ICMP6_RA, &cfd);
	if (fd < 0)
		sysfatal("can't open icmp_ra connection: %r");

	notify(catch);
	sendrscnt = Maxv6rss;

	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
	case -1:
		sysfatal("can't fork: %r");
	default:
		return;
	case 0:
		break;
	}

	procsetname("recvra6 on %s", conf.dev);
	ralog("recvra6 on %s", conf.dev);
	sleepfor = jitter();
	for (;;) {
		/*
		 * We only get 3 (Maxv6rss) tries, so make sure we
		 * wait long enough to be certain that at least one RA
		 * will be transmitted.
		 */
		if (sleepfor < 7000)
			sleepfor = 7000;
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		alarm(0);
		if (n <= 0) {
			if (sendrscnt > 0) {
				sendrscnt--;
				if (recvra6on(conf.mpoint, myifc) == IsHostRecv)
					sendrs(fd);
				sleepfor = V6rsintvl + nrand(100);
			}
			if (sendrscnt == 0) {
				sendrscnt--;
				sleepfor = 0;
				ralog("recvra6: no router advs after %d sols on %s",
					Maxv6rss, conf.dev);
			}
			continue;
		}

		sleepfor = 0;
		sendrscnt = -1;		/* got at least initial ra; no whining */
		switch (recvra6on(conf.mpoint, myifc)) {
		case IsRouter:
			recvrarouter(buf, n);
			break;
		case IsHostRecv:
			recvrahost(buf, n);
			break;
		case IsHostNoRecv:
			ralog("recvra6: recvra off, quitting on %s", conf.dev);
			close(fd);
			exits(0);
		default:
			ralog("recvra6: unable to read router status on %s",
				conf.dev);
			break;
		}
	}
}

/*
 * return -1 -- error, reading/writing some file,
 *         0 -- no arp table updates
 *         1 -- successful arp table update
 */
int
recvrs(uchar *buf, int pktlen, uchar *sol)
{
	int n, optsz, arpfd;
	char abuf[256];
	Routersol *rs;
	Lladdropt *llao;

	rs = (Routersol *)buf;
	n = sizeof *rs;
	optsz = pktlen - n;
	pkt2str(buf, buf+pktlen, abuf, abuf+nelem(abuf));

	if (optsz != sizeof *llao)
		return 0;
	if (buf[n] != V6nd_srclladdr || 8*buf[n+1] != sizeof *llao) {
		ralog("rs opt err %s", abuf);
		return -1;
	}

	ralog("rs recv %s", abuf);

	if (memcmp(rs->src, v6Unspecified, IPaddrlen) == 0)
		return 0;

	snprint(abuf, sizeof abuf, "%s/arp", conf.mpoint);
	arpfd = open(abuf, OWRITE);
	if (arpfd < 0) {
		ralog("recvrs: can't open %s/arp to write: %r", conf.mpoint);
		return -1;
	}

	llao = (Lladdropt *)buf[n];
	n = snprint(abuf, sizeof abuf, "add ether %I %E", rs->src, llao->lladdr);
	if (write(arpfd, abuf, n) < n) {
		ralog("recvrs: can't write to %s/arp: %r", conf.mpoint);
		close(arpfd);
		return -1;
	}

	memmove(sol, rs->src, IPaddrlen);
	close(arpfd);
	return 1;
}

void
sendra(int fd, uchar *dst, int rlt)
{
	int pktsz, preflen;
	char abuf[1024], tmp[40];
	uchar buf[1024], macaddr[6], src[IPaddrlen];
	Ipifc *ifc = nil;
	Iplifc *lifc, *nlifc;
	Lladdropt *llao;
	Prefixopt *prfo;
	Routeradv *ra;

	memset(buf, 0, sizeof buf);
	ra = (Routeradv *)buf;

	myetheraddr(macaddr, conf.dev);
	ea2lla(src, macaddr);
	memmove(ra->src, src, IPaddrlen);
	memmove(ra->dst, dst, IPaddrlen);
	ra->type = ICMP6_RA;
	ra->cttl = conf.ttl;

	if (conf.mflag > 0)
		ra->mor |= MFMASK;
	if (conf.oflag > 0)
		ra->mor |= OCMASK;
	if (rlt > 0)
		hnputs(ra->routerlt, conf.routerlt);
	else
		hnputs(ra->routerlt, 0);
	hnputl(ra->rchbltime, conf.reachtime);
	hnputl(ra->rxmtimer, conf.rxmitra);

	pktsz = sizeof *ra;

	/* include all global unicast prefixes on interface in prefix options */
	ifc = readipifc(conf.mpoint, ifc, myifc);
	for (lifc = (ifc? ifc->lifc: nil); lifc; lifc = nlifc) {
		nlifc = lifc->next;
		prfo = (Prefixopt *)(buf + pktsz);
		/* global unicast address? */
		if (!ISIPV6LINKLOCAL(lifc->ip) && !ISIPV6MCAST(lifc->ip) &&
		    memcmp(lifc->ip, IPnoaddr, IPaddrlen) != 0 &&
		    memcmp(lifc->ip, v6loopback, IPaddrlen) != 0 &&
		    !isv4(lifc->ip)) {
			memmove(prfo->pref, lifc->net, IPaddrlen);

			/* hack to find prefix length */
			snprint(tmp, sizeof tmp, "%M", lifc->mask);
			preflen = atoi(&tmp[1]);
			prfo->plen = preflen & 0xff;
			if (prfo->plen == 0)
				continue;

			prfo->type = V6nd_pfxinfo;
			prfo->len = 4;
			prfo->lar = AFMASK;
			hnputl(prfo->validlt, lifc->validlt);
			hnputl(prfo->preflt, lifc->preflt);
			pktsz += sizeof *prfo;
		}
	}
	/*
	 * include link layer address (mac address for now) in
	 * link layer address option
	 */
	llao = (Lladdropt *)(buf + pktsz);
	llao->type = V6nd_srclladdr;
	llao->len = 1;
	memmove(llao->lladdr, macaddr, sizeof macaddr);
	pktsz += sizeof *llao;

	pkt2str(buf+40, buf+pktsz, abuf, abuf+1024);
	if(write(fd, buf, pktsz) < pktsz)
		ralog("sendra fail %s: %r", abuf);
	else if (debug)
		ralog("sendra succ %s", abuf);
}

/*
 * daemon to send router advertisements to hosts
 */
void
sendra6(void)
{
	int fd, cfd, n, dstknown = 0, sendracnt, sleepfor, nquitmsgs;
	long lastra, now;
	uchar buf[4096], dst[IPaddrlen];
	Ipifc *ifc = nil;

	fd = dialicmp(v6allnodesL, ICMP6_RS, &cfd);
	if (fd < 0)
		sysfatal("can't open icmp_rs connection: %r");

	notify(catch);
	sendracnt = Maxv6initras;
	nquitmsgs = Maxv6finalras;

	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
	case -1:
		sysfatal("can't fork: %r");
	default:
		return;
	case 0:
		break;
	}

	procsetname("sendra6 on %s", conf.dev);
	ralog("sendra6 on %s", conf.dev);
	sleepfor = jitter();
	for (;;) {
		lastra = time(0);
		if (sleepfor < 0)
			sleepfor = 0;
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		alarm(0);

		ifc = readipifc(conf.mpoint, ifc, myifc);
		if (ifc == nil) {
			ralog("sendra6: can't read router params on %s",
				conf.mpoint);
			continue;
		}

		if (ifc->sendra6 <= 0)
			if (nquitmsgs > 0) {
				sendra(fd, v6allnodesL, 0);
				nquitmsgs--;
				sleepfor = Minv6interradelay + jitter();
				continue;
			} else {
				ralog("sendra6: sendra off, quitting on %s",
					conf.dev);
				exits(0);
			}

		nquitmsgs = Maxv6finalras;

		if (n <= 0) {			/* no RS */
			if (sendracnt > 0)
				sendracnt--;
		} else {			/* respond to RS */
			dstknown = recvrs(buf, n, dst);
			now = time(0);

			if (now - lastra < Minv6interradelay) {
				/* too close, skip */
				sleepfor = lastra + Minv6interradelay +
					jitter() - now;
				continue;
			}
			sleep(jitter());
		}
		sleepfor = randint(ifc->rp.minraint, ifc->rp.maxraint);
		if (dstknown > 0)
			sendra(fd, dst, 1);
		else
			sendra(fd, v6allnodesL, 1);
	}
}

void
startra6(void)
{
	static char routeon[] = "iprouting 1";

	if (conf.recvra > 0)
		recvra6();

	if (conf.sendra > 0) {
		if (write(conf.cfd, routeon, sizeof routeon - 1) < 0) {
			warning("write (iprouting 1) failed: %r");
			return;
		}
		sendra6();
		if (conf.recvra <= 0)
			recvra6();
	}
}

void
doipv6(int what)
{
	nip = nipifcs(conf.mpoint);
	if(!noconfig){
		lookforip(conf.mpoint);
		controldevice();
		binddevice();
	}

	switch (what) {
	default:
		sysfatal("unknown IPv6 verb");
	case Vaddpref6:
		issueadd6(&conf);
		break;
	case Vra6:
		issuebasera6(&conf);
		issuerara6(&conf);
		dolog = 1;
		startra6();
		break;
	}
}
