From 01e5e311b0db2ab80a179e217c2785964023be3c Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20H=C3=A4rdeman?= Date: Fri, 1 Aug 2025 19:39:17 +0200 Subject: [PATCH] odhcpd: support multiple per-client DUIDs MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch allows for multiple per-client DHCPv6 DUIDs. One use-case for multiple DUIDs is dual-boot hosts, where e.g. Windows creates it's own DUID and Linux (say, systemd-networkd) creates it's own DUID, and the two cannot be synced in a simple manner. This also makes DUIDs similar to multiple per-host MAC addresses (which is already supported by dnsmasq for DHCPv4 leases). Note that the "old" behaviour is still kept, in other words, where a simple DUID (as opposed to a "DUID%IAID") static association is defined, the address will be given out on a first-come-first-serve basis. Signed-off-by: David Härdeman Link: https://github.com/openwrt/odhcpd/pull/255 Signed-off-by: Álvaro Fernández Rojas --- README.md | 2 +- src/config.c | 149 +++++++++++++++++++++++++++++---------------------- src/odhcpd.h | 13 +++-- 3 files changed, 96 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 69d7a6c..cbea25f 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ and may also receive information from ubus | :-------------------- | :---- | :---- | :---------- | | ip |string |(none) | IPv4 host address | | mac |list\|string|(none) | HexadecimalMACaddress(es) | -| duid |string |(none) | Hexadecimal DUID, or DUID%IAID | +| duid |list\|string|(none) | Hexadecimal DUID(s), or DUID%IAID(s) | | hostid |string |(none) | IPv6hostidentifier | | name |string |(none) | Hostname | | leasetime |string |(none) | DHCPv4/v6leasetime | diff --git a/src/config.c b/src/config.c index 445b97a..cfc0af6 100644 --- a/src/config.c +++ b/src/config.c @@ -194,7 +194,7 @@ const struct uci_blob_param_list interface_attr_list = { const struct blobmsg_policy lease_attrs[LEASE_ATTR_MAX] = { [LEASE_ATTR_IP] = { .name = "ip", .type = BLOBMSG_TYPE_STRING }, [LEASE_ATTR_MAC] = { .name = "mac", .type = BLOBMSG_TYPE_ARRAY }, - [LEASE_ATTR_DUID] = { .name = "duid", .type = BLOBMSG_TYPE_STRING }, + [LEASE_ATTR_DUID] = { .name = "duid", .type = BLOBMSG_TYPE_ARRAY }, [LEASE_ATTR_HOSTID] = { .name = "hostid", .type = BLOBMSG_TYPE_STRING }, [LEASE_ATTR_LEASETIME] = { .name = "leasetime", .type = BLOBMSG_TYPE_STRING }, [LEASE_ATTR_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, @@ -463,14 +463,59 @@ static void free_lease(struct lease *l) free(l); } +static bool parse_duid(struct duid *duid, struct blob_attr *c) +{ + const char *duid_str = blobmsg_get_string(c); + size_t duid_str_len = blobmsg_data_len(c) - 1; + ssize_t duid_len; + const char *iaid_str; + + /* We support a hex string with either "", or "%" */ + iaid_str = strrchr(duid_str, '%'); + if (iaid_str) { + size_t iaid_str_len = strlen(++iaid_str); + char *end; + + /* IAID = uint32, RFC8415, §21.4, §21.5, §21.21 */ + if (iaid_str_len < 1 || iaid_str_len > 2 * sizeof(uint32_t)) { + syslog(LOG_ERR, "Invalid IAID length '%s'", iaid_str); + return false; + } + + errno = 0; + duid->iaid = strtoul(iaid_str, &end, 16); + if (errno || *end != '\0') { + syslog(LOG_ERR, "Invalid IAID '%s'", iaid_str); + return false; + } + + duid->iaid_set = true; + duid_str_len -= (iaid_str_len + 1); + } + + if (duid_str_len < 2 || duid_str_len > DUID_MAX_LEN * 2 || duid_str_len % 2) { + syslog(LOG_ERR, "Invalid DUID length '%.*s'", (int)duid_str_len, duid_str); + return false; + } + + duid_len = odhcpd_unhexlify(duid->id, duid_str_len / 2, duid_str); + if (duid_len < 0) { + syslog(LOG_ERR, "Invalid DUID '%.*s'", (int)duid_str_len, duid_str); + return false; + } + + duid->len = duid_len; + return true; +} + int set_lease_from_blobmsg(struct blob_attr *ba) { struct blob_attr *tb[LEASE_ATTR_MAX], *c; struct lease *l = NULL; int mac_count = 0; struct ether_addr *macs; - size_t duid_alloc_size = 0; - uint8_t *duid; + int duid_count = 0; + struct duid *duids; blobmsg_parse(lease_attrs, LEASE_ATTR_MAX, tb, blob_data(ba), blob_len(ba)); @@ -480,13 +525,15 @@ int set_lease_from_blobmsg(struct blob_attr *ba) goto err; } - if ((c = tb[LEASE_ATTR_DUID])) - /* We might overallocate some bytes here, that's fine */ - duid_alloc_size = (blobmsg_data_len(c) - 1) / 2; + if ((c = tb[LEASE_ATTR_DUID])) { + duid_count = blobmsg_check_array_len(c, BLOBMSG_TYPE_STRING, blob_raw_len(c)); + if (duid_count < 0) + goto err; + } l = calloc_a(sizeof(*l), &macs, mac_count * sizeof(*macs), - &duid, duid_alloc_size); + &duids, duid_count * sizeof(*duids)); if (!l) goto err; @@ -504,50 +551,16 @@ int set_lease_from_blobmsg(struct blob_attr *ba) } if ((c = tb[LEASE_ATTR_DUID])) { - const char *duid_str = blobmsg_get_string(c); - size_t duid_str_len = blobmsg_data_len(c) - 1; - ssize_t duid_len; - const char *iaid_str; - - /* We support a hex string with either "", or "%" */ - iaid_str = strrchr(duid_str, '%'); - if (iaid_str) { - size_t iaid_str_len = strlen(++iaid_str); - - /* IAID = uint32, RFC8415, §21.4, §21.5, §21.21 */ - if (iaid_str_len < 1 || iaid_str_len > 2 * sizeof(uint32_t)) { - syslog(LOG_ERR, "Invalid IAID length '%s'", iaid_str); - goto err; - } - - errno = 0; - l->iaid = strtoull(iaid_str, NULL, 16); - if (errno) { - syslog(LOG_ERR, "Invalid IAID '%s'", iaid_str); - goto err; - } - - l->iaid_set = true; - duid_str_len -= (iaid_str_len + 1); - } - - if (duid_str_len < 2 || duid_str_len > DUID_MAX_LEN * 2 || duid_str_len % 2) { - syslog(LOG_ERR, "Invalid DUID length '%.*s'", (int)duid_str_len, duid_str); - goto err; - } + struct blob_attr *cur; + size_t rem; + unsigned i = 0; - l->duid = duid; - duid_len = odhcpd_unhexlify(l->duid, duid_str_len / 2, duid_str); - if (duid_len < 0) { - syslog(LOG_ERR, "Invalid DUID '%.*s'", (int)duid_str_len, duid_str); - goto err; - } + l->duid_count = duid_count; + l->duids = duids; - l->duid_len = duid_len; - syslog(LOG_DEBUG, - "Found a static lease for DUID '%.*s' (%zi bytes), IAID 0x%08" PRIx32 "%s", - (int)duid_str_len, duid_str, duid_len, - l->iaid, l->iaid_set ? "" : " (not defined)"); + blobmsg_for_each_attr(cur, c, rem) + if (!parse_duid(&duids[i++], cur)) + goto err; } if ((c = tb[LEASE_ATTR_NAME])) { @@ -1590,14 +1603,19 @@ static int lease_cmp(const void *k1, const void *k2, _unused void *ptr) const struct lease *l1 = k1, *l2 = k2; int cmp = 0; - if (l1->duid_len != l2->duid_len) - return l1->duid_len - l2->duid_len; + if (l1->duid_count != l2->duid_count) + return l1->duid_count - l2->duid_count; - if (l1->duid_len && l2->duid_len) - cmp = memcmp(l1->duid, l2->duid, l1->duid_len); + for (size_t i = 0; i < l1->duid_count; i++) { + if (l1->duids[i].len != l2->duids[i].len) + return l1->duids[i].len - l2->duids[i].len; - if (cmp) - return cmp; + if (l1->duids[i].len && l2->duids[i].len) { + cmp = memcmp(l1->duids[i].id, l2->duids[i].id, l1->duids[i].len); + if (cmp) + return cmp; + } + } if (l1->mac_count != l2->mac_count) return l1->mac_count - l2->mac_count; @@ -1682,16 +1700,21 @@ struct lease *config_find_lease_by_duid_and_iaid(const uint8_t *duid, const uint struct lease *l, *candidate = NULL; vlist_for_each_element(&leases, l, node) { - if (l->duid_len != len || memcmp(l->duid, duid, len)) - continue; + for (size_t i = 0; i < l->duid_count; i++) { + if (l->duids[i].len != len) + continue; - if (!l->iaid_set) { - candidate = l; - continue; - } + if (memcmp(l->duids[i].id, duid, len)) + continue; - if (l->iaid == iaid) - return l; + if (!l->duids[i].iaid_set) { + candidate = l; + continue; + } + + if (l->duids[i].iaid == iaid) + return l; + } } return candidate; diff --git a/src/odhcpd.h b/src/odhcpd.h index e8ce5a9..e96cc35 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -184,6 +184,13 @@ struct config { #define DUID_MAX_LEN 130 #define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1) +struct duid { + uint8_t len; + uint8_t id[DUID_MAX_LEN]; + uint32_t iaid; + bool iaid_set; +}; + struct lease { struct vlist_node node; struct list_head assignments; @@ -191,10 +198,8 @@ struct lease { uint64_t hostid; size_t mac_count; struct ether_addr *macs; - uint16_t duid_len; - uint8_t *duid; - uint32_t iaid; - bool iaid_set; + size_t duid_count; + struct duid *duids; uint32_t leasetime; char *hostname; }; -- 2.30.2