/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2020 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 #include #include #include #include #include #include #include #include #include #define ELOOP_QUEUE ELOOP_IPV6ND #include "common.h" #include "dhcpcd.h" #include "dhcp-common.h" #include "dhcp6.h" #include "eloop.h" #include "if.h" #include "ipv6.h" #include "ipv6nd.h" #include "logerr.h" #include "privsep.h" #include "route.h" #include "script.h" /* Debugging Router Solicitations is a lot of spam, so disable it */ //#define DEBUG_RS #ifndef ND_RA_FLAG_HOME_AGENT #define ND_RA_FLAG_HOME_AGENT 0x20 /* Home Agent flag in RA */ #endif #ifndef ND_RA_FLAG_PROXY #define ND_RA_FLAG_PROXY 0x04 /* Proxy */ #endif #ifndef ND_OPT_PI_FLAG_ROUTER #define ND_OPT_PI_FLAG_ROUTER 0x20 /* Router flag in PI */ #endif #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 */ }; __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); #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 */ }; __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); #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 #define EXPIRED_MAX 5 /* Remember 5 expired routers to avoid logspam. */ #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 /* Handy defines */ #define ipv6nd_free_ra(ra) ipv6nd_freedrop_ra((ra), 0) #define ipv6nd_drop_ra(ra) ipv6nd_freedrop_ra((ra), 1) void ipv6nd_printoptions(const struct dhcpcd_ctx *ctx, const struct dhcp_opt *opts, size_t opts_len) { size_t i, j; const struct dhcp_opt *opt, *opt2; int cols; for (i = 0, opt = ctx->nd_opts; i < ctx->nd_opts_len; i++, opt++) { for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) if (opt2->option == opt->option) break; if (j == opts_len) { cols = printf("%03d %s", opt->option, opt->var); dhcp_print_option_encoding(opt, cols); } } for (i = 0, opt = opts; i < opts_len; i++, opt++) { cols = printf("%03d %s", opt->option, opt->var); dhcp_print_option_encoding(opt, cols); } } int ipv6nd_open(bool recv) { int fd, on; struct icmp6_filter filt; fd = xsocket(PF_INET6, SOCK_RAW | SOCK_CXNB, IPPROTO_ICMPV6); if (fd == -1) return -1; ICMP6_FILTER_SETBLOCKALL(&filt); /* RFC4861 4.1 */ on = 255; if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &on, sizeof(on)) == -1) goto eexit; if (recv) { on = 1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == -1) goto eexit; on = 1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) == -1) goto eexit; ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filt); #ifdef SO_RERROR on = 1; if (setsockopt(fd, SOL_SOCKET, SO_RERROR, &on, sizeof(on)) == -1) goto eexit; #endif } if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt)) == -1) goto eexit; return fd; eexit: close(fd); return -1; } #ifdef __sun int ipv6nd_openif(struct interface *ifp) { int fd; struct ipv6_mreq mreq = { .ipv6mr_multiaddr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, .ipv6mr_interface = ifp->index }; struct rs_state *state = RS_STATE(ifp); uint_t ifindex = ifp->index; if (state->nd_fd != -1) return state->nd_fd; fd = ipv6nd_open(true); if (fd == -1) return -1; if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &ifindex, sizeof(ifindex)) == -1) { close(fd); return -1; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1) { close(fd); return -1; } state->nd_fd = fd; eloop_event_add(ifp->ctx->eloop, fd, ipv6nd_handledata, ifp); return fd; } #endif static int ipv6nd_makersprobe(struct interface *ifp) { struct rs_state *state; struct nd_router_solicit *rs; state = RS_STATE(ifp); free(state->rs); state->rslen = sizeof(*rs); if (ifp->hwlen != 0) state->rslen += (size_t)ROUNDUP8(ifp->hwlen + 2); state->rs = calloc(1, state->rslen); if (state->rs == NULL) return -1; rs = state->rs; rs->nd_rs_type = ND_ROUTER_SOLICIT; //rs->nd_rs_code = 0; //rs->nd_rs_cksum = 0; //rs->nd_rs_reserved = 0; if (ifp->hwlen != 0) { struct nd_opt_hdr *nd; nd = (struct nd_opt_hdr *)(state->rs + 1); 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 rs_state *state = RS_STATE(ifp); struct sockaddr_in6 dst = { .sin6_family = AF_INET6, .sin6_addr = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT, .sin6_scope_id = ifp->index, }; struct iovec iov = { .iov_base = state->rs, .iov_len = state->rslen }; union { struct cmsghdr hdr; uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; struct cmsghdr *cm; struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; int s; #ifndef __sun struct dhcpcd_ctx *ctx = ifp->ctx; #endif if (ipv6_linklocal(ifp) == NULL) { logdebugx("%s: delaying Router Solicitation for LL address", ifp->name); ipv6_addlinklocalcallback(ifp, ipv6nd_sendrsprobe, ifp); return; } #ifdef HAVE_SA_LEN dst.sin6_len = sizeof(dst); #endif /* Set the outbound interface */ cm = CMSG_FIRSTHDR(&msg); if (cm == NULL) /* unlikely */ return; cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending Router Solicitation", ifp->name); #ifdef PRIVSEP if (IN_PRIVSEP(ifp->ctx)) { if (ps_inet_sendnd(ifp, &msg) == -1) logerr(__func__); goto sent; } #endif #ifdef __sun if (state->nd_fd == -1) { if (ipv6nd_openif(ifp) == -1) { logerr(__func__); return; } } s = state->nd_fd; #else if (ctx->nd_fd == -1) { ctx->nd_fd = ipv6nd_open(true); if (ctx->nd_fd == -1) { logerr(__func__); return; } eloop_event_add(ctx->eloop, ctx->nd_fd, ipv6nd_handledata, ctx); } s = ifp->ctx->nd_fd; #endif if (sendmsg(s, &msg, 0) == -1) { logerr(__func__); /* Allow IPv6ND to continue .... at most a few errors * would be logged. * Generally the error is ENOBUFS when struggling to * associate with an access point. */ } #ifdef PRIVSEP sent: #endif if (state->rsprobes++ < MAX_RTR_SOLICITATIONS) eloop_timeout_add_sec(ifp->ctx->eloop, RTR_SOLICITATION_INTERVAL, ipv6nd_sendrsprobe, ifp); else logwarnx("%s: no IPv6 Routers available", ifp->name); } #ifdef ND6_ADVERTISE static void ipv6nd_sendadvertisement(void *arg) { struct ipv6_addr *ia = arg; struct interface *ifp = ia->iface; struct dhcpcd_ctx *ctx = ifp->ctx; struct sockaddr_in6 dst = { .sin6_family = AF_INET6, .sin6_addr = IN6ADDR_LINKLOCAL_ALLNODES_INIT, .sin6_scope_id = ifp->index, }; struct iovec iov = { .iov_base = ia->na, .iov_len = ia->na_len }; union { struct cmsghdr hdr; uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; struct cmsghdr *cm; struct in6_pktinfo pi = { .ipi6_ifindex = ifp->index }; const struct rs_state *state = RS_CSTATE(ifp); int s; if (state == NULL || !if_is_link_up(ifp)) goto freeit; #ifdef SIN6_LEN dst.sin6_len = sizeof(dst); #endif /* Set the outbound interface. */ cm = CMSG_FIRSTHDR(&msg); assert(cm != NULL); cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = CMSG_LEN(sizeof(pi)); memcpy(CMSG_DATA(cm), &pi, sizeof(pi)); logdebugx("%s: sending NA for %s", ifp->name, ia->saddr); #ifdef PRIVSEP if (IN_PRIVSEP(ifp->ctx)) { if (ps_inet_sendnd(ifp, &msg) == -1) logerr(__func__); goto sent; } #endif #ifdef __sun s = state->nd_fd; #else s = ctx->nd_fd; #endif if (sendmsg(s, &msg, 0) == -1) logerr(__func__); #ifdef PRIVSEP sent: #endif if (++ia->na_count < MAX_NEIGHBOR_ADVERTISEMENT) { eloop_timeout_add_sec(ctx->eloop, state->retrans / 1000, ipv6nd_sendadvertisement, ia); return; } freeit: free(ia->na); ia->na = NULL; ia->na_count = 0; } void ipv6nd_advertise(struct ipv6_addr *ia) { struct dhcpcd_ctx *ctx; struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *iap, *iaf; struct nd_neighbor_advert *na; if (IN6_IS_ADDR_MULTICAST(&ia->addr)) return; #ifdef __sun if (!(ia->flags & IPV6_AF_AUTOCONF) && ia->flags & IPV6_AF_RAPFX) return; #endif ctx = ia->iface->ctx; /* Find the most preferred address to advertise. */ iaf = NULL; TAILQ_FOREACH(ifp, ctx->ifaces, next) { state = IPV6_STATE(ifp); if (state == NULL || !if_is_link_up(ifp)) continue; TAILQ_FOREACH(iap, &state->addrs, next) { if (!IN6_ARE_ADDR_EQUAL(&iap->addr, &ia->addr)) continue; /* Cancel any current advertisement. */ eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iap); /* Don't advertise what we can't use. */ if (iap->prefix_vltime == 0 || iap->addr_flags & IN6_IFF_NOTUSEABLE) continue; if (iaf == NULL || iaf->iface->metric > iap->iface->metric) iaf = iap; } } if (iaf == NULL) return; /* Make the packet. */ ifp = iaf->iface; iaf->na_len = sizeof(*na); if (ifp->hwlen != 0) iaf->na_len += (size_t)ROUNDUP8(ifp->hwlen + 2); na = calloc(1, iaf->na_len); if (na == NULL) { logerr(__func__); return; } na->nd_na_type = ND_NEIGHBOR_ADVERT; na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; #if defined(PRIVSEP) && (defined(__linux__) || defined(HAVE_PLEDGE)) if (IN_PRIVSEP(ctx)) { if (ps_root_ip6forwarding(ctx, ifp->name) != 0) na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; } else #endif if (ip6_forwarding(ifp->name) != 0) na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; na->nd_na_target = ia->addr; if (ifp->hwlen != 0) { struct nd_opt_hdr *opt; opt = (struct nd_opt_hdr *)(na + 1); opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; opt->nd_opt_len = (uint8_t)((ROUNDUP8(ifp->hwlen + 2)) >> 3); memcpy(opt + 1, ifp->hwaddr, ifp->hwlen); } iaf->na_count = 0; free(iaf->na); iaf->na = na; eloop_timeout_delete(ctx->eloop, ipv6nd_sendadvertisement, iaf); ipv6nd_sendadvertisement(iaf); } #elif !defined(SMALL) #warning kernel does not support userland sending ND6 advertisements #endif /* ND6_ADVERTISE */ static void ipv6nd_expire(void *arg) { struct interface *ifp = arg; struct ra *rap; if (ifp->ctx->ra_routers == NULL) return; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp && rap->willexpire) rap->doexpire = true; } ipv6nd_expirera(ifp); } void ipv6nd_startexpire(struct interface *ifp) { struct ra *rap; if (ifp->ctx->ra_routers == NULL) return; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp) rap->willexpire = true; } eloop_q_timeout_add_sec(ifp->ctx->eloop, ELOOP_IPV6RA_EXPIRE, RTR_CARRIER_EXPIRE, ipv6nd_expire, ifp); } int ipv6nd_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: logerrx("%s: impossible RA flag %x", __func__, rap->flags); return RTPREF_INVALID; } /* NOTREACHED */ } static void ipv6nd_sortrouters(struct dhcpcd_ctx *ctx) { struct ra_head sorted_routers = TAILQ_HEAD_INITIALIZER(sorted_routers); struct ra *ra1, *ra2; while ((ra1 = TAILQ_FIRST(ctx->ra_routers)) != NULL) { TAILQ_REMOVE(ctx->ra_routers, ra1, next); TAILQ_FOREACH(ra2, &sorted_routers, next) { if (ra1->iface->metric > ra2->iface->metric) continue; if (ra1->expired && !ra2->expired) continue; if (ra1->willexpire && !ra2->willexpire) continue; if (ra1->lifetime == 0 && ra2->lifetime != 0) continue; if (!ra1->isreachable && ra2->reachable) continue; if (ipv6nd_rtpref(ra1) <= ipv6nd_rtpref(ra2)) continue; /* All things being equal, prefer older routers. */ /* We don't need to check time, becase newer * routers are always added to the tail and then * sorted. */ TAILQ_INSERT_BEFORE(ra2, ra1, next); break; } if (ra2 == NULL) TAILQ_INSERT_TAIL(&sorted_routers, ra1, next); } TAILQ_CONCAT(ctx->ra_routers, &sorted_routers, next); } static void ipv6nd_applyra(struct interface *ifp) { struct ra *rap; struct rs_state *state = RS_STATE(ifp); struct ra defra = { .iface = ifp, .hoplimit = IPV6_DEFHLIM , .reachable = REACHABLE_TIME, .retrans = RETRANS_TIMER, }; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp) break; } /* If we have no Router Advertisement, then set default values. */ if (rap == NULL || rap->expired || rap->willexpire) rap = &defra; state->retrans = rap->retrans; if (if_applyra(rap) == -1 && errno != ENOENT) logerr(__func__); } /* * Neighbour reachability. * * RFC 4681 6.2.5 says when a node is no longer a router it MUST * send a RA with a zero lifetime. * All OS's I know of set the NA router flag if they are a router * or not and disregard that they are actively advertising or * shutting down. If the interface is disabled, it cant't send a NA at all. * * As such we CANNOT rely on the NA Router flag and MUST use * unreachability or receive a RA with a lifetime of zero to remove * the node as a default router. */ void ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, bool reachable) { struct ra *rap, *rapr; if (ctx->ra_routers == NULL) return; TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) break; } if (rap == NULL || rap->expired || rap->isreachable == reachable) return; rap->isreachable = reachable; loginfox("%s: %s is %s", rap->iface->name, rap->sfrom, reachable ? "reachable again" : "unreachable"); /* See if we can install a reachable default router. */ ipv6nd_sortrouters(ctx); ipv6nd_applyra(rap->iface); rt_build(ctx, AF_INET6); if (reachable) return; /* If we have no reachable default routers, try and solicit one. */ TAILQ_FOREACH(rapr, ctx->ra_routers, next) { if (rap == rapr || rap->iface != rapr->iface) continue; if (rapr->isreachable && !rapr->expired && rapr->lifetime) break; } if (rapr == NULL) ipv6nd_startrs(rap->iface); } const struct ipv6_addr * ipv6nd_iffindaddr(const struct interface *ifp, const struct in6_addr *addr, unsigned int flags) { struct ra *rap; struct ipv6_addr *ap; if (ifp->ctx->ra_routers == NULL) return NULL; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface != ifp) continue; TAILQ_FOREACH(ap, &rap->addrs, next) { if (ipv6_findaddrmatch(ap, addr, flags)) return ap; } } return NULL; } struct ipv6_addr * ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, unsigned int flags) { struct ra *rap; struct ipv6_addr *ap; if (ctx->ra_routers == NULL) return NULL; TAILQ_FOREACH(rap, ctx->ra_routers, next) { TAILQ_FOREACH(ap, &rap->addrs, next) { if (ipv6_findaddrmatch(ap, addr, flags)) return ap; } } return NULL; } static struct ipv6_addr * ipv6nd_rapfindprefix(struct ra *rap, const struct in6_addr *pfx, uint8_t pfxlen) { struct ipv6_addr *ia; TAILQ_FOREACH(ia, &rap->addrs, next) { if (ia->prefix_vltime == 0) continue; if (ia->prefix_len == pfxlen && IN6_ARE_ADDR_EQUAL(&ia->prefix, pfx)) break; } return ia; } struct ipv6_addr * ipv6nd_iffindprefix(struct interface *ifp, const struct in6_addr *pfx, uint8_t pfxlen) { struct ra *rap; struct ipv6_addr *ia; ia = NULL; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface != ifp) continue; ia = ipv6nd_rapfindprefix(rap, pfx, pfxlen); if (ia != NULL) break; } return ia; } static void ipv6nd_removefreedrop_ra(struct ra *rap, int remove_ra, int drop_ra) { eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); if (remove_ra) TAILQ_REMOVE(rap->iface->ctx->ra_routers, rap, next); ipv6_freedrop_addrs(&rap->addrs, drop_ra, NULL); free(rap->data); free(rap); } static void ipv6nd_freedrop_ra(struct ra *rap, int drop) { ipv6nd_removefreedrop_ra(rap, 1, drop); } 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; ctx = ifp->ctx; #ifdef __sun eloop_event_delete(ctx->eloop, state->nd_fd); close(state->nd_fd); #endif free(state->rs); free(state); ifp->if_data[IF_DATA_IPV6ND] = NULL; n = 0; TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { if (rap->iface == ifp) { ipv6nd_free_ra(rap); n++; } } #ifndef __sun /* If we don't have any more IPv6 enabled interfaces, * close the global socket and release resources */ TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (RS_STATE(ifp)) break; } if (ifp == NULL) { if (ctx->nd_fd != -1) { eloop_event_delete(ctx->eloop, ctx->nd_fd); close(ctx->nd_fd); ctx->nd_fd = -1; } } #endif return n; } static void ipv6nd_scriptrun(struct ra *rap) { int hasdns, hasaddress; struct ipv6_addr *ap; 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, IN6_IFF_TENTATIVE)) ap->flags |= IPV6_AF_DADCOMPLETED; if ((ap->flags & IPV6_AF_DADCOMPLETED) == 0) { logdebugx("%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 = rap->hasdns; } script_runreason(rap->iface, "ROUTERADVERT"); if (hasdns && (hasaddress || !(rap->flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER)))) dhcpcd_daemonise(rap->iface->ctx); #if 0 else if (options & DHCPCD_DAEMONISE && !(options & DHCPCD_DAEMONISED) && new_data) logwarnx("%s: did not fork due to an absent" " RDNSS option in the RA", ifp->name); #endif } 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->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 *ia = arg, *rapap; struct interface *ifp; struct ra *rap; int wascompleted, found; char buf[INET6_ADDRSTRLEN]; const char *p; int dadcounter; ifp = ia->iface; wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_DADCOMPLETED; if (ia->addr_flags & IN6_IFF_DUPLICATED) { ia->dadcounter++; logwarnx("%s: DAD detected %s", ifp->name, ia->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 && IA6_CANAUTOCONF(ia)) { unsigned int delay; if (ia->dadcounter >= IDGEN_RETRIES) { logerrx("%s: unable to obtain a" " stable private address", ifp->name); goto try_script; } loginfox("%s: deleting address %s", ifp->name, ia->saddr); if (if_address6(RTM_DELADDR, ia) == -1 && errno != EADDRNOTAVAIL && errno != ENXIO) logerr(__func__); dadcounter = ia->dadcounter; if (ipv6_makestableprivate(&ia->addr, &ia->prefix, ia->prefix_len, ifp, &dadcounter) == -1) { logerr("ipv6_makestableprivate"); return; } ia->dadcounter = dadcounter; ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); ia->flags |= IPV6_AF_NEW; p = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); if (p) snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", p, ia->prefix_len); else ia->saddr[0] = '\0'; delay = arc4random_uniform(IDGEN_DELAY * MSEC_PER_SEC); eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_addaddr, ia); return; } } try_script: if (!wascompleted) { TAILQ_FOREACH(rap, ifp->ctx->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 == ia) found = 1; } if (wascompleted && found) { logdebugx("%s: Router Advertisement DAD " "completed", rap->iface->name); ipv6nd_scriptrun(rap); } } #ifdef ND6_ADVERTISE ipv6nd_advertise(ia); #endif } } static struct ipv6_addr * ipv6nd_findmarkstale(struct ra *rap, struct ipv6_addr *ia, bool mark) { struct dhcpcd_ctx *ctx = ia->iface->ctx; struct ra *rap2; struct ipv6_addr *ia2; TAILQ_FOREACH(rap2, ctx->ra_routers, next) { if (rap2 == rap || rap2->iface != rap->iface || rap2->expired) continue; TAILQ_FOREACH(ia2, &rap2->addrs, next) { if (!IN6_ARE_ADDR_EQUAL(&ia->prefix, &ia2->prefix)) continue; if (!(ia2->flags & IPV6_AF_STALE)) return ia2; if (mark) ia2->prefix_pltime = 0; } } return NULL; } #ifndef DHCP6 /* If DHCPv6 is compiled out, supply a shim to provide an error message * if IPv6RA requests DHCPv6. */ enum DH6S { DH6S_REQUEST, DH6S_INFORM, }; static int dhcp6_start(__unused struct interface *ifp, __unused enum DH6S init_state) { errno = ENOTSUP; return -1; } #endif static void ipv6nd_handlera(struct dhcpcd_ctx *ctx, const struct sockaddr_in6 *from, const char *sfrom, struct interface *ifp, struct icmp6_hdr *icp, size_t len, int hoplimit) { size_t i, olen; struct nd_router_advert *nd_ra; struct nd_opt_hdr ndo; struct nd_opt_prefix_info pi; struct nd_opt_mtu mtu; struct nd_opt_rdnss rdnss; uint8_t *p; struct ra *rap; struct in6_addr pi_prefix; struct ipv6_addr *ia; struct dhcp_opt *dho; bool new_rap, new_data, has_address; uint32_t old_lifetime; int ifmtu; int loglevel; unsigned int flags; #ifdef IPV6_MANAGETEMPADDR bool new_ia; #endif if (ifp == NULL || RS_STATE(ifp) == NULL) { #ifdef DEBUG_RS logdebugx("RA for unexpected interface from %s", sfrom); #endif return; } if (len < sizeof(struct nd_router_advert)) { logerrx("IPv6 RA packet too short from %s", sfrom); return; } /* RFC 4861 7.1.2 */ if (hoplimit != 255) { logerrx("invalid hoplimit(%d) in RA from %s", hoplimit, sfrom); return; } if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { logerrx("RA from non local address %s", sfrom); return; } if (!(ifp->options->options & DHCPCD_IPV6RS)) { #ifdef DEBUG_RS logerrx("%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 logdebugx("%s: received RA from %s (no link-local)", ifp->name, sfrom); #endif return; } if (ipv6_iffindaddr(ifp, &from->sin6_addr, IN6_IFF_TENTATIVE)) { logdebugx("%s: ignoring RA from ourself %s", ifp->name, sfrom); return; } /* * Because we preserve RA's and expire them quickly after * carrier up, it's important to reset the kernels notion of * reachable timers back to default values before applying * new RA values. */ TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (ifp == rap->iface) break; } if (rap != NULL && rap->willexpire) ipv6nd_applyra(ifp); TAILQ_FOREACH(rap, ctx->ra_routers, next) { if (ifp == rap->iface && IN6_ARE_ADDR_EQUAL(&rap->from, &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; } new_data = true; } else new_data = false; if (rap == NULL) { rap = calloc(1, sizeof(*rap)); if (rap == NULL) { logerr(__func__); return; } rap->iface = ifp; rap->from = from->sin6_addr; strlcpy(rap->sfrom, sfrom, sizeof(rap->sfrom)); TAILQ_INIT(&rap->addrs); new_rap = true; rap->isreachable = true; } else new_rap = false; if (rap->data_len == 0) { rap->data = malloc(len); if (rap->data == NULL) { logerr(__func__); if (new_rap) free(rap); return; } memcpy(rap->data, icp, len); rap->data_len = len; } /* We could change the debug level based on new_data, but some * routers like to decrease the advertised valid and preferred times * in accordance with the own prefix times which would result in too * much needless log spam. */ if (rap->willexpire) new_data = true; loglevel = new_rap || rap->willexpire || !rap->isreachable ? LOG_INFO : LOG_DEBUG; logmessage(loglevel, "%s: Router Advertisement from %s", ifp->name, rap->sfrom); clock_gettime(CLOCK_MONOTONIC, &rap->acquired); rap->flags = nd_ra->nd_ra_flags_reserved; old_lifetime = rap->lifetime; rap->lifetime = ntohs(nd_ra->nd_ra_router_lifetime); if (!new_rap && rap->lifetime == 0 && old_lifetime != 0) logwarnx("%s: %s: no longer a default router", ifp->name, rap->sfrom); if (nd_ra->nd_ra_curhoplimit != 0) rap->hoplimit = nd_ra->nd_ra_curhoplimit; else rap->hoplimit = IPV6_DEFHLIM; if (nd_ra->nd_ra_reachable != 0) { rap->reachable = ntohl(nd_ra->nd_ra_reachable); if (rap->reachable > MAX_REACHABLE_TIME) rap->reachable = 0; } else rap->reachable = REACHABLE_TIME; if (nd_ra->nd_ra_retransmit != 0) rap->retrans = ntohl(nd_ra->nd_ra_retransmit); else rap->retrans = RETRANS_TIMER; rap->expired = rap->willexpire = rap->doexpire = false; rap->hasdns = false; rap->isreachable = true; has_address = false; rap->mtu = 0; #ifdef IPV6_AF_TEMPORARY ipv6_markaddrsstale(ifp, IPV6_AF_TEMPORARY); #endif TAILQ_FOREACH(ia, &rap->addrs, next) { ia->flags |= IPV6_AF_STALE; } len -= sizeof(struct nd_router_advert); p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); for (; len > 0; p += olen, len -= olen) { if (len < sizeof(ndo)) { logerrx("%s: short option", ifp->name); break; } memcpy(&ndo, p, sizeof(ndo)); olen = (size_t)ndo.nd_opt_len * 8; if (olen == 0) { logerrx("%s: zero length option", ifp->name); break; } if (olen > len) { logerrx("%s: option length exceeds message", ifp->name); break; } if (has_option_mask(ifp->options->rejectmasknd, ndo.nd_opt_type)) { for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) { if (dho->option == ndo.nd_opt_type) break; } if (dho != NULL) logwarnx("%s: reject RA (option %s) from %s", ifp->name, dho->var, rap->sfrom); else logwarnx("%s: reject RA (option %d) from %s", ifp->name, ndo.nd_opt_type, rap->sfrom); if (new_rap) ipv6nd_removefreedrop_ra(rap, 0, 0); else ipv6nd_free_ra(rap); return; } if (has_option_mask(ifp->options->nomasknd, ndo.nd_opt_type)) continue; switch (ndo.nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: loglevel = new_data ? LOG_ERR : LOG_DEBUG; if (ndo.nd_opt_len != 4) { logmessage(loglevel, "%s: invalid option len for prefix", ifp->name); continue; } memcpy(&pi, p, sizeof(pi)); if (pi.nd_opt_pi_prefix_len > 128) { logmessage(loglevel, "%s: invalid prefix len", ifp->name); continue; } /* nd_opt_pi_prefix is not aligned. */ memcpy(&pi_prefix, &pi.nd_opt_pi_prefix, sizeof(pi_prefix)); if (IN6_IS_ADDR_MULTICAST(&pi_prefix) || IN6_IS_ADDR_LINKLOCAL(&pi_prefix)) { logmessage(loglevel, "%s: invalid prefix in RA", ifp->name); continue; } if (ntohl(pi.nd_opt_pi_preferred_time) > ntohl(pi.nd_opt_pi_valid_time)) { logmessage(loglevel, "%s: pltime > vltime", ifp->name); continue; } flags = IPV6_AF_RAPFX; /* If no flags are set, that means the prefix is * available via the router. */ if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) flags |= IPV6_AF_ONLINK; if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO && rap->iface->options->options & DHCPCD_IPV6RA_AUTOCONF) flags |= IPV6_AF_AUTOCONF; if (pi.nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ROUTER) flags |= IPV6_AF_ROUTER; ia = ipv6nd_rapfindprefix(rap, &pi_prefix, pi.nd_opt_pi_prefix_len); if (ia == NULL) { ia = ipv6_newaddr(rap->iface, &pi_prefix, pi.nd_opt_pi_prefix_len, flags); if (ia == NULL) break; ia->prefix = pi_prefix; if (flags & IPV6_AF_AUTOCONF) ia->dadcallback = ipv6nd_dadcallback; ia->created = ia->acquired = rap->acquired; TAILQ_INSERT_TAIL(&rap->addrs, ia, 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 (flags & IPV6_AF_AUTOCONF && ipv6_iffindaddr(ifp, &ia->addr, IN6_IFF_NOTUSEABLE) && ipv6_settemptime(ia, 0)) new_ia = false; else new_ia = true; #endif } else { #ifdef IPV6_MANAGETEMPADDR new_ia = false; #endif ia->flags |= flags; ia->flags &= ~IPV6_AF_STALE; ia->acquired = rap->acquired; } ia->prefix_vltime = ntohl(pi.nd_opt_pi_valid_time); ia->prefix_pltime = ntohl(pi.nd_opt_pi_preferred_time); if (ia->prefix_vltime != 0 && ia->flags & IPV6_AF_AUTOCONF) has_address = true; #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.3.3 */ if (ia->flags & IPV6_AF_AUTOCONF && ia->iface->options->options & DHCPCD_SLAACTEMP && IA6_CANAUTOCONF(ia)) { if (!new_ia) { if (ipv6_settemptime(ia, 1) == NULL) new_ia = true; } if (new_ia && ia->prefix_pltime) { if (ipv6_createtempaddr(ia, &ia->acquired) == NULL) logerr("ipv6_createtempaddr"); } } #endif break; case ND_OPT_MTU: if (len < sizeof(mtu)) { logmessage(loglevel, "%s: short MTU option", ifp->name); break; } memcpy(&mtu, p, sizeof(mtu)); mtu.nd_opt_mtu_mtu = ntohl(mtu.nd_opt_mtu_mtu); if (mtu.nd_opt_mtu_mtu < IPV6_MMTU) { logmessage(loglevel, "%s: invalid MTU %d", ifp->name, mtu.nd_opt_mtu_mtu); break; } ifmtu = if_getmtu(ifp); if (ifmtu == -1) logerr("if_getmtu"); else if (mtu.nd_opt_mtu_mtu > (uint32_t)ifmtu) { logmessage(loglevel, "%s: advertised MTU %d" " is greater than link MTU %d", ifp->name, mtu.nd_opt_mtu_mtu, ifmtu); rap->mtu = (uint32_t)ifmtu; } else rap->mtu = mtu.nd_opt_mtu_mtu; break; case ND_OPT_RDNSS: if (len < sizeof(rdnss)) { logmessage(loglevel, "%s: short RDNSS option", ifp->name); break; } memcpy(&rdnss, p, sizeof(rdnss)); if (rdnss.nd_opt_rdnss_lifetime && rdnss.nd_opt_rdnss_len > 1) rap->hasdns = 1; break; default: continue; } } for (i = 0, dho = ctx->nd_opts; i < ctx->nd_opts_len; i++, dho++) { if (has_option_mask(ifp->options->requiremasknd, dho->option)) { logwarnx("%s: reject RA (no option %s) from %s", ifp->name, dho->var, rap->sfrom); if (new_rap) ipv6nd_removefreedrop_ra(rap, 0, 0); else ipv6nd_free_ra(rap); return; } } TAILQ_FOREACH(ia, &rap->addrs, next) { if (!(ia->flags & IPV6_AF_STALE) || ia->prefix_pltime == 0) continue; if (ipv6nd_findmarkstale(rap, ia, false) != NULL) continue; ipv6nd_findmarkstale(rap, ia, true); logdebugx("%s: %s: became stale", ifp->name, ia->saddr); /* Technically this violates RFC 4861 6.3.4, * but we need a mechanism to tell the kernel to * try and prefer other addresses. */ ia->prefix_pltime = 0; } if (new_data && !has_address && rap->lifetime && !ipv6_anyglobal(ifp)) logwarnx("%s: no global addresses for default route", ifp->name); if (new_rap) TAILQ_INSERT_TAIL(ctx->ra_routers, rap, next); if (new_data) ipv6nd_sortrouters(ifp->ctx); if (ifp->ctx->options & DHCPCD_TEST) { script_runreason(ifp, "TEST"); goto handle_flag; } if (!(ifp->options->options & DHCPCD_CONFIGURE)) goto run; ipv6nd_applyra(ifp); ipv6_addaddrs(&rap->addrs); #ifdef IPV6_MANAGETEMPADDR ipv6_addtempaddrs(ifp, &rap->acquired); #endif rt_build(ifp->ctx, AF_INET6); run: ipv6nd_scriptrun(rap); 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; /* Only log a DHCPv6 start error if compiled in or debugging is enabled. */ #ifdef DHCP6 #define LOG_DHCP6 logerr #else #define LOG_DHCP6 logdebug #endif if (rap->flags & ND_RA_FLAG_MANAGED) { if (new_data && dhcp6_start(ifp, DH6S_REQUEST) == -1) LOG_DHCP6("dhcp6_start: %s", ifp->name); } else if (rap->flags & ND_RA_FLAG_OTHER) { if (new_data && dhcp6_start(ifp, DH6S_INFORM) == -1) LOG_DHCP6("dhcp6_start: %s", ifp->name); } else { #ifdef DHCP6 if (new_data) logdebugx("%s: No DHCPv6 instruction in RA", ifp->name); #endif 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); } bool ipv6nd_hasralifetime(const struct interface *ifp, bool lifetime) { const struct ra *rap; if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) if (rap->iface == ifp && !rap->expired && (!lifetime ||rap->lifetime)) return true; } return false; } bool ipv6nd_hasradhcp(const struct interface *ifp, bool managed) { const struct ra *rap; if (ifp->ctx->ra_routers) { TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface == ifp && !rap->expired && !rap->willexpire && ((managed && rap->flags & ND_RA_FLAG_MANAGED) || (!managed && rap->flags & ND_RA_FLAG_OTHER))) return true; } } return false; } static const uint8_t * ipv6nd_getoption(struct dhcpcd_ctx *ctx, size_t *os, unsigned int *code, size_t *len, const uint8_t *od, size_t ol, struct dhcp_opt **oopt) { struct nd_opt_hdr ndo; size_t i; struct dhcp_opt *opt; if (od) { *os = sizeof(ndo); if (ol < *os) { errno = EINVAL; return NULL; } memcpy(&ndo, od, sizeof(ndo)); i = (size_t)(ndo.nd_opt_len * 8); if (i > ol) { errno = EINVAL; return NULL; } *len = i; *code = ndo.nd_opt_type; } for (i = 0, opt = ctx->nd_opts; i < ctx->nd_opts_len; i++, opt++) { if (opt->option == *code) { *oopt = opt; break; } } if (od) return od + sizeof(ndo); return NULL; } ssize_t ipv6nd_env(FILE *fp, const struct interface *ifp) { size_t i, j, n, len, olen; struct ra *rap; char ndprefix[32]; struct dhcp_opt *opt; uint8_t *p; struct nd_opt_hdr ndo; struct ipv6_addr *ia; struct timespec now; int pref; clock_gettime(CLOCK_MONOTONIC, &now); i = n = 0; TAILQ_FOREACH(rap, ifp->ctx->ra_routers, next) { if (rap->iface != ifp || rap->expired) continue; i++; snprintf(ndprefix, sizeof(ndprefix), "nd%zu", i); if (efprintf(fp, "%s_from=%s", ndprefix, rap->sfrom) == -1) return -1; if (efprintf(fp, "%s_acquired=%lld", ndprefix, (long long)rap->acquired.tv_sec) == -1) return -1; if (efprintf(fp, "%s_now=%lld", ndprefix, (long long)now.tv_sec) == -1) return -1; if (efprintf(fp, "%s_hoplimit=%u", ndprefix, rap->hoplimit) == -1) return -1; pref = ipv6nd_rtpref(rap); if (efprintf(fp, "%s_flags=%s%s%s%s%s", ndprefix, rap->flags & ND_RA_FLAG_MANAGED ? "M" : "", rap->flags & ND_RA_FLAG_OTHER ? "O" : "", rap->flags & ND_RA_FLAG_HOME_AGENT ? "H" : "", pref == RTPREF_HIGH ? "h" : pref == RTPREF_LOW ? "l" : "", rap->flags & ND_RA_FLAG_PROXY ? "P" : "") == -1) return -1; if (efprintf(fp, "%s_lifetime=%u", ndprefix, rap->lifetime) == -1) return -1; /* Zero our indexes */ for (j = 0, opt = rap->iface->ctx->nd_opts; j < rap->iface->ctx->nd_opts_len; j++, opt++) dhcp_zero_index(opt); for (j = 0, opt = rap->iface->options->nd_override; j < rap->iface->options->nd_override_len; j++, opt++) dhcp_zero_index(opt); /* Unlike DHCP, ND6 options *may* occur more than once. * There is also no provision for option concatenation * unlike DHCP. */ len = rap->data_len - sizeof(struct nd_router_advert); for (p = rap->data + sizeof(struct nd_router_advert); len >= sizeof(ndo); p += olen, len -= olen) { memcpy(&ndo, p, sizeof(ndo)); olen = (size_t)(ndo.nd_opt_len * 8); if (olen > len) { errno = EINVAL; break; } if (has_option_mask(rap->iface->options->nomasknd, ndo.nd_opt_type)) continue; for (j = 0, opt = rap->iface->options->nd_override; j < rap->iface->options->nd_override_len; j++, opt++) if (opt->option == ndo.nd_opt_type) break; if (j == rap->iface->options->nd_override_len) { for (j = 0, opt = rap->iface->ctx->nd_opts; j < rap->iface->ctx->nd_opts_len; j++, opt++) if (opt->option == ndo.nd_opt_type) break; if (j == rap->iface->ctx->nd_opts_len) opt = NULL; } if (opt == NULL) continue; dhcp_envoption(rap->iface->ctx, fp, ndprefix, rap->iface->name, opt, ipv6nd_getoption, p + sizeof(ndo), olen - sizeof(ndo)); } /* We need to output the addresses we actually made * from the prefix information options as well. */ j = 0; TAILQ_FOREACH(ia, &rap->addrs, next) { if (!(ia->flags & IPV6_AF_AUTOCONF) || #ifdef IPV6_AF_TEMPORARY ia->flags & IPV6_AF_TEMPORARY || #endif !(ia->flags & IPV6_AF_ADDED) || ia->prefix_vltime == 0) continue; if (efprintf(fp, "%s_addr%zu=%s", ndprefix, ++j, ia->saddr) == -1) return -1; } } return 1; } void ipv6nd_handleifa(int cmd, struct ipv6_addr *addr, pid_t pid) { struct ra *rap; /* IPv6 init may not have happened yet if we are learning * existing addresses when dhcpcd starts. */ if (addr->iface->ctx->ra_routers == NULL) return; TAILQ_FOREACH(rap, addr->iface->ctx->ra_routers, next) { if (rap->iface != addr->iface) continue; ipv6_handleifa_addrs(cmd, &rap->addrs, addr, pid); } } void ipv6nd_expirera(void *arg) { struct interface *ifp; struct ra *rap, *ran; struct timespec now; uint32_t elapsed; bool expired, valid; struct ipv6_addr *ia; size_t len, olen; uint8_t *p; struct nd_opt_hdr ndo; #if 0 struct nd_opt_prefix_info pi; #endif struct nd_opt_dnssl dnssl; struct nd_opt_rdnss rdnss; unsigned int next = 0, ltime; size_t nexpired = 0; ifp = arg; clock_gettime(CLOCK_MONOTONIC, &now); expired = false; TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { if (rap->iface != ifp || rap->expired) continue; valid = false; if (rap->lifetime) { elapsed = (uint32_t)eloop_timespec_diff(&now, &rap->acquired, NULL); if (elapsed >= rap->lifetime || rap->doexpire) { if (!rap->expired) { logwarnx("%s: %s: router expired", ifp->name, rap->sfrom); rap->lifetime = 0; expired = true; } } else { valid = true; ltime = rap->lifetime - elapsed; if (next == 0 || ltime < next) next = ltime; } } /* Not every prefix is tied to an address which * the kernel can expire, so we need to handle it ourself. * Also, some OS don't support address lifetimes (Solaris). */ TAILQ_FOREACH(ia, &rap->addrs, next) { if (ia->prefix_vltime == 0) continue; if (ia->prefix_vltime == ND6_INFINITE_LIFETIME && !rap->doexpire) { valid = true; continue; } elapsed = (uint32_t)eloop_timespec_diff(&now, &ia->acquired, NULL); if (elapsed >= ia->prefix_vltime || rap->doexpire) { if (ia->flags & IPV6_AF_ADDED) { logwarnx("%s: expired %s %s", ia->iface->name, ia->flags & IPV6_AF_AUTOCONF ? "address" : "prefix", ia->saddr); if (if_address6(RTM_DELADDR, ia)== -1 && errno != EADDRNOTAVAIL && errno != ENXIO) logerr(__func__); } ia->prefix_vltime = ia->prefix_pltime = 0; ia->flags &= ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED); expired = true; } else { valid = true; ltime = ia->prefix_vltime - elapsed; if (next == 0 || ltime < next) next = ltime; } } /* Work out expiry for ND options */ elapsed = (uint32_t)eloop_timespec_diff(&now, &rap->acquired, NULL); len = rap->data_len - sizeof(struct nd_router_advert); for (p = rap->data + sizeof(struct nd_router_advert); len >= sizeof(ndo); p += olen, len -= olen) { memcpy(&ndo, p, sizeof(ndo)); olen = (size_t)(ndo.nd_opt_len * 8); if (olen > len) { errno = EINVAL; break; } if (has_option_mask(rap->iface->options->nomasknd, ndo.nd_opt_type)) continue; switch (ndo.nd_opt_type) { /* Prefix info is already checked in the above loop. */ #if 0 case ND_OPT_PREFIX_INFORMATION: if (len < sizeof(pi)) break; memcpy(&pi, p, sizeof(pi)); ltime = pi.nd_opt_pi_valid_time; break; #endif case ND_OPT_DNSSL: if (len < sizeof(dnssl)) continue; memcpy(&dnssl, p, sizeof(dnssl)); ltime = dnssl.nd_opt_dnssl_lifetime; break; case ND_OPT_RDNSS: if (len < sizeof(rdnss)) continue; memcpy(&rdnss, p, sizeof(rdnss)); ltime = rdnss.nd_opt_rdnss_lifetime; break; default: continue; } if (ltime == 0) continue; if (rap->doexpire) { expired = true; continue; } if (ltime == ND6_INFINITE_LIFETIME) { valid = true; continue; } ltime = ntohl(ltime); if (elapsed >= ltime) { expired = true; continue; } valid = true; ltime -= elapsed; if (next == 0 || ltime < next) next = ltime; } if (valid) continue; /* Router has expired. Let's not keep a lot of them. */ rap->expired = true; if (++nexpired > EXPIRED_MAX) ipv6nd_free_ra(rap); } if (next != 0) eloop_timeout_add_sec(ifp->ctx->eloop, next, ipv6nd_expirera, ifp); if (expired) { logwarnx("%s: part of a Router Advertisement expired", ifp->name); ipv6nd_sortrouters(ifp->ctx); ipv6nd_applyra(ifp); rt_build(ifp->ctx, AF_INET6); script_runreason(ifp, "ROUTERADVERT"); } } void ipv6nd_drop(struct interface *ifp) { struct ra *rap, *ran; bool expired = false; if (ifp->ctx->ra_routers == NULL) return; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); TAILQ_FOREACH_SAFE(rap, ifp->ctx->ra_routers, next, ran) { if (rap->iface == ifp) { rap->expired = expired = true; ipv6nd_drop_ra(rap); } } if (expired) { ipv6nd_applyra(ifp); rt_build(ifp->ctx, AF_INET6); if ((ifp->options->options & DHCPCD_NODROP) != DHCPCD_NODROP) script_runreason(ifp, "ROUTERADVERT"); } } void ipv6nd_recvmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg) { struct sockaddr_in6 *from = (struct sockaddr_in6 *)msg->msg_name; char sfrom[INET6_ADDRSTRLEN]; int hoplimit = 0; struct icmp6_hdr *icp; struct interface *ifp; size_t len = msg->msg_iov[0].iov_len; inet_ntop(AF_INET6, &from->sin6_addr, sfrom, sizeof(sfrom)); if ((size_t)len < sizeof(struct icmp6_hdr)) { logerrx("IPv6 ICMP packet too short from %s", sfrom); return; } ifp = if_findifpfromcmsg(ctx, msg, &hoplimit); if (ifp == NULL) { logerr(__func__); return; } /* Don't do anything if the user hasn't configured it. */ if (ifp->active != IF_ACTIVE_USER || !(ifp->options->options & DHCPCD_IPV6)) return; icp = (struct icmp6_hdr *)msg->msg_iov[0].iov_base; if (icp->icmp6_code == 0) { switch(icp->icmp6_type) { case ND_ROUTER_ADVERT: ipv6nd_handlera(ctx, from, sfrom, ifp, icp, (size_t)len, hoplimit); return; } } logerrx("invalid IPv6 type %d or code %d from %s", icp->icmp6_type, icp->icmp6_code, sfrom); } static void ipv6nd_handledata(void *arg) { struct dhcpcd_ctx *ctx; int fd; struct sockaddr_in6 from; union { struct icmp6_hdr hdr; uint8_t buf[64 * 1024]; /* Maximum ICMPv6 size */ } iovbuf; struct iovec iov = { .iov_base = iovbuf.buf, .iov_len = sizeof(iovbuf.buf), }; union { struct cmsghdr hdr; uint8_t buf[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))]; } cmsgbuf = { .buf = { 0 } }; struct msghdr msg = { .msg_name = &from, .msg_namelen = sizeof(from), .msg_iov = &iov, .msg_iovlen = 1, .msg_control = cmsgbuf.buf, .msg_controllen = sizeof(cmsgbuf.buf), }; ssize_t len; #ifdef __sun struct interface *ifp; struct rs_state *state; ifp = arg; state = RS_STATE(ifp); ctx = ifp->ctx; fd = state->nd_fd; #else ctx = arg; fd = ctx->nd_fd; #endif len = recvmsg(fd, &msg, 0); if (len == -1) { logerr(__func__); return; } iov.iov_len = (size_t)len; ipv6nd_recvmsg(ctx, &msg); } static void ipv6nd_startrs1(void *arg) { struct interface *ifp = arg; struct rs_state *state; loginfox("%s: soliciting an IPv6 router", ifp->name); state = RS_STATE(ifp); if (state == NULL) { ifp->if_data[IF_DATA_IPV6ND] = calloc(1, sizeof(*state)); state = RS_STATE(ifp); if (state == NULL) { logerr(__func__); return; } #ifdef __sun state->nd_fd = -1; #endif } /* Always make a new probe as the underlying hardware * address could have changed. */ ipv6nd_makersprobe(ifp); if (state->rs == NULL) { logerr(__func__); return; } state->retrans = RETRANS_TIMER; state->rsprobes = 0; ipv6nd_sendrsprobe(ifp); } void ipv6nd_startrs(struct interface *ifp) { unsigned int delay; eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp); if (!(ifp->options->options & DHCPCD_INITIAL_DELAY)) { ipv6nd_startrs1(ifp); return; } delay = arc4random_uniform(MAX_RTR_SOLICITATION_DELAY * MSEC_PER_SEC); logdebugx("%s: delaying IPv6 router solicitation for %0.1f seconds", ifp->name, (float)delay / MSEC_PER_SEC); eloop_timeout_add_msec(ifp->ctx->eloop, delay, ipv6nd_startrs1, ifp); return; }