From 46822c2ef9c0fee400390a4b5bb5bc39be077487 Mon Sep 17 00:00:00 2001 From: Colin McInnes Date: Wed, 5 Feb 2025 08:06:10 -0600 Subject: [PATCH] Update route if lifetime needs changing (#441) 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 | 34 +++++++++++++++++++++++++++++----- src/ipv6.c | 13 +++++++++---- src/route.c | 32 +++++++++++++++++++++++++++++--- src/route.h | 10 +++++++++- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/if-linux.c b/src/if-linux.c index 96bcbbb9..b6317c35 100644 --- a/src/if-linux.c +++ b/src/if-linux.c @@ -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, diff --git a/src/ipv6.c b/src/ipv6.c index bc48a2a1..5cb74151 100644 --- a/src/ipv6.c +++ b/src/ipv6.c @@ -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); } diff --git a/src/route.c b/src/route.c index 483d4f65..d56b9d30 100644 --- a/src/route.c +++ b/src/route.c @@ -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; diff --git a/src/route.h b/src/route.h index 6be2e1ea..5c06d1da 100644 --- a/src/route.h +++ b/src/route.h @@ -59,6 +59,11 @@ # 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 /* 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; -- 2.47.3