Blame SOURCES/dnsmasq-2.66-Support-IPv6-assignment-based-on-MAC-for-DHCPv6.patch

dc4044
From 989461c95c82ac183eb3b01d2636b54e0731568f Mon Sep 17 00:00:00 2001
dc4044
From: Tomas Hozza <thozza@redhat.com>
dc4044
Date: Thu, 19 Feb 2015 17:37:52 +0100
dc4044
Subject: [PATCH] Support IPv6 assignment based on MAC for DHCPv6
dc4044
dc4044
Support added to --dhcp-host and --dhcp-mac
dc4044
dc4044
Signed-off-by: Tomas Hozza <thozza@redhat.com>
dc4044
---
dc4044
 man/dnsmasq.8        |  15 ++++---
dc4044
 src/dhcp-common.c    | 104 ++++++++++++++++++++++++++++++++++++++++++
dc4044
 src/dhcp.c           |  83 ----------------------------------
dc4044
 src/dhcp6-protocol.h |   1 +
dc4044
 src/dhcp6.c          | 124 +++++++++++++++++++++++++++++++++------------------
dc4044
 src/dnsmasq.c        |   2 +-
dc4044
 src/dnsmasq.h        |  28 ++++++------
dc4044
 src/helper.c         |  69 ++++++++++++++--------------
dc4044
 src/lease.c          |  60 +++++++++++++++----------
dc4044
 src/option.c         |   1 +
dc4044
 src/radv-protocol.h  |   7 +++
dc4044
 src/radv.c           |  14 +++---
dc4044
 src/rfc3315.c        | 104 ++++++++++++++++++++++++++++++++----------
dc4044
 13 files changed, 376 insertions(+), 236 deletions(-)
dc4044
dc4044
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
dc4044
index 045dee5..d0eb895 100644
dc4044
--- a/man/dnsmasq.8
dc4044
+++ b/man/dnsmasq.8
dc4044
@@ -733,7 +733,8 @@ the same subnet as some valid dhcp-range.  For
dc4044
 subnets which don't need a pool of dynamically allocated addresses,
dc4044
 use the "static" keyword in the dhcp-range declaration.
dc4044
 
dc4044
-It is allowed to use client identifiers rather than
dc4044
+It is allowed to use client identifiers (called client
dc4044
+DUID in IPv6-land rather than
dc4044
 hardware addresses to identify hosts by prefixing with 'id:'. Thus: 
dc4044
 .B --dhcp-host=id:01:02:03:04,..... 
dc4044
 refers to the host with client identifier 01:02:03:04. It is also
dc4044
@@ -748,11 +749,11 @@ IPv6 addresses may contain only the host-identifier part:
dc4044
 .B --dhcp-host=laptop,[::56]
dc4044
 in which case they act as wildcards in constructed dhcp ranges, with
dc4044
 the appropriate network part inserted. 
dc4044
-Note that in IPv6 DHCP, the hardware address is not normally
dc4044
-available, so a client must be identified by client-id (called client
dc4044
-DUID in IPv6-land) or hostname. 
dc4044
+Note that in IPv6 DHCP, the hardware address may not be
dc4044
+available, though it normally is for direct-connected clients, or
dc4044
+clients using DHCP relays which support RFC 6939.
dc4044
 
dc4044
-The special option id:* means "ignore any client-id 
dc4044
+For DHCPv4, the  special option id:* means "ignore any client-id
dc4044
 and use MAC addresses only." This is useful when a client presents a client-id sometimes 
dc4044
 but not others.
dc4044
 
dc4044
@@ -968,7 +969,7 @@ this to set a different printer server for hosts in the class
dc4044
 "accounts" than for hosts in the class "engineering".
dc4044
 .TP
dc4044
 .B \-4, --dhcp-mac=set:<tag>,<MAC address>
dc4044
-(IPv4 only) Map from a MAC address to a tag. The MAC address may include
dc4044
+Map from a MAC address to a tag. The MAC address may include
dc4044
 wildcards. For example
dc4044
 .B --dhcp-mac=set:3com,01:34:23:*:*:*
dc4044
 will set the tag "3com" for any host whose MAC address matches the pattern.
dc4044
@@ -1274,7 +1275,7 @@ every call to the script.
dc4044
 DNSMASQ_IAID containing the IAID for the lease. If the lease is a
dc4044
 temporary allocation, this is prefixed to 'T'.
dc4044
 
dc4044
-
dc4044
+DNSMASQ_MAC containing the MAC address of the client, if known.
dc4044
 
dc4044
 Note that the supplied hostname, vendorclass and userclass data is
dc4044
 only  supplied for
dc4044
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
dc4044
index 9321e92..242bd72 100644
dc4044
--- a/src/dhcp-common.c
dc4044
+++ b/src/dhcp-common.c
dc4044
@@ -253,6 +253,110 @@ int match_bytes(struct dhcp_opt *o, unsigned char *p, int len)
dc4044
   return 0;
dc4044
 }
dc4044
 
dc4044
+int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type)
dc4044
+{
dc4044
+  struct hwaddr_config *conf_addr;
dc4044
+  
dc4044
+  for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next)
dc4044
+    if (conf_addr->wildcard_mask == 0 &&
dc4044
+	conf_addr->hwaddr_len == len &&
dc4044
+	(conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) &&
dc4044
+	memcmp(conf_addr->hwaddr, hwaddr, len) == 0)
dc4044
+      return 1;
dc4044
+  
dc4044
+  return 0;
dc4044
+}
dc4044
+
dc4044
+static int is_config_in_context(struct dhcp_context *context, struct dhcp_config *config)
dc4044
+{
dc4044
+  if (!context) /* called via find_config() from lease_update_from_configs() */
dc4044
+    return 1; 
dc4044
+
dc4044
+  if (!(context->flags & CONTEXT_V6))
dc4044
+    {
dc4044
+      if (!(config->flags & CONFIG_ADDR))
dc4044
+	return 1;
dc4044
+
dc4044
+      for (; context; context = context->current)
dc4044
+	if (is_same_net(config->addr, context->start, context->netmask))
dc4044
+	  return 1;
dc4044
+    }
dc4044
+#ifdef HAVE_DHCP6
dc4044
+  else 
dc4044
+    {
dc4044
+      if (!(config->flags & CONFIG_ADDR6) || (config->flags & CONFIG_WILDCARD))
dc4044
+	return 1;
dc4044
+      
dc4044
+      for (; context; context = context->current)
dc4044
+	if (is_same_net6(&config->addr6, &context->start6, context->prefix))
dc4044
+      return 1;
dc4044
+    }
dc4044
+#endif
dc4044
+  
dc4044
+  return 0;
dc4044
+}
dc4044
+
dc4044
+struct dhcp_config *find_config(struct dhcp_config *configs,
dc4044
+				struct dhcp_context *context,
dc4044
+				unsigned char *clid, int clid_len,
dc4044
+				unsigned char *hwaddr, int hw_len, 
dc4044
+				int hw_type, char *hostname)
dc4044
+{
dc4044
+  int count, new;
dc4044
+  struct dhcp_config *config, *candidate; 
dc4044
+  struct hwaddr_config *conf_addr;
dc4044
+
dc4044
+  if (clid)
dc4044
+    for (config = configs; config; config = config->next)
dc4044
+      if (config->flags & CONFIG_CLID)
dc4044
+	{
dc4044
+	  if (config->clid_len == clid_len && 
dc4044
+	      memcmp(config->clid, clid, clid_len) == 0 &&
dc4044
+	      is_config_in_context(context, config))
dc4044
+	    return config;
dc4044
+	  
dc4044
+	  /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and
dc4044
+	     cope with that here */
dc4044
+	  if (!(context->flags & CONTEXT_V6) && *clid == 0 && config->clid_len == clid_len-1  &&
dc4044
+	      memcmp(config->clid, clid+1, clid_len-1) == 0 &&
dc4044
+	      is_config_in_context(context, config))
dc4044
+	    return config;
dc4044
+	}
dc4044
+  
dc4044
+
dc4044
+  if (hwaddr)
dc4044
+    for (config = configs; config; config = config->next)
dc4044
+      if (config_has_mac(config, hwaddr, hw_len, hw_type) &&
dc4044
+	  is_config_in_context(context, config))
dc4044
+	return config;
dc4044
+  
dc4044
+  if (hostname && context)
dc4044
+    for (config = configs; config; config = config->next)
dc4044
+      if ((config->flags & CONFIG_NAME) && 
dc4044
+	  hostname_isequal(config->hostname, hostname) &&
dc4044
+	  is_config_in_context(context, config))
dc4044
+	return config;
dc4044
+
dc4044
+  
dc4044
+  if (!hwaddr)
dc4044
+    return NULL;
dc4044
+
dc4044
+  /* use match with fewest wildcard octets */
dc4044
+  for (candidate = NULL, count = 0, config = configs; config; config = config->next)
dc4044
+    if (is_config_in_context(context, config))
dc4044
+      for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next)
dc4044
+	if (conf_addr->wildcard_mask != 0 &&
dc4044
+	    conf_addr->hwaddr_len == hw_len &&	
dc4044
+	    (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) &&
dc4044
+	    (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count)
dc4044
+	  {
dc4044
+	      count = new;
dc4044
+	      candidate = config;
dc4044
+	  }
dc4044
+  
dc4044
+  return candidate;
dc4044
+}
dc4044
+
dc4044
 void dhcp_update_configs(struct dhcp_config *configs)
