| /* |
| * dhcpcd - DHCP client daemon |
| * Copyright (c) 2006-2015 Roy Marples <[email protected]> |
| * 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; |
| } |