Add --dhcp-relay config option.
diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index e5e136a..939f25a 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -757,6 +757,16 @@
 #endif
 
 }
-      
 
+void log_relay(int family, struct dhcp_relay *relay)
+{
+  inet_ntop(family, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+  inet_ntop(family, &relay->server, daemon->namebuff, ADDRSTRLEN); 
+
+  if (relay->interface)
+    my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface);
+  else
+    my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff);
+}
+   
 #endif
diff --git a/src/dhcp.c b/src/dhcp.c
index b95a4ba..573de5b 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -20,6 +20,8 @@
 
 struct iface_param {
   struct dhcp_context *current;
+  struct dhcp_relay *relay;
+  struct in_addr relay_local;
   int ind;
 };
 
@@ -32,6 +34,8 @@
 			    struct in_addr netmask, struct in_addr broadcast, void *vparam);
 static int check_listen_addrs(struct in_addr local, int if_index, char *label,
 			      struct in_addr netmask, struct in_addr broadcast, void *vparam);
+static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index);
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface);
 
 static int make_fd(int port)
 {
@@ -132,6 +136,8 @@
   int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd;
   struct dhcp_packet *mess;
   struct dhcp_context *context;
+  struct dhcp_relay *relay;
+  int is_relay_reply = 0;
   struct iname *tmp;
   struct ifreq ifr;
   struct msghdr msg;
@@ -250,57 +256,86 @@
     unicast_dest = 1;
 #endif
   
-  ifr.ifr_addr.sa_family = AF_INET;
-  if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
-    iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
+  if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
+    {
+      /* Reply from server, using us as relay. */
+      iface_index = relay->iface_index;
+      if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name))
+	return;
+      is_relay_reply = 1; 
+      iov.iov_len = sz;
+#ifdef HAVE_LINUX_NETWORK
+      strncpy(arp_req.arp_dev, ifr.ifr_name, 16);
+#endif 
+    }
   else
     {
-      my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
-      return;
-    }
-  
-  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
-    if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
-      return;
-  
-  /* unlinked contexts are marked by context->current == context */
-  for (context = daemon->dhcp; context; context = context->next)
-    context->current = context;
-  
-  parm.current = NULL;
-  parm.ind = iface_index;
-
-  if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL))
-    {
-      /* If we failed to match the primary address of the interface, see if we've got a --listen-address
-	 for a secondary */
-      struct match_param match;
+      ifr.ifr_addr.sa_family = AF_INET;
+      if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 )
+	iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
+      else
+	{
+	  my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
+	  return;
+	}
       
-      match.matched = 0;
-      match.ind = iface_index;
+      for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+	if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+	  return;
       
-      if (!daemon->if_addrs ||
-	  !iface_enumerate(AF_INET, &match, check_listen_addrs) ||
-	  !match.matched)
+      /* unlinked contexts/relays are marked by context->current == context */
+      for (context = daemon->dhcp; context; context = context->next)
+	context->current = context;
+      
+      for (relay = daemon->relay4; relay; relay = relay->next)
+	relay->current = relay;
+      
+      parm.current = NULL;
+      parm.relay = NULL;
+      parm.relay_local.s_addr = 0;
+      parm.ind = iface_index;
+      
+      if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL))
+	{
+	  /* If we failed to match the primary address of the interface, see if we've got a --listen-address
+	     for a secondary */
+	  struct match_param match;
+	  
+	  match.matched = 0;
+	  match.ind = iface_index;
+	  
+	  if (!daemon->if_addrs ||
+	      !iface_enumerate(AF_INET, &match, check_listen_addrs) ||
+	      !match.matched)
+	    return;
+	  
+	  iface_addr = match.addr;
+	  /* make sure secondary address gets priority in case
+	     there is more than one address on the interface in the same subnet */
+	  complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
+	}    
+      
+      if (!iface_enumerate(AF_INET, &parm, complete_context))
 	return;
 
