odhcpd: support assignments on the basis of IAID
authorDavid Härdeman <[email protected]>
Sat, 20 Sep 2025 17:48:13 +0000 (19:48 +0200)
committerÁlvaro Fernández Rojas <[email protected]>
Thu, 9 Oct 2025 06:58:30 +0000 (08:58 +0200)
With this patch, IAIDs are actually taken into account when creating
assignments, which allows per-IAID assignments.

The old odhcpd behaviour is still kept for assignments which only
specify a DUID. That behaviour is a first-come-first-serve basis, where
the first request (no matter what the IAID is) will get the static
address, and later requests with different IAIDs will get a NoAddrsAvail
error message (in other words, assignments are not re-assigned and
random adresses are not provided when the "main" address is already
assigned, if the DUID is known in a static config).

The old odhcpd behaviour can be described as follows:

Imagine a "dhcp" configuration file with a single static assignment:

config host
option name 'test'
option ip '192.168.44.2'
option mac '00:00:00:00:ca:fe'
option duid '0003000100000000cafe'
option hostid '02'

And odhcpd running on an interface with IPv6 addr fd00:aaaa::1/48, using
a simple test client (command line parameters should be self-evident
here, foo-client is the client part of a veth interface pair, odhcpd is
listening to the peer interface foo-server):

$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x02 --duid=f000 foo-client
    IPv6: fd00:aaaa::817
$ sudo dhcpdig --iaid=0x02 --duid=f000 foo-client
    IPv6: fd00:aaaa::817
$ sudo dhcpdig --iaid=0x01 --duid=f000 foo-client
    IPv6: fd00:aaaa::9c5
$ sudo dhcpdig --iaid=0x01 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo dhcpdig --iaid=0x01 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo dhcpdig --iaid=0x02 --duid=0003000100000000cafe foo-client
  STATUS: NoAddrsAvail (2)

IOW, unknown DUIDs get randomly assigned per-IAID IPv6 addresses. Known
statically determined DUIDs will be awarded to the first client
attempting a lease, no matter what the IAID is, but the IAID will be
remembered and subsequent requests with other IAIDs will fail.

With this patch applied, assume a "dhcp" configuration file with the
following assignments:

config host
option name 'testcafe'
option ip '192.168.44.2'
option mac '00:00:00:00:ca:fe'
option hostid '02'
option duid '0003000100000000cafe%123'

config host
option name 'testbeef'
option ip '192.168.44.3'
option mac '00:00:00:00:be:ef'
option hostid '03'
option duid '0003000100000000beef'

config host
option name 'testfood'
option ip '192.168.44.4'
option mac '00:00:00:00:f0:0d'
option hostid '04'
option duid '0003000100000000f00d'

config host
option name 'testfood2'
option ip '192.168.44.5'
option mac '00:00:00:00:f0:0d'
option hostid '05'
option duid '0003000100000000f00d%123'

Now, using the same test client:

$ sudo ./build/dhcpdig --iaid=0x000 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::9c4
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::2
$ sudo ./build/dhcpdig --iaid=0x000 --duid=0003000100000000cafe foo-client
    IPv6: fd00:aaaa::9c4
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000beef foo-client
    IPv6: fd00:aaaa::3
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000beef foo-client
  STATUS: NoAddrsAvail (2)
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::4
$ sudo ./build/dhcpdig --iaid=0xabc --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::4
$ sudo ./build/dhcpdig --iaid=0x123 --duid=0003000100000000f00d foo-client
    IPv6: fd00:aaaa::5

See also: https://github.com/openwrt/odhcpd/issues/175

Signed-off-by: David Härdeman <[email protected]>
Link: https://github.com/openwrt/odhcpd/pull/255
Signed-off-by: Álvaro Fernández Rojas <[email protected]>
src/config.c
src/dhcpv6-ia.c
src/odhcpd.h

index 5f072ead1f65802fd446872b16c2bd70a0d4010f..445b97aee0ffde806d72f7019047e8c5f1c5d203 100644 (file)
@@ -1671,16 +1671,30 @@ static void lease_update(_unused struct vlist_tree *tree, struct vlist_node *nod
                lease_delete(lease_old);
 }
 
