mirror of
https://git.rtems.org/rtems-libbsd/
synced 2025-05-12 23:04:55 +08:00

This adds two new buildset configurations: One that leaves out as much features as possible and one that enables all features. For the default configuration WiFi support is now disabled. To disable IPv6 for the minimal configuration, all -DINET6 are eliminated in libbsd.py. They are now replaced by a #ifdef that checks for RTEMS_BSD_MODULE_NETINET6 instead. Close #3351.
1841 lines
44 KiB
C
1841 lines
44 KiB
C
/*
|
|
* dhcpcd - DHCP client daemon
|
|
* Copyright (c) 2006-2014 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.
|
|
*/
|
|
|
|
#ifdef __rtems__
|
|
#include <rtems/bsd/local/opt_inet6.h>
|
|
#endif /* __rtems__ */
|
|
#if defined(__rtems__) && defined(INET6)
|
|
#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"
|
|
|
|
#if defined(LISTEN_DAD) && defined(INET6)
|
|
# warning kernel does not report DAD results to userland
|
|
# warning listening to duplicated addresses on the wire
|
|
#endif
|
|
|
|
/* 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);
|
|
|
|
/*
|
|
* Android ships buggy ICMP6 filter headers.
|
|
* Supply our own until they fix their shit.
|
|
* References:
|
|
* https://android-review.googlesource.com/#/c/58438/
|
|
* http://code.google.com/p/android/issues/original?id=32621&seq=24
|
|
*/
|
|
#ifdef __ANDROID__
|
|
#undef ICMP6_FILTER_WILLPASS
|
|
#undef ICMP6_FILTER_WILLBLOCK
|
|
#undef ICMP6_FILTER_SETPASS
|
|
#undef ICMP6_FILTER_SETBLOCK
|
|
#undef ICMP6_FILTER_SETPASSALL
|
|
#undef ICMP6_FILTER_SETBLOCKALL
|
|
#define ICMP6_FILTER_WILLPASS(type, filterp) \
|
|
((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) == 0)
|
|
#define ICMP6_FILTER_WILLBLOCK(type, filterp) \
|
|
((((filterp)->icmp6_filt[(type) >> 5]) & (1 << ((type) & 31))) != 0)
|
|
#define ICMP6_FILTER_SETPASS(type, filterp) \
|
|
((((filterp)->icmp6_filt[(type) >> 5]) &= ~(1 << ((type) & 31))))
|
|
#define ICMP6_FILTER_SETBLOCK(type, filterp) \
|
|
((((filterp)->icmp6_filt[(type) >> 5]) |= (1 << ((type) & 31))))
|
|
#define ICMP6_FILTER_SETPASSALL(filterp) \
|
|
memset(filterp, 0, sizeof(struct icmp6_filter));
|
|
#define ICMP6_FILTER_SETBLOCKALL(filterp) \
|
|
memset(filterp, 0xff, sizeof(struct icmp6_filter));
|
|
#endif
|
|
|
|
#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 Solicitation 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 receive 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, <, &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, <);
|
|
if (!timerisset(&next) || timercmp(&next, <, >))
|
|
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, <);
|
|
if (!timerisset(&next) || timercmp(&next, <, >))
|
|
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)) {
|
|
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;
|
|
}
|
|
#endif /* defined(__rtems__) && defined(INET6) */
|