From f2ed769880271654297a4be420f26ab94d39666b Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Thu, 30 Jan 2014 13:29:46 +0100 Subject: DHCPCD(8): Import Import DHCPCD(8) from: http://roy.marples.name/projects/dhcpcd/ The upstream sources can be obtained via: fossil clone http://roy.marples.name/projects/dhcpcd The imported version is 2014-01-29 19:46:44 [6b209507bb]. --- dhcpcd/ipv6nd.c | 1835 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1835 insertions(+) create mode 100644 dhcpcd/ipv6nd.c (limited to 'dhcpcd/ipv6nd.c') diff --git a/dhcpcd/ipv6nd.c b/dhcpcd/ipv6nd.c new file mode 100644 index 00000000..2fd20842 --- /dev/null +++ b/dhcpcd/ipv6nd.c @@ -0,0 +1,1835 @@ +/* + * dhcpcd - DHCP client daemon + * Copyright (c) 2006-2014 Roy Marples + * 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 +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +# define _LINUX_IN6_H +# include +#endif + +#include +#include +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3