-struct lease *config_find_lease_by_duid(const uint8_t *duid, const uint16_t len)
+/*
+ * Either find:
+ *  a) a lease with an exact DUID/IAID match; or
+ *  b) a lease with a matching DUID and no IAID set
+ */
+struct lease *config_find_lease_by_duid_and_iaid(const uint8_t *duid, const uint16_t len,
+                                                const uint32_t iaid)
 {
-       struct lease *l;
+       struct lease *l, *candidate = NULL;
 
        vlist_for_each_element(&leases, l, node) {
-               if (l->duid_len == len && !memcmp(l->duid, duid, len))
+               if (l->duid_len != len || memcmp(l->duid, duid, len))
+                       continue;
+
+               if (!l->iaid_set) {
+                       candidate = l;
+                       continue;
+               }
+
+               if (l->iaid == iaid)
                        return l;
        }
 
-       return NULL;
+       return candidate;
 }
 
 struct lease *config_find_lease_by_mac(const uint8_t *mac)
index 549256f866ac8466d62ae20be7bbd31032aef5f4..f24f14c37352452a12bf2f8ed707b9d3b112be87 100644 (file)
@@ -488,7 +488,7 @@ void dhcpv6_ia_write_statefile(void)
                                        if (!(ctxt.c->flags & OAF_BOUND) || ctxt.c->managed_size < 0)
                                                continue;
 
-                                       char duidbuf[264];
+                                       char duidbuf[DUID_HEXSTRLEN];
 
                                        odhcpd_hexlify(duidbuf, ctxt.c->clid_data, ctxt.c->clid_len);
 
@@ -1389,7 +1389,6 @@ static bool dhcpv6_ia_on_link(const struct dhcpv6_ia_hdr *ia, struct dhcp_assign
 ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *iface,
                const struct sockaddr_in6 *addr, const void *data, const uint8_t *end)
 {
-       struct lease *l;
        struct dhcp_assignment *first = NULL;
        const struct dhcpv6_client_header *hdr = data;
        time_t now = odhcpd_time();
@@ -1398,7 +1397,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
        uint8_t *clid_data = NULL, mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
        size_t hostname_len = 0, response_len = 0;
        bool notonlink = false, rapid_commit = false, accept_reconf = false;
-       char duidbuf[261], hostname[256];
+       char duidbuf[DUID_HEXSTRLEN], hostname[256];
 
        dhcpv6_for_each_option(start, end, otype, olen, odata) {
                if (otype == DHCPV6_OPT_CLIENTID) {
@@ -1410,7 +1409,7 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                        else if (olen == 10 && odata[0] == 0 && odata[1] == 3)
                                memcpy(mac, &odata[4], sizeof(mac));
 
-                       if (olen <= 130)
+                       if (olen <= DUID_MAX_LEN)
                                odhcpd_hexlify(duidbuf, odata, olen);
                } else if (otype == DHCPV6_OPT_FQDN && olen >= 2 && olen <= 255) {
                        uint8_t fqdn_buf[256];
@@ -1425,13 +1424,9 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                        rapid_commit = true;
        }
 
-       if (!clid_data || !clid_len || clid_len > 130)
+       if (!clid_data || !clid_len || clid_len > DUID_MAX_LEN)
                goto out;
 
-       l = config_find_lease_by_duid(clid_data, clid_len);
-       if (!l)
-               l = config_find_lease_by_mac(mac);
-
        dhcpv6_for_each_option(start, end, otype, olen, odata) {
                bool is_pd = (otype == DHCPV6_OPT_IA_PD);
                bool is_na = (otype == DHCPV6_OPT_IA_NA);
@@ -1443,6 +1438,11 @@ ssize_t dhcpv6_ia_handle_IAs(uint8_t *buf, size_t buflen, struct interface *ifac
                size_t ia_response_len = 0;
                uint8_t reqlen = (is_pd) ? 62 : 128;
                uint32_t reqhint = 0;
+               struct lease *l;
+
+               l = config_find_lease_by_duid_and_iaid(clid_data, clid_len, ntohl(ia->iaid));
+               if (!l)
+                       l = config_find_lease_by_mac(mac);
 
                /* Parse request hint for IA-PD */
                if (is_pd) {
index e2251088459be2c6290635a2360de4595e467f25..e8ce5a92857b7a2aae6e867c895a9630b08c5bf5 100644 (file)
@@ -182,6 +182,7 @@ struct config {
 
 /* 2-byte type + 128-byte DUID, RFC8415, §11.1 */
 #define DUID_MAX_LEN 130
+#define DUID_HEXSTRLEN (DUID_MAX_LEN * 2 + 1)
 
 struct lease {
        struct vlist_node node;
@@ -510,7 +511,8 @@ bool odhcpd_bitlen2netmask(bool v6, unsigned int bits, void *mask);
 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_duid_and_iaid(const uint8_t *duid, const uint16_t len,
+                                                const uint32_t iaid);
 struct lease *config_find_lease_by_mac(const uint8_t *mac);
 struct lease *config_find_lease_by_hostid(const uint64_t hostid);
 struct lease *config_find_lease_by_ipaddr(const uint32_t ipaddr);