-      iface_addr = match.addr;
-      /* make sure secondary address gets priority in case
-	 there is more than one address on the interface in the same subnet */
-      complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm);
-    }    
+      /* We're relaying this request */
+      if  (parm.relay_local.s_addr != 0 &&
+	   relay_upstream4(parm.relay, (struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, iface_index))
+	return;
+
+      /* May have configured relay, but not DHCP server */
+      if (!daemon->dhcp)
+	return;
+
+      lease_prune(NULL, now); /* lose any expired leases */
+      iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, 
+			       now, unicast_dest, &is_inform, pxe_fd, iface_addr);
+      lease_update_file(now);
+      lease_update_dns(0);
       
-  if (!iface_enumerate(AF_INET, &parm, complete_context))
-    return;
-  
-  lease_prune(NULL, now); /* lose any expired leases */
-  iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, 
-			   now, unicast_dest, &is_inform, pxe_fd, iface_addr);
-  lease_update_file(now);
-  lease_update_dns(0);
-    
-  if (iov.iov_len == 0)
-    return;
+      if (iov.iov_len == 0)
+	return;
+    }
   
   msg.msg_name = &dest;
   msg.msg_namelen = sizeof(dest);
@@ -321,7 +356,7 @@
       if (mess->ciaddr.s_addr != 0)
 	dest.sin_addr = mess->ciaddr;
     }
-  else if (mess->giaddr.s_addr)
+  else if (mess->giaddr.s_addr && !is_relay_reply)
     {
       /* Send to BOOTP relay  */
       dest.sin_port = htons(daemon->dhcp_server_port);
@@ -334,7 +369,7 @@
 	 source port too, and send back to that.  If we're replying 
 	 to a DHCPINFORM, trust the source address always. */
       if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) ||
-	  dest.sin_port == 0 || dest.sin_addr.s_addr == 0)
+	  dest.sin_port == 0 || dest.sin_addr.s_addr == 0 || is_relay_reply)
 	{
 	  dest.sin_port = htons(daemon->dhcp_client_port); 
 	  dest.sin_addr = mess->ciaddr;
@@ -450,6 +485,7 @@
 			    struct in_addr netmask, struct in_addr broadcast, void *vparam)
 {
   struct dhcp_context *context;
+  struct dhcp_relay *relay;
   struct iface_param *param = vparam;
 
   (void)label;
@@ -495,6 +531,15 @@
 	}		
     }
 
+  for (relay = daemon->relay4; relay; relay = relay->next)
+    if (if_index == param->ind && relay->local.addr.addr4.s_addr == local.s_addr && relay->current == relay &&
+	(param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr))
+      {
+	relay->current = param->relay;
+	param->relay = relay;
+	param->relay_local = local;	
+      }
+
   return 1;
 }
 	  
@@ -988,5 +1033,74 @@
   return NULL;
 }
 
-#endif
+static int  relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index)
+{
+  /* ->local is same value for all relays on ->current chain */
+  struct all_addr from;
+  
+  if (mess->op != BOOTREQUEST)
+    return 0;
 
+  /* source address == relay address */
+  from.addr.addr4 = relay->local.addr.addr4;
+  
+  /* already gatewayed ? */
+  if (mess->giaddr.s_addr)
+    {
+      /* if so check if by us, to stomp on loops. */
+      if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr)
+	return 1;
+    }
+  else
+    {
+      /* plug in our address */
+      mess->giaddr.s_addr = relay->local.addr.addr4.s_addr;
+    }
+
+  if ((mess->hops++) > 20)
+    return 1;
+
+  for (; relay; relay = relay->current)
+    {
+      union mysockaddr to;
+      
+      to.sa.sa_family = AF_INET;
+      to.in.sin_addr = relay->server.addr.addr4;
+      to.in.sin_port = htons(daemon->dhcp_server_port);
+      
+      send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0);
+      
+      if (option_bool(OPT_LOG_OPTS))
+	{
+	  inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+	  my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr.addr4));
+	}
+      
+      /* Save this for replies */
+      relay->iface_index = iface_index;
+    }
+  
+  return 1;
+}
+
+
+static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface)
+{
+  struct dhcp_relay *relay;
+
+  if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY)
+    return NULL;
+
+  for (relay = daemon->relay4; relay; relay = relay->next)
+    {
+      if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr)
+	{
+	  if (!relay->interface || wildcard_match(relay->interface, arrival_interface))
+	    return relay->iface_index != 0 ? relay : NULL;
+	}
+    }
+  
+  return NULL;	 
+}     
+
+#endif
diff --git a/src/dhcp6.c b/src/dhcp6.c
index 3bb855f..35bb748 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -20,7 +20,8 @@
 
 struct iface_param {
   struct dhcp_context *current;
-  struct in6_addr fallback;
+  struct dhcp_relay *relay;
+  struct in6_addr fallback, relay_local;
   int ind, addr_match;
 };
 
