From 16667699228101fda936068e3d11c0cf19270e34 Mon Sep 17 00:00:00 2001 From: Mikael Magnusson Date: Wed, 14 Jul 2021 22:46:55 +0200 Subject: [PATCH] dhcpv6-ia: allow up to 64 bit wide hostid Add dhcpv6_hostid_len config option which controls the number of bits in the host identifier of dynamically assigned IPv6 addresses. The default is 12 bits which is also the minimum. The maximum is the whole interface identifier, i.e. 64 bits. Allow up to 64 bit wide hostid in static leases. Fixes #84 and #27. Signed-off-by: Mikael Magnusson Signed-off-by: Hans Dedecker --- README | 2 + src/config.c | 22 ++++++++- src/dhcpv6-ia.c | 119 +++++++++++++++++++++++++++++++++++------------- src/odhcpd.h | 10 ++-- src/ubus.c | 5 +- 5 files changed, 121 insertions(+), 37 deletions(-) diff --git a/README b/README index bd63b0e..6c79b95 100644 --- a/README +++ b/README @@ -95,6 +95,8 @@ dhcpv6_assignall bool 1 Assign all viable DHCPv6 addresses in statefull mode; if disabled only the DHCPv6 address having the longest preferred lifetime is assigned +dhcpv6_hostidlength integer 12 Host ID length of dynamically created leases, + allowed values: 12 - 64 (bits). dhcpv6_na bool 1 DHCPv6 stateful addressing hands out IA_NA - Internet Address - Network Address dhcpv6_pd bool 1 DHCPv6 stateful addressing hands out IA_PD - diff --git a/src/config.c b/src/config.c index c7d2ad3..71b786c 100644 --- a/src/config.c +++ b/src/config.c @@ -35,6 +35,10 @@ struct config config = {.legacy = false, .main_dhcpv4 = false, #define START_DEFAULT 100 #define LIMIT_DEFAULT 150 +#define HOSTID_LEN_MIN 12 +#define HOSTID_LEN_MAX 64 +#define HOSTID_LEN_DEFAULT HOSTID_LEN_MIN + #define OAF_DHCPV6 (OAF_DHCPV6_NA | OAF_DHCPV6_PD) enum { @@ -61,6 +65,7 @@ enum { IFACE_ATTR_DHCPV6_ASSIGNALL, IFACE_ATTR_DHCPV6_PD, IFACE_ATTR_DHCPV6_NA, + IFACE_ATTR_DHCPV6_HOSTID_LEN, IFACE_ATTR_RA_DEFAULT, IFACE_ATTR_RA_MANAGEMENT, IFACE_ATTR_RA_FLAGS, @@ -110,6 +115,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_DHCPV6_ASSIGNALL] = { .name ="dhcpv6_assignall", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_DHCPV6_PD] = { .name = "dhcpv6_pd", .type = BLOBMSG_TYPE_BOOL }, [IFACE_ATTR_DHCPV6_NA] = { .name = "dhcpv6_na", .type = BLOBMSG_TYPE_BOOL }, + [IFACE_ATTR_DHCPV6_HOSTID_LEN] = { .name = "dhcpv6_hostidlength", .type = BLOBMSG_TYPE_INT32 }, [IFACE_ATTR_PD_MANAGER] = { .name = "pd_manager", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_PD_CER] = { .name = "pd_cer", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_RA_DEFAULT] = { .name = "ra_default", .type = BLOBMSG_TYPE_INT32 }, @@ -205,6 +211,7 @@ static void set_interface_defaults(struct interface *iface) iface->dhcpv6_assignall = true; iface->dhcpv6_pd = true; iface->dhcpv6_na = true; + iface->dhcpv6_hostid_len = HOSTID_LEN_DEFAULT; iface->dns_service = true; iface->ra_flags = ND_RA_FLAG_OTHER; iface->ra_slaac = true; @@ -400,7 +407,7 @@ int set_lease_from_blobmsg(struct blob_attr *ba) if ((c = tb[LEASE_ATTR_HOSTID])) { errno = 0; - l->hostid = strtoul(blobmsg_get_string(c), NULL, 16); + l->hostid = strtoull(blobmsg_get_string(c), NULL, 16); if (errno) goto err; } else { @@ -754,6 +761,17 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr if ((c = tb[IFACE_ATTR_DHCPV6_NA])) iface->dhcpv6_na = blobmsg_get_bool(c); + if ((c = tb[IFACE_ATTR_DHCPV6_HOSTID_LEN])) { + uint32_t hostid_len = blobmsg_get_u32(c); + + if (hostid_len >= HOSTID_LEN_MIN && hostid_len <= HOSTID_LEN_MAX) + iface->dhcpv6_hostid_len = hostid_len; + else + syslog(LOG_ERR, "Invalid %s value configured for interface '%s'", + iface_attrs[IFACE_ATTR_DHCPV6_HOSTID_LEN].name, iface->name); + + } + if ((c = tb[IFACE_ATTR_RA_DEFAULT])) iface->default_router = blobmsg_get_u32(c); @@ -1039,7 +1057,7 @@ struct lease *config_find_lease_by_mac(const uint8_t *mac) return NULL; } -struct lease *config_find_lease_by_hostid(const uint32_t hostid) +struct lease *config_find_lease_by_hostid(const uint64_t hostid) { struct lease *l; diff --git a/src/dhcpv6-ia.c b/src/dhcpv6-ia.c index c378c09..e8255b5 100644 --- a/src/dhcpv6-ia.c +++ b/src/dhcpv6-ia.c @@ -248,12 +248,13 @@ void dhcpv6_ia_enum_addrs(struct interface *iface, struct dhcp_assignment *c, if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs)) continue; - addr.s6_addr32[3] = htonl(c->assigned); + addr.s6_addr32[2] = htonl(c->assigned_host_id >> 32); + addr.s6_addr32[3] = htonl(c->assigned_host_id & UINT32_MAX); } else { if (!valid_prefix_length(c, addrs[i].prefix)) continue; - addr.s6_addr32[1] |= htonl(c->assigned); + addr.s6_addr32[1] |= htonl(c->assigned_subnet_id); addr.s6_addr32[2] = addr.s6_addr32[3] = 0; } @@ -362,15 +363,21 @@ void dhcpv6_ia_write_statefile(void) odhcpd_hexlify(duidbuf, ctxt.c->clid_data, ctxt.c->clid_len); - /* iface DUID iaid hostname lifetime assigned length [addrs...] */ - ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %"PRId64" %x %u ", + /* iface DUID iaid hostname lifetime assigned_host_id length [addrs...] */ + ctxt.buf_idx = snprintf(ctxt.buf, ctxt.buf_len, "# %s %s %x %s%s %"PRId64" ", ctxt.iface->ifname, duidbuf, ntohl(ctxt.c->iaid), (ctxt.c->flags & OAF_BROKEN_HOSTNAME) ? "broken\\x20" : "", (ctxt.c->hostname ? ctxt.c->hostname : "-"), (ctxt.c->valid_until > now ? (int64_t)(ctxt.c->valid_until - now + wall_time) : - (INFINITE_VALID(ctxt.c->valid_until) ? -1 : 0)), - ctxt.c->assigned, (unsigned)ctxt.c->length); + (INFINITE_VALID(ctxt.c->valid_until) ? -1 : 0))); + + if (ctxt.c->flags & OAF_DHCPV6_NA) + ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx, + "%" PRIx64" %u ", ctxt.c->assigned_host_id, (unsigned)ctxt.c->length); + else + ctxt.buf_idx += snprintf(ctxt.buf + ctxt.buf_idx, ctxt.buf_len - ctxt.buf_idx, + "%" PRIx32" %u ", ctxt.c->assigned_subnet_id, (unsigned)ctxt.c->length); if (INFINITE_VALID(ctxt.c->valid_until) || ctxt.c->valid_until > now) dhcpv6_ia_enum_addrs(ctxt.iface, ctxt.c, now, @@ -459,7 +466,7 @@ static void __apply_lease(struct dhcp_assignment *a, continue; prefix = addrs[i].addr.in6; - prefix.s6_addr32[1] |= htonl(a->assigned); + prefix.s6_addr32[1] |= htonl(a->assigned_subnet_id); prefix.s6_addr32[2] = prefix.s6_addr32[3] = 0; netlink_setup_route(&prefix, (a->managed_size) ? addrs[i].prefix : a->length, a->iface->ifindex, &a->peer.sin6_addr, 1024, add); @@ -494,9 +501,9 @@ static void set_border_assignment_size(struct interface *iface, struct dhcp_assi } if (minprefix > 32 && minprefix <= 64) - b->assigned = 1U << (64 - minprefix); + b->assigned_subnet_id = 1U << (64 - minprefix); else - b->assigned = 0; + b->assigned_subnet_id = 0; } /* More data was received from TCP connection */ @@ -627,12 +634,12 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign) /* Try honoring the hint first */ uint32_t current = 1, asize = (1 << (64 - assign->length)) - 1; - if (assign->assigned) { + if (assign->assigned_subnet_id) { list_for_each_entry(c, &iface->ia_assignments, head) { if (c->flags & OAF_DHCPV6_NA) continue; - if (assign->assigned >= current && assign->assigned + asize < c->assigned) { + if (assign->assigned_subnet_id >= current && assign->assigned_subnet_id + asize < c->assigned_subnet_id) { list_add_tail(&assign->head, &c->head); if (assign->flags & OAF_BOUND) @@ -641,7 +648,7 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign) return true; } - current = (c->assigned + (1 << (64 - c->length))); + current = (c->assigned_subnet_id + (1 << (64 - c->length))); } } @@ -653,8 +660,8 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign) current = (current + asize) & (~asize); - if (current + asize < c->assigned) { - assign->assigned = current; + if (current + asize < c->assigned_subnet_id) { + assign->assigned_subnet_id = current; list_add_tail(&assign->head, &c->head); if (assign->flags & OAF_BOUND) @@ -663,24 +670,45 @@ static bool assign_pd(struct interface *iface, struct dhcp_assignment *assign) return true; } - current = (c->assigned + (1 << (64 - c->length))); + current = (c->assigned_subnet_id + (1 << (64 - c->length))); } return false; } +/* Check iid against reserved IPv6 interface identifiers. + Refer to: + http://www.iana.org/assignments/ipv6-interface-ids */ +static bool is_reserved_ipv6_iid(uint64_t iid) +{ + if (iid == 0x0000000000000000) + /* Subnet-Router Anycast [RFC4291] */ + return true; + + if ((iid & 0xFFFFFFFFFF000000) == 0x02005EFFFE000000) + /* Reserved IPv6 Interface Identifiers corresponding + to the IANA Ethernet Block [RFC4291] */ + return true; + + if ((iid & 0xFFFFFFFFFFFFFF80) == 0xFDFFFFFFFFFFFF80) + /* Reserved Subnet Anycast Addresses [RFC2526] */ + return true; + + return false; +} + static bool assign_na(struct interface *iface, struct dhcp_assignment *a) { struct dhcp_assignment *c; uint32_t seed = 0; /* Preconfigured assignment by static lease */ - if (a->assigned) { + if (a->assigned_host_id) { list_for_each_entry(c, &iface->ia_assignments, head) { - if (c->assigned > a->assigned || !(c->flags & OAF_DHCPV6_NA)) { + if (!(c->flags & OAF_DHCPV6_NA) || c->assigned_host_id > a->assigned_host_id ) { list_add_tail(&a->head, &c->head); return true; - } else if (c->assigned == a->assigned) + } else if (c->assigned_host_id == a->assigned_host_id) return false; } } @@ -688,22 +716,46 @@ 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]; - srand(seed); + srandom(seed); /* Try to assign up to 100x */ for (size_t i = 0; i < 100; ++i) { - uint32_t try; - do try = ((uint32_t)rand()) % 0x0fff; while (try < 0x100); + uint64_t try; + + if (iface->dhcpv6_hostid_len > 32) { + uint32_t mask_high; + + if (iface->dhcpv6_hostid_len >= 64) + mask_high = UINT32_MAX; + else + mask_high = (1 << (iface->dhcpv6_hostid_len - 32)) - 1; + + do { + try = (uint32_t)random(); + try |= (uint64_t)((uint32_t)random() & mask_high) << 32; + } while (try < 0x100); + } else { + uint32_t mask_low; + + if (iface->dhcpv6_hostid_len == 32) + mask_low = UINT32_MAX; + else + mask_low = (1 << iface->dhcpv6_hostid_len) - 1; + do try = ((uint32_t)random()) & mask_low; while (try < 0x100); + } + + if (is_reserved_ipv6_iid(try)) + continue; if (config_find_lease_by_hostid(try)) continue; list_for_each_entry(c, &iface->ia_assignments, head) { - if (c->assigned > try || !(c->flags & OAF_DHCPV6_NA)) { - a->assigned = try; + if (!(c->flags & OAF_DHCPV6_NA) || c->assigned_host_id > try) { + a->assigned_host_id = try; list_add_tail(&a->head, &c->head); return true; - } else if (c->assigned == try) + } else if (c->assigned_host_id == try) break; } } @@ -735,7 +787,7 @@ static void handle_addrlist_change(struct netevent_handler_info *info) c->managed_size) continue; - if (c->assigned >= border->assigned) + if (c->assigned_subnet_id >= border->assigned_subnet_id) list_move(&c->head, &reassign); else if (c->flags & OAF_BOUND) apply_lease(c, true); @@ -909,7 +961,7 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, .addr = addrs[i].addr.in6, }; - o_ia_p.addr.s6_addr32[1] |= htonl(a->assigned); + o_ia_p.addr.s6_addr32[1] |= htonl(a->assigned_subnet_id); o_ia_p.addr.s6_addr32[2] = o_ia_p.addr.s6_addr32[3] = 0; if (!valid_prefix_length(a, addrs[i].prefix)) @@ -931,7 +983,8 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, .valid = htonl(prefix_valid) }; - o_ia_a.addr.s6_addr32[3] = htonl(a->assigned); + o_ia_a.addr.s6_addr32[2] = htonl(a->assigned_host_id >> 32); + o_ia_a.addr.s6_addr32[3] = htonl(a->assigned_host_id & UINT32_MAX); if (!ADDR_ENTRY_VALID_IA_ADDR(iface, i, m, addrs)) continue; @@ -1002,14 +1055,15 @@ static size_t build_ia(uint8_t *buf, size_t buflen, uint16_t status, addr = addrs[i].addr.in6; if (ia->type == htons(DHCPV6_OPT_IA_PD)) { - addr.s6_addr32[1] |= htonl(a->assigned); + addr.s6_addr32[1] |= htonl(a->assigned_subnet_id); addr.s6_addr32[2] = addr.s6_addr32[3] = 0; if (!memcmp(&ia_p->addr, &addr, sizeof(addr)) && ia_p->prefix == ((a->managed) ? addrs[i].prefix : a->length)) found = true; } else { - addr.s6_addr32[3] = htonl(a->assigned); + addr.s6_addr32[2] = htonl(a->assigned_host_id >> 32); + addr.s6_addr32[3] = htonl(a->assigned_host_id & UINT32_MAX); if (!memcmp(&ia_a->addr, &addr, sizeof(addr))) found = true; @@ -1312,7 +1366,10 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac a->iaid = ia->iaid; a->length = reqlen; a->peer = *addr; - a->assigned = is_na && l ? l->hostid : reqhint; + if (is_na) + a->assigned_host_id = l ? l->hostid : 0; + else + a->assigned_subnet_id = reqhint; a->valid_until = now; a->preferred_until = now; a->dhcp_free_cb = dhcpv6_ia_free_assignment; @@ -1441,7 +1498,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac } else if ((a->flags & OAF_DHCPV6_NA) && hdr->msg_type == DHCPV6_MSG_DECLINE) { a->flags &= ~OAF_BOUND; - if (!(a->flags & OAF_STATIC) || a->lease->hostid != a->assigned) { + if (!(a->flags & OAF_STATIC) || a->lease->hostid != a->assigned_host_id) { memset(a->clid_data, 0, a->clid_len); a->valid_until = now + 3600; /* Block address for 1h */ } else diff --git a/src/odhcpd.h b/src/odhcpd.h index ee7b008..ff7e105 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -156,7 +156,7 @@ struct lease { struct vlist_node node; struct list_head assignments; uint32_t ipaddr; - uint32_t hostid; + uint64_t hostid; struct ether_addr mac; uint16_t duid_len; uint8_t *duid; @@ -199,7 +199,10 @@ struct dhcp_assignment { struct odhcpd_ref_ip *fr_ip; uint32_t addr; - uint32_t assigned; + union { + uint64_t assigned_host_id; + uint32_t assigned_subnet_id; + }; uint32_t iaid; uint8_t length; // length == 128 -> IA_NA, length <= 64 -> IA_PD @@ -323,6 +326,7 @@ struct interface { bool dhcpv6_assignall; bool dhcpv6_pd; bool dhcpv6_na; + uint32_t dhcpv6_hostid_len; char *upstream; size_t upstream_len; @@ -390,7 +394,7 @@ bool odhcpd_valid_hostname(const char *name); int config_parse_interface(void *data, size_t len, const char *iname, bool overwrite); struct lease *config_find_lease_by_duid(const uint8_t *duid, const uint16_t len); struct lease *config_find_lease_by_mac(const uint8_t *mac); -struct lease *config_find_lease_by_hostid(const uint32_t hostid); +struct lease *config_find_lease_by_hostid(const uint64_t hostid); struct lease *config_find_lease_by_ipaddr(const uint32_t ipaddr); int set_lease_from_blobmsg(struct blob_attr *ba); diff --git a/src/ubus.c b/src/ubus.c index ebc3103..45b29a4 100644 --- a/src/ubus.c +++ b/src/ubus.c @@ -145,7 +145,10 @@ static int handle_dhcpv6_leases(_unused struct ubus_context *ctx, _unused struct blobmsg_add_u32(&b, "iaid", ntohl(a->iaid)); blobmsg_add_string(&b, "hostname", (a->hostname) ? a->hostname : ""); blobmsg_add_u8(&b, "accept-reconf", a->accept_reconf); - blobmsg_add_u32(&b, "assigned", a->assigned); + if (a->flags & OAF_DHCPV6_NA) + blobmsg_add_u64(&b, "assigned", a->assigned_host_id); + else + blobmsg_add_u16(&b, "assigned", a->assigned_subnet_id); m = blobmsg_open_array(&b, "flags"); if (a->flags & OAF_BOUND) -- 2.30.2