Blob Blame History Raw
From 9b200103342c0909def9f8d9b97cfd889be6bfd8 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
Date: Mon, 3 Feb 2020 23:58:45 +0000
Subject: [PATCH] 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.

(cherry picked from commit 79aba0f10ad0157fb4f48afbbcb03f094caff97a)

Conflicts:
	CHANGELOG
	src/dhcp-common.c
	src/dnsmasq.h
        src/dhcp6.c

Extend 79aba0f10ad0157fb4f48afbbcb03f094caff97a for multiple IPv6 addresses.

(cherry picked from commit 137286e9baecf6a3ba97722ef1b49c851b531810)

Conflicts:
	man/dnsmasq.8
	src/dhcp-common.c
        src/dhcp6.c
        src/rfc3315.c
        src/option.c

Fix bug with prefixed wildcard addresses in 137286e9baecf6a3ba97722ef1b49c851b531810

(cherry picked from commit f064188032a829efdcf3988b24ac795ff52785ec)

Conflicts:
	src/rfc3315.c
---
 man/dnsmasq.8     |  13 +-
 src/dhcp-common.c |  56 +++++---
 src/dhcp6.c       |  51 +++----
 src/dnsmasq.h     |  17 +--
 src/option.c      | 402 ++++++++++++++++++++++++++++++------------------------
 src/rfc3315.c     |  83 ++++++++++-
 6 files changed, 370 insertions(+), 252 deletions(-)

diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index f52762f..2c9d9f6 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -985,13 +985,20 @@ allowed to specify the client ID as text, like this:
 .B --dhcp-host=id:clientidastext,..... 
 
 A single
-.B dhcp-host 
-may contain an IPv4 address or an IPv6 address, or both. IPv6 addresses must be bracketed by square brackets thus:
+.B --dhcp-host
+may contain an IPv4 address or one or more IPv6 addresses, or both. IPv6 addresses must be bracketed by square brackets thus:
 .B --dhcp-host=laptop,[1234::56]
 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, an 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 (an the ability
+to specify multiple addresses) 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 d9719d1..5d437dd 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -271,26 +271,35 @@ static int is_config_in_context(struct dhcp_context *context, struct dhcp_config
 {
   if (!context) /* called via find_config() from lease_update_from_configs() */
     return 1; 
-
-  if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6)))
-    return 1;
   
 #ifdef HAVE_DHCP6
-  if ((context->flags & CONTEXT_V6) && (config->flags & CONFIG_WILDCARD))
-    return 1;
-#endif
+  if (context->flags & CONTEXT_V6)
+    {
+       struct addrlist *addr_list;
 
-  for (; context; context = context->current)
-#ifdef HAVE_DHCP6
-    if (context->flags & CONTEXT_V6) 
-      {
-	if ((config->flags & CONFIG_ADDR6) && is_same_net6(&config->addr6, &context->start6, context->prefix))
-	  return 1;
-      }
-    else 
+       if (!(config->flags & CONFIG_ADDR6))
+	 return 1;
+       
+        for (; context; context = context->current)
+	  for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+	    {
+	      if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+		return 1;
+	      
+	      if (is_same_net6(&addr_list->addr.addr.addr6, &context->start6, context->prefix))
+		return 1;
+	    }
+    }
+  else
 #endif
-      if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask))
+    {
+      if (!(config->flags & CONFIG_ADDR))
 	return 1;
+      
+      for (; context; context = context->current)
+	if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask))
+	  return 1;
+    }
 
   return 0;
 }
@@ -418,10 +427,21 @@ void dhcp_update_configs(struct dhcp_config *configs)
 
 #ifdef HAVE_DHCP6
 	    if (prot == AF_INET6 && 
-		(!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config))
+		(!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr.addr.addr6)) || conf_tmp == config))
 	      {
-		memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ);
-		config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS;
+		/* host must have exactly one address if comming from /etc/hosts. */
+		if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist))))
+		  {
+		    config->addr6->next = NULL;
+		    config->addr6->flags = 0;
+		  }
+
+		if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX)))
+		  {
+		    memcpy(&config->addr6->addr.addr.addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ);
+		    config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS;
+		  }
+	    
 		continue;
 	      }
 #endif
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 0853664..6f1f54e 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -384,21 +384,26 @@ static int complete_context6(struct in6_addr *local,  int prefix,
  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))
