Support prefixed ranges of ipv6 addresses in dhcp-host.

When a request matching the clid or mac address is
recieved the server will iterate over all candidate
addresses until it find's one that is not already
leased to a different clid/iaid and advertise
this address.

Using multiple reservations for a single host makes it
possible to maintain a static leases only configuration
which support network booting systems with UEFI firmware
that request a new address (a new SOLICIT with a new IA_NA
option using a new IAID) for different boot modes, for
instance 'PXE over IPv6', and 'HTTP-Boot over IPv6'. Open
Virtual Machine Firmware (OVMF) and most UEFI firmware
build on the EDK2 code base exhibit this behaviour.
diff --git a/CHANGELOG b/CHANGELOG
index 80ea482..319b230 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -69,6 +69,14 @@
 	different interfaces on the same IPv6 net, and we're doing
 	RA/DHCP service on only one of them. Thanks to NIIBE Yutaka
 	for spotting this case and making the initial patch.
+
+	Support prefixed ranges of ipv6 addresses in dhcp-host.
+	This eases problems chain-netbooting, where each link in the
+	chain requests an address using a different UID. With a single
+	address, only one gets the "static" address, but with this
+	fix, enough addresses can be reserved for all the stages of the
+	boot. Many thanks to Harald Jensås for his work on this idea and
+	earlier patches. 
 	
 	
 version 2.80
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index cb5cc73..e4143b0 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1013,7 +1013,13 @@
 IPv6 addresses may contain only the host-identifier part:
 .B --dhcp-host=laptop,[::56]
 in which case they act as wildcards in constructed dhcp ranges, with
-the appropriate network part inserted. 
+the appropriate network part inserted. For IPv6, the address may include a prefix length:
+.B --dhcp-host=laptop,[1234:50/126]
+which (in this case) specifies four addresses, 1234::50 to 1234::53. This is useful
+when a host presents either a consistent name or hardware-ID, but varying DUIDs, since it allows
+dnsmasq to honour the static address allocation but assign a different adddress for each DUID. This
+typically occurs when chain netbooting, as each stage of the chain gets in turn allocates an address.
+
 Note that in IPv6 DHCP, the hardware address may not be
 available, though it normally is for direct-connected clients, or
 clients using DHCP relays which support RFC 6939.
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index dc7f945..a03edc9 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -424,10 +424,11 @@
 
 #ifdef HAVE_DHCP6
 	    if (prot == AF_INET6 && 
-		(!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr6, 128, 0)) || conf_tmp == config))
+		(!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config))
 	      {
 		memcpy(&config->addr6, &crec->addr.addr6, IN6ADDRSZ);
 		config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS;
+		config->flags &= ~CONFIG_PREFIX;
 		continue;
 	      }
 #endif
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 51788ed..63c0c93 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -418,14 +418,14 @@
   return 1;
 }
 
-struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
+struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix,  struct in6_addr *addr)
 {
   struct dhcp_config *config;
   
   for (config = configs; config; config = config->next)
     if ((config->flags & CONFIG_ADDR6) &&
-	is_same_net6(&config->addr6, net, prefix) &&
-	(prefix == 128 || addr6part(&config->addr6) == addr))
+	(!net || is_same_net6(&config->addr6, net, prefix)) &&
+	is_same_net6(&config->addr6, addr, (config->flags & CONFIG_PREFIX) ? config->prefix : 128))
       return config;
   
   return NULL;
@@ -494,16 +494,15 @@
 	    for (d = context; d; d = d->current)
 	      if (addr == addr6part(&d->local6))
 		break;
+	    
+	    *ans = c->start6;
+	    setaddr6part (ans, addr);
 
 	    if (!d &&
 		!lease6_find_by_addr(&c->start6, c->prefix, addr) && 
-		!config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, addr))
-	      {
-		*ans = c->start6;
-		setaddr6part (ans, addr);
-		return c;
-	      }
-	
+		!config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans))
+	      return c;
+	    
 	    addr++;
 	    
 	    if (addr  == addr6part(&c->end6) + 1)
@@ -557,27 +556,6 @@
   return NULL;
 }
 
-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
-{
-  if (!config || !(config->flags & CONFIG_ADDR6))
-    return 0;
-
-  if ((config->flags & CONFIG_WILDCARD) && context->prefix == 64)
-    {
-      *addr = context->start6;
-      setaddr6part(addr, addr6part(&config->addr6));
-      return 1;
-    }
-  
-  if (is_same_net6(&context->start6, &config->addr6, context->prefix))
-    {
-      *addr = config->addr6;
-      return 1;
-    }
-  
-  return 0;
-}
-
 void make_duid(time_t now)
 {
   (void)now;
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 7fb440c..2677dc5 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -767,6 +767,7 @@
   struct dhcp_netid_list *netid;
 #ifdef HAVE_DHCP6
   struct in6_addr addr6;
+  int prefix;
 #endif
   struct in_addr addr;
   time_t decline_time;
@@ -790,6 +791,7 @@
 #define CONFIG_ADDR6          4096
 #define CONFIG_WILDCARD       8192
 #define CONFIG_ADDR6_HOSTS   16384    /* address added by from /etc/hosts */
+#define CONFIG_PREFIX        32768    /* addr6 is a set, size given by prefix */
 
 struct dhcp_opt {
   int opt, len, flags;
@@ -1508,7 +1510,6 @@
 void dhcp6_packet(time_t now);
 struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned char *clid, int clid_len, int temp_addr,
 				       unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans);
-int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
 struct dhcp_context *address6_available(struct dhcp_context *context, 
 					struct in6_addr *taddr,
 					struct dhcp_netid *netids,
@@ -1518,7 +1519,7 @@
 				    struct dhcp_netid *netids,
 				    int plain_range);
 struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, 
-					    int prefix, u64 addr);
+					    int prefix, struct in6_addr *addr);
 void make_duid(time_t now);
 void dhcp_construct_contexts(time_t now);
 void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, 
