/* dnsmasq is Copyright (c) 2000-2026 Simon Kelley

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 dated June, 1991, or
   (at your option) version 3 dated 29 June, 2007.
 
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
     
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "dnsmasq.h"

#ifdef HAVE_DHCP6

#include <netinet/icmp6.h>

struct iface_param {
  struct dhcp_context *current;
  struct in6_addr fallback, ll_addr, ula_addr;
  int ind, addr_match;
};


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 make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm); 

void dhcp6_init(void)
{
  int fd;
  struct sockaddr_in6 saddr;
#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
  int class = IPTOS_CLASS_CS6;
#endif
  int oneopt = 1;

  if ((fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1 ||
#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
      setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
#endif
      setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &oneopt, sizeof(oneopt)) == -1 ||
      !fix_fd(fd) ||
      !set_ipv6pktinfo(fd))
    die (_("cannot create DHCPv6 socket: %s"), NULL, EC_BADNET);
  
 /* When bind-interfaces is set, there might be more than one dnsmasq
     instance binding port 547. That's OK if they serve different networks.
     Need to set REUSEADDR|REUSEPORT to make this possible.
     Handle the case that REUSEPORT is defined, but the kernel doesn't 
     support it. This handles the introduction of REUSEPORT on Linux. */
  if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
    {
      int rc = 0;

#ifdef SO_REUSEPORT
      if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 &&
	  errno == ENOPROTOOPT)
	rc = 0;
#endif
      
      if (rc != -1)
	rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt));
      
      if (rc == -1)
	die(_("failed to set SO_REUSE{ADDR|PORT} on DHCPv6 socket: %s"), NULL, EC_BADNET);
    }
  
  memset(&saddr, 0, sizeof(saddr));
#ifdef HAVE_SOCKADDR_SA_LEN
  saddr.sin6_len = sizeof(struct sockaddr_in6);
#endif
  saddr.sin6_family = AF_INET6;
  saddr.sin6_addr = in6addr_any;
  saddr.sin6_port = htons(DHCPV6_SERVER_PORT);
  
  if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in6)))
    die(_("failed to bind DHCPv6 server socket: %s"), NULL, EC_BADNET);
  
  daemon->dhcp6fd = fd;
}

