NetBSD/external/bsd/dhcpcd/dist/ipv6nd.c
2013-11-14 01:28:16 +00:00

1808 lines
43 KiB
C

#include <sys/cdefs.h>
__RCSID("$NetBSD: ipv6nd.c,v 1.2 2013/11/14 01:28:16 christos Exp $");
/*
* dhcpcd - DHCP client daemon
* Copyright (c) 2006-2013 Roy Marples <roy@marples.name>
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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/ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#ifdef __linux__
# define _LINUX_IN6_H
# include <linux/ipv6.h>
#endif
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#define ELOOP_QUEUE 2
#include "common.h"
#include "dhcpcd.h"
#include "dhcp6.h"
#include "eloop.h"
#include "ipv6.h"
#include "ipv6nd.h"
#include "script.h"
/* Debugging Router Solicitations is a lot of spam, so disable it */
//#define DEBUG_RS
#define RTR_SOLICITATION_INTERVAL 4 /* seconds */
#define MAX_RTR_SOLICITATIONS 3 /* times */
#ifndef ND_OPT_RDNSS
#define ND_OPT_RDNSS 25
struct nd_opt_rdnss { /* RDNSS option RFC 6106 */
uint8_t nd_opt_rdnss_type;
uint8_t nd_opt_rdnss_len;
uint16_t nd_opt_rdnss_reserved;
uint32_t nd_opt_rdnss_lifetime;
/* followed by list of IP prefixes */
} __packed;
#endif
#ifndef ND_OPT_DNSSL
#define ND_OPT_DNSSL 31
struct nd_opt_dnssl { /* DNSSL option RFC 6106 */
uint8_t nd_opt_dnssl_type;
uint8_t nd_opt_dnssl_len;
uint16_t nd_opt_dnssl_reserved;
uint32_t nd_opt_dnssl_lifetime;
/* followed by list of DNS servers */
} __packed;
#endif
/* Minimal IPv6 MTU */
#ifndef IPV6_MMTU
#define IPV6_MMTU 1280
#endif
#ifndef ND_RA_FLAG_RTPREF_HIGH
#define ND_RA_FLAG_RTPREF_MASK 0x18
#define ND_RA_FLAG_RTPREF_HIGH 0x08
#define ND_RA_FLAG_RTPREF_MEDIUM 0x00
#define ND_RA_FLAG_RTPREF_LOW 0x18
#define ND_RA_FLAG_RTPREF_RSV 0x10
#endif
/* RTPREF_MEDIUM has to be 0! */
#define RTPREF_HIGH 1
#define RTPREF_MEDIUM 0
#define RTPREF_LOW (-1)
#define RTPREF_RESERVED (-2)
#define RTPREF_INVALID (-3) /* internal */
#define MIN_RANDOM_FACTOR 500 /* millisecs */
#define MAX_RANDOM_FACTOR 1500 /* millisecs */
#define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */
#define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */
#if BYTE_ORDER == BIG_ENDIAN
#define IPV6_ADDR_INT32_ONE 1
#define IPV6_ADDR_INT16_MLL 0xff02
#elif BYTE_ORDER == LITTLE_ENDIAN
#define IPV6_ADDR_INT32_ONE 0x01000000
#define IPV6_ADDR_INT16_MLL 0x02ff
#endif
/* Debugging Neighbor Solicitations is a lot of spam, so disable it */
//#define DEBUG_NS
//
/* Currently, no known kernel allows us to send from the unspecified address
* which is required for DAD to work. This isn't that much of a problem as
* the kernel will do DAD for us correctly, however we don't know the exact
* randomness the kernel applies to the timeouts. So we just follow the same
* logic and have a little faith.
* This define is purely for completeness */
// #define IPV6_SEND_DAD
static int sock = -1;
#ifdef IPV6_SEND_DAD
static int unspec_sock = -1;
#endif
static struct sockaddr_in6 allrouters, from;
static struct msghdr sndhdr;
static struct iovec sndiov[2];
static unsigned char *sndbuf;
static struct msghdr rcvhdr;
static struct iovec rcviov[2];
static unsigned char *rcvbuf;
static unsigned char ansbuf[1500];
static char ntopbuf[INET6_ADDRSTRLEN];
static const char *sfrom;
static struct icmp6_filter filt;
struct rahead ipv6_routers = TAILQ_HEAD_INITIALIZER(ipv6_routers);
static void ipv6nd_handledata(void *arg);
#if DEBUG_MEMORY
static void
ipv6nd_cleanup(void)
{
free(sndbuf);
free(rcvbuf);
}
#endif
static int
ipv6nd_open(void)
{
int on;
int len;
#ifdef IPV6_SEND_DAD
union {
struct sockaddr sa;
struct sockaddr_in6 sin;
} su;
#endif
sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1)
return -1;
memset(&allrouters, 0, sizeof(allrouters));
allrouters.sin6_family = AF_INET6;
#ifdef SIN6_LEN
allrouters.sin6_len = sizeof(allrouters);
#endif
if (inet_pton(AF_INET6, ALLROUTERS, &allrouters.sin6_addr.s6_addr) != 1)
goto eexit;
on = 1;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&on, sizeof(on)) == -1)
goto eexit;
on = 1;
if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
&on, sizeof(on)) == -1)
goto eexit;
ICMP6_FILTER_SETBLOCKALL(&filt);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt);
if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
&filt, sizeof(filt)) == -1)
goto eexit;
set_cloexec(sock);
#if DEBUG_MEMORY
atexit(ipv6nd_cleanup);
#endif
len = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int));
sndbuf = calloc(1, len);
if (sndbuf == NULL)
goto eexit;
sndhdr.msg_namelen = sizeof(struct sockaddr_in6);
sndhdr.msg_iov = sndiov;
sndhdr.msg_iovlen = 1;
sndhdr.msg_control = sndbuf;
sndhdr.msg_controllen = len;
rcvbuf = calloc(1, len);
if (rcvbuf == NULL)
goto eexit;
rcvhdr.msg_name = &from;
rcvhdr.msg_namelen = sizeof(from);
rcvhdr.msg_iov = rcviov;
rcvhdr.msg_iovlen = 1;
rcvhdr.msg_control = rcvbuf;
rcvhdr.msg_controllen = len;
rcviov[0].iov_base = ansbuf;
rcviov[0].iov_len = sizeof(ansbuf);
return sock;
eexit:
close(sock);
sock = -1;
free(sndbuf);
sndbuf = NULL;
free(rcvbuf);
rcvbuf = NULL;
return -1;
}
static int
ipv6nd_naopen(void)
{
static int naopen = 0;
struct icmp6_filter unspec_filt;
#ifdef IPV6_SEND_DAD
union {
struct sockaddr sa;
struct sockaddr_in6 sin;
} su;
#endif
if (naopen)
return sock;
ICMP6_FILTER_SETBLOCKALL(&unspec_filt);
#ifdef IPV6_SEND_DAD
/* We send DAD requests from the unspecified address. */
unspec_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (unspec_sock == -1)
return -1;
if (setsockopt(unspec_sock, IPPROTO_ICMPV6, ICMP6_FILTER,
&unspec_filt, sizeof(unspec_filt)) == -1)
goto eexit;
memset(&su, 0, sizeof(su));
su.sin.sin6_family = AF_INET6;
#ifdef SIN6_LEN
su.sin.sin6_len = sizeof(su.sin);
#endif
if (bind(unspec_sock, &su.sa, sizeof(su.sin)) == -1)
goto eexit;
#endif
if (sock == -1) {
if (ipv6nd_open() == -1)
goto eexit;
eloop_event_add(sock, ipv6nd_handledata, NULL);
}
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt);
if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
&filt, sizeof(filt)) == -1)
goto eexit;
#ifdef LISTEN_DAD
syslog(LOG_WARNING, "kernel does not report DAD results to userland");
syslog(LOG_WARNING,
"warning listening to duplicated addresses on the wire");
#endif
naopen = sock;
return sock;
eexit:
syslog(LOG_ERR, "%s: %m", __func__);
#ifdef IPV6_SEND_DAD
close(unspec_sock);
unspec_sock = -1;
#endif
return -1;
}
static int
ipv6nd_makersprobe(struct interface *ifp)
{
struct rs_state *state;
struct nd_router_solicit *rs;
struct nd_opt_hdr *nd;
state = RS_STATE(ifp);
free(state->rs);
state->rslen = sizeof(*rs) + ROUNDUP8(ifp->hwlen + 2);
state->rs = calloc(1, state->rslen);
if (state->rs == NULL)
return -1;
rs = (struct nd_router_solicit *)(void *)state->rs;
rs->nd_rs_type = ND_ROUTER_SOLICIT;
rs->nd_rs_code = 0;
rs->nd_rs_cksum = 0;
rs->nd_rs_reserved = 0;
nd = (struct nd_opt_hdr *)(state->rs + sizeof(*rs));
nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd->nd_opt_len = (ROUNDUP8(ifp->hwlen + 2)) >> 3;
memcpy(nd + 1, ifp->hwaddr, ifp->hwlen);
return 0;
}
static void
ipv6nd_sendrsprobe(void *arg)
{
struct interface *ifp = arg;
struct rs_state *state;
struct sockaddr_in6 dst;
struct cmsghdr *cm;
struct in6_pktinfo pi;
int hoplimit = HOPLIMIT;
if (ipv6_linklocal(ifp) == NULL) {
syslog(LOG_DEBUG,
"%s: delaying Router Soliciation for LL address",
ifp->name);
ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp);
return;
}
dst = allrouters;
dst.sin6_scope_id = ifp->index;
state = RS_STATE(ifp);
sndhdr.msg_name = (caddr_t)&dst;
sndhdr.msg_iov[0].iov_base = state->rs;
sndhdr.msg_iov[0].iov_len = state->rslen;
/* Set the outbound interface */
cm = CMSG_FIRSTHDR(&sndhdr);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(pi));
memset(&pi, 0, sizeof(pi));
pi.ipi6_ifindex = ifp->index;
memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
/* Hop limit */
cm = CMSG_NXTHDR(&sndhdr, cm);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
syslog(LOG_DEBUG, "%s: sending Router Solicitation", ifp->name);
if (sendmsg(sock, &sndhdr, 0) == -1) {
syslog(LOG_ERR, "%s: %s: sendmsg: %m", ifp->name, __func__);
ipv6nd_drop(ifp);
ifp->options->options &= ~(DHCPCD_IPV6 | DHCPCD_IPV6RS);
return;
}
if (state->rsprobes++ < MAX_RTR_SOLICITATIONS)
eloop_timeout_add_sec(RTR_SOLICITATION_INTERVAL,
ipv6nd_sendrsprobe, ifp);
else
syslog(LOG_WARNING, "%s: no IPv6 Routers available", ifp->name);
}
static void
ipv6nd_free_opts(struct ra *rap)
{
struct ra_opt *rao;
while ((rao = TAILQ_FIRST(&rap->options))) {
TAILQ_REMOVE(&rap->options, rao, next);
free(rao->option);
free(rao);
}
}
int
ipv6nd_addrexists(const struct ipv6_addr *addr)
{
struct ra *rap;
struct ipv6_addr *ap;
TAILQ_FOREACH(rap, &ipv6_routers, next) {
TAILQ_FOREACH(ap, &rap->addrs, next) {
if (addr == NULL) {
if ((ap->flags &
(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED)) ==
(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED))
return 1;
} else if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr->addr))
return 1;
}
}
return 0;
}
void ipv6nd_freedrop_ra(struct ra *rap, int drop)
{
eloop_timeout_delete(NULL, rap->iface);
eloop_timeout_delete(NULL, rap);
if (!drop)
TAILQ_REMOVE(&ipv6_routers, rap, next);
ipv6_freedrop_addrs(&rap->addrs, drop, NULL);
ipv6nd_free_opts(rap);
free(rap->data);
free(rap->ns);
free(rap);
}
ssize_t
ipv6nd_free(struct interface *ifp)
{
struct rs_state *state;
struct ra *rap, *ran;
ssize_t n;
state = RS_STATE(ifp);
if (state) {
free(state->rs);
free(state);
ifp->if_data[IF_DATA_IPV6ND] = NULL;
}
n = 0;
TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) {
if (rap->iface == ifp) {
ipv6nd_free_ra(rap);
n++;
}
}
return n;
}
static int
rtpref(struct ra *rap)
{
switch (rap->flags & ND_RA_FLAG_RTPREF_MASK) {
case ND_RA_FLAG_RTPREF_HIGH:
return (RTPREF_HIGH);
case ND_RA_FLAG_RTPREF_MEDIUM:
case ND_RA_FLAG_RTPREF_RSV:
return (RTPREF_MEDIUM);
case ND_RA_FLAG_RTPREF_LOW:
return (RTPREF_LOW);
default:
syslog(LOG_ERR, "rtpref: impossible RA flag %x", rap->flags);
return (RTPREF_INVALID);
}
/* NOTREACHED */
}
static void
add_router(struct ra *router)
{
struct ra *rap;
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (router->iface->metric < rap->iface->metric ||
(router->iface->metric == rap->iface->metric &&
rtpref(router) > rtpref(rap)))
{
TAILQ_INSERT_BEFORE(rap, router, next);
return;
}
}
TAILQ_INSERT_TAIL(&ipv6_routers, router, next);
}
static void
ipv6nd_scriptrun(struct ra *rap)
{
int hasdns;
struct ipv6_addr *ap;
const struct ra_opt *rao;
/* If all addresses have completed DAD run the script */
TAILQ_FOREACH(ap, &rap->addrs, next) {
if ((ap->flags & (IPV6_AF_ONLINK | IPV6_AF_AUTOCONF)) ==
(IPV6_AF_ONLINK | IPV6_AF_AUTOCONF))
{
if (!(ap->flags & IPV6_AF_DADCOMPLETED) &&
ipv6_findaddr(ap->iface, &ap->addr))
ap->flags |= IPV6_AF_DADCOMPLETED;
if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) {
syslog(LOG_DEBUG,
"%s: waiting for Router Advertisement"
" DAD to complete",
rap->iface->name);
return;
}
}
}
/* If we don't require RDNSS then set hasdns = 1 so we fork */
if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS))
hasdns = 1;
else {
hasdns = 0;
TAILQ_FOREACH(rao, &rap->options, next) {
if (rao->type == ND_OPT_RDNSS &&
rao->option &&
timerisset(&rao->expire))
{
hasdns = 1;
break;
}
}
}
script_runreason(rap->iface, "ROUTERADVERT");
if (hasdns)
daemonise();
#if 0
else if (options & DHCPCD_DAEMONISE &&
!(options & DHCPCD_DAEMONISED) && new_data)
syslog(LOG_WARNING,
"%s: did not fork due to an absent"
" RDNSS option in the RA",
ifp->name);
}
#endif
}
static void
ipv6nd_dadcallback(void *arg)
{
struct ipv6_addr *ap = arg, *rapap;
struct interface *ifp;
struct ra *rap;
int wascompleted, found;
wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED);
ipv6nd_cancelprobeaddr(ap);
ap->flags |= IPV6_AF_DADCOMPLETED;
if (ap->flags & IPV6_AF_DUPLICATED)
/* No idea what how to try and make another address :( */
syslog(LOG_WARNING, "%s: DAD detected %s",
ap->iface->name, ap->saddr);
#ifdef IPV6_SEND_DAD
else
ipv6_addaddr(ap);
#endif
if (!wascompleted) {
ifp = ap->iface;
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (rap->iface != ifp)
continue;
wascompleted = 1;
found = 0;
TAILQ_FOREACH(rapap, &rap->addrs, next) {
if (rapap->flags & IPV6_AF_AUTOCONF &&
(rapap->flags & IPV6_AF_DADCOMPLETED) == 0)
{
wascompleted = 0;
break;
}
if (rapap == ap)
found = 1;
}
if (wascompleted && found && rap->lifetime) {
syslog(LOG_DEBUG,
"%s: Router Advertisement DAD completed",
rap->iface->name);
ipv6nd_scriptrun(rap);
}
}
}
}
static void
ipv6nd_handlera(struct interface *ifp, struct icmp6_hdr *icp, ssize_t len)
{
ssize_t l, m, n, olen;
struct nd_router_advert *nd_ra;
struct nd_opt_prefix_info *pi;
struct nd_opt_mtu *mtu;
struct nd_opt_rdnss *rdnss;
struct nd_opt_dnssl *dnssl;
uint32_t lifetime, mtuv;
uint8_t *p, *op;
struct in6_addr addr;
char buf[INET6_ADDRSTRLEN];
const char *cbp;
struct ra *rap;
struct nd_opt_hdr *ndo;
struct ra_opt *rao;
struct ipv6_addr *ap;
char *opt, *tmp;
struct timeval expire;
uint8_t new_rap, new_data;
if ((size_t)len < sizeof(struct nd_router_advert)) {
syslog(LOG_ERR, "IPv6 RA packet too short from %s", sfrom);
return;
}
if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) {
syslog(LOG_ERR, "RA from non local address %s", sfrom);
return;
}
if (ifp == NULL) {
#ifdef DEBUG_RS
syslog(LOG_DEBUG, "RA for unexpected interface from %s", sfrom);
#endif
return;
}
if (!(ifp->options->options & DHCPCD_IPV6RS)) {
#ifdef DEBUG_RS
syslog(LOG_DEBUG, "%s: unexpected RA from %s",
ifp->name, sfrom);
#endif
return;
}
/* We could recieve a RA before we sent a RS*/
if (ipv6_linklocal(ifp) == NULL) {
#ifdef DEBUG_RS
syslog(LOG_DEBUG, "%s: received RA from %s (no link-local)",
ifp->name, sfrom);
#endif
return;
}
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (ifp == rap->iface &&
memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr)) == 0)
break;
}
nd_ra = (struct nd_router_advert *)icp;
/* Don't bother doing anything if we don't know about a router
* expiring */
if ((rap == NULL || rap->lifetime == 0)
&& nd_ra->nd_ra_router_lifetime == 0)
return;
/* We don't want to spam the log with the fact we got an RA every
* 30 seconds or so, so only spam the log if it's different. */
if (rap == NULL || (rap->data_len != len ||
memcmp(rap->data, (unsigned char *)icp, rap->data_len) != 0))
{
if (rap) {
free(rap->data);
rap->data_len = 0;
free(rap->ns);
rap->ns = NULL;
rap->nslen = 0;
}
new_data = 1;
} else
new_data = 0;
if (new_data || ifp->options->options & DHCPCD_DEBUG)
syslog(LOG_INFO, "%s: Router Advertisement from %s",
ifp->name, sfrom);
if (rap == NULL) {
rap = calloc(1, sizeof(*rap));
if (rap == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
return;
}
rap->iface = ifp;
memcpy(rap->from.s6_addr, from.sin6_addr.s6_addr,
sizeof(rap->from.s6_addr));
strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom));
TAILQ_INIT(&rap->addrs);
TAILQ_INIT(&rap->options);
new_rap = 1;
} else
new_rap = 0;
if (rap->data_len == 0) {
rap->data = malloc(len);
if (rap->data == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
if (new_rap)
free(rap);
return;
}
memcpy(rap->data, icp, len);
rap->data_len = len;
}
get_monotonic(&rap->received);
rap->flags = nd_ra->nd_ra_flags_reserved;
if (new_rap == 0 && rap->lifetime == 0)
syslog(LOG_WARNING, "%s: %s router available",
ifp->name, rap->sfrom);
rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime);
if (nd_ra->nd_ra_reachable) {
rap->reachable = ntohl(nd_ra->nd_ra_reachable);
if (rap->reachable > MAX_REACHABLE_TIME)
rap->reachable = 0;
}
if (nd_ra->nd_ra_retransmit)
rap->retrans = ntohl(nd_ra->nd_ra_retransmit);
if (rap->lifetime)
rap->expired = 0;
len -= sizeof(struct nd_router_advert);
p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
olen = 0;
lifetime = ~0U;
for (olen = 0; len > 0; p += olen, len -= olen) {
if ((size_t)len < sizeof(struct nd_opt_hdr)) {
syslog(LOG_ERR, "%s: Short option", ifp->name);
break;
}
ndo = (struct nd_opt_hdr *)p;
olen = ndo->nd_opt_len * 8 ;
if (olen == 0) {
syslog(LOG_ERR, "%s: zero length option", ifp->name);
break;
}
if (olen > len) {
syslog(LOG_ERR,
"%s: Option length exceeds message", ifp->name);
break;
}
opt = NULL;
switch (ndo->nd_opt_type) {
case ND_OPT_PREFIX_INFORMATION:
pi = (struct nd_opt_prefix_info *)(void *)ndo;
if (pi->nd_opt_pi_len != 4) {
syslog(LOG_ERR,
"%s: invalid option len for prefix",
ifp->name);
break;
}
if (pi->nd_opt_pi_prefix_len > 128) {
syslog(LOG_ERR, "%s: invalid prefix len",
ifp->name);
break;
}
if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) ||
IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix))
{
syslog(LOG_ERR,
"%s: invalid prefix in RA", ifp->name);
break;
}
if (ntohl(pi->nd_opt_pi_preferred_time) >
ntohl(pi->nd_opt_pi_valid_time))
{
syslog(LOG_ERR,
"%s: pltime > vltime", ifp->name);
break;
}
TAILQ_FOREACH(ap, &rap->addrs, next)
if (ap->prefix_len ==pi->nd_opt_pi_prefix_len &&
memcmp(ap->prefix.s6_addr,
pi->nd_opt_pi_prefix.s6_addr,
sizeof(ap->prefix.s6_addr)) == 0)
break;
if (ap == NULL) {
if (!(pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_AUTO) &&
!(pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK))
break;
ap = calloc(1, sizeof(*ap));
if (ap == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
break;
}
ap->iface = rap->iface;
ap->flags = IPV6_AF_NEW;
ap->prefix_len = pi->nd_opt_pi_prefix_len;
memcpy(ap->prefix.s6_addr,
pi->nd_opt_pi_prefix.s6_addr,
sizeof(ap->prefix.s6_addr));
if (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_AUTO)
{
ap->flags |= IPV6_AF_AUTOCONF;
ipv6_makeaddr(&ap->addr, ifp,
&ap->prefix,
pi->nd_opt_pi_prefix_len);
cbp = inet_ntop(AF_INET6,
ap->addr.s6_addr,
ntopbuf, INET6_ADDRSTRLEN);
if (cbp)
snprintf(ap->saddr,
sizeof(ap->saddr),
"%s/%d",
cbp, ap->prefix_len);
else
ap->saddr[0] = '\0';
} else {
memset(&ap->addr, 0, sizeof(ap->addr));
ap->saddr[0] = '\0';
}
ap->dadcallback = ipv6nd_dadcallback;
TAILQ_INSERT_TAIL(&rap->addrs, ap, next);
}
if (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK)
ap->flags |= IPV6_AF_ONLINK;
ap->prefix_vltime =
ntohl(pi->nd_opt_pi_valid_time);
ap->prefix_pltime =
ntohl(pi->nd_opt_pi_preferred_time);
ap->nsprobes = 0;
if (opt) {
l = strlen(opt);
tmp = realloc(opt,
l + strlen(ap->saddr) + 2);
if (tmp) {
opt = tmp;
opt[l] = ' ';
strcpy(opt + l + 1, ap->saddr);
}
} else
opt = strdup(ap->saddr);
lifetime = ap->prefix_vltime;
break;
case ND_OPT_MTU:
mtu = (struct nd_opt_mtu *)(void *)p;
mtuv = ntohl(mtu->nd_opt_mtu_mtu);
if (mtuv < IPV6_MMTU) {
syslog(LOG_ERR, "%s: invalid MTU %d",
ifp->name, mtuv);
break;
}
rap->mtu = mtuv;
snprintf(buf, sizeof(buf), "%d", mtuv);
opt = strdup(buf);
break;
case ND_OPT_RDNSS:
rdnss = (struct nd_opt_rdnss *)p;
lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime);
op = (uint8_t *)ndo;
op += offsetof(struct nd_opt_rdnss,
nd_opt_rdnss_lifetime);
op += sizeof(rdnss->nd_opt_rdnss_lifetime);
l = 0;
for (n = ndo->nd_opt_len - 1; n > 1; n -= 2,
op += sizeof(addr.s6_addr))
{
m = ipv6_printaddr(NULL, 0, op, ifp->name);
if (m != -1)
l += m + 1;
}
op = (uint8_t *)ndo;
op += offsetof(struct nd_opt_rdnss,
nd_opt_rdnss_lifetime);
op += sizeof(rdnss->nd_opt_rdnss_lifetime);
tmp = opt = malloc(l);
if (opt) {
for (n = ndo->nd_opt_len - 1; n > 1; n -= 2,
op += sizeof(addr.s6_addr))
{
m = ipv6_printaddr(tmp, l, op,
ifp->name);
if (m != -1) {
l -= (m + 1);
tmp += m;
*tmp++ = ' ';
}
}
if (tmp != opt)
(*--tmp) = '\0';
else
*opt = '\0';
}
break;
case ND_OPT_DNSSL:
dnssl = (struct nd_opt_dnssl *)p;
lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime);
op = p + offsetof(struct nd_opt_dnssl,
nd_opt_dnssl_lifetime);
op += sizeof(dnssl->nd_opt_dnssl_lifetime);
n = (dnssl->nd_opt_dnssl_len - 1) * 8;
l = decode_rfc3397(NULL, 0, n, op);
if (l < 1) {
syslog(LOG_ERR, "%s: invalid DNSSL option",
ifp->name);
} else {
tmp = malloc(l);
if (tmp) {
decode_rfc3397(tmp, l, n, op);
n = print_string(NULL, 0,
l - 1, (const uint8_t *)tmp);
opt = malloc(n);
if (opt)
print_string(opt, n,
l - 1,
(const uint8_t *)tmp);
free(tmp);
}
}
break;
default:
continue;
}
if (opt == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
continue;
}
TAILQ_FOREACH(rao, &rap->options, next) {
if (rao->type == ndo->nd_opt_type &&
strcmp(rao->option, opt) == 0)
break;
}
if (lifetime == 0) {
if (rao) {
TAILQ_REMOVE(&rap->options, rao, next);
free(rao->option);
free(rao);
}
free(opt);
continue;
}
if (rao == NULL) {
rao = malloc(sizeof(*rao));
if (rao == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
continue;
}
rao->type = ndo->nd_opt_type;
rao->option = opt;
TAILQ_INSERT_TAIL(&rap->options, rao, next);
} else
free(opt);
if (lifetime == ~0U)
timerclear(&rao->expire);
else {
expire.tv_sec = lifetime;
expire.tv_usec = 0;
timeradd(&rap->received, &expire, &rao->expire);
}
}
if (new_rap)
add_router(rap);
if (options & DHCPCD_TEST) {
script_runreason(ifp, "TEST");
goto handle_flag;
}
ipv6nd_probeaddrs(&rap->addrs);
ipv6_buildroutes();
/* We will get run by the expire function */
if (rap->lifetime)
ipv6nd_scriptrun(rap);
eloop_timeout_delete(NULL, ifp);
eloop_timeout_delete(NULL, rap); /* reachable timer */
/* If we're owning the RA then we need to try and ensure the
* router is actually reachable */
if (ifp->options->options & DHCPCD_IPV6RA_OWN ||
ifp->options->options & DHCPCD_IPV6RA_OWN_DEFAULT)
{
rap->nsprobes = 0;
if (rap->lifetime)
ipv6nd_proberouter(rap);
}
handle_flag:
if (rap->flags & ND_RA_FLAG_MANAGED) {
if (rap->lifetime && new_data &&
dhcp6_start(ifp, DH6S_INIT) == -1)
syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name);
} else if (rap->flags & ND_RA_FLAG_OTHER) {
if (rap->lifetime && new_data &&
dhcp6_start(ifp, DH6S_INFORM) == -1)
syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name);
} else {
if (rap->lifetime && new_data)
syslog(LOG_DEBUG, "%s: No DHCPv6 instruction in RA",
ifp->name);
if (options & DHCPCD_TEST)
exit(EXIT_SUCCESS);
}
/* Expire should be called last as the rap object could be destroyed */
ipv6nd_expirera(ifp);
}
int
ipv6nd_has_ra(const struct interface *ifp)
{
const struct ra *rap;
TAILQ_FOREACH(rap, &ipv6_routers, next)
if (rap->iface == ifp)
return 1;
return 0;
}
ssize_t
ipv6nd_env(char **env, const char *prefix, const struct interface *ifp)
{
ssize_t l;
size_t len;
struct timeval now;
const struct ra *rap;
const struct ra_opt *rao;
int i;
char buffer[32];
const char *optn;
char **pref, **mtu, **rdnss, **dnssl, ***var, *new;
i = 0;
l = 0;
get_monotonic(&now);
TAILQ_FOREACH(rap, &ipv6_routers, next) {
i++;
if (rap->iface != ifp)
continue;
if (env) {
snprintf(buffer, sizeof(buffer),
"ra%d_from", i);
if (setvar(&env, prefix, buffer, rap->sfrom) == -1)
return -1;
}
l++;
pref = mtu = rdnss = dnssl = NULL;
TAILQ_FOREACH(rao, &rap->options, next) {
if (rao->option == NULL)
continue;
var = NULL;
switch(rao->type) {
case ND_OPT_PREFIX_INFORMATION:
optn = "prefix";
var = &pref;
break;
case ND_OPT_MTU:
optn = "mtu";
var = &mtu;
break;
case ND_OPT_RDNSS:
optn = "rdnss";
var = &rdnss;
break;
case ND_OPT_DNSSL:
optn = "dnssl";
var = &dnssl;
break;
default:
continue;
}
if (*var == NULL) {
*var = env ? env : &new;
l++;
} else if (env) {
/* With single only options, last one takes
* precedence */
if (rao->type == ND_OPT_MTU) {
new = strchr(**var, '=');
if (new == NULL) {
syslog(LOG_ERR, "new is null");
continue;
} else
new++;
len = (new - **var) +
strlen(rao->option) + 1;
if (len > strlen(**var))
new = realloc(**var, len);
else
new = **var;
if (new) {
**var = new;
new = strchr(**var, '=');
if (new)
strcpy(new + 1,
rao->option);
else
syslog(LOG_ERR,
"new is null");
}
continue;
}
new = realloc(**var,
strlen(**var) + 1 +
strlen(rao->option) + 1);
if (new == NULL)
return -1;
**var = new;
new += strlen(new);
*new++ = ' ';
strcpy(new, rao->option);
continue;
}
if (env) {
snprintf(buffer, sizeof(buffer),
"ra%d_%s", i, optn);
if (setvar(&env, prefix, buffer, rao->option)
== -1)
return -1;
}
}
}
if (env) {
if (setvard(&env, prefix, "ra_count", i) == -1)
return -1;
}
l++;
return l;
}
void
ipv6nd_handleifa(int cmd, const char *ifname,
const struct in6_addr *addr, int flags)
{
struct ra *rap;
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (strcmp(rap->iface->name, ifname))
continue;
ipv6_handleifa_addrs(cmd, &rap->addrs, addr, flags);
}
}
void
ipv6nd_expirera(void *arg)
{
struct interface *ifp;
struct ra *rap, *ran;
struct ra_opt *rao, *raon;
struct timeval now, lt, expire, next;
int expired, valid;
ifp = arg;
get_monotonic(&now);
expired = 0;
timerclear(&next);
TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) {
if (rap->iface != ifp)
continue;
lt.tv_sec = rap->lifetime;
lt.tv_usec = 0;
timeradd(&rap->received, &lt, &expire);
if (rap->lifetime == 0 || timercmp(&now, &expire, >)) {
valid = 0;
if (!rap->expired) {
syslog(LOG_WARNING,
"%s: %s: router expired",
ifp->name, rap->sfrom);
rap->expired = expired = 1;
ipv6nd_cancelproberouter(rap);
}
} else {
valid = 1;
timersub(&expire, &now, &lt);
if (!timerisset(&next) || timercmp(&next, &lt, >))
next = lt;
}
/* Addresses are expired in ipv6ns_probeaddrs
* so that DHCPv6 addresses can be removed also. */
TAILQ_FOREACH_SAFE(rao, &rap->options, next, raon) {
if (rap->expired) {
switch(rao->type) {
case ND_OPT_RDNSS: /* FALLTHROUGH */
case ND_OPT_DNSSL:
/* RFC6018 end of section 5.2 states
* that if tha RA has a lifetime of 0
* then we should expire these
* options */
TAILQ_REMOVE(&rap->options, rao, next);
expired = 1;
free(rao->option);
free(rao);
continue;
}
}
if (!timerisset(&rao->expire))
continue;
if (timercmp(&now, &rao->expire, >)) {
/* Expired prefixes are logged above */
if (rao->type != ND_OPT_PREFIX_INFORMATION)
syslog(LOG_WARNING,
"%s: %s: expired option %d",
ifp->name, rap->sfrom, rao->type);
TAILQ_REMOVE(&rap->options, rao, next);
expired = 1;
free(rao->option);
free(rao);
continue;
}
valid = 1;
timersub(&rao->expire, &now, &lt);
if (!timerisset(&next) || timercmp(&next, &lt, >))
next = lt;
}
/* No valid lifetimes are left on the RA, so we might
* as well punt it. */
if (!valid && TAILQ_FIRST(&rap->addrs) == NULL)
ipv6nd_free_ra(rap);
}
if (timerisset(&next))
eloop_timeout_add_tv(&next, ipv6nd_expirera, ifp);
if (expired) {
ipv6_buildroutes();
script_runreason(ifp, "ROUTERADVERT");
}
}
void
ipv6nd_drop(struct interface *ifp)
{
struct ra *rap;
int expired = 0;
TAILQ_HEAD(rahead, ra) rtrs;
eloop_timeout_delete(NULL, ifp);
TAILQ_INIT(&rtrs);
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (rap->iface == ifp) {
rap->expired = expired = 1;
TAILQ_REMOVE(&ipv6_routers, rap, next);
TAILQ_INSERT_TAIL(&rtrs, rap, next);
}
}
if (expired) {
while ((rap = TAILQ_FIRST(&rtrs))) {
TAILQ_REMOVE(&rtrs, rap, next);
ipv6nd_drop_ra(rap);
}
ipv6_buildroutes();
if ((ifp->options->options &
(DHCPCD_EXITING | DHCPCD_PERSISTENT)) !=
(DHCPCD_EXITING | DHCPCD_PERSISTENT))
script_runreason(ifp, "ROUTERADVERT");
}
}
static void
ipv6nd_unreachable(void *arg)
{
struct ra *rap = arg;
struct timeval tv;
/* We could add an unreachable flag and persist the information,
* but that is more effort than it's probably worth. */
syslog(LOG_WARNING, "%s: %s is unreachable, expiring it",
rap->iface->name, rap->sfrom);
rap->expired = 1;
ipv6_buildroutes();
script_runreason(rap->iface, "ROUTERADVERT"); /* XXX not RA */
/* We should still test if it's reachable or not so
* incase it comes back to life and it's preferable. */
if (rap->reachable) {
ms_to_tv(&tv, rap->reachable);
} else {
tv.tv_sec = REACHABLE_TIME;
tv.tv_usec = 0;
}
eloop_timeout_add_tv(&tv, ipv6nd_proberouter, rap);
}
#ifdef LISTEN_DAD
void
ipv6nd_cancelprobeaddr(struct ipv6_addr *ap)
{
eloop_timeout_delete(ipv6nd_probeaddr, ap);
if (ap->dadcallback)
eloop_timeout_delete(ap->dadcallback, ap);
}
#endif
void
ipv6nd_probeaddr(void *arg)
{
struct ipv6_addr *ap = arg;
#ifdef IPV6_SEND_DAD
struct nd_neighbor_solicit *ns;
struct nd_opt_hdr *nd;
struct sockaddr_in6 dst;
struct cmsghdr *cm;
struct in6_pktinfo pi;
int hoplimit = HOPLIMIT;
#else
#ifdef LISTEN_DAD
struct timeval tv, rtv;
struct timeval mtv;
int i;
#endif
#endif
if (ap->dadcallback &&
((ap->flags & IPV6_AF_NEW) == 0 ||
ap->nsprobes >= ap->iface->options->dadtransmits))
{
#ifdef IPV6_SEND_DAD
ap->dadcallback(ap);
#else
if (!(ap->flags & IPV6_AF_AUTOCONF) ||
ap->iface->options->options & DHCPCD_IPV6RA_OWN)
ipv6_addaddr(ap);
#endif
return;
}
if (ipv6nd_naopen() == -1)
return;
ap->flags &= ~IPV6_AF_DADCOMPLETED;
#ifdef IPV6_SEND_DAD
if (!ap->ns) {
ap->nslen = sizeof(*ns) + ROUNDUP8(ap->iface->hwlen + 2);
ap->ns = calloc(1, ap->nslen);
if (ap->ns == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
return;
}
ns = (struct nd_neighbor_solicit *)(void *)ap->ns;
ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
//ns->nd_ns_cksum = 0;
//ns->nd_ns_code = 0;
//ns->nd_ns_reserved = 0;
ns->nd_ns_target = ap->addr;
nd = (struct nd_opt_hdr *)(ap->ns + sizeof(*ns));
nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd->nd_opt_len = (ROUNDUP8(ap->iface->hwlen + 2)) >> 3;
memcpy(nd + 1, ap->iface->hwaddr, ap->iface->hwlen);
}
memset(&dst, 0, sizeof(dst));
dst.sin6_family = AF_INET6;
#ifdef SIN6_LEN
dst.sin6_len = sizeof(dst);
#endif
dst.sin6_addr.s6_addr16[0] = IPV6_ADDR_INT16_MLL;
dst.sin6_addr.s6_addr16[1] = 0;
dst.sin6_addr.s6_addr32[1] = 0;
dst.sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_ONE;
dst.sin6_addr.s6_addr32[3] = ap->addr.s6_addr32[3];
dst.sin6_addr.s6_addr[12] = 0xff;
//memcpy(&dst.sin6_addr, &ap->addr, sizeof(dst.sin6_addr));
dst.sin6_scope_id = ap->iface->index;
sndhdr.msg_name = (caddr_t)&dst;
sndhdr.msg_iov[0].iov_base = ap->ns;
sndhdr.msg_iov[0].iov_len = ap->nslen;
/* Set the outbound interface */
cm = CMSG_FIRSTHDR(&sndhdr);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(pi));
memset(&pi, 0, sizeof(pi));
pi.ipi6_ifindex = ap->iface->index;
memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
/* Hop limit */
cm = CMSG_NXTHDR(&sndhdr, cm);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
#ifdef DEBUG_NS
syslog(LOG_INFO, "%s: sending IPv6 NS for %s",
ap->iface->name, ap->saddr);
if (ap->dadcallback == NULL)
syslog(LOG_WARNING, "%s: no callback!", ap->iface->name);
#endif
if (sendmsg(unspec_sock, &sndhdr, 0) == -1) {
syslog(LOG_ERR, "%s: %s: sendmsg: %m",
ap->iface->name, __func__);
return;
}
if (ap->dadcallback) {
ms_to_tv(&tv, RETRANS_TIMER);
ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
timeradd(&tv, &rtv, &tv);
rtv.tv_sec = 0;
rtv.tv_usec = arc4random() %
(MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U);
timeradd(&tv, &rtv, &tv);
eloop_timeout_add_tv(&tv,
++(ap->nsprobes) < ap->iface->options->dadtransmits ?
ipv6nd_probeaddr : ap->dadcallback,
ap);
}
#else /* IPV6_SEND_DAD */
if (!(ap->flags & IPV6_AF_AUTOCONF) ||
ap->iface->options->options & DHCPCD_IPV6RA_OWN)
ipv6_addaddr(ap);
#ifdef LISTEN_DAD
/* Let the kernel handle DAD.
* We don't know the timings, so just wait for the max */
if (ap->dadcallback) {
mtv.tv_sec = 0;
mtv.tv_usec = 0;
for (i = 0; i < ap->iface->options->dadtransmits; i++) {
ms_to_tv(&tv, RETRANS_TIMER);
ms_to_tv(&rtv, MAX_RANDOM_FACTOR);
timeradd(&tv, &rtv, &tv);
timeradd(&mtv, &tv, &mtv);
}
eloop_timeout_add_tv(&mtv, ap->dadcallback, ap);
}
#endif
#endif /* IPV6_SEND_DAD */
}
ssize_t
ipv6nd_probeaddrs(struct ipv6_addrhead *addrs)
{
struct ipv6_addr *ap, *apn;
ssize_t i;
i = 0;
TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
if (ap->prefix_vltime == 0) {
TAILQ_REMOVE(addrs, ap, next);
if (ap->flags & IPV6_AF_ADDED) {
syslog(LOG_INFO, "%s: deleting address %s",
ap->iface->name, ap->saddr);
i++;
if (!IN6_IS_ADDR_UNSPECIFIED(&ap->addr) &&
del_address6(ap) == -1 &&
errno != EADDRNOTAVAIL && errno != ENXIO)
syslog(LOG_ERR, "del_address6 %m");
}
if (ap->dadcallback)
eloop_q_timeout_delete(0, NULL,
ap->dadcallback);
free(ap);
} else if (!IN6_IS_ADDR_UNSPECIFIED(&ap->addr) &&
!(ap->flags & IPV6_AF_DELEGATED))
{
ipv6nd_probeaddr(ap);
if (ap->flags & IPV6_AF_NEW)
i++;
}
}
return i;
}
void
ipv6nd_proberouter(void *arg)
{
struct ra *rap = arg;
struct nd_neighbor_solicit *ns;
struct nd_opt_hdr *nd;
struct sockaddr_in6 dst;
struct cmsghdr *cm;
struct in6_pktinfo pi;
int hoplimit = HOPLIMIT;
struct timeval tv, rtv;
if (ipv6nd_naopen() == -1)
return;
if (!rap->ns) {
rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2);
rap->ns = calloc(1, rap->nslen);
if (rap->ns == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
return;
}
ns = (struct nd_neighbor_solicit *)(void *)rap->ns;
ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
//ns->nd_ns_cksum = 0;
//ns->nd_ns_code = 0;
//ns->nd_ns_reserved = 0;
ns->nd_ns_target = rap->from;
nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns));
nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3;
memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen);
}
memset(&dst, 0, sizeof(dst));
dst.sin6_family = AF_INET6;
#ifdef SIN6_LEN
dst.sin6_len = sizeof(dst);
#endif
memcpy(&dst.sin6_addr, &rap->from, sizeof(dst.sin6_addr));
dst.sin6_scope_id = rap->iface->index;
sndhdr.msg_name = (caddr_t)&dst;
sndhdr.msg_iov[0].iov_base = rap->ns;
sndhdr.msg_iov[0].iov_len = rap->nslen;
/* Set the outbound interface */
cm = CMSG_FIRSTHDR(&sndhdr);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_PKTINFO;
cm->cmsg_len = CMSG_LEN(sizeof(pi));
memset(&pi, 0, sizeof(pi));
pi.ipi6_ifindex = rap->iface->index;
memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
/* Hop limit */
cm = CMSG_NXTHDR(&sndhdr, cm);
cm->cmsg_level = IPPROTO_IPV6;
cm->cmsg_type = IPV6_HOPLIMIT;
cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
#ifdef DEBUG_NS
syslog(LOG_INFO, "%s: sending IPv6 NS for %s",
rap->iface->name, rap->sfrom);
#endif
if (sendmsg(sock, &sndhdr, 0) == -1) {
syslog(LOG_ERR, "%s: %s: sendmsg: %m",
rap->iface->name, __func__);
return;
}
ms_to_tv(&tv, rap->retrans == 0 ? RETRANS_TIMER : rap->retrans);
ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
timeradd(&tv, &rtv, &tv);
rtv.tv_sec = 0;
rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR_U -MIN_RANDOM_FACTOR_U);
timeradd(&tv, &rtv, &tv);
eloop_timeout_add_tv(&tv, ipv6nd_proberouter, rap);
if (rap->nsprobes++ == 0)
eloop_timeout_add_sec(DELAY_FIRST_PROBE_TIME,
ipv6nd_unreachable, rap);
}
void
ipv6nd_cancelproberouter(struct ra *rap)
{
eloop_timeout_delete(ipv6nd_proberouter, rap);
eloop_timeout_delete(ipv6nd_unreachable, rap);
}
/* ARGSUSED */
static void
ipv6nd_handlena(struct interface *ifp, struct icmp6_hdr *icp, ssize_t len)
{
struct nd_neighbor_advert *nd_na;
struct ra *rap;
int is_router, is_solicited;
#ifdef DEBUG_NS
int found;
#endif
struct timeval tv;
#ifdef LISTEN_DAD
struct dhcp6_state *d6state;
struct ipv6_addr *ap;
#endif
if ((size_t)len < sizeof(struct nd_neighbor_advert)) {
syslog(LOG_ERR, "IPv6 NA packet too short from %s", sfrom);
return;
}
if (ifp == NULL) {
#ifdef DEBUG_NS
syslog(LOG_DEBUG, "NA for unexpected interface from %s", sfrom);
#endif
return;
}
nd_na = (struct nd_neighbor_advert *)icp;
is_router = nd_na->nd_na_flags_reserved & ND_NA_FLAG_ROUTER;
is_solicited = nd_na->nd_na_flags_reserved & ND_NA_FLAG_SOLICITED;
if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) {
syslog(LOG_ERR, "%s: NA for multicast address from %s",
ifp->name, sfrom);
return;
}
#ifdef DEBUG_NS
found = 0;
#endif
TAILQ_FOREACH(rap, &ipv6_routers, next) {
if (rap->iface != ifp)
continue;
if (memcmp(rap->from.s6_addr, nd_na->nd_na_target.s6_addr,
sizeof(rap->from.s6_addr)) == 0)
break;
#ifdef LISTEN_DAD
TAILQ_FOREACH(ap, &rap->addrs, next) {
if (memcmp(ap->addr.s6_addr,
nd_na->nd_na_target.s6_addr,
sizeof(ap->addr.s6_addr)) == 0)
{
ap->flags |= IPV6_AF_DUPLICATED;
if (ap->dadcallback)
ap->dadcallback(ap);
#ifdef DEBUG_NS
found++;
#endif
}
}
#endif
}
if (rap == NULL) {
#ifdef LISTEN_DAD
d6state = D6_STATE(ifp);
if (d6state) {
TAILQ_FOREACH(ap, &d6state->addrs, next) {
if (memcmp(ap->addr.s6_addr,
nd_na->nd_na_target.s6_addr,
sizeof(ap->addr.s6_addr)) == 0)
{
ap->flags |= IPV6_AF_DUPLICATED;
if (ap->dadcallback)
ap->dadcallback(ap);
#ifdef DEBUG_NS
found++;
#endif
}
}
}
#endif
#ifdef DEBUG_NS
if (found == 0)
syslog(LOG_DEBUG, "%s: unexpected NA from %s",
ifp->name, sfrom);
#endif
return;
}
#ifdef DEBUG_NS
syslog(LOG_DEBUG, "%s: %sNA from %s",
ifp->name, is_solicited ? "solicited " : "", sfrom);
#endif
/* Node is no longer a router, so remove it from consideration */
if (!is_router && !rap->expired) {
syslog(LOG_INFO, "%s: %s is no longer a router",
ifp->name, sfrom);
rap->expired = 1;
ipv6nd_cancelproberouter(rap);
ipv6_buildroutes();
script_runreason(ifp, "ROUTERADVERT");
return;
}
if (is_solicited && is_router && rap->lifetime) {
if (rap->expired) {
rap->expired = 0;
syslog(LOG_INFO, "%s: %s is reachable again",
ifp->name, sfrom);
ipv6_buildroutes();
script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */
}
rap->nsprobes = 0;
if (rap->reachable) {
ms_to_tv(&tv, rap->reachable);
} else {
tv.tv_sec = REACHABLE_TIME;
tv.tv_usec = 0;
}
eloop_timeout_add_tv(&tv, ipv6nd_proberouter, rap);
eloop_timeout_delete(ipv6nd_unreachable, rap);
}
}
/* ARGSUSED */
static void
ipv6nd_handledata(__unused void *arg)
{
ssize_t len;
struct cmsghdr *cm;
int hoplimit;
struct in6_pktinfo pkt;
struct icmp6_hdr *icp;
struct interface *ifp;
len = recvmsg(sock, &rcvhdr, 0);
if (len == -1) {
syslog(LOG_ERR, "recvmsg: %m");
return;
}
sfrom = inet_ntop(AF_INET6, &from.sin6_addr,
ntopbuf, INET6_ADDRSTRLEN);
if ((size_t)len < sizeof(struct icmp6_hdr)) {
syslog(LOG_ERR, "IPv6 ICMP packet too short from %s", sfrom);
return;
}
pkt.ipi6_ifindex = hoplimit = 0;
for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&rcvhdr);
cm;
cm = (struct cmsghdr *)CMSG_NXTHDR(&rcvhdr, cm))
{
if (cm->cmsg_level != IPPROTO_IPV6)
continue;
switch(cm->cmsg_type) {
case IPV6_PKTINFO:
if (cm->cmsg_len == CMSG_LEN(sizeof(pkt)))
memcpy(&pkt, CMSG_DATA(cm), sizeof(pkt));
break;
case IPV6_HOPLIMIT:
if (cm->cmsg_len == CMSG_LEN(sizeof(int)))
memcpy(&hoplimit, CMSG_DATA(cm), sizeof(int));
break;
}
}
if (pkt.ipi6_ifindex == 0 || hoplimit == 0) {
syslog(LOG_ERR,
"IPv6 RA did not contain index or hop limit from %s",
sfrom);
return;
}
TAILQ_FOREACH(ifp, ifaces, next) {
if (ifp->index == (unsigned int)pkt.ipi6_ifindex)
break;
}
icp = (struct icmp6_hdr *)rcvhdr.msg_iov[0].iov_base;
if (icp->icmp6_code == 0) {
switch(icp->icmp6_type) {
case ND_NEIGHBOR_ADVERT:
ipv6nd_handlena(ifp, icp, len);
return;
case ND_ROUTER_ADVERT:
ipv6nd_handlera(ifp, icp, len);
return;
}
}
syslog(LOG_ERR, "invalid IPv6 type %d or code %d from %s",
icp->icmp6_type, icp->icmp6_code, sfrom);
}
int
ipv6nd_startrs(struct interface *ifp)
{
struct rs_state *state;
syslog(LOG_INFO, "%s: soliciting an IPv6 router", ifp->name);
if (sock == -1) {
if (ipv6nd_open() == -1) {
syslog(LOG_ERR, "%s: ipv6nd_open: %m", __func__);
return -1;
}
eloop_event_add(sock, ipv6nd_handledata, NULL);
}
eloop_timeout_delete(NULL, ifp);
state = RS_STATE(ifp);
if (state == NULL) {
ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state));
state = RS_STATE(ifp);
if (state == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
return -1;
}
}
/* Always make a new probe as the underlying hardware
* address could have changed. */
ipv6nd_makersprobe(ifp);
if (state->rs == NULL) {
syslog(LOG_ERR, "%s: ipv6ns_makersprobe: %m", __func__);
return -1;
}
state->rsprobes = 0;
ipv6nd_sendrsprobe(ifp);
return 0;
}