diff --git a/src/option.c b/src/option.c
index f77545f..627aecf 100644
--- a/src/option.c
+++ b/src/option.c
@@ -3264,8 +3264,11 @@
 #ifdef HAVE_DHCP6
 	      else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
 		{
+		  char *pref;
+
 		  arg[strlen(arg)-1] = 0;
 		  arg++;
+		  pref = split_chr(arg, '/');
 		  
 		  if (!inet_pton(AF_INET6, arg, &new->addr6))
 		    {
@@ -3273,6 +3276,21 @@
 		      ret_err(_("bad IPv6 address"));
 		    }
 
+		  if (pref)
+		    {
+		      u64 addrpart = addr6part(&new->addr6);
+
+		      if (!atoi_check(pref, &new->prefix) ||
+			  new->prefix > 128 ||
+			  (((1<<(128-new->prefix))-1) & addrpart) != 0)
+			{
+			  dhcp_config_free(new);
+			  ret_err(_("bad IPv6 prefix"));
+			}
+		      
+		      new->flags |= CONFIG_PREFIX;
+		    }
+		  
 		  for (i= 0; i < 8; i++)
 		    if (new->addr6.s6_addr[i] != 0)
 		      break;
diff --git a/src/rfc3315.c b/src/rfc3315.c
index 9471f5c..e0fd7be 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -49,6 +49,8 @@
 static void mark_context_used(struct state *state, struct in6_addr *addr);
 static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr);
 static int check_address(struct state *state, struct in6_addr *addr);
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state);
+static int config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
 static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, 
 			unsigned int *min_time, struct in6_addr *addr, time_t now);
 static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now);
@@ -675,7 +677,7 @@
 		    /* If the client asks for an address on the same network as a configured address, 
 		       offer the configured address instead, to make moving to newly-configured
 		       addresses automatic. */
-		    if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr) && check_address(state, &addr))
+		    if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state))
 		      {
 			req_addr = addr;
 			mark_config_used(c, &addr);
@@ -699,8 +701,7 @@
 	    for (c = state->context; c; c = c->current) 
 	      if (!(c->flags & CONTEXT_CONF_USED) &&
 		  match_netid(c->filter, solicit_tags, plain_range) &&
-		  config_valid(config, c, &addr) && 
-		  check_address(state, &addr))
+		  config_valid(config, c, &addr, state))
 		{
 		  mark_config_used(state->context, &addr);
 		  if (have_config(config, CONFIG_TIME))
@@ -838,14 +839,13 @@
 		struct in6_addr req_addr;
 		struct dhcp_context *dynamic, *c;
 		unsigned int lease_time;
-		struct in6_addr addr;
 		int config_ok = 0;
 
 		/* align. */
 		memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
 		
 		if ((c = address6_valid(state->context, &req_addr, tagif, 1)))
-		  config_ok = config_valid(config, c, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr);
+		  config_ok = config_implies(config, c, &req_addr);
 		
 		if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c)
 		  {
@@ -971,12 +971,11 @@
 		if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) ||
 		    (this_context = address6_valid(state->context, &req_addr, tagif, 1)))
 		  {
-		    struct in6_addr addr;
 		    unsigned int lease_time;
 
 		    get_context_tag(state, this_context);
 		    
-		    if (config_valid(config, this_context, &addr) && IN6_ARE_ADDR_EQUAL(&addr, &req_addr) && have_config(config, CONFIG_TIME))
+		    if (config_implies(config, this_context, &req_addr) && have_config(config, CONFIG_TIME))
 		      lease_time = config->lease_time;
 		    else 
 		      lease_time = this_context->lease_time;
@@ -1667,6 +1666,75 @@
 }
 
 
+/* return true of *addr could have been generated from config. */
+static int config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
+{
+  int prefix;
+  struct in6_addr wild_addr;
+
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return 0;
+  
+  prefix = (config->flags & CONFIG_PREFIX) ? config->prefix : 128;
+  wild_addr = config->addr6;
+    
+  if (!is_same_net6(&context->start6, addr, context->prefix))
+    return 0;
+
+  if ((config->flags & CONFIG_WILDCARD))
+    {
+      if (context->prefix != 64)
+	return 0;
+      
+      wild_addr = context->start6;
+      setaddr6part(&wild_addr, addr6part(&config->addr6));
+    }
+  
+  if (is_same_net6(&wild_addr, addr, prefix))
+    return 1;
+
+  return 0;
+}
+
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state)
+{
+  u64 addrpart;
+
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return 0;
+
+  addrpart  = addr6part(&config->addr6);
+
+  if ((config->flags & CONFIG_WILDCARD))
+    {
+      if (context->prefix != 64)
+	return 0;
+      
+      *addr = context->start6;
+      setaddr6part(addr, addrpart);
+    }
+  else if (is_same_net6(&context->start6, &config->addr6, context->prefix))
+    *addr = config->addr6;
+  else
+   return 0;
+
+  while(1) {
+    if (check_address(state, addr))
+      return 1;
+    
+    if (!(config->flags & CONFIG_PREFIX))
+      return 0;
+    
+    /* config may specify a set of addresses, return first one not in use
+       by another client */
+    
+    addrpart++;
+    setaddr6part(addr, addrpart);
+    if (!is_same_net6(addr, &config->addr6, config->prefix))
+      return 0;
+  }
+}
+
 /* Calculate valid and preferred times to send in leases/renewals. 
 
    Inputs are: