/*
* Copyright (C) 1998 WIDE Project.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
if (if_nametoindex(intface) == 0) {
logit(LOG_INFO, "%s: interface %s not found, ignoring",
__func__, intface);
return;
}
logit(LOG_DEBUG, "%s: loading configuration for interface %s",
__func__, intface);
if ((stat = agetent(tbuf, intface)) <= 0) {
memset(tbuf, 0, sizeof(tbuf));
logit(LOG_INFO,
"%s: %s isn't defined in the configuration file"
" or the configuration file doesn't exist."
" Treat it as default",
__func__, intface);
}
/* check if we are allowed to forward packets (if not determined) */
if (forwarding < 0) {
if ((forwarding = getinet6sysctl(IPV6CTL_FORWARDING)) < 0)
exit(EXIT_FAILURE);
}
/* get interface information */
if (agetflag("nolladdr"))
tmp->advlinkopt = 0;
else
tmp->advlinkopt = 1;
if (tmp->advlinkopt) {
if ((tmp->sdl = if_nametosdl(intface)) == NULL) {
logit(LOG_ERR,
"%s: can't get information of %s",
__func__, intface);
goto errexit;
}
tmp->ifindex = tmp->sdl->sdl_index;
} else {
tmp->ifindex = if_nametoindex(intface);
if (tmp->ifindex == 0) {
logit(LOG_ERR,
"%s: can't get information of %s",
__func__, intface);
goto errexit;
}
}
tmp->ifflags = if_getflags(tmp->ifindex, 0);
strlcpy(tmp->ifname, intface, sizeof(tmp->ifname));
if ((tmp->phymtu = if_getmtu(intface)) == 0) {
tmp->phymtu = IPV6_MMTU;
logit(LOG_WARNING,
"%s: can't get interface mtu of %s. Treat as %d",
__func__, intface, IPV6_MMTU);
}
/*
* set router configuration variables.
*/
MAYHAVE(val, "maxinterval", DEF_MAXRTRADVINTERVAL);
if (val < MIN_MAXINTERVAL || val > MAX_MAXINTERVAL) {
logit(LOG_ERR,
"%s: maxinterval (%d) on %s is invalid "
"(must be between %u and %u)", __func__, val,
intface, MIN_MAXINTERVAL, MAX_MAXINTERVAL);
goto errexit;
}
tmp->maxinterval = val;
MAYHAVE(val, "mininterval", tmp->maxinterval/3);
if (val < MIN_MININTERVAL || val > (tmp->maxinterval * 3) / 4) {
logit(LOG_ERR,
"%s: mininterval (%d) on %s is invalid "
"(must be between %u and %d)",
__func__, val, intface, MIN_MININTERVAL,
(tmp->maxinterval * 3) / 4);
goto errexit;
}
tmp->mininterval = val;
MAYHAVE(val, "chlim", DEF_ADVCURHOPLIMIT);
tmp->hoplimit = val & 0xff;
if ((flagstr = agetstr("raflags", &bp))) {
val = 0;
if (strchr(flagstr, 'm'))
val |= ND_RA_FLAG_MANAGED;
if (strchr(flagstr, 'o'))
val |= ND_RA_FLAG_OTHER;
if (strchr(flagstr, 'h'))
val |= ND_RA_FLAG_RTPREF_HIGH;
if (strchr(flagstr, 'l')) {
if ((val & ND_RA_FLAG_RTPREF_HIGH)) {
logit(LOG_ERR, "%s: the \'h\' and \'l\'"
" router flags are exclusive", __func__);
goto errexit;
}
val |= ND_RA_FLAG_RTPREF_LOW;
}
} else {
MAYHAVE(val, "raflags", 0);
}
tmp->managedflg = val & ND_RA_FLAG_MANAGED;
tmp->otherflg = val & ND_RA_FLAG_OTHER;
#ifndef ND_RA_FLAG_RTPREF_MASK
#define ND_RA_FLAG_RTPREF_MASK 0x18 /* 00011000 */
#define ND_RA_FLAG_RTPREF_RSV 0x10 /* 00010000 */
#endif
tmp->rtpref = val & ND_RA_FLAG_RTPREF_MASK;
if (tmp->rtpref == ND_RA_FLAG_RTPREF_RSV) {
logit(LOG_ERR, "%s: invalid router preference (%02x) on %s",
__func__, tmp->rtpref, intface);
goto errexit;
}
MAYHAVE(val, "rltime", DEF_ADVROUTERLIFETIME);
if (val && (val < tmp->maxinterval || val > MAXROUTERLIFETIME)) {
logit(LOG_ERR, "%s: router lifetime (%d) on %s is invalid "
"(must be 0 or between %d and %d)",
__func__, val, intface,
tmp->maxinterval, MAXROUTERLIFETIME);
goto errexit;
}
/*
* Basically, hosts MUST NOT send Router Advertisement messages at any
* time (RFC 2461, Section 6.2.3). However, it would sometimes be
* useful to allow hosts to advertise some parameters such as prefix
* information and link MTU. Thus, we allow hosts to invoke rtadvd
* only when router lifetime (on every advertising interface) is
* explicitly set zero. (see also the above section)
*/
if (val && forwarding == 0) {
logit(LOG_ERR,
"%s: non zero router lifetime is specified for %s, "
"which must not be allowed for hosts. you must "
"change router lifetime or enable IPv6 forwarding.",
__func__, intface);
goto errexit;
}
tmp->lifetime = val & 0xffff;
MAYHAVE(val, "rtime", DEF_ADVREACHABLETIME);
if (val < 0 || val > MAXREACHABLETIME) {
logit(LOG_ERR,
"%s: reachable time (%d) on %s is invalid "
"(must be no greater than %d)",
__func__, val, intface, MAXREACHABLETIME);
goto errexit;
}
tmp->reachabletime = (uint32_t)val;
MAYHAVE(val64, "retrans", DEF_ADVRETRANSTIMER);
if (val64 < 0 || val64 > 0xffffffff) {
logit(LOG_ERR, "%s: retrans time (%lld) on %s out of range",
__func__, (long long)val64, intface);
goto errexit;
}
tmp->retranstimer = (uint32_t)val64;
if (agetnum("hapref") != -1 || agetnum("hatime") != -1) {
logit(LOG_ERR,
"%s: mobile-ip6 configuration not supported",
__func__);
goto errexit;
}
/* prefix information */
/*
* This is an implementation specific parameter to consider
* link propagation delays and poorly synchronized clocks when
* checking consistency of advertised lifetimes.
*/
MAYHAVE(val, "clockskew", 0);
tmp->clockskew = val;
tmp->pfxs = 0;
for (i = -1; i < MAXPREFIX; i++) {
struct prefix *pfx;
makeentry(entbuf, sizeof(entbuf), i, "addr");
addr = (char *)agetstr(entbuf, &bp);
if (addr == NULL)
continue;
/* allocate memory to store prefix information */
if ((pfx = calloc(1, sizeof(*pfx))) == NULL) {
logit(LOG_ERR,
"%s: can't allocate memory: %m",
__func__);
goto errexit;
}
if (inet_pton(AF_INET6, addr, &pfx->prefix) != 1) {
logit(LOG_ERR,
"%s: inet_pton failed for %s",
__func__, addr);
goto errexit;
}
if (IN6_IS_ADDR_MULTICAST(&pfx->prefix)) {
logit(LOG_ERR,
"%s: multicast prefix (%s) must "
"not be advertised on %s",
__func__, addr, intface);
goto errexit;
}
if (IN6_IS_ADDR_LINKLOCAL(&pfx->prefix))
logit(LOG_NOTICE,
"%s: link-local prefix (%s) will be"
" advertised on %s",
__func__, addr, intface);
makeentry(entbuf, sizeof(entbuf), i, "prefixlen");
MAYHAVE(val, entbuf, 64);
if (val < 0 || val > 128) {
logit(LOG_ERR, "%s: prefixlen (%d) for %s "
"on %s out of range",
__func__, val, addr, intface);
goto errexit;
}
pfx->prefixlen = (int)val;
makeentry(entbuf, sizeof(entbuf), i, "pinfoflags");
if ((flagstr = (char *)agetstr(entbuf, &bp))) {
val = 0;
if (strchr(flagstr, 'l'))
val |= ND_OPT_PI_FLAG_ONLINK;
if (strchr(flagstr, 'a'))
val |= ND_OPT_PI_FLAG_AUTO;
} else {
MAYHAVE(val, entbuf,
(ND_OPT_PI_FLAG_ONLINK|ND_OPT_PI_FLAG_AUTO));
}
pfx->onlinkflg = val & ND_OPT_PI_FLAG_ONLINK;
pfx->autoconfflg = val & ND_OPT_PI_FLAG_AUTO;
makeentry(entbuf, sizeof(entbuf), i, "vltime");
MAYHAVE(val64, entbuf, DEF_ADVVALIDLIFETIME);
if (val64 < 0 || val64 > 0xffffffff) {
logit(LOG_ERR, "%s: vltime (%lld) for "
"%s/%d on %s is out of range",
__func__, (long long)val64,
addr, pfx->prefixlen, intface);
goto errexit;
}
pfx->validlifetime = (uint32_t)val64;
makeentry(entbuf, sizeof(entbuf), i, "vltimedecr");
if (agetflag(entbuf)) {
struct timespec now;
prog_clock_gettime(CLOCK_MONOTONIC, &now);
pfx->vltimeexpire =
now.tv_sec + pfx->validlifetime;
}
makeentry(entbuf, sizeof(entbuf), i, "pltime");
MAYHAVE(val64, entbuf, DEF_ADVPREFERREDLIFETIME);
if (val64 < 0 || val64 > 0xffffffff) {
logit(LOG_ERR,
"%s: pltime (%lld) for %s/%d on %s "
"is out of range",
__func__, (long long)val64,
addr, pfx->prefixlen, intface);
goto errexit;
}
pfx->preflifetime = (uint32_t)val64;
makeentry(entbuf, sizeof(entbuf), i, "pltimedecr");
if (agetflag(entbuf)) {
struct timespec now;
prog_clock_gettime(CLOCK_MONOTONIC, &now);
pfx->pltimeexpire =
now.tv_sec + pfx->preflifetime;
}
}
if (TAILQ_FIRST(&tmp->prefix) == NULL && !agetflag("noifprefix"))
get_prefix(tmp);
MAYHAVE(val64, "mtu", 0);
if (val64 < 0 || val64 > 0xffffffff) {
logit(LOG_ERR,
"%s: mtu (%" PRIi64 ") on %s out of range",
__func__, val64, intface);
goto errexit;
}
tmp->linkmtu = (uint32_t)val64;
if (tmp->linkmtu == 0) {
char *mtustr;
if ((mtustr = (char *)agetstr("mtu", &bp)) &&
strcmp(mtustr, "auto") == 0)
tmp->linkmtu = tmp->phymtu;
}
else if (tmp->linkmtu < IPV6_MMTU || tmp->linkmtu > tmp->phymtu) {
logit(LOG_ERR,
"%s: advertised link mtu (%d) on %s is invalid (must "
"be between least MTU (%d) and physical link MTU (%d)",
__func__, tmp->linkmtu, intface,
IPV6_MMTU, tmp->phymtu);
goto errexit;
}
/* route information */
for (i = -1; i < MAXROUTE; i++) {
struct rtinfo *rti;
char oentbuf[256];
makeentry(entbuf, sizeof(entbuf), i, "rtprefix");
addr = (char *)agetstr(entbuf, &bp);
if (addr == NULL) {
makeentry(oentbuf, sizeof(oentbuf), i, "rtrprefix");
addr = (char *)agetstr(oentbuf, &bp);
if (addr) {
fprintf(stderr, "%s was obsoleted. Use %s.\n",
oentbuf, entbuf);
}
}
if (addr == NULL)
continue;
ELM_MALLOC(rti);
memset(rti, 0, sizeof(*rti));
/* link into chain */
TAILQ_INSERT_TAIL(&tmp->route, rti, next);
if (inet_pton(AF_INET6, addr, &rti->prefix) != 1) {
logit(LOG_ERR, "%s: inet_pton failed for %s",
__func__, addr);
goto errexit;
}
#if 0
/*
* XXX: currently there's no restriction in route information
* prefix according to
* draft-ietf-ipngwg-router-selection-00.txt.
* However, I think the similar restriction be necessary.
*/
MAYHAVE(val64, entbuf, DEF_ADVVALIDLIFETIME);
if (IN6_IS_ADDR_MULTICAST(&rti->prefix)) {
logit(LOG_ERR,
"%s: multicast route (%s) must "
"not be advertised on %s",
__func__, addr, intface);
goto errexit;
}
if (IN6_IS_ADDR_LINKLOCAL(&rti->prefix)) {
logit(LOG_NOTICE,
"%s: link-local route (%s) will "
"be advertised on %s",
__func__, addr, intface);
goto errexit;
}
#endif
makeentry(entbuf, sizeof(entbuf), i, "rtplen");
/* XXX: 256 is a magic number for compatibility check. */
MAYHAVE(val, entbuf, 256);
if (val == 256) {
makeentry(oentbuf, sizeof(oentbuf), i, "rtrplen");
MAYHAVE(val, oentbuf, 256);
if (val != 256) {
fprintf(stderr, "%s was obsoleted. Use %s.\n",
oentbuf, entbuf);
} else
val = 64;
}
if (val < 0 || val > 128) {
logit(LOG_ERR, "%s: prefixlen (%d) for %s on %s "
"out of range",
__func__, val, addr, intface);
goto errexit;
}
rti->prefixlen = (int)val;
makeentry(entbuf, sizeof(entbuf), i, "rtflags");
if ((flagstr = (char *)agetstr(entbuf, &bp))) {
val = 0;
if (strchr(flagstr, 'h'))
val |= ND_RA_FLAG_RTPREF_HIGH;
if (strchr(flagstr, 'l')) {
if ((val & ND_RA_FLAG_RTPREF_HIGH)) {
logit(LOG_ERR,
"%s: the \'h\' and \'l\' route"
" preferences are exclusive",
__func__);
goto errexit;
}
val |= ND_RA_FLAG_RTPREF_LOW;
}
} else
MAYHAVE(val, entbuf, 256); /* XXX */
if (val == 256) {
makeentry(oentbuf, sizeof(oentbuf), i, "rtrflags");
MAYHAVE(val, oentbuf, 256);
if (val != 256) {
fprintf(stderr, "%s was obsoleted. Use %s.\n",
oentbuf, entbuf);
} else
val = 0;
}
rti->rtpref = val & ND_RA_FLAG_RTPREF_MASK;
if (rti->rtpref == ND_RA_FLAG_RTPREF_RSV) {
logit(LOG_ERR, "%s: invalid route preference (%02x) "
"for %s/%d on %s",
__func__, rti->rtpref, addr,
rti->prefixlen, intface);
goto errexit;
}
/*
* Since the spec does not a default value, we should make
* this entry mandatory. However, FreeBSD 4.4 has shipped
* with this field being optional, we use the router lifetime
* as an ad-hoc default value with a warning message.
*/
makeentry(entbuf, sizeof(entbuf), i, "rtltime");
MAYHAVE(val64, entbuf, -1);
if (val64 == -1) {
makeentry(oentbuf, sizeof(oentbuf), i, "rtrltime");
MAYHAVE(val64, oentbuf, -1);
if (val64 != -1) {
fprintf(stderr, "%s was obsoleted. Use %s.\n",
oentbuf, entbuf);
} else {
fprintf(stderr, "%s should be specified "
"for interface %s.\n",
entbuf, intface);
val64 = tmp->lifetime;
}
}
if (val64 < 0 || val64 > 0xffffffff) {
logit(LOG_ERR, "%s: route lifetime (%lld) for "
"%s/%d on %s out of range", __func__,
(long long)val64, addr, rti->prefixlen, intface);
goto errexit;
}
rti->ltime = (uint32_t)val64;
}
/* RDNSS */
for (i = -1; i < MAXRDNSS; i++) {
struct rdnss_addr *rdnsa;
makeentry(entbuf, sizeof(entbuf), i, "rdnss");
addr = (char *)agetstr(entbuf, &bp);
if (addr == NULL)
continue;
if (id < 0)
strlcpy(buf, string, len);
else
snprintf(buf, len, "%s%d", string, id);
}
/*
* Delete a prefix to the list of specified interface and reconstruct
* the outgoing packet.
* The prefix must be in the list.
*/
void
delete_prefix(struct prefix *prefix)
{
char ntopbuf[INET6_ADDRSTRLEN];
struct rainfo *rai = prefix->rainfo;
if (prefix->timer == NULL) { /* sanity check */
logit(LOG_ERR,
"%s: assumption failure: timer does not exist",
__func__);
exit(EXIT_FAILURE);
}
logit(LOG_DEBUG, "%s: prefix %s/%d was re-enabled on %s",
__func__, inet_ntop(AF_INET6, &prefix->prefix, ntopbuf,
INET6_ADDRSTRLEN), prefix->prefixlen, rai->ifname);
/* stop the expiration timer */
rtadvd_remove_timer(&prefix->timer);
}
/*
* Add a prefix to the list of specified interface and reconstruct
* the outgoing packet.
* The prefix must not be in the list.
* XXX: other parameters of the prefix(e.g. lifetime) should be
* able to be specified.
*/
void
add_prefix(struct rainfo *rai, int ifindex, const struct in6_addr *addr,
int plen)
{
struct prefix *prefix;
char ntopbuf[INET6_ADDRSTRLEN];