dc4044
 {
dc4044
   /* Some people like to keep all static IP addresses in /etc/hosts.
dc4044
diff --git a/src/dhcp.c b/src/dhcp.c
dc4044
index b95a4ba..f69183e 100644
dc4044
--- a/src/dhcp.c
dc4044
+++ b/src/dhcp.c
dc4044
@@ -704,89 +704,6 @@ int address_allocate(struct dhcp_context *context,
dc4044
   return 0;
dc4044
 }
dc4044
 
dc4044
-static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config *config)
dc4044
-{
dc4044
-  if (!context) /* called via find_config() from lease_update_from_configs() */
dc4044
-    return 1; 
dc4044
-  if (!(config->flags & CONFIG_ADDR))
dc4044
-    return 1;
dc4044
-  for (; context; context = context->current)
dc4044
-    if (is_same_net(config->addr, context->start, context->netmask))
dc4044
-      return 1;
dc4044
-  
dc4044
-  return 0;
dc4044
-}
dc4044
-
dc4044
-int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type)
dc4044
-{
dc4044
-  struct hwaddr_config *conf_addr;
dc4044
-  
dc4044
-  for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next)
dc4044
-    if (conf_addr->wildcard_mask == 0 &&
dc4044
-	conf_addr->hwaddr_len == len &&
dc4044
-	(conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) &&
dc4044
-	memcmp(conf_addr->hwaddr, hwaddr, len) == 0)
dc4044
-      return 1;
dc4044
-  
dc4044
-  return 0;
dc4044
-}
dc4044
-
dc4044
-struct dhcp_config *find_config(struct dhcp_config *configs,
dc4044
-				struct dhcp_context *context,
dc4044
-				unsigned char *clid, int clid_len,
dc4044
-				unsigned char *hwaddr, int hw_len, 
dc4044
-				int hw_type, char *hostname)
dc4044
-{
dc4044
-  int count, new;
dc4044
-  struct dhcp_config *config, *candidate; 
dc4044
-  struct hwaddr_config *conf_addr;
dc4044
-
dc4044
-  if (clid)
dc4044
-    for (config = configs; config; config = config->next)
dc4044
-      if (config->flags & CONFIG_CLID)
dc4044
-	{
dc4044
-	  if (config->clid_len == clid_len && 
dc4044
-	      memcmp(config->clid, clid, clid_len) == 0 &&
dc4044
-	      is_addr_in_context(context, config))
dc4044
-	    return config;
dc4044
-	  
dc4044
-	  /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and
dc4044
-	     cope with that here */
dc4044
-	  if (*clid == 0 && config->clid_len == clid_len-1  &&
dc4044
-	      memcmp(config->clid, clid+1, clid_len-1) == 0 &&
dc4044
-	      is_addr_in_context(context, config))
dc4044
-	    return config;
dc4044
-	}
dc4044
-  
dc4044
-
dc4044
-  for (config = configs; config; config = config->next)
dc4044
-    if (config_has_mac(config, hwaddr, hw_len, hw_type) &&
dc4044
-	is_addr_in_context(context, config))
dc4044
-      return config;
dc4044
-  
dc4044
-  if (hostname && context)
dc4044
-    for (config = configs; config; config = config->next)
dc4044
-      if ((config->flags & CONFIG_NAME) && 
dc4044
-	  hostname_isequal(config->hostname, hostname) &&
dc4044
-	  is_addr_in_context(context, config))
dc4044
-	return config;
dc4044
-
dc4044
-  /* use match with fewest wildcard octets */
dc4044
-  for (candidate = NULL, count = 0, config = configs; config; config = config->next)
dc4044
-    if (is_addr_in_context(context, config))
dc4044
-      for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next)
dc4044
-	if (conf_addr->wildcard_mask != 0 &&
dc4044
-	    conf_addr->hwaddr_len == hw_len &&	
dc4044
-	    (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) &&
dc4044
-	    (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count)
dc4044
-	  {
dc4044
-	    count = new;
dc4044
-	    candidate = config;
dc4044
-	  }
dc4044
-
dc4044
-  return candidate;
dc4044
-}
dc4044
-
dc4044
 void dhcp_read_ethers(void)
dc4044
 {
dc4044
   FILE *f = fopen(ETHERSFILE, "r");
dc4044
diff --git a/src/dhcp6-protocol.h b/src/dhcp6-protocol.h
dc4044
index 50d84a9..8cef0e8 100644
dc4044
--- a/src/dhcp6-protocol.h
dc4044
+++ b/src/dhcp6-protocol.h
dc4044
@@ -58,6 +58,7 @@
dc4044
 #define OPTION6_REMOTE_ID       37
dc4044
 #define OPTION6_SUBSCRIBER_ID   38
dc4044
 #define OPTION6_FQDN            39
dc4044
+#define OPTION6_CLIENT_MAC      79
dc4044
 
dc4044
 /* replace this with the real number when allocated.
dc4044
    defining this also enables the relevant code. */ 
dc4044
diff --git a/src/dhcp6.c b/src/dhcp6.c
dc4044
index de3187d..5f4d298 100644
dc4044
--- a/src/dhcp6.c
dc4044
+++ b/src/dhcp6.c
dc4044
@@ -18,16 +18,25 @@
dc4044
 
dc4044
 #ifdef HAVE_DHCP6
dc4044
 
dc4044
+#include <netinet/icmp6.h>
dc4044
+
dc4044
 struct iface_param {
dc4044
   struct dhcp_context *current;
dc4044
   struct in6_addr fallback;
dc4044
   int ind, addr_match;
dc4044
 };
dc4044
 
dc4044
+struct mac_param {
dc4044
+  struct in6_addr *target;
dc4044
+  unsigned char *mac;
dc4044
+  unsigned int maclen;
dc4044
+};
dc4044
+
dc4044
 static int complete_context6(struct in6_addr *local,  int prefix,
dc4044
 			     int scope, int if_index, int flags, 
dc4044
 			     unsigned int preferred, unsigned int valid, void *vparam);
dc4044
 
dc4044
+static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv);
dc4044
 static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); 
dc4044
 
dc4044
 void dhcp6_init(void)
dc4044
@@ -156,7 +165,7 @@ void dhcp6_packet(time_t now)
dc4044
   
dc4044
   if (!iface_enumerate(AF_INET6, &parm, complete_context6))