@@ -87,6 +88,7 @@
 void dhcp6_packet(time_t now)
 {
   struct dhcp_context *context;
+  struct dhcp_relay *relay;
   struct iface_param parm;
   struct cmsghdr *cmptr;
   struct msghdr msg;
@@ -126,56 +128,75 @@
 
   if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
     return;
-    
-  for (tmp = daemon->if_except; tmp; tmp = tmp->next)
-    if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
-      return;
 
-  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
-    if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
-      return;
- 
-  parm.current = NULL;
-  parm.ind = if_index;
-  parm.addr_match = 0;
-  memset(&parm.fallback, 0, IN6ADDRSZ);
-
-  for (context = daemon->dhcp6; context; context = context->next)
-    if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
-      {
-	/* wildcard context for DHCP-stateless only */
-	parm.current = context;
-	context->current = NULL;
-      }
-    else
-      {
-	/* unlinked contexts are marked by context->current == context */
-	context->current = context;
-	memset(&context->local6, 0, IN6ADDRSZ);
-      }
-  
-  if (!iface_enumerate(AF_INET6, &parm, complete_context6))
-    return;
-  
-  if (daemon->if_names || daemon->if_addrs)
+  if ((port = relay_reply6(&from, sz, ifr.ifr_name)) == 0)
     {
       
-      for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+      for (tmp = daemon->if_except; tmp; tmp = tmp->next)
 	if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
-	  break;
+	  return;
+      
+      for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+	if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+	  return;
+      
+      parm.current = NULL;
+      parm.relay = NULL;
+      memset(&parm.relay_local, 0, IN6ADDRSZ);
+      parm.ind = if_index;
+      parm.addr_match = 0;
+      memset(&parm.fallback, 0, IN6ADDRSZ);
+      
+      for (context = daemon->dhcp6; context; context = context->next)
+	if (IN6_IS_ADDR_UNSPECIFIED(&context->start6) && context->prefix == 0)
+	  {
+	    /* wildcard context for DHCP-stateless only */
+	    parm.current = context;
+	    context->current = NULL;
+	  }
+	else
+	  {
+	    /* unlinked contexts are marked by context->current == context */
+	    context->current = context;
+	    memset(&context->local6, 0, IN6ADDRSZ);
+	  }
 
-      if (!tmp && !parm.addr_match)
+      for (relay = daemon->relay6; relay; relay = relay->next)
+	relay->current = relay;
+      
+      if (!iface_enumerate(AF_INET6, &parm, complete_context6))
 	return;
+      
+      if (daemon->if_names || daemon->if_addrs)
+	{
+	  
+	  for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+	    if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+	      break;
+	  
+	  if (!tmp && !parm.addr_match)
+	    return;
+	}
+      
+      if (parm.relay)
+	{
+	  relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id);
+	  return;
+	}
+      
+      /* May have configured relay, but not DHCP server */
+      if (!daemon->doing_dhcp6)
+	return;
+      
+      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);
+      
+      lease_update_file(now);
+      lease_update_dns(0);
     }
-
-  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);
-  
-  lease_update_file(now);
-  lease_update_dns(0);
-  
+			  
   /* The port in the source address of the original request should
      be correct, but at least once client sends from the server port,
      so we explicitly send to the client port to a client, and the
@@ -194,70 +215,84 @@
 			     unsigned int valid, void *vparam)
 {
   struct dhcp_context *context;
+  struct dhcp_relay *relay;
   struct iface_param *param = vparam;
   struct iname *tmp;
  
   (void)scope; /* warning */
   
