summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config.c22
-rw-r--r--src/dhcpv6-ia.c119
-rw-r--r--src/odhcpd.h10
-rw-r--r--src/ubus.c5
4 files changed, 119 insertions, 37 deletions
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);
diff --git a/src/ubus.c b/src/ubus.c
index ebc3103..45b29a4 100644
--- a/src/ubus.c
+++ b/src/ubus.c
@@ -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)