shithub: riscv

ref: c4603260f8736dbea8d7f47c46e8db0cf1a23fa0
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 "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);
}

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

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, pktlen;
	uchar *a;
	char *p = sps, *e = spe;

	a = ps;
	for (pktlen = pe - ps; pktlen > 0; pktlen -= 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(pktlen < 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(pktlen < 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
catch(void *a, char *msg)
{
	USED(a);
	if(strstr(msg, "alarm"))
		noted(NCONT);
	else
		noted(NDFLT);
}

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.laddr, 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.laddr);
	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.laddr);
	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;
}

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

/* 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");
	doremove();
	return 0;
}

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

static 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 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.laddr);
	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)
		ralog("sendrs: write failed, pkt size %d", pktlen);
	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);
}

/* host receiving a router advertisement calls this */

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

	if(fd < 0)
		return;

	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 m, n, optype;
	uchar src[IPaddrlen];
	Lladdropt *llao;
	Mtuopt *mtuo;
	Prefixopt *prfo;
	Routeradv *ra;
	static int first = 1;

	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);
	issuebasera6(&conf);

	while(pktlen - m >= 8) {
		n = m;
		optype = buf[n];
		m += 8 * buf[n+1];
		if(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_pfxinfo:
			prfo = (Prefixopt*)&buf[n];
			if(prfo->len != 4) {
				ralog("illegal len (%d) for prefix option", prfo->len);
				return;
			}
			if((prfo->plen & 127) == 0
			|| !validip(prfo->pref)
			|| isv4(prfo->pref)
			|| ipcmp(prfo->pref, v6loopback) == 0
			|| ISIPV6MCAST(prfo->pref)
			|| ISIPV6LINKLOCAL(prfo->pref)){
				ralog("igoring bogus prefix from %I on %s; pfx %I /%d",
					ra->src, conf.dev, prfo->pref, prfo->plen);
				break;
			}
			if(first) {
				first = 0;
				ralog("got initial RA from %I on %s; pfx %I /%d",
					ra->src, conf.dev, prfo->pref, prfo->plen);
			}
			conf.prefixlen = prfo->plen & 127;
			genipmkask(conf.mask, conf.prefixlen);
			maskip(prfo->pref, conf.mask, conf.v6pref);
			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(conf.routerlt == 0)
				break;
			if((prfo->lar & RFMASK) != 0)
				ipmove(conf.gaddr, prfo->pref);
			else
				ipmove(conf.gaddr, ra->src);

			memmove(src, conf.v6pref, 8);
			memmove(src+8, conf.laddr+8, 8);
			adddefroute(conf.gaddr, conf.laddr, src, conf.mask);
			break;
		}
	}
}

/*
 * daemon to receive router advertisements from routers
 */
void
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.laddr, ifc))
		sysfatal("no link local address");

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

	notify(catch);
	sendrscnt = Maxv6rss;
	recvracnt = 0;

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

	procsetname("recvra6 on %s %I", conf.dev, conf.laddr);
	ralog("recvra6 on %s", conf.dev);
	sleepfor = Minv6interradelay;
	for (;;) {
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		sleepfor = alarm(0);

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

		ifc = readipifc(conf.mpoint, ifc, myifc);
		if(ifc == nil) {
			ralog("recvra6: can't read router params on %s, quitting on %s",
				conf.mpoint, conf.dev);
			exits(nil);
		}
		
		if(n <= 0) {
			if(sendrscnt > 0) {
				sendrscnt--;
				if(recvra6on(ifc) == IsHostRecv)
					sendrs(fd, v6allroutersL);
				sleepfor = V6rsintvl + nrand(100);
			}
			if(sendrscnt == 0) {
				sendrscnt--;
				sleepfor = 0;
				ralog("recvra6: no router advs after %d sols on %s",
					Maxv6rss, conf.dev);
			}
			continue;
		}

		/* got at least initial ra; no whining */
		sendrscnt = -1;
		sleepfor = 0;

		if(++recvracnt >= Maxv6initras){
			recvracnt = 0;
			sleepfor = Maxv6radelay;
		}

		switch (recvra6on(ifc)) {
		case IsRouter:
			recvrarouter(buf, n);
			break;
		case IsHostRecv:
			recvrahost(buf, n);
			break;
		case IsHostNoRecv:
			ralog("recvra6: recvra off, quitting on %s", conf.dev);
			exits(nil);
		}
	}
}

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

void
sendra(int fd, uchar *dst, int rlt, Ipifc *ifc)
{
	uchar buf[1024];
	Iplifc *lifc;
	Lladdropt *llao;
	Prefixopt *prfo;
	Routeradv *ra;
	int pktlen;

	memset(buf, 0, sizeof buf);

	ra = (Routeradv *)buf;
	ipmove(ra->dst, dst);
	ipmove(ra->src, conf.laddr);
	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){
		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;
	}

	write(fd, buf, pktlen);
}

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

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

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

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

	notify(catch);
	nquitmsgs = Maxv6finalras;

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

	procsetname("sendra6 on %s %I", conf.dev, conf.laddr);
	ralog("sendra6 on %s", conf.dev);
	sleepfor = 100 + jitter();
	for (;;) {
		alarm(sleepfor);
		n = read(fd, buf, sizeof buf);
		sleepfor = alarm(0);

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

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

		ifc = readipifc(conf.mpoint, ifc, myifc);
		if(ifc == nil) {
			ralog("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);
				continue;
			} else {
				ralog("sendra6: sendra off, quitting on %s", conf.dev);
				exits(nil);
			}
		}
		sendra(fd, v6allnodesL, 1, ifc);
		sleepfor = randint(ifc->rp.minraint, ifc->rp.maxraint);
	}
}

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

	mklladdr();

	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)
{
	fprint(conf.rfd, "tag ra6");

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