]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
RA: expire RDNSS and DNSSL entries
authorRoy Marples <roy@marples.name>
Fri, 14 Jun 2019 12:53:51 +0000 (13:53 +0100)
committerRoy Marples <roy@marples.name>
Fri, 14 Jun 2019 12:53:51 +0000 (13:53 +0100)
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!

hooks/20-resolv.conf
src/ipv6.c
src/ipv6nd.c

index 61c75e82141c9cabf58e355fe4549c064d2b33d8..cf3237499548b211133eec659c012275260e53c3 100644 (file)
@@ -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"
index de8570f025c2f11ef2e497634835bcad46abdf02..e9e6dc257025dd69cf9022d9cfcdfadf199446d7 100644 (file)
@@ -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;
 }
index 947257adb1adb252622c96ba10046e6db68811b3..3ad8fecdd2f14e52e100bb4dca4b2f0ba5f9832c 100644 (file)
@@ -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, &lt, &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, &lt, &expire);
+                       if (timespeccmp(&now, &expire, >)) {
+                               expired = true;
+                               continue;
+                       }
+
+                       timespecsub(&expire, &now, &lt);
+                       if (!timespecisset(&next) ||
+                           timespeccmp(&next, &lt, >))
+                       {
+                               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");
        }