Blob Blame History Raw
From 989461c95c82ac183eb3b01d2636b54e0731568f Mon Sep 17 00:00:00 2001
From: Tomas Hozza <thozza@redhat.com>
Date: Thu, 19 Feb 2015 17:37:52 +0100
Subject: [PATCH] Support IPv6 assignment based on MAC for DHCPv6

Support added to --dhcp-host and --dhcp-mac

Signed-off-by: Tomas Hozza <thozza@redhat.com>
---
 man/dnsmasq.8        |  15 ++++---
 src/dhcp-common.c    | 104 ++++++++++++++++++++++++++++++++++++++++++
 src/dhcp.c           |  83 ----------------------------------
 src/dhcp6-protocol.h |   1 +
 src/dhcp6.c          | 124 +++++++++++++++++++++++++++++++++------------------
 src/dnsmasq.c        |   2 +-
 src/dnsmasq.h        |  28 ++++++------
 src/helper.c         |  69 ++++++++++++++--------------
 src/lease.c          |  60 +++++++++++++++----------
 src/option.c         |   1 +
 src/radv-protocol.h  |   7 +++
 src/radv.c           |  14 +++---
 src/rfc3315.c        | 104 ++++++++++++++++++++++++++++++++----------
 13 files changed, 376 insertions(+), 236 deletions(-)

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