void dhcp6_packet(time_t now)
{
  struct dhcp_context *context;
  struct dhcp_relay *relay;
  struct iface_param parm;
  struct cmsghdr *cmptr;
  struct msghdr msg;
  uint32_t if_index = 0;
  union {
    struct cmsghdr align; /* this ensures alignment */
    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
  } control_u;
  struct sockaddr_in6 from;
  ssize_t sz; 
  struct ifreq ifr;
  struct iname *tmp;
  unsigned short port;
  struct in6_addr dst_addr;
  struct in6_addr all_servers;
  
  memset(&dst_addr, 0, sizeof(dst_addr));

  msg.msg_control = control_u.control6;
  msg.msg_controllen = sizeof(control_u);
  msg.msg_flags = 0;
  msg.msg_name = &from;
  msg.msg_namelen = sizeof(from);
  msg.msg_iov =  &daemon->dhcp_packet;
  msg.msg_iovlen = 1;
  
  if ((sz = recv_dhcp_packet(daemon->dhcp6fd, &msg)) == -1)
    return;
  
  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
    if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
      {
	union {
	  unsigned char *c;
	  struct in6_pktinfo *p;
	} p;
	p.c = CMSG_DATA(cmptr);
        
	if_index = p.p->ipi6_ifindex;
	dst_addr = p.p->ipi6_addr;
      }

  if (!indextoname(daemon->dhcp6fd, if_index, ifr.ifr_name))
    return;
  
#ifdef HAVE_LINUX_NETWORK
  /* This works around a possible Linux kernel bug when using interfaces
     enslaved to a VRF. The scope_id in the source address gets set
     to the index of the VRF interface, not the slave. Fortunately,
     the interface index returned by packetinfo is correct so we use
     that instead. Log this once, so if it triggers in other circumstances
     we've not anticipated and breaks things, we get some clues. */
  if (from.sin6_scope_id != if_index)
    {
      static int logged = 0;
      
      if (!logged)
	{
	  my_syslog(MS_DHCP | LOG_WARNING,
		    _("Working around kernel bug: faulty source address scope for VRF slave %s"),
		    ifr.ifr_name);
	  logged = 1;
	}
      
      from.sin6_scope_id = if_index;
    }
#endif

#ifdef HAVE_DUMPFILE
  dump_packet_udp(DUMP_DHCPV6, (void *)daemon->dhcp_packet.iov_base, sz,
		  (union mysockaddr *)&from, NULL, daemon->dhcp6fd);
#endif

  if (relay_reply6(&from, sz, ifr.ifr_name))
    {
#ifdef HAVE_DUMPFILE
      dump_packet_udp(DUMP_DHCPV6, (void *)daemon->outpacket.iov_base, save_counter(-1), NULL,
		      (union mysockaddr *)&from, daemon->dhcp6fd);
#endif
      
      while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base, 
			       save_counter(-1), 0, (struct sockaddr *)&from, 
			       sizeof(from))));
    }
  else
    {
      struct dhcp_bridge *bridge, *alias;
      int multicast_dest = 0;
      
      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 && (tmp->flags & INAME_6) &&
	    wildcard_match(tmp->name, ifr.ifr_name))
	  return;
      
      parm.current = NULL;
      parm.ind = if_index;
      parm.addr_match = 0;
      memset(&parm.fallback, 0, IN6ADDRSZ);
      memset(&parm.ll_addr, 0, IN6ADDRSZ);
      memset(&parm.ula_addr, 0, IN6ADDRSZ);
      
      /* If the interface on which the DHCPv6 request was received is
         an alias of some other interface (as specified by the
         --bridge-interface option), change parm.ind so that we look
         for DHCPv6 contexts associated with the aliased interface
         instead of with the aliasing one. */
      for (bridge = daemon->bridges; bridge; bridge = bridge->next)
	{
	  for (alias = bridge->alias; alias; alias = alias->next)
	    if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE))
	      {
		parm.ind = if_nametoindex(bridge->iface);
		if (!parm.ind)
		  {
		    my_syslog(MS_DHCP | LOG_WARNING,
			      _("unknown interface %s in bridge-interface"),
			      bridge->iface);
		    return;
		  }
		break;
	      }
	  if (alias)
	    break;
	}
      
      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);
	  }

      for (relay = daemon->relay6; relay; relay = relay->next)
	relay->matchcount = 0;

      inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &all_servers);
      if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
	multicast_dest = 1;
      
      inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
      if (IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
	multicast_dest = 1;
      else
	{
	  /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
	     we're listening there for DHCPv6 server reasons. */
	  if (relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
	    return;
	}
      
      if (!iface_enumerate(AF_INET6, &parm, (callback_t){.af_inet6=complete_context6}))
	return;
      
      /* Check for a relay again after iface_enumerate/complete_context has had
	 chance to fill in relay->iface_index fields. This handles first time through
	 and any changes in interface config. */
      if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers) &&
	  relay_upstream6(if_index, (size_t)sz, &from.sin6_addr, from.sin6_scope_id, now))
	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;
	}
      
      /* 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, multicast_dest, if_index, ifr.ifr_name, &parm.fallback, 
			 &parm.ll_addr, &parm.ula_addr, sz, &from.sin6_addr, now);
      
      /* 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
	 server port to a relay. */
      if (port != 0)
	{
	  from.sin6_port = htons(port);
	  
#ifdef HAVE_DUMPFILE
	  dump_packet_udp(DUMP_DHCPV6, (void *)daemon->outpacket.iov_base, save_counter(-1),
			  NULL, (union mysockaddr *)&from, daemon->dhcp6fd);
#endif 
	  
	  while (retry_send(sendto(daemon->dhcp6fd, daemon->outpacket.iov_base,
				   save_counter(-1), 0, (struct sockaddr *)&from, sizeof(from))));
	}
      
      /* These need to be called _after_ we send DHCPv6 packet, since lease_update_file()
	 may trigger sending an RA packet, which overwrites our buffer. */
      lease_update_file(now);
      lease_update_dns(0);
    }
}