-      return config;
+    if (config->flags & CONFIG_ADDR6)
+      {
+	struct addrlist *addr_list;
+	
+	for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+	  if ((!net || is_same_net6(&addr_list->addr.addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) &&
+	      is_same_net6(&addr_list->addr.addr.addr6, addr, (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128))
+	    return config;
+      }
   
   return NULL;
 }
 
 struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned char *clid, int clid_len, int temp_addr,
-				       int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans)   
+				       unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans)   
 {
   /* Find a free address: exclude anything in use and anything allocated to
      a particular hwaddr/clientid/hostname in our configuration.
@@ -453,16 +458,15 @@ struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned c
 	    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)
@@ -516,27 +520,6 @@ struct dhcp_context *address6_valid(struct dhcp_context *context,
   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 6b18bb7..9437226 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -343,9 +343,11 @@ struct ds_config {
   struct ds_config *next;
 };
 
-#define ADDRLIST_LITERAL 1
-#define ADDRLIST_IPV6    2
-#define ADDRLIST_REVONLY 4
+#define ADDRLIST_LITERAL  1
+#define ADDRLIST_IPV6     2
+#define ADDRLIST_REVONLY  4
+#define ADDRLIST_PREFIX   8
+#define ADDRLIST_WILDCARD 16
 
 struct addrlist {
   struct all_addr addr;
@@ -748,7 +750,7 @@ struct dhcp_config {
   char *hostname, *domain;
   struct dhcp_netid_list *netid;
 #ifdef HAVE_DHCP6
-  struct in6_addr addr6;
+  struct addrlist *addr6;
 #endif
   struct in_addr addr;
   time_t decline_time;
@@ -770,7 +772,7 @@ struct dhcp_config {
 #define CONFIG_DECLINED       1024    /* address declined by client */
 #define CONFIG_BANK           2048    /* from dhcp hosts file */
 #define CONFIG_ADDR6          4096
-#define CONFIG_WILDCARD       8192
+#define CONFIG_ADDR6_HOSTS   16384    /* address added by from /etc/hosts */
 
 struct dhcp_opt {
   int opt, len, flags;
@@ -1463,8 +1465,7 @@ int get_incoming_mark(union mysockaddr *peer_addr, struct all_addr *local_addr,
 void dhcp6_init(void);
 void dhcp6_packet(time_t now);
 struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned char *clid, int clid_len, int temp_addr,
-				       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);
+				       unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans);
 struct dhcp_context *address6_available(struct dhcp_context *context, 
 					struct in6_addr *taddr,
 					struct dhcp_netid *netids,
@@ -1474,7 +1475,7 @@ struct dhcp_context *address6_valid(struct dhcp_context *context,
 				    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 b12183b..ea70ee3 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1010,15 +1010,30 @@ static void dhcp_config_free(struct dhcp_config *config)
   if (config)
     {
       struct hwaddr_config *hwaddr = config->hwaddr;
+      
       while (hwaddr)
         {
 	  struct hwaddr_config *tmp = hwaddr;
           hwaddr = hwaddr->next;
 	  free(tmp);
         }
+      
       dhcp_netid_list_free(config->netid);
+
       if (config->flags & CONFIG_CLID)
         free(config->clid);
+
+      if (config->flags & CONFIG_ADDR6)
+	{
+	  struct addrlist *addr, *tmp;
+	  
+	  for (addr = config->addr6; addr; addr = tmp)
+	    {
+	      tmp = addr->next;
+	      free(addr);
+	    }
+	}
+
       free(config);
     }
 }
@@ -3143,8 +3158,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
     case LOPT_BANK:
     case 'G':  /* --dhcp-host */
       {
-	int j, k = 0;
-	char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
 	struct dhcp_config *new;
 	struct in_addr in;
 	
@@ -3155,197 +3168,222 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
 	new->hwaddr = NULL;
 	new->netid = NULL;
 	new->clid = NULL;
+	new->addr6 = NULL;
 
-	if ((a[0] = arg))
-	  for (k = 1; k < 7; k++)
-	    if (!(a[k] = split(a[k-1])))
-	      break;
-	
-	for (j = 0; j < k; j++)
-	  if (strchr(a[j], ':')) /* ethernet address, netid or binary CLID */
-	    {
-	      char *arg = a[j];
-	      
-	      if ((arg[0] == 'i' || arg[0] == 'I') &&
-		  (arg[1] == 'd' || arg[1] == 'D') &&
-		  arg[2] == ':')
-		{
-		  if (arg[3] == '*')
-		    new->flags |= CONFIG_NOCLID;
-		  else
-		    {
-		      int len;
-		      arg += 3; /* dump id: */
-		      if (strchr(arg, ':'))
-			len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL);
-		      else
-			{
-			  unhide_metas(arg);
-			  len = (int) strlen(arg);
-			}
-
-		      if (len == -1)
-			{
-			  dhcp_config_free(new);
-			  ret_err(_("bad hex constant"));
-			}
-		      else if ((new->clid = opt_malloc(len)))
-			{
-			  new->flags |= CONFIG_CLID;
-			  new->clid_len = len;
-			  memcpy(new->clid, arg, len);
-			}
-		    }
-		}
-	      /* dhcp-host has strange backwards-compat needs. */
-	      else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg)
-		{
-		  struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
-		  newlist->next = new->netid;
-		  new->netid = newlist;
-		  newlist->list = dhcp_netid_create(arg+4, NULL);
-		}
-	      else if (strstr(arg, "tag:") == arg)
-		{
-		  
-		  dhcp_config_free(new);
-		  ret_err(_("cannot match tags in --dhcp-host"));
-		}
+	while (arg)
+	  {
+	    comma = split(arg);
+	    if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */
+	      {
+		if ((arg[0] == 'i' || arg[0] == 'I') &&
+		    (arg[1] == 'd' || arg[1] == 'D') &&
+		    arg[2] == ':')
+		  {
+		    if (arg[3] == '*')
+		      new->flags |= CONFIG_NOCLID;
+		    else
+		      {
+			int len;
+			arg += 3; /* dump id: */
+			if (strchr(arg, ':'))
+			  len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL);
+			else
+			  {
+			    unhide_metas(arg);
+			    len = (int) strlen(arg);
+			  }
+			
+			if (len == -1)
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad hex constant"));
+			  }
+			else if ((new->clid = opt_malloc(len)))
+			  {
+			    new->flags |= CONFIG_CLID;
+			    new->clid_len = len;
+			    memcpy(new->clid, arg, len);
+			  }
+		      }
+		  }
+		/* dhcp-host has strange backwards-compat needs. */
+		else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg)
+		  {
+		    struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
+		    newlist->next = new->netid;
+		    new->netid = newlist;
+		    newlist->list = dhcp_netid_create(arg+4, NULL);
+		  }
+		else if (strstr(arg, "tag:") == arg)
+		  {
+		    
+		    dhcp_config_free(new);
+		    ret_err(_("cannot match tags in --dhcp-host"));
+		  }
 #ifdef HAVE_DHCP6
-	      else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
-		{
-		  arg[strlen(arg)-1] = 0;
-		  arg++;
-		  
-		  if (!inet_pton(AF_INET6, arg, &new->addr6))
-		    {
-		      dhcp_config_free(new);
-		      ret_err(_("bad IPv6 address"));
-		    }
-
-		  for (i= 0; i < 8; i++)
-		    if (new->addr6.s6_addr[i] != 0)
-		      break;
+		else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
+		  {
+		    char *pref;
+		    struct in6_addr in6;
+		    struct addrlist *new_addr;
+		    
+		    arg[strlen(arg)-1] = 0;
+		    arg++;
+		    pref = split_chr(arg, '/');
+		    
+		    if (!inet_pton(AF_INET6, arg, &in6))
+		      {
+			dhcp_config_free(new);
+			ret_err(_("bad IPv6 address"));
+		      }
 
-		  /* set WILDCARD if network part all zeros */
-		  if (i == 8)
-		    new->flags |= CONFIG_WILDCARD;
+		    new_addr = opt_malloc(sizeof(struct addrlist));
+		    new_addr->next = new->addr6;
+		    new_addr->flags = 0;
+		    new_addr->addr.addr.addr6 = in6;
+		    new->addr6 = new_addr;
+		    
+		    if (pref)
+		      {
+			u64 addrpart = addr6part(&in6);
+			
+			if (!atoi_check(pref, &new_addr->prefixlen) ||
+			    new_addr->prefixlen > 128 ||
+			    (((1<<(128-new_addr->prefixlen))-1) & addrpart) != 0)
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad IPv6 prefix"));
+			  }
+			
+			new_addr->flags |= ADDRLIST_PREFIX;
+		      }
 		  
-		  new->flags |= CONFIG_ADDR6;
-		}
+		    for (i= 0; i < 8; i++)
+		      if (in6.s6_addr[i] != 0)
+			break;
+		    
+		    /* set WILDCARD if network part all zeros */
+		    if (i == 8)
+		      new_addr->flags |= ADDRLIST_WILDCARD;
+		    
+		    new->flags |= CONFIG_ADDR6;
+		  }
 #endif
