From 14da3727ac0ce89df0d1a80f4e29d3a8b04b1d9c Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20H=C3=A4rdeman?= Date: Sat, 25 Oct 2025 12:25:33 +0200 Subject: [PATCH] dhcpv6-ia: simplify/fix IID calculations MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This is a first stab at simplifying the IID calculations introduced with commit 7000557cd8f6396b82cb35e0cdf9b72b18288fbc. See the discussion in: https://github.com/openwrt/openwrt/issues/20523 Note that a "cold" jrand48() doesn't have much of an avalance effect in case of two very similar DUIDs, but it shouldn't matter because we're not looking for any kind of cryptographic randomness...just a predictable PRNG where the same DUID will (usually) get the same IPv6 addr (if available). Closes: https://github.com/openwrt/odhcpd/issues/293 Closes: https://github.com/openwrt/openwrt/issues/20523 Fixes: 7000557cd8f6 ("dhcpv6-ia: respect prefix assigned to interface (>= /64)") Signed-off-by: David Härdeman Link: https://github.com/openwrt/odhcpd/pull/290 Signed-off-by: Álvaro Fernández Rojas --- src/dhcpv6-ia.c | 94 ++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index e54bc04..3d9172a 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -53,25 +53,6 @@ static struct uloop_timeout valid_until_timeout = {.cb = valid_until_cb}; static uint32_t serial = 0; static uint8_t statemd5[16]; -static inline uint32_t mask_low_for_hostid(uint8_t dhcpv6_hostid_len) -{ - if (dhcpv6_hostid_len >= 32) - return UINT32_MAX; - - return (1 << dhcpv6_hostid_len) - 1; -} - -static inline uint32_t mask_high_for_hostid(uint8_t dhcpv6_hostid_len) -{ - if (dhcpv6_hostid_len >= 64) - return UINT32_MAX; - - if (dhcpv6_hostid_len >= 32) - return (1 << (dhcpv6_hostid_len - 32)) - 1; - - return 0; -} - int dhcpv6_ia_init(void) { uloop_timeout_set(&valid_until_timeout, 1000); @@ -236,13 +217,33 @@ static void dhcpv6_ia_free_assignment(struct dhcp_assignment *a) free(a->managed); } +static void in6_copy_iid(struct in6_addr *dest, uint64_t iid, unsigned n) +{ + uint64_t iid_be = htobe64(iid); + uint8_t *iid_bytes = (uint8_t *)&iid_be; + unsigned bytes = n / 8; + unsigned bits = n % 8; + + if (n == 0 || n > 64) + return; + + memcpy(&dest->s6_addr[16 - bytes], &iid_bytes[8 - bytes], bytes); + + if (bits > 0) { + unsigned dest_idx = 16 - bytes - 1; + unsigned src_idx = 8 - bytes - 1; + uint8_t mask = (1 << bits) - 1; + dest->s6_addr[dest_idx] = (dest->s6_addr[dest_idx] & ~mask) | + (iid_bytes[src_idx] & mask); + } +} + void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c, time_t now, dhcpv6_binding_cb_handler_t func, void *arg) { struct odhcpd_ipaddr *addrs = (c->managed) ? c->managed : iface->addr6; size_t addrlen = (c->managed) ? (size_t)c->managed_size : iface->addr6_len; size_t m = get_preferred_addr(addrs, addrlen); - uint32_t mask_high, mask_low; for (size_t i = 0; i < addrlen; ++i) { struct in6_addr addr; @@ -269,16 +270,7 @@ void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c, if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs)) continue; - mask_low = mask_low_for_hostid(iface->dhcpv6_hostid_len); - mask_high = mask_high_for_hostid(iface->dhcpv6_hostid_len); - addr.s6_addr32[2] = htonl( - ((c->assigned_host_id >> 32) & mask_high) - & (~mask_high & ntohl(addrs[i].addr.in6.s6_addr32[2])) - ); - addr.s6_addr32[3] = htonl( - ((c->assigned_host_id & UINT32_MAX) & mask_low) - & (~mask_low & ntohl(addrs[i].addr.in6.s6_addr32[3])) - ); + in6_copy_iid(&addr, c->assigned_host_id, iface->dhcpv6_hostid_len); } else { if (!valid_prefix_length(c, addrs[i].prefix)) continue; @@ -865,7 +857,11 @@ static bool is_reserved_ipv6_iid(uint64_t iid) static bool assign_na(struct interface *iface, struct dhcp_assignment *a) { struct dhcp_assignment *c; - uint32_t seed = 0; + uint64_t pool_start = 0x100; + uint64_t pool_end = (iface->dhcpv6_hostid_len >= 64) ? UINT64_MAX : ((1ULL << iface->dhcpv6_hostid_len) - 1); + uint64_t pool_size = pool_end - pool_start + 1; + uint64_t try; + unsigned short xsubi[3] = { 0 }; /* Preconfigured assignment by static lease */ if (a->assigned_host_id) { @@ -878,31 +874,17 @@ static bool assign_na(struct interface *iface, struct dhcp_assignment *a) } } - /* Seed RNG with checksum of DUID */ - for (size_t i = 0; i < a->clid_len; ++i) - seed += a->clid_data[i]; - srandom(seed); - - /* Try to assign up to 100x */ - for (size_t i = 0; i < 100; ++i) { - uint64_t try; - - if (iface->dhcpv6_hostid_len > 32) { - uint32_t mask_high = mask_high_for_hostid(iface->dhcpv6_hostid_len); - - do { - try = (uint32_t)random(); - try |= (uint64_t)((uint32_t)random() & mask_high) << 32; - } while (try < 0x100); - - } else { - uint32_t mask_low = mask_low_for_hostid(iface->dhcpv6_hostid_len); - do try = ((uint32_t)random()) & mask_low; while (try < 0x100); - } - - /* If prefix is < 64 bits, we need to shift the interface ID to the left */ - if (iface->addr6_len < 64) - try <<= (64 - iface->addr6_len); + /* Pick a starting point, using the last bytes of the DUID as seed... */ + memcpy(xsubi, + a->clid_data + (a->clid_len > sizeof(xsubi) ? a->clid_len - sizeof(xsubi) : 0), + min(a->clid_len, sizeof(xsubi))); + try = ((uint64_t)jrand48(xsubi) << 32) | (jrand48(xsubi) & UINT32_MAX); + try = pool_start + try % pool_size; + + /* ...then try to assign sequentially from that starting point... */ + for (size_t i = 0; i < 100; i++, try++) { + if (try > pool_end) + try = pool_start; if (is_reserved_ipv6_iid(try)) continue; -- 2.30.2