From 86c0200f80670c36d200f827f3558e4f676d9171 Mon Sep 17 00:00:00 2001 From: Aviana Cruz Date: Sat, 16 Sep 2023 15:04:12 +0000 Subject: [PATCH] odhcpd: apply RFC9096 recommended lifetimes To address issues with hosts retaining stale IPv6 configuration, this change implements the Recommended Option Lifetimes Configuration Values from RFC 9096-Section 4. The `ra_lifetime` is set to 2700s (45min) by default, and the DHCPv6-PD is now capped at 2700s for preferred lifetime and 5400s (90min) for valid lifetime. The following changes are introduced: - The `ra_useleasetime` option is removed. - New options `max_preferred_lifetime` and `max_valid_lifetime` are added to configure the upper limits for prefix lifetimes. - The Router Advertisement, DNS, and DNR option lifetimes are now dynamically adjusted based on the advertised prefix lifetimes to ensure consistency. These changes help to mitigate issues with stale configurations on client devices, particularly after network renumbering events. Signed-off-by: Aviana Cruz --- README | 12 +++++----- src/config.c | 35 ++++++++++++++++++----------- src/dhcpv6-ia.c | 49 ++++++++++++++++++++++++++-------------- src/odhcpd.h | 8 +++++-- src/router.c | 60 ++++++++++++++++++++++++++----------------------- 5 files changed, 98 insertions(+), 66 deletions(-) diff --git a/README b/README index 0a82762..bb9c082 100644 --- a/README +++ b/README @@ -119,7 +119,9 @@ domain list Search domains to announce leasetime string 12h DHCPv4 address leasetime start integer 100 DHCPv4 pool start limit integer 150 DHCPv4 pool size -preferred_lifetime string 7d Value for the preferred lifetime +max_preferred_lifetime string 45m Upper limit for the preferred lifetime + for a prefix +max_valid_lifetime string 90m Upper limit for the valid lifetime for a prefix ra_default integer 0 Override default route 0: default, 1: ignore no public address, 2: ignore all @@ -134,11 +136,9 @@ ra_maxinterval integer 600 Maximum time allowed between sending unsolicited RA ra_mininterval integer 200 Minimum time allowed between sending unsolicited RA -ra_lifetime integer 1800 Value to be placed in Router - Lifetime field of RA -ra_useleasetime bool 0 Use configured leasetime as - limit for the preferred and - valid lifetime of a prefix +ra_lifetime integer 2700 Value to be placed in Router + Lifetime field of RA. Not recommended to be + more than 2700 (RFC9096). ra_reachabletime integer 0 Reachable Time in milliseconds to be advertised in RA messages ra_retranstime integer 0 Retransmit Time in milliseconds to be diff --git a/src/config.c b/src/config.c index 7992882..a6b699e 100644 --- a/src/config.c +++ b/src/config.c @@ -97,7 +97,6 @@ enum { IFACE_ATTR_RA_MININTERVAL, IFACE_ATTR_RA_MAXINTERVAL, IFACE_ATTR_RA_LIFETIME, - IFACE_ATTR_RA_USELEASETIME, IFACE_ATTR_RA_REACHABLETIME, IFACE_ATTR_RA_RETRANSTIME, IFACE_ATTR_RA_HOPLIMIT, @@ -109,7 +108,8 @@ enum { IFACE_ATTR_NDPROXY_ROUTING, IFACE_ATTR_NDPROXY_SLAVE, IFACE_ATTR_PREFIX_FILTER, - IFACE_ATTR_PREFERRED_LIFETIME, + IFACE_ATTR_MAX_PREFERRED_LIFETIME, + IFACE_ATTR_MAX_VALID_LIFETIME, IFACE_ATTR_NTP, IFACE_ATTR_MAX }; @@ -153,7 +153,6 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_RA_MININTERVAL] = { .name = "ra_mininterval", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_RA_MAXINTERVAL] = { .name = "ra_maxinterval", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_RA_LIFETIME] = { .name = "ra_lifetime", .type = BLOBMSG_TYPE_INT32 }, - [IFACE_ATTR_RA_USELEASETIME] = { .name = "ra_useleasetime", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_RA_REACHABLETIME] = { .name = "ra_reachabletime", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_RA_RETRANSTIME] = { .name = "ra_retranstime", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_RA_HOPLIMIT] = { .name = "ra_hoplimit", .type = BLOBMSG_TYPE_INT32 }, @@ -163,7 +162,8 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_NDPROXY_ROUTING] = { .name = "ndproxy_routing", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_NDPROXY_SLAVE] = { .name = "ndproxy_slave", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_PREFIX_FILTER] = { .name = "prefix_filter", .type = BLOBMSG_TYPE_STRING }, - [IFACE_ATTR_PREFERRED_LIFETIME] = { .name = "preferred_lifetime", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_MAX_PREFERRED_LIFETIME] = { .name = "max_preferred_lifetime", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_MAX_VALID_LIFETIME] = { .name = "max_valid_lifetime", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_NTP] = { .name = "ntp", .type = BLOBMSG_TYPE_ARRAY }, }; @@ -260,7 +260,8 @@ static void set_interface_defaults(struct interface *iface) iface->ndp = MODE_DISABLED; iface->learn_routes = 1; iface->dhcp_leasetime = 43200; - iface->preferred_lifetime = 604800; /* rfc4861#section-6.2.1: AdvPreferredLifetime 7 days */ + iface->max_preferred_lifetime = ND_PREFERRED_LIMIT; + iface->max_valid_lifetime = ND_VALID_LIMIT; iface->dhcpv4_start.s_addr = htonl(START_DEFAULT); iface->dhcpv4_end.s_addr = htonl(START_DEFAULT + LIMIT_DEFAULT - 1); iface->dhcpv6_assignall = true; @@ -998,15 +999,26 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr } - if ((c = tb[IFACE_ATTR_PREFERRED_LIFETIME])) { + if ((c = tb[IFACE_ATTR_MAX_PREFERRED_LIFETIME])) { double time = parse_leasetime(c); - if (time >= 0) - iface->preferred_lifetime = time; - else + if (time >= 0) { + iface->max_preferred_lifetime = time; + } else { syslog(LOG_ERR, "Invalid %s value configured for interface '%s'", - iface_attrs[IFACE_ATTR_PREFERRED_LIFETIME].name, iface->name); + iface_attrs[IFACE_ATTR_MAX_PREFERRED_LIFETIME].name, iface->name); + } + } + if ((c = tb[IFACE_ATTR_MAX_VALID_LIFETIME])) { + double time = parse_leasetime(c); + + if (time >= 0) { + iface->max_valid_lifetime = time; + } else { + syslog(LOG_ERR, "Invalid %s value configured for interface '%s'", + iface_attrs[IFACE_ATTR_MAX_VALID_LIFETIME].name, iface->name); + } } if ((c = tb[IFACE_ATTR_START])) { @@ -1330,9 +1342,6 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr if ((c = tb[IFACE_ATTR_RA_LIFETIME])) iface->ra_lifetime = blobmsg_get_u32(c); - if ((c = tb[IFACE_ATTR_RA_USELEASETIME])) - iface->ra_useleasetime = blobmsg_get_bool(c); - if ((c = tb[IFACE_ATTR_RA_DNS])) iface->ra_dns = blobmsg_get_bool(c); diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index b4ed9a0..b4c6c95 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -120,7 +120,7 @@ static inline bool valid_prefix_length(const struct dhcp_assignment *a, const ui static inline bool valid_addr(const struct odhcpd_ipaddr *addr, time_t now) { - return (addr->prefix <= 96 && addr->preferred_lt > (uint32_t)now); + return (addr->prefix <= 96 && addr->valid_lt > (uint32_t)now && addr->preferred_lt > (uint32_t)now); } static size_t get_preferred_addr(const struct odhcpd_ipaddr *addrs, const size_t addrlen) @@ -1039,17 +1039,27 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, } if (a) { - uint32_t leasetime, preferred_lt; + uint32_t leasetime; if (a->leasetime) { leasetime = a->leasetime; - preferred_lt = a->leasetime; } else { leasetime = iface->dhcp_leasetime; - preferred_lt = iface->preferred_lifetime; } - uint32_t valid_lt = leasetime; + uint32_t floor_preferred_lifetime, floor_valid_lifetime; /* For calculating T1 / T2 */ + + if (iface->max_preferred_lifetime && iface->max_preferred_lifetime < leasetime) { + floor_preferred_lifetime = iface->max_preferred_lifetime; + } else { + floor_preferred_lifetime = leasetime; + } + + if (iface->max_valid_lifetime && iface->max_valid_lifetime < leasetime) { + floor_valid_lifetime = iface->max_valid_lifetime; + } else { + floor_valid_lifetime = leasetime; + } struct odhcpd_ipaddr *addrs = (a->managed) ? a->managed : iface->addr6; size_t addrlen = (a->managed) ? (size_t)a->managed_size : iface->addr6_len; @@ -1073,15 +1083,20 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, prefix_preferred_lt = addrs[i].preferred_lt; prefix_valid_lt = addrs[i].valid_lt; - if (prefix_preferred_lt != UINT32_MAX) + if (prefix_preferred_lt != UINT32_MAX) { prefix_preferred_lt -= now; - if (prefix_preferred_lt > preferred_lt) - prefix_preferred_lt = preferred_lt; + if (iface->max_preferred_lifetime && prefix_preferred_lt > iface->max_preferred_lifetime) + prefix_preferred_lt = iface->max_preferred_lifetime; + } - if (prefix_valid_lt != UINT32_MAX) + if (prefix_valid_lt != UINT32_MAX) { prefix_valid_lt -= now; + if (iface->max_valid_lifetime && prefix_valid_lt > iface->max_valid_lifetime) + prefix_valid_lt = iface->max_valid_lifetime; + } + if (prefix_valid_lt > leasetime) prefix_valid_lt = leasetime; @@ -1135,24 +1150,24 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, /* Calculate T1 / T2 based on non-deprecated addresses */ if (prefix_preferred_lt > 0) { - if (prefix_preferred_lt < preferred_lt) - preferred_lt = prefix_preferred_lt; + if (floor_preferred_lifetime > prefix_preferred_lt) + floor_preferred_lifetime = prefix_preferred_lt; - if (prefix_valid_lt < valid_lt) - valid_lt = prefix_valid_lt; + if (floor_valid_lifetime > prefix_valid_lt) + floor_valid_lifetime = prefix_valid_lt; } } if (!INFINITE_VALID(a->valid_until)) /* UINT32_MAX is RFC defined as infinite lease-time */ - a->valid_until = (valid_lt == UINT32_MAX) ? 0 : valid_lt + now; + a->valid_until = (floor_valid_lifetime == UINT32_MAX) ? 0 : floor_valid_lifetime + now; if (!INFINITE_VALID(a->preferred_until)) /* UINT32_MAX is RFC defined as infinite lease-time */ - a->preferred_until = (preferred_lt == UINT32_MAX) ? 0 : preferred_lt + now; + a->preferred_until = (floor_preferred_lifetime == UINT32_MAX) ? 0 : floor_preferred_lifetime + now; - o_ia.t1 = htonl((preferred_lt == UINT32_MAX) ? preferred_lt : preferred_lt * 5 / 10); - o_ia.t2 = htonl((preferred_lt == UINT32_MAX) ? preferred_lt : preferred_lt * 8 / 10); + o_ia.t1 = htonl((floor_preferred_lifetime == UINT32_MAX) ? floor_preferred_lifetime : floor_preferred_lifetime * 5 / 10); + o_ia.t2 = htonl((floor_preferred_lifetime == UINT32_MAX) ? floor_preferred_lifetime : floor_preferred_lifetime * 8 / 10); if (!o_ia.t1) o_ia.t1 = htonl(1); diff --git a/src/odhcpd.h b/src/odhcpd.h index 0c8b671..4c510e2 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -37,6 +37,10 @@ // RFC 8781 defines PREF64 option #define ND_OPT_PREF64 38 +// RFC9096 defines recommended option lifetimes configuration values +#define ND_PREFERRED_LIMIT 2700 +#define ND_VALID_LIMIT 5400 + // RFC 9463 - Discovery of Network-designated Resolvers (DNR) #define ND_OPT_DNR 144 @@ -328,7 +332,6 @@ struct interface { bool ra_slaac; bool ra_not_onlink; bool ra_advrouter; - bool ra_useleasetime; bool ra_dns; uint8_t pref64_length; uint8_t pref64_plc; @@ -346,7 +349,8 @@ struct interface { uint32_t ra_retranstime; uint32_t ra_hoplimit; int ra_mtu; - uint32_t preferred_lifetime; + uint32_t max_preferred_lifetime; + uint32_t max_valid_lifetime; // DHCP uint32_t dhcp_leasetime; diff --git a/src/router.c b/src/router.c index 931c24d..7982ccf 100644 --- a/src/router.c +++ b/src/router.c @@ -342,13 +342,8 @@ static int calc_adv_interval(struct interface *iface, uint32_t lowest_found_life *maxival = iface->ra_maxinterval; - /* - * rfc4861#section-6.2.1 : AdvDefaultLifetime Default: 3 * MaxRtrAdvInterval - * therefore max interval shall be no greater than 1/3 of the lowest valid - * lease time of all known prefixes. - */ - if (*maxival > lowest_found_lifetime/3) - *maxival = lowest_found_lifetime/3; + if (*maxival > lowest_found_lifetime) + *maxival = lowest_found_lifetime; if (*maxival > MaxRtrAdvInterval) *maxival = MaxRtrAdvInterval; @@ -376,16 +371,17 @@ static int calc_adv_interval(struct interface *iface, uint32_t lowest_found_life static uint32_t calc_ra_lifetime(struct interface *iface, uint32_t maxival) { - uint32_t lifetime = 3*maxival; + uint32_t lifetime = iface->max_preferred_lifetime; if (iface->ra_lifetime >= 0) { lifetime = iface->ra_lifetime; - if (lifetime > 0 && lifetime < maxival) - lifetime = maxival; - else if (lifetime > 9000) - lifetime = 9000; } + if (lifetime > 0 && lifetime < maxival) + lifetime = maxival; + else if (lifetime > 9000) + lifetime = 9000; + return lifetime; } @@ -470,10 +466,10 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr size_t valid_addr_cnt = 0, invalid_addr_cnt = 0; /* * lowest_found_lifetime stores the lowest lifetime of all prefixes; - * necessary to find shortest adv interval necessary + * necessary to find longest adv interval necessary * for shortest lived prefix */ - uint32_t lowest_found_lifetime = UINT32_MAX, maxival, lifetime; + uint32_t lowest_found_lifetime = UINT32_MAX, highest_found_lifetime = 0, maxival, ra_lifetime; int msecs, mtu = iface->ra_mtu, hlim = iface->ra_hoplimit; bool default_route = false; bool valid_prefix = false; @@ -611,17 +607,16 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr if (addr->preferred_lt > (uint32_t)now) { preferred_lt = TIME_LEFT(addr->preferred_lt, now); - if (preferred_lt > iface->preferred_lifetime) { - /* set to possibly user mandated preferred_lt */ - preferred_lt = iface->preferred_lifetime; + if (iface->max_preferred_lifetime && preferred_lt > iface->max_preferred_lifetime) { + preferred_lt = iface->max_preferred_lifetime; } } if (addr->valid_lt > (uint32_t)now) { valid_lt = TIME_LEFT(addr->valid_lt, now); - if (iface->ra_useleasetime && valid_lt > iface->dhcp_leasetime) - valid_lt = iface->dhcp_leasetime; + if (iface->max_valid_lifetime && valid_lt > iface->max_valid_lifetime) + valid_lt = iface->max_valid_lifetime; } if (preferred_lt > valid_lt) { @@ -639,6 +634,11 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr if ((!IN6_IS_ADDR_ULA(&addr->addr.in6) || iface->default_router) && valid_lt) valid_prefix = true; + if (!IN6_IS_ADDR_ULA(&addr->addr.in6) && valid_lt) { + if (highest_found_lifetime < valid_lt) + highest_found_lifetime = valid_lt; + } + odhcpd_bmemcpy(&p->nd_opt_pi_prefix, &addr->addr.in6, (iface->ra_advrouter) ? 128 : addr->prefix); p->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; @@ -660,7 +660,9 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr /* Calculate periodic transmit */ msecs = calc_adv_interval(iface, lowest_found_lifetime, &maxival); - lifetime = calc_ra_lifetime(iface, maxival); + ra_lifetime = calc_ra_lifetime(iface, maxival); + if (!highest_found_lifetime) + highest_found_lifetime = ra_lifetime; if (!iface->have_link_local) { syslog(LOG_NOTICE, "Skip sending a RA on %s as no link local address is available", iface->name); @@ -668,15 +670,15 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr } if (default_route && valid_prefix) { - adv.h.nd_ra_router_lifetime = htons(lifetime < UINT16_MAX ? lifetime : UINT16_MAX); + adv.h.nd_ra_router_lifetime = htons(ra_lifetime < UINT16_MAX ? ra_lifetime : UINT16_MAX); } else { adv.h.nd_ra_router_lifetime = 0; if (default_route) { syslog(LOG_WARNING, "A default route is present but there is no public prefix " - "on %s thus we announce no default route by overriding ra_lifetime to 0!", iface->name); + "on %s thus we announce no default route by setting ra_lifetime to 0!", iface->name); } else { - syslog(LOG_WARNING, "No default route present, overriding ra_lifetime to 0!"); + syslog(LOG_WARNING, "No default route present, setting ra_lifetime to 0!"); } } @@ -705,7 +707,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr memset(dns, 0, dns_sz); dns->type = ND_OPT_RECURSIVE_DNS; dns->len = 1 + (2 * dns_cnt); - dns->lifetime = htonl(lifetime); + dns->lifetime = htonl(highest_found_lifetime); memcpy(dns->addr, dns_addr, sizeof(struct in6_addr)*dns_cnt); } @@ -728,7 +730,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr memset(search, 0, search_sz); search->type = ND_OPT_DNS_SEARCH; search->len = search_len ? ((sizeof(*search) + search_padded) / 8) : 0; - search->lifetime = htonl(lifetime); + search->lifetime = htonl(highest_found_lifetime); memcpy(search->name, search_domain, search_len); memset(&search->name[search_len], 0, search_padded - search_len); } @@ -741,7 +743,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr if (iface->pref64_length) { /* RFC 8781 § 4.1 rounding up lifetime to multiple of 8 */ - uint16_t pref64_lifetime = lifetime < (UINT16_MAX - 7) ? lifetime + 7 : UINT16_MAX; + uint16_t pref64_lifetime = ra_lifetime < (UINT16_MAX - 7) ? ra_lifetime + 7 : (UINT16_MAX - 7); pref64_sz = sizeof(*pref64); pref64 = alloca(pref64_sz); @@ -783,7 +785,7 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr if (iface->dnr[i].lifetime_set) dnr->lifetime = htonl(iface->dnr[i].lifetime); else - dnr->lifetime = htonl(lifetime); + dnr->lifetime = htonl(highest_found_lifetime); dnr->adn_len = htons(iface->dnr[i].adn_len); memcpy(tmp, iface->dnr[i].adn, iface->dnr[i].adn_len); @@ -858,7 +860,9 @@ static int send_router_advert(struct interface *iface, const struct in6_addr *fr routes[routes_cnt].flags |= ND_RA_PREF_HIGH; valid_lt = TIME_LEFT(addr->valid_lt, now); - routes[routes_cnt].lifetime = htonl(valid_lt < lifetime ? valid_lt : lifetime); + if (iface->max_valid_lifetime && valid_lt > iface->max_valid_lifetime) + valid_lt = iface->max_valid_lifetime; + routes[routes_cnt].lifetime = htonl(valid_lt); routes[routes_cnt].addr[0] = addr->addr.in6.s6_addr32[0]; routes[routes_cnt].addr[1] = addr->addr.in6.s6_addr32[1]; routes[routes_cnt].addr[2] = 0; -- 2.30.2