dhcpv6-ia: allow up to 64 bit wide hostid
authorMikael Magnusson <mikma@users.sourceforge.net>
Wed, 14 Jul 2021 20:46:55 +0000 (22:46 +0200)
committerHans Dedecker <dedeckeh@gmail.com>
Sat, 14 Aug 2021 19:11:30 +0000 (21:11 +0200)
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 <mikma@users.sourceforge.net>
Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
README
src/config.c
src/dhcpv6-ia.c
src/odhcpd.h
src/ubus.c

diff --git a/README b/README
index bd63b0e23a97f410de1b99b2aba0a647431d1c6f..6c79b95bb8edb4348d3ecc30172dc4971cc5a07f 100644 (file)
--- 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 -
index c7d2ad316b0143a75d5542969b67eccad4bad42c..71b786c913cc57202b1a5926ea52eb542df13c56 100644 (file)
@@ -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;
 
index c378c09607e45811025b9acc221abc580f7422f7..e8255b5c0381a45b6cbe8d6bf8bd43725430af93 100644 (file)
@@ -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
index ee7b0086782700be141266d71a824110a1d447cd..ff7e105c68c79615adf6898bf6345c853436f732 100644 (file)
@@ -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);
 
index ebc31032828e66ae541d7c9a6c27396c03035e7f..45b29a4e6459d86d46a6d6f2cd7961ab552fcd51 100644 (file)
@@ -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)