/*
* 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,
};
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 && isether();
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, defmask(conf.laddr));
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)
&& ipcmp(conf.gaddr, conf.laddr) != 0
&& ipcmp(conf.gaddr, conf.lladdr) != 0)
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)
&& ipcmp(conf.gaddr, conf.laddr) != 0
&& ipcmp(conf.gaddr, conf.lladdr) != 0)
adddefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.mask);
if(noconfig)
continue;
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);
recvracnt = 0;
sendrscnt = 0;
if(recvra6on(ifc) == IsHostRecv){
sendrs(fd, v6allroutersL);
sendrscnt = Maxv6rss;
}
sleepfor = Minv6interradelay;
for (;;) {
alarm(sleepfor);
n = read(fd, buf, sizeof buf);
sleepfor = alarm(0);
/* wait for alarm to expire */
if(recvracnt >= Maxv6initras && 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(recvracnt == 0)
rendezvous(recvra6, (void*)-1);
exits(nil);
}
if(recvra6on(ifc) == IsHostNoRecv || noconfig && sendrscnt < 0){
warning("recvra6: recvra off, quitting on %s", conf.dev);
if(recvracnt == 0)
rendezvous(recvra6, (void*)-1);
exits(nil);
}
if(n <= 0) {
if(sendrscnt > 0) {
sendrscnt--;
sendrs(fd, v6allroutersL);
sleepfor = V6rsintvl + nrand(100);
} else if(recvracnt == 0) {
warning("recvra6: no router advs after %d sols on %s",
Maxv6rss, conf.dev);
rendezvous(recvra6, (void*)0);
recvracnt = 1;
}
continue;
}
switch (recvra6on(ifc)) {
case IsRouter:
recvrarouter(buf, n);
break;
case IsHostRecv:
recvrahost(buf, n);
break;
}
/* got at least initial ra; no whining */
if(recvracnt == 0)
rendezvous(recvra6, (void*)1);
if(recvracnt < Maxv6initras)
recvracnt++;
else
recvracnt = 1;
}
}
/*
* 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)
{
if(conf.recvra > 0)
recvra6();
dolog = 1;
if(conf.sendra > 0) {
ewrite(conf.cfd, "iprouting 1");
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;
}
}