/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2015 Roy Marples <roy@marples.name> * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <sys/ioctl.h> #include <sys/param.h> #include <sys/socket.h> #include <net/if.h> #include <net/route.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <errno.h> #include <fcntl.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define ELOOP_QUEUE 3 #include "common.h" #include "dhcpcd.h" #include "dhcp6.h" #include "eloop.h" #include "if.h" #include "ipv6.h" #include "ipv6nd.h" #include "script.h" /* Debugging Router Solicitations is a lot of spam, so disable it */ //#define DEBUG_RS #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 /* Impossible options, so we can easily add extras */ #define _ND_OPT_PREFIX_ADDR 255 + 1 /* 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 // static void ipv6nd_handledata(void *); /* * 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 /* Support older systems with different defines */ #if !defined(IPV6_RECVHOPLIMIT) && defined(IPV6_HOPLIMIT) #define IPV6_RECVHOPLIMIT IPV6_HOPLIMIT #endif #if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) #define IPV6_RECVPKTINFO IPV6_PKTINFO #endif static int ipv6nd_open(struct dhcpcd_ctx *dctx) { struct ipv6_ctx *ctx; int on; struct icmp6_filter filt; ctx = dctx->ipv6; if (ctx->nd_fd != -1) return ctx->nd_fd; #ifdef SOCK_CLOEXEC ctx->nd_fd = socket(PF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); if (ctx->nd_fd == -1) return -1; #else if ((ctx->nd_fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1) return -1; if ((on = fcntl(ctx->nd_fd, F_GETFD, 0)) == -1 || fcntl(ctx->nd_fd, F_SETFD, on | FD_CLOEXEC) == -1) { close(ctx->nd_fd); ctx->nd_fd = -1; return -1; } if ((on = fcntl(ctx->nd_fd, F_GETFL, 0)) == -1 || fcntl(ctx->nd_fd, F_SETFL, on | O_NONBLOCK) == -1) { close(ctx->nd_fd); ctx->nd_fd = -1; return -1; } #endif /* RFC4861 4.1 */ on = 255; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &on, sizeof(on)) == -1) goto eexit; on = 1; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) goto eexit; on = 1; if (setsockopt(ctx->nd_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) == -1) goto eexit; ICMP6_FILTER_SETBLOCKALL(&filt); ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); if (setsockopt(ctx->nd_fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) goto eexit; eloop_event_add(dctx->eloop, ctx->nd_fd, ipv6nd_handledata, dctx, NULL, NULL); return ctx->nd_fd; eexit: if (ctx->nd_fd != -1) { eloop_event_delete(dctx->eloop, ctx->nd_fd, 0); close(ctx->nd_fd); ctx->nd_fd = -1; } 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) + (size_t)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 = (uint8_t)((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 ipv6_ctx *ctx; struct rs_state *state; struct sockaddr_in6 dst; struct cmsghdr *cm; struct in6_pktinfo pi; if (ipv6_linklocal(ifp) == NULL) { logger(ifp->ctx, LOG_DEBUG, "%s: delaying Router Solicitation for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); return; } memset(&dst, 0, sizeof(dst)); dst.sin6_family = AF_INET6; #ifdef SIN6_LEN dst.sin6_len = sizeof(dst); #endif dst.sin6_scope_id = ifp->index; if (inet_pton(AF_INET6, ALLROUTERS, &dst.sin6_addr) != 1) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return; } state = RS_STATE(ifp); ctx = ifp->ctx->ipv6; ctx->sndhdr.msg_name = (void *)&dst; ctx->sndhdr.msg_iov[0].iov_base = state->rs; ctx->sndhdr.msg_iov[0].iov_len = state->rslen; /* Set the outbound interface */ cm = CMSG_FIRSTHDR(&ctx->sndhdr); if (cm == NULL) /* unlikely */ return; 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)); logger(ifp->ctx, LOG_DEBUG, "%s: sending Router Solicitation", ifp->name); if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) { logger(ifp->ctx, 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(ifp->ctx->eloop, RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); else { logger(ifp->ctx, LOG_WARNING, "%s: no IPv6 Routers available", ifp->name); ipv6nd_drop(ifp); dhcp6_drop(ifp, "EXPIRE6"); } } void ipv6nd_expire(struct interface *ifp, uint32_t seconds) { struct ra *rap; struct timespec now; get_monotonic(&now); TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface == ifp) { rap->received = now; rap->expired = seconds ? 0 : 1; if (seconds) { struct ra_opt *rao; struct ipv6_addr *ap; rap->lifetime = seconds; TAILQ_FOREACH(ap, &rap->addrs, next) { if (ap->prefix_vltime) { ap->prefix_vltime = seconds; ap->prefix_pltime = seconds / 2; } } ipv6_addaddrs(&rap->addrs); TAILQ_FOREACH(rao, &rap->options, next) { timespecclear(&rao->expire); } } } } if (seconds) ipv6nd_expirera(ifp); else ipv6_buildroutes(ifp->ctx); } static void ipv6nd_reachable(struct ra *rap, int flags) { if (flags & IPV6ND_REACHABLE) { if (rap->lifetime && rap->expired) { logger(rap->iface->ctx, LOG_INFO, "%s: %s is reachable again", rap->iface->name, rap->sfrom); rap->expired = 0; ipv6_buildroutes(rap->iface->ctx); /* XXX Not really an RA */ script_runreason(rap->iface, "ROUTERADVERT"); } } else { if (rap->lifetime && !rap->expired) { logger(rap->iface->ctx, LOG_WARNING, "%s: %s is unreachable, expiring it", rap->iface->name, rap->sfrom); rap->expired = 1; ipv6_buildroutes(rap->iface->ctx); /* XXX Not really an RA */ script_runreason(rap->iface, "ROUTERADVERT"); } } } void ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags) { struct ra *rap; if (ctx->ipv6) { TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) { if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) { ipv6nd_reachable(rap, flags); break; } } } } 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); } } struct ipv6_addr * ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, short flags) { struct ra *rap; struct ipv6_addr *ap; if (ctx->ipv6 == NULL) return NULL; TAILQ_FOREACH(rap, ctx->ipv6->ra_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 ap; } else if (ap->prefix_vltime && IN6_ARE_ADDR_EQUAL(&ap->addr, addr) && (!flags || ap->flags & flags)) return ap; } } return NULL; } void ipv6nd_freedrop_ra(struct ra *rap, int drop) { eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); if (!drop) TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next); ipv6_freedrop_addrs(&rap->addrs, drop, NULL); ipv6nd_free_opts(rap); free(rap->data); free(rap); } ssize_t ipv6nd_free(struct interface *ifp) { struct rs_state *state; struct ra *rap, *ran; struct dhcpcd_ctx *ctx; ssize_t n; state = RS_STATE(ifp); if (state == NULL) return 0; free(state->rs); free(state); ifp->if_data[IF_DATA_IPV6ND] = NULL; n = 0; TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) { if (rap->iface == ifp) { ipv6nd_free_ra(rap); n++; } } /* If we don't have any more IPv6 enabled interfaces, * close the global socket and release resources */ ctx = ifp->ctx; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (RS_STATE(ifp)) break; } if (ifp == NULL) { if (ctx->ipv6->nd_fd != -1) { eloop_event_delete(ctx->eloop, ctx->ipv6->nd_fd, 0); close(ctx->ipv6->nd_fd); ctx->ipv6->nd_fd = -1; } } 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: logger(rap->iface->ctx, LOG_ERR, "rtpref: impossible RA flag %x", rap->flags); return (RTPREF_INVALID); } /* NOTREACHED */ } static void add_router(struct ipv6_ctx *ctx, struct ra *router) { struct ra *rap; TAILQ_FOREACH(rap, ctx->ra_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(ctx->ra_routers, router, next); } static int ipv6nd_scriptrun(struct ra *rap) { int hasdns, hasaddress, pid; struct ipv6_addr *ap; const struct ra_opt *rao; hasaddress = 0; /* If all addresses have completed DAD run the script */ TAILQ_FOREACH(ap, &rap->addrs, next) { if ((ap->flags & (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) == (IPV6_AF_AUTOCONF | IPV6_AF_ADDED)) { hasaddress = 1; if (!(ap->flags & IPV6_AF_DADCOMPLETED) && ipv6_iffindaddr(ap->iface, &ap->addr)) ap->flags |= IPV6_AF_DADCOMPLETED; if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { logger(ap->iface->ctx, LOG_DEBUG, "%s: waiting for Router Advertisement" " DAD to complete", rap->iface->name); return 0; } } } /* 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 && timespecisset(&rao->expire)) { hasdns = 1; break; } } } script_runreason(rap->iface, "ROUTERADVERT"); pid = 0; if (hasdns && (hasaddress || !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) pid = dhcpcd_daemonise(rap->iface->ctx); #if 0 else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED) && new_data) logger(rap->iface->ctx, LOG_WARNING, "%s: did not fork due to an absent" " RDNSS option in the RA", ifp->name); } #endif return pid; } static void ipv6nd_addaddr(void *arg) { struct ipv6_addr *ap = arg; ipv6_addaddr(ap, NULL); } int ipv6nd_dadcompleted(const struct interface *ifp) { const struct ra *rap; const struct ipv6_addr *ap; TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface != ifp) continue; TAILQ_FOREACH(ap, &rap->addrs, next) { if (ap->flags & IPV6_AF_AUTOCONF && ap->flags & IPV6_AF_ADDED && !(ap->flags & IPV6_AF_DADCOMPLETED)) return 0; } } return 1; } static void ipv6nd_dadcallback(void *arg) { struct ipv6_addr *ap = arg, *rapap; struct interface *ifp; struct ra *rap; int wascompleted, found; struct timespec tv; char buf[INET6_ADDRSTRLEN]; const char *p; int dadcounter; ifp = ap->iface; wascompleted = (ap->flags & IPV6_AF_DADCOMPLETED); ap->flags |= IPV6_AF_DADCOMPLETED; if (ap->flags & IPV6_AF_DUPLICATED) { ap->dadcounter++; logger(ifp->ctx, LOG_WARNING, "%s: DAD detected %s", ifp->name, ap->saddr); /* Try and make another stable private address. * Because ap->dadcounter is always increamented, * a different address is generated. */ /* XXX Cache DAD counter per prefix/id/ssid? */ if (ifp->options->options & DHCPCD_SLAACPRIVATE) { if (ap->dadcounter >= IDGEN_RETRIES) { logger(ifp->ctx, LOG_ERR, "%s: unable to obtain a" " stable private address", ifp->name); goto try_script; } logger(ifp->ctx, LOG_INFO, "%s: deleting address %s", ifp->name, ap->saddr); if (if_deladdress6(ap) == -1 && errno != EADDRNOTAVAIL && errno != ENXIO) logger(ifp->ctx, LOG_ERR, "if_deladdress6: %m"); dadcounter = ap->dadcounter; if (ipv6_makestableprivate(&ap->addr, &ap->prefix, ap->prefix_len, ifp, &dadcounter) == -1) { logger(ifp->ctx, LOG_ERR, "%s: ipv6_makestableprivate: %m", ifp->name); return; } ap->dadcounter = dadcounter; ap->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); ap->flags |= IPV6_AF_NEW; p = inet_ntop(AF_INET6, &ap->addr, buf, sizeof(buf)); if (p) snprintf(ap->saddr, sizeof(ap->saddr), "%s/%d", p, ap->prefix_len); else ap->saddr[0] = '\0'; tv.tv_sec = 0; tv.tv_nsec = (suseconds_t) arc4random_uniform(IDGEN_DELAY * NSEC_PER_SEC); timespecnorm(&tv); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_addaddr, ap); return; } } try_script: if (!wascompleted) { TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_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_ADDED && (rapap->flags & IPV6_AF_DADCOMPLETED) == 0) { wascompleted = 0; break; } if (rapap == ap) found = 1; } if (wascompleted && found) { logger(rap->iface->ctx, LOG_DEBUG, "%s: Router Advertisement DAD completed", rap->iface->name); if (ipv6nd_scriptrun(rap)) return; } } } } static int ipv6nd_ra_has_public_addr(const struct ra *rap) { const struct ipv6_addr *ia; TAILQ_FOREACH(ia, &rap->addrs, next) { if (ia->flags & IPV6_AF_AUTOCONF && ipv6_publicaddr(ia)) return 1; } return 0; } static void ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, struct icmp6_hdr *icp, size_t len) { struct ipv6_ctx *ctx = dctx->ipv6; size_t olen, l, n; ssize_t r; 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, *opt2, *tmp; struct timespec expire; uint8_t new_rap, new_data; #ifdef IPV6_MANAGETEMPADDR uint8_t new_ap; #endif if (len < sizeof(struct nd_router_advert)) { logger(dctx, LOG_ERR, "IPv6 RA packet too short from %s", ctx->sfrom); return; } if (!IN6_IS_ADDR_LINKLOCAL(&ctx->from.sin6_addr)) { logger(dctx, LOG_ERR, "RA from non local address %s", ctx->sfrom); return; } if (ifp == NULL) { #ifdef DEBUG_RS logger(dctx, LOG_DEBUG, "RA for unexpected interface from %s", ctx->sfrom); #endif return; } if (!(ifp->options->options & DHCPCD_IPV6RS)) { #ifdef DEBUG_RS logger(ifp->ctx, LOG_DEBUG, "%s: unexpected RA from %s", ifp->name, ctx->sfrom); #endif return; } /* We could receive a RA before we sent a RS*/ if (ipv6_linklocal(ifp) == NULL) { #ifdef DEBUG_RS logger(ifp->ctx, LOG_DEBUG, "%s: received RA from %s (no link-local)", ifp->name, ctx->sfrom); #endif return; } if (ipv6_iffindaddr(ifp, &ctx->from.sin6_addr)) { logger(ifp->ctx, LOG_DEBUG, "%s: ignoring RA from ourself %s", ifp->name, ctx->sfrom); return; } TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (ifp == rap->iface && IN6_ARE_ADDR_EQUAL(&rap->from, &ctx->from.sin6_addr)) break; } nd_ra = (struct nd_router_advert *)icp; /* 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; rap->no_public_warned = 0; } new_data = 1; } else new_data = 0; if (new_data || ifp->options->options & DHCPCD_DEBUG) logger(ifp->ctx, LOG_INFO, "%s: Router Advertisement from %s", ifp->name, ctx->sfrom); if (rap == NULL) { rap = calloc(1, sizeof(*rap)); if (rap == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return; } rap->iface = ifp; rap->from = ctx->from.sin6_addr; strlcpy(rap->sfrom, ctx->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) { logger(ifp->ctx, 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) logger(ifp->ctx, 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; ipv6_settempstale(ifp); TAILQ_FOREACH(ap, &rap->addrs, next) { ap->flags |= IPV6_AF_STALE; } len -= sizeof(struct nd_router_advert); p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); lifetime = ~0U; for (; len > 0; p += olen, len -= olen) { if (len < sizeof(struct nd_opt_hdr)) { logger(ifp->ctx, LOG_ERR, "%s: short option", ifp->name); break; } ndo = (struct nd_opt_hdr *)p; olen = (size_t)ndo->nd_opt_len * 8; if (olen == 0) { logger(ifp->ctx, LOG_ERR, "%s: zero length option", ifp->name); break; } if (olen > len) { logger(ifp->ctx, LOG_ERR, "%s: option length exceeds message", ifp->name); break; } opt = opt2 = 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) { logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, "%s: invalid option len for prefix", ifp->name); continue; } if (pi->nd_opt_pi_prefix_len > 128) { logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, "%s: invalid prefix len", ifp->name); continue; } if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) || IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) { logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, "%s: invalid prefix in RA", ifp->name); continue; } if (ntohl(pi->nd_opt_pi_preferred_time) > ntohl(pi->nd_opt_pi_valid_time)) { logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, "%s: pltime > vltime", ifp->name); continue; } TAILQ_FOREACH(ap, &rap->addrs, next) if (ap->prefix_len ==pi->nd_opt_pi_prefix_len && IN6_ARE_ADDR_EQUAL(&ap->prefix, &pi->nd_opt_pi_prefix)) 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)) continue; ap = calloc(1, sizeof(*ap)); if (ap == NULL) break; ap->iface = rap->iface; ap->flags = IPV6_AF_NEW; ap->prefix_len = pi->nd_opt_pi_prefix_len; ap->prefix = pi->nd_opt_pi_prefix; if (pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && ap->iface->options->options & DHCPCD_IPV6RA_AUTOCONF) { ap->flags |= IPV6_AF_AUTOCONF; ap->dadcounter = ipv6_makeaddr(&ap->addr, ifp, &ap->prefix, pi->nd_opt_pi_prefix_len); if (ap->dadcounter == -1) { free(ap); break; } cbp = inet_ntop(AF_INET6, &ap->addr, buf, sizeof(buf)); 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; ap->created = ap->acquired = rap->received; TAILQ_INSERT_TAIL(&rap->addrs, ap, next); #ifdef IPV6_MANAGETEMPADDR /* New address to dhcpcd RA handling. * If the address already exists and a valid * temporary address also exists then * extend the existing one rather than * create a new one */ if (ipv6_iffindaddr(ifp, &ap->addr) && ipv6_settemptime(ap, 0)) new_ap = 0; else new_ap = 1; #endif } else { #ifdef IPV6_MANAGETEMPADDR new_ap = 0; #endif ap->flags &= ~IPV6_AF_STALE; ap->acquired = rap->received; } 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; cbp = inet_ntop(AF_INET6, &ap->prefix, buf, sizeof(buf)); if (cbp) { l = strlen(cbp); opt = malloc(l + 5); if (opt) { snprintf(opt, l + 5, "%s/%d", cbp, ap->prefix_len); opt2 = strdup(ap->saddr); } } #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.3.3 */ if (ap->flags & IPV6_AF_AUTOCONF && ap->iface->options->options & DHCPCD_IPV6RA_OWN && ip6_use_tempaddr(ap->iface->name)) { if (!new_ap) { if (ipv6_settemptime(ap, 1) == NULL) new_ap = 1; } if (new_ap && ap->prefix_pltime) { if (ipv6_createtempaddr(ap, &ap->acquired) == NULL) logger(ap->iface->ctx, LOG_ERR, "ipv6_createtempaddr: %m"); } } #endif 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) { logger(ifp->ctx, 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 = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, op += sizeof(addr)) { r = ipv6_printaddr(NULL, 0, op, ifp->name); if (r != -1) l += (size_t)r + 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 == NULL) continue; for (n = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, op += sizeof(addr)) { r = ipv6_printaddr(tmp, l, op, ifp->name); if (r != -1) { l -= ((size_t)r + 1); tmp += (size_t)r; *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 = (size_t)(dnssl->nd_opt_dnssl_len - 1) * 8; r = decode_rfc3397(NULL, 0, op, n); if (r < 1) { logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, "%s: invalid DNSSL option", ifp->name); continue; } else { l = (size_t)r + 1; tmp = malloc(l); if (tmp) { decode_rfc3397(tmp, l, op, n); l -= 1; n = (size_t)print_string(NULL, 0, STRING | ARRAY | DOMAIN, (const uint8_t *)tmp, l); n++; opt = malloc(n); if (opt) { print_string(opt, n, STRING | ARRAY | DOMAIN, (const uint8_t *)tmp, l); } else logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); free(tmp); } } break; default: continue; } if (opt == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); continue; } n = ndo->nd_opt_type; extra_opt: TAILQ_FOREACH(rao, &rap->options, next) { if (rao->type == n && strcmp(rao->option, opt) == 0) break; } if (lifetime == 0 || *opt == '\0') { if (rao) { TAILQ_REMOVE(&rap->options, rao, next); free(rao->option); free(rao); } free(opt); free(opt2); continue; } if (rao == NULL) { rao = malloc(sizeof(*rao)); if (rao == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); continue; } rao->type = (uint16_t)n; rao->option = opt; TAILQ_INSERT_TAIL(&rap->options, rao, next); } else free(opt); if (lifetime == ~0U) timespecclear(&rao->expire); else { expire.tv_sec = (time_t)lifetime; expire.tv_nsec = 0; timespecadd(&rap->received, &expire, &rao->expire); } if (rao && rao->type == ND_OPT_PREFIX_INFORMATION && opt2) { n = _ND_OPT_PREFIX_ADDR; opt = opt2; opt2 = NULL; goto extra_opt; } } if (new_rap) add_router(ifp->ctx->ipv6, rap); if (!ipv6nd_ra_has_public_addr(rap) && !(rap->iface->options->options & DHCPCD_IPV6RA_ACCEPT_NOPUBLIC) && (!(rap->flags & ND_RA_FLAG_MANAGED) || !dhcp6_has_public_addr(rap->iface))) { logger(rap->iface->ctx, rap->no_public_warned ? LOG_DEBUG : LOG_WARNING, "%s: ignoring RA from %s" " (no public prefix, no managed address)", rap->iface->name, rap->sfrom); rap->no_public_warned = 1; return; } if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); goto handle_flag; } ipv6_addaddrs(&rap->addrs); #ifdef IPV6_MANAGETEMPADDR ipv6_addtempaddrs(ifp, &rap->received); #endif /* Find any freshly added routes, such as the subnet route. * We do this because we cannot rely on recieving the kernel * notification right now via our link socket. */ if_initrt6(ifp); ipv6_buildroutes(ifp->ctx); if (ipv6nd_scriptrun(rap)) return; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */ handle_flag: if (!(ifp->options->options & DHCPCD_DHCP6)) goto nodhcp6; if (rap->flags & ND_RA_FLAG_MANAGED) { if (new_data && dhcp6_start(ifp, DH6S_INIT) == -1) logger(ifp->ctx, LOG_ERR, "dhcp6_start: %s: %m", ifp->name); } else if (rap->flags & ND_RA_FLAG_OTHER) { if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) logger(ifp->ctx, LOG_ERR, "dhcp6_start: %s: %m", ifp->name); } else { if (new_data) logger(ifp->ctx, LOG_DEBUG, "%s: No DHCPv6 instruction in RA", ifp->name); nodhcp6: if (ifp->ctx->options & DHCPCD_TEST) { eloop_exit(ifp->ctx->eloop, EXIT_SUCCESS); return; } } /* Expire should be called last as the rap object could be destroyed */ ipv6nd_expirera(ifp); } /* Run RA's we ignored becuase they had no public addresses * This should only be called when DHCPv6 applies a public address */ void ipv6nd_runignoredra(struct interface *ifp) { struct ra *rap; TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface == ifp && !rap->expired && rap->no_public_warned) { rap->no_public_warned = 0; logger(rap->iface->ctx, LOG_INFO, "%s: applying ignored RA from %s", rap->iface->name, rap->sfrom); if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); continue; } if (ipv6nd_scriptrun(rap)) return; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); } } } int ipv6nd_hasra(const struct interface *ifp) { const struct ra *rap; if (ifp->ctx->ipv6) { TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) if (rap->iface == ifp && !rap->expired) return 1; } return 0; } int ipv6nd_hasradhcp(const struct interface *ifp) { const struct ra *rap; if (ifp->ctx->ipv6) { TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface == ifp && !rap->expired && (rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) return 1; } } return 0; } ssize_t ipv6nd_env(char **env, const char *prefix, const struct interface *ifp) { size_t i, l, len; const struct ra *rap; const struct ra_opt *rao; char buffer[32]; const char *optn; char **pref, **addr, **mtu, **rdnss, **dnssl, ***var, *new; i = l = 0; TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface != ifp) continue; i++; if (env) { snprintf(buffer, sizeof(buffer), "ra%zu_from", i); setvar(ifp->ctx, &env, prefix, buffer, rap->sfrom); } l++; pref = addr = 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_PREFIX_ADDR: optn = "addr"; var = &addr; 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) { logger(ifp->ctx, LOG_ERR, "new is null"); continue; } else new++; len = (size_t)(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) { len -= (size_t) (new - **var); strlcpy(new + 1, rao->option, len - 1); } else logger(ifp->ctx, LOG_ERR, "new is null"); } continue; } len = strlen(rao->option) + 1; new = realloc(**var, strlen(**var) + 1 + len); if (new) { **var = new; new += strlen(new); *new++ = ' '; strlcpy(new, rao->option, len); } else logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); continue; } if (env) { snprintf(buffer, sizeof(buffer), "ra%zu_%s", i, optn); setvar(ifp->ctx, &env, prefix, buffer, rao->option); } } } if (env) setvard(ifp->ctx, &env, prefix, "ra_count", i); l++; return (ssize_t)l; } void ipv6nd_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname, const struct in6_addr *addr, int flags) { struct ra *rap; if (ctx->ipv6 == NULL) return; TAILQ_FOREACH(rap, ctx->ipv6->ra_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 timespec now, lt, expire, next; uint8_t expired, valid, validone; ifp = arg; get_monotonic(&now); expired = 0; timespecclear(&next); validone = 0; TAILQ_FOREACH_SAFE(rap, ifp->ctx->ipv6->ra_routers, next, ran) { if (rap->iface != ifp) continue; valid = 0; if (rap->lifetime) { lt.tv_sec = (time_t)rap->lifetime; lt.tv_nsec = 0; timespecadd(&rap->received, <, &expire); if (rap->lifetime == 0 || timespeccmp(&now, &expire, >)) { if (!rap->expired) { logger(ifp->ctx, LOG_WARNING, "%s: %s: router expired", ifp->name, rap->sfrom); rap->expired = expired = 1; rap->lifetime = 0; } } else { valid = 1; timespecsub(&expire, &now, <); if (!timespecisset(&next) || timespeccmp(&next, <, >)) next = lt; } } /* Addresses are expired in ipv6_addaddrs * 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 (!timespecisset(&rao->expire)) continue; if (timespeccmp(&now, &rao->expire, >)) { /* Expired prefixes are logged above */ if (rao->type != ND_OPT_PREFIX_INFORMATION) logger(ifp->ctx, 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; timespecsub(&rao->expire, &now, <); if (!timespecisset(&next) || timespeccmp(&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); else validone = 1; } if (timespecisset(&next)) eloop_timeout_add_tv(ifp->ctx->eloop, &next, ipv6nd_expirera, ifp); if (expired) { ipv6_buildroutes(ifp->ctx); script_runreason(ifp, "ROUTERADVERT"); } /* No valid routers? Kill any DHCPv6. */ if (!validone) dhcp6_drop(ifp, "EXPIRE6"); } void ipv6nd_drop(struct interface *ifp) { struct ra *rap; uint8_t expired = 0; TAILQ_HEAD(rahead, ra) rtrs; if (ifp->ctx->ipv6 == NULL) return; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); TAILQ_INIT(&rtrs); TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface == ifp) { rap->expired = expired = 1; TAILQ_REMOVE(ifp->ctx->ipv6->ra_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(ifp->ctx); if ((ifp->options->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != (DHCPCD_EXITING | DHCPCD_PERSISTENT)) script_runreason(ifp, "ROUTERADVERT"); } } static void ipv6nd_handlena(struct dhcpcd_ctx *dctx, struct interface *ifp, struct icmp6_hdr *icp, size_t len) { struct ipv6_ctx *ctx = dctx->ipv6; struct nd_neighbor_advert *nd_na; struct ra *rap; uint32_t is_router, is_solicited; char buf[INET6_ADDRSTRLEN]; const char *taddr; if (ifp == NULL) { #ifdef DEBUG_NS logger(ctx, LOG_DEBUG, "NA for unexpected interface from %s", dctx->sfrom); #endif return; } if ((size_t)len < sizeof(struct nd_neighbor_advert)) { logger(ifp->ctx, LOG_ERR, "%s: IPv6 NA too short from %s", ifp->name, ctx->sfrom); 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; taddr = inet_ntop(AF_INET6, &nd_na->nd_na_target, buf, INET6_ADDRSTRLEN); if (IN6_IS_ADDR_MULTICAST(&nd_na->nd_na_target)) { logger(ifp->ctx, LOG_ERR, "%s: NA multicast address %s (%s)", ifp->name, taddr, ctx->sfrom); return; } TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (rap->iface == ifp && IN6_ARE_ADDR_EQUAL(&rap->from, &nd_na->nd_na_target)) break; } if (rap == NULL) { #ifdef DEBUG_NS logger(ifp->ctx, LOG_DEBUG, "%s: unexpected NA from %s for %s", ifp->name, ctx->sfrom, taddr); #endif return; } #ifdef DEBUG_NS logger(ifp->ctx, LOG_DEBUG, "%s: %sNA for %s from %s", ifp->name, is_solicited ? "solicited " : "", taddr, ctx->sfrom); #endif /* Node is no longer a router, so remove it from consideration */ if (!is_router && !rap->expired) { logger(ifp->ctx, LOG_INFO, "%s: %s not a router (%s)", ifp->name, taddr, ctx->sfrom); rap->expired = 1; ipv6_buildroutes(ifp->ctx); script_runreason(ifp, "ROUTERADVERT"); return; } if (is_solicited && is_router && rap->lifetime) { if (rap->expired) { rap->expired = 0; logger(ifp->ctx, LOG_INFO, "%s: %s reachable (%s)", ifp->name, taddr, ctx->sfrom); ipv6_buildroutes(ifp->ctx); script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */ } } } static void ipv6nd_handledata(void *arg) { struct dhcpcd_ctx *dctx; struct ipv6_ctx *ctx; ssize_t len; struct cmsghdr *cm; int hoplimit; struct in6_pktinfo pkt; struct icmp6_hdr *icp; struct interface *ifp; dctx = arg; ctx = dctx->ipv6; ctx->rcvhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int)); len = recvmsg(ctx->nd_fd, &ctx->rcvhdr, 0); if (len == -1) { logger(dctx, LOG_ERR, "recvmsg: %m"); eloop_event_delete(dctx->eloop, ctx->nd_fd, 0); close(ctx->nd_fd); ctx->nd_fd = -1; return; } ctx->sfrom = inet_ntop(AF_INET6, &ctx->from.sin6_addr, ctx->ntopbuf, INET6_ADDRSTRLEN); if ((size_t)len < sizeof(struct icmp6_hdr)) { logger(dctx, LOG_ERR, "IPv6 ICMP packet too short from %s", ctx->sfrom); return; } pkt.ipi6_ifindex = 0; hoplimit = 0; for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&ctx->rcvhdr); cm; cm = (struct cmsghdr *)CMSG_NXTHDR(&ctx->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) { logger(dctx, LOG_ERR, "IPv6 RA/NA did not contain index or hop limit from %s", ctx->sfrom); return; } TAILQ_FOREACH(ifp, dctx->ifaces, next) { if (ifp->index == (unsigned int)pkt.ipi6_ifindex && !(ifp->options->options & DHCPCD_PFXDLGONLY)) { if (!(ifp->options->options & DHCPCD_IPV6)) return; break; } } icp = (struct icmp6_hdr *)ctx->rcvhdr.msg_iov[0].iov_base; if (icp->icmp6_code == 0) { switch(icp->icmp6_type) { case ND_NEIGHBOR_ADVERT: ipv6nd_handlena(dctx, ifp, icp, (size_t)len); return; case ND_ROUTER_ADVERT: ipv6nd_handlera(dctx, ifp, icp, (size_t)len); return; } } logger(dctx, LOG_ERR, "invalid IPv6 type %d or code %d from %s", icp->icmp6_type, icp->icmp6_code, ctx->sfrom); } static void ipv6nd_startrs1(void *arg) { struct interface *ifp = arg; struct rs_state *state; logger(ifp->ctx, LOG_INFO, "%s: soliciting an IPv6 router", ifp->name); if (ipv6nd_open(ifp->ctx) == -1) { logger(ifp->ctx, LOG_ERR, "%s: ipv6nd_open: %m", __func__); return; } state = RS_STATE(ifp); if (state == NULL) { ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); state = RS_STATE(ifp); if (state == NULL) { logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); return; } } /* Always make a new probe as the underlying hardware * address could have changed. */ ipv6nd_makersprobe(ifp); if (state->rs == NULL) { logger(ifp->ctx, LOG_ERR, "%s: ipv6ns_makersprobe: %m", __func__); return; } state->rsprobes = 0; ipv6nd_sendrsprobe(ifp); } void ipv6nd_startrs(struct interface *ifp) { struct timespec tv; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); tv.tv_sec = 0; tv.tv_nsec = (suseconds_t)arc4random_uniform( MAX_RTR_SOLICITATION_DELAY * NSEC_PER_SEC); timespecnorm(&tv); logger(ifp->ctx, LOG_DEBUG, "%s: delaying IPv6 router solicitation for %0.1f seconds", ifp->name, timespec_to_double(&tv)); eloop_timeout_add_tv(ifp->ctx->eloop, &tv, ipv6nd_startrs1, ifp); return; }