#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 {
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,
[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 },
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;
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 {
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);
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;
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;
}
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,
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);
}
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 */
/* 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)
return true;
}
- current = (c->assigned + (1 << (64 - c->length)));
+ current = (c->assigned_subnet_id + (1 << (64 - c->length)));
}
}
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)
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;
}
}
/* 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;
}
}
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);
.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))
.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;
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;
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;
} 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