dc4044
     return;
dc4044
-  
dc4044
+
dc4044
   if (daemon->if_names || daemon->if_addrs)
dc4044
     {
dc4044
       
dc4044
@@ -171,7 +180,7 @@ void dhcp6_packet(time_t now)
dc4044
   lease_prune(NULL, now); /* lose any expired leases */
dc4044
 
dc4044
   port = dhcp6_reply(parm.current, if_index, ifr.ifr_name, &parm.fallback, 
dc4044
-		     sz, IN6_IS_ADDR_MULTICAST(&from.sin6_addr), now);
dc4044
+		     sz, &from.sin6_addr, now);
dc4044
   
dc4044
   lease_update_file(now);
dc4044
   lease_update_dns(0);
dc4044
@@ -189,6 +198,75 @@ void dhcp6_packet(time_t now)
dc4044
     }
dc4044
 }
dc4044
 
dc4044
+void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep)
dc4044
+{
dc4044
+  /* Recieving a packet from a host does not populate the neighbour
dc4044
+     cache, so we send a neighbour discovery request if we can't 
dc4044
+     find the sender. Repeat a few times in case of packet loss. */
dc4044
+  
dc4044
+  struct neigh_packet neigh;
dc4044
+  struct sockaddr_in6 addr;
dc4044
+  struct mac_param mac_param;
dc4044
+  int i;
dc4044
+
dc4044
+  neigh.type = ND_NEIGHBOR_SOLICIT;
dc4044
+  neigh.code = 0;
dc4044
+  neigh.reserved = 0;
dc4044
+  neigh.target = *client;
dc4044
+  // https://tools.ietf.org/html/rfc4443#section-2.3
dc4044
+  neigh.checksum = 0;
dc4044
+  
dc4044
+  memset(&addr, 0, sizeof(addr));
dc4044
+#ifdef HAVE_SOCKADDR_SA_LEN
dc4044
+  addr.sin6_len = sizeof(struct sockaddr_in6);
dc4044
+#endif
dc4044
+  addr.sin6_family = AF_INET6;
dc4044
+  addr.sin6_port = htons(IPPROTO_ICMPV6);
dc4044
+  addr.sin6_addr = *client;
dc4044
+  addr.sin6_scope_id = iface;
dc4044
+  
dc4044
+  mac_param.target = client;
dc4044
+  mac_param.maclen = 0;
dc4044
+  mac_param.mac = mac;
dc4044
+  
dc4044
+  for (i = 0; i < 5; i++)
dc4044
+    {
dc4044
+      struct timespec ts;
dc4044
+      
dc4044
+      iface_enumerate(AF_UNSPEC, &mac_param, find_mac);
dc4044
+      
dc4044
+      if (mac_param.maclen != 0)
dc4044
+	break;
dc4044
+      
dc4044
+      sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, (struct sockaddr *)&addr, sizeof(addr));
dc4044
+      
dc4044
+      ts.tv_sec = 0;
dc4044
+      ts.tv_nsec = 100000000; /* 100ms */
dc4044
+      nanosleep(&ts, NULL);
dc4044
+    }
dc4044
+
dc4044
+  *maclenp = mac_param.maclen;
dc4044
+  *mactypep = ARPHRD_ETHER;
dc4044
+}
dc4044
+
dc4044
+static int find_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
dc4044
+{
dc4044
+  struct mac_param *parm = parmv;
dc4044
+  
dc4044
+  if (family == AF_INET6 && IN6_ARE_ADDR_EQUAL(parm->target, addrp))
dc4044
+    {
dc4044
+      if (maclen <= DHCP_CHADDR_MAX)
dc4044
+	{
dc4044
+	  parm->maclen = maclen;
dc4044
+	  memcpy(parm->mac, mac, maclen);
dc4044
+	}
dc4044
+      
dc4044
+      return 0; /* found, abort */
dc4044
+    }
dc4044
+  
dc4044
+  return 1;
dc4044
+}
dc4044
+
dc4044
 static int complete_context6(struct in6_addr *local,  int prefix,
dc4044
 			     int scope, int if_index, int flags, unsigned int preferred, 
dc4044
 			     unsigned int valid, void *vparam)
dc4044
@@ -400,48 +478,6 @@ int config_valid(struct dhcp_config *config, struct dhcp_context *context, struc
dc4044
   return 0;
dc4044
 }
dc4044
 
dc4044
-static int is_config_in_context6(struct dhcp_context *context, struct dhcp_config *config)
dc4044
-{
dc4044
-  if (!(config->flags & CONFIG_ADDR6) || 
dc4044
-      (config->flags & CONFIG_WILDCARD))
dc4044
-
dc4044
-    return 1;
dc4044
-  
dc4044
-  for (; context; context = context->current)
dc4044
-    if (is_same_net6(&config->addr6, &context->start6, context->prefix))
dc4044
-      return 1;
dc4044
-      
dc4044
-  return 0;
dc4044
-}
dc4044
-
dc4044
-
dc4044
-struct dhcp_config *find_config6(struct dhcp_config *configs,
dc4044
-				 struct dhcp_context *context,
dc4044
-				 unsigned char *duid, int duid_len,
dc4044
-				 char *hostname)
dc4044
-{
dc4044
-  struct dhcp_config *config; 
dc4044
-      
dc4044
-  if (duid)
dc4044
-    for (config = configs; config; config = config->next)
dc4044
-      if (config->flags & CONFIG_CLID)
dc4044
-	{
dc4044
-	  if (config->clid_len == duid_len && 
dc4044
-	      memcmp(config->clid, duid, duid_len) == 0 &&
dc4044
-	      is_config_in_context6(context, config))
dc4044
-	    return config;
dc4044
-	}
dc4044
-    
dc4044
-  if (hostname && context)
dc4044
-    for (config = configs; config; config = config->next)
dc4044
-      if ((config->flags & CONFIG_NAME) && 
dc4044
-          hostname_isequal(config->hostname, hostname) &&
dc4044
-          is_config_in_context6(context, config))
dc4044
-        return config;
dc4044
-
dc4044
-  return NULL;
dc4044
-}
dc4044
-
dc4044
 void make_duid(time_t now)
