shithub: riscv

ref: 2aec1f8a09ae0bc9fb269d84ef53defebc47eede
dir: /sys/src/cmd/ip/ipconfig/ipv6.c/

View raw version
/*
 * ipconfig for IPv6
 *	RS means Router Solicitation
 *	RA means Router Advertisement
 */

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

#include <libsec.h>	/* for sha1 */

enum {
	IsRouter 	= 1,
	IsHostRecv	= 2,
	IsHostNoRecv	= 3,

	ICMP6_RS	= 133,
	ICMP6_RA	= 134,

	MFMASK = 1 << 7,
	OCMASK = 1 << 6,
	OLMASK = 1 << 7,
	AFMASK = 1 << 6,
	RFMASK = 1 << 5,

	MAXTTL		= 255,
	DEFMTU		= 1500,
};

typedef struct Routeradv Routeradv;
typedef struct Routersol Routersol;
typedef struct Lladdropt Lladdropt;
typedef struct Prefixopt Prefixopt;
typedef struct Mtuopt Mtuopt;
typedef struct Ipaddrsopt Ipaddrsopt;

struct Routersol {
	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
	uchar	ploadlen[2];	/* payload length: packet length - 40 */
	uchar	proto;		/* next header	type */
	uchar	ttl;		/* hop limit */
	uchar	src[16];
	uchar	dst[16];
	uchar	type;
	uchar	code;
	uchar	cksum[2];
	uchar	res[4];
};

struct Routeradv {
	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
	uchar	ploadlen[2];	/* payload length: packet length - 40 */
	uchar	proto;		/* next header	type */
	uchar	ttl;		/* hop limit */
	uchar	src[16];
	uchar	dst[16];
	uchar	type;
	uchar	code;
	uchar	cksum[2];
	uchar	cttl;
	uchar	mor;
	uchar	routerlt[2];
	uchar	rchbltime[4];
	uchar	rxmtimer[4];
};

struct Lladdropt {
	uchar	type;
	uchar	len;
	uchar	lladdr[6];
};

struct Prefixopt {
	uchar	type;
	uchar	len;
	uchar	plen;
	uchar	lar;
	uchar	validlt[4];
	uchar	preflt[4];
	uchar	reserv[4];
	uchar	pref[16];
};

struct Mtuopt {
	uchar	type;
	uchar	len;
	uchar	reserv[2];
	uchar	mtu[4];
};

struct Ipaddrsopt {
	uchar	type;
	uchar	len;
	uchar	reserv[2];
	uchar	lifetime[4];
	uchar	addrs[];
};

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
};

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

	cf->routerlt = 0;

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

void
parse6pref(int argc, char **argv)
{
	switch(argc){
	case 6:
		conf.preflt = strtoul(argv[5], 0, 10);
		/* fall through */
	case 5:
		conf.validlt = strtoul(argv[4], 0, 10);
		/* fall through */
	case 4:
		conf.autoflag = (atoi(argv[3]) != 0);
		/* fall through */
	case 3:
		conf.onlink = (atoi(argv[2]) != 0);
		/* fall through */
	case 2:
		conf.prefixlen = atoi(argv[1]);
		/* fall through */
	case 1:
		if (parseip(conf.v6pref, argv[0]) == -1)
			sysfatal("bad address %s", argv[0]);
		break;
	}
	DEBUG("parse6pref: pref %I len %d", conf.v6pref, conf.prefixlen);
}