-  if (if_index == param->ind &&
-      !IN6_IS_ADDR_LOOPBACK(local) &&
-      !IN6_IS_ADDR_LINKLOCAL(local) &&
-      !IN6_IS_ADDR_MULTICAST(local))
+  if (if_index == param->ind)
     {
-      /* if we have --listen-address config, see if the 
-	 arrival interface has a matching address. */
-      for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
-	if (tmp->addr.sa.sa_family == AF_INET6 &&
-	    IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
-	  param->addr_match = 1;
-      
-      /* Determine a globally address on the arrival interface, even
-	 if we have no matching dhcp-context, because we're only
-	 allocating on remote subnets via relays. This
-	 is used as a default for the DNS server option. */
-      param->fallback = *local;
-      
-      for (context = daemon->dhcp6; context; context = context->next)
+      if (!IN6_IS_ADDR_LOOPBACK(local) &&
+	  !IN6_IS_ADDR_LINKLOCAL(local) &&
+	  !IN6_IS_ADDR_MULTICAST(local))
 	{
-	  if ((context->flags & CONTEXT_DHCP) &&
-	      !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
-	      prefix == context->prefix &&
-	      is_same_net6(local, &context->start6, prefix) &&
-	      is_same_net6(local, &context->end6, prefix))
+	  /* if we have --listen-address config, see if the 
+	     arrival interface has a matching address. */
+	  for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+	    if (tmp->addr.sa.sa_family == AF_INET6 &&
+		IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, local))
+	      param->addr_match = 1;
+	  
+	  /* Determine a globally address on the arrival interface, even
+	     if we have no matching dhcp-context, because we're only
+	     allocating on remote subnets via relays. This
+	     is used as a default for the DNS server option. */
+	  param->fallback = *local;
+	  
+	  for (context = daemon->dhcp6; context; context = context->next)
 	    {
-
-
-	      /* link it onto the current chain if we've not seen it before */
-	      if (context->current == context)
+	      if ((context->flags & CONTEXT_DHCP) &&
+		  !(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+		  prefix == context->prefix &&
+		  is_same_net6(local, &context->start6, prefix) &&
+		  is_same_net6(local, &context->end6, prefix))
 		{
-		  struct dhcp_context *tmp, **up;
 		  
-		  /* use interface values only for contructed contexts */
-		  if (!(context->flags & CONTEXT_CONSTRUCTED))
-		    preferred = valid = 0xffffffff;
-		  else if (flags & IFACE_DEPRECATED)
-		    preferred = 0;
-
-		  if (context->flags & CONTEXT_DEPRECATE)
-		    preferred = 0;
 		  
-		  /* order chain, longest preferred time first */
-		  for (up = &param->current, tmp = param->current; tmp; tmp = tmp->current)
-		    if (tmp->preferred <= preferred)
-		      break;
-		    else
-		      up = &tmp->current;
-		  
-		  context->current = *up;
-		  *up = context;
-		  context->local6 = *local;
-		  context->preferred = preferred;
-		  context->valid = valid;
+		  /* link it onto the current chain if we've not seen it before */
+		  if (context->current == context)
+		    {
+		      struct dhcp_context *tmp, **up;
+		      
+		      /* use interface values only for contructed contexts */
+		      if (!(context->flags & CONTEXT_CONSTRUCTED))
+			preferred = valid = 0xffffffff;
+		      else if (flags & IFACE_DEPRECATED)
+			preferred = 0;
+		      
+		      if (context->flags & CONTEXT_DEPRECATE)
+			preferred = 0;
+		      
+		      /* order chain, longest preferred time first */
+		      for (up = &param->current, tmp = param->current; tmp; tmp = tmp->current)
+			if (tmp->preferred <= preferred)
+			  break;
+			else
+			  up = &tmp->current;
+		      
+		      context->current = *up;
+		      *up = context;
+		      context->local6 = *local;
+		      context->preferred = preferred;
+		      context->valid = valid;
+		    }
 		}
 	    }
 	}
+
+      for (relay = daemon->relay6; relay; relay = relay->next)
+	if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr.addr6) && relay->current == relay &&
+	    (IN6_IS_ADDR_UNSPECIFIED(&param->relay_local) || IN6_ARE_ADDR_EQUAL(local, &param->relay_local)))
+	  {
+	    relay->current = param->relay;
+	    param->relay = relay;
+	    param->relay_local = *local;
+	  }
+      
     }          