dc4044
 {
dc4044
   if (daemon->duid_config)
dc4044
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
dc4044
index b0f984d..3c0d7e7 100644
dc4044
--- a/src/dnsmasq.c
dc4044
+++ b/src/dnsmasq.c
dc4044
@@ -203,7 +203,7 @@ int main (int argc, char **argv)
dc4044
 	dhcp_init();
dc4044
  
dc4044
 #  ifdef HAVE_DHCP6
dc4044
-      if (daemon->doing_ra)
dc4044
+      if (daemon->doing_ra || daemon->doing_dhcp6)
dc4044
 	ra_init(now);
dc4044
       
dc4044
       if (daemon->doing_dhcp6)
dc4044
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
dc4044
index ca000ad..45e3d6d 100644
dc4044
--- a/src/dnsmasq.h
dc4044
+++ b/src/dnsmasq.h
dc4044
@@ -530,13 +530,15 @@ struct dhcp_lease {
dc4044
 #ifdef HAVE_BROKEN_RTC
dc4044
   unsigned int length;
dc4044
 #endif
dc4044
-  int hwaddr_len, hwaddr_type; /* hw_type used for iaid in v6 */
dc4044
-  unsigned char hwaddr[DHCP_CHADDR_MAX]; /* also IPv6 address */
dc4044
+  int hwaddr_len, hwaddr_type;
dc4044
+  unsigned char hwaddr[DHCP_CHADDR_MAX];
dc4044
   struct in_addr addr, override, giaddr;
dc4044
   unsigned char *extradata;
dc4044
   unsigned int extradata_len, extradata_size;
dc4044
   int last_interface;
dc4044
 #ifdef HAVE_DHCP6
dc4044
+  struct in6_addr addr6;
dc4044
+  int iaid;
dc4044
   struct slaac_address {
dc4044
     struct in6_addr addr, local;
dc4044
     time_t ping_time;
dc4044
@@ -724,6 +726,7 @@ struct dhcp_context {
dc4044
 #define CONTEXT_RA          8192
dc4044
 #define CONTEXT_CONF_USED  16384
dc4044
 #define CONTEXT_USED       32768
dc4044
+#define CONTEXT_V6         65536
dc4044
 
dc4044
 struct ping_result {
dc4044
   struct in_addr addr;
dc4044
@@ -1050,12 +1053,6 @@ struct dhcp_context *narrow_context(struct dhcp_context *context,
dc4044
 int address_allocate(struct dhcp_context *context,
dc4044
 		     struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
dc4044
 		     struct dhcp_netid *netids, time_t now);
dc4044
-int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type);
dc4044
-struct dhcp_config *find_config(struct dhcp_config *configs,
dc4044
-				struct dhcp_context *context,
dc4044
-				unsigned char *clid, int clid_len,
dc4044
-				unsigned char *hwaddr, int hw_len, 
dc4044
-				int hw_type, char *hostname);
dc4044
 void dhcp_read_ethers(void);
dc4044
 struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr);
dc4044
 char *host_from_dns(struct in_addr addr);
dc4044
@@ -1077,6 +1074,7 @@ struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 add
dc4044
 u64 lease_find_max_addr6(struct dhcp_context *context);
dc4044
 void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface);
dc4044
 void lease_update_slaac(time_t now);
dc4044
+void lease_set_iaid(struct dhcp_lease *lease, int iaid);
dc4044
 #endif
dc4044
 void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr,
dc4044
 		      unsigned char *clid, int hw_len, int hw_type, int clid_len, time_t now, int force);
dc4044
@@ -1188,20 +1186,18 @@ struct dhcp_context *address6_valid(struct dhcp_context *context,
dc4044
 				    struct in6_addr *taddr,
dc4044
 				    struct dhcp_netid *netids,
dc4044
 				    int plain_range);
dc4044
-struct dhcp_config *find_config6(struct dhcp_config *configs,
dc4044
-				 struct dhcp_context *context,
dc4044
-				 unsigned char *duid, int duid_len,
dc4044
-				 char *hostname);
dc4044
 struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, 
dc4044
 					    int prefix, u64 addr);
dc4044
 void make_duid(time_t now);
dc4044
 void dhcp_construct_contexts(time_t now);
dc4044
+void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac,
dc4044
+		    unsigned int *maclenp, unsigned int *mactypep);
dc4044
 #endif
dc4044
 
dc4044
 /* rfc3315.c */
dc4044
 #ifdef HAVE_DHCP6
dc4044
 unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,  
dc4044
-			   struct in6_addr *fallback, size_t sz, int is_multicast, time_t now);
dc4044
+			   struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now);
dc4044
 #endif
dc4044
 
dc4044
 /* dhcp-common.c */
dc4044
@@ -1221,6 +1217,12 @@ int lookup_dhcp_opt(int prot, char *name);
dc4044
 int lookup_dhcp_len(int prot, int val);
dc4044
 char *option_string(int prot, unsigned int opt, unsigned char *val, 
dc4044
 		    int opt_len, char *buf, int buf_len);
dc4044
+struct dhcp_config *find_config(struct dhcp_config *configs,
dc4044
+				struct dhcp_context *context,
dc4044
+				unsigned char *clid, int clid_len,
dc4044
+				unsigned char *hwaddr, int hw_len, 
dc4044
+				int hw_type, char *hostname);
dc4044
+int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type);
dc4044
 #ifdef HAVE_LINUX_NETWORK
dc4044
 void bindtodevice(int fd);
dc4044
 #endif
dc4044
diff --git a/src/helper.c b/src/helper.c
dc4044
index ab691b7..c6838ba 100644
dc4044
--- a/src/helper.c
dc4044
+++ b/src/helper.c
dc4044
@@ -61,6 +61,10 @@ struct script_data
dc4044
 #else
dc4044
   time_t expires;
dc4044
 #endif
dc4044
+#ifdef HAVE_DHCP6
dc4044
+  struct in6_addr addr6;
dc4044
+  int iaid, vendorclass_count;
dc4044
+#endif
dc4044
   unsigned char hwaddr[DHCP_CHADDR_MAX];
dc4044
   char interface[IF_NAMESIZE];
dc4044
 
dc4044
@@ -215,8 +219,6 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	continue;
dc4044
 
dc4044
       	
dc4044
-      if (!is6)
dc4044
-	{
dc4044
 	  /* stringify MAC into dhcp_buff */
dc4044
 	  p = daemon->dhcp_buff;
dc4044
 	  if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0) 
dc4044
@@ -227,7 +229,6 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	      if (i != data.hwaddr_len - 1)
dc4044
 		p += sprintf(p, ":");
dc4044
 	    }
dc4044
-	}
dc4044
        
dc4044
       /* supplied data may just exceed normal buffer (unlikely) */
dc4044
       if ((data.hostname_len + data.ed_len + data.clid_len) > MAXDNAME && 
dc4044
@@ -239,7 +240,6 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	continue;
dc4044
 
dc4044
       /* CLID into packet */
dc4044
-      if (!is6)
dc4044
 	for (p = daemon->packet, i = 0; i < data.clid_len; i++)
dc4044
 	  {
dc4044
 	    p += sprintf(p, "%.2x", buf[i]);
dc4044
@@ -247,24 +247,17 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	      p += sprintf(p, ":");
dc4044
 	  }
dc4044
 #ifdef HAVE_DHCP6
dc4044
-      else
dc4044
+      if (is6)
dc4044
 	{
dc4044
 	  /* or IAID and server DUID for IPv6 */
dc4044
-	  sprintf(daemon->dhcp_buff3, "%s%u", data.flags & LEASE_TA ? "T" : "", data.hwaddr_type);	
dc4044
-	  for (p = daemon->packet, i = 0; i < daemon->duid_len; i++)
dc4044
+	  sprintf(daemon->dhcp_buff3, "%s%u", data.flags & LEASE_TA ? "T" : "", data.iaid);	
dc4044
+	  for (p = daemon->dhcp_packet.iov_base, i = 0; i < daemon->duid_len; i++)
dc4044
 	    {
dc4044
 	      p += sprintf(p, "%.2x", daemon->duid[i]);
dc4044
 	      if (i != daemon->duid_len - 1) 
dc4044
 		p += sprintf(p, ":");
dc4044
 	    }
dc4044
 
dc4044
-	  /* duid not MAC for IPv6 */
dc4044
-	  for (p = daemon->dhcp_buff, i = 0; i < data.clid_len; i++)
dc4044
-	    {
dc4044
-	      p += sprintf(p, "%.2x", buf[i]);
dc4044
-	      if (i != data.clid_len - 1) 
dc4044
-		p += sprintf(p, ":");
dc4044
-	    } 
dc4044
 	}
dc4044
 #endif
dc4044
 
dc4044
@@ -293,7 +286,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN);
dc4044
 #ifdef HAVE_DHCP6
dc4044
       else
dc4044
-	inet_ntop(AF_INET6, &data.hwaddr, daemon->addrbuff, ADDRSTRLEN);
dc4044
+	inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN);
dc4044
 #endif
dc4044
 
dc4044
       /* file length */
dc4044
@@ -329,9 +322,9 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	      
dc4044
 	      if (is6)
