diff options
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | src/config.c | 22 | ||||
-rw-r--r-- | src/dhcpv6-ia.c | 119 | ||||
-rw-r--r-- | src/odhcpd.h | 10 | ||||
-rw-r--r-- | src/ubus.c | 5 |
5 files changed, 121 insertions, 37 deletions
@@ -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); @@ -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) |