Mercurial > hg > dhcpcd
changeset 4530:999c0c21711d draft
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!
| author | Roy Marples <roy@marples.name> |
|---|---|
| date | Fri, 14 Jun 2019 13:53:51 +0100 |
| parents | edec0822a9d7 |
| children | a44c7ef76f2d |
| files | hooks/20-resolv.conf src/ipv6.c src/ipv6nd.c |
| diffstat | 3 files changed, 128 insertions(+), 62 deletions(-) [+] |
line wrap: on
line diff
--- a/hooks/20-resolv.conf Wed Jun 12 19:44:16 2019 +0100 +++ b/hooks/20-resolv.conf Fri Jun 14 13:53:51 2019 +0100 @@ -69,30 +69,26 @@ } # 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 - fi - eval ltime=\$nd${i}_dnssl${j}_lifetime - if [ -z "$ltime" ] || [ "$ltime" = 0 ]; then - dnssl= - else - eval dnssl=\$nd${i}_dnssl${j}_search + [ -n "$rdnss" ] && new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" fi - [ -z "${rdnss}${dnssl}" ] && return 1 + 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 - [ -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 @@ 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"
--- a/src/ipv6.c Wed Jun 12 19:44:16 2019 +0100 +++ b/src/ipv6.c Fri Jun 14 13:53:51 2019 +0100 @@ -2240,15 +2240,14 @@ } 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 @@ 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 @@ 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 @@ 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; }
--- a/src/ipv6nd.c Wed Jun 12 19:44:16 2019 +0100 +++ b/src/ipv6nd.c Fri Jun 14 13:53:51 2019 +0100 @@ -105,6 +105,9 @@ #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 @@ 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 @@ 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 @@ * 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 @@ 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 @@ 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 @@ } } - /* 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; - /* No valid lifetimes are left on the RA, so we might - * as well punt it. */ - if (!valid && !validone) + 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; + + /* 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 @@ 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"); }