dc4044
 		{
dc4044
-		  lua_pushstring(lua, daemon->dhcp_buff);
dc4044
-		  lua_setfield(lua, -2, "client_duid");
dc4044
 		  lua_pushstring(lua, daemon->packet);
dc4044
+		  lua_setfield(lua, -2, "client_duid");
dc4044
+		  lua_pushstring(lua, daemon->dhcp_packet.iov_base);
dc4044
 		  lua_setfield(lua, -2, "server_duid");
dc4044
 		  lua_pushstring(lua, daemon->dhcp_buff3);
dc4044
 		  lua_setfield(lua, -2, "iaid");
dc4044
@@ -375,12 +368,16 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	      if (!is6)
dc4044
 		buf = grab_extradata_lua(buf, end, "vendor_class");
dc4044
 #ifdef HAVE_DHCP6
dc4044
-	      else
dc4044
-		for (i = 0; i < data.hwaddr_len; i++)
dc4044
-		  {
dc4044
-		    sprintf(daemon->dhcp_buff2, "vendor_class%i", i);
dc4044
-		    buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
dc4044
-		  }
dc4044
+	      else  if (data.vendorclass_count != 0)
dc4044
+		{
dc4044
+		  sprintf(daemon->dhcp_buff2, "vendor_class_id");
dc4044
+		  buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
dc4044
+		  for (i = 0; i < data.vendorclass_count - 1; i++)
dc4044
+		    {
dc4044
+		      sprintf(daemon->dhcp_buff2, "vendor_class%i", i);
dc4044
+		      buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
dc4044
+		    }
dc4044
+		}
dc4044
 #endif
dc4044
 	      
dc4044
 	      buf = grab_extradata_lua(buf, end, "supplied_hostname");
dc4044
@@ -423,7 +420,7 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 		  lua_setfield(lua, -2, "old_hostname");
dc4044
 		}
dc4044
 	      
