From: Roy Marples Date: Fri, 14 Jun 2019 12:53:51 +0000 (+0100) Subject: RA: expire RDNSS and DNSSL entries X-Git-Tag: v8.0.0~34^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1900556025e2d36418293ab63fed30554fa53f0f;p=thirdparty%2Fdhcpcd.git RA: expire RDNSS and DNSSL entries This allows us to remember N fully expired RA's which works around an obscure issue where a received RA has no lifetime or any prefixes with lifetimes but does have an instruction to start DHCP6. It was harmless but filled the log with spam and now there is no log spam! --- diff --git a/hooks/20-resolv.conf b/hooks/20-resolv.conf index 61c75e82..cf323749 100644 --- a/hooks/20-resolv.conf +++ b/hooks/20-resolv.conf @@ -69,30 +69,26 @@ build_resolv_conf() } # Extract any ND DNS options from the RA -# For now, we ignore the lifetime of the DNS options unless they -# are absent or zero. -# In this case they are removed from consideration. -# See draft-gont-6man-slaac-dns-config-issues-01 for issues -# regarding DNS option lifetime in ND messages. +# Obey the lifetimes eval_nd_dns() { - eval ltime=\$nd${i}_rdnss${j}_lifetime - if [ -z "$ltime" ] || [ "$ltime" = 0 ]; then - rdnss= - else + + eval rdnsstime=\$nd${i}_rdnss${j}_lifetime + [ -z "$rdnsstime" ] && return 1 + ltime=$(($rdnsstime - $offset)) + if [ "$ltime" -gt 0 ]; then eval rdnss=\$nd${i}_rdnss${j}_servers + [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" fi - eval ltime=\$nd${i}_dnssl${j}_lifetime - if [ -z "$ltime" ] || [ "$ltime" = 0 ]; then - dnssl= - else + + eval dnssltime=\$nd${i}_dnssl${j}_lifetime + [ -z "$dnssltime" ] && return 1 + ltime=$(($dnssltime - $offset)) + if [ "$ltime" -gt 0 ]; then eval dnssl=\$nd${i}_dnssl${j}_search + [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" fi - [ -z "${rdnss}${dnssl}" ] && return 1 - - [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" - [ -n "$dnssl" ] && new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" j=$(($j + 1)) return 0 } @@ -106,12 +102,16 @@ add_resolv_conf() i=1 j=1 while true; do + eval acquired=\$nd${i}_acquired + [ -z "$acquired" ] && break + eval now=\$nd${i}_now + [ -z "$now" ] && break + offset=$(($now - $acquired)) while true; do eval_nd_dns || break done i=$(($i + 1)) j=1 - eval_nd_dns || break done [ -n "$new_rdnss" ] && \ new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss" diff --git a/src/ipv6.c b/src/ipv6.c index de8570f0..e9e6dc25 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -2240,15 +2240,14 @@ inet6_staticroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx) } static int -inet6_raroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, int expired, - bool *have_default) +inet6_raroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx) { struct rt *rt; struct ra *rap; const struct ipv6_addr *addr; TAILQ_FOREACH(rap, ctx->ra_routers, next) { - if (rap->expired != expired) + if (rap->expired) continue; TAILQ_FOREACH(addr, &rap->addrs, next) { if (addr->prefix_vltime == 0) @@ -2259,15 +2258,13 @@ inet6_raroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, int expired, TAILQ_INSERT_TAIL(routes, rt, rt_next); } } - if (rap->lifetime) { - rt = inet6_makerouter(rap); - if (rt) { - rt->rt_dflags |= RTDF_RA; - TAILQ_INSERT_TAIL(routes, rt, rt_next); - if (have_default) - *have_default = true; - } - } + if (rap->lifetime == 0) + continue; + rt = inet6_makerouter(rap); + if (rt == NULL) + continue; + rt->rt_dflags |= RTDF_RA; + TAILQ_INSERT_TAIL(routes, rt, rt_next); } return 0; } @@ -2301,15 +2298,13 @@ inet6_dhcproutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, bool inet6_getroutes(struct dhcpcd_ctx *ctx, struct rt_head *routes) { - bool have_default; /* Should static take priority? */ if (inet6_staticroutes(routes, ctx) == -1) return false; /* First add reachable routers and their prefixes */ - have_default = false; - if (inet6_raroutes(routes, ctx, 0, &have_default) == -1) + if (inet6_raroutes(routes, ctx) == -1) return false; #ifdef DHCP6 @@ -2322,21 +2317,5 @@ inet6_getroutes(struct dhcpcd_ctx *ctx, struct rt_head *routes) return false; #endif -#ifdef HAVE_ROUTE_METRIC - /* If we have an unreachable router, we really do need to remove the - * route to it beause it could be a lower metric than a reachable - * router. Of course, we should at least have some routers if all - * are unreachable. */ - if (!have_default) { -#endif - /* Add our non-reachable routers and prefixes - * Unsure if this is needed, but it's a close match to kernel - * behaviour */ - if (inet6_raroutes(routes, ctx, 1, NULL) == -1) - return false; -#ifdef HAVE_ROUTE_METRIC - } -#endif - return true; } diff --git a/src/ipv6nd.c b/src/ipv6nd.c index 947257ad..3ad8fecd 100644 --- a/src/ipv6nd.c +++ b/src/ipv6nd.c @@ -105,6 +105,9 @@ __CTASSERT(sizeof(struct nd_opt_rdnss) == 8); #define RTPREF_RESERVED (-2) #define RTPREF_INVALID (-3) /* internal */ +#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 */ @@ -1228,6 +1231,10 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, break; case ND_OPT_MTU: + if (len < sizeof(mtu)) { + logerrx("%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) { @@ -1239,6 +1246,10 @@ ipv6nd_handlera(struct dhcpcd_ctx *ctx, break; case ND_OPT_RDNSS: + if (len < sizeof(rdnss)) { + logerrx("%s: short RDNSS option", ifp->name); + break; + } memcpy(&rdnss, p, sizeof(rdnss)); if (rdnss.nd_opt_rdnss_lifetime && rdnss.nd_opt_rdnss_len > 1) @@ -1480,11 +1491,12 @@ ipv6nd_env(char **env, const char *prefix, const struct interface *ifp) * from the prefix information options as well. */ j = 0; TAILQ_FOREACH(ia, &rap->addrs, next) { - if (!(ia->flags & IPV6_AF_AUTOCONF) + if (!(ia->flags & IPV6_AF_AUTOCONF) || #ifdef IPV6_AF_TEMPORARY - || ia->flags & IPV6_AF_TEMPORARY + ia->flags & IPV6_AF_TEMPORARY || #endif - ) + !(ia->flags & IPV6_AF_ADDED) || + ia->prefix_vltime == 0) continue; j++; if (env) { @@ -1522,6 +1534,16 @@ ipv6nd_expirera(void *arg) struct timespec now, lt, expire, next; bool expired, valid, validone; 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; + uint32_t ltime; + size_t nexpired = 0; ifp = arg; clock_gettime(CLOCK_MONOTONIC, &now); @@ -1536,8 +1558,7 @@ ipv6nd_expirera(void *arg) lt.tv_sec = (time_t)rap->lifetime; lt.tv_nsec = 0; timespecadd(&rap->acquired, <, &expire); - if (rap->lifetime == 0 || timespeccmp(&now, &expire, >)) - { + if (timespeccmp(&now, &expire, >)) { if (!rap->expired) { logwarnx("%s: %s: router expired", ifp->name, rap->sfrom); @@ -1588,14 +1609,79 @@ ipv6nd_expirera(void *arg) } } - /* XXX FixMe! - * We need to extract the lifetime from each option and check - * if that has expired or not. - * If it has, zero the option out in the returned data. */ + /* Work out expiry for ND options */ + 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)) + break; + memcpy(&dnssl, p, sizeof(dnssl)); + ltime = dnssl.nd_opt_dnssl_lifetime; + break; + case ND_OPT_RDNSS: + if (len < sizeof(rdnss)) + break; + memcpy(&rdnss, p, sizeof(rdnss)); + ltime = rdnss.nd_opt_rdnss_lifetime; + break; + default: + continue; + } + + if (ltime == 0) + continue; + if (ltime == ND6_INFINITE_LIFETIME) { + validone = true; + continue; + } + + lt.tv_sec = (time_t)ntohl(ltime); + lt.tv_nsec = 0; + timespecadd(&rap->acquired, <, &expire); + if (timespeccmp(&now, &expire, >)) { + expired = true; + continue; + } + + timespecsub(&expire, &now, <); + if (!timespecisset(&next) || + timespeccmp(&next, <, >)) + { + next = lt; + validone = true; + } + } + + if (valid || validone) + continue; - /* No valid lifetimes are left on the RA, so we might - * as well punt it. */ - if (!valid && !validone) + /* Router has expired. Let's not keep a lot of them. + * We should work out if all the options have expired .... */ + if (++nexpired > EXPIRED_MAX) ipv6nd_free_ra(rap); } @@ -1603,6 +1689,7 @@ ipv6nd_expirera(void *arg) eloop_timeout_add_tv(ifp->ctx->eloop, &next, ipv6nd_expirera, ifp); if (expired) { + logwarnx("%s: part of Router Advertisement expired", ifp->name); rt_build(ifp->ctx, AF_INET6); script_runreason(ifp, "ROUTERADVERT"); }