-	      else
-		{
-		  struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config));
-		  if ((newhw->hwaddr_len = parse_hex(a[j], newhw->hwaddr, DHCP_CHADDR_MAX, 
-						     &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1)
-		    {
-		      free(newhw);
-		      dhcp_config_free(new);
-		      ret_err(_("bad hex constant"));
-		    }
-		  else
-		    {
-		      newhw->next = new->hwaddr;
-		      new->hwaddr = newhw;
-		    }		    
-		}
-	    }
-	  else if (strchr(a[j], '.') && (inet_pton(AF_INET, a[j], &in) > 0))
-	    {
-	      struct dhcp_config *configs;
-	      
-	      new->addr = in;
-	      new->flags |= CONFIG_ADDR;
-
-	      /* If the same IP appears in more than one host config, then DISCOVER
-		 for one of the hosts will get the address, but REQUEST will be NAKed,
-		 since the address is reserved by the other one -> protocol loop. */
-	      for (configs = daemon->dhcp_conf; configs; configs = configs->next) 
-		if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr)
+		else
 		  {
-		    sprintf(errstr, _("duplicate dhcp-host IP address %s"),  inet_ntoa(in));
-		    return 0;
-		  }	      
-	    }
-	  else
-	    {
-	      char *cp, *lastp = NULL, last = 0;
-	      int fac = 1, isdig = 0;
-	      
-	      if (strlen(a[j]) > 1)
-		{
-		  lastp = a[j] + strlen(a[j]) - 1;
-		  last = *lastp;
-		  switch (last)
+		    struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config));
+		    if ((newhw->hwaddr_len = parse_hex(arg, newhw->hwaddr, DHCP_CHADDR_MAX, 
+						       &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1)
+		      {
+			free(newhw);
+			dhcp_config_free(new);
+			ret_err(_("bad hex constant"));
+		      }
+		    else
+		      {
+			newhw->next = new->hwaddr;
+			new->hwaddr = newhw;
+		      }		    
+		  }
+	      }
+	    else if (strchr(arg, '.') && (inet_pton(AF_INET, arg, &in) > 0))
+	      {
+		struct dhcp_config *configs;
+		
+		new->addr = in;
+		new->flags |= CONFIG_ADDR;
+		
+		/* If the same IP appears in more than one host config, then DISCOVER
+		   for one of the hosts will get the address, but REQUEST will be NAKed,
+		   since the address is reserved by the other one -> protocol loop. */
+		for (configs = daemon->dhcp_conf; configs; configs = configs->next) 
+		  if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr)
 		    {
-		    case 'w':
-		    case 'W':
-		      fac *= 7;
-		      /* fall through */
-		    case 'd':
-		    case 'D':
-		      fac *= 24;
-		      /* fall through */
-		    case 'h':
-		    case 'H':
-		      fac *= 60;
-		      /* fall through */
-		    case 'm':
-		    case 'M':
-		      fac *= 60;
-		      /* fall through */
-		    case 's':
-		    case 'S':
-		      *lastp = 0;
-		    }
-		}
-	      
-	      for (cp = a[j]; *cp; cp++)
-		if (isdigit((unsigned char)*cp))
-		  isdig = 1;
-		else if (*cp != ' ')
-		  break;
+		      sprintf(errstr, _("duplicate dhcp-host IP address %s"),  inet_ntoa(in));
+		      return 0;
+		    }	      
+	      }
+	    else
+	      {
+		char *cp, *lastp = NULL, last = 0;
+		int fac = 1, isdig = 0;
+		
+		if (strlen(arg) > 1)
+		  {
+		    lastp = arg + strlen(arg) - 1;
+		    last = *lastp;
+		    switch (last)
+		      {
+		      case 'w':
+		      case 'W':
+			fac *= 7;
+			/* fall through */
+		      case 'd':
+		      case 'D':
+			fac *= 24;
+			/* fall through */
+		      case 'h':
+		      case 'H':
+			fac *= 60;
+			/* fall through */
+		      case 'm':
+		      case 'M':
+			fac *= 60;
+			/* fall through */
+		      case 's':
+		      case 'S':
+			*lastp = 0;
+		      }
+		  }
+		
+		for (cp = arg; *cp; cp++)
+		  if (isdigit((unsigned char)*cp))
+		    isdig = 1;
+		  else if (*cp != ' ')
+		    break;
+
+		if (*cp)
+		  {
+		    if (lastp)
+		      *lastp = last;
+		    if (strcmp(arg, "infinite") == 0)
+		      {
+			new->lease_time = 0xffffffff;
+			new->flags |= CONFIG_TIME;
+		      }
+		    else if (strcmp(arg, "ignore") == 0)
+		      new->flags |= CONFIG_DISABLE;
+		    else
+		      {
+			if (!(new->hostname = canonicalise_opt(arg)) ||
+			    !legal_hostname(new->hostname))
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad DHCP host name"));
+			  }
+			
+			new->flags |= CONFIG_NAME;
+			new->domain = strip_hostname(new->hostname);			
+		      }
+		  }
+		else if (isdig)
+		  {
+		    new->lease_time = atoi(arg) * fac; 
+		    /* Leases of a minute or less confuse
+		       some clients, notably Apple's */
+		    if (new->lease_time < 120)
+		      new->lease_time = 120;
+		    new->flags |= CONFIG_TIME;
+		  }
+	      }
+
+	    arg = comma;
+	  }
 