void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, unsigned int *maclenp, unsigned int *mactypep, time_t now)
{
  /* Receiving 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;
  union mysockaddr addr;
  int i, maclen;

  neigh.type = ND_NEIGHBOR_SOLICIT;
  neigh.code = 0;
  neigh.reserved = 0;
  neigh.target = *client;
  /* RFC4443 section-2.3: checksum has to be zero to be calculated */
  neigh.checksum = 0;
   
  memset(&addr, 0, sizeof(addr));
#ifdef HAVE_SOCKADDR_SA_LEN
  addr.in6.sin6_len = sizeof(struct sockaddr_in6);
#endif
  addr.in6.sin6_family = AF_INET6;
  addr.in6.sin6_port = htons(IPPROTO_ICMPV6);
  addr.in6.sin6_addr = *client;
  addr.in6.sin6_scope_id = iface;
  
  for (i = 0; i < 5; i++)
    {
      struct timespec ts;
      
      if ((maclen = find_mac(&addr, mac, 0, now)) != 0)
	break;
	  
      while(retry_send(sendto(daemon->icmp6fd, &neigh, sizeof(neigh), 0, &addr.sa, sizeof(addr))));
      
      ts.tv_sec = 0;
      ts.tv_nsec = 100000000; /* 100ms */
      nanosleep(&ts, NULL);
    }

  *maclenp = maclen;
  *mactypep = ARPHRD_ETHER;
}
    
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)
{
  struct dhcp_context *context;
  struct shared_network *share;
  struct dhcp_relay *relay;
  struct iface_param *param = vparam;
  struct iname *tmp;
  int match = !daemon->if_addrs;
 
  (void)scope; /* warning */
  
  if (if_index != param->ind)
    return 1;
  
  if (IN6_IS_ADDR_LINKLOCAL(local))
    param->ll_addr = *local;
  else if (IN6_IS_ADDR_ULA(local))
    param->ula_addr = *local;
      
  if (IN6_IS_ADDR_LOOPBACK(local) ||
      IN6_IS_ADDR_LINKLOCAL(local) ||
      IN6_IS_ADDR_MULTICAST(local))
    return 1;
  
  /* 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))
      match = 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 ((context->flags & CONTEXT_DHCP) &&
	!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
	prefix <= context->prefix &&
	context->current == context)
      {
	if (is_same_net6(local, &context->start6, context->prefix) &&
	    is_same_net6(local, &context->end6, context->prefix))
	  {
	    struct dhcp_context *tmp, **up;
	    
	    /* use interface values only for constructed 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;
	  }
	else
	  {
	    for (share = daemon->shared_networks; share; share = share->next)
	      {
		/* IPv4 shared_address - ignore */
		if (share->shared_addr.s_addr != 0)
		  continue;
			
		if (share->if_index != 0)
		  {
		    if (share->if_index != if_index)
		      continue;
		  }
		else
		  {
		    if (!IN6_ARE_ADDR_EQUAL(&share->match_addr6, local))
		      continue;
		  }
		
		if (is_same_net6(&share->shared_addr6, &context->start6, context->prefix) &&
		    is_same_net6(&share->shared_addr6, &context->end6, context->prefix))
		  {
		    context->current = param->current;
		    param->current = context;
		    context->local6 = *local;
		    context->preferred = context->flags & CONTEXT_DEPRECATE ? 0 :0xffffffff;
		    context->valid = 0xffffffff;
		  }
	      }
	  }      
      }
  
  if (match)
    for (relay = daemon->relay6; relay; relay = relay->next)
      if (IN6_ARE_ADDR_EQUAL(local, &relay->local.addr6))
	{
	  relay->iface_index = if_index;

	  /* More than one interface with the relay address breaks things. */
	  if (relay->matchcount++ == 1 && !relay->warned)
	    {
	      relay->warned = 1;
	      inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN);
	      my_syslog(MS_DHCP | LOG_WARNING, _("DHCP relay address %s appears on more than one interface"), daemon->addrbuff);
	    }
	}
  
  return 1;
}

struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, int prefix,  struct in6_addr *addr)
{
  struct dhcp_config *config;
  
  for (config = configs; config; config = config->next)
    if (config->flags & CONFIG_ADDR6)
      {
	struct addrlist *addr_list;
	
	for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
	  if ((!net || is_same_net6(&addr_list->addr.addr6, net, prefix) || ((addr_list->flags & ADDRLIST_WILDCARD) && prefix == 64)) &&
	      is_same_net6(&addr_list->addr.addr6, addr, (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128))
	    return config;
      }
  
  return NULL;
}

struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned char *clid, int clid_len, int temp_addr,
				       unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans)
{
  /* Find a free address: exclude anything in use and anything allocated to
     a particular hwaddr/clientid/hostname in our configuration.
     Try to return from contexts which match netids first. 
     
     Note that we assume the address prefix lengths are 64 or greater, so we can
     get by with 64 bit arithmetic.
*/

  u64 start, addr;
  struct dhcp_context *c, *d;
  int i, pass;
  u64 j; 

  /* hash hwaddr: use the SDBM hashing algorithm.  This works
     for MAC addresses, let's see how it manages with client-ids! 
     For temporary addresses, we generate a new random one each time. */
  if (temp_addr)
    j = rand64();
  else
    for (j = iaid, i = 0; i < clid_len; i++)
      j = clid[i] + (j << 6) + (j << 16) - j;
  
  for (pass = 0; pass <= plain_range ? 1 : 0; pass++)
    for (c = context; c; c = c->current)
      if (c->flags & (CONTEXT_DEPRECATE | CONTEXT_STATIC | CONTEXT_RA_STATELESS | CONTEXT_USED))
	continue;
      else if (!match_netid(c->filter, netids, pass))
	continue;
      else
	{ 
	  if (!temp_addr && option_bool(OPT_CONSEC_ADDR))
	    {
	      /* seed is largest extant lease addr in this context,
		 skip addresses equal to the number of addresses rejected
		 by clients. This should avoid the same client being offered the same
		 address after it has rjected it. */
	      start = lease_find_max_addr6(c) + 1 + serial + c->addr_epoch;
	      if (c->addr_epoch)
		c->addr_epoch--;
	    }
	  else
	    {
	      u64 range = 1 + addr6part(&c->end6) - addr6part(&c->start6);
	      u64 offset = j + c->addr_epoch;

	      /* don't divide by zero if range is whole 2^64 */
	      if (range != 0)
		offset = offset % range;

	      start = addr6part(&c->start6) + offset;
	    }

	  /* iterate until we find a free address. */
	  addr = start;
	  
	  do {
	    /* eliminate addresses in use by the server. */
	    for (d = context; d; d = d->current)
	      if (addr == addr6part(&d->local6))
		break;
	    
	    *ans = c->start6;
	    setaddr6part (ans, addr);

	    if (!d &&
		!lease6_find_by_addr(&c->start6, c->prefix, addr) && 
		!config_find_by_address6(daemon->dhcp_conf, &c->start6, c->prefix, ans))
	      return c;
	    
	    addr++;
	    
	    if (addr  == addr6part(&c->end6) + 1)
	      addr = addr6part(&c->start6);
	    
	  } while (addr != start);
	}
	   
  return NULL;
}

/* can dynamically allocate addr */
struct dhcp_context *address6_available(struct dhcp_context *context, 
					struct in6_addr *taddr,
					struct dhcp_netid *netids,
					int plain_range)
{
  u64 start, end, addr = addr6part(taddr);
  struct dhcp_context *tmp;
 
  for (tmp = context; tmp; tmp = tmp->current)
    {
      start = addr6part(&tmp->start6);
      end = addr6part(&tmp->end6);

      if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_RA_STATELESS)) &&
          is_same_net6(&tmp->start6, taddr, tmp->prefix) &&
	  is_same_net6(&tmp->end6, taddr, tmp->prefix) &&
	  addr >= start &&
          addr <= end &&
          match_netid(tmp->filter, netids, plain_range))
        return tmp;
    }

  return NULL;
}

/* address OK if configured */
struct dhcp_context *address6_valid(struct dhcp_context *context, 
				    struct in6_addr *taddr,
				    struct dhcp_netid *netids,
				    int plain_range)
{
  struct dhcp_context *tmp;
 
  for (tmp = context; tmp; tmp = tmp->current)
    if (is_same_net6(&tmp->start6, taddr, tmp->prefix) &&
	match_netid(tmp->filter, netids, plain_range))
      return tmp;

  return NULL;
}

