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.
}
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) {
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,
#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);
}
#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);
}
#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);
}
#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)
{
#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;
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;
}
!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;
# 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.... */
#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;