/* parse router advertisement (keyword, value) pairs */
void
parse6ra(int argc, char **argv)
{
	int i, argsleft;
	char *kw, *val;

	if (argc % 2 != 0)
		usage();

	i = 0;
	for (argsleft = argc; argsleft > 1; argsleft -= 2) {
		kw =  argv[i];
		val = argv[i+1];
		if (strcmp(kw, "recvra") == 0)
			conf.recvra = (atoi(val) != 0);
		else if (strcmp(kw, "sendra") == 0)
			conf.sendra = (atoi(val) != 0);
		else if (strcmp(kw, "mflag") == 0)
			conf.mflag = (atoi(val) != 0);
		else if (strcmp(kw, "oflag") == 0)
			conf.oflag = (atoi(val) != 0);
		else if (strcmp(kw, "maxraint") == 0)
			conf.maxraint = atoi(val);
		else if (strcmp(kw, "minraint") == 0)
			conf.minraint = atoi(val);
		else if (strcmp(kw, "linkmtu") == 0)
			conf.linkmtu = atoi(val);
		else if (strcmp(kw, "reachtime") == 0)
			conf.reachtime = atoi(val);
		else if (strcmp(kw, "rxmitra") == 0)
			conf.rxmitra = atoi(val);
		else if (strcmp(kw, "ttl") == 0)
			conf.ttl = atoi(val);
		else if (strcmp(kw, "routerlt") == 0)
			conf.routerlt = atoi(val);
		else {
			warning("bad ra6 keyword %s", kw);
			usage();
		}
		i += 2;
	}

	/* consistency check */
	if (conf.maxraint < conf.minraint)
		sysfatal("maxraint %d < minraint %d",
			conf.maxraint, conf.minraint);
}

void
ea2lla(uchar *lla, uchar *ea)
{
	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];
}

int
findllip(uchar *ip, Ipifc *ifc)
{
	Iplifc *lifc;

	for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
		if(ISIPV6LINKLOCAL(lifc->ip)){
			ipmove(ip, lifc->ip);
			return 1;
		}
	}
	ipmove(ip, v6Unspecified);
	return 0;
}

static int
dialicmpv6(uchar *ip, int port)
{
	char addr[128], local[128];
	int fd, cfd;

	snprint(addr, sizeof(addr), "%s/icmpv6!%I!%d!r", conf.mpoint, ip, port);
	snprint(local, sizeof(local), "%I!%d", conf.lladdr, port);
	if((fd = dial(addr, local, nil, &cfd)) < 0)
		sysfatal("dialicmp6: %r");
	fprint(cfd, "headers");
	fprint(cfd, "ignoreadvice");
	if(ISIPV6MCAST(ip))
		fprint(cfd, "addmulti %I", conf.lladdr);
	close(cfd);
	return fd;
}

static int
arpenter(uchar *ip, uchar *mac)
{
	char buf[256];
	int fd, n;

	if(!validip(ip))
		return 0;
	snprint(buf, sizeof buf, "%s/arp", conf.mpoint);
	if((fd = open(buf, OWRITE)) < 0){
		warning("couldn't open %s: %r", buf);
		return -1;
	}
	n = snprint(buf, sizeof buf, "add %s %I %E %I\n", conf.type, ip, mac, conf.lladdr);
	if(write(fd, buf, n) != n) {
		warning("arpenter: %s: %r", buf);
		close(fd);
		return 0;
	}
	close(fd);
	return 1;
}

static int
arpcheck(uchar *ip)
{
	char buf[256], *f[5], *p;
	uchar addr[IPaddrlen];
	Biobuf *bp;
	int rv;

	snprint(buf, sizeof buf, "%s/arp", conf.mpoint);
	bp = Bopen(buf, OREAD);
	if(bp == nil){
		warning("couldn't open %s: %r", buf);
		return -1;
	}
	rv = 0;
	while((p = Brdline(bp, '\n')) != nil){
		p[Blinelen(bp)-1] = 0;
		if(tokenize(p, f, nelem(f)) < 3)
			continue;
		if(parseip(addr, f[2]) != -1)
			continue;
		if(ipcmp(addr, ip) == 0){
			rv = 1;
			break;
		}
	}
	Bterm(bp);
	return rv;
}

/* add ipv6 addr to an interface */
int
ip6cfg(void)
{
	int tentative, n;
	char buf[256];

	if(!validip(conf.laddr) || isv4(conf.laddr))
		return -1;

	tentative = dupl_disc;

Again:
	if(tentative)
		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(!tentative){
		if(validip(conf.gaddr) && !isv4(conf.gaddr))
			adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.mask);
		return 0;
	}

	sleep(1000);

	if(arpcheck(conf.laddr) <= 0) {
		tentative = 0;
		goto Again;
	}

	warning("found dup entry in arp cache");
	ipunconfig();
	return -1;
}

