]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Update route if lifetime needs changing (#441)
authorColin McInnes <colin.mcinnes@vecima.com>
Wed, 5 Feb 2025 14:06:10 +0000 (08:06 -0600)
committerGitHub <noreply@github.com>
Wed, 5 Feb 2025 14:06:10 +0000 (14:06 +0000)
Everything about IPv6 is lifetime, not expiry.
Only linux routes have an expiry.

OK, this is not consistent at all!
In the kernel RTA_EXPIRES is only set for IPv4 multicast routes
when emitting route changes and only read for IPv6 routes when
userland makes route changes.
We cannot set this for non IPv6 routes currently.

To make it worse, we set a UINT32 for IPv6 routes but read a
UINT64 for IPv4 multicast routes.

To make this even more totally bonkers, the expiry we set for
the IPv6 route can be read back via RTA_CACHEINE rta_expires
but we need to convert to divide it by hz.

Now we can read kernel route lifetime correctly, we can compare this to what we think it should be when building the routing table (allowing for some deviation due to processing time) and update if needed.

Fixes #428.

src/if-linux.c
src/ipv6.c
src/route.c
src/route.h

index 96bcbbb971e79f45aa7b13717c3c60ff58d76c6d..b6317c35d018a76555428f7f50a1573f2615a987 100644 (file)
@@ -752,11 +752,34 @@ if_copyrt(struct dhcpcd_ctx *ctx, struct rt *rt, struct nlmsghdr *nlm)
                        }
                        break;
                }
-               case RTA_EXPIRES:
+#ifdef HAVE_ROUTE_LIFETIME
+               case RTA_CACHEINFO:
                {
-                       rt->rt_expires = *(uint32_t *)RTA_DATA(rta);
+                       struct rta_cacheinfo ci;
+                       static long hz;
+
+                       if (hz == 0) {
+                               hz = sysconf(_SC_CLK_TCK);
+                               if (hz == -1)
+                                       hz = CLOCKS_PER_SEC;
+                       }
+
+                       memcpy(&ci, RTA_DATA(rta), sizeof(ci));
+                       rt->rt_lifetime = (uint32_t)(ci.rta_expires / hz);
                        break;
                }
+#endif
+#if 0
+               case RTA_EXPIRES:
+                       /* Reading the kernel source, this is only
+                        * emitted by IPv4 multicast routes as a UINT64.
+                        * Although we can set it for IPv6 routes as a UINT32,
+                        * the kernel will massage the value to HZ and put it
+                        * into RTA_CACHINFO as read above.
+                        * Gotta love that consistency! */
+                       rt->rt_lifetime = (uint32_t)*(uint64_t *)RTA_DATA(rta);
+                       break;
+#endif
                }
 
                if (sa != NULL) {
@@ -1740,9 +1763,10 @@ if_route(unsigned char cmd, const struct rt *rt)
        if (!sa_is_loopback(&rt->rt_gateway))
                add_attr_32(&nlm.hdr, sizeof(nlm), RTA_OIF, rt->rt_ifp->index);
 
-       /* add route lifetime */
-       if (rt->rt_expires != 0)
-               add_attr_32(&nlm.hdr, sizeof(nlm), RTA_EXPIRES, rt->rt_expires);
+#ifdef HAVE_ROUTE_LIFETIME
+       if (rt->rt_lifetime != 0)
+               add_attr_32(&nlm.hdr, sizeof(nlm), RTA_EXPIRES,rt->rt_lifetime);
+#endif
 
        if (rt->rt_metric != 0)
                add_attr_32(&nlm.hdr, sizeof(nlm), RTA_PRIORITY,
index bc48a2a1fa4befb82a481146f6e39186eb30b7e2..5cb74151d11d8e6da5574ba56f502292c76f3b15 100644 (file)
@@ -2301,9 +2301,10 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
 #ifdef HAVE_ROUTE_PREF
                        rt->rt_pref = ipv6nd_rtpref(rinfo->flags);
 #endif
-                       rt->rt_expires = lifetime_left(rinfo->lifetime,
+#ifdef HAVE_ROUTE_LIFETIME
+                       rt->rt_lifetime = lifetime_left(rinfo->lifetime,
                            &rinfo->acquired, &now);
-
+#endif
                        rt_proto_add(routes, rt);
                }
 
@@ -2317,9 +2318,11 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
 #ifdef HAVE_ROUTE_PREF
                                rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
-                               rt->rt_expires =
+#ifdef HAVE_ROUTE_LIFETIME
+                               rt->rt_lifetime =
                                    lifetime_left(addr->prefix_vltime,
                                    &addr->acquired, &now);
+#endif
 
                                rt_proto_add(routes, rt);
                        }
@@ -2352,8 +2355,10 @@ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
 #ifdef HAVE_ROUTE_PREF
                rt->rt_pref = ipv6nd_rtpref(rap->flags);
 #endif
-               rt->rt_expires = lifetime_left(rap->lifetime,
+#ifdef HAVE_ROUTE_LIFETIME
+               rt->rt_lifetime = lifetime_left(rap->lifetime,
                    &rap->acquired, &now);
+#endif
 
                rt_proto_add(routes, rt);
        }
index 483d4f65f6b804b6f786ff5c0e3ba39a40ffbd3e..d56b9d307972c08777bc9294fbb9cb4c34c16e34 100644 (file)
@@ -502,6 +502,32 @@ rt_recvrt(int cmd, const struct rt *rt, pid_t pid)
 #endif
 }
 