dc4044
-	      if (!is6)
dc4044
+	      if (!is6 || data.hwaddr_len != 0)
dc4044
 		{
dc4044
 		  lua_pushstring(lua, daemon->dhcp_buff);
dc4044
 		  lua_setfield(lua, -2, "mac_address");
dc4044
@@ -476,12 +473,15 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
       
dc4044
       if (data.action != ACTION_TFTP)
dc4044
 	{
dc4044
+#ifdef HAVE_DHCP6
dc4044
 	  if (is6)
dc4044
 	    {
dc4044
 	      my_setenv("DNSMASQ_IAID", daemon->dhcp_buff3, &err;;
dc4044
-	      my_setenv("DNSMASQ_SERVER_DUID", daemon->packet, &err;;
dc4044
+	      my_setenv("DNSMASQ_SERVER_DUID", daemon->dhcp_packet.iov_base, &err;; 
dc4044
+	      if (data.hwaddr_len != 0)
dc4044
+		my_setenv("DNSMASQ_MAC", daemon->dhcp_buff, &err;;
dc4044
 	    }
dc4044
-	  
dc4044
+#endif
dc4044
 	  if (!is6 && data.clid_len != 0)
dc4044
 	    my_setenv("DNSMASQ_CLIENT_ID", daemon->packet, &err;;
dc4044
 	  
dc4044
@@ -507,10 +507,10 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 #ifdef HAVE_DHCP6
dc4044
 	  else
dc4044
 	    {
dc4044
-	      if (data.hwaddr_len != 0)
dc4044
+	      if (data.vendorclass_count != 0)
dc4044
 		{
dc4044
 		  buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS_ID", &err;;
dc4044
-		  for (i = 0; i < data.hwaddr_len - 1; i++)
dc4044
+		  for (i = 0; i < data.vendorclass_count - 1; i++)
dc4044
 		    {
dc4044
 		      sprintf(daemon->dhcp_buff2, "DNSMASQ_VENDOR_CLASS%i", i);
dc4044
 		      buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err;;
dc4044
@@ -570,7 +570,8 @@ int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
dc4044
 	{
dc4044
 	  execl(daemon->lease_change_command, 
dc4044
 		p ? p+1 : daemon->lease_change_command,
dc4044
-		action_str, daemon->dhcp_buff, daemon->addrbuff, hostname, (char*)NULL);
dc4044
+		action_str, is6 ? daemon->packet : daemon->dhcp_buff, 
dc4044
+		daemon->addrbuff, hostname, (char*)NULL);
dc4044
 	  err = errno;
dc4044
 	}
dc4044
       /* failed, send event so the main process logs the problem */
dc4044
@@ -656,8 +657,6 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n
dc4044
   unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
dc4044
   int fd = daemon->dhcpfd;
dc4044
 #ifdef HAVE_DHCP6 
dc4044
-  int is6 = !!(lease->flags & (LEASE_TA | LEASE_NA));
dc4044
-
dc4044
   if (!daemon->dhcp)
dc4044
     fd = daemon->dhcp6fd;
dc4044
 #endif
dc4044
@@ -677,10 +676,10 @@ void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t n
dc4044
 
dc4044
   buf->action = action;
dc4044
   buf->flags = lease->flags;
dc4044
-#ifdef HAVE_DHCP6 
dc4044
-  if (is6)
dc4044
-    buf->hwaddr_len = lease->vendorclass_count;
dc4044
-  else
dc4044
+#ifdef HAVE_DHCP6
dc4044
+  buf->vendorclass_count = lease->vendorclass_count;
dc4044
+  buf->addr6 = lease->addr6;
dc4044
+  buf->iaid = lease->iaid; 
dc4044
 #endif
dc4044
     buf->hwaddr_len = lease->hwaddr_len;
dc4044
   buf->hwaddr_type = lease->hwaddr_type;
dc4044
diff --git a/src/lease.c b/src/lease.c
dc4044
index b85cf57..e5fe8a6 100644
dc4044
--- a/src/lease.c
dc4044
+++ b/src/lease.c
dc4044
@@ -108,6 +108,7 @@ void lease_init(time_t now)
dc4044
 	  {
dc4044
 	    char *s = daemon->dhcp_buff2;
dc4044
 	    int lease_type = LEASE_NA;
dc4044
+        int iaid;
dc4044
 
dc4044
 	    if (s[0] == 'T')
dc4044
 	      {
dc4044
@@ -115,12 +116,12 @@ void lease_init(time_t now)
dc4044
 		s++;
dc4044
 	      }
dc4044
 	    
dc4044
-	    hw_type = strtoul(s, NULL, 10);
dc4044
+	    iaid = strtoul(s, NULL, 10);
dc4044
 	    
dc4044
 	    if ((lease = lease6_allocate(&addr.addr.addr6, lease_type)))
dc4044
 	      {
dc4044
-		lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, hw_type, clid_len, now, 0);
dc4044
-		
dc4044
+		lease_set_hwaddr(lease, NULL, (unsigned char *)daemon->packet, 0, 0, clid_len, now, 0);
dc4044
+		lease_set_iaid(lease, iaid);
dc4044
 		if (strcmp(daemon->dhcp_buff, "*") !=  0)
dc4044
 		  lease_set_hostname(lease, daemon->dhcp_buff, 0, get_domain6((struct in6_addr *)lease->hwaddr), NULL);
dc4044
 	      }
dc4044
@@ -187,7 +188,9 @@ void lease_update_from_configs(void)
dc4044
   char *name;
dc4044
   
dc4044
   for (lease = leases; lease; lease = lease->next)
dc4044
-    if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, 
dc4044
+    if (lease->flags & (LEASE_TA | LEASE_NA))
dc4044
+      continue;
dc4044
+    else if ((config = find_config(daemon->dhcp_conf, NULL, lease->clid, lease->clid_len, 
dc4044
 			      lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, NULL)) && 
dc4044
 	(config->flags & CONFIG_NAME) &&
dc4044
 	(!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr))
dc4044
@@ -277,10 +280,10 @@ void lease_update_file(time_t now)
dc4044
 	      ourprintf(&err, "%lu ", (unsigned long)lease->expires);
dc4044
 #endif
dc4044
     
dc4044
-	      inet_ntop(AF_INET6, lease->hwaddr, daemon->addrbuff, ADDRSTRLEN);
dc4044
+	      inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
dc4044
 	 
dc4044
 	      ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "",
dc4044
-			lease->hwaddr_type, daemon->addrbuff);
dc4044
+			lease->iaid, daemon->addrbuff);
dc4044
 	      ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
dc4044
 	      
dc4044
 	      if (lease->clid && lease->clid_len != 0)
dc4044
@@ -376,7 +379,7 @@ static int find_interface_v6(struct in6_addr *local,  int prefix,
dc4044
 
dc4044
   for (lease = leases; lease; lease = lease->next)
dc4044
     if ((lease->flags & (LEASE_TA | LEASE_NA)))
dc4044
-      if (is_same_net6(local, (struct in6_addr *)&lease->hwaddr, prefix))
dc4044
+      if (is_same_net6(local, &lease->addr6, prefix))
dc4044
 	lease_set_interface(lease, if_index, *((time_t *)vparam));
dc4044
   
dc4044
   return 1;
dc4044
@@ -463,12 +466,12 @@ void lease_update_dns(int force)
dc4044
 	  
dc4044
 	  if (lease->fqdn)
dc4044
 	    cache_add_dhcp_entry(lease->fqdn, prot, 
dc4044
-				 prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->hwaddr,
dc4044
+				 prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6,
dc4044
 				 lease->expires);
dc4044
 	     
dc4044
 	  if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
dc4044
 	    cache_add_dhcp_entry(lease->hostname, prot, 
dc4044
-				 prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->hwaddr, 
dc4044
+				 prot == AF_INET ? (struct all_addr *)&lease->addr : (struct all_addr *)&lease->addr6, 
dc4044
 				 lease->expires);
dc4044
 	}
dc4044
       
dc4044
@@ -564,10 +567,10 @@ struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len,
dc4044
   
dc4044
   for (lease = leases; lease; lease = lease->next)
dc4044
     {
dc4044
-      if (!(lease->flags & lease_type) || lease->hwaddr_type != iaid)
dc4044
+      if (!(lease->flags & lease_type) || lease->iaid != iaid)
dc4044
 	continue;
dc4044
 
dc4044
-      if (memcmp(lease->hwaddr, addr, IN6ADDRSZ) != 0)
dc4044
+      if (!IN6_ARE_ADDR_EQUAL(&lease->addr6, addr))
dc4044
 	continue;
dc4044
       
dc4044
       if ((clid_len != lease->clid_len ||
dc4044
@@ -604,7 +607,7 @@ struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_typ
dc4044
       if (lease->flags & LEASE_USED)
dc4044
 	continue;
dc4044
 
dc4044
-      if (!(lease->flags & lease_type) || lease->hwaddr_type != iaid)
dc4044
+      if (!(lease->flags & lease_type) || lease->iaid != iaid)
dc4044
 	continue;
dc4044
  
dc4044
       if ((clid_len != lease->clid_len ||
dc4044
@@ -626,8 +629,8 @@ struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 add
dc4044
       if (!(lease->flags & (LEASE_TA | LEASE_NA)))
dc4044
 	continue;
dc4044
       
dc4044
-      if (is_same_net6((struct in6_addr *)lease->hwaddr, net, prefix) &&
dc4044
-	  (prefix == 128 || addr6part((struct in6_addr *)lease->hwaddr) == addr))
dc4044
+      if (is_same_net6(&lease->addr6, net, prefix) &&
dc4044
+	  (prefix == 128 || addr6part(&lease->addr6) == addr))
dc4044
 	return lease;
dc4044
     }
dc4044
   
dc4044
@@ -646,11 +649,11 @@ u64 lease_find_max_addr6(struct dhcp_context *context)
dc4044
 	if (!(lease->flags & (LEASE_TA | LEASE_NA)))
dc4044
 	  continue;
dc4044
 
dc4044
-	if (is_same_net6((struct in6_addr *)lease->hwaddr, &context->start6, 64) &&
dc4044
-	    addr6part((struct in6_addr *)lease->hwaddr) > addr6part(&context->start6) &&
dc4044
-	    addr6part((struct in6_addr *)lease->hwaddr) <= addr6part(&context->end6) &&
dc4044
-	    addr6part((struct in6_addr *)lease->hwaddr) > addr)
dc4044
-	  addr = addr6part((struct in6_addr *)lease->hwaddr);
dc4044
+	if (is_same_net6(&lease->addr6, &context->start6, 64) &&
dc4044
+	    addr6part(&lease->addr6) > addr6part(&context->start6) &&
dc4044
+	    addr6part(&lease->addr6) <= addr6part(&context->end6) &&
dc4044
+	    addr6part(&lease->addr6) > addr)
dc4044
+	  addr = addr6part(&lease->addr6);
dc4044
       }
dc4044
   
dc4044
   return addr;
dc4044
@@ -692,6 +695,7 @@ static struct dhcp_lease *lease_allocate(void)
dc4044
 #ifdef HAVE_BROKEN_RTC
dc4044
   lease->length = 0xffffffff; /* illegal value */
dc4044
 #endif
dc4044
+  lease->hwaddr_len = 256; /* illegal value */
dc4044
   lease->next = leases;
dc4044
   leases = lease;
dc4044
   
dc4044
@@ -707,7 +711,6 @@ struct dhcp_lease *lease4_allocate(struct in_addr addr)
dc4044
   if (lease)
dc4044
     {
dc4044
       lease->addr = addr;
dc4044
-      lease->hwaddr_len = 256; /* illegal value */
dc4044
     }
dc4044
 
dc4044
   return lease;
dc4044
@@ -720,8 +723,9 @@ struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type)
dc4044
 
dc4044
   if (lease)
dc4044
     {
dc4044
-      memcpy(lease->hwaddr, addrp, sizeof(*addrp)) ;
dc4044
+      lease->addr6 = *addrp;
dc4044
       lease->flags |= lease_type;
dc4044
+      lease->iaid = 0;
dc4044
     }
dc4044
 
dc4044
   return lease;
dc4044
@@ -758,6 +762,17 @@ void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
dc4044
 #endif
dc4044
 } 
dc4044
 
dc4044
+#ifdef HAVE_DHCP6
dc4044
+void lease_set_iaid(struct dhcp_lease *lease, int iaid)
dc4044
+{
dc4044
+  if (lease->iaid != iaid)
dc4044
+    {
dc4044
+      lease->iaid = iaid;
dc4044
+      lease->flags |= LEASE_CHANGED;
dc4044
+    }
dc4044
+}
dc4044
+#endif
dc4044
+
dc4044
 void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr,
dc4044
 		      unsigned char *clid, int hw_len, int hw_type, int clid_len, 
dc4044
 		      time_t now, int force)
dc4044
@@ -779,9 +794,6 @@ void lease_set_hwaddr(struct dhcp_lease *lease, unsigned char *hwaddr,
dc4044
       lease->hwaddr_type = hw_type;
dc4044
       lease->flags |= LEASE_CHANGED;
dc4044
       file_dirty = 1; /* run script on change */
dc4044
-#ifdef HAVE_DHCP6
dc4044
-      change = 1;
dc4044
-#endif
dc4044
     }
dc4044
 
dc4044
   /* only update clid when one is available, stops packets
dc4044
diff --git a/src/option.c b/src/option.c
dc4044
index 9f63d0e..2fc90cb 100644
dc4044
--- a/src/option.c
dc4044
+++ b/src/option.c
dc4044
@@ -2361,6 +2361,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
dc4044
 	  {
dc4044
 	    new->prefix = 64; /* default */
dc4044
 	    new->end6 = new->start6;
dc4044
+        new->flags |= CONTEXT_V6;
dc4044
 	    
dc4044
 	    /* dhcp-range=:: enables DHCP stateless on any interface */
dc4044
 	    if (IN6_IS_ADDR_UNSPECIFIED(&new->start6))
dc4044
diff --git a/src/radv-protocol.h b/src/radv-protocol.h
dc4044
index 1f0f88a..8d5b153 100644
dc4044
--- a/src/radv-protocol.h
dc4044
+++ b/src/radv-protocol.h
dc4044
@@ -33,6 +33,13 @@ struct ra_packet {
dc4044
   u32 retrans_time;
dc4044
 };
dc4044
 
dc4044
+struct neigh_packet {
dc4044
+  u8 type, code;
dc4044
+  u16 checksum;
dc4044
+  u16 reserved;
dc4044
+  struct in6_addr target;
dc4044
+};
dc4044
+
dc4044
 struct prefix_opt {
dc4044
   u8 type, len, prefix_len, flags;
dc4044
   u32 valid_lifetime, preferred_lifetime, reserved;
dc4044
diff --git a/src/radv.c b/src/radv.c
dc4044
index 72a93cb..940d6a1 100644
dc4044
--- a/src/radv.c
dc4044
+++ b/src/radv.c
dc4044
@@ -68,12 +68,15 @@ void ra_init(time_t now)
dc4044
   for (context = daemon->dhcp6; context; context = context->next)
dc4044
     if ((context->flags & CONTEXT_RA_NAME))
dc4044
       break;
dc4044
+  /* Need ICMP6 socket for transmission for DHCPv6 even when not doing RA. */
dc4044
   
dc4044
   ICMP6_FILTER_SETBLOCKALL(&filter);
dc4044
-  ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
dc4044
-  if (context)
dc4044
-    ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
dc4044
-  
dc4044
+  if (daemon->doing_ra)
dc4044
+    {
dc4044
+      ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
dc4044
+      if (context)
dc4044
+	ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
dc4044
+    } 
dc4044
   if ((fd = socket(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) == -1 ||
dc4044
       getsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hop_limit, &len) ||
dc4044
 #if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
dc4044
@@ -88,7 +91,8 @@ void ra_init(time_t now)
dc4044
   
dc4044
    daemon->icmp6fd = fd;
dc4044
    
dc4044
-   ra_start_unsolicted(now, NULL);
dc4044
+   if (daemon->doing_ra)
dc4044
+     ra_start_unsolicted(now, NULL);
dc4044
 }
dc4044
 
dc4044
 void ra_start_unsolicted(time_t now, struct dhcp_context *context)
dc4044
diff --git a/src/rfc3315.c b/src/rfc3315.c
dc4044
index c8ba3d0..0408e18 100644
dc4044
--- a/src/rfc3315.c
dc4044
+++ b/src/rfc3315.c
dc4044
@@ -29,15 +29,20 @@ struct state {
dc4044
   char *iface_name;
dc4044
   void *packet_options, *end;
dc4044
   struct dhcp_netid *tags, *context_tags;
dc4044
+  unsigned char mac[DHCP_CHADDR_MAX];
dc4044
+  unsigned int mac_len, mac_type;
dc4044
 #ifdef OPTION6_PREFIX_CLASS
dc4044
   struct prefix_class *send_prefix_class;
dc4044
 #endif
dc4044
 };
dc4044
 
dc4044
-static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **relay_tagsp, struct dhcp_context *context, 
dc4044
-			     int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now);
dc4044
+static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **relay_tagsp, struct dhcp_context *context,
dc4044
+			     int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz,
dc4044
+                 struct in6_addr *client_addr, int is_unicast, time_t now,
dc4044
+                 unsigned char *mac, unsigned int mac_len, unsigned int mac_type);
dc4044
 static int dhcp6_no_relay(int msg_type,  struct in6_addr *link_address, struct dhcp_netid *tags, struct dhcp_context *context, 
dc4044
-			  int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now);
dc4044
+			  int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now,
dc4044
+			  unsigned int mac_len, unsigned int mac_type, unsigned char *mac);
dc4044
 static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts);
dc4044
 static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string);
dc4044
 
dc4044
@@ -68,11 +73,14 @@ static void calculate_times(struct dhcp_context *context, unsigned int *min_time
dc4044
 
dc4044
 
dc4044
 unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
dc4044
-			   struct in6_addr *fallback, size_t sz, int is_unicast, time_t now)
dc4044
+			   struct in6_addr *fallback, size_t sz, struct in6_addr *client_addr, time_t now)
dc4044
 {
dc4044
   struct dhcp_netid *relay_tags = NULL;
dc4044
   struct dhcp_vendor *vendor;
dc4044
   int msg_type;
dc4044
+  unsigned int mac_len = 0;
dc4044
+  unsigned int mac_type = 0;
dc4044
+  unsigned char mac[DHCP_CHADDR_MAX];
dc4044
   
dc4044
   if (sz <= 4)
dc4044
     return 0;
dc4044
@@ -85,7 +93,10 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
dc4044
   
dc4044
   save_counter(0);
dc4044
   
dc4044
-  if (dhcp6_maybe_relay(NULL, &relay_tags, context, interface, iface_name, fallback, daemon->dhcp_packet.iov_base, sz, is_unicast, now))
dc4044
+  if (dhcp6_maybe_relay(NULL, &relay_tags, context, interface, iface_name,
dc4044
+              fallback, daemon->dhcp_packet.iov_base, sz, client_addr,
dc4044
+			  IN6_IS_ADDR_MULTICAST(client_addr), now,
dc4044
+              mac, mac_len, mac_type))
dc4044
     return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
dc4044
 
dc4044
   return 0;
dc4044
@@ -93,7 +104,9 @@ unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *if
dc4044
 
dc4044
 /* This cost me blood to write, it will probably cost you blood to understand - srk. */
dc4044
 static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **relay_tagsp, struct dhcp_context *context,
dc4044
-			     int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now)
dc4044
+			     int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz,
dc4044
+                 struct in6_addr *client_addr, int is_unicast, time_t now,
dc4044
+                 unsigned char *mac, unsigned int mac_len, unsigned int mac_type)
dc4044
 {
dc4044
   void *end = inbuff + sz;
dc4044
   void *opts = inbuff + 34;
dc4044
@@ -108,9 +121,14 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
dc4044
       /* if link_address != NULL if points to the link address field of the 
dc4044
 	 innermost nested RELAYFORW message, which is where we find the
dc4044
 	 address of the network on which we can allocate an address.
dc4044
-	 Recalculate the available contexts using that information. */
dc4044
-      
dc4044
-      if (link_address)
dc4044
+	 Recalculate the available contexts using that information. 
dc4044
+
dc4044
+      link_address == NULL means there's no relay in use, so we try and find the client's 
dc4044
+      MAC address from the local ND cache. */
dc4044
+
dc4044
+      if (!link_address)
dc4044
+	get_client_mac(client_addr, interface, mac, &mac_len, &mac_type);
dc4044
+      else
dc4044
 	{
dc4044
 	  struct dhcp_context *c;
dc4044
 	  context = NULL;
dc4044
@@ -146,7 +164,8 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
dc4044
 	  return 0;
dc4044
 	}
dc4044
 
dc4044
-      return dhcp6_no_relay(msg_type, link_address, *relay_tagsp, context, interface, iface_name, fallback, inbuff, sz, is_unicast, now);
dc4044
+      return dhcp6_no_relay(msg_type, link_address, *relay_tagsp, context, interface, iface_name, fallback, inbuff,
dc4044
+              sz, is_unicast, now, mac_len, mac_type, mac);
dc4044
     }
dc4044
 
dc4044
   /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
dc4044
@@ -180,21 +199,31 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
dc4044
 	  break;
dc4044
 	}