static int
recvra6on(Ipifc *ifc)
{
	if(ifc == nil)
		return 0;
	else if(ifc->sendra6 > 0)
		return IsRouter;
	else if(ifc->recvra6 > 0 || noconfig)
		return IsHostRecv;
	else
		return IsHostNoRecv;
}

static void
sendrs(int fd, uchar *dst)
{
	Routersol *rs;
	Lladdropt *llao;
	uchar buf[1024];
	int pktlen;

	memset(buf, 0, sizeof buf);

	rs = (Routersol*)buf;
	rs->type = ICMP6_RS;
	ipmove(rs->dst, dst);
	ipmove(rs->src, conf.lladdr);
	pktlen = sizeof *rs;

	if(conf.hwalen > 0){
		llao = (Lladdropt*)&buf[pktlen];
		llao->type = V6nd_srclladdr;
		llao->len = (2+7+conf.hwalen)/8;
		memmove(llao->lladdr, conf.hwa, conf.hwalen);
		pktlen += 8 * llao->len;
	}

	if(write(fd, rs, pktlen) != pktlen){
		DEBUG("sendrs: write failed, pkt size %d", pktlen);
	} else {
		DEBUG("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);
}

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

	if(fd < 0)
		return;

	n = strlen(str);
	if(write(fd, str, n) != n)
		warning("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 linkmtu %d",
		cf->mflag, cf->oflag, cf->reachtime, cf->rxmitra,
		cf->ttl, cf->routerlt, cf->linkmtu);
	ewrite(cf->cfd, cfg);
	free(cfg);
}

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

	cfg = smprint("ra6 sendra %d recvra %d maxraint %d minraint %d",
		cf->sendra, cf->recvra, cf->maxraint, cf->minraint);
	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 int
masklen(uchar *mask)
{
	int len;

	for(len=0; len < 128; len += 8){
		if(*mask != 255)
			break;
		mask++;
	}
	while(len < 128 && (*mask & (0x80 >> (len & 7))) != 0)
		len++;
	return len;
}

static void
genipmkask(uchar *mask, int len)
{
	memset(mask, 0, IPaddrlen);
	if(len < 0)
		len = 0;
	else if(len > 128)
		len = 128;
	for(; len >= 8; len -= 8)
		*mask++ = 255;
	if(len > 0)
		*mask = ~((1<<(8-len))-1);
}

typedef struct Route Route;
struct Route
{
	Route	*next;
	ulong	time;

	ulong	prefixlt;
	ulong	routerlt;

	uchar	src[IPaddrlen];
	uchar	gaddr[IPaddrlen];
	uchar	laddr[IPaddrlen];
	uchar	mask[IPaddrlen];

	uchar	hash[SHA1dlen];
};

static Route	*routelist;

/*
 * host receiving a router advertisement calls this
 */
static void
recvrahost(uchar buf[], int pktlen)
{
	char dnsdomain[sizeof(conf.dnsdomain)];
	int m, n, optype, seen;
	Lladdropt *llao;
	Mtuopt *mtuo;
	Prefixopt *prfo;
	Ipaddrsopt *addrso;
	Routeradv *ra;
	uchar hash[SHA1dlen];
	Route *r, **rr;
	ulong now;

	m = sizeof *ra;
	ra = (Routeradv*)buf;
	if(pktlen < m)
		return;

	if(!ISIPV6LINKLOCAL(ra->src))
		return;

	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);
	conf.linkmtu = DEFMTU;

	memset(conf.dns, 0, sizeof(conf.dns));
	memset(conf.fs, 0, sizeof(conf.fs));
	memset(conf.auth, 0, sizeof(conf.auth));
	memset(conf.dnsdomain, 0, sizeof(conf.dnsdomain));

	/* process options */
	while(pktlen - m >= 8) {
		n = m;
		optype = buf[n];
		m += 8 * buf[n+1];
		if(m <= n || pktlen < m)
			return;

		switch (optype) {
		case V6nd_srclladdr:
			llao = (Lladdropt*)&buf[n];
			if(llao->len == 1 && conf.hwalen == 6)
				arpenter(ra->src, llao->lladdr);
			break;
		case V6nd_mtu:
			mtuo = (Mtuopt*)&buf[n];
			conf.linkmtu = nhgetl(mtuo->mtu);
			break;

		case V6nd_rdnssl:
			addrso = (Ipaddrsopt*)&buf[n];
			if(gnames(dnsdomain, sizeof(dnsdomain),
				addrso->addrs, (addrso->len - 1)*8) <= 0)
				break;
			addnames(conf.dnsdomain, dnsdomain, sizeof(conf.dnsdomain));
			break;

		case V6nd_rdns:
			addrso = (Ipaddrsopt*)&buf[n];
			n = (addrso->len - 1) * 8;
			if(n == 0 || n % IPaddrlen)
				break;
			addaddrs(conf.dns, sizeof(conf.dns), addrso->addrs, n);
			break;

		case V6nd_9fs:
			addrso = (Ipaddrsopt*)&buf[n];
			n = (addrso->len - 1) * 8;
			if(n == 0 || n % IPaddrlen || !plan9)
				break;
			addaddrs(conf.fs, sizeof(conf.fs), addrso->addrs, n);
			break;
		case V6nd_9auth:
			addrso = (Ipaddrsopt*)&buf[n];
			n = (addrso->len - 1) * 8;
			if(n == 0 || n % IPaddrlen || !plan9)
				break;
			addaddrs(conf.auth, sizeof(conf.auth), addrso->addrs, n);
			break;
		}
	}

	issuebasera6(&conf);

	/* remove expired default routes */
	m = 0;
	now = time(nil);
	for(rr = &routelist; (r = *rr) != nil;){
		if(m > 100
		|| r->prefixlt != ~0UL && r->prefixlt < now-r->time
		|| r->routerlt != ~0UL && r->routerlt < now-r->time
		|| ipcmp(r->src, ra->src) == 0 && r->routerlt != 0 && conf.routerlt == 0){
			DEBUG("purging RA from %I on %s; pfx %I %M",
				r->src, conf.dev, r->laddr, r->mask);
			if(validip(r->gaddr))
				removedefroute(r->gaddr, conf.lladdr, r->laddr, r->mask);
			*rr = r->next;
			free(r);
			continue;
		}

		rr = &r->next;
		m++;
	}

	/* process prefixes */
	m = sizeof *ra;
	while(pktlen - m >= 8) {
		n = m;
		optype = buf[n];
		m += 8 * buf[n+1];
		if(m <= n || pktlen < m)
			return;

		if(optype != V6nd_pfxinfo)
			continue;

		prfo = (Prefixopt*)&buf[n];
		if(prfo->len != 4)
			continue;

		if((prfo->lar & AFMASK) == 0)
			continue;

		conf.prefixlen = prfo->plen & 127;
		genipmkask(conf.mask, conf.prefixlen);
		maskip(prfo->pref, conf.mask, conf.v6pref);
		memmove(conf.laddr, conf.v6pref, 8);
		memmove(conf.laddr+8, conf.lladdr+8, 8);
		conf.onlink = (prfo->lar & OLMASK) != 0;
		conf.autoflag = (prfo->lar & AFMASK) != 0;
		conf.validlt = nhgetl(prfo->validlt);
		conf.preflt = nhgetl(prfo->preflt);

		if(conf.routerlt == 0)
			ipmove(conf.gaddr, IPnoaddr);
		else if((prfo->lar & RFMASK) != 0)
			ipmove(conf.gaddr, prfo->pref);
		else
			ipmove(conf.gaddr, ra->src);

		seen = 0;
		sha1((uchar*)&conf, sizeof(conf), hash, nil);
		for(rr = &routelist; (r = *rr) != nil; rr = &r->next){
			if(ipcmp(r->src, ra->src) == 0
			&& ipcmp(r->laddr, conf.laddr) == 0){
				seen = memcmp(r->hash, hash, SHA1dlen) == 0;
				*rr = r->next;
				break;
			}
		}
		if(r == nil)
			r = malloc(sizeof(*r));

		memmove(r->hash, hash, SHA1dlen);

		ipmove(r->src, ra->src);
		ipmove(r->gaddr, conf.gaddr);
		ipmove(r->laddr, conf.laddr);
		ipmove(r->mask, conf.mask);

		r->time = now;
		r->routerlt = conf.routerlt;
		r->prefixlt = conf.validlt;

		r->next = routelist;
		routelist = r;
	
		if(conf.prefixlen < 1
		|| conf.prefixlen > 64
		|| !validip(conf.v6pref)
		|| isv4(conf.v6pref)
		|| ipcmp(conf.v6pref, v6loopback) == 0
		|| ISIPV6MCAST(conf.v6pref)
		|| ISIPV6LINKLOCAL(conf.v6pref)){
			if(!seen)
				warning("igoring bogus prefix from %I on %s; pfx %I %M",
					ra->src, conf.dev, conf.v6pref, conf.mask);

			/* keep it arround so we wont comlain again */
			r->prefixlt = r->routerlt = ~0UL;
			continue;
		}

		/* add prefix and update parameters */
		issueadd6(&conf);

		/* report this prefix configuration only once */
		if(seen)
			continue;

		DEBUG("got RA from %I on %s; pfx %I %M",
			ra->src, conf.dev, conf.v6pref, conf.mask);

		if(validip(conf.gaddr))
			adddefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.mask);

		if(noconfig)
			continue;

		if(beprimary)
			putndb();
		refresh();
	}
}

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

	ifc = readipifc(conf.mpoint, nil, myifc);
	if(ifc == nil)
		sysfatal("can't read ipifc: %r");

	if(!findllip(conf.lladdr, ifc))
		sysfatal("no link local address");

	fd = dialicmpv6(v6allnodesL, ICMP6_RA);
	if(fd < 0)
		sysfatal("can't open icmp_ra connection: %r");

	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
	case -1:
		sysfatal("can't fork: %r");
	default:
		close(fd);
		DEBUG("recvra6 on %s", conf.dev);

		/* wait for initial RA */
		return (int)(uintptr)rendezvous(recvra6, (void*)0);
	case 0:
		break;
	}
	procsetname("recvra6 on %s %I", conf.dev, conf.lladdr);
	notify(catch);

	sendrscnt = 0;
	if(recvra6on(ifc) == IsHostRecv){
		sendrs(fd, v6allroutersL);
		sendrscnt = Maxv6rss;
	}

	recvracnt = Maxv6initras;
	sleepfor = Minv6interradelay;

	for (;;) {
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		sleepfor = alarm(0);

		/* wait for alarm to expire */
		if(recvracnt == 0 && sleepfor > 100)
			continue;

		sleepfor = Maxv6radelay;

		ifc = readipifc(conf.mpoint, ifc, myifc);
		if(ifc == nil) {
			warning("recvra6: can't read router params on %s, quitting on %s",
				conf.mpoint, conf.dev);
			if(sendrscnt >= 0)
				rendezvous(recvra6, (void*)-1);
			exits(nil);
		}

		if(recvra6on(ifc) == IsHostNoRecv || noconfig && sendrscnt < 0){
			warning("recvra6: recvra off, quitting on %s", conf.dev);
			if(sendrscnt >= 0)
				rendezvous(recvra6, (void*)-1);
			exits(nil);
		}

		if(n <= 0) {
			if(sendrscnt > 0) {
				sendrscnt--;
				sendrs(fd, v6allroutersL);
				sleepfor = V6rsintvl + nrand(100);
			}
			if(sendrscnt == 0) {
				sendrscnt--;
				warning("recvra6: no router advs after %d sols on %s",
					Maxv6rss, conf.dev);
				rendezvous(recvra6, (void*)0);
			}
			continue;
		}

		switch (recvra6on(ifc)) {
		case IsRouter:
			recvrarouter(buf, n);
			break;
		case IsHostRecv:
			recvrahost(buf, n);
			break;
		}

		/* got at least initial ra; no whining */
		if(sendrscnt >= 0)
			rendezvous(recvra6, (void*)1);
		sendrscnt = -1;

		if(recvracnt > 0)
			recvracnt--;
		else
			recvracnt = Maxv6initras;
	}
}

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

	n = sizeof *rs + sizeof *llao;
	rs = (Routersol*)buf;
	if(pktlen < n)
		return 0;

	llao = (Lladdropt*)&buf[sizeof *rs];
	if(llao->type != V6nd_srclladdr || llao->len != 1 || conf.hwalen != 6)
		return 0;

	if(!validip(rs->src)
	|| isv4(rs->src)
	|| ipcmp(rs->src, v6loopback) == 0
	|| ISIPV6MCAST(rs->src))
		return 0;

	if((n = arpenter(rs->src, llao->lladdr)) <= 0)
		return n;

	ipmove(sol, rs->src);
	return 1;
}