-	      if (*cp)
-		{
-		  if (lastp)
-		    *lastp = last;
-		  if (strcmp(a[j], "infinite") == 0)
-		    {
-		      new->lease_time = 0xffffffff;
-		      new->flags |= CONFIG_TIME;
-		    }
-		  else if (strcmp(a[j], "ignore") == 0)
-		    new->flags |= CONFIG_DISABLE;
-		  else
-		    {
-		      if (!(new->hostname = canonicalise_opt(a[j])) ||
-			  !legal_hostname(new->hostname))
-			{
-			  dhcp_config_free(new);
-			  ret_err(_("bad DHCP host name"));
-			}
-		     
-		      new->flags |= CONFIG_NAME;
-		      new->domain = strip_hostname(new->hostname);			
-		    }
-		}
-	      else if (isdig)
-		{
-		  new->lease_time = atoi(a[j]) * fac; 
-		  /* Leases of a minute or less confuse
-		     some clients, notably Apple's */
-		  if (new->lease_time < 120)
-		    new->lease_time = 120;
-		  new->flags |= CONFIG_TIME;
-		}
-	    }
-	
 	daemon->dhcp_conf = new;
 	break;
       }
-
+      
     case LOPT_TAG_IF:  /* --tag-if */
       {
 	struct tag_if *new = opt_malloc(sizeof(struct tag_if));
diff --git a/src/rfc3315.c b/src/rfc3315.c
index ee1cf17..ee58b57 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -55,6 +55,8 @@ static struct prefix_class *prefix_class_from_context(struct dhcp_context *conte
 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);
@@ -717,7 +719,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
 		    /* 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);
@@ -745,8 +747,7 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
 	    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))
@@ -895,14 +896,13 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
 		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)
 		  {
@@ -1032,12 +1032,11 @@ static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_
 		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;
@@ -1760,6 +1759,76 @@ static int check_address(struct state *state, struct in6_addr *addr)
 }
 
 
+/* 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;
+  struct addrlist *addr_list;
+  
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return 0;
+  
+  for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+    {
+      prefix = (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128;
+      wild_addr = addr_list->addr.addr.addr6;
+      
+      if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+	{
+	  wild_addr = context->start6;
+	  setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr.addr6));
+	}
+      else if (!is_same_net6(&context->start6, addr, context->prefix))
+	continue;
+      
+      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, i, addresses;
+  struct addrlist *addr_list;
+  
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return 0;
+
+  for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+    {
+      addrpart = addr6part(&addr_list->addr.addr.addr6);
+      addresses = 1;
+
+      if (addr_list->flags & ADDRLIST_PREFIX)
+	addresses = 1<<(128-addr_list->prefixlen);
+		
+      if ((addr_list->flags & ADDRLIST_WILDCARD))
+	{
+	  if (context->prefix != 64)
+	    continue;
+      
+	  *addr = context->start6;
+	}
+      else if (is_same_net6(&context->start6, &addr_list->addr.addr.addr6, context->prefix))
+	*addr = addr_list->addr.addr.addr6;
+      else
+	continue;
+      
+      for (i = 0 ; i < addresses; i++)
+	{
+	  setaddr6part(addr, addrpart+i);
+
+	  if (check_address(state, addr))
+	    return 1;
+	}
+    }
+
+  return 0;
+}
+
 /* Calculate valid and preferred times to send in leases/renewals. 
 
    Inputs are:
-- 
1.8.3.1