mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-21 11:13:30 +00:00
3724189620
added/removed interfaces in a more consistent manner and reloading the configuration file. - Implement burst unsolicited RA sending into the internal RA timer framework when AdvSendAdvertisements and/or configuration entries are changed as described in RFC 4861 6.2.4. This fixes issues that make termination of the rtadvd(8) daemon take very long time. An interface now has three internal states, UNCONFIGURED, TRANSITIVE, or CONFIGURED, and the burst unsolicited sending happens in TRANSITIVE. See rtadvd.h for the details. - rtadvd(8) now accepts non-existent interfaces as well in the command line. - Add control socket support and rtadvctl(8) utility to show the RA information in rtadvd(8). Dumping by SIGUSR1 has been removed in favor of it.
1530 lines
41 KiB
C
1530 lines
41 KiB
C
/* $FreeBSD$ */
|
|
/* $KAME: config.c,v 1.84 2003/08/05 12:34:23 itojun Exp $ */
|
|
|
|
/*
|
|
* Copyright (C) 1998 WIDE Project.
|
|
* Copyright (C) 2011 Hiroki Sato <hrs@FreeBSD.org>
|
|
* 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.
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_var.h>
|
|
#include <net/route.h>
|
|
#include <net/if_dl.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet6/ip6_var.h>
|
|
#include <netinet/icmp6.h>
|
|
#include <netinet6/nd6.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <stdio.h>
|
|
#include <syslog.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <netdb.h>
|
|
#include <string.h>
|
|
#include <search.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <ifaddrs.h>
|
|
|
|
#include "rtadvd.h"
|
|
#include "advcap.h"
|
|
#include "timer.h"
|
|
#include "if.h"
|
|
#include "config.h"
|
|
|
|
/* label of tcapcode + number + domain name + zero octet */
|
|
static char entbuf[10 + 3 + NI_MAXHOST + 1];
|
|
static char oentbuf[10 + 3 + NI_MAXHOST + 1];
|
|
static char abuf[DNAME_LABELENC_MAXLEN];
|
|
|
|
static time_t prefix_timo = (60 * 120); /* 2 hours.
|
|
* XXX: should be configurable. */
|
|
|
|
static struct rtadvd_timer *prefix_timeout(void *);
|
|
static void makeentry(char *, size_t, int, const char *);
|
|
static size_t dname_labelenc(char *, const char *);
|
|
|
|
/* Encode domain name label encoding in RFC 1035 Section 3.1 */
|
|
static size_t
|
|
dname_labelenc(char *dst, const char *src)
|
|
{
|
|
char *dst_origin;
|
|
char *p;
|
|
size_t len;
|
|
|
|
dst_origin = dst;
|
|
len = strlen(src);
|
|
|
|
/* Length fields per 63 octets + '\0' (<= DNAME_LABELENC_MAXLEN) */
|
|
memset(dst, 0, len + len / 64 + 1 + 1);
|
|
|
|
syslog(LOG_DEBUG, "<%s> labelenc = %s", __func__, src);
|
|
while (src && (len = strlen(src)) != 0) {
|
|
/* Put a length field with 63 octet limitation first. */
|
|
p = strchr(src, '.');
|
|
if (p == NULL)
|
|
*dst++ = len = MIN(63, len);
|
|
else
|
|
*dst++ = len = MIN(63, p - src);
|
|
/* Copy 63 octets at most. */
|
|
memcpy(dst, src, len);
|
|
dst += len;
|
|
if (p == NULL) /* the last label */
|
|
break;
|
|
src = p + 1;
|
|
}
|
|
/* Always need a 0-length label at the tail. */
|
|
*dst++ = '\0';
|
|
|
|
syslog(LOG_DEBUG, "<%s> labellen = %td", __func__, dst - dst_origin);
|
|
return (dst - dst_origin);
|
|
}
|
|
|
|
#define MUSTHAVE(var, cap) \
|
|
do { \
|
|
int64_t t; \
|
|
if ((t = agetnum(cap)) < 0) { \
|
|
fprintf(stderr, "rtadvd: need %s for interface %s\n", \
|
|
cap, intface); \
|
|
exit(1); \
|
|
} \
|
|
var = t; \
|
|
} while (0)
|
|
|
|
#define MAYHAVE(var, cap, def) \
|
|
do { \
|
|
if ((var = agetnum(cap)) < 0) \
|
|
var = def; \
|
|
} while (0)
|
|
|
|
int
|
|
loadconfig_index(int idx)
|
|
{
|
|
char ifname[IFNAMSIZ];
|
|
|
|
syslog(LOG_DEBUG, "<%s> enter", __func__);
|
|
|
|
if (if_indextoname(idx, ifname) != NULL)
|
|
return (loadconfig_ifname(ifname));
|
|
else
|
|
return (1);
|
|
}
|
|
|
|
int
|
|
loadconfig_ifname(char *ifname)
|
|
{
|
|
struct ifinfo *ifi;
|
|
|
|
syslog(LOG_DEBUG, "<%s> enter", __func__);
|
|
|
|
update_ifinfo(&ifilist, UPDATE_IFINFO_ALL);
|
|
TAILQ_FOREACH(ifi, &ifilist, ifi_next) {
|
|
/* NULL means all IFs will be processed. */
|
|
if (ifname != NULL &&
|
|
strcmp(ifi->ifi_ifname, ifname) != 0)
|
|
continue;
|
|
|
|
if (!ifi->ifi_persist) {
|
|
syslog(LOG_INFO,
|
|
"<%s> %s is not a target interface. "
|
|
"Ignored at this moment.", __func__,
|
|
ifi->ifi_ifname);
|
|
continue;
|
|
|
|
}
|
|
if (ifi->ifi_ifindex == 0) {
|
|
syslog(LOG_ERR,
|
|
"<%s> %s not found. "
|
|
"Ignored at this moment.", __func__,
|
|
ifi->ifi_ifname);
|
|
continue;
|
|
}
|
|
if (getconfig(ifi) == NULL) {
|
|
syslog(LOG_ERR,
|
|
"<%s> invalid configuration for %s. "
|
|
"Ignored at this moment.", __func__,
|
|
ifi->ifi_ifname);
|
|
continue;
|
|
}
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
rm_ifinfo_index(int idx)
|
|
{
|
|
struct ifinfo *ifi;
|
|
|
|
ifi = if_indextoifinfo(idx);
|
|
if (ifi == NULL) {
|
|
syslog(LOG_ERR, "<%s>: ifinfo not found (idx=%d)",
|
|
__func__, idx);
|
|
return (-1);
|
|
}
|
|
|
|
return (rm_ifinfo(ifi));
|
|
}
|
|
|
|
int
|
|
rm_ifinfo(struct ifinfo *ifi)
|
|
{
|
|
int error;
|
|
|
|
syslog(LOG_DEBUG, "<%s> enter (%s).", __func__, ifi->ifi_ifname);
|
|
switch (ifi->ifi_state) {
|
|
case IFI_STATE_UNCONFIGURED:
|
|
return (0);
|
|
break;
|
|
default:
|
|
ifi->ifi_state = IFI_STATE_UNCONFIGURED;
|
|
syslog(LOG_DEBUG,
|
|
"<%s> ifname=%s marked as UNCONFIGURED.",
|
|
__func__, ifi->ifi_ifname);
|
|
|
|
/* XXX: No MC leaving here becasue index is disappeared */
|
|
|
|
/* Inactivate timer */
|
|
rtadvd_remove_timer(ifi->ifi_ra_timer);
|
|
ifi->ifi_ra_timer = NULL;
|
|
break;
|
|
}
|
|
|
|
/* clean up ifi */
|
|
if (!ifi->ifi_persist) {
|
|
TAILQ_REMOVE(&ifilist, ifi, ifi_next);
|
|
syslog(LOG_DEBUG, "<%s>: ifinfo (idx=%d) removed.",
|
|
__func__, ifi->ifi_ifindex);
|
|
free(ifi);
|
|
} else {
|
|
/* recreate an empty entry */
|
|
update_persist_ifinfo(&ifilist, ifi->ifi_ifname);
|
|
syslog(LOG_DEBUG, "<%s>: ifname=%s is persistent.",
|
|
__func__, ifi->ifi_ifname);
|
|
}
|
|
|
|
/* clean up rai if any */
|
|
switch (ifi->ifi_state) {
|
|
case IFI_STATE_CONFIGURED:
|
|
if (ifi->ifi_rainfo != NULL) {
|
|
error = rm_rainfo(ifi->ifi_rainfo);
|
|
if (error)
|
|
return (error);
|
|
ifi->ifi_rainfo = NULL;
|
|
}
|
|
break;
|
|
case IFI_STATE_TRANSITIVE:
|
|
if (ifi->ifi_rainfo == ifi->ifi_rainfo_trans) {
|
|
if (ifi->ifi_rainfo != NULL) {
|
|
error = rm_rainfo(ifi->ifi_rainfo);
|
|
if (error)
|
|
return (error);
|
|
ifi->ifi_rainfo = NULL;
|
|
ifi->ifi_rainfo_trans = NULL;
|
|
}
|
|
} else {
|
|
if (ifi->ifi_rainfo != NULL) {
|
|
error = rm_rainfo(ifi->ifi_rainfo);
|
|
if (error)
|
|
return (error);
|
|
ifi->ifi_rainfo = NULL;
|
|
}
|
|
if (ifi->ifi_rainfo_trans != NULL) {
|
|
error = rm_rainfo(ifi->ifi_rainfo_trans);
|
|
if (error)
|
|
return (error);
|
|
ifi->ifi_rainfo_trans = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "<%s> leave (%s).", __func__, ifi->ifi_ifname);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
rm_rainfo(struct rainfo *rai)
|
|
{
|
|
struct prefix *pfx;
|
|
struct soliciter *sol;
|
|
struct rdnss *rdn;
|
|
struct rdnss_addr *rdna;
|
|
struct dnssl *dns;
|
|
struct rtinfo *rti;
|
|
|
|
syslog(LOG_DEBUG, "<%s>: enter", __func__);
|
|
|
|
TAILQ_REMOVE(&railist, rai, rai_next);
|
|
if (rai->rai_ifinfo != NULL)
|
|
syslog(LOG_DEBUG, "<%s>: rainfo (idx=%d) removed.",
|
|
__func__, rai->rai_ifinfo->ifi_ifindex);
|
|
|
|
if (rai->rai_ra_data != NULL)
|
|
free(rai->rai_ra_data);
|
|
|
|
while ((pfx = TAILQ_FIRST(&rai->rai_prefix)) != NULL) {
|
|
TAILQ_REMOVE(&rai->rai_prefix, pfx, pfx_next);
|
|
free(pfx);
|
|
}
|
|
while ((sol = TAILQ_FIRST(&rai->rai_soliciter)) != NULL) {
|
|
TAILQ_REMOVE(&rai->rai_soliciter, sol, sol_next);
|
|
free(sol);
|
|
}
|
|
while ((rdn = TAILQ_FIRST(&rai->rai_rdnss)) != NULL) {
|
|
TAILQ_REMOVE(&rai->rai_rdnss, rdn, rd_next);
|
|
while ((rdna = TAILQ_FIRST(&rdn->rd_list)) != NULL) {
|
|
TAILQ_REMOVE(&rdn->rd_list, rdna, ra_next);
|
|
free(rdna);
|
|
}
|
|
free(rdn);
|
|
}
|
|
while ((dns = TAILQ_FIRST(&rai->rai_dnssl)) != NULL) {
|
|
TAILQ_REMOVE(&rai->rai_dnssl, dns, dn_next);
|
|
free(dns);
|
|
}
|
|
while ((rti = TAILQ_FIRST(&rai->rai_route)) != NULL) {
|
|
TAILQ_REMOVE(&rai->rai_route, rti, rti_next);
|
|
free(rti);
|
|
}
|
|
free(rai);
|
|
syslog(LOG_DEBUG, "<%s>: leave", __func__);
|
|
|
|
return (0);
|
|
}
|
|
|
|
struct ifinfo *
|
|
getconfig(struct ifinfo *ifi)
|
|
{
|
|
int stat, i;
|
|
int error;
|
|
char tbuf[BUFSIZ];
|
|
struct rainfo *rai;
|
|
struct rainfo *rai_old;
|
|
int32_t val;
|
|
int64_t val64;
|
|
char buf[BUFSIZ];
|
|
char *bp = buf;
|
|
char *addr, *flagstr;
|
|
|
|
if (ifi == NULL) /* if does not exist */
|
|
return (NULL);
|
|
|
|
if (ifi->ifi_state == IFI_STATE_TRANSITIVE &&
|
|
ifi->ifi_rainfo == NULL) {
|
|
syslog(LOG_INFO, "<%s> %s is shutting down. Skipped.",
|
|
__func__, ifi->ifi_ifname);
|
|
return (NULL);
|
|
}
|
|
|
|
if ((stat = agetent(tbuf, ifi->ifi_ifname)) <= 0) {
|
|
memset(tbuf, 0, sizeof(tbuf));
|
|
syslog(LOG_INFO,
|
|
"<%s> %s isn't defined in the configuration file"
|
|
" or the configuration file doesn't exist."
|
|
" Treat it as default",
|
|
__func__, ifi->ifi_ifname);
|
|
}
|
|
|
|
ELM_MALLOC(rai, exit(1));
|
|
TAILQ_INIT(&rai->rai_prefix);
|
|
TAILQ_INIT(&rai->rai_route);
|
|
TAILQ_INIT(&rai->rai_rdnss);
|
|
TAILQ_INIT(&rai->rai_dnssl);
|
|
TAILQ_INIT(&rai->rai_soliciter);
|
|
rai->rai_ifinfo = ifi;
|
|
|
|
/* gather on-link prefixes from the network interfaces. */
|
|
if (agetflag("noifprefix"))
|
|
rai->rai_advifprefix = 0;
|
|
else
|
|
rai->rai_advifprefix = 1;
|
|
|
|
/* get interface information */
|
|
if (agetflag("nolladdr"))
|
|
rai->rai_advlinkopt = 0;
|
|
else
|
|
rai->rai_advlinkopt = 1;
|
|
if (rai->rai_advlinkopt) {
|
|
if (ifi->ifi_sdl.sdl_type == 0) {
|
|
syslog(LOG_ERR,
|
|
"<%s> can't get information of %s",
|
|
__func__, ifi->ifi_ifname);
|
|
goto getconfig_free_rai;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* set router configuration variables.
|
|
*/
|
|
MAYHAVE(val, "maxinterval", DEF_MAXRTRADVINTERVAL);
|
|
if (val < MIN_MAXINTERVAL || val > MAX_MAXINTERVAL) {
|
|
syslog(LOG_ERR,
|
|
"<%s> maxinterval (%" PRIu32 ") on %s is invalid "
|
|
"(must be between %u and %u)", __func__, val,
|
|
ifi->ifi_ifname, MIN_MAXINTERVAL, MAX_MAXINTERVAL);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_maxinterval = (uint16_t)val;
|
|
|
|
MAYHAVE(val, "mininterval", rai->rai_maxinterval/3);
|
|
if ((uint16_t)val < MIN_MININTERVAL ||
|
|
(uint16_t)val > (rai->rai_maxinterval * 3) / 4) {
|
|
syslog(LOG_ERR,
|
|
"<%s> mininterval (%" PRIu32 ") on %s is invalid "
|
|
"(must be between %d and %d)",
|
|
__func__, val, ifi->ifi_ifname, MIN_MININTERVAL,
|
|
(rai->rai_maxinterval * 3) / 4);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_mininterval = (uint16_t)val;
|
|
|
|
MAYHAVE(val, "chlim", DEF_ADVCURHOPLIMIT);
|
|
rai->rai_hoplimit = val & 0xff;
|
|
|
|
if ((flagstr = (char *)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)) {
|
|
syslog(LOG_ERR, "<%s> the \'h\' and \'l\'"
|
|
" router flags are exclusive", __func__);
|
|
goto getconfig_free_rai;
|
|
}
|
|
val |= ND_RA_FLAG_RTPREF_LOW;
|
|
}
|
|
} else
|
|
MAYHAVE(val, "raflags", 0);
|
|
|
|
rai->rai_managedflg = val & ND_RA_FLAG_MANAGED;
|
|
rai->rai_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
|
|
rai->rai_rtpref = val & ND_RA_FLAG_RTPREF_MASK;
|
|
if (rai->rai_rtpref == ND_RA_FLAG_RTPREF_RSV) {
|
|
syslog(LOG_ERR, "<%s> invalid router preference (%02x) on %s",
|
|
__func__, rai->rai_rtpref, ifi->ifi_ifname);
|
|
goto getconfig_free_rai;
|
|
}
|
|
|
|
MAYHAVE(val, "rltime", rai->rai_maxinterval * 3);
|
|
if ((uint16_t)val && ((uint16_t)val < rai->rai_maxinterval ||
|
|
(uint16_t)val > MAXROUTERLIFETIME)) {
|
|
syslog(LOG_ERR,
|
|
"<%s> router lifetime (%" PRIu32 ") on %s is invalid "
|
|
"(must be 0 or between %d and %d)",
|
|
__func__, val, ifi->ifi_ifname, rai->rai_maxinterval,
|
|
MAXROUTERLIFETIME);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_lifetime = val & 0xffff;
|
|
|
|
MAYHAVE(val, "rtime", DEF_ADVREACHABLETIME);
|
|
if (val < 0 || val > MAXREACHABLETIME) {
|
|
syslog(LOG_ERR,
|
|
"<%s> reachable time (%" PRIu32 ") on %s is invalid "
|
|
"(must be no greater than %d)",
|
|
__func__, val, ifi->ifi_ifname, MAXREACHABLETIME);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_reachabletime = (uint32_t)val;
|
|
|
|
MAYHAVE(val64, "retrans", DEF_ADVRETRANSTIMER);
|
|
if (val64 < 0 || val64 > 0xffffffff) {
|
|
syslog(LOG_ERR, "<%s> retrans time (%" PRIu64 ") on %s out of range",
|
|
__func__, val64, ifi->ifi_ifname);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_retranstimer = (uint32_t)val64;
|
|
|
|
if (agetnum("hapref") != -1 || agetnum("hatime") != -1) {
|
|
syslog(LOG_ERR,
|
|
"<%s> mobile-ip6 configuration not supported",
|
|
__func__);
|
|
goto getconfig_free_rai;
|
|
}
|
|
/* 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);
|
|
rai->rai_clockskew = val;
|
|
|
|
rai->rai_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 */
|
|
ELM_MALLOC(pfx, exit(1));
|
|
pfx->pfx_rainfo = rai;
|
|
pfx->pfx_origin = PREFIX_FROM_CONFIG;
|
|
|
|
if (inet_pton(AF_INET6, addr, &pfx->pfx_prefix) != 1) {
|
|
syslog(LOG_ERR,
|
|
"<%s> inet_pton failed for %s",
|
|
__func__, addr);
|
|
goto getconfig_free_pfx;
|
|
}
|
|
if (IN6_IS_ADDR_MULTICAST(&pfx->pfx_prefix)) {
|
|
syslog(LOG_ERR,
|
|
"<%s> multicast prefix (%s) must "
|
|
"not be advertised on %s",
|
|
__func__, addr, ifi->ifi_ifname);
|
|
goto getconfig_free_pfx;
|
|
}
|
|
if (IN6_IS_ADDR_LINKLOCAL(&pfx->pfx_prefix))
|
|
syslog(LOG_NOTICE,
|
|
"<%s> link-local prefix (%s) will be"
|
|
" advertised on %s",
|
|
__func__, addr, ifi->ifi_ifname);
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "prefixlen");
|
|
MAYHAVE(val, entbuf, 64);
|
|
if (val < 0 || val > 128) {
|
|
syslog(LOG_ERR, "<%s> prefixlen (%" PRIu32 ") for %s "
|
|
"on %s out of range",
|
|
__func__, val, addr, ifi->ifi_ifname);
|
|
goto getconfig_free_pfx;
|
|
}
|
|
pfx->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->pfx_onlinkflg = val & ND_OPT_PI_FLAG_ONLINK;
|
|
pfx->pfx_autoconfflg = val & ND_OPT_PI_FLAG_AUTO;
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "vltime");
|
|
MAYHAVE(val64, entbuf, DEF_ADVVALIDLIFETIME);
|
|
if (val64 < 0 || val64 > 0xffffffff) {
|
|
syslog(LOG_ERR, "<%s> vltime (%" PRIu64 ") for "
|
|
"%s/%d on %s is out of range",
|
|
__func__, val64,
|
|
addr, pfx->pfx_prefixlen, ifi->ifi_ifname);
|
|
goto getconfig_free_pfx;
|
|
}
|
|
pfx->pfx_validlifetime = (uint32_t)val64;
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "vltimedecr");
|
|
if (agetflag(entbuf)) {
|
|
struct timeval now;
|
|
gettimeofday(&now, 0);
|
|
pfx->pfx_vltimeexpire =
|
|
now.tv_sec + pfx->pfx_validlifetime;
|
|
}
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "pltime");
|
|
MAYHAVE(val64, entbuf, DEF_ADVPREFERREDLIFETIME);
|
|
if (val64 < 0 || val64 > 0xffffffff) {
|
|
syslog(LOG_ERR,
|
|
"<%s> pltime (%" PRIu64 ") for %s/%d on %s "
|
|
"is out of range",
|
|
__func__, val64,
|
|
addr, pfx->pfx_prefixlen, ifi->ifi_ifname);
|
|
goto getconfig_free_pfx;
|
|
}
|
|
pfx->pfx_preflifetime = (uint32_t)val64;
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "pltimedecr");
|
|
if (agetflag(entbuf)) {
|
|
struct timeval now;
|
|
gettimeofday(&now, 0);
|
|
pfx->pfx_pltimeexpire =
|
|
now.tv_sec + pfx->pfx_preflifetime;
|
|
}
|
|
/* link into chain */
|
|
TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
|
|
rai->rai_pfxs++;
|
|
continue;
|
|
getconfig_free_pfx:
|
|
free(pfx);
|
|
}
|
|
if (rai->rai_advifprefix && rai->rai_pfxs == 0)
|
|
get_prefix(rai);
|
|
|
|
MAYHAVE(val64, "mtu", 0);
|
|
if (val < 0 || val64 > 0xffffffff) {
|
|
syslog(LOG_ERR,
|
|
"<%s> mtu (%" PRIu64 ") on %s out of range",
|
|
__func__, val64, ifi->ifi_ifname);
|
|
goto getconfig_free_rai;
|
|
}
|
|
rai->rai_linkmtu = (uint32_t)val64;
|
|
if (rai->rai_linkmtu == 0) {
|
|
char *mtustr;
|
|
|
|
if ((mtustr = (char *)agetstr("mtu", &bp)) &&
|
|
strcmp(mtustr, "auto") == 0)
|
|
rai->rai_linkmtu = ifi->ifi_phymtu;
|
|
}
|
|
else if (rai->rai_linkmtu < IPV6_MMTU ||
|
|
rai->rai_linkmtu > ifi->ifi_phymtu) {
|
|
syslog(LOG_ERR,
|
|
"<%s> advertised link mtu (%" PRIu32 ") on %s is invalid (must "
|
|
"be between least MTU (%d) and physical link MTU (%d)",
|
|
__func__, rai->rai_linkmtu, ifi->ifi_ifname,
|
|
IPV6_MMTU, ifi->ifi_phymtu);
|
|
goto getconfig_free_rai;
|
|
}
|
|
|
|
#ifdef SIOCSIFINFO_IN6
|
|
{
|
|
struct in6_ndireq ndi;
|
|
int s;
|
|
|
|
if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
|
|
syslog(LOG_ERR, "<%s> socket: %s", __func__,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
memset(&ndi, 0, sizeof(ndi));
|
|
strncpy(ndi.ifname, ifi->ifi_ifname, sizeof(ndi.ifname));
|
|
if (ioctl(s, SIOCGIFINFO_IN6, (caddr_t)&ndi) < 0)
|
|
syslog(LOG_INFO, "<%s> ioctl:SIOCGIFINFO_IN6 at %s: %s",
|
|
__func__, ifi->ifi_ifname, strerror(errno));
|
|
|
|
/* reflect the RA info to the host variables in kernel */
|
|
ndi.ndi.chlim = rai->rai_hoplimit;
|
|
ndi.ndi.retrans = rai->rai_retranstimer;
|
|
ndi.ndi.basereachable = rai->rai_reachabletime;
|
|
if (ioctl(s, SIOCSIFINFO_IN6, (caddr_t)&ndi) < 0)
|
|
syslog(LOG_INFO, "<%s> ioctl:SIOCSIFINFO_IN6 at %s: %s",
|
|
__func__, ifi->ifi_ifname, strerror(errno));
|
|
|
|
close(s);
|
|
}
|
|
#endif
|
|
|
|
/* route information */
|
|
rai->rai_routes = 0;
|
|
for (i = -1; i < MAXROUTE; i++) {
|
|
struct rtinfo *rti;
|
|
|
|
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;
|
|
|
|
/* allocate memory to store prefix information */
|
|
ELM_MALLOC(rti, exit(1));
|
|
|
|
if (inet_pton(AF_INET6, addr, &rti->rti_prefix) != 1) {
|
|
syslog(LOG_ERR, "<%s> inet_pton failed for %s",
|
|
__func__, addr);
|
|
goto getconfig_free_rti;
|
|
}
|
|
#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)) {
|
|
syslog(LOG_ERR,
|
|
"<%s> multicast route (%s) must "
|
|
"not be advertised on %s",
|
|
__func__, addr, ifi->ifi_ifname);
|
|
goto getconfig_free_rti;
|
|
}
|
|
if (IN6_IS_ADDR_LINKLOCAL(&rti->prefix)) {
|
|
syslog(LOG_NOTICE,
|
|
"<%s> link-local route (%s) will "
|
|
"be advertised on %s",
|
|
__func__, addr, ifi->ifi_ifname);
|
|
goto getconfig_free_rti;
|
|
}
|
|
#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) {
|
|
syslog(LOG_ERR, "<%s> prefixlen (%" PRIu32 ") for %s on %s "
|
|
"out of range",
|
|
__func__, val, addr, ifi->ifi_ifname);
|
|
goto getconfig_free_rti;
|
|
}
|
|
rti->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)) {
|
|
syslog(LOG_ERR,
|
|
"<%s> the \'h\' and \'l\' route"
|
|
" preferences are exclusive",
|
|
__func__);
|
|
goto getconfig_free_rti;
|
|
}
|
|
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->rti_rtpref = val & ND_RA_FLAG_RTPREF_MASK;
|
|
if (rti->rti_rtpref == ND_RA_FLAG_RTPREF_RSV) {
|
|
syslog(LOG_ERR, "<%s> invalid route preference (%02x) "
|
|
"for %s/%d on %s",
|
|
__func__, rti->rti_rtpref, addr,
|
|
rti->rti_prefixlen, ifi->ifi_ifname);
|
|
goto getconfig_free_rti;
|
|
}
|
|
|
|
/*
|
|
* 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,
|
|
ifi->ifi_ifname);
|
|
val64 = rai->rai_lifetime;
|
|
}
|
|
}
|
|
if (val64 < 0 || val64 > 0xffffffff) {
|
|
syslog(LOG_ERR, "<%s> route lifetime (%" PRIu64 ") for "
|
|
"%s/%d on %s out of range", __func__,
|
|
val64, addr, rti->rti_prefixlen,
|
|
ifi->ifi_ifname);
|
|
goto getconfig_free_rti;
|
|
}
|
|
rti->rti_ltime = (uint32_t)val64;
|
|
|
|
/* link into chain */
|
|
TAILQ_INSERT_TAIL(&rai->rai_route, rti, rti_next);
|
|
rai->rai_routes++;
|
|
continue;
|
|
getconfig_free_rti:
|
|
free(rti);
|
|
}
|
|
|
|
/* DNS server and DNS search list information */
|
|
for (i = -1; i < MAXRDNSSENT ; i++) {
|
|
struct rdnss *rdn;
|
|
struct rdnss_addr *rdna;
|
|
char *ap;
|
|
int c;
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "rdnss");
|
|
addr = (char *)agetstr(entbuf, &bp);
|
|
if (addr == NULL)
|
|
break;
|
|
ELM_MALLOC(rdn, exit(1));
|
|
|
|
TAILQ_INIT(&rdn->rd_list);
|
|
|
|
for (ap = addr; ap - addr < (ssize_t)strlen(addr); ap += c+1) {
|
|
c = strcspn(ap, ",");
|
|
strncpy(abuf, ap, c);
|
|
abuf[c] = '\0';
|
|
ELM_MALLOC(rdna, goto getconfig_free_rdn);
|
|
if (inet_pton(AF_INET6, abuf, &rdna->ra_dns) != 1) {
|
|
syslog(LOG_ERR, "<%s> inet_pton failed for %s",
|
|
__func__, abuf);
|
|
free(rdna);
|
|
goto getconfig_free_rdn;
|
|
}
|
|
TAILQ_INSERT_TAIL(&rdn->rd_list, rdna, ra_next);
|
|
}
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "rdnssltime");
|
|
MAYHAVE(val, entbuf, (rai->rai_maxinterval * 3 / 2));
|
|
if ((uint16_t)val < rai->rai_maxinterval ||
|
|
(uint16_t)val > rai->rai_maxinterval * 2) {
|
|
syslog(LOG_ERR, "%s (%" PRIu16 ") on %s is invalid "
|
|
"(must be between %d and %d)",
|
|
entbuf, val, ifi->ifi_ifname, rai->rai_maxinterval,
|
|
rai->rai_maxinterval * 2);
|
|
goto getconfig_free_rdn;
|
|
}
|
|
rdn->rd_ltime = val;
|
|
|
|
/* link into chain */
|
|
TAILQ_INSERT_TAIL(&rai->rai_rdnss, rdn, rd_next);
|
|
continue;
|
|
getconfig_free_rdn:
|
|
while ((rdna = TAILQ_FIRST(&rdn->rd_list)) != NULL) {
|
|
TAILQ_REMOVE(&rdn->rd_list, rdna, ra_next);
|
|
free(rdna);
|
|
}
|
|
free(rdn);
|
|
}
|
|
|
|
for (i = -1; i < MAXDNSSLENT ; i++) {
|
|
struct dnssl *dns;
|
|
struct dnssl_addr *dnsa;
|
|
char *ap;
|
|
int c;
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "dnssl");
|
|
addr = (char *)agetstr(entbuf, &bp);
|
|
if (addr == NULL)
|
|
break;
|
|
|
|
ELM_MALLOC(dns, exit(1));
|
|
|
|
TAILQ_INIT(&dns->dn_list);
|
|
|
|
for (ap = addr; ap - addr < (ssize_t)strlen(addr); ap += c+1) {
|
|
c = strcspn(ap, ",");
|
|
strncpy(abuf, ap, c);
|
|
abuf[c] = '\0';
|
|
ELM_MALLOC(dnsa, goto getconfig_free_dns);
|
|
dnsa->da_len = dname_labelenc(dnsa->da_dom, abuf);
|
|
syslog(LOG_DEBUG, "<%s>: dnsa->da_len = %d", __func__,
|
|
dnsa->da_len);
|
|
TAILQ_INSERT_TAIL(&dns->dn_list, dnsa, da_next);
|
|
}
|
|
|
|
makeentry(entbuf, sizeof(entbuf), i, "dnsslltime");
|
|
MAYHAVE(val, entbuf, (rai->rai_maxinterval * 3 / 2));
|
|
if ((uint16_t)val < rai->rai_maxinterval ||
|
|
(uint16_t)val > rai->rai_maxinterval * 2) {
|
|
syslog(LOG_ERR, "%s (%" PRIu16 ") on %s is invalid "
|
|
"(must be between %d and %d)",
|
|
entbuf, val, ifi->ifi_ifname, rai->rai_maxinterval,
|
|
rai->rai_maxinterval * 2);
|
|
goto getconfig_free_dns;
|
|
}
|
|
dns->dn_ltime = val;
|
|
|
|
/* link into chain */
|
|
TAILQ_INSERT_TAIL(&rai->rai_dnssl, dns, dn_next);
|
|
continue;
|
|
getconfig_free_dns:
|
|
while ((dnsa = TAILQ_FIRST(&dns->dn_list)) != NULL) {
|
|
TAILQ_REMOVE(&dns->dn_list, dnsa, da_next);
|
|
free(dnsa);
|
|
}
|
|
free(dns);
|
|
}
|
|
/* construct the sending packet */
|
|
make_packet(rai);
|
|
|
|
/*
|
|
* If an entry with the same ifindex exists, remove it first.
|
|
* Before the removal, RDNSS and DNSSL options with
|
|
* zero-lifetime will be sent.
|
|
*/
|
|
switch (ifi->ifi_state) {
|
|
case IFI_STATE_UNCONFIGURED:
|
|
/* UNCONFIGURED -> TRANSITIVE */
|
|
|
|
error = sock_mc_join(&sock, ifi->ifi_ifindex);
|
|
if (error)
|
|
exit(1);
|
|
|
|
ifi->ifi_state = IFI_STATE_TRANSITIVE;
|
|
ifi->ifi_burstcount = MAX_INITIAL_RTR_ADVERTISEMENTS;
|
|
ifi->ifi_burstinterval = MAX_INITIAL_RTR_ADVERT_INTERVAL;
|
|
|
|
/* The same two rai mean initial burst */
|
|
ifi->ifi_rainfo = rai;
|
|
ifi->ifi_rainfo_trans = rai;
|
|
TAILQ_INSERT_TAIL(&railist, rai, rai_next);
|
|
|
|
if (ifi->ifi_ra_timer == NULL)
|
|
ifi->ifi_ra_timer = rtadvd_add_timer(ra_timeout,
|
|
ra_timer_update, ifi, ifi);
|
|
ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
|
|
rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
|
|
ifi->ifi_ra_timer);
|
|
|
|
syslog(LOG_DEBUG,
|
|
"<%s> ifname=%s marked as TRANSITIVE (initial burst).",
|
|
__func__, ifi->ifi_ifname);
|
|
break;
|
|
case IFI_STATE_CONFIGURED:
|
|
/* CONFIGURED -> TRANSITIVE */
|
|
rai_old = ifi->ifi_rainfo;
|
|
if (rai_old == NULL) {
|
|
syslog(LOG_ERR,
|
|
"<%s> ifi_rainfo is NULL"
|
|
" in IFI_STATE_CONFIGURED.", __func__);
|
|
ifi = NULL;
|
|
break;
|
|
} else {
|
|
struct rdnss *rdn;
|
|
struct dnssl *dns;
|
|
|
|
rai_old->rai_lifetime = 0;
|
|
TAILQ_FOREACH(rdn, &rai_old->rai_rdnss, rd_next)
|
|
rdn->rd_ltime = 0;
|
|
TAILQ_FOREACH(dns, &rai_old->rai_dnssl, dn_next)
|
|
dns->dn_ltime = 0;
|
|
|
|
ifi->ifi_rainfo_trans = rai_old;
|
|
ifi->ifi_state = IFI_STATE_TRANSITIVE;
|
|
ifi->ifi_burstcount = MAX_FINAL_RTR_ADVERTISEMENTS;
|
|
ifi->ifi_burstinterval = MIN_DELAY_BETWEEN_RAS;
|
|
|
|
ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
|
|
rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
|
|
ifi->ifi_ra_timer);
|
|
|
|
syslog(LOG_DEBUG,
|
|
"<%s> ifname=%s marked as TRANSITIVE"
|
|
" (transitional burst)",
|
|
__func__, ifi->ifi_ifname);
|
|
}
|
|
ifi->ifi_rainfo = rai;
|
|
TAILQ_INSERT_TAIL(&railist, rai, rai_next);
|
|
break;
|
|
case IFI_STATE_TRANSITIVE:
|
|
if (ifi->ifi_rainfo != NULL) {
|
|
if (ifi->ifi_rainfo == ifi->ifi_rainfo_trans) {
|
|
/* Reinitialize initial burst */
|
|
rm_rainfo(ifi->ifi_rainfo);
|
|
ifi->ifi_rainfo = rai;
|
|
ifi->ifi_rainfo_trans = rai;
|
|
ifi->ifi_burstcount =
|
|
MAX_INITIAL_RTR_ADVERTISEMENTS;
|
|
ifi->ifi_burstinterval =
|
|
MAX_INITIAL_RTR_ADVERT_INTERVAL;
|
|
} else {
|
|
/* Replace ifi_rainfo with the new one */
|
|
rm_rainfo(ifi->ifi_rainfo);
|
|
ifi->ifi_rainfo = rai;
|
|
}
|
|
TAILQ_INSERT_TAIL(&railist, rai, rai_next);
|
|
|
|
ra_timer_update(ifi, &ifi->ifi_ra_timer->rat_tm);
|
|
rtadvd_set_timer(&ifi->ifi_ra_timer->rat_tm,
|
|
ifi->ifi_ra_timer);
|
|
} else {
|
|
/* XXX: NOTREACHED. Being shut down. */
|
|
syslog(LOG_ERR,
|
|
"<%s> %s is shutting down. Skipped.",
|
|
__func__, ifi->ifi_ifname);
|
|
rm_rainfo(rai);
|
|
|
|
return (NULL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return (ifi);
|
|
|
|
getconfig_free_rai:
|
|
free(rai);
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
get_prefix(struct rainfo *rai)
|
|
{
|
|
struct ifaddrs *ifap, *ifa;
|
|
struct prefix *pfx;
|
|
struct in6_addr *a;
|
|
struct ifinfo *ifi;
|
|
char *p, *ep, *m, *lim;
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
if (getifaddrs(&ifap) < 0) {
|
|
syslog(LOG_ERR,
|
|
"<%s> can't get interface addresses",
|
|
__func__);
|
|
exit(1);
|
|
}
|
|
ifi = rai->rai_ifinfo;
|
|
|
|
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
|
int plen;
|
|
|
|
if (strcmp(ifa->ifa_name, ifi->ifi_ifname) != 0)
|
|
continue;
|
|
if (ifa->ifa_addr->sa_family != AF_INET6)
|
|
continue;
|
|
a = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
|
|
if (IN6_IS_ADDR_LINKLOCAL(a))
|
|
continue;
|
|
|
|
/* get prefix length */
|
|
m = (char *)&((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr;
|
|
lim = (char *)(ifa->ifa_netmask) + ifa->ifa_netmask->sa_len;
|
|
plen = prefixlen(m, lim);
|
|
if (plen <= 0 || plen > 128) {
|
|
syslog(LOG_ERR, "<%s> failed to get prefixlen "
|
|
"or prefix is invalid",
|
|
__func__);
|
|
exit(1);
|
|
}
|
|
if (plen == 128) /* XXX */
|
|
continue;
|
|
if (find_prefix(rai, a, plen)) {
|
|
/* ignore a duplicated prefix. */
|
|
continue;
|
|
}
|
|
|
|
/* allocate memory to store prefix info. */
|
|
ELM_MALLOC(pfx, exit(1));
|
|
|
|
/* set prefix, sweep bits outside of prefixlen */
|
|
pfx->pfx_prefixlen = plen;
|
|
memcpy(&pfx->pfx_prefix, a, sizeof(*a));
|
|
p = (char *)&pfx->pfx_prefix;
|
|
ep = (char *)(&pfx->pfx_prefix + 1);
|
|
while (m < lim && p < ep)
|
|
*p++ &= *m++;
|
|
while (p < ep)
|
|
*p++ = 0x00;
|
|
if (!inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
|
|
sizeof(ntopbuf))) {
|
|
syslog(LOG_ERR, "<%s> inet_ntop failed", __func__);
|
|
exit(1);
|
|
}
|
|
syslog(LOG_DEBUG,
|
|
"<%s> add %s/%d to prefix list on %s",
|
|
__func__, ntopbuf, pfx->pfx_prefixlen, ifi->ifi_ifname);
|
|
|
|
/* set other fields with protocol defaults */
|
|
pfx->pfx_validlifetime = DEF_ADVVALIDLIFETIME;
|
|
pfx->pfx_preflifetime = DEF_ADVPREFERREDLIFETIME;
|
|
pfx->pfx_onlinkflg = 1;
|
|
pfx->pfx_autoconfflg = 1;
|
|
pfx->pfx_origin = PREFIX_FROM_KERNEL;
|
|
pfx->pfx_rainfo = rai;
|
|
|
|
/* link into chain */
|
|
TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
|
|
|
|
/* counter increment */
|
|
rai->rai_pfxs++;
|
|
}
|
|
|
|
freeifaddrs(ifap);
|
|
}
|
|
|
|
static void
|
|
makeentry(char *buf, size_t len, int id, const char *string)
|
|
{
|
|
|
|
if (id < 0)
|
|
strlcpy(buf, string, len);
|
|
else
|
|
snprintf(buf, len, "%s%d", string, id);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
static void
|
|
add_prefix(struct rainfo *rai, struct in6_prefixreq *ipr)
|
|
{
|
|
struct prefix *pfx;
|
|
struct ifinfo *ifi;
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
ifi = rai->rai_ifinfo;
|
|
ELM_MALLOC(pfx, return);
|
|
pfx->pfx_prefix = ipr->ipr_prefix.sin6_addr;
|
|
pfx->pfx_prefixlen = ipr->ipr_plen;
|
|
pfx->pfx_validlifetime = ipr->ipr_vltime;
|
|
pfx->pfx_preflifetime = ipr->ipr_pltime;
|
|
pfx->pfx_onlinkflg = ipr->ipr_raf_onlink;
|
|
pfx->pfx_autoconfflg = ipr->ipr_raf_auto;
|
|
pfx->pfx_origin = PREFIX_FROM_DYNAMIC;
|
|
|
|
TAILQ_INSERT_TAIL(&rai->rai_prefix, pfx, pfx_next);
|
|
pfx->pfx_rainfo = rai;
|
|
|
|
syslog(LOG_DEBUG, "<%s> new prefix %s/%d was added on %s",
|
|
__func__,
|
|
inet_ntop(AF_INET6, &ipr->ipr_prefix.sin6_addr, ntopbuf,
|
|
sizeof(ntopbuf)), ipr->ipr_plen, ifi->ifi_ifname);
|
|
|
|
rai->rai_pfxs++;
|
|
}
|
|
|
|
/*
|
|
* 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 *pfx)
|
|
{
|
|
struct rainfo *rai;
|
|
struct ifinfo *ifi;
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
rai = pfx->pfx_rainfo;
|
|
ifi = rai->rai_ifinfo;
|
|
TAILQ_REMOVE(&rai->rai_prefix, pfx, pfx_next);
|
|
syslog(LOG_DEBUG, "<%s> prefix %s/%d was deleted on %s",
|
|
__func__,
|
|
inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
|
|
sizeof(ntopbuf)), pfx->pfx_prefixlen, ifi->ifi_ifname);
|
|
if (pfx->pfx_timer)
|
|
rtadvd_remove_timer(pfx->pfx_timer);
|
|
free(pfx);
|
|
|
|
rai->rai_pfxs--;
|
|
}
|
|
|
|
void
|
|
invalidate_prefix(struct prefix *pfx)
|
|
{
|
|
struct timeval timo;
|
|
struct rainfo *rai;
|
|
struct ifinfo *ifi;
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
rai = pfx->pfx_rainfo;
|
|
ifi = rai->rai_ifinfo;
|
|
if (pfx->pfx_timer) { /* sanity check */
|
|
syslog(LOG_ERR,
|
|
"<%s> assumption failure: timer already exists",
|
|
__func__);
|
|
exit(1);
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "<%s> prefix %s/%d was invalidated on %s, "
|
|
"will expire in %ld seconds", __func__,
|
|
inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf, sizeof(ntopbuf)),
|
|
pfx->pfx_prefixlen, ifi->ifi_ifname, (long)prefix_timo);
|
|
|
|
/* set the expiration timer */
|
|
pfx->pfx_timer = rtadvd_add_timer(prefix_timeout, NULL, pfx, NULL);
|
|
if (pfx->pfx_timer == NULL) {
|
|
syslog(LOG_ERR, "<%s> failed to add a timer for a prefix. "
|
|
"remove the prefix", __func__);
|
|
delete_prefix(pfx);
|
|
}
|
|
timo.tv_sec = prefix_timo;
|
|
timo.tv_usec = 0;
|
|
rtadvd_set_timer(&timo, pfx->pfx_timer);
|
|
}
|
|
|
|
static struct rtadvd_timer *
|
|
prefix_timeout(void *arg)
|
|
{
|
|
|
|
delete_prefix((struct prefix *)arg);
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
void
|
|
update_prefix(struct prefix *pfx)
|
|
{
|
|
struct rainfo *rai;
|
|
struct ifinfo *ifi;
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
rai = pfx->pfx_rainfo;
|
|
ifi = rai->rai_ifinfo;
|
|
if (pfx->pfx_timer == NULL) { /* sanity check */
|
|
syslog(LOG_ERR,
|
|
"<%s> assumption failure: timer does not exist",
|
|
__func__);
|
|
exit(1);
|
|
}
|
|
|
|
syslog(LOG_DEBUG, "<%s> prefix %s/%d was re-enabled on %s",
|
|
__func__, inet_ntop(AF_INET6, &pfx->pfx_prefix, ntopbuf,
|
|
sizeof(ntopbuf)), pfx->pfx_prefixlen, ifi->ifi_ifname);
|
|
|
|
/* stop the expiration timer */
|
|
rtadvd_remove_timer(pfx->pfx_timer);
|
|
pfx->pfx_timer = NULL;
|
|
}
|
|
|
|
/*
|
|
* Try to get an in6_prefixreq contents for a prefix which matches
|
|
* ipr->ipr_prefix and ipr->ipr_plen and belongs to
|
|
* the interface whose name is ipr->ipr_name[].
|
|
*/
|
|
static int
|
|
init_prefix(struct in6_prefixreq *ipr)
|
|
{
|
|
#if 0
|
|
int s;
|
|
|
|
if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) {
|
|
syslog(LOG_ERR, "<%s> socket: %s", __func__,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if (ioctl(s, SIOCGIFPREFIX_IN6, (caddr_t)ipr) < 0) {
|
|
syslog(LOG_INFO, "<%s> ioctl:SIOCGIFPREFIX %s", __func__,
|
|
strerror(errno));
|
|
|
|
ipr->ipr_vltime = DEF_ADVVALIDLIFETIME;
|
|
ipr->ipr_pltime = DEF_ADVPREFERREDLIFETIME;
|
|
ipr->ipr_raf_onlink = 1;
|
|
ipr->ipr_raf_auto = 1;
|
|
/* omit other field initialization */
|
|
}
|
|
else if (ipr->ipr_origin < PR_ORIG_RR) {
|
|
char ntopbuf[INET6_ADDRSTRLEN];
|
|
|
|
syslog(LOG_WARNING, "<%s> Added prefix(%s)'s origin %d is"
|
|
"lower than PR_ORIG_RR(router renumbering)."
|
|
"This should not happen if I am router", __func__,
|
|
inet_ntop(AF_INET6, &ipr->ipr_prefix.sin6_addr, ntopbuf,
|
|
sizeof(ntopbuf)), ipr->ipr_origin);
|
|
close(s);
|
|
return (1);
|
|
}
|
|
|
|
close(s);
|
|
return (0);
|
|
#else
|
|
ipr->ipr_vltime = DEF_ADVVALIDLIFETIME;
|
|
ipr->ipr_pltime = DEF_ADVPREFERREDLIFETIME;
|
|
ipr->ipr_raf_onlink = 1;
|
|
ipr->ipr_raf_auto = 1;
|
|
return (0);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
make_prefix(struct rainfo *rai, int ifindex, struct in6_addr *addr, int plen)
|
|
{
|
|
struct in6_prefixreq ipr;
|
|
|
|
memset(&ipr, 0, sizeof(ipr));
|
|
if (if_indextoname(ifindex, ipr.ipr_name) == NULL) {
|
|
syslog(LOG_ERR, "<%s> Prefix added interface No.%d doesn't"
|
|
"exist. This should not happen! %s", __func__,
|
|
ifindex, strerror(errno));
|
|
exit(1);
|
|
}
|
|
ipr.ipr_prefix.sin6_len = sizeof(ipr.ipr_prefix);
|
|
ipr.ipr_prefix.sin6_family = AF_INET6;
|
|
ipr.ipr_prefix.sin6_addr = *addr;
|
|
ipr.ipr_plen = plen;
|
|
|
|
if (init_prefix(&ipr))
|
|
return; /* init failed by some error */
|
|
add_prefix(rai, &ipr);
|
|
}
|
|
|
|
void
|
|
make_packet(struct rainfo *rai)
|
|
{
|
|
size_t packlen, lladdroptlen = 0;
|
|
char *buf;
|
|
struct nd_router_advert *ra;
|
|
struct nd_opt_prefix_info *ndopt_pi;
|
|
struct nd_opt_mtu *ndopt_mtu;
|
|
struct nd_opt_route_info *ndopt_rti;
|
|
struct rtinfo *rti;
|
|
struct nd_opt_rdnss *ndopt_rdnss;
|
|
struct rdnss *rdn;
|
|
struct nd_opt_dnssl *ndopt_dnssl;
|
|
struct dnssl *dns;
|
|
size_t len;
|
|
struct prefix *pfx;
|
|
struct ifinfo *ifi;
|
|
|
|
ifi = rai->rai_ifinfo;
|
|
/* calculate total length */
|
|
packlen = sizeof(struct nd_router_advert);
|
|
if (rai->rai_advlinkopt) {
|
|
if ((lladdroptlen = lladdropt_length(&ifi->ifi_sdl)) == 0) {
|
|
syslog(LOG_INFO,
|
|
"<%s> link-layer address option has"
|
|
" null length on %s. Treat as not included.",
|
|
__func__, ifi->ifi_ifname);
|
|
rai->rai_advlinkopt = 0;
|
|
}
|
|
packlen += lladdroptlen;
|
|
}
|
|
if (rai->rai_pfxs)
|
|
packlen += sizeof(struct nd_opt_prefix_info) * rai->rai_pfxs;
|
|
if (rai->rai_linkmtu)
|
|
packlen += sizeof(struct nd_opt_mtu);
|
|
|
|
TAILQ_FOREACH(rti, &rai->rai_route, rti_next)
|
|
packlen += sizeof(struct nd_opt_route_info) +
|
|
((rti->rti_prefixlen + 0x3f) >> 6) * 8;
|
|
|
|
TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
|
|
struct rdnss_addr *rdna;
|
|
|
|
packlen += sizeof(struct nd_opt_rdnss);
|
|
TAILQ_FOREACH(rdna, &rdn->rd_list, ra_next)
|
|
packlen += sizeof(rdna->ra_dns);
|
|
}
|
|
TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
|
|
struct dnssl_addr *dnsa;
|
|
|
|
packlen += sizeof(struct nd_opt_dnssl);
|
|
len = 0;
|
|
TAILQ_FOREACH(dnsa, &dns->dn_list, da_next)
|
|
len += dnsa->da_len;
|
|
|
|
/* A zero octet and 8 octet boundary */
|
|
len++;
|
|
len += (len % 8) ? 8 - len % 8 : 0;
|
|
|
|
packlen += len;
|
|
}
|
|
/* allocate memory for the packet */
|
|
if ((buf = malloc(packlen)) == NULL) {
|
|
syslog(LOG_ERR,
|
|
"<%s> can't get enough memory for an RA packet",
|
|
__func__);
|
|
exit(1);
|
|
}
|
|
memset(buf, 0, packlen);
|
|
if (rai->rai_ra_data) /* Free old data if any. */
|
|
free(rai->rai_ra_data);
|
|
rai->rai_ra_data = buf;
|
|
/* XXX: what if packlen > 576? */
|
|
rai->rai_ra_datalen = packlen;
|
|
|
|
/*
|
|
* construct the packet
|
|
*/
|
|
ra = (struct nd_router_advert *)buf;
|
|
ra->nd_ra_type = ND_ROUTER_ADVERT;
|
|
ra->nd_ra_code = 0;
|
|
ra->nd_ra_cksum = 0;
|
|
ra->nd_ra_curhoplimit = (uint8_t)(0xff & rai->rai_hoplimit);
|
|
ra->nd_ra_flags_reserved = 0; /* just in case */
|
|
/*
|
|
* XXX: the router preference field, which is a 2-bit field, should be
|
|
* initialized before other fields.
|
|
*/
|
|
ra->nd_ra_flags_reserved = 0xff & rai->rai_rtpref;
|
|
ra->nd_ra_flags_reserved |=
|
|
rai->rai_managedflg ? ND_RA_FLAG_MANAGED : 0;
|
|
ra->nd_ra_flags_reserved |=
|
|
rai->rai_otherflg ? ND_RA_FLAG_OTHER : 0;
|
|
ra->nd_ra_router_lifetime = htons(rai->rai_lifetime);
|
|
ra->nd_ra_reachable = htonl(rai->rai_reachabletime);
|
|
ra->nd_ra_retransmit = htonl(rai->rai_retranstimer);
|
|
buf += sizeof(*ra);
|
|
|
|
if (rai->rai_advlinkopt) {
|
|
lladdropt_fill(&ifi->ifi_sdl, (struct nd_opt_hdr *)buf);
|
|
buf += lladdroptlen;
|
|
}
|
|
|
|
if (rai->rai_linkmtu) {
|
|
ndopt_mtu = (struct nd_opt_mtu *)buf;
|
|
ndopt_mtu->nd_opt_mtu_type = ND_OPT_MTU;
|
|
ndopt_mtu->nd_opt_mtu_len = 1;
|
|
ndopt_mtu->nd_opt_mtu_reserved = 0;
|
|
ndopt_mtu->nd_opt_mtu_mtu = htonl(rai->rai_linkmtu);
|
|
buf += sizeof(struct nd_opt_mtu);
|
|
}
|
|
|
|
TAILQ_FOREACH(pfx, &rai->rai_prefix, pfx_next) {
|
|
uint32_t vltime, pltime;
|
|
struct timeval now;
|
|
|
|
ndopt_pi = (struct nd_opt_prefix_info *)buf;
|
|
ndopt_pi->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
|
|
ndopt_pi->nd_opt_pi_len = 4;
|
|
ndopt_pi->nd_opt_pi_prefix_len = pfx->pfx_prefixlen;
|
|
ndopt_pi->nd_opt_pi_flags_reserved = 0;
|
|
if (pfx->pfx_onlinkflg)
|
|
ndopt_pi->nd_opt_pi_flags_reserved |=
|
|
ND_OPT_PI_FLAG_ONLINK;
|
|
if (pfx->pfx_autoconfflg)
|
|
ndopt_pi->nd_opt_pi_flags_reserved |=
|
|
ND_OPT_PI_FLAG_AUTO;
|
|
if (pfx->pfx_timer)
|
|
vltime = 0;
|
|
else {
|
|
if (pfx->pfx_vltimeexpire || pfx->pfx_pltimeexpire)
|
|
gettimeofday(&now, NULL);
|
|
if (pfx->pfx_vltimeexpire == 0)
|
|
vltime = pfx->pfx_validlifetime;
|
|
else
|
|
vltime = ((time_t)pfx->pfx_vltimeexpire > now.tv_sec) ?
|
|
pfx->pfx_vltimeexpire - now.tv_sec : 0;
|
|
}
|
|
if (pfx->pfx_timer)
|
|
pltime = 0;
|
|
else {
|
|
if (pfx->pfx_pltimeexpire == 0)
|
|
pltime = pfx->pfx_preflifetime;
|
|
else
|
|
pltime = ((time_t)pfx->pfx_pltimeexpire > now.tv_sec) ?
|
|
pfx->pfx_pltimeexpire - now.tv_sec : 0;
|
|
}
|
|
if (vltime < pltime) {
|
|
/*
|
|
* this can happen if vltime is decrement but pltime
|
|
* is not.
|
|
*/
|
|
pltime = vltime;
|
|
}
|
|
ndopt_pi->nd_opt_pi_valid_time = htonl(vltime);
|
|
ndopt_pi->nd_opt_pi_preferred_time = htonl(pltime);
|
|
ndopt_pi->nd_opt_pi_reserved2 = 0;
|
|
ndopt_pi->nd_opt_pi_prefix = pfx->pfx_prefix;
|
|
|
|
buf += sizeof(struct nd_opt_prefix_info);
|
|
}
|
|
|
|
TAILQ_FOREACH(rti, &rai->rai_route, rti_next) {
|
|
uint8_t psize = (rti->rti_prefixlen + 0x3f) >> 6;
|
|
|
|
ndopt_rti = (struct nd_opt_route_info *)buf;
|
|
ndopt_rti->nd_opt_rti_type = ND_OPT_ROUTE_INFO;
|
|
ndopt_rti->nd_opt_rti_len = 1 + psize;
|
|
ndopt_rti->nd_opt_rti_prefixlen = rti->rti_prefixlen;
|
|
ndopt_rti->nd_opt_rti_flags = 0xff & rti->rti_rtpref;
|
|
ndopt_rti->nd_opt_rti_lifetime = htonl(rti->rti_ltime);
|
|
memcpy(ndopt_rti + 1, &rti->rti_prefix, psize * 8);
|
|
buf += sizeof(struct nd_opt_route_info) + psize * 8;
|
|
}
|
|
|
|
TAILQ_FOREACH(rdn, &rai->rai_rdnss, rd_next) {
|
|
struct rdnss_addr *rdna;
|
|
|
|
ndopt_rdnss = (struct nd_opt_rdnss *)buf;
|
|
ndopt_rdnss->nd_opt_rdnss_type = ND_OPT_RDNSS;
|
|
ndopt_rdnss->nd_opt_rdnss_len = 0;
|
|
ndopt_rdnss->nd_opt_rdnss_reserved = 0;
|
|
ndopt_rdnss->nd_opt_rdnss_lifetime = htonl(rdn->rd_ltime);
|
|
buf += sizeof(struct nd_opt_rdnss);
|
|
|
|
TAILQ_FOREACH(rdna, &rdn->rd_list, ra_next) {
|
|
memcpy(buf, &rdna->ra_dns, sizeof(rdna->ra_dns));
|
|
buf += sizeof(rdna->ra_dns);
|
|
}
|
|
/* Length field should be in 8 octets */
|
|
ndopt_rdnss->nd_opt_rdnss_len = (buf - (char *)ndopt_rdnss) / 8;
|
|
|
|
syslog(LOG_DEBUG, "<%s>: nd_opt_dnss_len = %d", __func__,
|
|
ndopt_rdnss->nd_opt_rdnss_len);
|
|
}
|
|
|
|
TAILQ_FOREACH(dns, &rai->rai_dnssl, dn_next) {
|
|
struct dnssl_addr *dnsa;
|
|
|
|
ndopt_dnssl = (struct nd_opt_dnssl *)buf;
|
|
ndopt_dnssl->nd_opt_dnssl_type = ND_OPT_DNSSL;
|
|
ndopt_dnssl->nd_opt_dnssl_len = 0;
|
|
ndopt_dnssl->nd_opt_dnssl_reserved = 0;
|
|
ndopt_dnssl->nd_opt_dnssl_lifetime = htonl(dns->dn_ltime);
|
|
buf += sizeof(*ndopt_dnssl);
|
|
|
|
TAILQ_FOREACH(dnsa, &dns->dn_list, da_next) {
|
|
memcpy(buf, dnsa->da_dom, dnsa->da_len);
|
|
buf += dnsa->da_len;
|
|
}
|
|
|
|
/* A zero octet after encoded DNS server list. */
|
|
*buf++ = '\0';
|
|
|
|
/* Padding to next 8 octets boundary */
|
|
len = buf - (char *)ndopt_dnssl;
|
|
len += (len % 8) ? 8 - len % 8 : 0;
|
|
|
|
/* Length field must be in 8 octets */
|
|
ndopt_dnssl->nd_opt_dnssl_len = len / 8;
|
|
|
|
syslog(LOG_DEBUG, "<%s>: nd_opt_dnssl_len = %d", __func__,
|
|
ndopt_dnssl->nd_opt_dnssl_len);
|
|
}
|
|
return;
|
|
}
|