static void
sendra(int fd, uchar *dst, int rlt, Ipifc *ifc, Ndb *db)
{
	uchar dns[sizeof(conf.dns)], fs[sizeof(conf.fs)], auth[sizeof(conf.auth)];
	char dnsdomain[sizeof(conf.dnsdomain)];
	Ipaddrsopt *addrso;
	Prefixopt *prfo;
	Iplifc *lifc;
	Routeradv *ra;
	uchar buf[1024];
	int pktlen, n;

	memset(dns, 0, sizeof(dns));
	memset(fs, 0, sizeof(fs));
	memset(auth, 0, sizeof(auth));
	memset(dnsdomain, 0, sizeof(dnsdomain));

	memset(buf, 0, sizeof buf);

	ra = (Routeradv*)buf;
	ipmove(ra->dst, dst);
	ipmove(ra->src, conf.lladdr);
	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);
	pktlen = sizeof *ra;

	/*
	 * include link layer address (mac address for now) in
	 * link layer address option
	 */
	if(conf.hwalen > 0){
		Lladdropt *llao = (Lladdropt *)&buf[pktlen];
		llao->type = V6nd_srclladdr;
		llao->len = (2+7+conf.hwalen)/8;
		memmove(llao->lladdr, conf.hwa, conf.hwalen);
		pktlen += 8 * llao->len;
	}

	/* include all global unicast prefixes on interface in prefix options */
	for (lifc = (ifc != nil? ifc->lifc: nil); lifc != nil; lifc = lifc->next) {
		if(pktlen > sizeof buf - 4*8)
			break;

		if(!validip(lifc->ip)
		|| isv4(lifc->ip)
		|| ipcmp(lifc->ip, v6loopback) == 0
		|| ISIPV6MCAST(lifc->ip)
		|| ISIPV6LINKLOCAL(lifc->ip))
			continue;

		prfo = (Prefixopt*)&buf[pktlen];
		prfo->type = V6nd_pfxinfo;
		prfo->len = 4;
		prfo->plen = masklen(lifc->mask) & 127;
		if(prfo->plen == 0)
			continue;
		ipmove(prfo->pref, lifc->net);
		prfo->lar = AFMASK|OLMASK;
		hnputl(prfo->validlt, lifc->validlt);
		hnputl(prfo->preflt, lifc->preflt);
		pktlen += 8 * prfo->len;

		/* get ndb configuration for this prefix */
		ipmove(conf.laddr, lifc->ip);
		ndb2conf(db, lifc->net);

		addaddrs(dns, sizeof(dns), conf.dns, sizeof(conf.dns));
		addaddrs(fs, sizeof(fs), conf.fs, sizeof(conf.fs));
		addaddrs(auth, sizeof(auth), conf.auth, sizeof(conf.auth));

		addnames(dnsdomain, conf.dnsdomain, sizeof(dnsdomain));
	}

	addrso = (Ipaddrsopt*)&buf[pktlen];
	n = pnames(addrso->addrs, sizeof buf - 8 - pktlen, dnsdomain);
	if(n > 0){
		addrso->type = V6nd_rdnssl;
		addrso->len = 1 + ((n + 7) / 8);
		hnputl(addrso->lifetime, ~0L);
		pktlen += 8 * addrso->len;
	}

	if((n = countaddrs(dns, sizeof(dns))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
		addrso = (Ipaddrsopt*)&buf[pktlen];
		addrso->type = V6nd_rdns;
		addrso->len = 1 + n*2;
		memmove(addrso->addrs, dns, n*IPaddrlen);
		hnputl(addrso->lifetime, ~0L);
		pktlen += 8 * addrso->len;
	}

	if(!plan9)
		goto send;

	/* send plan9 specific options */
	if((n = countaddrs(fs, sizeof(fs))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
		addrso = (Ipaddrsopt*)&buf[pktlen];
		addrso->type = V6nd_9fs;
		addrso->len = 1 + n*2;
		memmove(addrso->addrs, fs, n*IPaddrlen);
		hnputl(addrso->lifetime, ~0L);
		pktlen += 8 * addrso->len;
	}
	if((n = countaddrs(auth, sizeof(auth))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
		addrso = (Ipaddrsopt*)&buf[pktlen];
		addrso->type = V6nd_9auth;
		addrso->len = 1 + n*2;
		memmove(addrso->addrs, auth, n*IPaddrlen);
		hnputl(addrso->lifetime, ~0L);
		pktlen += 8 * addrso->len;
	}

send:
	write(fd, buf, pktlen);
}

/*
 * daemon to send router advertisements to hosts
 */
static void
sendra6(void)
{
	int fd, n, sleepfor, nquitmsgs;
	uchar buf[4096], dst[IPaddrlen];
	Ipifc *ifc;
	Ndb *db;

	db = opendatabase();
	if(db == nil)
		warning("couldn't open ndb: %r");

	ifc = readipifc(conf.mpoint, nil, myifc);
	if(ifc == nil)
		sysfatal("can't read ipifc: %r");

	if(!findllip(conf.lladdr, ifc))
		sysfatal("no link local address");

	fd = dialicmpv6(v6allroutersL, ICMP6_RS);
	if(fd < 0)
		sysfatal("can't open icmp_rs connection: %r");

	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
	case -1:
		sysfatal("can't fork: %r");
	default:
		close(fd);
		DEBUG("sendra6 on %s", conf.dev);
		return;
	case 0:
		break;
	}
	procsetname("sendra6 on %s %I", conf.dev, conf.lladdr);
	notify(catch);

	nquitmsgs = Maxv6finalras;
	sleepfor = 100 + jitter();

	for (;;) {
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		sleepfor = alarm(0);

		if(ifc->sendra6 > 0 && n > 0 && recvrs(buf, n, dst) > 0)
			sendra(fd, dst, 1, ifc, db);

		/* wait for alarm to expire */
		if(sleepfor > 100)
			continue;
		sleepfor = Minv6interradelay;

		ifc = readipifc(conf.mpoint, ifc, myifc);
		if(ifc == nil) {
			warning("sendra6: can't read router params on %s, quitting on %s",
				conf.mpoint, conf.dev);
			exits(nil);
		}
		if(ifc->sendra6 <= 0){
			if(nquitmsgs > 0) {
				nquitmsgs--;
				sendra(fd, v6allnodesL, 0, ifc, nil);
				continue;
			}
			warning("sendra6: sendra off on %s, quitting on %s",
				conf.mpoint, conf.dev);
			exits(nil);
		}
		db = opendatabase();
		sendra(fd, v6allnodesL, 1, ifc, db);
		sleepfor = randint(ifc->rp.minraint, ifc->rp.maxraint);
	}
}

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

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

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

void
doipv6(int what)
{
	fprint(conf.rfd, "tag ra6");

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