void make_duid(time_t now)
{
  (void)now;

  if (daemon->duid_config)
    {
      unsigned char *p;
      
      daemon->duid = p = safe_malloc(daemon->duid_config_len + 6);
      daemon->duid_len = daemon->duid_config_len + 6;
      PUTSHORT(2, p); /* DUID_EN */
      PUTLONG(daemon->duid_enterprise, p);
      memcpy(p, daemon->duid_config, daemon->duid_config_len);
    }
  else
    {
      time_t newnow = 0;
      
      /* If we have no persistent lease database, or a non-stable RTC, use DUID_LL (newnow == 0) */
#ifndef HAVE_BROKEN_RTC
      /* rebase epoch to 1/1/2000 */
      if (!option_bool(OPT_LEASE_RO) || daemon->lease_change_command)
	newnow = now - 946684800;
#endif      
      
      iface_enumerate(AF_LOCAL, &newnow, (callback_t){.af_local=make_duid1});
      
      if(!daemon->duid)
	die("Cannot create DHCPv6 server DUID: %s", NULL, EC_MISC);
    }
}

static int make_duid1(int index, unsigned int type, char *mac, size_t maclen, void *parm)
{
  /* create DUID as specified in RFC3315. We use the MAC of the
     first interface we find that isn't loopback or P-to-P and
     has address-type < 256. Address types above 256 are things like 
     tunnels which don't have usable MAC addresses. */
  
  unsigned char *p;
  (void)index;
  (void)parm;
  time_t newnow = *((time_t *)parm);
  
  if (type >= 256)
    return 1;

  if (newnow == 0)
    {
      daemon->duid = p = safe_malloc(maclen + 4);
      daemon->duid_len = maclen + 4;
      PUTSHORT(3, p); /* DUID_LL */
      PUTSHORT(type, p); /* address type */
    }
  else
    {
      daemon->duid = p = safe_malloc(maclen + 8);
      daemon->duid_len = maclen + 8;
      PUTSHORT(1, p); /* DUID_LLT */
      PUTSHORT(type, p); /* address type */
      PUTLONG(*((time_t *)parm), p); /* time */
    }
  
  memcpy(p, mac, maclen);

  return 0;
}

struct cparam {
  time_t now;
  int newone, newname;
};