dc4044
     }
dc4044
-  
dc4044
+
dc4044
+  /* RFC-6939 */
dc4044
+  if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))
dc4044
+    {
dc4044
+      mac_type = opt6_uint(opt, 0, 2);
dc4044
+      mac_len = opt6_len(opt) - 2;
dc4044
+      mac = opt6_ptr(opt, 2);
dc4044
+    }
dc4044
+
dc4044
   for (opt = opts; opt; opt = opt6_next(opt, end))
dc4044
     {
dc4044
       int o = new_opt6(opt6_type(opt));
dc4044
       if (opt6_type(opt) == OPTION6_RELAY_MSG)
dc4044
 	{
dc4044
-	  struct in6_addr link_address;
dc4044
+	  struct in6_addr align;
dc4044
 	  /* the packet data is unaligned, copy to aligned storage */
dc4044
-	  memcpy(&link_address, inbuff + 2, IN6ADDRSZ); 
dc4044
+	  memcpy(&align, inbuff + 2, IN6ADDRSZ);
dc4044
 	  /* Not, zero is_unicast since that is now known to refer to the 
dc4044
 	     relayed packet, not the original sent by the client */
dc4044
-	  if (!dhcp6_maybe_relay(&link_address, relay_tagsp, context, interface, iface_name, fallback, opt6_ptr(opt, 0), opt6_len(opt), 0, now))
dc4044
+	  if (!dhcp6_maybe_relay(&align, relay_tagsp, context, interface, iface_name, fallback,
dc4044
+                  opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now,
dc4044
+                  mac, mac_len, mac_type))
dc4044
 	    return 0;
dc4044
 	}
dc4044
-      else
dc4044
+      else if (opt6_type(opt) != OPTION6_CLIENT_MAC)
dc4044
 	put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
dc4044
       end_opt6(o);	    
dc4044
     }