+/* Compare miscellaneous route details */
+static bool
+rt_cmp_misc(struct rt *nrt, struct rt *ort)
+{
+       /* MTU changed */
+       if (ort->rt_mtu != nrt->rt_mtu)
+               return false;
+
+#ifdef HAVE_ROUTE_LIFETIME
+       uint32_t deviation;
+
+       /* There might be a minor difference between kernel route
+        * lifetime and our lifetime due to processing times.
+        * We allow a small deviation to avoid needless route changes.
+        * dhcpcd will expire the route regardless of route lifetime support. */
+       if (nrt->rt_lifetime > ort->rt_lifetime)
+               deviation = nrt->rt_lifetime - ort->rt_lifetime;
+       else
+               deviation = ort->rt_lifetime - nrt->rt_lifetime;
+       if (deviation > RTLIFETIME_DEV_MAX)
+               return false;
+#endif
+
+       return true;
+}
+
 static bool
 rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
 {
@@ -540,7 +566,7 @@ rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
 #endif
                    sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)))
                {
-                       if (ort->rt_mtu == nrt->rt_mtu)
+                       if (rt_cmp_misc(nrt, ort))
                                return true;
                        change = true;
                        kroute = true;
@@ -555,7 +581,7 @@ rt_add(rb_tree_t *kroutes, struct rt *nrt, struct rt *ort)
            rt_cmp_netmask(ort, nrt) == 0 &&
            sa_cmp(&ort->rt_gateway, &nrt->rt_gateway) == 0)
        {
-               if (ort->rt_mtu == nrt->rt_mtu)
+               if (rt_cmp_misc(nrt, ort))
                        return true;
                change = true;
        }
@@ -678,7 +704,7 @@ rt_doroute(rb_tree_t *kroutes, struct rt *rt)
                    !rt_cmp(rt, or) ||
                    (rt->rt_ifa.sa_family != AF_UNSPEC &&
                    sa_cmp(&or->rt_ifa, &rt->rt_ifa) != 0) ||
-                   or->rt_mtu != rt->rt_mtu)
+                   !rt_cmp_misc(rt, or))
                {
                        if (!rt_add(kroutes, rt, or))
                                return false;
index 6be2e1eaed69998c70efd01a46d649a40d57e8a6..5c06d1da786bb9a10f526e94101790d62e78a2e1 100644 (file)
 #  define HAVE_ROUTE_METRIC 1
 # endif
 #endif
+#ifndef HAVE_ROUTE_LIFETIME
+# if defined(__linux__)
+#  define HAVE_ROUTE_LIFETIME 1 /* For IPv6 routes only */
+# endif
+#endif
 
 #ifdef __linux__
 # include <linux/version.h> /* RTA_PREF is only an enum.... */
@@ -120,7 +125,10 @@ struct rt {
 #define        RTDF_GATELINK           0x40            /* Gateway is on link */
        size_t                  rt_order;
        rb_node_t               rt_tree;
-       uint32_t                rt_expires;     /* current lifetime of route */
+#ifdef HAVE_ROUTE_LIFETIME
+       uint32_t                rt_lifetime;    /* current lifetime of route */
+#define        RTLIFETIME_DEV_MAX      2               /* max deviation for cmp */
+#endif
 };
 
 extern const rb_tree_ops_t rt_compare_list_ops;