Browse Source

dhcpv6-ia: allow up to 64 bit wide hostid

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>
Mikael Magnusson 2 years ago
parent
commit
1666769922
5 changed files with 121 additions and 37 deletions
  1. 2 0
      README
  2. 20 2
      src/config.c
  3. 88 31
      src/dhcpv6-ia.c
  4. 7 3
      src/odhcpd.h
  5. 4 1
      src/ubus.c

+ 2 - 0
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 -

+ 20 - 2
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;
 

+ 88 - 31
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

+ 7 - 3
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);
 

+ 4 - 1
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)