Mercurial > hg > dhcpcd
view src/ipv6nd.c @ 5557:e65d193a1960 draft
Linux: Support wireless IP roaming
This is achieved by checking that the interface is wireless,
IFF_UP and IFF_LOWER_UP are present, but IFF_RUNNING is missing.
This gives exactly the same support as modern NetBSD when carrier
loss is detected, but without the address verifications when the
carrier comes back as that needs to be handled in the kernel.
While IP setup is maintained, other configuration data is discarded.
Note that this should be improved in the future.
Thanks to Boris Krasnovskiy <borkra@gmail.com> for helping with this.
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Sat, 12 Dec 2020 13:12:26 +0000 |
| parents | a0d828e25482 |
| children |
line wrap: on
line source
/* SPDX-License-Identifier: BSD-2-Clause */ /* * dhcpcd - IPv6 ND handling * Copyright (c) 2006-2020 Roy Marples <roy@marples.name> * All rights reserved * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include <sys/ioctl.h> #include <sys/param.h> #include <sys/socket.h> #include <net/if.h> #include <net/route.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <assert.h> #include <errno.h> #include <fcntl.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <syslog.h> #include <unistd.h> #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; }