static int construct_worker(struct in6_addr *local, int prefix, 
			    int scope, int if_index, int flags, 
			    unsigned int preferred, unsigned int valid, void *vparam)
{
  char ifrn_name[IFNAMSIZ];
  struct in6_addr start6, end6;
  struct dhcp_context *template, *context;
  struct iname *tmp;
  
  (void)scope;
  (void)flags;
  (void)valid;
  (void)preferred;

  struct cparam *param = vparam;

  if (IN6_IS_ADDR_LOOPBACK(local) ||
      IN6_IS_ADDR_LINKLOCAL(local) ||
      IN6_IS_ADDR_MULTICAST(local))
    return 1;

  if (!(flags & IFACE_PERMANENT))
    return 1;

  if (flags & IFACE_DEPRECATED)
    return 1;

  /* Ignore interfaces where we're not doing RA/DHCP6 */
  if (!indextoname(daemon->icmp6fd, if_index, ifrn_name) ||
      !iface_check(AF_LOCAL, NULL, ifrn_name, NULL))
    return 1;
  
  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
    if (tmp->name && wildcard_match(tmp->name, ifrn_name))
      return 1;

  for (template = daemon->dhcp6; template; template = template->next)
    if (!(template->flags & (CONTEXT_TEMPLATE | CONTEXT_CONSTRUCTED)))
      {
	/* non-template entries, just fill in interface and local addresses */
	if (prefix <= template->prefix &&
	    is_same_net6(local, &template->start6, template->prefix) &&
	    is_same_net6(local, &template->end6, template->prefix))
	  {
	    /* First time found, do fast RA. */
	    if (template->if_index == 0)
	      {
		ra_start_unsolicited(param->now, template);
		param->newone = 1;
	      }
	    
	    template->if_index = if_index;
	    template->local6 = *local;
	  }
	
      }
    else if (wildcard_match(template->template_interface, ifrn_name) &&
	     template->prefix >= prefix)
      {
	start6 = *local;
	setaddr6part(&start6, addr6part(&template->start6));
	end6 = *local;
	setaddr6part(&end6, addr6part(&template->end6));
	
	for (context = daemon->dhcp6; context; context = context->next)
	  if (!(context->flags & CONTEXT_TEMPLATE) &&
	      IN6_ARE_ADDR_EQUAL(&start6, &context->start6) &&
	      IN6_ARE_ADDR_EQUAL(&end6, &context->end6))
	    {
	      /* If there's an absolute address context covering this address
		 then don't construct one as well. */
	      if (!(context->flags & CONTEXT_CONSTRUCTED))
		break;
	      
	      if (context->if_index == if_index)
		{
		  int cflags = context->flags;
		  context->flags &= ~(CONTEXT_GC | CONTEXT_OLD);
		  if (cflags & CONTEXT_OLD)
		    {
		      /* address went, now it's back, and on the same interface */
		      log_context(AF_INET6, context); 
		      /* fast RAs for a while */
		      ra_start_unsolicited(param->now, context);
		      param->newone = 1; 
		      /* Add address to name again */
		      if (context->flags & CONTEXT_RA_NAME)
			param->newname = 1;
		    
		    }
		  break;
		}
	    }
	
	if (!context && (context = whine_malloc(sizeof (struct dhcp_context))))
	  {
	    *context = *template;
	    context->start6 = start6;
	    context->end6 = end6;
	    context->flags &= ~CONTEXT_TEMPLATE;
	    context->flags |= CONTEXT_CONSTRUCTED;
	    context->if_index = if_index;
	    context->local6 = *local;
	    context->saved_valid = 0;
	    
	    context->next = daemon->dhcp6;
	    daemon->dhcp6 = context;

	    ra_start_unsolicited(param->now, context);
	    /* we created a new one, need to call
	       lease_update_file to get periodic functions called */
	    param->newone = 1; 

	    /* Will need to add new putative SLAAC addresses to existing leases */
	    if (context->flags & CONTEXT_RA_NAME)
	      param->newname = 1;
	    
	    log_context(AF_INET6, context);
	  } 
      }
  
  return 1;
}

void dhcp_construct_contexts(time_t now)
{ 
  struct dhcp_context *context, *tmp, **up;
  struct cparam param;
  param.newone = 0;
  param.newname = 0;
  param.now = now;

  for (context = daemon->dhcp6; context; context = context->next)
    if (context->flags & CONTEXT_CONSTRUCTED)
      context->flags |= CONTEXT_GC;
   
  iface_enumerate(AF_INET6, &param, (callback_t){.af_inet6=construct_worker});

  for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
    {
      
      tmp = context->next; 
     
      if (context->flags & CONTEXT_GC && !(context->flags & CONTEXT_OLD))
	{
	  if ((context->flags & CONTEXT_RA) || option_bool(OPT_RA))
	    {
	      /* previously constructed context has gone; advertise its demise */
	      context->flags |= CONTEXT_OLD;
	      context->address_lost_time = now;
	      /* Apply same ceiling of configured lease time as in radv.c */
	      if (context->saved_valid > context->lease_time)
		context->saved_valid = context->lease_time;
	      /* maximum time is 2 hours, from RFC */
	      if (context->saved_valid > 7200) /* 2 hours */
		context->saved_valid = 7200;
	      ra_start_unsolicited(now, context);
	      param.newone = 1; /* include deletion */ 
	      
	      if (context->flags & CONTEXT_RA_NAME)
		param.newname = 1; 
			      
	      log_context(AF_INET6, context);
	      
	      up = &context->next;
	    }
	  else
	    {
	      /* we were never doing RA for this, so free now */
	      *up = context->next;
	      free(context);
	    }
	}
      else
	 up = &context->next;
    }
  
  if (param.newone)
    {
      if (daemon->dhcp || daemon->doing_dhcp6)
	{
	  if (param.newname)
	    lease_update_slaac(now);
	  lease_update_file(now);
	}
      else 
	/* Not doing DHCP, so no lease system, manage alarms for ra only */
	send_alarm(periodic_ra(now), now);
    }
}

#endif /* HAVE_DHCP6 */