-  return 1;
+ 
+ return 1;
 }
 
 struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix, u64 addr)
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 71d4412..4663591 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -52,6 +52,7 @@
   cap_user_data_t data = NULL;
 #endif 
   struct dhcp_context *context;
+  struct dhcp_relay *relay;
 
 #ifdef LOCALEDIR
   setlocale(LC_ALL, "");
@@ -166,50 +167,47 @@
   daemon->soa_sn = now;
 #endif
   
-#ifdef HAVE_DHCP
-  if (daemon->dhcp || daemon->dhcp6)
-    { 
+#ifdef HAVE_DHCP6
+  if (daemon->dhcp6)
+    {
+      daemon->doing_ra = option_bool(OPT_RA);
       
-#  ifdef HAVE_DHCP6
-      if (daemon->dhcp6)
+      for (context = daemon->dhcp6; context; context = context->next)
 	{
-	  daemon->doing_ra = option_bool(OPT_RA);
-	  
-	  for (context = daemon->dhcp6; context; context = context->next)
-	    {
-	      if (context->flags & CONTEXT_DHCP)
-		daemon->doing_dhcp6 = 1;
-	      if (context->flags & CONTEXT_RA)
-		daemon->doing_ra = 1;
+	  if (context->flags & CONTEXT_DHCP)
+	    daemon->doing_dhcp6 = 1;
+	  if (context->flags & CONTEXT_RA)
+	    daemon->doing_ra = 1;
 #ifndef  HAVE_LINUX_NETWORK
-	      if (context->flags & CONTEXT_TEMPLATE)
-		die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
+	  if (context->flags & CONTEXT_TEMPLATE)
+	    die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
 #endif 
-	    }
 	}
-#  endif
-
-      /* Note that order matters here, we must call lease_init before
-	 creating any file descriptors which shouldn't be leaked
-	 to the lease-script init process. We need to call common_init
-	 before lease_init to allocate buffers it uses.*/
-      if (daemon->dhcp || daemon->doing_dhcp6)
-	{
-	  dhcp_common_init();
-	  lease_init(now);
-	}
-
-      if (daemon->dhcp)
-	dhcp_init();
- 
-#  ifdef HAVE_DHCP6
-      if (daemon->doing_ra)
-	ra_init(now);
-      
-      if (daemon->doing_dhcp6)
-	dhcp6_init();
-#  endif
     }
+#endif
+  
+#ifdef HAVE_DHCP
+  /* Note that order matters here, we must call lease_init before
+     creating any file descriptors which shouldn't be leaked
+     to the lease-script init process. We need to call common_init
+     before lease_init to allocate buffers it uses.*/
+  if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || daemon->relay6)
+    {
+      dhcp_common_init();
+      if (daemon->dhcp || daemon->doing_dhcp6)
+	lease_init(now);
+    }
+  
+  if (daemon->dhcp || daemon->relay4)
+    dhcp_init();
+  
+#  ifdef HAVE_DHCP6
+  if (daemon->doing_ra)
+    ra_init(now);
+  
+  if (daemon->doing_dhcp6 || daemon->relay6)
+    dhcp6_init();
+#  endif
 
 #endif
 
@@ -239,7 +237,7 @@
 
 #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
       /* after enumerate_interfaces()  */
-      if (daemon->dhcp)
+      if (daemon->dhcp || daemon->relay4)
 	{
 	  bindtodevice(daemon->dhcpfd);
 	  if (daemon->enable_pxe)
@@ -248,7 +246,7 @@
 #endif
 
 #if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
-      if (daemon->doing_dhcp6)
+      if (daemon->doing_dhcp6 || daemon->relay6)
 	bindtodevice(daemon->dhcp6fd);
 #endif
     }
@@ -257,7 +255,7 @@
  
 #ifdef HAVE_DHCP6
   /* after enumerate_interfaces() */
-  if (daemon->doing_dhcp6 || daemon->doing_ra)
+  if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
     join_multicast(1);
 #endif
   