dc4044
@@ -203,7 +232,8 @@ static int dhcp6_maybe_relay(struct in6_addr *link_address, struct dhcp_netid **
dc4044
 }
dc4044
 
dc4044
 static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dhcp_netid *tags, struct dhcp_context *context, 
dc4044
-			  int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now)
dc4044
+			  int interface, char *iface_name, struct in6_addr *fallback, void *inbuff, size_t sz, int is_unicast, time_t now,
dc4044
+              unsigned int mac_len, unsigned int mac_type, unsigned char *mac)
dc4044
 {
dc4044
   void *opt;
dc4044
   int i, o, o1, start_opts;
dc4044
@@ -214,6 +244,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
   unsigned char *outmsgtypep;
dc4044
   struct dhcp_vendor *vendor;
dc4044
   struct dhcp_context *context_tmp;
dc4044
+  struct dhcp_mac *mac_opt;
dc4044
   unsigned int ignore = 0;
dc4044
   struct state state;
dc4044
 #ifdef OPTION6_PREFIX_CLASS
dc4044
@@ -237,6 +268,9 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
   state.client_hostname = NULL;
dc4044
   state.iface_name = iface_name;
dc4044
   state.fqdn_flags = 0x01; /* default to send if we recieve no FQDN option */
dc4044
+  state.mac_len = mac_len;
dc4044
+  state.mac_type = mac_type;
dc4044
+  memcpy(state.mac, mac, mac_len);
dc4044
 #ifdef OPTION6_PREFIX_CLASS
dc4044
   state.send_prefix_class = NULL;
dc4044
 #endif
dc4044
@@ -390,7 +424,17 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
 	  state.tags = opt_cfg->netid;
dc4044
 	}
dc4044
     }
dc4044
-  
dc4044
+
dc4044
+  if (mac_len != 0)
dc4044
+    for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next)
dc4044
+      if ((unsigned)mac_opt->hwaddr_len == mac_len &&
dc4044
+	  ((unsigned)mac_opt->hwaddr_type == mac_type || mac_opt->hwaddr_type == 0) &&
dc4044
+	  memcmp_masked(mac_opt->hwaddr, mac, mac_len, mac_opt->mask))
dc4044
+	{
dc4044
+	  mac_opt->netid.next = state.tags;
dc4044
+	  state.tags = &mac_opt->netid;
dc4044
+	}
dc4044
+
dc4044
   if ((opt = opt6_find(state.packet_options, state.end, OPTION6_FQDN, 1)))
dc4044
     {
dc4044
       /* RFC4704 refers */
dc4044
@@ -433,7 +477,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
   
dc4044
   if (state.clid)
dc4044
     {
dc4044
-      config = find_config6(daemon->dhcp_conf, context, state.clid, state.clid_len, NULL);
dc4044
+      config = find_config(daemon->dhcp_conf, context, state.clid, state.clid_len, mac, mac_len, mac_type, NULL);
dc4044
       
dc4044
       if (have_config(config, CONFIG_NAME))
dc4044
 	{
dc4044
@@ -453,7 +497,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
 		  /* Search again now we have a hostname. 
dc4044
 		     Only accept configs without CLID here, (it won't match)
dc4044
 		     to avoid impersonation by name. */
dc4044
-		  struct dhcp_config *new = find_config6(daemon->dhcp_conf, context, NULL, 0, state.hostname);
dc4044
+		  struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, NULL, 0, 0, state.hostname);
dc4044
 		  if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr)
dc4044
 		    config = new;
dc4044
 		}
dc4044
@@ -704,7 +748,7 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
 	    ltmp = NULL;
dc4044
 	    while ((ltmp = lease6_find_by_client(ltmp, state.ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, state.clid, state.clid_len, state.iaid)))
dc4044
 	      {
dc4044
-		req_addr = (struct in6_addr *)ltmp->hwaddr;
dc4044
+		req_addr = &ltmp->addr6;
dc4044
 		if ((c = address6_available(context, req_addr, solicit_tags, plain_range)))
dc4044
 		  {
dc4044
 #ifdef OPTION6_PREFIX_CLASS
dc4044
@@ -935,6 +979,9 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
 		    calculate_times(this_context, &min_time, &valid_time, &preferred_time, lease_time, requested_time); 
dc4044
 		    
dc4044
 		    lease_set_expires(lease, valid_time, now);
dc4044
+		    /* Update MAC record in case it's new information. */
dc4044
+		    if (mac_len != 0)
dc4044
+		      lease_set_hwaddr(lease, mac, state.clid, mac_len, mac_type, state.clid_len, now, 0);
dc4044
 		    if (state.ia_type == OPTION6_IA_NA && state.hostname)
dc4044
 		      {
dc4044
 			char *addr_domain = get_domain6(req_addr);
dc4044
@@ -1163,8 +1210,16 @@ static int dhcp6_no_relay(int msg_type, struct in6_addr *link_address, struct dh
dc4044
   log_tags(tagif, state.xid);
dc4044
   
dc4044
   if (option_bool(OPT_LOG_OPTS))
dc4044
-    log6_opts(0, state.xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1));
dc4044
-  
dc4044
+    {
dc4044
+      if (mac_len != 0)
dc4044
+	{
dc4044
+	  print_mac(daemon->dhcp_buff, mac, mac_len);
dc4044
+	  my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state.xid, daemon->dhcp_buff);
dc4044
+	}
dc4044
+      
dc4044
+      log6_opts(0, state.xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1));
dc4044
+    }
dc4044
+
dc4044
   return 1;
dc4044
 
dc4044
 }
dc4044
@@ -1548,7 +1603,7 @@ static int check_address(struct state *state, struct in6_addr *addr)
dc4044
 
dc4044
   if (lease->clid_len != state->clid_len || 
dc4044
       memcmp(lease->clid, state->clid, state->clid_len) != 0 ||
dc4044
-      lease->hwaddr_type != state->iaid)
dc4044
+      lease->iaid != state->iaid)
dc4044
     return 0;
dc4044
 
dc4044
   return 1;
dc4044
@@ -1591,7 +1646,8 @@ static void update_leases(struct state *state, struct dhcp_context *context, str
dc4044
   if (lease)
dc4044
     {
dc4044
       lease_set_expires(lease, lease_time, now);
dc4044
-      lease_set_hwaddr(lease, NULL, state->clid, 0, state->iaid, state->clid_len, now, 0);
dc4044
+      lease_set_iaid(lease, state->iaid); 
dc4044
+      lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0);
dc4044
       lease_set_interface(lease, state->interface, now);
dc4044
       if (state->hostname && state->ia_type == OPTION6_IA_NA)
dc4044
 	{
dc4044
-- 
dc4044
2.1.0
dc4044