@@ -641,10 +639,16 @@
   for (context = daemon->dhcp; context; context = context->next)
     log_context(AF_INET, context);
 
+  for (relay = daemon->relay4; relay; relay = relay->next)
+    log_relay(AF_INET, relay);
+
 #  ifdef HAVE_DHCP6
   for (context = daemon->dhcp6; context; context = context->next)
     log_context(AF_INET6, context);
 
+  for (relay = daemon->relay6; relay; relay = relay->next)
+    log_relay(AF_INET6, relay);
+  
   if (daemon->doing_dhcp6 || daemon->doing_ra)
     dhcp_construct_contexts(now);
   
@@ -749,7 +753,7 @@
 #endif	
   
 #ifdef HAVE_DHCP
-      if (daemon->dhcp)
+      if (daemon->dhcp || daemon->relay4)
 	{
 	  FD_SET(daemon->dhcpfd, &rset);
 	  bump_maxfd(daemon->dhcpfd, &maxfd);
@@ -762,7 +766,7 @@
 #endif
 
 #ifdef HAVE_DHCP6
-      if (daemon->doing_dhcp6)
+      if (daemon->doing_dhcp6 || daemon->relay6)
 	{
 	  FD_SET(daemon->dhcp6fd, &rset);
 	  bump_maxfd(daemon->dhcp6fd, &maxfd);
@@ -874,7 +878,7 @@
 #endif      
 
 #ifdef HAVE_DHCP
-      if (daemon->dhcp)
+      if (daemon->dhcp || daemon->relay4)
 	{
 	  if (FD_ISSET(daemon->dhcpfd, &rset))
 	    dhcp_packet(now, 0);
@@ -883,7 +887,7 @@
 	}
 
 #ifdef HAVE_DHCP6
-      if (daemon->doing_dhcp6 && FD_ISSET(daemon->dhcp6fd, &rset))
+      if ((daemon->doing_dhcp6 || daemon->relay6) && FD_ISSET(daemon->dhcp6fd, &rset))
 	dhcp6_packet(now);
 
       if (daemon->doing_ra && FD_ISSET(daemon->icmp6fd, &rset))
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index f37034d..b1a1644 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -775,6 +775,12 @@
   struct tftp_prefix *next;
 };
 
+struct dhcp_relay {
+  struct all_addr local, server;
+  char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
+  int iface_index; /* working - interface in which requests arrived, for return */
+  struct dhcp_relay *current, *next;
+};
 
 extern struct daemon {
   /* datastuctures representing the command-line and 
@@ -824,6 +830,7 @@
   struct pxe_service *pxe_services;
   struct tag_if *tag_if; 
   struct addr_list *override_relays;
+  struct dhcp_relay *relay4, *relay6;
   int override;
   int enable_pxe;
   int doing_ra, doing_dhcp6;
@@ -1217,6 +1224,9 @@
 #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);
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id);
+
+unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
 #endif
 
 /* dhcp-common.c */
@@ -1243,6 +1253,7 @@
 void display_opts6(void);
 #  endif
 void log_context(int family, struct dhcp_context *context);
+void log_relay(int family, struct dhcp_relay *relay);
 #endif
 
 /* outpacket.c */
diff --git a/src/netlink.c b/src/netlink.c
index 43cd21e..2bcaf01 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -402,18 +402,18 @@
   	
 static void nl_newaddress(time_t now)
 {
-  if (option_bool(OPT_CLEVERBIND) || daemon->doing_dhcp6 || daemon->doing_ra)
+  if (option_bool(OPT_CLEVERBIND) || daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
     enumerate_interfaces(0);
   
   if (option_bool(OPT_CLEVERBIND))
     create_bound_listeners(0);
   
 #ifdef HAVE_DHCP6
+  if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+    join_multicast(0);
+  
   if (daemon->doing_dhcp6 || daemon->doing_ra)
-    {
-      join_multicast(0);
-      dhcp_construct_contexts(now);
-    }
+    dhcp_construct_contexts(now);
   
   if (daemon->doing_dhcp6)
     lease_find_interfaces(now);
diff --git a/src/network.c b/src/network.c
index 7a5d49e..46e49ce 100644
--- a/src/network.c
+++ b/src/network.c
@@ -851,7 +851,7 @@
 	    
 	    inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
 	    
-	    if (daemon->doing_dhcp6 &&
+	    if ((daemon->doing_dhcp6 || daemon->relay6) &&
 		setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
 	      err = 1;
 	    
diff --git a/src/option.c b/src/option.c
index b03388a..b12ca39 100644
--- a/src/option.c
+++ b/src/option.c
@@ -133,6 +133,7 @@
 #define LOPT_PREF_CLSS 321
 #endif
 #define LOPT_FAST_RA   322
+#define LOPT_RELAY     323
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -271,6 +272,7 @@
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
 #endif
     { "force-fast-ra", 0, 0, LOPT_FAST_RA },
+    { "dhcp-relay", 1, 0, LOPT_RELAY },
     { NULL, 0, 0, 0 }
   };
 
@@ -389,6 +391,7 @@
   { LOPT_DHCP_FQDN, OPT_DHCP_FQDN, NULL, gettext_noop("Use only fully qualified domain names for DHCP clients."), NULL },
   { LOPT_GEN_NAMES, ARG_DUP, "[=tag:<tag>]", gettext_noop("Generate hostnames based on MAC address for nameless clients."), NULL},
   { LOPT_PROXY, ARG_DUP, "[=<ipaddr>]...", gettext_noop("Use these DHCP relays as full proxies."), NULL },
+  { LOPT_RELAY, ARG_DUP, "<local-addr>,<server>[,<interface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL},
   { LOPT_CNAME, ARG_DUP, "<alias>,<target>", gettext_noop("Specify alias name for LOCAL DNS name."), NULL },
   { LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL },
   { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
@@ -3178,6 +3181,31 @@
 	arg = comma;
       }
       break;
+
+    case LOPT_RELAY: /* --dhcp-relay */
+      {
+	struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
+	comma = split(arg);
+	new->interface = opt_string_alloc(split(comma));
+	new->iface_index = 0;
+	if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server))
+	  {
+	    new->next = daemon->relay4;
+	    daemon->relay4 = new;
+	  }
+#ifdef HAVE_DHCP6
+	else if (inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server))
+	  {
+	    new->next = daemon->relay6;
+	    daemon->relay6 = new;
+	  }
+#endif
+	else
+	  ret_err(_("Bad dhcp-relay"));
+	
+	break;
+      }
+
 #endif
       
 #ifdef HAVE_DHCP6
diff --git a/src/outpacket.c b/src/outpacket.c
index 7f11cd3..9d64c01 100644
--- a/src/outpacket.c
+++ b/src/outpacket.c
@@ -70,9 +70,9 @@
 {
   void *p;
 
-  if ((p = expand(len)))
+  if ((p = expand(len)) && data)
     memcpy(p, data, len);   
-
+  
   return p;
 }
   
diff --git a/src/rfc3315.c b/src/rfc3315.c
index e1292fb..f0e9982 100644
--- a/src/rfc3315.c
+++ b/src/rfc3315.c
@@ -155,7 +155,8 @@
     return 0;
   
   /* copy header stuff into reply message and set type to reply */
-  outmsgtypep = put_opt6(inbuff, 34);
+  if (!(outmsgtypep = put_opt6(inbuff, 34)))
+    return 0;
   *outmsgtypep = DHCP6RELAYREPL;
 
   /* look for relay options and set tags if found. */
@@ -252,7 +253,8 @@
   state.tags = &v6_id;
 
   /* copy over transaction-id, and save pointer to message type */
-  outmsgtypep = put_opt6(inbuff, 4);
+  if (!(outmsgtypep = put_opt6(inbuff, 4)))
+    return 0;
   start_opts = save_counter(-1);
   state.xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16;
    
@@ -1912,4 +1914,118 @@
   return ret;
 } 
 
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, u32 scope_id)
+{
+  /* ->local is same value for all relays on ->current chain */
+  
+  struct all_addr from;
+  unsigned char *header;
+  unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+  int msg_type = *inbuff;
+  int hopcount;
+  struct in6_addr multicast;
+
+  inet_pton(AF_INET6, ALL_SERVERS, &multicast);
+   
+  /* source address == relay address */
+  from.addr.addr6 = relay->local.addr.addr6;
+    
+  /* Get hop count from nested relayed message */ 
+  if (msg_type == DHCP6RELAYFORW)
+    hopcount = *((unsigned char *)inbuff+1) + 1;
+  else
+    hopcount = 0;
+
+  /* RFC 3315 HOP_COUNT_LIMIT */
+  if (hopcount > 32)
+    return;
+
+  save_counter(0);
+
+  if ((header = put_opt6(NULL, 34)))
+    {
+      int o;
+
+      header[0] = DHCP6RELAYFORW;
+      header[1] = hopcount;
+      memcpy(&header[2],  &relay->local.addr.addr6, IN6ADDRSZ);
+      memcpy(&header[18], peer_address, IN6ADDRSZ);
+      
+      o = new_opt6(OPTION6_RELAY_MSG);
+      put_opt6(inbuff, sz);
+      end_opt6(o);
+      
+      for (; relay; relay = relay->current)
+	{
+	  union mysockaddr to;
+	  
+	  to.sa.sa_family = AF_INET6;
+	  to.in6.sin6_addr = relay->server.addr.addr6;
+	  to.in6.sin6_port = htons(DHCPV6_SERVER_PORT);
+	  to.in6.sin6_flowinfo = 0;
+	  to.in6.sin6_scope_id = 0;
+
+	  if (IN6_ARE_ADDR_EQUAL(&relay->server.addr.addr6, &multicast))
+	    {
+	      int multicast_iface;
+	      if (!relay->interface || strchr(relay->interface, '*') ||
+		  (multicast_iface = if_nametoindex(relay->interface)) == 0 ||
+		  setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &multicast_iface, sizeof(multicast_iface)) == -1)
+		my_syslog(MS_DHCP | LOG_ERR, _("Cannot multicast to DHCPv6 server without correct interface"));
+	    }
+		
+	  send_from(daemon->dhcp6fd, 0, daemon->outpacket.iov_base, save_counter(0), &to, &from, 0);
+	  
+	  if (option_bool(OPT_LOG_OPTS))
+	    {
+	      inet_ntop(AF_INET6, &relay->local, daemon->addrbuff, ADDRSTRLEN);
+	      inet_ntop(AF_INET6, &relay->server, daemon->namebuff, ADDRSTRLEN);
+	      my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->namebuff);
+	    }
+
+	  /* Save this for replies */
+	  relay->iface_index = scope_id;
+	}
+    }
+}
+
+unsigned short relay_reply6(struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface)
+{
+  struct dhcp_relay *relay;
+  struct in6_addr link;
+  unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+  
+  /* must have at least msg_type+hopcount+link_address+peer_address+minimal size option
+     which is               1   +    1   +    16      +     16     + 2 + 2 = 38 */
+  
+  if (sz < 38 || *inbuff != DHCP6RELAYREPL)
+    return 0;
+  
+  memcpy(&link, &inbuff[2], IN6ADDRSZ); 
+  
+  for (relay = daemon->relay6; relay; relay = relay->next)
+    if (IN6_ARE_ADDR_EQUAL(&link, &relay->local.addr.addr6) &&
+	(!relay->interface || wildcard_match(relay->interface, arrival_interface)))
+      break;
+      
+  save_counter(0);
+
+  if (relay)
+    {
+      void *opt, *opts = inbuff + 34;
+      void *end = inbuff + sz;
+      for (opt = opts; opt; opt = opt6_next(opt, end))
+	if (opt6_type(opt) == OPTION6_RELAY_MSG && opt6_len(opt) > 0)
+	  {
+	    int encap_type = *((unsigned char *)opt6_ptr(opt, 0));
+	    put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
+	    memcpy(&peer->sin6_addr, &inbuff[18], IN6ADDRSZ); 
+	    peer->sin6_scope_id = relay->iface_index;
+	    return encap_type == DHCP6RELAYREPL ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
+	  }
+    }
+
+  return 0;
+}
+
 #endif