[T106][ZXW-22]7520V3SCV2.01.01.02P42U09_VEC_V0.8_AP_VEC origin source commit

Change-Id: Ic6e05d89ecd62fc34f82b23dcf306c93764aec4b
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c
new file mode 100755
index 0000000..1e4aad2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/arp.c
@@ -0,0 +1,232 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+/* Time between forced re-loads from kernel. */
+#define INTERVAL 90
+
+#define ARP_MARK  0
+#define ARP_FOUND 1  /* Confirmed */
+#define ARP_NEW   2  /* Newly created */
+#define ARP_EMPTY 3  /* No MAC addr */
+
+struct arp_record {
+  unsigned short hwlen, status;
+  int family;
+  unsigned char hwaddr[DHCP_CHADDR_MAX]; 
+  union all_addr addr;
+  struct arp_record *next;
+};
+
+static struct arp_record *arps = NULL, *old = NULL, *freelist = NULL;
+static time_t last = 0;
+
+static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *parmv)
+{
+  struct arp_record *arp;
+
+  (void)parmv;
+
+  if (maclen > DHCP_CHADDR_MAX)
+    return 1;
+
+  /* Look for existing entry */
+  for (arp = arps; arp; arp = arp->next)
+    {
+      if (family != arp->family || arp->status == ARP_NEW)
+	continue;
+      
+      if (family == AF_INET)
+	{
+	  if (arp->addr.addr4.s_addr != ((struct in_addr *)addrp)->s_addr)
+	    continue;
+	}
+      else
+	{
+	  if (!IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, (struct in6_addr *)addrp))
+	    continue;
+	}
+
+      if (arp->status == ARP_EMPTY)
+	{
+	  /* existing address, was negative. */
+	  arp->status = ARP_NEW;
+	  arp->hwlen = maclen;
+	  memcpy(arp->hwaddr, mac, maclen);
+	}
+      else if (arp->hwlen == maclen && memcmp(arp->hwaddr, mac, maclen) == 0)
+	/* Existing entry matches - confirm. */
+	arp->status = ARP_FOUND;
+      else
+	continue;
+      
+      break;
+    }
+
+  if (!arp)
+    {
+      /* New entry */
+      if (freelist)
+	{
+	  arp = freelist;
+	  freelist = freelist->next;
+	}
+      else if (!(arp = whine_malloc(sizeof(struct arp_record))))
+	return 1;
+      
+      arp->next = arps;
+      arps = arp;
+      arp->status = ARP_NEW;
+      arp->hwlen = maclen;
+      arp->family = family;
+      memcpy(arp->hwaddr, mac, maclen);
+      if (family == AF_INET)
+	arp->addr.addr4.s_addr = ((struct in_addr *)addrp)->s_addr;
+      else
+	memcpy(&arp->addr.addr6, addrp, IN6ADDRSZ);
+    }
+  
+  return 1;
+}
+
+/* If in lazy mode, we cache absence of ARP entries. */
+int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now)
+{
+  struct arp_record *arp, *tmp, **up;
+  int updated = 0;
+
+ again:
+  
+  /* If the database is less then INTERVAL old, look in there */
+  if (difftime(now, last) < INTERVAL)
+    {
+      /* addr == NULL -> just make cache up-to-date */
+      if (!addr)
+	return 0;
+
+      for (arp = arps; arp; arp = arp->next)
+	{
+	  if (addr->sa.sa_family != arp->family)
+	    continue;
+	    
+	  if (arp->family == AF_INET &&
+	      arp->addr.addr4.s_addr != addr->in.sin_addr.s_addr)
+	    continue;
+	    
+	  if (arp->family == AF_INET6 && 
+	      !IN6_ARE_ADDR_EQUAL(&arp->addr.addr6, &addr->in6.sin6_addr))
+	    continue;
+	  
+	  /* Only accept positive entries unless in lazy mode. */
+	  if (arp->status != ARP_EMPTY || lazy || updated)
+	    {
+	      if (mac && arp->hwlen != 0)
+		memcpy(mac, arp->hwaddr, arp->hwlen);
+	      return arp->hwlen;
+	    }
+	}
+    }
+
+  /* Not found, try the kernel */
+  if (!updated)
+     {
+       updated = 1;
+       last = now;
+
+       /* Mark all non-negative entries */
+       for (arp = arps; arp; arp = arp->next)
+	 if (arp->status != ARP_EMPTY)
+	   arp->status = ARP_MARK;
+       
+       iface_enumerate(AF_UNSPEC, NULL, filter_mac);
+       
+       /* Remove all unconfirmed entries to old list. */
+       for (arp = arps, up = &arps; arp; arp = tmp)
+	 {
+	   tmp = arp->next;
+	   
+	   if (arp->status == ARP_MARK)
+	     {
+	       *up = arp->next;
+	       arp->next = old;
+	       old = arp;
+	     }
+	   else
+	     up = &arp->next;
+	 }
+
+       goto again;
+     }
+
+  /* record failure, so we don't consult the kernel each time
+     we're asked for this address */
+  if (freelist)
+    {
+      arp = freelist;
+      freelist = freelist->next;
+    }
+  else
+    arp = whine_malloc(sizeof(struct arp_record));
+  
+  if (arp)
+    {      
+      arp->next = arps;
+      arps = arp;
+      arp->status = ARP_EMPTY;
+      arp->family = addr->sa.sa_family;
+      arp->hwlen = 0;
+
+      if (addr->sa.sa_family == AF_INET)
+	arp->addr.addr4.s_addr = addr->in.sin_addr.s_addr;
+      else
+	memcpy(&arp->addr.addr6, &addr->in6.sin6_addr, IN6ADDRSZ);
+    }
+	  
+   return 0;
+}
+
+int do_arp_script_run(void)
+{
+  struct arp_record *arp;
+  
+  /* Notify any which went, then move to free list */
+  if (old)
+    {
+#ifdef HAVE_SCRIPT
+      if (option_bool(OPT_SCRIPT_ARP))
+	queue_arp(ACTION_ARP_DEL, old->hwaddr, old->hwlen, old->family, &old->addr);
+#endif
+      arp = old;
+      old = arp->next;
+      arp->next = freelist;
+      freelist = arp;
+      return 1;
+    }
+
+  for (arp = arps; arp; arp = arp->next)
+    if (arp->status == ARP_NEW)
+      {
+#ifdef HAVE_SCRIPT
+	if (option_bool(OPT_SCRIPT_ARP))
+	  queue_arp(ACTION_ARP, arp->hwaddr, arp->hwlen, arp->family, &arp->addr);
+#endif
+	arp->status = ARP_FOUND;
+	return 1;
+      }
+
+  return 0;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c b/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c
new file mode 100755
index 0000000..172a4b2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/auth.c
@@ -0,0 +1,906 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_AUTH
+
+static struct addrlist *find_addrlist(struct addrlist *list, int flag, union all_addr *addr_u)
+{
+  do {
+    if (!(list->flags & ADDRLIST_IPV6))
+      {
+	struct in_addr netmask, addr = addr_u->addr4;
+	
+	if (!(flag & F_IPV4))
+	  continue;
+	
+	netmask.s_addr = htonl(~(in_addr_t)0 << (32 - list->prefixlen));
+	
+	if  (is_same_net(addr, list->addr.addr4, netmask))
+	  return list;
+      }
+    else if (is_same_net6(&(addr_u->addr6), &list->addr.addr6, list->prefixlen))
+      return list;
+    
+  } while ((list = list->next));
+  
+  return NULL;
+}
+
+static struct addrlist *find_subnet(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+  if (!zone->subnet)
+    return NULL;
+  
+  return find_addrlist(zone->subnet, flag, addr_u);
+}
+
+static struct addrlist *find_exclude(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+  if (!zone->exclude)
+    return NULL;
+  
+  return find_addrlist(zone->exclude, flag, addr_u);
+}
+
+static int filter_zone(struct auth_zone *zone, int flag, union all_addr *addr_u)
+{
+  if (find_exclude(zone, flag, addr_u))
+    return 0;
+
+  /* No subnets specified, no filter */
+  if (!zone->subnet)
+    return 1;
+  
+  return find_subnet(zone, flag, addr_u) != NULL;
+}
+
+int in_zone(struct auth_zone *zone, char *name, char **cut)
+{
+  size_t namelen = strlen(name);
+  size_t domainlen = strlen(zone->domain);
+
+  if (cut)
+    *cut = NULL;
+  
+  if (namelen >= domainlen && 
+      hostname_isequal(zone->domain, &name[namelen - domainlen]))
+    {
+      
+      if (namelen == domainlen)
+	return 1;
+      
+      if (name[namelen - domainlen - 1] == '.')
+	{
+	  if (cut)
+	    *cut = &name[namelen - domainlen - 1]; 
+	  return 1;
+	}
+    }
+
+  return 0;
+}
+
+
+size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, time_t now, union mysockaddr *peer_addr, 
+		   int local_query, int do_bit, int have_pseudoheader) 
+{
+  char *name = daemon->namebuff;
+  unsigned char *p, *ansp;
+  int qtype, qclass, rc;
+  int nameoffset, axfroffset = 0;
+  int q, anscount = 0, authcount = 0;
+  struct crec *crecp;
+  int  auth = !local_query, trunc = 0, nxdomain = 1, soa = 0, ns = 0, axfr = 0, out_of_zone = 0;
+  struct auth_zone *zone = NULL;
+  struct addrlist *subnet = NULL;
+  char *cut;
+  struct mx_srv_record *rec, *move, **up;
+  struct txt_record *txt;
+  struct interface_name *intr;
+  struct naptr *na;
+  union all_addr addr;
+  struct cname *a, *candidate;
+  unsigned int wclen;
+  
+  if (ntohs(header->qdcount) == 0 || OPCODE(header) != QUERY )
+    return 0;
+
+  /* determine end of question section (we put answers there) */
+  if (!(ansp = skip_questions(header, qlen)))
+    return 0; /* bad packet */
+  
+  /* now process each question, answers go in RRs after the question */
+  p = (unsigned char *)(header+1);
+
+  for (q = ntohs(header->qdcount); q != 0; q--)
+    {
+      unsigned int flag = 0;
+      int found = 0;
+      int cname_wildcard = 0;
+  
+      /* save pointer to name for copying into answers */
+      nameoffset = p - (unsigned char *)header;
+
+      /* now extract name as .-concatenated string into name */
+      if (!extract_name(header, qlen, &p, name, 1, 4))
+	return 0; /* bad packet */
+ 
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+      
+      if (qclass != C_IN)
+	{
+	  auth = 0;
+	  out_of_zone = 1;
+	  continue;
+	}
+
+      if ((qtype == T_PTR || qtype == T_SOA || qtype == T_NS) &&
+	  (flag = in_arpa_name_2_addr(name, &addr)) &&
+	  !local_query)
+	{
+	  for (zone = daemon->auth_zones; zone; zone = zone->next)
+	    if ((subnet = find_subnet(zone, flag, &addr)))
+	      break;
+	  
+	  if (!zone)
+	    {
+	      out_of_zone = 1;
+	      auth = 0;
+	      continue;
+	    }
+	  else if (qtype == T_SOA)
+	    soa = 1, found = 1;
+	  else if (qtype == T_NS)
+	    ns = 1, found = 1;
+	}
+
+      if (qtype == T_PTR && flag)
+	{
+	  intr = NULL;
+
+	  if (flag == F_IPV4)
+	    for (intr = daemon->int_names; intr; intr = intr->next)
+	      {
+		struct addrlist *addrlist;
+		
+		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+		  if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr)
+		    break;
+		
+		if (addrlist)
+		  break;
+		else
+		  while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+		    intr = intr->next;
+	      }
+	  else if (flag == F_IPV6)
+	    for (intr = daemon->int_names; intr; intr = intr->next)
+	      {
+		struct addrlist *addrlist;
+		
+		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+		  if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6))
+		    break;
+		
+		if (addrlist)
+		  break;
+		else
+		  while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+		    intr = intr->next;
+	      }
+	  
+	  if (intr)
+	    {
+	      if (local_query || in_zone(zone, intr->name, NULL))
+		{	
+		  found = 1;
+		  log_query(flag | F_REVERSE | F_CONFIG, intr->name, &addr, NULL);
+		  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					  daemon->auth_ttl, NULL,
+					  T_PTR, C_IN, "d", intr->name))
+		    anscount++;
+		}
+	    }
+	  
+	  if ((crecp = cache_find_by_addr(NULL, &addr, now, flag)))
+	    do { 
+	      strcpy(name, cache_get_name(crecp));
+	      
+	      if (crecp->flags & F_DHCP && !option_bool(OPT_DHCP_FQDN))
+		{
+		  char *p = strchr(name, '.');
+		  if (p)
+		    *p = 0; /* must be bare name */
+		  
+		  /* add  external domain */
+		  if (zone)
+		    {
+		      strcat(name, ".");
+		      strcat(name, zone->domain);
+		    }
+		  log_query(flag | F_DHCP | F_REVERSE, name, &addr, record_source(crecp->uid));
+		  found = 1;
+		  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					  daemon->auth_ttl, NULL,
+					  T_PTR, C_IN, "d", name))
+		    anscount++;
+		}
+	      else if (crecp->flags & (F_DHCP | F_HOSTS) && (local_query || in_zone(zone, name, NULL)))
+		{
+		  log_query(crecp->flags & ~F_FORWARD, name, &addr, record_source(crecp->uid));
+		  found = 1;
+		  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					  daemon->auth_ttl, NULL,
+					  T_PTR, C_IN, "d", name))
+		    anscount++;
+		}
+	      else
+		continue;
+		    
+	    } while ((crecp = cache_find_by_addr(crecp, &addr, now, flag)));
+
+	  if (!found && is_rev_synth(flag, &addr, name) && (local_query || in_zone(zone, name, NULL)))
+	    {
+	      log_query(F_CONFIG | F_REVERSE | flag, name, &addr, NULL); 
+	      found = 1;
+	      
+	      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+				      daemon->auth_ttl, NULL,
+				      T_PTR, C_IN, "d", name))
+		anscount++;
+	    }
+
+	  if (found)
+	    nxdomain = 0;
+	  else
+	    log_query(flag | F_NEG | F_NXDOMAIN | F_REVERSE | (auth ? F_AUTH : 0), NULL, &addr, NULL);
+
+	  continue;
+	}
+      
+    cname_restart:
+      if (found)
+	/* NS and SOA .arpa requests have set found above. */
+	cut = NULL;
+      else
+	{
+	  for (zone = daemon->auth_zones; zone; zone = zone->next)
+	    if (in_zone(zone, name, &cut))
+	      break;
+	  
+	  if (!zone)
+	    {
+	      out_of_zone = 1;
+	      auth = 0;
+	      continue;
+	    }
+	}
+
+      for (rec = daemon->mxnames; rec; rec = rec->next)
+	if (!rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
+	  {
+	    nxdomain = 0;
+	         
+	    if (rc == 2 && qtype == T_MX)
+	      {
+		found = 1;
+		log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>"); 
+		if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+					NULL, T_MX, C_IN, "sd", rec->weight, rec->target))
+		  anscount++;
+	      }
+	  }
+      
+      for (move = NULL, up = &daemon->mxnames, rec = daemon->mxnames; rec; rec = rec->next)
+	if (rec->issrv && (rc = hostname_issubdomain(name, rec->name)))
+	  {
+	    nxdomain = 0;
+	    
+	    if (rc == 2 && qtype == T_SRV)
+	      {
+		found = 1;
+		log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>"); 
+		if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+					NULL, T_SRV, C_IN, "sssd", 
+					rec->priority, rec->weight, rec->srvport, rec->target))
+
+		  anscount++;
+	      } 
+	    
+	    /* unlink first SRV record found */
+	    if (!move)
+	      {
+		move = rec;
+		*up = rec->next;
+	      }
+	    else
+	      up = &rec->next;      
+	  }
+	else
+	  up = &rec->next;
+	  
+      /* put first SRV record back at the end. */
+      if (move)
+	{
+	  *up = move;
+	  move->next = NULL;
+	}
+
+      for (txt = daemon->rr; txt; txt = txt->next)
+	if ((rc = hostname_issubdomain(name, txt->name)))
+	  {
+	    nxdomain = 0;
+	    if (rc == 2 && txt->class == qtype)
+	      {
+		found = 1;
+		log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, txt->class)); 
+		if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+					NULL, txt->class, C_IN, "t", txt->len, txt->txt))
+		  anscount++;
+	      }
+	  }
+      
+      for (txt = daemon->txt; txt; txt = txt->next)
+	if (txt->class == C_IN && (rc = hostname_issubdomain(name, txt->name)))
+	  {
+	    nxdomain = 0;
+	    if (rc == 2 && qtype == T_TXT)
+	      {
+		found = 1;
+		log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>"); 
+		if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl,
+					NULL, T_TXT, C_IN, "t", txt->len, txt->txt))
+		  anscount++;
+	      }
+	  }
+
+       for (na = daemon->naptr; na; na = na->next)
+	 if ((rc = hostname_issubdomain(name, na->name)))
+	   {
+	     nxdomain = 0;
+	     if (rc == 2 && qtype == T_NAPTR)
+	       {
+		 found = 1;
+		 log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>");
+		 if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->auth_ttl, 
+					 NULL, T_NAPTR, C_IN, "sszzzd", 
+					 na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
+			  anscount++;
+	       }
+	   }
+    
+       if (qtype == T_A)
+	 flag = F_IPV4;
+       
+       if (qtype == T_AAAA)
+	 flag = F_IPV6;
+       
+       for (intr = daemon->int_names; intr; intr = intr->next)
+	 if ((rc = hostname_issubdomain(name, intr->name)))
+	   {
+	     struct addrlist *addrlist;
+	     
+	     nxdomain = 0;
+	     
+	     if (rc == 2 && flag)
+	       for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)  
+		 if (((addrlist->flags & ADDRLIST_IPV6)  ? T_AAAA : T_A) == qtype &&
+		     (local_query || filter_zone(zone, flag, &addrlist->addr)))
+		   {
+		     if (addrlist->flags & ADDRLIST_REVONLY)
+		       continue;
+
+		     found = 1;
+		     log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL);
+		     if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					     daemon->auth_ttl, NULL, qtype, C_IN, 
+					     qtype == T_A ? "4" : "6", &addrlist->addr))
+		       anscount++;
+		   }
+	     }
+
+       if (!found && is_name_synthetic(flag, name, &addr) )
+	 {
+	   found = 1;
+	   nxdomain = 0;
+	   
+	   log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL);
+	   if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+				   daemon->auth_ttl, NULL, qtype, C_IN, qtype == T_A ? "4" : "6", &addr))
+	     anscount++;
+	 }
+       
+      if (!cut)
+	{
+	  nxdomain = 0;
+	  
+	  if (qtype == T_SOA)
+	    {
+	      auth = soa = 1; /* inhibits auth section */
+	      found = 1;
+	      log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<SOA>");
+	    }
+      	  else if (qtype == T_AXFR)
+	    {
+	      struct iname *peers;
+	      
+	      if (peer_addr->sa.sa_family == AF_INET)
+		peer_addr->in.sin_port = 0;
+	      else
+		{
+		  peer_addr->in6.sin6_port = 0; 
+		  peer_addr->in6.sin6_scope_id = 0;
+		}
+	      
+	      for (peers = daemon->auth_peers; peers; peers = peers->next)
+		if (sockaddr_isequal(peer_addr, &peers->addr))
+		  break;
+	      
+	      /* Refuse all AXFR unless --auth-sec-servers or auth-peers is set */
+	      if ((!daemon->secondary_forward_server && !daemon->auth_peers) ||
+		  (daemon->auth_peers && !peers)) 
+		{
+		  if (peer_addr->sa.sa_family == AF_INET)
+		    inet_ntop(AF_INET, &peer_addr->in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+		  else
+		    inet_ntop(AF_INET6, &peer_addr->in6.sin6_addr, daemon->addrbuff, ADDRSTRLEN); 
+		  
+		  my_syslog(LOG_WARNING, _("ignoring zone transfer request from %s"), daemon->addrbuff);
+		  return 0;
+		}
+	       	      
+	      auth = 1;
+	      soa = 1; /* inhibits auth section */
+	      ns = 1; /* ensure we include NS records! */
+	      axfr = 1;
+	      found = 1;
+	      axfroffset = nameoffset;
+	      log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<AXFR>");
+	    }
+      	  else if (qtype == T_NS)
+	    {
+	      auth = 1;
+	      ns = 1; /* inhibits auth section */
+	      found = 1;
+	      log_query(F_RRNAME | F_AUTH, zone->domain, NULL, "<NS>"); 
+	    }
+	}
+      
+      if (!option_bool(OPT_DHCP_FQDN) && cut)
+	{	  
+	  *cut = 0; /* remove domain part */
+	  
+	  if (!strchr(name, '.') && (crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
+	    {
+	      if (crecp->flags & F_DHCP)
+		do
+		  { 
+		    nxdomain = 0;
+		    if ((crecp->flags & flag) && 
+			(local_query || filter_zone(zone, flag, &(crecp->addr))))
+		      {
+			*cut = '.'; /* restore domain part */
+			log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
+			*cut  = 0; /* remove domain part */
+			found = 1;
+			if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+						daemon->auth_ttl, NULL, qtype, C_IN, 
+						qtype == T_A ? "4" : "6", &crecp->addr))
+			  anscount++;
+		      }
+		  } while ((crecp = cache_find_by_name(crecp, name, now,  F_IPV4 | F_IPV6)));
+	    }
+       	  
+	  *cut = '.'; /* restore domain part */	    
+	}
+      
+      if ((crecp = cache_find_by_name(NULL, name, now, F_IPV4 | F_IPV6)))
+	{
+	  if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
+	    do
+	      { 
+		 nxdomain = 0;
+		 if ((crecp->flags & flag) && (local_query || filter_zone(zone, flag, &(crecp->addr))))
+		   {
+		     log_query(crecp->flags, name, &crecp->addr, record_source(crecp->uid));
+		     found = 1;
+		     if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					     daemon->auth_ttl, NULL, qtype, C_IN, 
+					     qtype == T_A ? "4" : "6", &crecp->addr))
+		       anscount++;
+		   }
+	      } while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4 | F_IPV6)));
+	}
+      
+      /* Only supply CNAME if no record for any type is known. */
+      if (nxdomain)
+	{
+	  /* Check for possible wildcard match against *.domain 
+	     return length of match, to get longest.
+	     Note that if return length of wildcard section, so
+	     we match b.simon to _both_ *.simon and b.simon
+	     but return a longer (better) match to b.simon.
+	  */  
+	  for (wclen = 0, candidate = NULL, a = daemon->cnames; a; a = a->next)
+	    if (a->alias[0] == '*')
+	      {
+		char *test = name;
+		
+		while ((test = strchr(test+1, '.')))
+		  {
+		    if (hostname_isequal(test, &(a->alias[1])))
+		      {
+			if (strlen(test) > wclen && !cname_wildcard)
+			  {
+			    wclen = strlen(test);
+			    candidate = a;
+			    cname_wildcard = 1;
+			  }
+			break;
+		      }
+		  }
+		
+	      }
+	    else if (hostname_isequal(a->alias, name) && strlen(a->alias) > wclen)
+	      {
+		/* Simple case, no wildcard */
+		wclen = strlen(a->alias);
+		candidate = a;
+	      }
+	  
+	  if (candidate)
+	    {
+	      log_query(F_CONFIG | F_CNAME, name, NULL, NULL);
+	      strcpy(name, candidate->target);
+	      if (!strchr(name, '.'))
+		{
+		  strcat(name, ".");
+		  strcat(name, zone->domain);
+		}
+	      found = 1;
+	      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+				      daemon->auth_ttl, &nameoffset,
+				      T_CNAME, C_IN, "d", name))
+		anscount++;
+	      
+	      goto cname_restart;
+	    }
+	  else if (cache_find_non_terminal(name, now))
+	    nxdomain = 0;
+
+	  log_query(flag | F_NEG | (nxdomain ? F_NXDOMAIN : 0) | F_FORWARD | F_AUTH, name, NULL, NULL);
+	}
+      
+    }
+  
+  /* Add auth section */
+  if (auth && zone)
+    {
+      char *authname;
+      int newoffset, offset = 0;
+
+      if (!subnet)
+	authname = zone->domain;
+      else
+	{
+	  /* handle NS and SOA for PTR records */
+	  
+	  authname = name;
+
+	  if (!(subnet->flags & ADDRLIST_IPV6))
+	    {
+	      in_addr_t a = ntohl(subnet->addr.addr4.s_addr) >> 8;
+	      char *p = name;
+	      
+	      if (subnet->prefixlen >= 24)
+		p += sprintf(p, "%u.", a & 0xff);
+	      a = a >> 8;
+	      if (subnet->prefixlen >= 16 )
+		p += sprintf(p, "%u.", a & 0xff);
+	      a = a >> 8;
+	      p += sprintf(p, "%u.in-addr.arpa", a & 0xff);
+	      
+	    }
+	  else
+	    {
+	      char *p = name;
+	      int i;
+	      
+	      for (i = subnet->prefixlen-1; i >= 0; i -= 4)
+		{ 
+		  int dig = ((unsigned char *)&subnet->addr.addr6)[i>>3];
+		  p += sprintf(p, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
+		}
+	      p += sprintf(p, "ip6.arpa");
+	      
+	    }
+	}
+      
+      /* handle NS and SOA in auth section or for explicit queries */
+       newoffset = ansp - (unsigned char *)header;
+       if (((anscount == 0 && !ns) || soa) &&
+	  add_resource_record(header, limit, &trunc, 0, &ansp, 
+			      daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
+			      authname, daemon->authserver,  daemon->hostmaster,
+			      daemon->soa_sn, daemon->soa_refresh, 
+			      daemon->soa_retry, daemon->soa_expiry, 
+			      daemon->auth_ttl))
+	{
+	  offset = newoffset;
+	  if (soa)
+	    anscount++;
+	  else
+	    authcount++;
+	}
+      
+      if (anscount != 0 || ns)
+	{
+	  struct name_list *secondary;
+	  
+	  /* Only include the machine running dnsmasq if it's acting as an auth server */
+	  if (daemon->authinterface)
+	    {
+	      newoffset = ansp - (unsigned char *)header;
+	      if (add_resource_record(header, limit, &trunc, -offset, &ansp, 
+				      daemon->auth_ttl, NULL, T_NS, C_IN, "d", offset == 0 ? authname : NULL, daemon->authserver))
+		{
+		  if (offset == 0) 
+		    offset = newoffset;
+		  if (ns) 
+		    anscount++;
+		  else
+		    authcount++;
+		}
+	    }
+
+	  if (!subnet)
+	    for (secondary = daemon->secondary_forward_server; secondary; secondary = secondary->next)
+	      if (add_resource_record(header, limit, &trunc, offset, &ansp, 
+				      daemon->auth_ttl, NULL, T_NS, C_IN, "d", secondary->name))
+		{
+		  if (ns) 
+		    anscount++;
+		  else
+		    authcount++;
+		}
+	}
+      
+      if (axfr)
+	{
+	  for (rec = daemon->mxnames; rec; rec = rec->next)
+	    if (in_zone(zone, rec->name, &cut))
+	      {
+		if (cut)
+		   *cut = 0;
+
+		if (rec->issrv)
+		  {
+		    if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+					    NULL, T_SRV, C_IN, "sssd", cut ? rec->name : NULL,
+					    rec->priority, rec->weight, rec->srvport, rec->target))
+		      
+		      anscount++;
+		  }
+		else
+		  {
+		    if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+					    NULL, T_MX, C_IN, "sd", cut ? rec->name : NULL, rec->weight, rec->target))
+		      anscount++;
+		  }
+		
+		/* restore config data */
+		if (cut)
+		  *cut = '.';
+	      }
+	      
+	  for (txt = daemon->rr; txt; txt = txt->next)
+	    if (in_zone(zone, txt->name, &cut))
+	      {
+		if (cut)
+		  *cut = 0;
+		
+		if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+					NULL, txt->class, C_IN, "t",  cut ? txt->name : NULL, txt->len, txt->txt))
+		  anscount++;
+		
+		/* restore config data */
+		if (cut)
+		  *cut = '.';
+	      }
+	  
+	  for (txt = daemon->txt; txt; txt = txt->next)
+	    if (txt->class == C_IN && in_zone(zone, txt->name, &cut))
+	      {
+		if (cut)
+		  *cut = 0;
+		
+		if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl,
+					NULL, T_TXT, C_IN, "t", cut ? txt->name : NULL, txt->len, txt->txt))
+		  anscount++;
+		
+		/* restore config data */
+		if (cut)
+		  *cut = '.';
+	      }
+	  
+	  for (na = daemon->naptr; na; na = na->next)
+	    if (in_zone(zone, na->name, &cut))
+	      {
+		if (cut)
+		  *cut = 0;
+		
+		if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, daemon->auth_ttl, 
+					NULL, T_NAPTR, C_IN, "sszzzd", cut ? na->name : NULL,
+					na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
+		  anscount++;
+		
+		/* restore config data */
+		if (cut)
+		  *cut = '.'; 
+	      }
+	  
+	  for (intr = daemon->int_names; intr; intr = intr->next)
+	    if (in_zone(zone, intr->name, &cut))
+	      {
+		struct addrlist *addrlist;
+		
+		if (cut)
+		  *cut = 0;
+		
+		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) 
+		  if (!(addrlist->flags & ADDRLIST_IPV6) &&
+		      (local_query || filter_zone(zone, F_IPV4, &addrlist->addr)) && 
+		      add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
+					  daemon->auth_ttl, NULL, T_A, C_IN, "4", cut ? intr->name : NULL, &addrlist->addr))
+		    anscount++;
+		
+		for (addrlist = intr->addr; addrlist; addrlist = addrlist->next) 
+		  if ((addrlist->flags & ADDRLIST_IPV6) && 
+		      (local_query || filter_zone(zone, F_IPV6, &addrlist->addr)) &&
+		      add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
+					  daemon->auth_ttl, NULL, T_AAAA, C_IN, "6", cut ? intr->name : NULL, &addrlist->addr))
+		    anscount++;
+		
+		/* restore config data */
+		if (cut)
+		  *cut = '.'; 
+	      }
+             
+	  for (a = daemon->cnames; a; a = a->next)
+	    if (in_zone(zone, a->alias, &cut))
+	      {
+		strcpy(name, a->target);
+		if (!strchr(name, '.'))
+		  {
+		    strcat(name, ".");
+		    strcat(name, zone->domain);
+		  }
+		
+		if (cut)
+		  *cut = 0;
+		
+		if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
+					daemon->auth_ttl, NULL,
+					T_CNAME, C_IN, "d",  cut ? a->alias : NULL, name))
+		  anscount++;
+	      }
+	
+	  cache_enumerate(1);
+	  while ((crecp = cache_enumerate(0)))
+	    {
+	      if ((crecp->flags & (F_IPV4 | F_IPV6)) &&
+		  !(crecp->flags & (F_NEG | F_NXDOMAIN)) &&
+		  (crecp->flags & F_FORWARD))
+		{
+		  if ((crecp->flags & F_DHCP) && !option_bool(OPT_DHCP_FQDN))
+		    {
+		      char *cache_name = cache_get_name(crecp);
+		      if (!strchr(cache_name, '.') && 
+			  (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))) &&
+			  add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
+					      daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, 
+					      (crecp->flags & F_IPV4) ? "4" : "6", cache_name, &crecp->addr))
+			anscount++;
+		    }
+		  
+		  if ((crecp->flags & F_HOSTS) || (((crecp->flags & F_DHCP) && option_bool(OPT_DHCP_FQDN))))
+		    {
+		      strcpy(name, cache_get_name(crecp));
+		      if (in_zone(zone, name, &cut) && 
+			  (local_query || filter_zone(zone, (crecp->flags & (F_IPV6 | F_IPV4)), &(crecp->addr))))
+			{
+			  if (cut)
+			    *cut = 0;
+
+			  if (add_resource_record(header, limit, &trunc, -axfroffset, &ansp, 
+						  daemon->auth_ttl, NULL, (crecp->flags & F_IPV6) ? T_AAAA : T_A, C_IN, 
+						  (crecp->flags & F_IPV4) ? "4" : "6", cut ? name : NULL, &crecp->addr))
+			    anscount++;
+			}
+		    }
+		}
+	    }
+	   
+	  /* repeat SOA as last record */
+	  if (add_resource_record(header, limit, &trunc, axfroffset, &ansp, 
+				  daemon->auth_ttl, NULL, T_SOA, C_IN, "ddlllll",
+				  daemon->authserver,  daemon->hostmaster,
+				  daemon->soa_sn, daemon->soa_refresh, 
+				  daemon->soa_retry, daemon->soa_expiry, 
+				  daemon->auth_ttl))
+	    anscount++;
+	  
+	}
+      
+    }
+  
+  /* done all questions, set up header and return length of result */
+  /* clear authoritative and truncated flags, set QR flag */
+  header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR;
+
+  if (local_query)
+    {
+      /* set RA flag */
+      header->hb4 |= HB4_RA;
+    }
+  else
+    {
+      /* clear RA flag */
+      header->hb4 &= ~HB4_RA;
+    }
+
+  /* data is never DNSSEC signed. */
+  header->hb4 &= ~HB4_AD;
+
+  /* authoritative */
+  if (auth)
+    header->hb3 |= HB3_AA;
+  
+  /* truncation */
+  if (trunc)
+    header->hb3 |= HB3_TC;
+  
+  if ((auth || local_query) && nxdomain)
+    SET_RCODE(header, NXDOMAIN);
+  else
+    SET_RCODE(header, NOERROR); /* no error */
+  
+  header->ancount = htons(anscount);
+  header->nscount = htons(authcount);
+  header->arcount = htons(0);
+
+  if (!local_query && out_of_zone)
+    {
+      SET_RCODE(header, REFUSED); 
+      header->ancount = htons(0);
+      header->nscount = htons(0);
+      addr.log.rcode = REFUSED;
+      addr.log.ede = EDE_NOT_AUTH;
+      log_query(F_UPSTREAM | F_RCODE, "error", &addr, NULL);
+      return resize_packet(header,  ansp - (unsigned char *)header, NULL, 0);
+    }
+  
+  /* Advertise our packet size limit in our reply */
+  if (have_pseudoheader)
+    return add_pseudoheader(header,  ansp - (unsigned char *)header, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+
+  return ansp - (unsigned char *)header;
+}
+  
+#endif  
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c b/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c
new file mode 100755
index 0000000..f7740b5
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/blockdata.c
@@ -0,0 +1,176 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+static struct blockdata *keyblock_free;
+static unsigned int blockdata_count, blockdata_hwm, blockdata_alloced;
+
+static void blockdata_expand(int n)
+{
+  struct blockdata *new = whine_malloc(n * sizeof(struct blockdata));
+  
+  if (new)
+    {
+      int i;
+      
+      new[n-1].next = keyblock_free;
+      keyblock_free = new;
+
+      for (i = 0; i < n - 1; i++)
+	new[i].next = &new[i+1];
+
+      blockdata_alloced += n;
+    }
+}
+
+/* Preallocate some blocks, proportional to cachesize, to reduce heap fragmentation. */
+void blockdata_init(void)
+{
+  keyblock_free = NULL;
+  blockdata_alloced = 0;
+  blockdata_count = 0;
+  blockdata_hwm = 0;
+
+  /* Note that daemon->cachesize is enforced to have non-zero size if OPT_DNSSEC_VALID is set */  
+  if (option_bool(OPT_DNSSEC_VALID))
+    blockdata_expand(daemon->cachesize);
+}
+
+void blockdata_report(void)
+{
+  my_syslog(LOG_INFO, _("pool memory in use %u, max %u, allocated %u"), 
+	    blockdata_count * sizeof(struct blockdata),  
+	    blockdata_hwm * sizeof(struct blockdata),  
+	    blockdata_alloced * sizeof(struct blockdata));
+} 
+
+static struct blockdata *blockdata_alloc_real(int fd, char *data, size_t len)
+{
+  struct blockdata *block, *ret = NULL;
+  struct blockdata **prev = &ret;
+  size_t blen;
+
+  while (len > 0)
+    {
+      if (!keyblock_free)
+	blockdata_expand(50);
+      
+      if (keyblock_free)
+	{
+	  block = keyblock_free;
+	  keyblock_free = block->next;
+	  blockdata_count++; 
+	}
+      else
+	{
+	  /* failed to alloc, free partial chain */
+	  blockdata_free(ret);
+	  return NULL;
+	}
+       
+      if (blockdata_hwm < blockdata_count)
+	blockdata_hwm = blockdata_count; 
+      
+      blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+      if (data)
+	{
+	  memcpy(block->key, data, blen);
+	  data += blen;
+	}
+      else if (!read_write(fd, block->key, blen, 1))
+	{
+	  /* failed read free partial chain */
+	  blockdata_free(ret);
+	  return NULL;
+	}
+      len -= blen;
+      *prev = block;
+      prev = &block->next;
+      block->next = NULL;
+    }
+  
+  return ret;
+}
+
+struct blockdata *blockdata_alloc(char *data, size_t len)
+{
+  return blockdata_alloc_real(0, data, len);
+}
+
+void blockdata_free(struct blockdata *blocks)
+{
+  struct blockdata *tmp;
+  
+  if (blocks)
+    {
+      for (tmp = blocks; tmp->next; tmp = tmp->next)
+	blockdata_count--;
+      tmp->next = keyblock_free;
+      keyblock_free = blocks; 
+      blockdata_count--;
+    }
+}
+
+/* if data == NULL, return pointer to static block of sufficient size */
+void *blockdata_retrieve(struct blockdata *block, size_t len, void *data)
+{
+  size_t blen;
+  struct  blockdata *b;
+  void *new, *d;
+  
+  static unsigned int buff_len = 0;
+  static unsigned char *buff = NULL;
+   
+  if (!data)
+    {
+      if (len > buff_len)
+	{
+	  if (!(new = whine_malloc(len)))
+	    return NULL;
+	  if (buff)
+	    free(buff);
+	  buff = new;
+	}
+      data = buff;
+    }
+  
+  for (d = data, b = block; len > 0 && b;  b = b->next)
+    {
+      blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+      memcpy(d, b->key, blen);
+      d += blen;
+      len -= blen;
+    }
+
+  return data;
+}
+
+
+void blockdata_write(struct blockdata *block, size_t len, int fd)
+{
+  for (; len > 0 && block; block = block->next)
+    {
+      size_t blen = len > KEYBLOCK_LEN ? KEYBLOCK_LEN : len;
+      read_write(fd, block->key, blen, 0);
+      len -= blen;
+    }
+}
+
+struct blockdata *blockdata_read(int fd, size_t len)
+{
+  return blockdata_alloc_real(fd, NULL, len);
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c b/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c
new file mode 100755
index 0000000..15c42fc
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/bpf.c
@@ -0,0 +1,442 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+#if defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK)
+#include <ifaddrs.h>
+
+#include <sys/param.h>
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+#include <sys/sysctl.h>
+#endif
+#include <net/if.h>
+#include <net/route.h>
+#include <net/if_dl.h>
+#include <netinet/if_ether.h>
+#if defined(__FreeBSD__)
+#  include <net/if_var.h> 
+#endif
+#include <netinet/in_var.h>
+#include <netinet6/in6_var.h>
+
+#ifndef SA_SIZE
+#define SA_SIZE(sa)                                             \
+    (  (!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ?      \
+        sizeof(long)            :                               \
+        1 + ( (((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1) ) )
+#endif
+
+#ifdef HAVE_BSD_NETWORK
+static int del_family = 0;
+static union all_addr del_addr;
+#endif
+
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+
+int arp_enumerate(void *parm, int (*callback)())
+{
+  int mib[6];
+  size_t needed;
+  char *next;
+  struct rt_msghdr *rtm;
+  struct sockaddr_inarp *sin2;
+  struct sockaddr_dl *sdl;
+  struct iovec buff;
+  int rc;
+
+  buff.iov_base = NULL;
+  buff.iov_len = 0;
+
+  mib[0] = CTL_NET;
+  mib[1] = PF_ROUTE;
+  mib[2] = 0;
+  mib[3] = AF_INET;
+  mib[4] = NET_RT_FLAGS;
+#ifdef RTF_LLINFO
+  mib[5] = RTF_LLINFO;
+#else
+  mib[5] = 0;
+#endif	
+  if (sysctl(mib, 6, NULL, &needed, NULL, 0) == -1 || needed == 0)
+    return 0;
+
+  while (1) 
+    {
+      if (!expand_buf(&buff, needed))
+	return 0;
+      if ((rc = sysctl(mib, 6, buff.iov_base, &needed, NULL, 0)) == 0 ||
+	  errno != ENOMEM)
+	break;
+      needed += needed / 8;
+    }
+  if (rc == -1)
+    return 0;
+  
+  for (next = buff.iov_base ; next < (char *)buff.iov_base + needed; next += rtm->rtm_msglen)
+    {
+      rtm = (struct rt_msghdr *)next;
+      sin2 = (struct sockaddr_inarp *)(rtm + 1);
+      sdl = (struct sockaddr_dl *)((char *)sin2 + SA_SIZE(sin2));
+      if (!(*callback)(AF_INET, &sin2->sin_addr, LLADDR(sdl), sdl->sdl_alen, parm))
+	return 0;
+    }
+
+  return 1;
+}
+#endif /* defined(HAVE_BSD_NETWORK) && !defined(__APPLE__) */
+
+
+int iface_enumerate(int family, void *parm, int (*callback)())
+{
+  struct ifaddrs *head, *addrs;
+  int errsave, fd = -1, ret = 0;
+
+  if (family == AF_UNSPEC)
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+    return  arp_enumerate(parm, callback);
+#else
+  return 0; /* need code for Solaris and MacOS*/
+#endif
+
+  /* AF_LINK doesn't exist in Linux, so we can't use it in our API */
+  if (family == AF_LOCAL)
+    family = AF_LINK;
+
+  if (getifaddrs(&head) == -1)
+    return 0;
+
+#if defined(HAVE_BSD_NETWORK)
+  if (family == AF_INET6)
+    fd = socket(PF_INET6, SOCK_DGRAM, 0);
+#endif
+  
+  for (addrs = head; addrs; addrs = addrs->ifa_next)
+    {
+      if (addrs->ifa_addr->sa_family == family)
+	{
+	  int iface_index = if_nametoindex(addrs->ifa_name);
+
+	  if (iface_index == 0 || !addrs->ifa_addr || 
+	      (!addrs->ifa_netmask && family != AF_LINK))
+	    continue;
+
+	  if (family == AF_INET)
+	    {
+	      struct in_addr addr, netmask, broadcast;
+	      addr = ((struct sockaddr_in *) addrs->ifa_addr)->sin_addr;
+#ifdef HAVE_BSD_NETWORK
+	      if (del_family == AF_INET && del_addr.addr4.s_addr == addr.s_addr)
+		continue;
+#endif
+	      netmask = ((struct sockaddr_in *) addrs->ifa_netmask)->sin_addr;
+	      if (addrs->ifa_broadaddr)
+		broadcast = ((struct sockaddr_in *) addrs->ifa_broadaddr)->sin_addr; 
+	      else 
+		broadcast.s_addr = 0;	      
+	      if (!((*callback)(addr, iface_index, NULL, netmask, broadcast, parm)))
+		goto err;
+	    }
+	  else if (family == AF_INET6)
+	    {
+	      struct in6_addr *addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
+	      unsigned char *netmask = (unsigned char *) &((struct sockaddr_in6 *) addrs->ifa_netmask)->sin6_addr;
+	      int scope_id = ((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_scope_id;
+	      int i, j, prefix = 0;
+	      u32 valid = 0xffffffff, preferred = 0xffffffff;
+	      int flags = 0;
+#ifdef HAVE_BSD_NETWORK
+	      if (del_family == AF_INET6 && IN6_ARE_ADDR_EQUAL(&del_addr.addr6, addr))
+		continue;
+#endif
+#if defined(HAVE_BSD_NETWORK) && !defined(__APPLE__)
+	      struct in6_ifreq ifr6;
+
+	      memset(&ifr6, 0, sizeof(ifr6));
+	      safe_strncpy(ifr6.ifr_name, addrs->ifa_name, sizeof(ifr6.ifr_name));
+	      
+	      ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
+	      if (fd != -1 && ioctl(fd, SIOCGIFAFLAG_IN6, &ifr6) != -1)
+		{
+		  if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
+		    flags |= IFACE_TENTATIVE;
+		  
+		  if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_DEPRECATED)
+		    flags |= IFACE_DEPRECATED;
+
+#ifdef IN6_IFF_TEMPORARY
+		  if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_TEMPORARY)))
+		    flags |= IFACE_PERMANENT;
+#endif
+
+#ifdef IN6_IFF_PRIVACY
+		  if (!(ifr6.ifr_ifru.ifru_flags6 & (IN6_IFF_AUTOCONF | IN6_IFF_PRIVACY)))
+		    flags |= IFACE_PERMANENT;
+#endif
+		}
+	      
+	      ifr6.ifr_addr = *((struct sockaddr_in6 *) addrs->ifa_addr);
+	      if (fd != -1 && ioctl(fd, SIOCGIFALIFETIME_IN6, &ifr6) != -1)
+		{
+		  valid = ifr6.ifr_ifru.ifru_lifetime.ia6t_vltime;
+		  preferred = ifr6.ifr_ifru.ifru_lifetime.ia6t_pltime;
+		}
+#endif
+	      	      
+	      for (i = 0; i < IN6ADDRSZ; i++, prefix += 8) 
+                if (netmask[i] != 0xff)
+		  break;
+	      
+	      if (i != IN6ADDRSZ && netmask[i]) 
+                for (j = 7; j > 0; j--, prefix++) 
+		  if ((netmask[i] & (1 << j)) == 0)
+		    break;
+	      
+	      /* voodoo to clear interface field in address */
+	      if (!option_bool(OPT_NOWILD) && IN6_IS_ADDR_LINKLOCAL(addr))
+		{
+		  addr->s6_addr[2] = 0;
+		  addr->s6_addr[3] = 0;
+		} 
+	     
+	      if (!((*callback)(addr, prefix, scope_id, iface_index, flags,
+				(int) preferred, (int)valid, parm)))
+		goto err;	      
+	    }
+
+#ifdef HAVE_DHCP6      
+	  else if (family == AF_LINK)
+	    { 
+	      /* Assume ethernet again here */
+	      struct sockaddr_dl *sdl = (struct sockaddr_dl *) addrs->ifa_addr;
+	      if (sdl->sdl_alen != 0 && 
+		  !((*callback)(iface_index, ARPHRD_ETHER, LLADDR(sdl), sdl->sdl_alen, parm)))
+		goto err;
+	    }
+#endif 
+	}
+    }
+  
+  ret = 1;
+
+ err:
+  errsave = errno;
+  freeifaddrs(head); 
+  if (fd != -1)
+    close(fd);
+  errno = errsave;
+
+  return ret;
+}
+#endif /* defined(HAVE_BSD_NETWORK) || defined(HAVE_SOLARIS_NETWORK) */
+
+
+#if defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP)
+#include <net/bpf.h>
+
+void init_bpf(void)
+{
+  int i = 0;
+
+  while (1) 
+    {
+      sprintf(daemon->dhcp_buff, "/dev/bpf%d", i++);
+      if ((daemon->dhcp_raw_fd = open(daemon->dhcp_buff, O_RDWR, 0)) != -1)
+	return;
+
+      if (errno != EBUSY)
+	die(_("cannot create DHCP BPF socket: %s"), NULL, EC_BADNET);
+    }	     
+}
+
+void send_via_bpf(struct dhcp_packet *mess, size_t len,
+		  struct in_addr iface_addr, struct ifreq *ifr)
+{
+   /* Hairy stuff, packet either has to go to the
+      net broadcast or the destination can't reply to ARP yet,
+      but we do know the physical address. 
+      Build the packet by steam, and send directly, bypassing
+      the kernel IP stack */
+  
+  struct ether_header ether; 
+  struct ip ip;
+  struct udphdr {
+    u16 uh_sport;               /* source port */
+    u16 uh_dport;               /* destination port */
+    u16 uh_ulen;                /* udp length */
+    u16 uh_sum;                 /* udp checksum */
+  } udp;
+  
+  u32 i, sum;
+  struct iovec iov[4];
+
+  /* Only know how to do ethernet on *BSD */
+  if (mess->htype != ARPHRD_ETHER || mess->hlen != ETHER_ADDR_LEN)
+    {
+      my_syslog(MS_DHCP | LOG_WARNING, _("DHCP request for unsupported hardware type (%d) received on %s"), 
+		mess->htype, ifr->ifr_name);
+      return;
+    }
+   
+  ifr->ifr_addr.sa_family = AF_LINK;
+  if (ioctl(daemon->dhcpfd, SIOCGIFADDR, ifr) < 0)
+    return;
+  
+  memcpy(ether.ether_shost, LLADDR((struct sockaddr_dl *)&ifr->ifr_addr), ETHER_ADDR_LEN);
+  ether.ether_type = htons(ETHERTYPE_IP);
+  
+  if (ntohs(mess->flags) & 0x8000)
+    {
+      memset(ether.ether_dhost, 255,  ETHER_ADDR_LEN);
+      ip.ip_dst.s_addr = INADDR_BROADCAST;
+    }
+  else
+    {
+      memcpy(ether.ether_dhost, mess->chaddr, ETHER_ADDR_LEN); 
+      ip.ip_dst.s_addr = mess->yiaddr.s_addr;
+    }
+  
+  ip.ip_p = IPPROTO_UDP;
+  ip.ip_src.s_addr = iface_addr.s_addr;
+  ip.ip_len = htons(sizeof(struct ip) + 
+		    sizeof(struct udphdr) +
+		    len) ;
+  ip.ip_hl = sizeof(struct ip) / 4;
+  ip.ip_v = IPVERSION;
+  ip.ip_tos = 0;
+  ip.ip_id = htons(0);
+  ip.ip_off = htons(0x4000); /* don't fragment */
+  ip.ip_ttl = IPDEFTTL;
+  ip.ip_sum = 0;
+  for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
+    sum += ((u16 *)&ip)[i];
+  while (sum>>16)
+    sum = (sum & 0xffff) + (sum >> 16);  
+  ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
+  
+  udp.uh_sport = htons(daemon->dhcp_server_port);
+  udp.uh_dport = htons(daemon->dhcp_client_port);
+  if (len & 1)
+    ((char *)mess)[len] = 0; /* for checksum, in case length is odd. */
+  udp.uh_sum = 0;
+  udp.uh_ulen = sum = htons(sizeof(struct udphdr) + len);
+  sum += htons(IPPROTO_UDP);
+  sum += ip.ip_src.s_addr & 0xffff;
+  sum += (ip.ip_src.s_addr >> 16) & 0xffff;
+  sum += ip.ip_dst.s_addr & 0xffff;
+  sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
+  for (i = 0; i < sizeof(struct udphdr)/2; i++)
+    sum += ((u16 *)&udp)[i];
+  for (i = 0; i < (len + 1) / 2; i++)
+    sum += ((u16 *)mess)[i];
+  while (sum>>16)
+    sum = (sum & 0xffff) + (sum >> 16);
+  udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
+  
+  ioctl(daemon->dhcp_raw_fd, BIOCSETIF, ifr);
+  
+  iov[0].iov_base = &ether;
+  iov[0].iov_len = sizeof(ether);
+  iov[1].iov_base = &ip;
+  iov[1].iov_len = sizeof(ip);
+  iov[2].iov_base = &udp;
+  iov[2].iov_len = sizeof(udp);
+  iov[3].iov_base = mess;
+  iov[3].iov_len = len;
+
+  while (retry_send(writev(daemon->dhcp_raw_fd, iov, 4)));
+}
+
+#endif /* defined(HAVE_BSD_NETWORK) && defined(HAVE_DHCP) */
+ 
+
+#ifdef HAVE_BSD_NETWORK
+
+void route_init(void)
+{
+  /* AF_UNSPEC: all addr families */
+  daemon->routefd = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
+  
+  if (daemon->routefd == -1 || !fix_fd(daemon->routefd))
+    die(_("cannot create PF_ROUTE socket: %s"), NULL, EC_BADNET);
+}
+
+void route_sock(void)
+{
+  struct if_msghdr *msg;
+  int rc = recv(daemon->routefd, daemon->packet, daemon->packet_buff_sz, 0);
+
+  if (rc < 4)
+    return;
+
+  msg = (struct if_msghdr *)daemon->packet;
+  
+  if (rc < msg->ifm_msglen)
+    return;
+
+   if (msg->ifm_version != RTM_VERSION)
+     {
+       static int warned = 0;
+       if (!warned)
+	 {
+	   my_syslog(LOG_WARNING, _("Unknown protocol version from route socket"));
+	   warned = 1;
+	 }
+     }
+   else if (msg->ifm_type == RTM_NEWADDR)
+     {
+       del_family = 0;
+       queue_event(EVENT_NEWADDR);
+     }
+   else if (msg->ifm_type == RTM_DELADDR)
+     {
+       /* There's a race in the kernel, such that if we run iface_enumerate() immediately
+	  we get a DELADDR event, the deleted address still appears. Here we store the deleted address
+	  in a static variable, and omit it from the set returned by iface_enumerate() */
+       int mask = ((struct ifa_msghdr *)msg)->ifam_addrs;
+       int maskvec[] = { RTA_DST, RTA_GATEWAY, RTA_NETMASK, RTA_GENMASK,
+			 RTA_IFP, RTA_IFA, RTA_AUTHOR, RTA_BRD };
+       int of;
+       unsigned int i;
+       
+       for (i = 0,  of = sizeof(struct ifa_msghdr); of < rc && i < sizeof(maskvec)/sizeof(maskvec[0]); i++) 
+	 if (mask & maskvec[i]) 
+	   {
+	     struct sockaddr *sa = (struct sockaddr *)((char *)msg + of);
+	     size_t diff = (sa->sa_len != 0) ? sa->sa_len : sizeof(long);
+	     
+	     if (maskvec[i] == RTA_IFA)
+	       {
+		 del_family = sa->sa_family;
+		 if (del_family == AF_INET)
+		   del_addr.addr4 = ((struct sockaddr_in *)sa)->sin_addr;
+		 else if (del_family == AF_INET6)
+		   del_addr.addr6 = ((struct sockaddr_in6 *)sa)->sin6_addr;
+		 else
+		   del_family = 0;
+	       }
+	     
+	     of += diff;
+	     /* round up as needed */
+	     if (diff & (sizeof(long) - 1)) 
+	       of += sizeof(long) - (diff & (sizeof(long) - 1));
+	   }
+       
+       queue_event(EVENT_NEWADDR);
+     }
+}
+
+#endif /* HAVE_BSD_NETWORK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
new file mode 100755
index 0000000..8add610
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/cache.c
@@ -0,0 +1,2031 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+static struct crec *cache_head = NULL, *cache_tail = NULL, **hash_table = NULL;
+#ifdef HAVE_DHCP
+static struct crec *dhcp_spare = NULL;
+#endif
+static struct crec *new_chain = NULL;
+static int insert_error;
+static union bigname *big_free = NULL;
+static int bignames_left, hash_size;
+
+static void make_non_terminals(struct crec *source);
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+				  time_t now,  unsigned long ttl, unsigned int flags);
+
+/* type->string mapping: this is also used by the name-hash function as a mixing table. */
+static const struct {
+  unsigned int type;
+  const char * const name;
+} typestr[] = {
+  { 1,   "A" },
+  { 2,   "NS" },
+  { 5,   "CNAME" },
+  { 6,   "SOA" },
+  { 10,  "NULL" },
+  { 11,  "WKS" },
+  { 12,  "PTR" },
+  { 13,  "HINFO" },	
+  { 15,  "MX" },
+  { 16,  "TXT" },
+  { 22,  "NSAP" },
+  { 23,  "NSAP_PTR" },
+  { 24,  "SIG" },
+  { 25,  "KEY" },
+  { 28,  "AAAA" },
+  { 29,  "LOC" },
+  { 33,  "SRV" },
+  { 35,  "NAPTR" },
+  { 36,  "KX" },
+  { 37,  "CERT" },
+  { 38,  "A6" },
+  { 39,  "DNAME" },
+  { 41,  "OPT" },
+  { 43,  "DS" },
+  { 46,  "RRSIG" },
+  { 47,  "NSEC" },
+  { 48,  "DNSKEY" },
+  { 50,  "NSEC3" },
+  { 51,  "NSEC3PARAM" },
+  { 52,  "TLSA" },
+  { 53,  "SMIMEA" },
+  { 55,  "HIP" },
+  { 249, "TKEY" },
+  { 250, "TSIG" },
+  { 251, "IXFR" },
+  { 252, "AXFR" },
+  { 253, "MAILB" },
+  { 254, "MAILA" },
+  { 255, "ANY" },
+  { 257, "CAA" }
+};
+
+static void cache_free(struct crec *crecp);
+static void cache_unlink(struct crec *crecp);
+static void cache_link(struct crec *crecp);
+static void rehash(int size);
+static void cache_hash(struct crec *crecp);
+
+void next_uid(struct crec *crecp)
+{
+  static unsigned int uid = 0;
+
+  if (crecp->uid == UID_NONE)
+    {
+      uid++;
+  
+      /* uid == 0 used to indicate CNAME to interface name. */
+      if (uid == UID_NONE)
+	uid++;
+      
+      crecp->uid = uid;
+    }
+}
+
+void cache_init(void)
+{
+  struct crec *crecp;
+  int i;
+ 
+  bignames_left = daemon->cachesize/10;
+  
+  if (daemon->cachesize > 0)
+    {
+      crecp = safe_malloc(daemon->cachesize*sizeof(struct crec));
+      
+      for (i=0; i < daemon->cachesize; i++, crecp++)
+	{
+	  cache_link(crecp);
+	  crecp->flags = 0;
+	  crecp->uid = UID_NONE;
+	}
+    }
+  
+  /* create initial hash table*/
+  rehash(daemon->cachesize);
+}
+
+/* In most cases, we create the hash table once here by calling this with (hash_table == NULL)
+   but if the hosts file(s) are big (some people have 50000 ad-block entries), the table
+   will be much too small, so the hosts reading code calls rehash every 1000 addresses, to
+   expand the table. */
+static void rehash(int size)
+{
+  struct crec **new, **old, *p, *tmp;
+  int i, new_size, old_size;
+
+  /* hash_size is a power of two. */
+  for (new_size = 64; new_size < size/10; new_size = new_size << 1);
+  
+  /* must succeed in getting first instance, failure later is non-fatal */
+  if (!hash_table)
+    new = safe_malloc(new_size * sizeof(struct crec *));
+  else if (new_size <= hash_size || !(new = whine_malloc(new_size * sizeof(struct crec *))))
+    return;
+
+  for(i = 0; i < new_size; i++)
+    new[i] = NULL;
+
+  old = hash_table;
+  old_size = hash_size;
+  hash_table = new;
+  hash_size = new_size;
+  
+  if (old)
+    {
+      for (i = 0; i < old_size; i++)
+	for (p = old[i]; p ; p = tmp)
+	  {
+	    tmp = p->hash_next;
+	    cache_hash(p);
+	  }
+      free(old);
+    }
+}
+  
+static struct crec **hash_bucket(char *name)
+{
+  unsigned int c, val = 017465; /* Barker code - minimum self-correlation in cyclic shift */
+  const unsigned char *mix_tab = (const unsigned char*)typestr; 
+
+  while((c = (unsigned char) *name++))
+    {
+      /* don't use tolower and friends here - they may be messed up by LOCALE */
+      if (c >= 'A' && c <= 'Z')
+	c += 'a' - 'A';
+      val = ((val << 7) | (val >> (32 - 7))) + (mix_tab[(val + c) & 0x3F] ^ c);
+    } 
+  
+  /* hash_size is a power of two */
+  return hash_table + ((val ^ (val >> 16)) & (hash_size - 1));
+}
+
+static void cache_hash(struct crec *crecp)
+{
+  /* maintain an invariant that all entries with F_REVERSE set
+     are at the start of the hash-chain  and all non-reverse
+     immortal entries are at the end of the hash-chain.
+     This allows reverse searches and garbage collection to be optimised */
+
+  struct crec **up = hash_bucket(cache_get_name(crecp));
+
+  if (!(crecp->flags & F_REVERSE))
+    {
+      while (*up && ((*up)->flags & F_REVERSE))
+	up = &((*up)->hash_next); 
+      
+      if (crecp->flags & F_IMMORTAL)
+	while (*up && !((*up)->flags & F_IMMORTAL))
+	  up = &((*up)->hash_next);
+    }
+  crecp->hash_next = *up;
+  *up = crecp;
+}
+
+static void cache_blockdata_free(struct crec *crecp)
+{
+  if (!(crecp->flags & F_NEG))
+    {
+      if (crecp->flags & F_SRV)
+	blockdata_free(crecp->addr.srv.target);
+#ifdef HAVE_DNSSEC
+      else if (crecp->flags & F_DNSKEY)
+	blockdata_free(crecp->addr.key.keydata);
+      else if (crecp->flags & F_DS)
+	blockdata_free(crecp->addr.ds.keydata);
+#endif
+    }
+}
+
+static void cache_free(struct crec *crecp)
+{
+  crecp->flags &= ~F_FORWARD;
+  crecp->flags &= ~F_REVERSE;
+  crecp->uid = UID_NONE; /* invalidate CNAMES pointing to this. */
+
+  if (cache_tail)
+    cache_tail->next = crecp;
+  else
+    cache_head = crecp;
+  crecp->prev = cache_tail;
+  crecp->next = NULL;
+  cache_tail = crecp;
+  
+  /* retrieve big name for further use. */
+  if (crecp->flags & F_BIGNAME)
+    {
+      crecp->name.bname->next = big_free;
+      big_free = crecp->name.bname;
+      crecp->flags &= ~F_BIGNAME;
+    }
+
+  cache_blockdata_free(crecp);
+}    
+
+/* insert a new cache entry at the head of the list (youngest entry) */
+static void cache_link(struct crec *crecp)
+{
+  if (cache_head) /* check needed for init code */
+    cache_head->prev = crecp;
+  crecp->next = cache_head;
+  crecp->prev = NULL;
+  cache_head = crecp;
+  if (!cache_tail)
+    cache_tail = crecp;
+}
+
+/* remove an arbitrary cache entry for promotion */ 
+static void cache_unlink (struct crec *crecp)
+{
+  if (crecp->prev)
+    crecp->prev->next = crecp->next;
+  else
+    cache_head = crecp->next;
+
+  if (crecp->next)
+    crecp->next->prev = crecp->prev;
+  else
+    cache_tail = crecp->prev;
+}
+
+char *cache_get_name(struct crec *crecp)
+{
+  if (crecp->flags & F_BIGNAME)
+    return crecp->name.bname->name;
+  else if (crecp->flags & F_NAMEP) 
+    return crecp->name.namep;
+  
+  return crecp->name.sname;
+}
+
+char *cache_get_cname_target(struct crec *crecp)
+{
+  if (crecp->addr.cname.is_name_ptr)
+     return crecp->addr.cname.target.name;
+  else
+    return cache_get_name(crecp->addr.cname.target.cache);
+}
+
+
+
+struct crec *cache_enumerate(int init)
+{
+  static int bucket;
+  static struct crec *cache;
+
+  if (init)
+    {
+      bucket = 0;
+      cache = NULL;
+    }
+  else if (cache && cache->hash_next)
+    cache = cache->hash_next;
+  else
+    {
+       cache = NULL; 
+       while (bucket < hash_size)
+	 if ((cache = hash_table[bucket++]))
+	   break;
+    }
+  
+  return cache;
+}
+
+static int is_outdated_cname_pointer(struct crec *crecp)
+{
+  if (!(crecp->flags & F_CNAME) || crecp->addr.cname.is_name_ptr)
+    return 0;
+  
+  /* NB. record may be reused as DS or DNSKEY, where uid is 
+     overloaded for something completely different */
+  if (crecp->addr.cname.target.cache && 
+      !(crecp->addr.cname.target.cache->flags & (F_DNSKEY | F_DS)) &&
+      crecp->addr.cname.uid == crecp->addr.cname.target.cache->uid)
+    return 0;
+  
+  return 1;
+}
+
+static int is_expired(time_t now, struct crec *crecp)
+{
+  if (crecp->flags & F_IMMORTAL)
+    return 0;
+
+  if (difftime(now, crecp->ttd) < 0)
+    return 0;
+  
+  return 1;
+}
+
+static struct crec *cache_scan_free(char *name, union all_addr *addr, unsigned short class, time_t now,
+				    unsigned int flags, struct crec **target_crec, unsigned int *target_uid)
+{
+  /* Scan and remove old entries.
+     If (flags & F_FORWARD) then remove any forward entries for name and any expired
+     entries but only in the same hash bucket as name.
+     If (flags & F_REVERSE) then remove any reverse entries for addr and any expired
+     entries in the whole cache.
+     If (flags == 0) remove any expired entries in the whole cache. 
+
+     In the flags & F_FORWARD case, the return code is valid, and returns a non-NULL pointer
+     to a cache entry if the name exists in the cache as a HOSTS or DHCP entry (these are never deleted)
+
+     We take advantage of the fact that hash chains have stuff in the order <reverse>,<other>,<immortal>
+     so that when we hit an entry which isn't reverse and is immortal, we're done. 
+
+     If we free a crec which is a CNAME target, return the entry and uid in target_crec and target_uid.
+     This entry will get re-used with the same name, to preserve CNAMEs. */
+ 
+  struct crec *crecp, **up;
+
+  (void)class;
+  
+  if (flags & F_FORWARD)
+    {
+      for (up = hash_bucket(name), crecp = *up; crecp; crecp = crecp->hash_next)
+	{
+	  if ((crecp->flags & F_FORWARD) && hostname_isequal(cache_get_name(crecp), name))
+	    {
+	      /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+	      if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_SRV)) || 
+		  (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))))
+		{
+		  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+		    return crecp;
+		  *up = crecp->hash_next;
+		  /* If this record is for the name we're inserting and is the target
+		     of a CNAME record. Make the new record for the same name, in the same
+		     crec, with the same uid to avoid breaking the existing CNAME. */
+		  if (crecp->uid != UID_NONE)
+		    {
+		      if (target_crec)
+			*target_crec = crecp;
+		      if (target_uid)
+			*target_uid = crecp->uid;
+		    }
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		  continue;
+		}
+	      
+#ifdef HAVE_DNSSEC
+	      /* Deletion has to be class-sensitive for DS and DNSKEY */
+	      if ((flags & crecp->flags & (F_DNSKEY | F_DS)) && crecp->uid == class)
+		{
+		  if (crecp->flags & F_CONFIG)
+		    return crecp;
+		  *up = crecp->hash_next;
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		  continue;
+		}
+#endif
+	    }
+
+	  if (is_expired(now, crecp) || is_outdated_cname_pointer(crecp))
+	    { 
+	      *up = crecp->hash_next;
+	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		{
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		}
+	      continue;
+	    } 
+	  
+	  up = &crecp->hash_next;
+	}
+    }
+  else
+    {
+      int i;
+      int addrlen = (flags & F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+
+      for (i = 0; i < hash_size; i++)
+	for (crecp = hash_table[i], up = &hash_table[i]; 
+	     crecp && ((crecp->flags & F_REVERSE) || !(crecp->flags & F_IMMORTAL));
+	     crecp = crecp->hash_next)
+	  if (is_expired(now, crecp))
+	    {
+	      *up = crecp->hash_next;
+	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		{ 
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		}
+	    }
+	  else if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) &&
+		   (flags & crecp->flags & F_REVERSE) && 
+		   (flags & crecp->flags & (F_IPV4 | F_IPV6)) &&
+		   memcmp(&crecp->addr, addr, addrlen) == 0)
+	    {
+	      *up = crecp->hash_next;
+	      cache_unlink(crecp);
+	      cache_free(crecp);
+	    }
+	  else
+	    up = &crecp->hash_next;
+    }
+  
+  return NULL;
+}
+
+/* Note: The normal calling sequence is
+   cache_start_insert
+   cache_insert * n
+   cache_end_insert
+
+   but an abort can cause the cache_end_insert to be missed 
+   in which can the next cache_start_insert cleans things up. */
+
+void cache_start_insert(void)
+{
+  /* Free any entries which didn't get committed during the last
+     insert due to error.
+  */
+  while (new_chain)
+    {
+      struct crec *tmp = new_chain->next;
+      cache_free(new_chain);
+      new_chain = tmp;
+    }
+  new_chain = NULL;
+  insert_error = 0;
+}
+
+struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
+			  time_t now,  unsigned long ttl, unsigned int flags)
+{
+#ifdef HAVE_DNSSEC
+  if (flags & (F_DNSKEY | F_DS)) 
+    {
+      /* The DNSSEC validation process works by getting needed records into the
+	 cache, then retrying the validation until they are all in place.
+	 This can be messed up by very short TTLs, and _really_ messed up by
+	 zero TTLs, so we force the TTL to be at least long enough to do a validation.
+	 Ideally, we should use some kind of reference counting so that records are
+	 locked until the validation that asked for them is complete, but this
+	 is much easier, and just as effective. */
+      if (ttl < DNSSEC_MIN_TTL)
+	ttl = DNSSEC_MIN_TTL;
+    }
+  else
+#endif
+    {
+      if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
+	ttl = daemon->max_cache_ttl;
+      if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
+	ttl = daemon->min_cache_ttl;
+    }	
+  
+  return really_insert(name, addr, class, now, ttl, flags);
+}
+
+
+static struct crec *really_insert(char *name, union all_addr *addr, unsigned short class,
+				  time_t now,  unsigned long ttl, unsigned int flags)
+{
+  struct crec *new, *target_crec = NULL;
+  union bigname *big_name = NULL;
+  int freed_all = flags & F_REVERSE;
+  int free_avail = 0;
+  unsigned int target_uid;
+  
+  /* if previous insertion failed give up now. */
+  if (insert_error)
+    return NULL;
+
+  /* we don't cache zero-TTL records. */
+  if (ttl == 0)
+    {
+      insert_error = 1;
+      return NULL;
+    }
+  
+  /* First remove any expired entries and entries for the name/address we
+     are currently inserting. */
+  if ((new = cache_scan_free(name, addr, class, now, flags, &target_crec, &target_uid)))
+    {
+      /* We're trying to insert a record over one from 
+	 /etc/hosts or DHCP, or other config. If the 
+	 existing record is for an A or AAAA or CNAME and
+	 the record we're trying to insert is the same, 
+	 just drop the insert, but don't error the whole process. */
+      if ((flags & (F_IPV4 | F_IPV6)) && (flags & F_FORWARD) && addr)
+	{
+	  if ((flags & F_IPV4) && (new->flags & F_IPV4) &&
+	      new->addr.addr4.s_addr == addr->addr4.s_addr)
+	    return new;
+	  else if ((flags & F_IPV6) && (new->flags & F_IPV6) &&
+		   IN6_ARE_ADDR_EQUAL(&new->addr.addr6, &addr->addr6))
+	    return new;
+	}
+
+      insert_error = 1;
+      return NULL;
+    }
+  
+  /* Now get a cache entry from the end of the LRU list */
+  if (!target_crec)
+    while (1) {
+      if (!(new = cache_tail)) /* no entries left - cache is too small, bail */
+	{
+	  insert_error = 1;
+	  return NULL;
+	}
+      
+      /* Free entry at end of LRU list, use it. */
+      if (!(new->flags & (F_FORWARD | F_REVERSE)))
+	break;
+
+      /* End of LRU list is still in use: if we didn't scan all the hash
+	 chains for expired entries do that now. If we already tried that
+	 then it's time to start spilling things. */
+      
+      /* If free_avail set, we believe that an entry has been freed.
+	 Bugs have been known to make this not true, resulting in
+	 a tight loop here. If that happens, abandon the
+	 insert. Once in this state, all inserts will probably fail. */
+      if (free_avail)
+	{
+	  static int warned = 0;
+	  if (!warned)
+	    {
+	      my_syslog(LOG_ERR, _("Internal error in cache."));
+	      warned = 1;
+	    }
+	  insert_error = 1;
+	  return NULL;
+	}
+      
+      if (freed_all)
+	{
+	  /* For DNSSEC records, uid holds class. */
+	  free_avail = 1; /* Must be free space now. */
+	  cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL);
+	  daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++;
+	}
+      else
+	{
+	  cache_scan_free(NULL, NULL, class, now, 0, NULL, NULL);
+	  freed_all = 1;
+	}
+    }
+      
+  /* Check if we need to and can allocate extra memory for a long name.
+     If that fails, give up now, always succeed for DNSSEC records. */
+  if (name && (strlen(name) > SMALLDNAME-1))
+    {
+      if (big_free)
+	{ 
+	  big_name = big_free;
+	  big_free = big_free->next;
+	}
+      else if ((bignames_left == 0 && !(flags & (F_DS | F_DNSKEY))) ||
+	       !(big_name = (union bigname *)whine_malloc(sizeof(union bigname))))
+	{
+	  insert_error = 1;
+	  return NULL;
+	}
+      else if (bignames_left != 0)
+	bignames_left--;
+      
+    }
+
+  /* If we freed a cache entry for our name which was a CNAME target, use that.
+     and preserve the uid, so that existing CNAMES are not broken. */
+  if (target_crec)
+    {
+      new = target_crec;
+      new->uid = target_uid;
+    }
+  
+  /* Got the rest: finally grab entry. */
+  cache_unlink(new);
+  
+  new->flags = flags;
+  if (big_name)
+    {
+      new->name.bname = big_name;
+      new->flags |= F_BIGNAME;
+    }
+
+  if (name)
+    strcpy(cache_get_name(new), name);
+  else
+    *cache_get_name(new) = 0;
+
+#ifdef HAVE_DNSSEC
+  if (flags & (F_DS | F_DNSKEY))
+    new->uid = class;
+#endif
+
+  if (addr)
+    new->addr = *addr;	
+
+  new->ttd = now + (time_t)ttl;
+  new->next = new_chain;
+  new_chain = new;
+
+  return new;
+}
+
+/* after end of insertion, commit the new entries */
+void cache_end_insert(void)
+{
+  if (insert_error)
+    return;
+  
+  while (new_chain)
+    { 
+      struct crec *tmp = new_chain->next;
+      /* drop CNAMEs which didn't find a target. */
+      if (is_outdated_cname_pointer(new_chain))
+	cache_free(new_chain);
+      else
+	{
+	  cache_hash(new_chain);
+	  cache_link(new_chain);
+	  daemon->metrics[METRIC_DNS_CACHE_INSERTED]++;
+
+	  /* If we're a child process, send this cache entry up the pipe to the master.
+	     The marshalling process is rather nasty. */
+	  if (daemon->pipe_to_parent != -1)
+	    {
+	      char *name = cache_get_name(new_chain);
+	      ssize_t m = strlen(name);
+	      unsigned int flags = new_chain->flags;
+#ifdef HAVE_DNSSEC
+	      u16 class = new_chain->uid;
+#endif
+	      
+	      read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+	      read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
+	      read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
+	      read_write(daemon->pipe_to_parent, (unsigned  char *)&flags, sizeof(flags), 0);
+
+	      if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+		read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+	      if (flags & F_SRV)
+		{
+		  /* A negative SRV entry is possible and has no data, obviously. */
+		  if (!(flags & F_NEG))
+		    blockdata_write(new_chain->addr.srv.target, new_chain->addr.srv.targetlen, daemon->pipe_to_parent);
+		}
+#ifdef HAVE_DNSSEC
+	      if (flags & F_DNSKEY)
+		{
+		  read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+		  blockdata_write(new_chain->addr.key.keydata, new_chain->addr.key.keylen, daemon->pipe_to_parent);
+		}
+	      else if (flags & F_DS)
+		{
+		  read_write(daemon->pipe_to_parent, (unsigned char *)&class, sizeof(class), 0);
+		  /* A negative DS entry is possible and has no data, obviously. */
+		  if (!(flags & F_NEG))
+		    blockdata_write(new_chain->addr.ds.keydata, new_chain->addr.ds.keylen, daemon->pipe_to_parent);
+		}
+#endif
+	    }
+	}
+      
+      new_chain = tmp;
+    }
+
+  /* signal end of cache insert in master process */
+  if (daemon->pipe_to_parent != -1)
+    {
+      ssize_t m = -1;
+      read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+    }
+      
+  new_chain = NULL;
+}
+
+
+/* A marshalled cache entry arrives on fd, read, unmarshall and insert into cache of master process. */
+int cache_recv_insert(time_t now, int fd)
+{
+  ssize_t m;
+  union all_addr addr;
+  unsigned long ttl;
+  time_t ttd;
+  unsigned int flags;
+  struct crec *crecp = NULL;
+  
+  cache_start_insert();
+  
+  while(1)
+    {
+ 
+      if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+	return 0;
+      
+      if (m == -1)
+	{
+	  cache_end_insert();
+	  return 1;
+	}
+
+      if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
+	  !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
+	  !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1))
+	return 0;
+
+      daemon->namebuff[m] = 0;
+
+      ttl = difftime(ttd, now);
+      
+      if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_SRV))
+	{
+	  unsigned short class = C_IN;
+
+	  if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+	    return 0;
+
+	  if ((flags & F_SRV) && !(flags & F_NEG) && !(addr.srv.target = blockdata_read(fd, addr.srv.targetlen)))
+	    return 0;
+	
+#ifdef HAVE_DNSSEC
+	   if (flags & F_DNSKEY)
+	     {
+	       if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+		   !(addr.key.keydata = blockdata_read(fd, addr.key.keylen)))
+		 return 0;
+	     }
+	   else  if (flags & F_DS)
+	     {
+	        if (!read_write(fd, (unsigned char *)&class, sizeof(class), 1) ||
+		    (!(flags & F_NEG) && !(addr.key.keydata = blockdata_read(fd, addr.key.keylen))))
+		  return 0;
+	     }
+#endif
+	       
+	  crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
+	}
+      else if (flags & F_CNAME)
+	{
+	  struct crec *newc = really_insert(daemon->namebuff, NULL, C_IN, now, ttl, flags);
+	  /* This relies on the fact that the target of a CNAME immediately precedes
+	     it because of the order of extraction in extract_addresses, and
+	     the order reversal on the new_chain. */
+	  if (newc)
+	    {
+	       newc->addr.cname.is_name_ptr = 0;
+	       
+	       if (!crecp)
+		 newc->addr.cname.target.cache = NULL;
+	       else
+		{
+		  next_uid(crecp);
+		  newc->addr.cname.target.cache = crecp;
+		  newc->addr.cname.uid = crecp->uid;
+		}
+	    }
+	}
+    }
+}
+	
+int cache_find_non_terminal(char *name, time_t now)
+{
+  struct crec *crecp;
+
+  for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+    if (!is_outdated_cname_pointer(crecp) &&
+	!is_expired(now, crecp) &&
+	(crecp->flags & F_FORWARD) &&
+	!(crecp->flags & F_NXDOMAIN) && 
+	hostname_isequal(name, cache_get_name(crecp)))
+      return 1;
+
+  return 0;
+}
+
+struct crec *cache_find_by_name(struct crec *crecp, char *name, time_t now, unsigned int prot)
+{
+  struct crec *ans;
+  int no_rr = prot & F_NO_RR;
+
+  prot &= ~F_NO_RR;
+  
+  if (crecp) /* iterating */
+    ans = crecp->next;
+  else
+    {
+      /* first search, look for relevant entries and push to top of list
+	 also free anything which has expired */
+      struct crec *next, **up, **insert = NULL, **chainp = &ans;
+      unsigned int ins_flags = 0;
+      
+      for (up = hash_bucket(name), crecp = *up; crecp; crecp = next)
+	{
+	  next = crecp->hash_next;
+	  
+	  if (!is_expired(now, crecp) && !is_outdated_cname_pointer(crecp))
+	    {
+	      if ((crecp->flags & F_FORWARD) && 
+		  (crecp->flags & prot) &&
+		  hostname_isequal(cache_get_name(crecp), name))
+		{
+		  if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+		    {
+		      *chainp = crecp;
+		      chainp = &crecp->next;
+		    }
+		  else
+		    {
+		      cache_unlink(crecp);
+		      cache_link(crecp);
+		    }
+	      	      
+		  /* Move all but the first entry up the hash chain
+		     this implements round-robin. 
+		     Make sure that re-ordering doesn't break the hash-chain
+		     order invariants. 
+		  */
+		  if (insert && (crecp->flags & (F_REVERSE | F_IMMORTAL)) == ins_flags)
+		    {
+		      *up = crecp->hash_next;
+		      crecp->hash_next = *insert;
+		      *insert = crecp;
+		      insert = &crecp->hash_next;
+		    }
+		  else
+		    {
+		      if (!insert && !no_rr)
+			{
+			  insert = up;
+			  ins_flags = crecp->flags & (F_REVERSE | F_IMMORTAL);
+			}
+		      up = &crecp->hash_next; 
+		    }
+		}
+	      else
+		/* case : not expired, incorrect entry. */
+		up = &crecp->hash_next; 
+	    }
+	  else
+	    {
+	      /* expired entry, free it */
+	      *up = crecp->hash_next;
+	      if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		{ 
+		  cache_unlink(crecp);
+		  cache_free(crecp);
+		}
+	    }
+	}
+	  
+      *chainp = cache_head;
+    }
+
+  if (ans && 
+      (ans->flags & F_FORWARD) &&
+      (ans->flags & prot) &&     
+      hostname_isequal(cache_get_name(ans), name))
+    return ans;
+  
+  return NULL;
+}
+
+struct crec *cache_find_by_addr(struct crec *crecp, union all_addr *addr, 
+				time_t now, unsigned int prot)
+{
+  struct crec *ans;
+  int addrlen = (prot == F_IPV6) ? IN6ADDRSZ : INADDRSZ;
+  
+  if (crecp) /* iterating */
+    ans = crecp->next;
+  else
+    {  
+      /* first search, look for relevant entries and push to top of list
+	 also free anything which has expired. All the reverse entries are at the
+	 start of the hash chain, so we can give up when we find the first 
+	 non-REVERSE one.  */
+       int i;
+       struct crec **up, **chainp = &ans;
+       
+       for (i=0; i<hash_size; i++)
+	 for (crecp = hash_table[i], up = &hash_table[i]; 
+	      crecp && (crecp->flags & F_REVERSE);
+	      crecp = crecp->hash_next)
+	   if (!is_expired(now, crecp))
+	     {      
+	       if ((crecp->flags & prot) &&
+		   memcmp(&crecp->addr, addr, addrlen) == 0)
+		 {	    
+		   if (crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG))
+		     {
+		       *chainp = crecp;
+		       chainp = &crecp->next;
+		     }
+		   else
+		     {
+		       cache_unlink(crecp);
+		       cache_link(crecp);
+		     }
+		 }
+	       up = &crecp->hash_next;
+	     }
+	   else
+	     {
+	       *up = crecp->hash_next;
+	       if (!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+		 {
+		   cache_unlink(crecp);
+		   cache_free(crecp);
+		 }
+	     }
+       
+       *chainp = cache_head;
+    }
+  
+  if (ans && 
+      (ans->flags & F_REVERSE) &&
+      (ans->flags & prot) &&
+      memcmp(&ans->addr, addr, addrlen) == 0)
+    return ans;
+  
+  return NULL;
+}
+
+static void add_hosts_entry(struct crec *cache, union all_addr *addr, int addrlen, 
+			    unsigned int index, struct crec **rhash, int hashsz)
+{
+  struct crec *lookup = cache_find_by_name(NULL, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6));
+  int i;
+  unsigned int j; 
+
+  /* Remove duplicates in hosts files. */
+  if (lookup && (lookup->flags & F_HOSTS) && memcmp(&lookup->addr, addr, addrlen) == 0)
+    {
+      free(cache);
+      return;
+    }
+    
+  /* Ensure there is only one address -> name mapping (first one trumps) 
+     We do this by steam here, The entries are kept in hash chains, linked
+     by ->next (which is unused at this point) held in hash buckets in
+     the array rhash, hashed on address. Note that rhash and the values
+     in ->next are only valid  whilst reading hosts files: the buckets are
+     then freed, and the ->next pointer used for other things. 
+
+     Only insert each unique address once into this hashing structure.
+
+     This complexity avoids O(n^2) divergent CPU use whilst reading
+     large (10000 entry) hosts files. 
+
+     Note that we only do this process when bulk-reading hosts files, 
+     for incremental reads, rhash is NULL, and we use cache lookups
+     instead.
+  */
+  
+  if (rhash)
+    {
+      /* hash address */
+      for (j = 0, i = 0; i < addrlen; i++)
+	j = (j*2 +((unsigned char *)addr)[i]) % hashsz;
+      
+      for (lookup = rhash[j]; lookup; lookup = lookup->next)
+	if ((lookup->flags & cache->flags & (F_IPV4 | F_IPV6)) &&
+	    memcmp(&lookup->addr, addr, addrlen) == 0)
+	  {
+	    cache->flags &= ~F_REVERSE;
+	    break;
+	  }
+      
+      /* maintain address hash chain, insert new unique address */
+      if (!lookup)
+	{
+	  cache->next = rhash[j];
+	  rhash[j] = cache;
+	}
+    }
+  else
+    {
+      /* incremental read, lookup in cache */
+      lookup = cache_find_by_addr(NULL, addr, 0, cache->flags & (F_IPV4 | F_IPV6));
+      if (lookup && lookup->flags & F_HOSTS)
+	cache->flags &= ~F_REVERSE;
+    }
+
+  cache->uid = index;
+  memcpy(&cache->addr, addr, addrlen);  
+  cache_hash(cache);
+  make_non_terminals(cache);
+}
+
+static int eatspace(FILE *f)
+{
+  int c, nl = 0;
+
+  while (1)
+    {
+      if ((c = getc(f)) == '#')
+	while (c != '\n' && c != EOF)
+	  c = getc(f);
+      
+      if (c == EOF)
+	return 1;
+
+      if (!isspace(c))
+	{
+	  ungetc(c, f);
+	  return nl;
+	}
+
+      if (c == '\n')
+	nl++;
+    }
+}
+	 
+static int gettok(FILE *f, char *token)
+{
+  int c, count = 0;
+ 
+  while (1)
+    {
+      if ((c = getc(f)) == EOF)
+	return (count == 0) ? -1 : 1;
+
+      if (isspace(c) || c == '#')
+	{
+	  ungetc(c, f);
+	  return eatspace(f);
+	}
+      
+      if (count < (MAXDNAME - 1))
+	{
+	  token[count++] = c;
+	  token[count] = 0;
+	}
+    }
+}
+
+int read_hostsfile(char *filename, unsigned int index, int cache_size, struct crec **rhash, int hashsz)
+{  
+  FILE *f = fopen(filename, "r");
+  char *token = daemon->namebuff, *domain_suffix = NULL;
+  int addr_count = 0, name_count = cache_size, lineno = 1;
+  unsigned int flags = 0;
+  union all_addr addr;
+  int atnl, addrlen = 0;
+
+  if (!f)
+    {
+      my_syslog(LOG_ERR, _("failed to load names from %s: %s"), filename, strerror(errno));
+      return cache_size;
+    }
+  
+  lineno += eatspace(f);
+  
+  while ((atnl = gettok(f, token)) != -1)
+    {
+      if (inet_pton(AF_INET, token, &addr) > 0)
+	{
+	  flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4;
+	  addrlen = INADDRSZ;
+	  domain_suffix = get_domain(addr.addr4);
+	}
+      else if (inet_pton(AF_INET6, token, &addr) > 0)
+	{
+	  flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6;
+	  addrlen = IN6ADDRSZ;
+	  domain_suffix = get_domain6(&addr.addr6);
+	}
+      else
+	{
+	  my_syslog(LOG_ERR, _("bad address at %s line %d"), filename, lineno); 
+	  while (atnl == 0)
+	    atnl = gettok(f, token);
+	  lineno += atnl;
+	  continue;
+	}
+      
+      addr_count++;
+      
+      /* rehash every 1000 names. */
+      if (rhash && ((name_count - cache_size) > 1000))
+	{
+	  rehash(name_count);
+	  cache_size = name_count;
+	} 
+      
+      while (atnl == 0)
+	{
+	  struct crec *cache;
+	  int fqdn, nomem;
+	  char *canon;
+	  
+	  if ((atnl = gettok(f, token)) == -1)
+	    break;
+
+	  fqdn = !!strchr(token, '.');
+
+	  if ((canon = canonicalise(token, &nomem)))
+	    {
+	      /* If set, add a version of the name with a default domain appended */
+	      if (option_bool(OPT_EXPAND) && domain_suffix && !fqdn && 
+		  (cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 2 + strlen(domain_suffix))))
+		{
+		  strcpy(cache->name.sname, canon);
+		  strcat(cache->name.sname, ".");
+		  strcat(cache->name.sname, domain_suffix);
+		  cache->flags = flags;
+		  cache->ttd = daemon->local_ttl;
+		  add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+		  name_count++;
+		}
+	      if ((cache = whine_malloc(SIZEOF_BARE_CREC + strlen(canon) + 1)))
+		{
+		  strcpy(cache->name.sname, canon);
+		  cache->flags = flags;
+		  cache->ttd = daemon->local_ttl;
+		  add_hosts_entry(cache, &addr, addrlen, index, rhash, hashsz);
+		  name_count++;
+		}
+	      free(canon);
+	      
+	    }
+	  else if (!nomem)
+	    my_syslog(LOG_ERR, _("bad name at %s line %d"), filename, lineno); 
+	}
+
+      lineno += atnl;
+    } 
+
+  fclose(f);
+  
+  if (rhash)
+    rehash(name_count); 
+  
+  my_syslog(LOG_INFO, _("read %s - %d addresses"), filename, addr_count);
+  
+  return name_count;
+}
+	    
+void cache_reload(void)
+{
+  struct crec *cache, **up, *tmp;
+  int revhashsz, i, total_size = daemon->cachesize;
+  struct hostsfile *ah;
+  struct host_record *hr;
+  struct name_list *nl;
+  struct cname *a;
+  struct crec lrec;
+  struct mx_srv_record *mx;
+  struct txt_record *txt;
+  struct interface_name *intr;
+  struct ptr_record *ptr;
+  struct naptr *naptr;
+#ifdef HAVE_DNSSEC
+  struct ds_config *ds;
+#endif
+
+  daemon->metrics[METRIC_DNS_CACHE_INSERTED] = 0;
+  daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED] = 0;
+  
+  for (i=0; i<hash_size; i++)
+    for (cache = hash_table[i], up = &hash_table[i]; cache; cache = tmp)
+      {
+	cache_blockdata_free(cache);
+
+	tmp = cache->hash_next;
+	if (cache->flags & (F_HOSTS | F_CONFIG))
+	  {
+	    *up = cache->hash_next;
+	    free(cache);
+	  }
+	else if (!(cache->flags & F_DHCP))
+	  {
+	    *up = cache->hash_next;
+	    if (cache->flags & F_BIGNAME)
+	      {
+		cache->name.bname->next = big_free;
+		big_free = cache->name.bname;
+	      }
+	    cache->flags = 0;
+	  }
+	else
+	  up = &cache->hash_next;
+      }
+  
+  /* Add locally-configured CNAMEs to the cache */
+  for (a = daemon->cnames; a; a = a->next)
+    if (a->alias[1] != '*' &&
+	((cache = whine_malloc(SIZEOF_POINTER_CREC))))
+      {
+	cache->flags = F_FORWARD | F_NAMEP | F_CNAME | F_IMMORTAL | F_CONFIG;
+	cache->ttd = a->ttl;
+	cache->name.namep = a->alias;
+	cache->addr.cname.target.name = a->target;
+	cache->addr.cname.is_name_ptr = 1;
+	cache->uid = UID_NONE;
+	cache_hash(cache);
+	make_non_terminals(cache);
+      }
+  
+#ifdef HAVE_DNSSEC
+  for (ds = daemon->ds; ds; ds = ds->next)
+    if ((cache = whine_malloc(SIZEOF_POINTER_CREC)) &&
+	(cache->addr.ds.keydata = blockdata_alloc(ds->digest, ds->digestlen)))
+      {
+	cache->flags = F_FORWARD | F_IMMORTAL | F_DS | F_CONFIG | F_NAMEP;
+	cache->ttd = daemon->local_ttl;
+	cache->name.namep = ds->name;
+	cache->addr.ds.keylen = ds->digestlen;
+	cache->addr.ds.algo = ds->algo;
+	cache->addr.ds.keytag = ds->keytag;
+	cache->addr.ds.digest = ds->digest_type;
+	cache->uid = ds->class;
+	cache_hash(cache);
+	make_non_terminals(cache);
+      }
+#endif
+  
+  /* borrow the packet buffer for a temporary by-address hash */
+  memset(daemon->packet, 0, daemon->packet_buff_sz);
+  revhashsz = daemon->packet_buff_sz / sizeof(struct crec *);
+  /* we overwrote the buffer... */
+  daemon->srv_save = NULL;
+
+  /* Do host_records in config. */
+  for (hr = daemon->host_records; hr; hr = hr->next)
+    for (nl = hr->names; nl; nl = nl->next)
+      {
+	if ((hr->flags & HR_4) &&
+	    (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+	  {
+	    cache->name.namep = nl->name;
+	    cache->ttd = hr->ttl;
+	    cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV4 | F_NAMEP | F_CONFIG;
+	    add_hosts_entry(cache, (union all_addr *)&hr->addr, INADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+	  }
+
+	if ((hr->flags & HR_6) &&
+	    (cache = whine_malloc(SIZEOF_POINTER_CREC)))
+	  {
+	    cache->name.namep = nl->name;
+	    cache->ttd = hr->ttl;
+	    cache->flags = F_HOSTS | F_IMMORTAL | F_FORWARD | F_REVERSE | F_IPV6 | F_NAMEP | F_CONFIG;
+	    add_hosts_entry(cache, (union all_addr *)&hr->addr6, IN6ADDRSZ, SRC_CONFIG, (struct crec **)daemon->packet, revhashsz);
+	  }
+      }
+	
+  if (option_bool(OPT_NO_HOSTS) && !daemon->addn_hosts)
+    {
+      if (daemon->cachesize > 0)
+	my_syslog(LOG_INFO, _("cleared cache"));
+    }
+  else
+    {
+      if (!option_bool(OPT_NO_HOSTS))
+	total_size = read_hostsfile(HOSTSFILE, SRC_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+      
+      daemon->addn_hosts = expand_filelist(daemon->addn_hosts);
+      for (ah = daemon->addn_hosts; ah; ah = ah->next)
+	if (!(ah->flags & AH_INACTIVE))
+	  total_size = read_hostsfile(ah->fname, ah->index, total_size, (struct crec **)daemon->packet, revhashsz);
+    }
+  
+  /* Make non-terminal records for all locally-define RRs */
+  lrec.flags = F_FORWARD | F_CONFIG | F_NAMEP | F_IMMORTAL;
+  
+  for (txt = daemon->txt; txt; txt = txt->next)
+    {
+      lrec.name.namep = txt->name;
+      make_non_terminals(&lrec);
+    }
+
+  for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+    {
+      lrec.name.namep = naptr->name;
+      make_non_terminals(&lrec);
+    }
+
+  for (mx = daemon->mxnames; mx; mx = mx->next)
+    {
+      lrec.name.namep = mx->name;
+      make_non_terminals(&lrec);
+    }
+
+  for (intr = daemon->int_names; intr; intr = intr->next)
+    {
+      lrec.name.namep = intr->name;
+      make_non_terminals(&lrec);
+    }
+  
+  for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+    {
+      lrec.name.namep = ptr->name;
+      make_non_terminals(&lrec);
+    }
+  
+#ifdef HAVE_INOTIFY
+  set_dynamic_inotify(AH_HOSTS, total_size, (struct crec **)daemon->packet, revhashsz);
+#endif
+  
+} 
+
+#ifdef HAVE_DHCP
+struct in_addr a_record_from_hosts(char *name, time_t now)
+{
+  struct crec *crecp = NULL;
+  struct in_addr ret;
+  
+  while ((crecp = cache_find_by_name(crecp, name, now, F_IPV4)))
+    if (crecp->flags & F_HOSTS)
+      return crecp->addr.addr4;
+
+  my_syslog(MS_DHCP | LOG_WARNING, _("No IPv4 address found for %s"), name);
+  
+  ret.s_addr = 0;
+  return ret;
+}
+
+void cache_unhash_dhcp(void)
+{
+  struct crec *cache, **up;
+  int i;
+
+  for (i=0; i<hash_size; i++)
+    for (cache = hash_table[i], up = &hash_table[i]; cache; cache = cache->hash_next)
+      if (cache->flags & F_DHCP)
+	{
+	  *up = cache->hash_next;
+	  cache->next = dhcp_spare;
+	  dhcp_spare = cache;
+	}
+      else
+	up = &cache->hash_next;
+}
+
+void cache_add_dhcp_entry(char *host_name, int prot,
+			  union all_addr *host_address, time_t ttd) 
+{
+  struct crec *crec = NULL, *fail_crec = NULL;
+  unsigned int flags = F_IPV4;
+  int in_hosts = 0;
+  size_t addrlen = sizeof(struct in_addr);
+
+  if (prot == AF_INET6)
+    {
+      flags = F_IPV6;
+      addrlen = sizeof(struct in6_addr);
+    }
+  
+  inet_ntop(prot, host_address, daemon->addrbuff, ADDRSTRLEN);
+  
+  while ((crec = cache_find_by_name(crec, host_name, 0, flags | F_CNAME)))
+    {
+      /* check all addresses associated with name */
+      if (crec->flags & (F_HOSTS | F_CONFIG))
+	{
+	  if (crec->flags & F_CNAME)
+	    my_syslog(MS_DHCP | LOG_WARNING, 
+		      _("%s is a CNAME, not giving it to the DHCP lease of %s"),
+		      host_name, daemon->addrbuff);
+	  else if (memcmp(&crec->addr, host_address, addrlen) == 0)
+	    in_hosts = 1;
+	  else
+	    fail_crec = crec;
+	}
+      else if (!(crec->flags & F_DHCP))
+	{
+	  cache_scan_free(host_name, NULL, C_IN, 0, crec->flags & (flags | F_CNAME | F_FORWARD), NULL, NULL);
+	  /* scan_free deletes all addresses associated with name */
+	  break;
+	}
+    }
+  
+  /* if in hosts, don't need DHCP record */
+  if (in_hosts)
+    return;
+  
+  /* Name in hosts, address doesn't match */
+  if (fail_crec)
+    {
+      inet_ntop(prot, &fail_crec->addr, daemon->namebuff, MAXDNAME);
+      my_syslog(MS_DHCP | LOG_WARNING, 
+		_("not giving name %s to the DHCP lease of %s because "
+		  "the name exists in %s with address %s"), 
+		host_name, daemon->addrbuff,
+		record_source(fail_crec->uid), daemon->namebuff);
+      return;
+    }	  
+  
+  if ((crec = cache_find_by_addr(NULL, (union all_addr *)host_address, 0, flags)))
+    {
+      if (crec->flags & F_NEG)
+	{
+	  flags |= F_REVERSE;
+	  cache_scan_free(NULL, (union all_addr *)host_address, C_IN, 0, flags, NULL, NULL);
+	}
+    }
+  else
+    flags |= F_REVERSE;
+  
+  if ((crec = dhcp_spare))
+    dhcp_spare = dhcp_spare->next;
+  else /* need new one */
+    crec = whine_malloc(SIZEOF_POINTER_CREC);
+  
+  if (crec) /* malloc may fail */
+    {
+      crec->flags = flags | F_NAMEP | F_DHCP | F_FORWARD;
+      if (ttd == 0)
+	crec->flags |= F_IMMORTAL;
+      else
+	crec->ttd = ttd;
+      crec->addr = *host_address;
+      crec->name.namep = host_name;
+      crec->uid = UID_NONE;
+      cache_hash(crec);
+      make_non_terminals(crec);
+    }
+}
+#endif
+
+/* Called when we put a local or DHCP name into the cache.
+   Creates empty cache entries for subnames (ie,
+   for three.two.one, for two.one and one), without
+   F_IPV4 or F_IPV6 or F_CNAME set. These convert
+   NXDOMAIN answers to NoData ones. */
+static void make_non_terminals(struct crec *source)
+{
+  char *name = cache_get_name(source);
+  struct crec *crecp, *tmp, **up;
+  int type = F_HOSTS | F_CONFIG;
+#ifdef HAVE_DHCP
+  if (source->flags & F_DHCP)
+    type = F_DHCP;
+#endif
+  
+  /* First delete any empty entries for our new real name. Note that
+     we only delete empty entries deriving from DHCP for a new DHCP-derived
+     entry and vice-versa for HOSTS and CONFIG. This ensures that 
+     non-terminals from DHCP go when we reload DHCP and 
+     for HOSTS/CONFIG when we re-read. */
+  for (up = hash_bucket(name), crecp = *up; crecp; crecp = tmp)
+    {
+      tmp = crecp->hash_next;
+
+      if (!is_outdated_cname_pointer(crecp) &&
+	  (crecp->flags & F_FORWARD) &&
+	  (crecp->flags & type) &&
+	  !(crecp->flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS)) && 
+	  hostname_isequal(name, cache_get_name(crecp)))
+	{
+	  *up = crecp->hash_next;
+#ifdef HAVE_DHCP
+	  if (type & F_DHCP)
+	    {
+	      crecp->next = dhcp_spare;
+	      dhcp_spare = crecp;
+	    }
+	  else
+#endif
+	    free(crecp);
+	  break;
+	}
+      else
+	 up = &crecp->hash_next;
+    }
+     
+  while ((name = strchr(name, '.')))
+    {
+      name++;
+
+      /* Look for one existing, don't need another */
+      for (crecp = *hash_bucket(name); crecp; crecp = crecp->hash_next)
+	if (!is_outdated_cname_pointer(crecp) &&
+	    (crecp->flags & F_FORWARD) &&
+	    (crecp->flags & type) &&
+	    hostname_isequal(name, cache_get_name(crecp)))
+	  break;
+      
+      if (crecp)
+	{
+	  /* If the new name expires later, transfer that time to
+	     empty non-terminal entry. */
+	  if (!(crecp->flags & F_IMMORTAL))
+	    {
+	      if (source->flags & F_IMMORTAL)
+		crecp->flags |= F_IMMORTAL;
+	      else if (difftime(crecp->ttd, source->ttd) < 0)
+		crecp->ttd = source->ttd;
+	    }
+	  continue;
+	}
+      
+#ifdef HAVE_DHCP
+      if ((source->flags & F_DHCP) && dhcp_spare)
+	{
+	  crecp = dhcp_spare;
+	  dhcp_spare = dhcp_spare->next;
+	}
+      else
+#endif
+	crecp = whine_malloc(SIZEOF_POINTER_CREC);
+
+      if (crecp)
+	{
+	  crecp->flags = (source->flags | F_NAMEP) & ~(F_IPV4 | F_IPV6 | F_CNAME | F_SRV | F_DNSKEY | F_DS | F_REVERSE);
+	  crecp->ttd = source->ttd;
+	  crecp->name.namep = name;
+	  
+	  cache_hash(crecp);
+	}
+    }
+}
+
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t)
+{ 
+  static char *buff = NULL;
+  static int bufflen = 60;
+  int len;
+  struct server *serv, *serv1;
+  char *p;
+
+  if (!buff && !(buff = whine_malloc(60)))
+    return 0;
+
+  p = buff;
+  
+  switch (t->stat)
+    {
+    case TXT_STAT_CACHESIZE:
+      sprintf(buff+1, "%d", daemon->cachesize);
+      break;
+
+    case TXT_STAT_INSERTS:
+      sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+      break;
+
+    case TXT_STAT_EVICTIONS:
+      sprintf(buff+1, "%d", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+      break;
+
+    case TXT_STAT_MISSES:
+      sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+      break;
+
+    case TXT_STAT_HITS:
+      sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+      break;
+
+#ifdef HAVE_AUTH
+    case TXT_STAT_AUTH:
+      sprintf(buff+1, "%u", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+      break;
+#endif
+
+    case TXT_STAT_SERVERS:
+      /* sum counts from different records for same server */
+      for (serv = daemon->servers; serv; serv = serv->next)
+	serv->flags &= ~SERV_MARK;
+      
+      for (serv = daemon->servers; serv; serv = serv->next)
+	if (!(serv->flags & SERV_MARK))
+	  {
+	    char *new, *lenp;
+	    int port, newlen, bytes_avail, bytes_needed;
+	    unsigned int queries = 0, failed_queries = 0;
+	    for (serv1 = serv; serv1; serv1 = serv1->next)
+	      if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+		{
+		  serv1->flags |= SERV_MARK;
+		  queries += serv1->queries;
+		  failed_queries += serv1->failed_queries;
+		}
+	    port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+	    lenp = p++; /* length */
+	    bytes_avail = bufflen - (p - buff );
+	    bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+	    if (bytes_needed >= bytes_avail)
+	      {
+		/* expand buffer if necessary */
+		newlen = bytes_needed + 1 + bufflen - bytes_avail;
+		if (!(new = whine_malloc(newlen)))
+		  return 0;
+		memcpy(new, buff, bufflen);
+		free(buff);
+		p = new + (p - buff);
+		lenp = p - 1;
+		buff = new;
+		bufflen = newlen;
+		bytes_avail =  bufflen - (p - buff );
+		bytes_needed = snprintf(p, bytes_avail, "%s#%d %u %u", daemon->addrbuff, port, queries, failed_queries);
+	      }
+	    *lenp = bytes_needed;
+	    p += bytes_needed;
+	  }
+      t->txt = (unsigned char *)buff;
+      t->len = p - buff;
+
+      return 1;
+    }
+  
+  len = strlen(buff+1);
+  t->txt = (unsigned char *)buff;
+  t->len = len + 1;
+  *buff = len;
+  return 1;
+}
+#endif
+
+/* There can be names in the cache containing control chars, don't 
+   mess up logging or open security holes. */
+static char *sanitise(char *name)
+{
+  unsigned char *r;
+  if (name)
+    for (r = (unsigned char *)name; *r; r++)
+      if (!isprint((int)*r))
+	return "<name unprintable>";
+
+  return name;
+}
+
+
+void dump_cache(time_t now)
+{
+  struct server *serv, *serv1;
+
+  my_syslog(LOG_INFO, _("time %lu"), (unsigned long)now);
+  my_syslog(LOG_INFO, _("cache size %d, %d/%d cache insertions re-used unexpired cache entries."), 
+	    daemon->cachesize, daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED], daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+  my_syslog(LOG_INFO, _("queries forwarded %u, queries answered locally %u"), 
+	    daemon->metrics[METRIC_DNS_QUERIES_FORWARDED], daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+#ifdef HAVE_AUTH
+  my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+
+  blockdata_report();
+
+  /* sum counts from different records for same server */
+  for (serv = daemon->servers; serv; serv = serv->next)
+    serv->flags &= ~SERV_MARK;
+  
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (!(serv->flags & SERV_MARK))
+      {
+	int port;
+	unsigned int queries = 0, failed_queries = 0;
+	for (serv1 = serv; serv1; serv1 = serv1->next)
+	  if (!(serv1->flags & SERV_MARK) && sockaddr_isequal(&serv->addr, &serv1->addr))
+	    {
+	      serv1->flags |= SERV_MARK;
+	      queries += serv1->queries;
+	      failed_queries += serv1->failed_queries;
+	    }
+	port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+	my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried or failed %u"), daemon->addrbuff, port, queries, failed_queries);
+      }
+
+  if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG))
+    {
+      struct crec *cache ;
+      int i;
+      my_syslog(LOG_INFO, "Host                                     Address                        Flags      Expires");
+    
+      for (i=0; i<hash_size; i++)
+	for (cache = hash_table[i]; cache; cache = cache->hash_next)
+	  {
+	    char *t = " ";
+	    char *a = daemon->addrbuff, *p = daemon->namebuff, *n = cache_get_name(cache);
+	    *a = 0;
+	    if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
+	      n = "<Root>";
+	    p += sprintf(p, "%-30.30s ", sanitise(n));
+	    if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+	      a = sanitise(cache_get_cname_target(cache));
+	    else if ((cache->flags & F_SRV) && !(cache->flags & F_NEG))
+	      {
+		int targetlen = cache->addr.srv.targetlen;
+		ssize_t len = sprintf(a, "%u %u %u ", cache->addr.srv.priority,
+				      cache->addr.srv.weight, cache->addr.srv.srvport);
+
+		if (targetlen > (40 - len))
+		  targetlen = 40 - len;
+		blockdata_retrieve(cache->addr.srv.target, targetlen, a + len);
+		a[len + targetlen] = 0;		
+	      }
+#ifdef HAVE_DNSSEC
+	    else if (cache->flags & F_DS)
+	      {
+		if (!(cache->flags & F_NEG))
+		  sprintf(a, "%5u %3u %3u", cache->addr.ds.keytag,
+			  cache->addr.ds.algo, cache->addr.ds.digest);
+	      }
+	    else if (cache->flags & F_DNSKEY)
+	      sprintf(a, "%5u %3u %3u", cache->addr.key.keytag,
+		      cache->addr.key.algo, cache->addr.key.flags);
+#endif
+	    else if (!(cache->flags & F_NEG) || !(cache->flags & F_FORWARD))
+	      { 
+		a = daemon->addrbuff;
+		if (cache->flags & F_IPV4)
+		  inet_ntop(AF_INET, &cache->addr, a, ADDRSTRLEN);
+		else if (cache->flags & F_IPV6)
+		  inet_ntop(AF_INET6, &cache->addr, a, ADDRSTRLEN);
+	      }
+
+	    if (cache->flags & F_IPV4)
+	      t = "4";
+	    else if (cache->flags & F_IPV6)
+	      t = "6";
+	    else if (cache->flags & F_CNAME)
+	      t = "C";
+	    else if (cache->flags & F_SRV)
+	      t = "V";
+#ifdef HAVE_DNSSEC
+	    else if (cache->flags & F_DS)
+	      t = "S";
+	    else if (cache->flags & F_DNSKEY)
+	      t = "K";
+#endif
+	    p += sprintf(p, "%-40.40s %s%s%s%s%s%s%s%s%s  ", a, t,
+			 cache->flags & F_FORWARD ? "F" : " ",
+			 cache->flags & F_REVERSE ? "R" : " ",
+			 cache->flags & F_IMMORTAL ? "I" : " ",
+			 cache->flags & F_DHCP ? "D" : " ",
+			 cache->flags & F_NEG ? "N" : " ",
+			 cache->flags & F_NXDOMAIN ? "X" : " ",
+			 cache->flags & F_HOSTS ? "H" : " ",
+			 cache->flags & F_DNSSECOK ? "V" : " ");
+#ifdef HAVE_BROKEN_RTC
+	    p += sprintf(p, "%lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now));
+#else
+	    p += sprintf(p, "%s", cache->flags & F_IMMORTAL ? "\n" : ctime(&(cache->ttd)));
+	    /* ctime includes trailing \n - eat it */
+	    *(p-1) = 0;
+#endif
+	    my_syslog(LOG_INFO, "%s", daemon->namebuff);
+	  }
+    }
+}
+
+char *record_source(unsigned int index)
+{
+  struct hostsfile *ah;
+
+  if (index == SRC_CONFIG)
+    return "config";
+  else if (index == SRC_HOSTS)
+    return HOSTSFILE;
+
+  for (ah = daemon->addn_hosts; ah; ah = ah->next)
+    if (ah->index == index)
+      return ah->fname;
+
+#ifdef HAVE_INOTIFY
+  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+     if (ah->index == index)
+       return ah->fname;
+#endif
+
+  return "<unknown>";
+}
+
+char *querystr(char *desc, unsigned short type)
+{
+  unsigned int i;
+  int len = 10; /* strlen("type=xxxxx") */
+  const char *types = NULL;
+  static char *buff = NULL;
+  static int bufflen = 0;
+
+  for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+    if (typestr[i].type == type)
+      {
+	types = typestr[i].name;
+	len = strlen(types);
+	break;
+      }
+
+  if (desc)
+    {
+       len += 2; /* braces */
+       len += strlen(desc);
+    }
+  len++; /* terminator */
+  
+  if (!buff || bufflen < len)
+    {
+      if (buff)
+	free(buff);
+      else if (len < 20)
+	len = 20;
+      
+      buff = whine_malloc(len);
+      bufflen = len;
+    }
+
+  if (buff)
+    {
+      if (desc)
+	{
+	  if (types)
+	    sprintf(buff, "%s[%s]", desc, types);
+	  else
+	    sprintf(buff, "%s[type=%d]", desc, type);
+	}
+      else
+	{
+	  if (types)
+	    sprintf(buff, "<%s>", types);
+	  else
+	    sprintf(buff, "<type=%d>", type);
+	}
+    }
+  
+  return buff ? buff : "";
+}
+
+static char *edestr(int ede)
+{
+  switch (ede)
+    {
+    case EDE_OTHER:                       return "other";
+    case EDE_USUPDNSKEY:                  return "unsupported DNSKEY algorithm";
+    case EDE_USUPDS:                      return "unsupported DS digest";
+    case EDE_STALE:                       return "stale answer";
+    case EDE_FORGED:                      return "forged";
+    case EDE_DNSSEC_IND:                  return "DNSSEC indeterminate";
+    case EDE_DNSSEC_BOGUS:                return "DNSSEC bogus";
+    case EDE_SIG_EXP:                     return "DNSSEC signature expired";
+    case EDE_SIG_NYV:                     return "DNSSEC sig not yet valid";
+    case EDE_NO_DNSKEY:                   return "DNSKEY missing";
+    case EDE_NO_RRSIG:                    return "RRSIG missing";
+    case EDE_NO_ZONEKEY:                  return "no zone key bit set";
+    case EDE_NO_NSEC:                     return "NSEC(3) missing";
+    case EDE_CACHED_ERR:                  return "cached error";
+    case EDE_NOT_READY:                   return "not ready";
+    case EDE_BLOCKED:                     return "blocked";
+    case EDE_CENSORED:                    return "censored";
+    case EDE_FILTERED:                    return "filtered";
+    case EDE_PROHIBITED:                  return "prohibited";
+    case EDE_STALE_NXD:                   return "stale NXDOMAIN";
+    case EDE_NOT_AUTH:                    return "not authoritative";
+    case EDE_NOT_SUP:                     return "not supported";
+    case EDE_NO_AUTH:                     return "no reachable authority";
+    case EDE_NETERR:                      return "network error";
+    case EDE_INVALID_DATA:                return "invalid data";
+    default:                              return "unknown";
+    }
+}
+
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg)
+{
+  char *source, *dest = arg;
+  char *verb = "is";
+  char *extra = "";
+  
+  if (!option_bool(OPT_LOG))
+    return;
+  
+#ifdef HAVE_DNSSEC
+  if ((flags & F_DNSSECOK) && option_bool(OPT_EXTRALOG))
+    extra = " (DNSSEC signed)";
+#endif
+
+  name = sanitise(name);
+
+  if (addr)
+    {
+      dest = daemon->addrbuff;
+
+      if (flags & F_KEYTAG)
+	sprintf(daemon->addrbuff, arg, addr->log.keytag, addr->log.algo, addr->log.digest);
+      else if (flags & F_RCODE)
+	{
+	  unsigned int rcode = addr->log.rcode;
+
+	  if (rcode == SERVFAIL)
+	    dest = "SERVFAIL";
+	  else if (rcode == REFUSED)
+	    dest = "REFUSED";
+	  else if (rcode == NOTIMP)
+	    dest = "not implemented";
+	  else
+	    sprintf(daemon->addrbuff, "%u", rcode);
+
+	  if (addr->log.ede != EDE_UNSET)
+	    {
+	      extra = daemon->addrbuff;
+	      sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+	    }
+	}
+      else if (flags & (F_IPV4 | F_IPV6))
+	inet_ntop(flags & F_IPV4 ? AF_INET : AF_INET6,
+		  addr, daemon->addrbuff, ADDRSTRLEN);
+      else
+	dest = arg;
+    }
+
+  if (flags & F_REVERSE)
+    {
+      dest = name;
+      name = daemon->addrbuff;
+    }
+  
+  if (flags & F_NEG)
+    {
+      if (flags & F_NXDOMAIN)
+	dest = "NXDOMAIN";
+      else
+	{      
+	  if (flags & F_IPV4)
+	    dest = "NODATA-IPv4";
+	  else if (flags & F_IPV6)
+	    dest = "NODATA-IPv6";
+	  else
+	    dest = "NODATA";
+	}
+    }
+  else if (flags & F_CNAME)
+    dest = "<CNAME>";
+  else if (flags & F_SRV)
+    dest = "<SRV>";
+  else if (flags & F_RRNAME)
+    dest = arg;
+    
+  if (flags & F_CONFIG)
+    source = "config";
+  else if (flags & F_DHCP)
+    source = "DHCP";
+  else if (flags & F_HOSTS)
+    source = arg;
+  else if (flags & F_UPSTREAM)
+    source = "reply";
+  else if (flags & F_SECSTAT)
+    {
+      if (addr && addr->log.ede != EDE_UNSET && option_bool(OPT_EXTRALOG))
+	{
+	  extra = daemon->addrbuff;
+	  sprintf(extra, " (EDE: %s)", edestr(addr->log.ede));
+	}
+      source = "validation";
+      dest = arg;
+    }
+  else if (flags & F_AUTH)
+    source = "auth";
+  else if (flags & F_SERVER)
+    {
+      source = "forwarded";
+      verb = "to";
+    }
+  else if (flags & F_QUERY)
+    {
+      source = arg;
+      verb = "from";
+    }
+  else if (flags & F_DNSSEC)
+    {
+      source = arg;
+      verb = "to";
+    }
+  else if (flags & F_IPSET)
+    {
+      source = "ipset add";
+      dest = name;
+      name = arg;
+      verb = daemon->addrbuff;
+    }
+  else
+    source = "cached";
+  
+  if (strlen(name) == 0)
+    name = ".";
+
+  if (option_bool(OPT_EXTRALOG))
+    {
+      if (flags & F_NOEXTRA)
+	my_syslog(LOG_INFO, "%u %s %s %s %s%s", daemon->log_display_id, source, name, verb, dest, extra);
+      else
+	{
+	   int port = prettyprint_addr(daemon->log_source_addr, daemon->addrbuff2);
+	   my_syslog(LOG_INFO, "%u %s/%u %s %s %s %s%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, verb, dest, extra);
+	}
+    }
+  else
+    my_syslog(LOG_INFO, "%s %s %s %s%s", source, name, verb, dest, extra);
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/config.h b/ap/app/dnsmasq/dnsmasq-2.86/src/config.h
new file mode 100755
index 0000000..26fcaf0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/config.h
@@ -0,0 +1,452 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+#define FTABSIZ 150 /* max number of outstanding requests (default) */
+#define MAX_PROCS 2 /* max no children for TCP requests */
+#define CHILD_LIFETIME 150 /* secs 'till terminated (RFC1035 suggests > 120s) */
+#define TCP_MAX_QUERIES 100 /* Maximum number of queries per incoming TCP connection */
+#define TCP_BACKLOG 32  /* kernel backlog limit for TCP connections */
+//#define EDNS_PKTSZ 4096 CVE-2023-28450/* default max EDNS.0 UDP packet from RFC5625 */
+#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from  /dnsflagday.net/2020 */
+#define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
+#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
+#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
+#define TIMEOUT 10     /* drop UDP queries after TIMEOUT seconds */
+#define FORWARD_TEST 50 /* try all servers every 50 queries */
+#define FORWARD_TIME 20 /* or 20 seconds */
+#define UDP_TEST_TIME 60 /* How often to reset our idea of max packet size. */
+#define SERVERS_LOGGED 30 /* Only log this many servers when logging state */
+#define LOCALS_LOGGED 8 /* Only log this many local addresses when logging state */
+#define LEASE_RETRY 60 /* on error, retry writing leasefile after LEASE_RETRY seconds */
+#define CACHESIZ 150 /* default cache size */
+#define TTL_FLOOR_LIMIT 3600 /* don't allow --min-cache-ttl to raise TTL above this under any circumstances */
+#define MAXLEASES 1000 /* maximum number of DHCP leases */
+#define PING_WAIT 3 /* wait for ping address-in-use test */
+#define PING_CACHE_TIME 30 /* Ping test assumed to be valid this long. */
+#define DECLINE_BACKOFF 600 /* disable DECLINEd static addresses for this long */
+#define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */
+#define SMALLDNAME 50 /* most domain names are smaller than this */
+#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */
+#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */
+#define HOSTSFILE "/etc/hosts"
+#define ETHERSFILE "/etc/ethers"
+#define DEFLEASE 3600 /* default DHCPv4 lease time, one hour */
+#define DEFLEASE6 (3600*24) /* default lease time for DHCPv6. One day. */
+#define CHUSER "nobody"
+#define CHGRP "dip"
+#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
+#define LOG_MAX 5 /* log-queue length */
+#define RANDFILE "/dev/urandom"
+#define DNSMASQ_SERVICE "uk.org.thekelleys.dnsmasq" /* Default - may be overridden by config */
+#define DNSMASQ_PATH "/uk/org/thekelleys/dnsmasq"
+#define DNSMASQ_UBUS_NAME "dnsmasq" /* Default - may be overridden by config */
+#define AUTH_TTL 600 /* default TTL for auth DNS */
+#define SOA_REFRESH 1200 /* SOA refresh default */
+#define SOA_RETRY 180 /* SOA retry default */
+#define SOA_EXPIRY 1209600 /* SOA expiry default */
+#define LOOP_TEST_DOMAIN "test" /* domain for loop testing, "test" is reserved by RFC 2606 and won't therefore clash */
+#define LOOP_TEST_TYPE T_TXT
+ 
+/* compile-time options: uncomment below to enable or do eg.
+   make COPTS=-DHAVE_BROKEN_RTC
+
+HAVE_BROKEN_RTC
+   define this on embedded systems which don't have an RTC
+   which keeps time over reboots. Causes dnsmasq to use uptime
+   for timing, and keep lease lengths rather than expiry times
+   in its leases file. This also make dnsmasq "flash disk friendly".
+   Normally, dnsmasq tries very hard to keep the on-disk leases file
+   up-to-date: rewriting it after every renewal.  When HAVE_BROKEN_RTC 
+   is in effect, the lease file is only written when a new lease is 
+   created, or an old one destroyed. (Because those are the only times 
+   it changes.) This vastly reduces the number of file writes, and makes
+   it viable to keep the lease file on a flash filesystem.
+   NOTE: when enabling or disabling this, be sure to delete any old
+   leases file, otherwise dnsmasq may get very confused.
+
+HAVE_TFTP
+   define this to get dnsmasq's built-in TFTP server.
+
+HAVE_DHCP
+   define this to get dnsmasq's DHCPv4 server.
+
+HAVE_DHCP6
+   define this to get dnsmasq's DHCPv6 server. (implies HAVE_DHCP).
+
+HAVE_SCRIPT
+   define this to get the ability to call scripts on lease-change.
+
+HAVE_LUASCRIPT
+   define this to get the ability to call Lua script on lease-change. (implies HAVE_SCRIPT) 
+
+HAVE_DBUS
+   define this if you want to link against libdbus, and have dnsmasq
+   support some methods to allow (re)configuration of the upstream DNS 
+   servers via DBus.
+
+HAVE_UBUS
+   define this if you want to link against libubus
+
+HAVE_IDN
+   define this if you want international domain name 2003 support.
+   
+HAVE_LIBIDN2
+   define this if you want international domain name 2008 support.
+
+HAVE_CONNTRACK
+   define this to include code which propagates conntrack marks from
+   incoming DNS queries to the corresponding upstream queries. This adds
+   a build-dependency on libnetfilter_conntrack, but the resulting binary will
+   still run happily on a kernel without conntrack support.
+
+HAVE_IPSET
+    define this to include the ability to selectively add resolved ip addresses
+    to given ipsets.
+
+HAVE_AUTH
+   define this to include the facility to act as an authoritative DNS
+   server for one or more zones.
+
+HAVE_CRYPTOHASH
+   include just hash function from crypto library, but no DNSSEC.
+
+HAVE_DNSSEC
+   include DNSSEC validator.
+
+HAVE_DUMPFILE
+   include code to dump packets to a libpcap-format file for debugging.
+
+HAVE_LOOP
+   include functionality to probe for and remove DNS forwarding loops.
+
+HAVE_INOTIFY
+   use the Linux inotify facility to efficiently re-read configuration files.
+
+NO_ID
+   Don't report *.bind CHAOS info to clients, forward such requests upstream instead.
+NO_TFTP
+NO_DHCP
+NO_DHCP6
+NO_SCRIPT
+NO_LARGEFILE
+NO_AUTH
+NO_DUMPFILE
+NO_LOOP
+NO_INOTIFY
+   these are available to explicitly disable compile time options which would 
+   otherwise be enabled automatically or which are enabled  by default 
+   in the distributed source tree. Building dnsmasq
+   with something like "make COPTS=-DNO_SCRIPT" will do the trick.
+NO_GMP
+   Don't use and link against libgmp, Useful if nettle is built with --enable-mini-gmp.
+
+LEASEFILE
+CONFFILE
+RESOLVFILE
+   the default locations of these files are determined below, but may be overridden 
+   in a build command line using COPTS.
+
+*/
+
+/* Defining this builds a binary which handles time differently and works better on a system without a 
+   stable RTC (it uses uptime, not epoch time) and writes the DHCP leases file less often to avoid flash wear. 
+*/
+
+/* #define HAVE_BROKEN_RTC */
+
+/* The default set of options to build. Built with these options, dnsmasq
+   has no library dependencies other than libc */
+
+#define HAVE_DHCP
+#define HAVE_DHCP6 
+#define HAVE_TFTP
+#define HAVE_SCRIPT
+#define HAVE_AUTH
+#define HAVE_IPSET 
+#define HAVE_LOOP
+#define HAVE_DUMPFILE
+
+/* Build options which require external libraries.
+   
+   Defining HAVE_<opt>_STATIC as _well_ as HAVE_<opt> will link the library statically.
+
+   You can use "make COPTS=-DHAVE_<opt>" instead of editing these.
+*/
+
+/* #define HAVE_LUASCRIPT */
+/* #define HAVE_DBUS */
+/* #define HAVE_IDN */
+/* #define HAVE_LIBIDN2 */
+/* #define HAVE_CONNTRACK */
+/* #define HAVE_CRYPTOHASH */
+/* #define HAVE_DNSSEC */
+
+
+/* Default locations for important system files. */
+
+#ifndef LEASEFILE
+#   if defined(__FreeBSD__) || defined (__OpenBSD__) || defined(__DragonFly__) || defined(__NetBSD__)
+#      define LEASEFILE "/var/db/dnsmasq.leases"
+#   elif defined(__sun__) || defined (__sun)
+#      define LEASEFILE "/var/cache/dnsmasq.leases"
+#   elif defined(__ANDROID__)
+#      define LEASEFILE "/data/misc/dhcp/dnsmasq.leases"
+#   else
+#      define LEASEFILE "/var/lib/misc/dnsmasq.leases"
+#   endif
+#endif
+
+#ifndef CONFFILE
+#   if defined(__FreeBSD__)
+#      define CONFFILE "/usr/local/etc/dnsmasq.conf"
+#   else
+#      define CONFFILE "/etc/dnsmasq.conf"
+#   endif
+#endif
+
+#ifndef RESOLVFILE
+#   if defined(__uClinux__)
+#      define RESOLVFILE "/etc/config/resolv.conf"
+#   else
+#      define RESOLVFILE "/etc/resolv.conf"
+#   endif
+#endif
+
+#ifndef RUNFILE
+#   if defined(__ANDROID__)
+#      define RUNFILE "/data/dnsmasq.pid"
+#    else
+#      define RUNFILE "/var/run/dnsmasq.pid"
+#    endif
+#endif
+
+/* platform dependent options: these are determined automatically below
+
+HAVE_LINUX_NETWORK
+HAVE_BSD_NETWORK
+HAVE_SOLARIS_NETWORK
+   define exactly one of these to alter interaction with kernel networking.
+
+HAVE_GETOPT_LONG
+   defined when GNU-style getopt_long available. 
+
+HAVE_SOCKADDR_SA_LEN
+   defined if struct sockaddr has sa_len field (*BSD) 
+*/
+
+#if defined(__UCLIBC__)
+#define HAVE_LINUX_NETWORK
+#if defined(__UCLIBC_HAS_GNU_GETOPT__) || \
+   ((__UCLIBC_MAJOR__==0) && (__UCLIBC_MINOR__==9) && (__UCLIBC_SUBLEVEL__<21))
+#    define HAVE_GETOPT_LONG
+#endif
+#undef HAVE_SOCKADDR_SA_LEN
+#if defined(__UCLIBC_HAS_IPV6__)
+#  ifndef IPV6_V6ONLY
+#    define IPV6_V6ONLY 26
+#  endif
+#endif
+
+/* This is for glibc 2.x */
+#elif defined(__linux__)
+#define HAVE_LINUX_NETWORK
+#define HAVE_GETOPT_LONG
+#undef HAVE_SOCKADDR_SA_LEN
+
+#elif defined(__FreeBSD__) || \
+      defined(__OpenBSD__) || \
+      defined(__DragonFly__) || \
+      defined(__FreeBSD_kernel__)
+#define HAVE_BSD_NETWORK
+/* Later versions of FreeBSD have getopt_long() */
+#if defined(optional_argument) && defined(required_argument)
+#   define HAVE_GETOPT_LONG
+#endif
+#define HAVE_SOCKADDR_SA_LEN
+
+#elif defined(__APPLE__)
+#define HAVE_BSD_NETWORK
+#define HAVE_GETOPT_LONG
+#define HAVE_SOCKADDR_SA_LEN
+#define NO_IPSET
+/* Define before sys/socket.h is included so we get socklen_t */
+#define _BSD_SOCKLEN_T_
+/* Select the RFC_3542 version of the IPv6 socket API. 
+   Define before netinet6/in6.h is included. */
+#define __APPLE_USE_RFC_3542
+/* Required for Mojave. */
+#ifndef SOL_TCP
+#  define SOL_TCP IPPROTO_TCP
+#endif
+#define NO_IPSET
+
+#elif defined(__NetBSD__)
+#define HAVE_BSD_NETWORK
+#define HAVE_GETOPT_LONG
+#define HAVE_SOCKADDR_SA_LEN
+
+#elif defined(__sun) || defined(__sun__)
+#define HAVE_SOLARIS_NETWORK
+#define HAVE_GETOPT_LONG
+#undef HAVE_SOCKADDR_SA_LEN
+#define ETHER_ADDR_LEN 6 
+ 
+#endif
+
+/* rules to implement compile-time option dependencies and 
+   the NO_XXX flags */
+
+#ifdef NO_TFTP
+#undef HAVE_TFTP
+#endif
+
+#ifdef NO_DHCP
+#undef HAVE_DHCP
+#undef HAVE_DHCP6
+#endif
+
+#if defined(NO_DHCP6)
+#undef HAVE_DHCP6
+#endif
+
+/* DHCP6 needs DHCP too */
+#ifdef HAVE_DHCP6
+#define HAVE_DHCP
+#endif
+
+#if defined(NO_SCRIPT)
+#undef HAVE_SCRIPT
+#undef HAVE_LUASCRIPT
+#endif
+
+/* Must HAVE_SCRIPT to HAVE_LUASCRIPT */
+#ifdef HAVE_LUASCRIPT
+#define HAVE_SCRIPT
+#endif
+
+#ifdef NO_AUTH
+#undef HAVE_AUTH
+#endif
+
+#if defined(NO_IPSET)
+#undef HAVE_IPSET
+#endif
+
+#ifdef NO_LOOP
+#undef HAVE_LOOP
+#endif
+
+#ifdef NO_DUMPFILE
+#undef HAVE_DUMPFILE
+#endif
+
+#if defined (HAVE_LINUX_NETWORK) && !defined(NO_INOTIFY)
+#define HAVE_INOTIFY
+#endif
+
+/* Define a string indicating which options are in use.
+   DNSMASQ_COMPILE_OPTS is only defined in dnsmasq.c */
+
+#ifdef DNSMASQ_COMPILE_OPTS
+
+static char *compile_opts = 
+"IPv6 "
+#ifndef HAVE_GETOPT_LONG
+"no-"
+#endif
+"GNU-getopt "
+#ifdef HAVE_BROKEN_RTC
+"no-RTC "
+#endif
+#ifndef HAVE_DBUS
+"no-"
+#endif
+"DBus "
+#ifndef HAVE_UBUS
+"no-"
+#endif
+"UBus "
+#ifndef LOCALEDIR
+"no-"
+#endif
+"i18n "
+#if defined(HAVE_LIBIDN2)
+"IDN2 "
+#else
+ #if !defined(HAVE_IDN)
+"no-"
+ #endif 
+"IDN " 
+#endif
+#ifndef HAVE_DHCP
+"no-"
+#endif
+"DHCP "
+#if defined(HAVE_DHCP)
+#  if !defined (HAVE_DHCP6)
+     "no-"
+#  endif  
+     "DHCPv6 "
+#endif
+#if !defined(HAVE_SCRIPT)
+     "no-scripts "
+#else
+#  if !defined(HAVE_LUASCRIPT)
+     "no-"
+#  endif
+     "Lua "
+#endif
+#ifndef HAVE_TFTP
+"no-"
+#endif
+"TFTP "
+#ifndef HAVE_CONNTRACK
+"no-"
+#endif
+"conntrack "
+#ifndef HAVE_IPSET
+"no-"
+#endif
+"ipset "
+#ifndef HAVE_AUTH
+"no-"
+#endif
+"auth "
+#if !defined(HAVE_CRYPTOHASH) && !defined(HAVE_DNSSEC)
+"no-"
+#endif
+"cryptohash "
+#ifndef HAVE_DNSSEC
+"no-"
+#endif
+"DNSSEC "
+#ifdef NO_ID
+"no-ID "
+#endif
+#ifndef HAVE_LOOP
+"no-"
+#endif
+"loop-detect "
+#ifndef HAVE_INOTIFY
+"no-"
+#endif
+"inotify "
+#ifndef HAVE_DUMPFILE
+"no-"
+#endif
+"dumpfile";
+
+#endif /* defined(HAVE_DHCP) */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c b/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c
new file mode 100755
index 0000000..745a2a3
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/conntrack.c
@@ -0,0 +1,85 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_CONNTRACK
+
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
+
+static int gotit = 0; /* yuck */
+
+static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data);
+
+int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr, int istcp, unsigned int *markp)
+{
+  struct nf_conntrack *ct;
+  struct nfct_handle *h;
+  
+  gotit = 0;
+  
+  if ((ct = nfct_new())) 
+    {
+      nfct_set_attr_u8(ct, ATTR_L4PROTO, istcp ? IPPROTO_TCP : IPPROTO_UDP);
+      nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(daemon->port));
+      
+      if (peer_addr->sa.sa_family == AF_INET6)
+	{
+	  nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6);
+	  nfct_set_attr(ct, ATTR_IPV6_SRC, peer_addr->in6.sin6_addr.s6_addr);
+	  nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in6.sin6_port);
+	  nfct_set_attr(ct, ATTR_IPV6_DST, local_addr->addr6.s6_addr);
+	}
+      else
+	{
+	  nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET);
+	  nfct_set_attr_u32(ct, ATTR_IPV4_SRC, peer_addr->in.sin_addr.s_addr);
+	  nfct_set_attr_u16(ct, ATTR_PORT_SRC, peer_addr->in.sin_port);
+	  nfct_set_attr_u32(ct, ATTR_IPV4_DST, local_addr->addr4.s_addr);
+	}
+      
+      
+      if ((h = nfct_open(CONNTRACK, 0))) 
+	{
+	  nfct_callback_register(h, NFCT_T_ALL, callback, (void *)markp);  
+	  if (nfct_query(h, NFCT_Q_GET, ct) == -1)
+	    {
+	      static int warned = 0;
+	      if (!warned)
+		{
+		  my_syslog(LOG_ERR, _("Conntrack connection mark retrieval failed: %s"), strerror(errno));
+		  warned = 1;
+		}
+	    }
+	  nfct_close(h);  
+	}
+      nfct_destroy(ct);
+    }
+
+  return gotit;
+}
+
+static int callback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *data)
+{
+  unsigned int *ret = (unsigned int *)data;
+  *ret = nfct_get_attr_u32(ct, ATTR_MARK);
+  (void)type; /* eliminate warning */
+  gotit = 1;
+
+  return NFCT_CB_CONTINUE;
+}
+
+#endif /* HAVE_CONNTRACK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c b/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c
new file mode 100755
index 0000000..4009569
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/crypto.c
@@ -0,0 +1,499 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+
+/* Minimal version of nettle */
+
+/* bignum.h includes version.h and works on
+   earlier releases of nettle which don't have version.h */
+#include <nettle/bignum.h>
+#if !defined(NETTLE_VERSION_MAJOR)
+#  define NETTLE_VERSION_MAJOR 2
+#  define NETTLE_VERSION_MINOR 0
+#endif
+#define MIN_VERSION(major, minor) ((NETTLE_VERSION_MAJOR == (major) && NETTLE_VERSION_MINOR >= (minor)) || \
+				   (NETTLE_VERSION_MAJOR > (major)))
+
+#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */
+
+#if defined(HAVE_DNSSEC)
+#include <nettle/rsa.h>
+#include <nettle/ecdsa.h>
+#include <nettle/ecc-curve.h>
+#if MIN_VERSION(3, 1)
+#include <nettle/eddsa.h>
+#endif
+#if MIN_VERSION(3, 6)
+#  include <nettle/gostdsa.h>
+#endif
+
+#if MIN_VERSION(3, 1)
+/* Implement a "hash-function" to the nettle API, which simply returns
+   the input data, concatenated into a single, statically maintained, buffer.
+
+   Used for the EdDSA sigs, which operate on the whole message, rather 
+   than a digest. */
+
+struct null_hash_digest
+{
+  uint8_t *buff;
+  size_t len;
+};
+
+struct null_hash_ctx
+{
+  size_t len;
+};
+
+static size_t null_hash_buff_sz = 0;
+static uint8_t *null_hash_buff = NULL;
+#define BUFF_INCR 128
+
+static void null_hash_init(void *ctx)
+{
+  ((struct null_hash_ctx *)ctx)->len = 0;
+}
+
+static void null_hash_update(void *ctxv, size_t length, const uint8_t *src)
+{
+  struct null_hash_ctx *ctx = ctxv;
+  size_t new_len = ctx->len + length;
+  
+  if (new_len > null_hash_buff_sz)
+    {
+      uint8_t *new;
+      
+      if (!(new = whine_malloc(new_len + BUFF_INCR)))
+	return;
+
+      if (null_hash_buff)
+	{
+	  if (ctx->len != 0)
+	    memcpy(new, null_hash_buff, ctx->len);
+	  free(null_hash_buff);
+	}
+      
+      null_hash_buff_sz = new_len + BUFF_INCR;
+      null_hash_buff = new;
+    }
+
+  memcpy(null_hash_buff + ctx->len, src, length);
+  ctx->len += length;
+}
+ 
+static void null_hash_digest(void *ctx, size_t length, uint8_t *dst)
+{
+  (void)length;
+  
+  ((struct null_hash_digest *)dst)->buff = null_hash_buff;
+  ((struct null_hash_digest *)dst)->len = ((struct null_hash_ctx *)ctx)->len;
+}
+
+static struct nettle_hash null_hash = {
+  "null_hash",
+  sizeof(struct null_hash_ctx),
+  sizeof(struct null_hash_digest),
+  0,
+  (nettle_hash_init_func *) null_hash_init,
+  (nettle_hash_update_func *) null_hash_update,
+  (nettle_hash_digest_func *) null_hash_digest
+};
+
+#endif /* MIN_VERSION(3, 1) */
+
+/* expand ctx and digest memory allocations if necessary and init hash function */
+int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp)
+{
+  static void *ctx = NULL;
+  static unsigned char *digest = NULL;
+  static unsigned int ctx_sz = 0;
+  static unsigned int digest_sz = 0;
+
+  void *new;
+
+  if (ctx_sz < hash->context_size)
+    {
+      if (!(new = whine_malloc(hash->context_size)))
+	return 0;
+      if (ctx)
+	free(ctx);
+      ctx = new;
+      ctx_sz = hash->context_size;
+    }
+  
+  if (digest_sz < hash->digest_size)
+    {
+      if (!(new = whine_malloc(hash->digest_size)))
+	return 0;
+      if (digest)
+	free(digest);
+      digest = new;
+      digest_sz = hash->digest_size;
+    }
+
+  *ctxp = ctx;
+  *digestp = digest;
+
+  hash->init(ctx);
+
+  return 1;
+}
+
+static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+			      unsigned char *digest, size_t digest_len, int algo)
+{
+  unsigned char *p;
+  size_t exp_len;
+  
+  static struct rsa_public_key *key = NULL;
+  static mpz_t sig_mpz;
+
+  (void)digest_len;
+  
+  if (key == NULL)
+    {
+      if (!(key = whine_malloc(sizeof(struct rsa_public_key))))
+	return 0;
+      
+      nettle_rsa_public_key_init(key);
+      mpz_init(sig_mpz);
+    }
+  
+  if ((key_len < 3) || !(p = blockdata_retrieve(key_data, key_len, NULL)))
+    return 0;
+  
+  key_len--;
+  if ((exp_len = *p++) == 0)
+    {
+      GETSHORT(exp_len, p);
+      key_len -= 2;
+    }
+  
+  if (exp_len >= key_len)
+    return 0;
+  
+  key->size =  key_len - exp_len;
+  mpz_import(key->e, exp_len, 1, 1, 0, 0, p);
+  mpz_import(key->n, key->size, 1, 1, 0, 0, p + exp_len);
+
+  mpz_import(sig_mpz, sig_len, 1, 1, 0, 0, sig);
+  
+  switch (algo)
+    {
+    case 5: case 7:
+      return nettle_rsa_sha1_verify_digest(key, digest, sig_mpz);
+    case 8:
+      return nettle_rsa_sha256_verify_digest(key, digest, sig_mpz);
+    case 10:
+      return nettle_rsa_sha512_verify_digest(key, digest, sig_mpz);
+    }
+
+  return 0;
+}  
+
+static int dnsmasq_ecdsa_verify(struct blockdata *key_data, unsigned int key_len, 
+				unsigned char *sig, size_t sig_len,
+				unsigned char *digest, size_t digest_len, int algo)
+{
+  unsigned char *p;
+  unsigned int t;
+  struct ecc_point *key;
+
+  static struct ecc_point *key_256 = NULL, *key_384 = NULL;
+  static mpz_t x, y;
+  static struct dsa_signature *sig_struct;
+#if !MIN_VERSION(3, 4)
+#define nettle_get_secp_256r1() (&nettle_secp_256r1)
+#define nettle_get_secp_384r1() (&nettle_secp_384r1)
+#endif
+  
+  if (!sig_struct)
+    {
+      if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))))
+	return 0;
+      
+      nettle_dsa_signature_init(sig_struct);
+      mpz_init(x);
+      mpz_init(y);
+    }
+  
+  switch (algo)
+    {
+    case 13:
+      if (!key_256)
+	{
+	  if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))
+	    return 0;
+	  
+	  nettle_ecc_point_init(key_256, nettle_get_secp_256r1());
+	}
+      
+      key = key_256;
+      t = 32;
+      break;
+      
+    case 14:
+      if (!key_384)
+	{
+	  if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))
+	    return 0;
+	  
+	  nettle_ecc_point_init(key_384, nettle_get_secp_384r1());
+	}
+      
+      key = key_384;
+      t = 48;
+      break;
+        
+    default:
+      return 0;
+    }
+  
+  if (sig_len != 2*t || key_len != 2*t ||
+      !(p = blockdata_retrieve(key_data, key_len, NULL)))
+    return 0;
+  
+  mpz_import(x, t , 1, 1, 0, 0, p);
+  mpz_import(y, t , 1, 1, 0, 0, p + t);
+
+  if (!ecc_point_set(key, x, y))
+    return 0;
+  
+  mpz_import(sig_struct->r, t, 1, 1, 0, 0, sig);
+  mpz_import(sig_struct->s, t, 1, 1, 0, 0, sig + t);
+  
+  return nettle_ecdsa_verify(key, digest_len, digest, sig_struct);
+}
+
+#if MIN_VERSION(3, 6)
+static int dnsmasq_gostdsa_verify(struct blockdata *key_data, unsigned int key_len, 
+				  unsigned char *sig, size_t sig_len,
+				  unsigned char *digest, size_t digest_len, int algo)
+{
+  unsigned char *p;
+  
+  static struct ecc_point *gost_key = NULL;
+  static mpz_t x, y;
+  static struct dsa_signature *sig_struct;
+
+  if (algo != 12 ||
+      sig_len != 64 || key_len != 64 ||
+      !(p = blockdata_retrieve(key_data, key_len, NULL)))
+    return 0;
+  
+  if (!sig_struct)
+    {
+      if (!(sig_struct = whine_malloc(sizeof(struct dsa_signature))) ||
+	  !(gost_key = whine_malloc(sizeof(struct ecc_point))))
+	return 0;
+      
+      nettle_dsa_signature_init(sig_struct);
+      nettle_ecc_point_init(gost_key, nettle_get_gost_gc256b());
+      mpz_init(x);
+      mpz_init(y);
+    }
+    
+  mpz_import(x, 32 , 1, 1, 0, 0, p);
+  mpz_import(y, 32 , 1, 1, 0, 0, p + 32);
+
+  if (!ecc_point_set(gost_key, x, y))
+    return 0;
+  
+  mpz_import(sig_struct->r, 32, 1, 1, 0, 0, sig);
+  mpz_import(sig_struct->s, 32, 1, 1, 0, 0, sig + 32);
+  
+  return nettle_gostdsa_verify(gost_key, digest_len, digest, sig_struct);
+}
+#endif
+
+#if MIN_VERSION(3, 1)
+static int dnsmasq_eddsa_verify(struct blockdata *key_data, unsigned int key_len, 
+				unsigned char *sig, size_t sig_len,
+				unsigned char *digest, size_t digest_len, int algo)
+{
+  unsigned char *p;
+   
+  if (digest_len != sizeof(struct null_hash_digest) ||
+      !(p = blockdata_retrieve(key_data, key_len, NULL)))
+    return 0;
+  
+  /* The "digest" returned by the null_hash function is simply a struct null_hash_digest
+     which has a pointer to the actual data and a length, because the buffer
+     may need to be extended during "hashing". */
+  
+  switch (algo)
+    {
+    case 15:
+      if (key_len != ED25519_KEY_SIZE ||
+	  sig_len != ED25519_SIGNATURE_SIZE)
+	return 0;
+
+      return ed25519_sha512_verify(p,
+				   ((struct null_hash_digest *)digest)->len,
+				   ((struct null_hash_digest *)digest)->buff,
+				   sig);
+      
+#if MIN_VERSION(3, 6)
+    case 16:
+      if (key_len != ED448_KEY_SIZE ||
+	  sig_len != ED448_SIGNATURE_SIZE)
+	return 0;
+
+      return ed448_shake256_verify(p,
+				   ((struct null_hash_digest *)digest)->len,
+				   ((struct null_hash_digest *)digest)->buff,
+				   sig);
+#endif
+
+    }
+
+  return 0;
+}
+#endif
+
+static int (*verify_func(int algo))(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+			     unsigned char *digest, size_t digest_len, int algo)
+{
+    
+  /* Ensure at runtime that we have support for this digest */
+  if (!hash_find(algo_digest_name(algo)))
+    return NULL;
+  
+  /* This switch defines which sig algorithms we support, can't introspect Nettle for that. */
+  switch (algo)
+    {
+    case 5: case 7: case 8: case 10:
+      return dnsmasq_rsa_verify;
+
+#if MIN_VERSION(3, 6)
+    case 12:
+      return dnsmasq_gostdsa_verify;
+#endif
+      
+    case 13: case 14:
+      return dnsmasq_ecdsa_verify;
+      
+#if MIN_VERSION(3, 1)
+    case 15: case 16:
+      return dnsmasq_eddsa_verify;
+#endif
+    }
+  
+  return NULL;
+}
+
+int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+	   unsigned char *digest, size_t digest_len, int algo)
+{
+
+  int (*func)(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+	      unsigned char *digest, size_t digest_len, int algo);
+  
+  func = verify_func(algo);
+  
+  if (!func)
+    return 0;
+
+  return (*func)(key_data, key_len, sig, sig_len, digest, digest_len, algo);
+}
+
+/* Note the ds_digest_name(), algo_digest_name() and nsec3_digest_name()
+   define which algo numbers we support. If algo_digest_name() returns
+   non-NULL for an algorithm number, we assume that algorithm is 
+   supported by verify(). */
+
+/* http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */
+char *ds_digest_name(int digest)
+{
+  switch (digest)
+    {
+    case 1: return "sha1";
+    case 2: return "sha256";
+    case 3: return "gosthash94";
+    case 4: return "sha384";
+    default: return NULL;
+    }
+}
+ 
+/* http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */
+char *algo_digest_name(int algo)
+{
+  switch (algo)
+    {
+    case 1: return NULL;          /* RSA/MD5 - Must Not Implement.  RFC 6944 para 2.3. */
+    case 2: return NULL;          /* Diffie-Hellman */
+    case 3: return NULL; ;        /* DSA/SHA1 - Must Not Implement. RFC 8624 section 3.1 */ 
+    case 5: return "sha1";        /* RSA/SHA1 */
+    case 6: return NULL;          /* DSA-NSEC3-SHA1 - Must Not Implement. RFC 8624 section 3.1 */
+    case 7: return "sha1";        /* RSASHA1-NSEC3-SHA1 */
+    case 8: return "sha256";      /* RSA/SHA-256 */
+    case 10: return "sha512";     /* RSA/SHA-512 */
+    case 12: return "gosthash94"; /* ECC-GOST */
+    case 13: return "sha256";     /* ECDSAP256SHA256 */
+    case 14: return "sha384";     /* ECDSAP384SHA384 */ 	
+    case 15: return "null_hash";  /* ED25519 */
+    case 16: return "null_hash";  /* ED448 */
+    default: return NULL;
+    }
+}
+  
+/* http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */
+char *nsec3_digest_name(int digest)
+{
+  switch (digest)
+    {
+    case 1: return "sha1";
+    default: return NULL;
+    }
+}
+
+#endif /* defined(HAVE_DNSSEC) */
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+/* Find pointer to correct hash function in nettle library */
+const struct nettle_hash *hash_find(char *name)
+{
+  if (!name)
+    return NULL;
+  
+#if MIN_VERSION(3,1) && defined(HAVE_DNSSEC)
+  /* We provide a "null" hash which returns the input data as digest. */
+  if (strcmp(null_hash.name, name) == 0)
+    return &null_hash;
+#endif
+  
+  /* libnettle >= 3.4 provides nettle_lookup_hash() which avoids nasty ABI
+     incompatibilities if sizeof(nettle_hashes) changes between library
+     versions. */
+#if MIN_VERSION(3, 4)
+  return nettle_lookup_hash(name);
+#else
+  {
+    int i;
+
+    for (i = 0; nettle_hashes[i]; i++)
+      if (strcmp(nettle_hashes[i]->name, name) == 0)
+	return nettle_hashes[i];
+  }
+  
+  return NULL;
+#endif
+}
+
+#endif /* defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH) */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c
new file mode 100755
index 0000000..cbdce9c
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dbus.c
@@ -0,0 +1,880 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DBUS
+
+#include <dbus/dbus.h>
+
+const char* introspection_xml_template =
+"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
+"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
+"<node name=\"" DNSMASQ_PATH "\">\n"
+"  <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
+"    <method name=\"Introspect\">\n"
+"      <arg name=\"data\" direction=\"out\" type=\"s\"/>\n"
+"    </method>\n"
+"  </interface>\n"
+"  <interface name=\"%s\">\n"
+"    <method name=\"ClearCache\">\n"
+"    </method>\n"
+"    <method name=\"GetVersion\">\n"
+"      <arg name=\"version\" direction=\"out\" type=\"s\"/>\n"
+"    </method>\n"
+#ifdef HAVE_LOOP
+"    <method name=\"GetLoopServers\">\n"
+"      <arg name=\"server\" direction=\"out\" type=\"as\"/>\n"
+"    </method>\n"
+#endif
+"    <method name=\"SetServers\">\n"
+"      <arg name=\"servers\" direction=\"in\" type=\"av\"/>\n"
+"    </method>\n"
+"    <method name=\"SetDomainServers\">\n"
+"      <arg name=\"servers\" direction=\"in\" type=\"as\"/>\n"
+"    </method>\n"
+"    <method name=\"SetServersEx\">\n"
+"      <arg name=\"servers\" direction=\"in\" type=\"aas\"/>\n"
+"    </method>\n"
+"    <method name=\"SetFilterWin2KOption\">\n"
+"      <arg name=\"filterwin2k\" direction=\"in\" type=\"b\"/>\n"
+"    </method>\n"
+"    <method name=\"SetBogusPrivOption\">\n"
+"      <arg name=\"boguspriv\" direction=\"in\" type=\"b\"/>\n"
+"    </method>\n"
+"    <signal name=\"DhcpLeaseAdded\">\n"
+"      <arg name=\"ipaddr\" type=\"s\"/>\n"
+"      <arg name=\"hwaddr\" type=\"s\"/>\n"
+"      <arg name=\"hostname\" type=\"s\"/>\n"
+"    </signal>\n"
+"    <signal name=\"DhcpLeaseDeleted\">\n"
+"      <arg name=\"ipaddr\" type=\"s\"/>\n"
+"      <arg name=\"hwaddr\" type=\"s\"/>\n"
+"      <arg name=\"hostname\" type=\"s\"/>\n"
+"    </signal>\n"
+"    <signal name=\"DhcpLeaseUpdated\">\n"
+"      <arg name=\"ipaddr\" type=\"s\"/>\n"
+"      <arg name=\"hwaddr\" type=\"s\"/>\n"
+"      <arg name=\"hostname\" type=\"s\"/>\n"
+"    </signal>\n"
+#ifdef HAVE_DHCP
+"    <method name=\"AddDhcpLease\">\n"
+"       <arg name=\"ipaddr\" type=\"s\"/>\n"
+"       <arg name=\"hwaddr\" type=\"s\"/>\n"
+"       <arg name=\"hostname\" type=\"ay\"/>\n"
+"       <arg name=\"clid\" type=\"ay\"/>\n"
+"       <arg name=\"lease_duration\" type=\"u\"/>\n"
+"       <arg name=\"ia_id\" type=\"u\"/>\n"
+"       <arg name=\"is_temporary\" type=\"b\"/>\n"
+"    </method>\n"
+"    <method name=\"DeleteDhcpLease\">\n"
+"       <arg name=\"ipaddr\" type=\"s\"/>\n"
+"       <arg name=\"success\" type=\"b\" direction=\"out\"/>\n"
+"    </method>\n"
+#endif
+"    <method name=\"GetMetrics\">\n"
+"      <arg name=\"metrics\" direction=\"out\" type=\"a{su}\"/>\n"
+"    </method>\n"
+"  </interface>\n"
+"</node>\n";
+
+static char *introspection_xml = NULL;
+
+struct watch {
+  DBusWatch *watch;      
+  struct watch *next;
+};
+
+
+static dbus_bool_t add_watch(DBusWatch *watch, void *data)
+{
+  struct watch *w;
+
+  for (w = daemon->watches; w; w = w->next)
+    if (w->watch == watch)
+      return TRUE;
+
+  if (!(w = whine_malloc(sizeof(struct watch))))
+    return FALSE;
+
+  w->watch = watch;
+  w->next = daemon->watches;
+  daemon->watches = w;
+
+  w = data; /* no warning */
+  return TRUE;
+}
+
+static void remove_watch(DBusWatch *watch, void *data)
+{
+  struct watch **up, *w, *tmp;  
+  
+  for (up = &(daemon->watches), w = daemon->watches; w; w = tmp)
+    {
+      tmp = w->next;
+      if (w->watch == watch)
+	{
+	  *up = tmp;
+	  free(w);
+	}
+      else
+	up = &(w->next);
+    }
+
+  w = data; /* no warning */
+}
+
+static void dbus_read_servers(DBusMessage *message)
+{
+  DBusMessageIter iter;
+  union  mysockaddr addr, source_addr;
+  char *domain;
+  
+  dbus_message_iter_init(message, &iter);
+
+  mark_servers(SERV_FROM_DBUS);
+  
+  while (1)
+    {
+      int skip = 0;
+
+      if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_UINT32)
+	{
+	  u32 a;
+	  
+	  dbus_message_iter_get_basic(&iter, &a);
+	  dbus_message_iter_next (&iter);
+	  
+#ifdef HAVE_SOCKADDR_SA_LEN
+	  source_addr.in.sin_len = addr.in.sin_len = sizeof(struct sockaddr_in);
+#endif
+	  addr.in.sin_addr.s_addr = ntohl(a);
+	  source_addr.in.sin_family = addr.in.sin_family = AF_INET;
+	  addr.in.sin_port = htons(NAMESERVER_PORT);
+	  source_addr.in.sin_addr.s_addr = INADDR_ANY;
+	  source_addr.in.sin_port = htons(daemon->query_port);
+	}
+      else if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_BYTE)
+	{
+	  unsigned char p[sizeof(struct in6_addr)];
+	  unsigned int i;
+
+	  skip = 1;
+
+	  for(i = 0; i < sizeof(struct in6_addr); i++)
+	    {
+	      dbus_message_iter_get_basic(&iter, &p[i]);
+	      dbus_message_iter_next (&iter);
+	      if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BYTE)
+		{
+		  i++;
+		  break;
+		}
+	    }
+
+	  if (i == sizeof(struct in6_addr))
+	    {
+	      memcpy(&addr.in6.sin6_addr, p, sizeof(struct in6_addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+              source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+              source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6;
+              addr.in6.sin6_port = htons(NAMESERVER_PORT);
+              source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0;
+	      source_addr.in6.sin6_scope_id = addr.in6.sin6_scope_id = 0;
+              source_addr.in6.sin6_addr = in6addr_any;
+              source_addr.in6.sin6_port = htons(daemon->query_port);
+	      skip = 0;
+	    }
+	}
+      else
+	/* At the end */
+	break;
+      
+      /* process each domain */
+      do {
+	if (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING)
+	  {
+	    dbus_message_iter_get_basic(&iter, &domain);
+	    dbus_message_iter_next (&iter);
+	  }
+	else
+	  domain = NULL;
+	
+	if (!skip)
+	  add_update_server(SERV_FROM_DBUS, &addr, &source_addr, NULL, domain, NULL);
+     
+      } while (dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); 
+    }
+   
+  /* unlink and free anything still marked. */
+  cleanup_servers();
+}
+
+#ifdef HAVE_LOOP
+static DBusMessage *dbus_reply_server_loop(DBusMessage *message)
+{
+  DBusMessageIter args, args_iter;
+  struct server *serv;
+  DBusMessage *reply = dbus_message_new_method_return(message);
+   
+  dbus_message_iter_init_append (reply, &args);
+  dbus_message_iter_open_container (&args, DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING, &args_iter);
+
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (serv->flags & SERV_LOOP)
+      {
+	(void)prettyprint_addr(&serv->addr, daemon->addrbuff);
+	dbus_message_iter_append_basic (&args_iter, DBUS_TYPE_STRING, &daemon->addrbuff);
+      }
+  
+  dbus_message_iter_close_container (&args, &args_iter);
+
+  return reply;
+}
+#endif
+
+static DBusMessage* dbus_read_servers_ex(DBusMessage *message, int strings)
+{
+  DBusMessageIter iter, array_iter, string_iter;
+  DBusMessage *error = NULL;
+  const char *addr_err;
+  char *dup = NULL;
+  
+  if (!dbus_message_iter_init(message, &iter))
+    {
+      return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+                                    "Failed to initialize dbus message iter");
+    }
+
+  /* check that the message contains an array of arrays */
+  if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+      (dbus_message_iter_get_element_type(&iter) != (strings ? DBUS_TYPE_STRING : DBUS_TYPE_ARRAY)))
+    {
+      return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+                                    strings ? "Expected array of string" : "Expected array of string arrays");
+     }
+ 
+  mark_servers(SERV_FROM_DBUS);
+
+  /* array_iter points to each "as" element in the outer array */
+  dbus_message_iter_recurse(&iter, &array_iter);
+  while (dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID)
+    {
+      const char *str = NULL;
+      union  mysockaddr addr, source_addr;
+      u16 flags = 0;
+      char interface[IF_NAMESIZE];
+      char *str_addr, *str_domain = NULL;
+
+      if (strings)
+	{
+	  dbus_message_iter_get_basic(&array_iter, &str);
+	  if (!str || !strlen (str))
+	    {
+	      error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+					     "Empty string");
+	      break;
+	    }
+	  
+	  /* dup the string because it gets modified during parsing */
+	  if (dup)
+	    free(dup);
+	  if (!(dup = str_domain = whine_malloc(strlen(str)+1)))
+	    break;
+	  
+	  strcpy(str_domain, str);
+
+	  /* point to address part of old string for error message */
+	  if ((str_addr = strrchr(str, '/')))
+	    str = str_addr+1;
+	  
+	  if ((str_addr = strrchr(str_domain, '/')))
+	    {
+	      if (*str_domain != '/' || str_addr == str_domain)
+		{
+		  error = dbus_message_new_error_printf(message,
+							DBUS_ERROR_INVALID_ARGS,
+							"No domain terminator '%s'",
+							str);
+		  break;
+		}
+	      *str_addr++ = 0;
+	      str_domain++;
+	    }
+	  else
+	    {
+	      str_addr = str_domain;
+	      str_domain = NULL;
+	    }
+
+	  
+	}
+      else
+	{
+	  /* check the types of the struct and its elements */
+	  if ((dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_ARRAY) ||
+	      (dbus_message_iter_get_element_type(&array_iter) != DBUS_TYPE_STRING))
+	    {
+	      error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+					     "Expected inner array of strings");
+	      break;
+	    }
+	  
+	  /* string_iter points to each "s" element in the inner array */
+	  dbus_message_iter_recurse(&array_iter, &string_iter);
+	  if (dbus_message_iter_get_arg_type(&string_iter) != DBUS_TYPE_STRING)
+	    {
+	      /* no IP address given */
+	      error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+					     "Expected IP address");
+	      break;
+	    }
+	  
+	  dbus_message_iter_get_basic(&string_iter, &str);
+	  if (!str || !strlen (str))
+	    {
+	      error = dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+					     "Empty IP address");
+	      break;
+	    }
+	  
+	  /* dup the string because it gets modified during parsing */
+	  if (dup)
+	    free(dup);
+	  if (!(dup = str_addr = whine_malloc(strlen(str)+1)))
+	    break;
+	  
+	  strcpy(str_addr, str);
+	}
+
+      /* parse the IP address */
+      if ((addr_err = parse_server(str_addr, &addr, &source_addr, (char *) &interface, &flags)))
+	{
+          error = dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+                                                "Invalid IP address '%s': %s",
+                                                str, addr_err);
+          break;
+        }
+      
+      /* 0.0.0.0 for server address == NULL, for Dbus */
+      if (addr.in.sin_family == AF_INET &&
+          addr.in.sin_addr.s_addr == 0)
+        flags |= SERV_LITERAL_ADDRESS;
+      
+      if (strings)
+	{
+	  char *p;
+	  
+	  do {
+	    if (str_domain)
+	      {
+		if ((p = strchr(str_domain, '/')))
+		  *p++ = 0;
+	      }
+	    else 
+	      p = NULL;
+	    
+	    add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str_domain, NULL);
+	  } while ((str_domain = p));
+	}
+      else
+	{
+	  /* jump past the address to the domain list (if any) */
+	  dbus_message_iter_next (&string_iter);
+	  
+	  /* parse domains and add each server/domain pair to the list */
+	  do {
+	    str = NULL;
+	    if (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING)
+	      dbus_message_iter_get_basic(&string_iter, &str);
+	    dbus_message_iter_next (&string_iter);
+	    
+	    add_update_server(flags | SERV_FROM_DBUS, &addr, &source_addr, interface, str, NULL);
+	  } while (dbus_message_iter_get_arg_type(&string_iter) == DBUS_TYPE_STRING);
+	}
+	 
+      /* jump to next element in outer array */
+      dbus_message_iter_next(&array_iter);
+    }
+
+  cleanup_servers();
+    
+  if (dup)
+    free(dup);
+
+  return error;
+}
+
+static DBusMessage *dbus_set_bool(DBusMessage *message, int flag, char *name)
+{
+  DBusMessageIter iter;
+  dbus_bool_t enabled;
+
+  if (!dbus_message_iter_init(message, &iter) || dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS, "Expected boolean argument");
+  
+  dbus_message_iter_get_basic(&iter, &enabled);
+
+  if (enabled)
+    { 
+      my_syslog(LOG_INFO, _("Enabling --%s option from D-Bus"), name);
+      set_option_bool(flag);
+    }
+  else
+    {
+      my_syslog(LOG_INFO, _("Disabling --%s option from D-Bus"), name);
+      reset_option_bool(flag);
+    }
+
+  return NULL;
+}
+
+#ifdef HAVE_DHCP
+static DBusMessage *dbus_add_lease(DBusMessage* message)
+{
+  struct dhcp_lease *lease;
+  const char *ipaddr, *hwaddr, *hostname, *tmp;
+  const unsigned char* clid;
+  int clid_len, hostname_len, hw_len, hw_type;
+  dbus_uint32_t expires, ia_id;
+  dbus_bool_t is_temporary;
+  union all_addr addr;
+  time_t now = dnsmasq_time();
+  unsigned char dhcp_chaddr[DHCP_CHADDR_MAX];
+
+  DBusMessageIter iter, array_iter;
+  if (!dbus_message_iter_init(message, &iter))
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Failed to initialize dbus message iter");
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected string as first argument");
+
+  dbus_message_iter_get_basic(&iter, &ipaddr);
+  dbus_message_iter_next(&iter);
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected string as second argument");
+    
+  dbus_message_iter_get_basic(&iter, &hwaddr);
+  dbus_message_iter_next(&iter);
+
+  if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+      (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE))
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected byte array as third argument");
+    
+  dbus_message_iter_recurse(&iter, &array_iter);
+  dbus_message_iter_get_fixed_array(&array_iter, &hostname, &hostname_len);
+  tmp = memchr(hostname, '\0', hostname_len);
+  if (tmp)
+    {
+      if (tmp == &hostname[hostname_len - 1])
+	hostname_len--;
+      else
+	return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				      "Hostname contains an embedded NUL character");
+    }
+  dbus_message_iter_next(&iter);
+
+  if ((dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) ||
+      (dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_BYTE))
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected byte array as fourth argument");
+
+  dbus_message_iter_recurse(&iter, &array_iter);
+  dbus_message_iter_get_fixed_array(&array_iter, &clid, &clid_len);
+  dbus_message_iter_next(&iter);
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected uint32 as fifth argument");
+    
+  dbus_message_iter_get_basic(&iter, &expires);
+  dbus_message_iter_next(&iter);
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+                                    "Expected uint32 as sixth argument");
+  
+  dbus_message_iter_get_basic(&iter, &ia_id);
+  dbus_message_iter_next(&iter);
+
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_BOOLEAN)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected uint32 as sixth argument");
+
+  dbus_message_iter_get_basic(&iter, &is_temporary);
+
+  if (inet_pton(AF_INET, ipaddr, &addr.addr4))
+    {
+      if (ia_id != 0 || is_temporary)
+	return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				      "ia_id and is_temporary must be zero for IPv4 lease");
+      
+      if (!(lease = lease_find_by_addr(addr.addr4)))
+    	lease = lease4_allocate(addr.addr4);
+    }
+#ifdef HAVE_DHCP6
+  else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
+    {
+      if (!(lease = lease6_find_by_addr(&addr.addr6, 128, 0)))
+	lease = lease6_allocate(&addr.addr6,
+				is_temporary ? LEASE_TA : LEASE_NA);
+      lease_set_iaid(lease, ia_id);
+    }
+#endif
+  else
+    return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+					 "Invalid IP address '%s'", ipaddr);
+   
+  hw_len = parse_hex((char*)hwaddr, dhcp_chaddr, DHCP_CHADDR_MAX, NULL, &hw_type);
+  if (hw_type == 0 && hw_len != 0)
+    hw_type = ARPHRD_ETHER;
+  
+  lease_set_hwaddr(lease, dhcp_chaddr, clid, hw_len, hw_type,
+                   clid_len, now, 0);
+  lease_set_expires(lease, expires, now);
+  if (hostname_len != 0)
+    lease_set_hostname(lease, hostname, 0, get_domain(lease->addr), NULL);
+  
+  lease_update_file(now);
+  lease_update_dns(0);
+
+  return NULL;
+}
+
+static DBusMessage *dbus_del_lease(DBusMessage* message)
+{
+  struct dhcp_lease *lease;
+  DBusMessageIter iter;
+  const char *ipaddr;
+  DBusMessage *reply;
+  union all_addr addr;
+  dbus_bool_t ret = 1;
+  time_t now = dnsmasq_time();
+
+  if (!dbus_message_iter_init(message, &iter))
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Failed to initialize dbus message iter");
+   
+  if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+    return dbus_message_new_error(message, DBUS_ERROR_INVALID_ARGS,
+				  "Expected string as first argument");
+   
+  dbus_message_iter_get_basic(&iter, &ipaddr);
+
+  if (inet_pton(AF_INET, ipaddr, &addr.addr4))
+    lease = lease_find_by_addr(addr.addr4);
+#ifdef HAVE_DHCP6
+  else if (inet_pton(AF_INET6, ipaddr, &addr.addr6))
+    lease = lease6_find_by_addr(&addr.addr6, 128, 0);
+#endif
+  else
+    return dbus_message_new_error_printf(message, DBUS_ERROR_INVALID_ARGS,
+					 "Invalid IP address '%s'", ipaddr);
+    
+  if (lease)
+    {
+      lease_prune(lease, now);
+      lease_update_file(now);
+      lease_update_dns(0);
+    }
+  else
+    ret = 0;
+  
+  if ((reply = dbus_message_new_method_return(message)))
+    dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &ret,
+			     DBUS_TYPE_INVALID);
+  
+    
+  return reply;
+}
+#endif
+
+static DBusMessage *dbus_get_metrics(DBusMessage* message)
+{
+  DBusMessage *reply = dbus_message_new_method_return(message);
+  DBusMessageIter array, dict, iter;
+  int i;
+
+  dbus_message_iter_init_append(reply, &iter);
+  dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{su}", &array);
+
+  for (i = 0; i < __METRIC_MAX; i++) {
+    const char *key     = get_metric_name(i);
+    dbus_uint32_t value = daemon->metrics[i];
+
+    dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict);
+    dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &key);
+    dbus_message_iter_append_basic(&dict, DBUS_TYPE_UINT32, &value);
+    dbus_message_iter_close_container(&array, &dict);
+  }
+
+  dbus_message_iter_close_container(&iter, &array);
+
+  return reply;
+}
+
+DBusHandlerResult message_handler(DBusConnection *connection, 
+				  DBusMessage *message, 
+				  void *user_data)
+{
+  char *method = (char *)dbus_message_get_member(message);
+  DBusMessage *reply = NULL;
+  int clear_cache = 0, new_servers = 0;
+    
+  if (dbus_message_is_method_call(message, DBUS_INTERFACE_INTROSPECTABLE, "Introspect"))
+    {
+      /* string length: "%s" provides space for termination zero */
+      if (!introspection_xml && 
+	  (introspection_xml = whine_malloc(strlen(introspection_xml_template) + strlen(daemon->dbus_name))))
+	sprintf(introspection_xml, introspection_xml_template, daemon->dbus_name);
+    
+      if (introspection_xml)
+	{
+	  reply = dbus_message_new_method_return(message);
+	  dbus_message_append_args(reply, DBUS_TYPE_STRING, &introspection_xml, DBUS_TYPE_INVALID);
+	}
+    }
+  else if (strcmp(method, "GetVersion") == 0)
+    {
+      char *v = VERSION;
+      reply = dbus_message_new_method_return(message);
+      
+      dbus_message_append_args(reply, DBUS_TYPE_STRING, &v, DBUS_TYPE_INVALID);
+    }
+#ifdef HAVE_LOOP
+  else if (strcmp(method, "GetLoopServers") == 0)
+    {
+      reply = dbus_reply_server_loop(message);
+    }
+#endif
+  else if (strcmp(method, "SetServers") == 0)
+    {
+      dbus_read_servers(message);
+      new_servers = 1;
+    }
+  else if (strcmp(method, "SetServersEx") == 0)
+    {
+      reply = dbus_read_servers_ex(message, 0);
+      new_servers = 1;
+    }
+  else if (strcmp(method, "SetDomainServers") == 0)
+    {
+      reply = dbus_read_servers_ex(message, 1);
+      new_servers = 1;
+    }
+  else if (strcmp(method, "SetFilterWin2KOption") == 0)
+    {
+      reply = dbus_set_bool(message, OPT_FILTER, "filterwin2k");
+    }
+  else if (strcmp(method, "SetBogusPrivOption") == 0)
+    {
+      reply = dbus_set_bool(message, OPT_BOGUSPRIV, "bogus-priv");
+    }
+#ifdef HAVE_DHCP
+  else if (strcmp(method, "AddDhcpLease") == 0)
+    {
+      reply = dbus_add_lease(message);
+    }
+  else if (strcmp(method, "DeleteDhcpLease") == 0)
+    {
+      reply = dbus_del_lease(message);
+    }
+#endif
+  else if (strcmp(method, "GetMetrics") == 0)
+    {
+      reply = dbus_get_metrics(message);
+    }
+  else if (strcmp(method, "ClearCache") == 0)
+    clear_cache = 1;
+  else
+    return (DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
+   
+  if (new_servers)
+    {
+      my_syslog(LOG_INFO, _("setting upstream servers from DBus"));
+      check_servers(0);
+      if (option_bool(OPT_RELOAD))
+	clear_cache = 1;
+    }
+
+  if (clear_cache)
+    clear_cache_and_reload(dnsmasq_time());
+  
+  method = user_data; /* no warning */
+
+  /* If no reply or no error, return nothing */
+  if (!reply)
+    reply = dbus_message_new_method_return(message);
+
+  if (reply)
+    {
+      dbus_connection_send (connection, reply, NULL);
+      dbus_message_unref (reply);
+    }
+
+  return (DBUS_HANDLER_RESULT_HANDLED);
+}
+ 
+
+/* returns NULL or error message, may fail silently if dbus daemon not yet up. */
+char *dbus_init(void)
+{
+  DBusConnection *connection = NULL;
+  DBusObjectPathVTable dnsmasq_vtable = {NULL, &message_handler, NULL, NULL, NULL, NULL };
+  DBusError dbus_error;
+  DBusMessage *message;
+
+  dbus_error_init (&dbus_error);
+  if (!(connection = dbus_bus_get (DBUS_BUS_SYSTEM, &dbus_error)))
+    return NULL;
+    
+  dbus_connection_set_exit_on_disconnect(connection, FALSE);
+  dbus_connection_set_watch_functions(connection, add_watch, remove_watch, 
+				      NULL, NULL, NULL);
+  dbus_error_init (&dbus_error);
+  dbus_bus_request_name (connection, daemon->dbus_name, 0, &dbus_error);
+  if (dbus_error_is_set (&dbus_error))
+    return (char *)dbus_error.message;
+  
+  if (!dbus_connection_register_object_path(connection,  DNSMASQ_PATH, 
+					    &dnsmasq_vtable, NULL))
+    return _("could not register a DBus message handler");
+  
+  daemon->dbus = connection; 
+  
+  if ((message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, "Up")))
+    {
+      dbus_connection_send(connection, message, NULL);
+      dbus_message_unref(message);
+    }
+
+  return NULL;
+}
+ 
+
+void set_dbus_listeners(void)
+{
+  struct watch *w;
+  
+  for (w = daemon->watches; w; w = w->next)
+    if (dbus_watch_get_enabled(w->watch))
+      {
+	unsigned int flags = dbus_watch_get_flags(w->watch);
+	int fd = dbus_watch_get_unix_fd(w->watch);
+	
+	if (flags & DBUS_WATCH_READABLE)
+	  poll_listen(fd, POLLIN);
+	
+	if (flags & DBUS_WATCH_WRITABLE)
+	  poll_listen(fd, POLLOUT);
+	
+	poll_listen(fd, POLLERR);
+      }
+}
+
+void check_dbus_listeners()
+{
+  DBusConnection *connection = (DBusConnection *)daemon->dbus;
+  struct watch *w;
+
+  for (w = daemon->watches; w; w = w->next)
+    if (dbus_watch_get_enabled(w->watch))
+      {
+	unsigned int flags = 0;
+	int fd = dbus_watch_get_unix_fd(w->watch);
+	
+	if (poll_check(fd, POLLIN))
+	  flags |= DBUS_WATCH_READABLE;
+	
+	if (poll_check(fd, POLLOUT))
+	  flags |= DBUS_WATCH_WRITABLE;
+	
+	if (poll_check(fd, POLLERR))
+	  flags |= DBUS_WATCH_ERROR;
+
+	if (flags != 0)
+	  dbus_watch_handle(w->watch, flags);
+      }
+
+  if (connection)
+    {
+      dbus_connection_ref (connection);
+      while (dbus_connection_dispatch (connection) == DBUS_DISPATCH_DATA_REMAINS);
+      dbus_connection_unref (connection);
+    }
+}
+
+#ifdef HAVE_DHCP
+void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname)
+{
+  DBusConnection *connection = (DBusConnection *)daemon->dbus;
+  DBusMessage* message = NULL;
+  DBusMessageIter args;
+  char *action_str, *mac = daemon->namebuff;
+  unsigned char *p;
+  int i;
+
+  if (!connection)
+    return;
+  
+  if (!hostname)
+    hostname = "";
+  
+#ifdef HAVE_DHCP6
+   if (lease->flags & (LEASE_TA | LEASE_NA))
+     {
+       print_mac(mac, lease->clid, lease->clid_len);
+       inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+     }
+   else
+#endif
+     {
+       p = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len,
+			   lease->hwaddr, lease->clid_len, lease->clid, &i);
+       print_mac(mac, p, i);
+       inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
+     }
+
+  if (action == ACTION_DEL)
+    action_str = "DhcpLeaseDeleted";
+  else if (action == ACTION_ADD)
+    action_str = "DhcpLeaseAdded";
+  else if (action == ACTION_OLD)
+    action_str = "DhcpLeaseUpdated";
+  else
+    return;
+
+  if (!(message = dbus_message_new_signal(DNSMASQ_PATH, daemon->dbus_name, action_str)))
+    return;
+  
+  dbus_message_iter_init_append(message, &args);
+  
+  if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &daemon->addrbuff) &&
+      dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &mac) &&
+      dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &hostname))
+    dbus_connection_send(connection, message, NULL);
+  
+  dbus_message_unref(message);
+}
+#endif
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c
new file mode 100755
index 0000000..1f91ca0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-common.c
@@ -0,0 +1,997 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP
+
+void dhcp_common_init(void)
+{
+  /* These each hold a DHCP option max size 255
+     and get a terminating zero added */
+  daemon->dhcp_buff = safe_malloc(DHCP_BUFF_SZ);
+  daemon->dhcp_buff2 = safe_malloc(DHCP_BUFF_SZ); 
+  daemon->dhcp_buff3 = safe_malloc(DHCP_BUFF_SZ);
+  
+  /* dhcp_packet is used by v4 and v6, outpacket only by v6 
+     sizeof(struct dhcp_packet) is as good an initial size as any,
+     even for v6 */
+  expand_buf(&daemon->dhcp_packet, sizeof(struct dhcp_packet));
+#ifdef HAVE_DHCP6
+  if (daemon->dhcp6)
+    expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet));
+#endif
+}
+
+ssize_t recv_dhcp_packet(int fd, struct msghdr *msg)
+{  
+  ssize_t sz, new_sz;
+ 
+  while (1)
+    {
+      msg->msg_flags = 0;
+      while ((sz = recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR);
+      
+      if (sz == -1)
+	return -1;
+      
+      if (!(msg->msg_flags & MSG_TRUNC))
+	break;
+
+      /* Very new Linux kernels return the actual size needed, 
+	 older ones always return truncated size */
+      if ((size_t)sz == msg->msg_iov->iov_len)
+	{
+	  if (!expand_buf(msg->msg_iov, sz + 100))
+	    return -1;
+	}
+      else
+	{
+	  expand_buf(msg->msg_iov, sz);
+	  break;
+	}
+    }
+  
+  while ((new_sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR);
+
+  /* Some kernels seem to ignore MSG_PEEK, and dequeue the packet anyway. 
+     If that happens we get EAGAIN here because the socket is non-blocking.
+     Use the result of the original testing recvmsg as long as the buffer
+     was big enough. There's a small race here that may lose the odd packet,
+     but it's UDP anyway. */
+  
+  if (new_sz == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
+    new_sz = sz;
+  
+  return (msg->msg_flags & MSG_TRUNC) ? -1 : new_sz;
+}
+
+/* like match_netid() except that the check can have a trailing * for wildcard */
+/* started as a direct copy of match_netid() */
+int match_netid_wild(struct dhcp_netid *check, struct dhcp_netid *pool)
+{
+  struct dhcp_netid *tmp1;
+  
+  for (; check; check = check->next)
+    {
+      const int check_len = strlen(check->net);
+      const int is_wc = (check->net[check_len - 1] == '*');
+      
+      /* '#' for not is for backwards compat. */
+      if (check->net[0] != '!' && check->net[0] != '#')
+	{
+	  for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+	    if (is_wc ? (strncmp(check->net, tmp1->net, check_len-1) == 0) :
+		(strcmp(check->net, tmp1->net) == 0))
+	      break;
+	  if (!tmp1)
+	    return 0;
+	}
+      else
+	for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+	  if (is_wc ? (strncmp((check->net)+1, tmp1->net, check_len-2) == 0) :
+	      (strcmp((check->net)+1, tmp1->net) == 0))
+	    return 0;
+    }
+  return 1;
+}
+
+struct dhcp_netid *run_tag_if(struct dhcp_netid *tags)
+{
+  struct tag_if *exprs;
+  struct dhcp_netid_list *list;
+
+  /* this now uses match_netid_wild() above so that tag_if can
+   * be used to set a 'group of interfaces' tag.
+   */
+  for (exprs = daemon->tag_if; exprs; exprs = exprs->next)
+    if (match_netid_wild(exprs->tag, tags))
+      for (list = exprs->set; list; list = list->next)
+	{
+	  list->list->next = tags;
+	  tags = list->list;
+	}
+
+  return tags;
+}
+
+
+struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags, struct dhcp_opt *opts)
+{
+  struct dhcp_netid *tagif = run_tag_if(tags);
+  struct dhcp_opt *opt;
+  struct dhcp_opt *tmp;  
+
+  /* flag options which are valid with the current tag set (sans context tags) */
+  for (opt = opts; opt; opt = opt->next)
+    {
+      opt->flags &= ~DHOPT_TAGOK;
+      if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) &&
+	  match_netid(opt->netid, tagif, 0))
+	opt->flags |= DHOPT_TAGOK;
+    }
+
+  /* now flag options which are valid, including the context tags,
+     otherwise valid options are inhibited if we found a higher priority one above */
+  if (context_tags)
+    {
+      struct dhcp_netid *last_tag;
+
+      for (last_tag = context_tags; last_tag->next; last_tag = last_tag->next);
+      last_tag->next = tags;
+      tagif = run_tag_if(context_tags);
+      
+      /* reset stuff with tag:!<tag> which now matches. */
+      for (opt = opts; opt; opt = opt->next)
+	if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) &&
+	    (opt->flags & DHOPT_TAGOK) &&
+	    !match_netid(opt->netid, tagif, 0))
+	  opt->flags &= ~DHOPT_TAGOK;
+
+      for (opt = opts; opt; opt = opt->next)
+	if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) &&
+	    match_netid(opt->netid, tagif, 0))
+	  {
+	    struct dhcp_opt *tmp;  
+	    for (tmp = opts; tmp; tmp = tmp->next) 
+	      if (tmp->opt == opt->opt && opt->netid && (tmp->flags & DHOPT_TAGOK))
+		break;
+	    if (!tmp)
+	      opt->flags |= DHOPT_TAGOK;
+	  }      
+    }
+  
+  /* now flag untagged options which are not overridden by tagged ones */
+  for (opt = opts; opt; opt = opt->next)
+    if (!(opt->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925 | DHOPT_TAGOK)) && !opt->netid)
+      {
+	for (tmp = opts; tmp; tmp = tmp->next) 
+	  if (tmp->opt == opt->opt && (tmp->flags & DHOPT_TAGOK))
+	    break;
+	if (!tmp)
+	  opt->flags |= DHOPT_TAGOK;
+	else if (!tmp->netid)
+	  my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring duplicate dhcp-option %d"), tmp->opt); 
+      }
+
+  /* Finally, eliminate duplicate options later in the chain, and therefore earlier in the config file. */
+  for (opt = opts; opt; opt = opt->next)
+    if (opt->flags & DHOPT_TAGOK)
+      for (tmp = opt->next; tmp; tmp = tmp->next) 
+	if (tmp->opt == opt->opt)
+	  tmp->flags &= ~DHOPT_TAGOK;
+  
+  return tagif;
+}
+	
+/* Is every member of check matched by a member of pool? 
+   If tagnotneeded, untagged is OK */
+int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded)
+{
+  struct dhcp_netid *tmp1;
+  
+  if (!check && !tagnotneeded)
+    return 0;
+
+  for (; check; check = check->next)
+    {
+      /* '#' for not is for backwards compat. */
+      if (check->net[0] != '!' && check->net[0] != '#')
+	{
+	  for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+	    if (strcmp(check->net, tmp1->net) == 0)
+	      break;
+	  if (!tmp1)
+	    return 0;
+	}
+      else
+	for (tmp1 = pool; tmp1; tmp1 = tmp1->next)
+	  if (strcmp((check->net)+1, tmp1->net) == 0)
+	    return 0;
+    }
+  return 1;
+}
+
+/* return domain or NULL if none. */
+char *strip_hostname(char *hostname)
+{
+  char *dot = strchr(hostname, '.');
+ 
+  if (!dot)
+    return NULL;
+  
+  *dot = 0; /* truncate */
+  if (strlen(dot+1) != 0)
+    return dot+1;
+  
+  return NULL;
+}
+
+void log_tags(struct dhcp_netid *netid, u32 xid)
+{
+  if (netid && option_bool(OPT_LOG_OPTS))
+    {
+      char *s = daemon->namebuff;
+      for (*s = 0; netid; netid = netid->next)
+	{
+	  /* kill dupes. */
+	  struct dhcp_netid *n;
+	  
+	  for (n = netid->next; n; n = n->next)
+	    if (strcmp(netid->net, n->net) == 0)
+	      break;
+	  
+	  if (!n)
+	    {
+	      strncat (s, netid->net, (MAXDNAME-1) - strlen(s));
+	      if (netid->next)
+		strncat (s, ", ", (MAXDNAME-1) - strlen(s));
+	    }
+	}
+      my_syslog(MS_DHCP | LOG_INFO, _("%u tags: %s"), xid, s);
+    } 
+}   
+  
+int match_bytes(struct dhcp_opt *o, unsigned char *p, int len)
+{
+  int i;
+  
+  if (o->len > len)
+    return 0;
+  
+  if (o->len == 0)
+    return 1;
+     
+  if (o->flags & DHOPT_HEX)
+    { 
+      if (memcmp_masked(o->val, p, o->len, o->u.wildcard_mask))
+	return 1;
+    }
+  else 
+    for (i = 0; i <= (len - o->len); ) 
+      {
+	if (memcmp(o->val, p + i, o->len) == 0)
+	  return 1;
+	    
+	if (o->flags & DHOPT_STRING)
+	  i++;
+	else
+	  i += o->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 (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6)))
+    return 1;
+  
+#ifdef HAVE_DHCP6
+  if (context->flags & CONTEXT_V6)
+    {
+       struct addrlist *addr_list;
+
+       if (config->flags & CONFIG_ADDR6)
+	 for (; context; context = context->current)
+	   for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+	     {
+	       if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+		 return 1;
+	       
+	       if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix))
+		 return 1;
+	     }
+    }
+  else
+#endif
+    {
+      for (; context; context = context->current)
+	if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask))
+	  return 1;
+    }
+
+  return 0;
+}
+
+static struct dhcp_config *find_config_match(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,
+					     struct dhcp_netid *tags, int tag_not_needed)
+{
+  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) &&
+	      match_netid(config->filter, tags, tag_not_needed))
+	    
+	    return config;
+	  
+	  /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and
+	     cope with that here. This is IPv4 only. context==NULL implies IPv4, 
+	     see lease_update_from_configs() */
+	  if ((!context || !(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) &&
+	      match_netid(config->filter, tags, tag_not_needed))
+	    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) &&
+	  match_netid(config->filter, tags, tag_not_needed))
+	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) &&
+	  match_netid(config->filter, tags, tag_not_needed))
+	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) &&
+	match_netid(config->filter, tags, tag_not_needed))
+      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;
+}
+
+/* Find tagged configs first. */
+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, struct dhcp_netid *tags)
+{
+  struct dhcp_config *ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 0);
+
+  if (!ret)
+    ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 1);
+
+  return ret;
+}
+
+void dhcp_update_configs(struct dhcp_config *configs)
+{
+  /* Some people like to keep all static IP addresses in /etc/hosts.
+     This goes through /etc/hosts and sets static addresses for any DHCP config
+     records which don't have an address and whose name matches. 
+     We take care to maintain the invariant that any IP address can appear
+     in at most one dhcp-host. Since /etc/hosts can be re-read by SIGHUP, 
+     restore the status-quo ante first. */
+  
+  struct dhcp_config *config, *conf_tmp;
+  struct crec *crec;
+  int prot = AF_INET;
+
+  for (config = configs; config; config = config->next)
+  {
+    if (config->flags & CONFIG_ADDR_HOSTS)
+      config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS);
+#ifdef HAVE_DHCP6
+    if (config->flags & CONFIG_ADDR6_HOSTS)
+      config->flags &= ~(CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS);
+#endif
+  }
+
+#ifdef HAVE_DHCP6 
+ again:  
+#endif
+
+  if (daemon->port != 0)
+    for (config = configs; config; config = config->next)
+      {
+	int conflags = CONFIG_ADDR;
+	int cacheflags = F_IPV4;
+
+#ifdef HAVE_DHCP6
+	if (prot == AF_INET6)
+	  {
+	    conflags = CONFIG_ADDR6;
+	    cacheflags = F_IPV6;
+	  }
+#endif
+	if (!(config->flags & conflags) &&
+	    (config->flags & CONFIG_NAME) && 
+	    (crec = cache_find_by_name(NULL, config->hostname, 0, cacheflags)) &&
+	    (crec->flags & F_HOSTS))
+	  {
+	    if (cache_find_by_name(crec, config->hostname, 0, cacheflags))
+	      {
+		/* use primary (first) address */
+		while (crec && !(crec->flags & F_REVERSE))
+		  crec = cache_find_by_name(crec, config->hostname, 0, cacheflags);
+		if (!crec)
+		  continue; /* should be never */
+		inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN);
+		my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), 
+			  config->hostname, daemon->addrbuff);
+	      }
+	    
+	    if (prot == AF_INET && 
+		(!(conf_tmp = config_find_by_address(configs, crec->addr.addr4)) || conf_tmp == config))
+	      {
+		config->addr = crec->addr.addr4;
+		config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS;
+		continue;
+	      }
+
+#ifdef HAVE_DHCP6
+	    if (prot == AF_INET6 && 
+		(!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config))
+	      {
+		/* host must have exactly one address if comming from /etc/hosts. */
+		if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist))))
+		  {
+		    config->addr6->next = NULL;
+		    config->addr6->flags = 0;
+		  }
+
+		if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX)))
+		  {
+		    memcpy(&config->addr6->addr.addr6, &crec->addr.addr6, IN6ADDRSZ);
+		    config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS;
+		  }
+	    
+		continue;
+	      }
+#endif
+
+	    inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN);
+	    my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), 
+		      daemon->addrbuff, config->hostname);
+	    
+	    
+	  }
+      }
+
+#ifdef HAVE_DHCP6
+  if (prot == AF_INET)
+    {
+      prot = AF_INET6;
+      goto again;
+    }
+#endif
+
+}
+
+#ifdef HAVE_LINUX_NETWORK 
+char *whichdevice(void)
+{
+  /* If we are doing DHCP on exactly one interface, and running linux, do SO_BINDTODEVICE
+     to that device. This is for the use case of  (eg) OpenStack, which runs a new
+     dnsmasq instance for each VLAN interface it creates. Without the BINDTODEVICE, 
+     individual processes don't always see the packets they should.
+     SO_BINDTODEVICE is only available Linux. 
+
+     Note that if wildcards are used in --interface, or --interface is not used at all,
+     or a configured interface doesn't yet exist, then more interfaces may arrive later, 
+     so we can't safely assert there is only one interface and proceed.
+*/
+  
+  struct irec *iface, *found;
+  struct iname *if_tmp;
+  
+  if (!daemon->if_names)
+    return NULL;
+  
+  for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+    if (if_tmp->name && (!if_tmp->used || strchr(if_tmp->name, '*')))
+      return NULL;
+
+  for (found = NULL, iface = daemon->interfaces; iface; iface = iface->next)
+    if (iface->dhcp_ok)
+      {
+	if (!found)
+	  found = iface;
+	else if (strcmp(found->name, iface->name) != 0) 
+	  return NULL; /* more than one. */
+      }
+
+  if (found)
+    return found->name;
+
+  return NULL;
+}
+ 
+void  bindtodevice(char *device, int fd)
+{
+  size_t len = strlen(device)+1;
+  if (len > IFNAMSIZ)
+    len = IFNAMSIZ;
+  /* only allowed by root. */
+  if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, len) == -1 &&
+      errno != EPERM)
+    die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET);
+}
+#endif
+
+static const struct opttab_t {
+  char *name;
+  u16 val, size;
+} opttab[] = {
+  { "netmask", 1, OT_ADDR_LIST },
+  { "time-offset", 2, 4 },
+  { "router", 3, OT_ADDR_LIST  },
+  { "dns-server", 6, OT_ADDR_LIST },
+  { "log-server", 7, OT_ADDR_LIST },
+  { "lpr-server", 9, OT_ADDR_LIST },
+  { "hostname", 12, OT_INTERNAL | OT_NAME },
+  { "boot-file-size", 13, 2 | OT_DEC },
+  { "domain-name", 15, OT_NAME },
+  { "swap-server", 16, OT_ADDR_LIST },
+  { "root-path", 17, OT_NAME },
+  { "extension-path", 18, OT_NAME },
+  { "ip-forward-enable", 19, 1 },
+  { "non-local-source-routing", 20, 1 },
+  { "policy-filter", 21, OT_ADDR_LIST },
+  { "max-datagram-reassembly", 22, 2 | OT_DEC },
+  { "default-ttl", 23, 1 | OT_DEC },
+  { "mtu", 26, 2 | OT_DEC },
+  { "all-subnets-local", 27, 1 },
+  { "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST },
+  { "router-discovery", 31, 1 },
+  { "router-solicitation", 32, OT_ADDR_LIST },
+  { "static-route", 33, OT_ADDR_LIST },
+  { "trailer-encapsulation", 34, 1 },
+  { "arp-timeout", 35, 4 | OT_DEC },
+  { "ethernet-encap", 36, 1 },
+  { "tcp-ttl", 37, 1 },
+  { "tcp-keepalive", 38, 4 | OT_DEC },
+  { "nis-domain", 40, OT_NAME },
+  { "nis-server", 41, OT_ADDR_LIST },
+  { "ntp-server", 42, OT_ADDR_LIST },
+  { "vendor-encap", 43, OT_INTERNAL },
+  { "netbios-ns", 44, OT_ADDR_LIST },
+  { "netbios-dd", 45, OT_ADDR_LIST },
+  { "netbios-nodetype", 46, 1 },
+  { "netbios-scope", 47, 0 },
+  { "x-windows-fs", 48, OT_ADDR_LIST },
+  { "x-windows-dm", 49, OT_ADDR_LIST },
+  { "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST },
+  { "lease-time", 51, OT_INTERNAL | OT_TIME },
+  { "option-overload", 52, OT_INTERNAL },
+  { "message-type", 53, OT_INTERNAL | OT_DEC },
+  { "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST },
+  { "parameter-request", 55, OT_INTERNAL },
+  { "message", 56, OT_INTERNAL },
+  { "max-message-size", 57, OT_INTERNAL },
+  { "T1", 58, OT_TIME},
+  { "T2", 59, OT_TIME},
+  { "vendor-class", 60, 0 },
+  { "client-id", 61, OT_INTERNAL },
+  { "nis+-domain", 64, OT_NAME },
+  { "nis+-server", 65, OT_ADDR_LIST },
+  { "tftp-server", 66, OT_NAME },
+  { "bootfile-name", 67, OT_NAME },
+  { "mobile-ip-home", 68, OT_ADDR_LIST }, 
+  { "smtp-server", 69, OT_ADDR_LIST }, 
+  { "pop3-server", 70, OT_ADDR_LIST }, 
+  { "nntp-server", 71, OT_ADDR_LIST }, 
+  { "irc-server", 74, OT_ADDR_LIST }, 
+  { "user-class", 77, 0 },
+  { "rapid-commit", 80, 0 },
+  { "FQDN", 81, OT_INTERNAL },
+  { "agent-id", 82, OT_INTERNAL },
+  { "client-arch", 93, 2 | OT_DEC },
+  { "client-interface-id", 94, 0 },
+  { "client-machine-id", 97, 0 },
+  { "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
+  { "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
+  { "subnet-select", 118, OT_INTERNAL },
+  { "domain-search", 119, OT_RFC1035_NAME },
+  { "sip-server", 120, 0 },
+  { "classless-static-route", 121, 0 },
+  { "vendor-id-encap", 125, 0 },
+  { "tftp-server-address", 150, OT_ADDR_LIST },
+  { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */
+  { NULL, 0, 0 }
+};
+
+#ifdef HAVE_DHCP6
+static const struct opttab_t opttab6[] = {
+  { "client-id", 1, OT_INTERNAL },
+  { "server-id", 2, OT_INTERNAL },
+  { "ia-na", 3, OT_INTERNAL },
+  { "ia-ta", 4, OT_INTERNAL },
+  { "iaaddr", 5, OT_INTERNAL },
+  { "oro", 6, OT_INTERNAL },
+  { "preference", 7, OT_INTERNAL | OT_DEC },
+  { "unicast", 12, OT_INTERNAL },
+  { "status", 13, OT_INTERNAL },
+  { "rapid-commit", 14, OT_INTERNAL },
+  { "user-class", 15, OT_INTERNAL | OT_CSTRING },
+  { "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
+  { "vendor-opts", 17, OT_INTERNAL },
+  { "sip-server-domain", 21,  OT_RFC1035_NAME },
+  { "sip-server", 22, OT_ADDR_LIST },
+  { "dns-server", 23, OT_ADDR_LIST },
+  { "domain-search", 24, OT_RFC1035_NAME },
+  { "nis-server", 27, OT_ADDR_LIST },
+  { "nis+-server", 28, OT_ADDR_LIST },
+  { "nis-domain", 29,  OT_RFC1035_NAME },
+  { "nis+-domain", 30, OT_RFC1035_NAME },
+  { "sntp-server", 31,  OT_ADDR_LIST },
+  { "information-refresh-time", 32, OT_TIME },
+  { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME },
+  { "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ },
+  { "bootfile-url", 59, OT_NAME },
+  { "bootfile-param", 60, OT_CSTRING },
+  { NULL, 0, 0 }
+};
+#endif
+
+
+
+void display_opts(void)
+{
+  int i;
+  
+  printf(_("Known DHCP options:\n"));
+  
+  for (i = 0; opttab[i].name; i++)
+    if (!(opttab[i].size & OT_INTERNAL))
+      printf("%3d %s\n", opttab[i].val, opttab[i].name);
+}
+
+#ifdef HAVE_DHCP6
+void display_opts6(void)
+{
+  int i;
+  printf(_("Known DHCPv6 options:\n"));
+  
+  for (i = 0; opttab6[i].name; i++)
+    if (!(opttab6[i].size & OT_INTERNAL))
+      printf("%3d %s\n", opttab6[i].val, opttab6[i].name);
+}
+#endif
+
+int lookup_dhcp_opt(int prot, char *name)
+{
+  const struct opttab_t *t;
+  int i;
+
+  (void)prot;
+
+#ifdef HAVE_DHCP6
+  if (prot == AF_INET6)
+    t = opttab6;
+  else
+#endif
+    t = opttab;
+
+  for (i = 0; t[i].name; i++)
+    if (strcasecmp(t[i].name, name) == 0)
+      return t[i].val;
+  
+  return -1;
+}
+
+int lookup_dhcp_len(int prot, int val)
+{
+  const struct opttab_t *t;
+  int i;
+
+  (void)prot;
+
+#ifdef HAVE_DHCP6
+  if (prot == AF_INET6)
+    t = opttab6;
+  else
+#endif
+    t = opttab;
+
+  for (i = 0; t[i].name; i++)
+    if (val == t[i].val)
+      return t[i].size & ~OT_DEC;
+
+   return 0;
+}
+
+char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len, char *buf, int buf_len)
+{
+  int o, i, j, nodecode = 0;
+  const struct opttab_t *ot = opttab;
+
+#ifdef HAVE_DHCP6
+  if (prot == AF_INET6)
+    ot = opttab6;
+#endif
+
+  for (o = 0; ot[o].name; o++)
+    if (ot[o].val == opt)
+      {
+	if (buf)
+	  {
+	    memset(buf, 0, buf_len);
+	    
+	    if (ot[o].size & OT_ADDR_LIST) 
+	      {
+		union all_addr addr;
+		int addr_len = INADDRSZ;
+
+#ifdef HAVE_DHCP6
+		if (prot == AF_INET6)
+		  addr_len = IN6ADDRSZ;
+#endif
+		for (buf[0]= 0, i = 0; i <= opt_len - addr_len; i += addr_len) 
+		  {
+		    if (i != 0)
+		      strncat(buf, ", ", buf_len - strlen(buf));
+		    /* align */
+		    memcpy(&addr, &val[i], addr_len); 
+		    inet_ntop(prot, &val[i], daemon->addrbuff, ADDRSTRLEN);
+		    strncat(buf, daemon->addrbuff, buf_len - strlen(buf));
+		  }
+	      }
+	    else if (ot[o].size & OT_NAME)
+		for (i = 0, j = 0; i < opt_len && j < buf_len ; i++)
+		  {
+		    char c = val[i];
+		    if (isprint((int)c))
+		      buf[j++] = c;
+		  }
+#ifdef HAVE_DHCP6
+	    /* We don't handle compressed rfc1035 names, so no good in IPv4 land */
+	    else if ((ot[o].size & OT_RFC1035_NAME) && prot == AF_INET6)
+	      {
+		i = 0, j = 0;
+		while (i < opt_len && val[i] != 0)
+		  {
+		    int k, l = i + val[i] + 1;
+		    for (k = i + 1; k < opt_len && k < l && j < buf_len ; k++)
+		     {
+		       char c = val[k];
+		       if (isprint((int)c))
+			 buf[j++] = c;
+		     }
+		    i = l;
+		    if (val[i] != 0 && j < buf_len)
+		      buf[j++] = '.';
+		  }
+	      }
+	    else if ((ot[o].size & OT_CSTRING))
+	      {
+		int k, len;
+		unsigned char *p;
+
+		i = 0, j = 0;
+		while (1)
+		  {
+		    p = &val[i];
+		    GETSHORT(len, p);
+		    for (k = 0; k < len && j < buf_len; k++)
+		      {
+		       char c = *p++;
+		       if (isprint((int)c))
+			 buf[j++] = c;
+		     }
+		    i += len +2;
+		    if (i >= opt_len)
+		      break;
+
+		    if (j < buf_len)
+		      buf[j++] = ',';
+		  }
+	      }	      
+#endif
+	    else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
+	      {
+		unsigned int dec = 0;
+		
+		for (i = 0; i < opt_len; i++)
+		  dec = (dec << 8) | val[i]; 
+
+		if (ot[o].size & OT_TIME)
+		  prettyprint_time(buf, dec);
+		else
+		  sprintf(buf, "%u", dec);
+	      }
+	    else
+	      nodecode = 1;
+	  }
+	break;
+      }
+
+  if (opt_len != 0 && buf && (!ot[o].name || nodecode))
+    {
+      int trunc  = 0;
+      if (opt_len > 14)
+	{
+	  trunc = 1;
+	  opt_len = 14;
+	}
+      print_mac(buf, val, opt_len);
+      if (trunc)
+	strncat(buf, "...", buf_len - strlen(buf));
+    
+
+    }
+
+  return ot[o].name ? ot[o].name : "";
+
+}
+
+void log_context(int family, struct dhcp_context *context)
+{
+  /* Cannot use dhcp_buff* for RA contexts */
+
+  void *start = &context->start;
+  void *end = &context->end;
+  char *template = "", *p = daemon->namebuff;
+  
+  *p = 0;
+    
+#ifdef HAVE_DHCP6
+  if (family == AF_INET6)
+    {
+      struct in6_addr subnet = context->start6;
+      if (!(context->flags & CONTEXT_TEMPLATE))
+	setaddr6part(&subnet, 0);
+      inet_ntop(AF_INET6, &subnet, daemon->addrbuff, ADDRSTRLEN); 
+      start = &context->start6;
+      end = &context->end6;
+    }
+#endif
+
+  if (family != AF_INET && (context->flags & CONTEXT_DEPRECATE))
+    strcpy(daemon->namebuff, _(", prefix deprecated"));
+  else
+    {
+      p += sprintf(p, _(", lease time "));
+      prettyprint_time(p, context->lease_time);
+      p += strlen(p);
+    }	
+
+#ifdef HAVE_DHCP6
+  if (context->flags & CONTEXT_CONSTRUCTED)
+    {
+      char ifrn_name[IFNAMSIZ];
+      
+      template = p;
+      p += sprintf(p, ", ");
+      
+      if (indextoname(daemon->icmp6fd, context->if_index, ifrn_name))
+	sprintf(p, "%s for %s", (context->flags & CONTEXT_OLD) ? "old prefix" : "constructed", ifrn_name);
+    }
+  else if (context->flags & CONTEXT_TEMPLATE && !(context->flags & CONTEXT_RA_STATELESS))
+    {
+      template = p;
+      p += sprintf(p, ", ");
+      
+      sprintf(p, "template for %s", context->template_interface);  
+    }
+#endif
+     
+  if (!(context->flags & CONTEXT_OLD) &&
+      ((context->flags & CONTEXT_DHCP) || family == AF_INET)) 
+    {
+#ifdef HAVE_DHCP6
+      if (context->flags & CONTEXT_RA_STATELESS)
+	{
+	  if (context->flags & CONTEXT_TEMPLATE)
+	    strncpy(daemon->dhcp_buff, context->template_interface, DHCP_BUFF_SZ);
+	  else
+	    strcpy(daemon->dhcp_buff, daemon->addrbuff);
+	}
+      else 
+#endif
+	inet_ntop(family, start, daemon->dhcp_buff, DHCP_BUFF_SZ);
+      inet_ntop(family, end, daemon->dhcp_buff3, DHCP_BUFF_SZ);
+      my_syslog(MS_DHCP | LOG_INFO, 
+		(context->flags & CONTEXT_RA_STATELESS) ? 
+		_("%s stateless on %s%.0s%.0s%s") :
+		(context->flags & CONTEXT_STATIC) ? 
+		_("%s, static leases only on %.0s%s%s%.0s") :
+		(context->flags & CONTEXT_PROXY) ?
+		_("%s, proxy on subnet %.0s%s%.0s%.0s") :
+		_("%s, IP range %s -- %s%s%.0s"),
+		(family != AF_INET) ? "DHCPv6" : "DHCP",
+		daemon->dhcp_buff, daemon->dhcp_buff3, daemon->namebuff, template);
+    }
+  
+#ifdef HAVE_DHCP6
+  if (context->flags & CONTEXT_TEMPLATE)
+    {
+      strcpy(daemon->addrbuff, context->template_interface);
+      template = "";
+    }
+
+  if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD))
+    my_syslog(MS_DHCP | LOG_INFO, _("DHCPv4-derived IPv6 names on %s%s"), daemon->addrbuff, template);
+  
+  if ((context->flags & CONTEXT_RA) || (option_bool(OPT_RA) && (context->flags & CONTEXT_DHCP) && family == AF_INET6)) 
+    my_syslog(MS_DHCP | LOG_INFO, _("router advertisement on %s%s"), daemon->addrbuff, template);
+#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/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h
new file mode 100755
index 0000000..6ff3ffa
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp-protocol.h
@@ -0,0 +1,101 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+#define DHCP_SERVER_ALTPORT 1067
+#define DHCP_CLIENT_ALTPORT 1068
+#define PXE_PORT 4011
+
+/* These each hold a DHCP option max size 255
+   and get a terminating zero added */
+#define DHCP_BUFF_SZ 256
+
+#define BOOTREQUEST              1
+#define BOOTREPLY                2
+#define DHCP_COOKIE              0x63825363
+
+/* The Linux in-kernel DHCP client silently ignores any packet 
+   smaller than this. Sigh...........   */
+#define MIN_PACKETSZ             300
+
+#define OPTION_PAD               0
+#define OPTION_NETMASK           1
+#define OPTION_ROUTER            3
+#define OPTION_DNSSERVER         6
+#define OPTION_HOSTNAME          12
+#define OPTION_DOMAINNAME        15
+#define OPTION_BROADCAST         28
+#define OPTION_VENDOR_CLASS_OPT  43
+#define OPTION_REQUESTED_IP      50 
+#define OPTION_LEASE_TIME        51
+#define OPTION_OVERLOAD          52
+#define OPTION_MESSAGE_TYPE      53
+#define OPTION_SERVER_IDENTIFIER 54
+#define OPTION_REQUESTED_OPTIONS 55
+#define OPTION_MESSAGE           56
+#define OPTION_MAXMESSAGE        57
+#define OPTION_T1                58
+#define OPTION_T2                59
+#define OPTION_VENDOR_ID         60
+#define OPTION_CLIENT_ID         61
+#define OPTION_SNAME             66
+#define OPTION_FILENAME          67
+#define OPTION_USER_CLASS        77
+#define OPTION_RAPID_COMMIT      80
+#define OPTION_CLIENT_FQDN       81
+#define OPTION_AGENT_ID          82
+#define OPTION_ARCH              93
+#define OPTION_PXE_UUID          97
+#define OPTION_SUBNET_SELECT     118
+#define OPTION_DOMAIN_SEARCH     119
+#define OPTION_SIP_SERVER        120
+#define OPTION_VENDOR_IDENT      124
+#define OPTION_VENDOR_IDENT_OPT  125
+#define OPTION_END               255
+
+#define SUBOPT_CIRCUIT_ID        1
+#define SUBOPT_REMOTE_ID         2
+#define SUBOPT_SUBNET_SELECT     5     /* RFC 3527 */
+#define SUBOPT_SUBSCR_ID         6     /* RFC 3393 */
+#define SUBOPT_SERVER_OR         11    /* RFC 5107 */
+
+#define SUBOPT_PXE_BOOT_ITEM     71    /* PXE standard */
+#define SUBOPT_PXE_DISCOVERY     6
+#define SUBOPT_PXE_SERVERS       8
+#define SUBOPT_PXE_MENU          9
+#define SUBOPT_PXE_MENU_PROMPT   10
+
+#define DHCPDISCOVER             1
+#define DHCPOFFER                2
+#define DHCPREQUEST              3
+#define DHCPDECLINE              4
+#define DHCPACK                  5
+#define DHCPNAK                  6
+#define DHCPRELEASE              7
+#define DHCPINFORM               8
+
+#define BRDBAND_FORUM_IANA       3561 /* Broadband forum IANA enterprise */
+
+#define DHCP_CHADDR_MAX 16
+
+struct dhcp_packet {
+  u8 op, htype, hlen, hops;
+  u32 xid;
+  u16 secs, flags;
+  struct in_addr ciaddr, yiaddr, siaddr, giaddr;
+  u8 chaddr[DHCP_CHADDR_MAX], sname[64], file[128];
+  u8 options[312];
+};
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c
new file mode 100755
index 0000000..e500bc2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp.c
@@ -0,0 +1,1135 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP
+
+struct iface_param {
+  struct dhcp_context *current;
+  struct dhcp_relay *relay;
+  struct in_addr relay_local;
+  int ind;
+};
+
+struct match_param {
+  int ind, matched;
+  struct in_addr netmask, broadcast, addr;
+};
+
+static int complete_context(struct in_addr local, int if_index, char *label,
+			    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)
+{
+  int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+  struct sockaddr_in saddr;
+  int oneopt = 1;
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+  int mtu = IP_PMTUDISC_DONT;
+#endif
+#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+  int tos = IPTOS_CLASS_CS6;
+#endif
+
+  if (fd == -1)
+    die (_("cannot create DHCP socket: %s"), NULL, EC_BADNET);
+  
+  if (!fix_fd(fd) ||
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+      setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 ||
+#endif
+#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6)
+      setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1 ||
+#endif
+#if defined(HAVE_LINUX_NETWORK)
+      setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 ||
+#else
+      setsockopt(fd, IPPROTO_IP, IP_RECVIF, &oneopt, sizeof(oneopt)) == -1 ||
+#endif
+      setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &oneopt, sizeof(oneopt)) == -1)  
+    die(_("failed to set options on DHCP socket: %s"), NULL, EC_BADNET);
+  
+  /* When bind-interfaces is set, there might be more than one dnsmasq
+     instance binding port 67. 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 DHCP socket: %s"), NULL, EC_BADNET);
+    }
+  
+  memset(&saddr, 0, sizeof(saddr));
+  saddr.sin_family = AF_INET;
+  saddr.sin_port = htons(port);
+  saddr.sin_addr.s_addr = INADDR_ANY;
+#ifdef HAVE_SOCKADDR_SA_LEN
+  saddr.sin_len = sizeof(struct sockaddr_in);
+#endif
+
+  if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)))
+    die(_("failed to bind DHCP server socket: %s"), NULL, EC_BADNET);
+
+  return fd;
+}
+
+void dhcp_init(void)
+{
+#if defined(HAVE_BSD_NETWORK)
+  int oneopt = 1;
+#endif
+
+  daemon->dhcpfd = make_fd(daemon->dhcp_server_port);
+  if (daemon->enable_pxe)
+    daemon->pxefd = make_fd(PXE_PORT);
+  else
+    daemon->pxefd = -1;
+
+#if defined(HAVE_BSD_NETWORK)
+  /* When we're not using capabilities, we need to do this here before
+     we drop root. Also, set buffer size small, to avoid wasting
+     kernel buffers */
+  
+  if (option_bool(OPT_NO_PING))
+    daemon->dhcp_icmp_fd = -1;
+  else if ((daemon->dhcp_icmp_fd = make_icmp_sock()) == -1 ||
+	   setsockopt(daemon->dhcp_icmp_fd, SOL_SOCKET, SO_RCVBUF, &oneopt, sizeof(oneopt)) == -1 )
+    die(_("cannot create ICMP raw socket: %s."), NULL, EC_BADNET);
+  
+  /* Make BPF raw send socket */
+  init_bpf();
+#endif  
+}
+
+void dhcp_packet(time_t now, int pxe_fd)
+{
+  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;
+  struct sockaddr_in dest;
+  struct cmsghdr *cmptr;
+  struct iovec iov;
+  ssize_t sz; 
+  int iface_index = 0, unicast_dest = 0, is_inform = 0, loopback = 0;
+  int rcvd_iface_index;
+  struct in_addr iface_addr;
+  struct iface_param parm;
+  time_t recvtime = now;
+#ifdef HAVE_LINUX_NETWORK
+  struct arpreq arp_req;
+  struct timeval tv;
+#endif
+  
+  union {
+    struct cmsghdr align; /* this ensures alignment */
+#if defined(HAVE_LINUX_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#elif defined(HAVE_SOLARIS_NETWORK)
+    char control[CMSG_SPACE(sizeof(unsigned int))];
+#elif defined(HAVE_BSD_NETWORK) 
+    char control[CMSG_SPACE(sizeof(struct sockaddr_dl))];
+#endif
+  } control_u;
+  struct dhcp_bridge *bridge, *alias;
+
+  msg.msg_controllen = sizeof(control_u);
+  msg.msg_control = control_u.control;
+  msg.msg_name = &dest;
+  msg.msg_namelen = sizeof(dest);
+  msg.msg_iov = &daemon->dhcp_packet;
+  msg.msg_iovlen = 1;
+  
+  if ((sz = recv_dhcp_packet(fd, &msg)) == -1 || 
+      (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options)))) 
+    return;
+    
+  #if defined (HAVE_LINUX_NETWORK)
+  if (ioctl(fd, SIOCGSTAMP, &tv) == 0)
+    recvtime = tv.tv_sec;
+
+  if (msg.msg_controllen >= sizeof(struct cmsghdr))
+    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+      if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+	{
+	  union {
+	    unsigned char *c;
+	    struct in_pktinfo *p;
+	  } p;
+	  p.c = CMSG_DATA(cmptr);
+	  iface_index = p.p->ipi_ifindex;
+	  if (p.p->ipi_addr.s_addr != INADDR_BROADCAST)
+	    unicast_dest = 1;
+	}
+
+#elif defined(HAVE_BSD_NETWORK) 
+  if (msg.msg_controllen >= sizeof(struct cmsghdr))
+    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+      if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
+        {
+	  union {
+            unsigned char *c;
+            struct sockaddr_dl *s;
+          } p;
+	  p.c = CMSG_DATA(cmptr);
+	  iface_index = p.s->sdl_index;
+	}
+  
+#elif defined(HAVE_SOLARIS_NETWORK) 
+  if (msg.msg_controllen >= sizeof(struct cmsghdr))
+    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+      if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
+	{
+	  union {
+	    unsigned char *c;
+	    unsigned int *i;
+	  } p;
+	  p.c = CMSG_DATA(cmptr);
+	  iface_index = *(p.i);
+	}
+#endif
+	
+  if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name) ||
+      ioctl(daemon->dhcpfd, SIOCGIFFLAGS, &ifr) != 0)
+    return;
+  
+  mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+  loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK);
+  
+#ifdef HAVE_LINUX_NETWORK
+  /* ARP fiddling uses original interface even if we pretend to use a different one. */
+  safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
+#endif 
+
+  /* If the interface on which the DHCP request was received is an
+     alias of some other interface (as specified by the
+     --bridge-interface option), change ifr.ifr_name so that we look
+     for DHCP contexts associated with the aliased interface instead
+     of with the aliasing one. */
+  rcvd_iface_index = iface_index;
+  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))
+	  {
+	    if (!(iface_index = if_nametoindex(bridge->iface)))
+	      {
+		my_syslog(MS_DHCP | LOG_WARNING,
+			  _("unknown interface %s in bridge-interface"),
+			  bridge->iface);
+		return;
+	      }
+	    else 
+	      {
+		safe_strncpy(ifr.ifr_name,  bridge->iface, sizeof(ifr.ifr_name));
+		break;
+	      }
+	  }
+      
+      if (alias)
+	break;
+    }
+
+#ifdef MSG_BCAST
+  /* OpenBSD tells us when a packet was broadcast */
+  if (!(msg.msg_flags & MSG_BCAST))
+    unicast_dest = 1;
+#endif
+  
+  if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
+    {
+      /* Reply from server, using us as relay. */
+      rcvd_iface_index = relay->iface_index;
+      if (!indextoname(daemon->dhcpfd, rcvd_iface_index, ifr.ifr_name))
+	return;
+      is_relay_reply = 1; 
+      iov.iov_len = sz;
+#ifdef HAVE_LINUX_NETWORK
+      safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
+#endif 
+    }
+  else
+    {
+      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
+	{
+	  if (iface_check(AF_INET, NULL, ifr.ifr_name, NULL))
+	    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/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, (union 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;
+
+      /* We're relaying this request */
+      if  (parm.relay_local.s_addr != 0 &&
+	   relay_upstream4(parm.relay, mess, (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, loopback, &is_inform, pxe_fd, iface_addr, recvtime);
+      lease_update_file(now);
+      lease_update_dns(0);
+      
+      if (iov.iov_len == 0)
+	return;
+    }
+
+  msg.msg_name = &dest;
+  msg.msg_namelen = sizeof(dest);
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_iov = &iov;
+  iov.iov_base = daemon->dhcp_packet.iov_base;
+  
+  /* packet buffer may have moved */
+  mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+  
+#ifdef HAVE_SOCKADDR_SA_LEN
+  dest.sin_len = sizeof(struct sockaddr_in);
+#endif
+  
+  if (pxe_fd)
+    { 
+      if (mess->ciaddr.s_addr != 0)
+	dest.sin_addr = mess->ciaddr;
+    }
+  else if (mess->giaddr.s_addr && !is_relay_reply)
+    {
+      /* Send to BOOTP relay  */
+      dest.sin_port = htons(daemon->dhcp_server_port);
+      dest.sin_addr = mess->giaddr; 
+    }
+  else if (mess->ciaddr.s_addr)
+    {
+      /* If the client's idea of its own address tallys with
+	 the source address in the request packet, we believe the
+	 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 || is_relay_reply)
+	{
+	  dest.sin_port = htons(daemon->dhcp_client_port); 
+	  dest.sin_addr = mess->ciaddr;
+	}
+    } 
+#if defined(HAVE_LINUX_NETWORK)
+  else
+    {
+      /* fill cmsg for outbound interface (both broadcast & unicast) */
+      struct in_pktinfo *pkt;
+      msg.msg_control = control_u.control;
+      msg.msg_controllen = sizeof(control_u);
+      cmptr = CMSG_FIRSTHDR(&msg);
+      pkt = (struct in_pktinfo *)CMSG_DATA(cmptr);
+      pkt->ipi_ifindex = rcvd_iface_index;
+      pkt->ipi_spec_dst.s_addr = 0;
+      msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+      cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+      cmptr->cmsg_level = IPPROTO_IP;
+      cmptr->cmsg_type = IP_PKTINFO;
+
+      if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 ||
+         mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0)
+        {
+          /* broadcast to 255.255.255.255 (or mac address invalid) */
+          dest.sin_addr.s_addr = INADDR_BROADCAST;
+          dest.sin_port = htons(daemon->dhcp_client_port);
+        }
+      else
+        {
+          /* unicast to unconfigured client. Inject mac address direct into ARP cache.
+          struct sockaddr limits size to 14 bytes. */
+          dest.sin_addr = mess->yiaddr;
+          dest.sin_port = htons(daemon->dhcp_client_port);
+          memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in));
+          arp_req.arp_ha.sa_family = mess->htype;
+          memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen);
+          /* interface name already copied in */
+          arp_req.arp_flags = ATF_COM;
+          if (ioctl(daemon->dhcpfd, SIOCSARP, &arp_req) == -1)
+            my_syslog(MS_DHCP | LOG_ERR, _("ARP-cache injection failed: %s"), strerror(errno));
+        }
+    }
+#elif defined(HAVE_SOLARIS_NETWORK)
+  else if ((ntohs(mess->flags) & 0x8000) || mess->hlen != ETHER_ADDR_LEN || mess->htype != ARPHRD_ETHER)
+    {
+      /* broadcast to 255.255.255.255 (or mac address invalid) */
+      dest.sin_addr.s_addr = INADDR_BROADCAST;
+      dest.sin_port = htons(daemon->dhcp_client_port);
+      /* note that we don't specify the interface here: that's done by the
+	 IP_BOUND_IF sockopt lower down. */
+    }
+  else
+    {
+      /* unicast to unconfigured client. Inject mac address direct into ARP cache. 
+	 Note that this only works for ethernet on solaris, because we use SIOCSARP
+	 and not SIOCSXARP, which would be perfect, except that it returns ENXIO 
+	 mysteriously. Bah. Fall back to broadcast for other net types. */
+      struct arpreq req;
+      dest.sin_addr = mess->yiaddr;
+      dest.sin_port = htons(daemon->dhcp_client_port);
+      *((struct sockaddr_in *)&req.arp_pa) = dest;
+      req.arp_ha.sa_family = AF_UNSPEC;
+      memcpy(req.arp_ha.sa_data, mess->chaddr, mess->hlen);
+      req.arp_flags = ATF_COM;
+      ioctl(daemon->dhcpfd, SIOCSARP, &req);
+    }
+#elif defined(HAVE_BSD_NETWORK)
+  else 
+    {
+      send_via_bpf(mess, iov.iov_len, iface_addr, &ifr);
+      return;
+    }
+#endif
+   
+#ifdef HAVE_SOLARIS_NETWORK
+  setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index));
+#endif
+  
+  while(retry_send(sendmsg(fd, &msg, 0)));
+
+  /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */
+  if (errno != 0)
+    {
+      inet_ntop(AF_INET, &dest.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+      my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"),
+		daemon->addrbuff, strerror(errno));
+    }
+}
+
+/* check against secondary interface addresses */
+static int check_listen_addrs(struct in_addr local, int if_index, char *label,
+			      struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+  struct match_param *param = vparam;
+  struct iname *tmp;
+
+  (void) label;
+
+  if (if_index == param->ind)
+    {
+      for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+	if ( tmp->addr.sa.sa_family == AF_INET &&
+	     tmp->addr.in.sin_addr.s_addr == local.s_addr)
+	  {
+	    param->matched = 1;
+	    param->addr = local;
+	    param->netmask = netmask;
+	    param->broadcast = broadcast;
+	    break;
+	  }
+    }
+  
+  return 1;
+}
+
+/* This is a complex routine: it gets called with each (address,netmask,broadcast) triple 
+   of each interface (and any relay address) and does the  following things:
+
+   1) Discards stuff for interfaces other than the one on which a DHCP packet just arrived.
+   2) Fills in any netmask and broadcast addresses which have not been explicitly configured.
+   3) Fills in local (this host) and router (this host or relay) addresses.
+   4) Links contexts which are valid for hosts directly connected to the arrival interface on ->current.
+
+   Note that the current chain may be superseded later for configured hosts or those coming via gateways. */
+
+static void guess_range_netmask(struct in_addr addr, struct in_addr netmask)
+{
+  struct dhcp_context *context;
+
+  for (context = daemon->dhcp; context; context = context->next)
+    if (!(context->flags & CONTEXT_NETMASK) &&
+	(is_same_net(addr, context->start, netmask) ||
+	 is_same_net(addr, context->end, netmask)))
+      { 
+	if (context->netmask.s_addr != netmask.s_addr &&
+	    !(is_same_net(addr, context->start, netmask) &&
+	      is_same_net(addr, context->end, netmask)))
+	  {
+	    inet_ntop(AF_INET, &context->start, daemon->dhcp_buff, DHCP_BUFF_SZ);
+	    inet_ntop(AF_INET, &context->end, daemon->dhcp_buff2, DHCP_BUFF_SZ);
+	    inet_ntop(AF_INET, &netmask, daemon->addrbuff, ADDRSTRLEN);
+	    my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"),
+		      daemon->dhcp_buff, daemon->dhcp_buff2, daemon->addrbuff);
+	  }	
+	context->netmask = netmask;
+      }
+}
+
+static int complete_context(struct in_addr local, int if_index, char *label,
+			    struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+  struct dhcp_context *context;
+  struct dhcp_relay *relay;
+  struct iface_param *param = vparam;
+  struct shared_network *share;
+  
+  (void)label;
+
+  for (share = daemon->shared_networks; share; share = share->next)
+    {
+      
+#ifdef HAVE_DHCP6
+      if (share->shared_addr.s_addr == 0)
+	continue;
+#endif
+      
+      if (share->if_index != 0)
+	{
+	  if (share->if_index != if_index)
+	    continue;
+	}
+      else
+	{
+	  if (share->match_addr.s_addr != local.s_addr)
+	    continue;
+	}
+
+      for (context = daemon->dhcp; context; context = context->next)
+	{
+	  if (context->netmask.s_addr != 0 &&
+	      is_same_net(share->shared_addr, context->start, context->netmask) &&
+	      is_same_net(share->shared_addr, context->end, context->netmask))
+	    {
+	      /* link it onto the current chain if we've not seen it before */
+	      if (context->current == context)
+		{
+		  /* For a shared network, we have no way to guess what the default route should be. */
+		  context->router.s_addr = 0;
+		  context->local = local; /* Use configured address for Server Identifier */
+		  context->current = param->current;
+		  param->current = context;
+		}
+	      
+	      if (!(context->flags & CONTEXT_BRDCAST))
+		context->broadcast.s_addr  = context->start.s_addr | ~context->netmask.s_addr;
+	    }		
+	}
+    }
+
+  guess_range_netmask(local, netmask);
+  
+  for (context = daemon->dhcp; context; context = context->next)
+    {
+      if (context->netmask.s_addr != 0 &&
+	  is_same_net(local, context->start, context->netmask) &&
+	  is_same_net(local, context->end, context->netmask))
+	{
+	  /* link it onto the current chain if we've not seen it before */
+	  if (if_index == param->ind && context->current == context)
+	    {
+	      context->router = local;
+	      context->local = local;
+	      context->current = param->current;
+	      param->current = context;
+	    }
+	  
+	  if (!(context->flags & CONTEXT_BRDCAST))
+	    {
+	      if (is_same_net(broadcast, context->start, context->netmask))
+		context->broadcast = broadcast;
+	      else 
+		context->broadcast.s_addr  = context->start.s_addr | ~context->netmask.s_addr;
+	    }
+	}		
+    }
+
+  for (relay = daemon->relay4; relay; relay = relay->next)
+    if (if_index == param->ind && relay->local.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;
+}
+	  
+struct dhcp_context *address_available(struct dhcp_context *context, 
+				       struct in_addr taddr,
+				       struct dhcp_netid *netids)
+{
+  /* Check is an address is OK for this network, check all
+     possible ranges. Make sure that the address isn't in use
+     by the server itself. */
+  
+  unsigned int start, end, addr = ntohl(taddr.s_addr);
+  struct dhcp_context *tmp;
+
+  for (tmp = context; tmp; tmp = tmp->current)
+    if (taddr.s_addr == context->router.s_addr)
+      return NULL;
+  
+  for (tmp = context; tmp; tmp = tmp->current)
+    {
+      start = ntohl(tmp->start.s_addr);
+      end = ntohl(tmp->end.s_addr);
+
+      if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) &&
+	  addr >= start &&
+	  addr <= end &&
+	  match_netid(tmp->filter, netids, 1))
+	return tmp;
+    }
+
+  return NULL;
+}
+
+struct dhcp_context *narrow_context(struct dhcp_context *context, 
+				    struct in_addr taddr,
+				    struct dhcp_netid *netids)
+{
+  /* We start of with a set of possible contexts, all on the current physical interface.
+     These are chained on ->current.
+     Here we have an address, and return the actual context corresponding to that
+     address. Note that none may fit, if the address came a dhcp-host and is outside
+     any dhcp-range. In that case we return a static range if possible, or failing that,
+     any context on the correct subnet. (If there's more than one, this is a dodgy 
+     configuration: maybe there should be a warning.) */
+  
+  struct dhcp_context *tmp;
+
+  if (!(tmp = address_available(context, taddr, netids)))
+    {
+      for (tmp = context; tmp; tmp = tmp->current)
+	if (match_netid(tmp->filter, netids, 1) &&
+	    is_same_net(taddr, tmp->start, tmp->netmask) && 
+	    (tmp->flags & CONTEXT_STATIC))
+	  break;
+      
+      if (!tmp)
+	for (tmp = context; tmp; tmp = tmp->current)
+	  if (match_netid(tmp->filter, netids, 1) &&
+	      is_same_net(taddr, tmp->start, tmp->netmask) &&
+	      !(tmp->flags & CONTEXT_PROXY))
+	    break;
+    }
+  
+  /* Only one context allowed now */
+  if (tmp)
+    tmp->current = NULL;
+  
+  return tmp;
+}
+
+struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr)
+{
+  struct dhcp_config *config;
+  
+  for (config = configs; config; config = config->next)
+    if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr)
+      return config;
+
+  return NULL;
+}
+
+/* Check if and address is in use by sending ICMP ping.
+   This wrapper handles a cache and load-limiting.
+   Return is NULL is address in use, or a pointer to a cache entry
+   recording that it isn't. */
+struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash, int loopback)
+{
+  static struct ping_result dummy;
+  struct ping_result *r, *victim = NULL;
+  int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/
+				((float)PING_WAIT)));
+
+  /* check if we failed to ping addr sometime in the last
+     PING_CACHE_TIME seconds. If so, assume the same situation still exists.
+     This avoids problems when a stupid client bangs
+     on us repeatedly. As a final check, if we did more
+     than 60% of the possible ping checks in the last 
+     PING_CACHE_TIME, we are in high-load mode, so don't do any more. */
+  for (count = 0, r = daemon->ping_results; r; r = r->next)
+    if (difftime(now, r->time) >  (float)PING_CACHE_TIME)
+      victim = r; /* old record */
+    else 
+      {
+	count++;
+	if (r->addr.s_addr == addr.s_addr)
+	  return r;
+      }
+  
+  /* didn't find cached entry */
+  if ((count >= max) || option_bool(OPT_NO_PING) || loopback)
+    {
+      /* overloaded, or configured not to check, loopback interface, return "not in use" */
+      dummy.hash = hash;
+      return &dummy;
+    }
+  else if (icmp_ping(addr))
+    return NULL; /* address in use. */
+  else
+    {
+      /* at this point victim may hold an expired record */
+      if (!victim)
+	{
+	  if ((victim = whine_malloc(sizeof(struct ping_result))))
+	    {
+	      victim->next = daemon->ping_results;
+	      daemon->ping_results = victim;
+	    }
+	}
+      
+      /* record that this address is OK for 30s 
+	 without more ping checks */
+      if (victim)
+	{
+	  victim->addr = addr;
+	  victim->time = now;
+	  victim->hash = hash;
+	}
+      return victim;
+    }
+}
+
+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 loopback)   
+{
+  /* 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. */
+
+  struct in_addr start, addr;
+  struct dhcp_context *c, *d;
+  int i, pass;
+  unsigned int j; 
+
+  /* hash hwaddr: use the SDBM hashing algorithm.  Seems to give good
+     dispersal even with similarly-valued "strings". */ 
+  for (j = 0, i = 0; i < hw_len; i++)
+    j = hwaddr[i] + (j << 6) + (j << 16) - j;
+
+  /* j == 0 is marker */
+  if (j == 0)
+    j = 1;
+  
+  for (pass = 0; pass <= 1; pass++)
+    for (c = context; c; c = c->current)
+      if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+	continue;
+      else if (!match_netid(c->filter, netids, pass))
+	continue;
+      else
+	{
+	  if (option_bool(OPT_CONSEC_ADDR))
+	    /* seed is largest extant lease addr in this context */
+	    start = lease_find_max_addr(c);
+	  else
+	    /* pick a seed based on hwaddr */
+	    start.s_addr = htonl(ntohl(c->start.s_addr) + 
+				 ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr))));
+
+	  /* 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.s_addr == d->router.s_addr)
+		break;
+
+	    /* Addresses which end in .255 and .0 are broken in Windows even when using 
+	       supernetting. ie dhcp-range=192.168.0.1,192.168.1.254,255,255,254.0
+	       then 192.168.0.255 is a valid IP address, but not for Windows as it's
+	       in the class C range. See  KB281579. We therefore don't allocate these 
+	       addresses to avoid hard-to-diagnose problems. Thanks Bill. */	    
+	    if (!d &&
+		!lease_find_by_addr(addr) && 
+		!config_find_by_address(daemon->dhcp_conf, addr) &&
+		(!IN_CLASSC(ntohl(addr.s_addr)) || 
+		 ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0))))
+	      {
+		/* in consec-ip mode, 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. */
+		if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch)
+		  c->addr_epoch--;
+		else
+		  {
+		    struct ping_result *r;
+		    
+		    if ((r = do_icmp_ping(now, addr, j, loopback)))
+		      {
+			/* consec-ip mode: we offered this address for another client
+			   (different hash) recently, don't offer it to this one. */
+			if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j)
+			  {
+			    *addrp = addr;
+			    return 1;
+			  }
+		      }
+		    else
+		      {
+			/* address in use: perturb address selection so that we are
+			   less likely to try this address again. */
+			if (!option_bool(OPT_CONSEC_ADDR))
+			  c->addr_epoch++;
+		      }
+		  }
+	      }
+	    
+	    addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
+	    
+	    if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1))
+	      addr = c->start;
+	    
+	  } while (addr.s_addr != start.s_addr);
+	}
+
+  return 0;
+}
+
+void dhcp_read_ethers(void)
+{
+  FILE *f = fopen(ETHERSFILE, "r");
+  unsigned int flags;
+  char *buff = daemon->namebuff;
+  char *ip, *cp;
+  struct in_addr addr;
+  unsigned char hwaddr[ETHER_ADDR_LEN];
+  struct dhcp_config **up, *tmp;
+  struct dhcp_config *config;
+  int count = 0, lineno = 0;
+
+  addr.s_addr = 0; /* eliminate warning */
+  
+  if (!f)
+    {
+      my_syslog(MS_DHCP | LOG_ERR, _("failed to read %s: %s"), ETHERSFILE, strerror(errno));
+      return;
+    }
+
+  /* This can be called again on SIGHUP, so remove entries created last time round. */
+  for (up = &daemon->dhcp_conf, config = daemon->dhcp_conf; config; config = tmp)
+    {
+      tmp = config->next;
+      if (config->flags & CONFIG_FROM_ETHERS)
+	{
+	  *up = tmp;
+	  /* cannot have a clid */
+	  if (config->flags & CONFIG_NAME)
+	    free(config->hostname);
+	  free(config->hwaddr);
+	  free(config);
+	}
+      else
+	up = &config->next;
+    }
+
+  while (fgets(buff, MAXDNAME, f))
+    {
+      char *host = NULL;
+      
+      lineno++;
+      
+      while (strlen(buff) > 0 && isspace((int)buff[strlen(buff)-1]))
+	buff[strlen(buff)-1] = 0;
+      
+      if ((*buff == '#') || (*buff == '+') || (*buff == 0))
+	continue;
+      
+      for (ip = buff; *ip && !isspace((int)*ip); ip++);
+      for(; *ip && isspace((int)*ip); ip++)
+	*ip = 0;
+      if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN)
+	{
+	  my_syslog(MS_DHCP | LOG_ERR, _("bad line at %s line %d"), ETHERSFILE, lineno); 
+	  continue;
+	}
+      
+      /* check for name or dotted-quad */
+      for (cp = ip; *cp; cp++)
+	if (!(*cp == '.' || (*cp >='0' && *cp <= '9')))
+	  break;
+      
+      if (!*cp)
+	{
+	  if (inet_pton(AF_INET, ip, &addr.s_addr) < 1)
+	    {
+	      my_syslog(MS_DHCP | LOG_ERR, _("bad address at %s line %d"), ETHERSFILE, lineno); 
+	      continue;
+	    }
+
+	  flags = CONFIG_ADDR;
+	  
+	  for (config = daemon->dhcp_conf; config; config = config->next)
+	    if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr)
+	      break;
+	}
+      else 
+	{
+	  int nomem;
+	  if (!(host = canonicalise(ip, &nomem)) || !legal_hostname(host))
+	    {
+	      if (!nomem)
+		my_syslog(MS_DHCP | LOG_ERR, _("bad name at %s line %d"), ETHERSFILE, lineno); 
+	      free(host);
+	      continue;
+	    }
+	      
+	  flags = CONFIG_NAME;
+
+	  for (config = daemon->dhcp_conf; config; config = config->next)
+	    if ((config->flags & CONFIG_NAME) && hostname_isequal(config->hostname, host))
+	      break;
+	}
+
+      if (config && (config->flags & CONFIG_FROM_ETHERS))
+	{
+	  my_syslog(MS_DHCP | LOG_ERR, _("ignoring %s line %d, duplicate name or IP address"), ETHERSFILE, lineno); 
+	  continue;
+	}
+	
+      if (!config)
+	{ 
+	  for (config = daemon->dhcp_conf; config; config = config->next)
+	    {
+	      struct hwaddr_config *conf_addr = config->hwaddr;
+	      if (conf_addr && 
+		  conf_addr->next == NULL && 
+		  conf_addr->wildcard_mask == 0 &&
+		  conf_addr->hwaddr_len == ETHER_ADDR_LEN &&
+		  (conf_addr->hwaddr_type == ARPHRD_ETHER || conf_addr->hwaddr_type == 0) &&
+		  memcmp(conf_addr->hwaddr, hwaddr, ETHER_ADDR_LEN) == 0)
+		break;
+	    }
+	  
+	  if (!config)
+	    {
+	      if (!(config = whine_malloc(sizeof(struct dhcp_config))))
+		continue;
+	      config->flags = CONFIG_FROM_ETHERS;
+	      config->hwaddr = NULL;
+	      config->domain = NULL;
+	      config->netid = NULL;
+	      config->next = daemon->dhcp_conf;
+	      daemon->dhcp_conf = config;
+	    }
+	  
+	  config->flags |= flags;
+	  
+	  if (flags & CONFIG_NAME)
+	    {
+	      config->hostname = host;
+	      host = NULL;
+	    }
+	  
+	  if (flags & CONFIG_ADDR)
+	    config->addr = addr;
+	}
+      
+      config->flags |= CONFIG_NOCLID;
+      if (!config->hwaddr)
+	config->hwaddr = whine_malloc(sizeof(struct hwaddr_config));
+      if (config->hwaddr)
+	{
+	  memcpy(config->hwaddr->hwaddr, hwaddr, ETHER_ADDR_LEN);
+	  config->hwaddr->hwaddr_len = ETHER_ADDR_LEN;
+	  config->hwaddr->hwaddr_type = ARPHRD_ETHER;
+	  config->hwaddr->wildcard_mask = 0;
+	  config->hwaddr->next = NULL;
+	}
+      count++;
+      
+      free(host);
+
+    }
+  
+  fclose(f);
+
+  my_syslog(MS_DHCP | LOG_INFO, _("read %s - %d addresses"), ETHERSFILE, count);
+}
+
+
+/* If we've not found a hostname any other way, try and see if there's one in /etc/hosts
+   for this address. If it has a domain part, that must match the set domain and
+   it gets stripped. The set of legal domain names is bigger than the set of legal hostnames
+   so check here that the domain name is legal as a hostname. 
+   NOTE: we're only allowed to overwrite daemon->dhcp_buff if we succeed. */
+char *host_from_dns(struct in_addr addr)
+{
+  struct crec *lookup;
+
+  if (daemon->port == 0)
+    return NULL; /* DNS disabled. */
+  
+  lookup = cache_find_by_addr(NULL, (union all_addr *)&addr, 0, F_IPV4);
+
+  if (lookup && (lookup->flags & F_HOSTS))
+    {
+      char *dot, *hostname = cache_get_name(lookup);
+      dot = strchr(hostname, '.');
+      
+      if (dot && strlen(dot+1) != 0)
+	{
+	  char *d2 = get_domain(addr);
+	  if (!d2 || !hostname_isequal(dot+1, d2))
+	    return NULL; /* wrong domain */
+	}
+
+      if (!legal_hostname(hostname))
+	return NULL;
+      
+      safe_strncpy(daemon->dhcp_buff, hostname, 256);
+      strip_hostname(daemon->dhcp_buff);
+
+      return daemon->dhcp_buff;
+    }
+  
+  return NULL;
+}
+
+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 */
+  union all_addr from;
+  
+  if (mess->op != BOOTREQUEST)
+    return 0;
+
+  /* source address == relay address */
+  from.addr4 = relay->local.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.addr4.s_addr)
+	return 1;
+    }
+  else
+    {
+      /* plug in our address */
+      mess->giaddr.s_addr = relay->local.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.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);
+	  inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ);
+	  my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2);
+	}
+      
+      /* 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.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/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h
new file mode 100755
index 0000000..ebe8dfd
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6-protocol.h
@@ -0,0 +1,74 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+#define DHCPV6_SERVER_PORT 547
+#define DHCPV6_CLIENT_PORT 546
+
+#define ALL_SERVERS                  "FF05::1:3"
+#define ALL_RELAY_AGENTS_AND_SERVERS "FF02::1:2"
+
+#define DHCP6SOLICIT      1
+#define DHCP6ADVERTISE    2
+#define DHCP6REQUEST      3
+#define DHCP6CONFIRM      4
+#define DHCP6RENEW        5
+#define DHCP6REBIND       6
+#define DHCP6REPLY        7
+#define DHCP6RELEASE      8
+#define DHCP6DECLINE      9
+#define DHCP6RECONFIGURE  10
+#define DHCP6IREQ         11
+#define DHCP6RELAYFORW    12
+#define DHCP6RELAYREPL    13
+
+#define OPTION6_CLIENT_ID       1
+#define OPTION6_SERVER_ID       2
+#define OPTION6_IA_NA           3
+#define OPTION6_IA_TA           4
+#define OPTION6_IAADDR          5
+#define OPTION6_ORO             6
+#define OPTION6_PREFERENCE      7
+#define OPTION6_ELAPSED_TIME    8
+#define OPTION6_RELAY_MSG       9
+#define OPTION6_AUTH            11
+#define OPTION6_UNICAST         12
+#define OPTION6_STATUS_CODE     13
+#define OPTION6_RAPID_COMMIT    14
+#define OPTION6_USER_CLASS      15
+#define OPTION6_VENDOR_CLASS    16
+#define OPTION6_VENDOR_OPTS     17
+#define OPTION6_INTERFACE_ID    18
+#define OPTION6_RECONFIGURE_MSG 19
+#define OPTION6_RECONF_ACCEPT   20
+#define OPTION6_DNS_SERVER      23
+#define OPTION6_DOMAIN_SEARCH   24
+#define OPTION6_REFRESH_TIME    32
+#define OPTION6_REMOTE_ID       37
+#define OPTION6_SUBSCRIBER_ID   38
+#define OPTION6_FQDN            39
+#define OPTION6_NTP_SERVER      56
+#define OPTION6_CLIENT_MAC      79
+
+#define NTP_SUBOPTION_SRV_ADDR  1
+#define NTP_SUBOPTION_MC_ADDR   2
+#define NTP_SUBOPTION_SRV_FQDN  3
+
+#define DHCP6SUCCESS     0
+#define DHCP6UNSPEC      1
+#define DHCP6NOADDRS     2
+#define DHCP6NOBINDING   3
+#define DHCP6NOTONLINK   4
+#define DHCP6USEMULTI    5
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c
new file mode 100755
index 0000000..2be877f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dhcp6.c
@@ -0,0 +1,828 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 dhcp_relay *relay;
+  struct in6_addr fallback, relay_local, 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;
+  int 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;
+
+  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;
+
+  if ((port = relay_reply6(&from, sz, ifr.ifr_name)) != 0)
+    {
+      from.sin6_port = htons(port);
+      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;
+
+      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.relay = NULL;
+      memset(&parm.relay_local, 0, IN6ADDRSZ);
+      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->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)
+	{
+	  /* Ignore requests sent to the ALL_SERVERS multicast address for relay when
+	     we're listening there for DHCPv6 server reasons. */
+	  struct in6_addr all_servers;
+	  
+	  inet_pton(AF_INET6, ALL_SERVERS, &all_servers);
+	  
+	  if (!IN6_ARE_ADDR_EQUAL(&dst_addr, &all_servers))
+	    relay_upstream6(parm.relay, sz, &from.sin6_addr, from.sin6_scope_id, now);
+	  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, 
+			 &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);
+	  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;
+	  
+      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;
+ 
+  (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))
+      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;
+		  }
+	      }
+	  }      
+      }
+
+  for (relay = daemon->relay6; relay; relay = relay->next)
+    if (IN6_ARE_ADDR_EQUAL(local, &relay->local.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;
+}
+
+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, 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, 
+			    int preferred, 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, 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 it's 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 */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h
new file mode 100755
index 0000000..496a4bb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dns-protocol.h
@@ -0,0 +1,190 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+#define NAMESERVER_PORT 53
+#define TFTP_PORT       69
+#define MIN_PORT        1024           /* first non-reserved port */
+#define MAX_PORT        65535u
+
+#define IN6ADDRSZ       16
+#define INADDRSZ        4
+
+#define PACKETSZ	512		/* maximum packet size */
+#define MAXDNAME	1025		/* maximum presentation domain name */
+#define RRFIXEDSZ	10		/* #/bytes of fixed data in r record */
+#define MAXLABEL        63              /* maximum length of domain label */
+
+#define NOERROR		0		/* no error */
+#define FORMERR		1		/* format error */
+#define SERVFAIL	2		/* server failure */
+#define NXDOMAIN	3		/* non existent domain */
+#define NOTIMP		4		/* not implemented */
+#define REFUSED		5		/* query refused */
+
+#define QUERY           0               /* opcode */
+
+#define C_IN            1               /* the arpa internet */
+#define C_CHAOS         3               /* for chaos net (MIT) */
+#define C_HESIOD        4               /* hesiod */
+#define C_ANY           255             /* wildcard match */
+
+#define T_A		1
+#define T_NS            2
+#define T_MD            3
+#define T_MF            4             
+#define T_CNAME		5
+#define T_SOA		6
+#define T_MB            7
+#define T_MG            8
+#define T_MR            9
+#define T_PTR		12
+#define T_MINFO         14
+#define T_MX		15
+#define T_TXT		16
+#define T_RP            17
+#define T_AFSDB         18
+#define T_RT            21
+#define T_SIG		24
+#define T_PX            26
+#define T_AAAA		28
+#define T_NXT           30
+#define T_SRV		33
+#define T_NAPTR		35
+#define T_KX            36
+#define T_DNAME         39
+#define T_OPT		41
+#define T_DS            43
+#define T_RRSIG         46
+#define T_NSEC          47
+#define T_DNSKEY        48
+#define T_NSEC3         50
+#define	T_TKEY		249		
+#define	T_TSIG		250
+#define T_AXFR          252
+#define T_MAILB		253	
+#define T_ANY		255
+#define T_CAA           257
+
+#define EDNS0_OPTION_MAC            65001 /* dyndns.org temporary assignment */
+#define EDNS0_OPTION_CLIENT_SUBNET  8     /* IANA */
+#define EDNS0_OPTION_EDE            15    /* IANA - RFC 8914 */
+#define EDNS0_OPTION_NOMDEVICEID    65073 /* Nominum temporary assignment */
+#define EDNS0_OPTION_NOMCPEID       65074 /* Nominum temporary assignment */
+#define EDNS0_OPTION_UMBRELLA       20292 /* Cisco Umbrella temporary assignment */
+
+/* RFC-8914 extended errors, negative values are our definitions */
+#define EDE_UNSET          -1  /* No extended DNS error available */
+#define EDE_OTHER           0  /* Other */
+#define EDE_USUPDNSKEY      1  /* Unsupported DNSKEY algo */
+#define EDE_USUPDS          2  /* Unsupported DS Digest */
+#define EDE_STALE           3  /* Stale answer */
+#define EDE_FORGED          4  /* Forged answer */
+#define EDE_DNSSEC_IND      5  /* DNSSEC Indeterminate  */
+#define EDE_DNSSEC_BOGUS    6  /* DNSSEC Bogus */
+#define EDE_SIG_EXP         7  /* Signature Expired */
+#define EDE_SIG_NYV         8  /* Signature Not Yet Valid  */
+#define EDE_NO_DNSKEY       9  /* DNSKEY missing */
+#define EDE_NO_RRSIG       10  /* RRSIGs missing */
+#define EDE_NO_ZONEKEY     11  /* No Zone Key Bit Set */
+#define EDE_NO_NSEC        12  /* NSEC Missing  */
+#define EDE_CACHED_ERR     13  /* Cached Error */
+#define EDE_NOT_READY      14  /* Not Ready */
+#define EDE_BLOCKED        15  /* Blocked */
+#define EDE_CENSORED       16  /* Censored */
+#define EDE_FILTERED       17  /* Filtered */
+#define EDE_PROHIBITED     18  /* Prohibited */
+#define EDE_STALE_NXD      19  /* Stale NXDOMAIN */
+#define EDE_NOT_AUTH       20  /* Not Authoritative */
+#define EDE_NOT_SUP        21  /* Not Supported */
+#define EDE_NO_AUTH        22  /* No Reachable Authority */
+#define EDE_NETERR         23  /* Network error */
+#define EDE_INVALID_DATA   24  /* Invalid Data */
+
+
+
+
+struct dns_header {
+  u16 id;
+  u8  hb3,hb4;
+  u16 qdcount,ancount,nscount,arcount;
+};
+
+#define HB3_QR       0x80 /* Query */
+#define HB3_OPCODE   0x78
+#define HB3_AA       0x04 /* Authoritative Answer */
+#define HB3_TC       0x02 /* TrunCated */
+#define HB3_RD       0x01 /* Recursion Desired */
+
+#define HB4_RA       0x80 /* Recursion Available */
+#define HB4_AD       0x20 /* Authenticated Data */
+#define HB4_CD       0x10 /* Checking Disabled */
+#define HB4_RCODE    0x0f
+
+#define OPCODE(x)          (((x)->hb3 & HB3_OPCODE) >> 3)
+#define SET_OPCODE(x, code) (x)->hb3 = ((x)->hb3 & ~HB3_OPCODE) | code
+
+#define RCODE(x)           ((x)->hb4 & HB4_RCODE)
+#define SET_RCODE(x, code) (x)->hb4 = ((x)->hb4 & ~HB4_RCODE) | code
+  
+#define GETSHORT(s, cp) { \
+	unsigned char *t_cp = (unsigned char *)(cp); \
+	(s) = ((u16)t_cp[0] << 8) \
+	    | ((u16)t_cp[1]) \
+	    ; \
+	(cp) += 2; \
+}
+
+#define GETLONG(l, cp) { \
+	unsigned char *t_cp = (unsigned char *)(cp); \
+	(l) = ((u32)t_cp[0] << 24) \
+	    | ((u32)t_cp[1] << 16) \
+	    | ((u32)t_cp[2] << 8) \
+	    | ((u32)t_cp[3]) \
+	    ; \
+	(cp) += 4; \
+}
+
+#define PUTSHORT(s, cp) { \
+	u16 t_s = (u16)(s); \
+	unsigned char *t_cp = (unsigned char *)(cp); \
+	*t_cp++ = t_s >> 8; \
+	*t_cp   = t_s; \
+	(cp) += 2; \
+}
+
+#define PUTLONG(l, cp) { \
+	u32 t_l = (u32)(l); \
+	unsigned char *t_cp = (unsigned char *)(cp); \
+	*t_cp++ = t_l >> 24; \
+	*t_cp++ = t_l >> 16; \
+	*t_cp++ = t_l >> 8; \
+	*t_cp   = t_l; \
+	(cp) += 4; \
+}
+
+#define CHECK_LEN(header, pp, plen, len) \
+    ((size_t)((pp) - (unsigned char *)(header) + (len)) <= (plen))
+
+#define ADD_RDLEN(header, pp, plen, len) \
+  (!CHECK_LEN(header, pp, plen, len) ? 0 : (((pp) += (len)), 1))
+
+/* Escape character in our presentation format for names.
+   Cannot be '.' or /000 and must be !isprint().
+   Note that escaped chars are stored as
+   <NAME_ESCAPE> <orig-char+1>
+   to ensure that the escaped form of /000 doesn't include /000
+*/
+#define NAME_ESCAPE 1
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c
new file mode 100755
index 0000000..0b455b8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.c
@@ -0,0 +1,2166 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* Declare static char *compiler_opts  in config.h */
+#define DNSMASQ_COMPILE_OPTS
+
+#include "dnsmasq.h"
+
+struct daemon *daemon;
+
+static volatile pid_t pid = 0;
+static volatile int pipewrite;
+
+static void set_dns_listeners(void);
+static void check_dns_listeners(time_t now);
+static void sig_handler(int sig);
+static void async_event(int pipe, time_t now);
+static void fatal_event(struct event_desc *ev, char *msg);
+static int read_event(int fd, struct event_desc *evp, char **msg);
+static void poll_resolv(int force, int do_reload, time_t now);
+
+int main (int argc, char **argv)
+{
+  int bind_fallback = 0;
+  time_t now;
+  struct sigaction sigact;
+  struct iname *if_tmp;
+  int piperead, pipefd[2], err_pipe[2];
+  struct passwd *ent_pw = NULL;
+#if defined(HAVE_SCRIPT)
+  uid_t script_uid = 0;
+  gid_t script_gid = 0;
+#endif
+  struct group *gp = NULL;
+  long i, max_fd = sysconf(_SC_OPEN_MAX);
+  char *baduser = NULL;
+  int log_err;
+  int chown_warn = 0;
+#if defined(HAVE_LINUX_NETWORK)
+  cap_user_header_t hdr = NULL;
+  cap_user_data_t data = NULL;
+  int need_cap_net_admin = 0;
+  int need_cap_net_raw = 0;
+  int need_cap_net_bind_service = 0;
+  char *bound_device = NULL;
+  int did_bind = 0;
+  struct server *serv;
+  char *netlink_warn;
+#endif 
+#if defined(HAVE_DHCP) || defined(HAVE_DHCP6)
+  struct dhcp_context *context;
+  struct dhcp_relay *relay;
+#endif
+#ifdef HAVE_TFTP
+  int tftp_prefix_missing = 0;
+#endif
+
+#ifdef LOCALEDIR
+  setlocale(LC_ALL, "");
+  bindtextdomain("dnsmasq", LOCALEDIR); 
+  textdomain("dnsmasq");
+#endif
+
+  sigact.sa_handler = sig_handler;
+  sigact.sa_flags = 0;
+  sigemptyset(&sigact.sa_mask);
+  sigaction(SIGUSR1, &sigact, NULL);
+  sigaction(SIGUSR2, &sigact, NULL);
+  sigaction(SIGHUP, &sigact, NULL);
+  sigaction(SIGTERM, &sigact, NULL);
+  sigaction(SIGALRM, &sigact, NULL);
+  sigaction(SIGCHLD, &sigact, NULL);
+  sigaction(SIGINT, &sigact, NULL);
+  
+  /* ignore SIGPIPE */
+  sigact.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &sigact, NULL);
+
+  umask(022); /* known umask, create leases and pid files as 0644 */
+
+  rand_init(); /* Must precede read_opts() */
+  
+  read_opts(argc, argv, compile_opts);
+ 
+#ifdef HAVE_LINUX_NETWORK
+  daemon->kernel_version = kernel_version();
+#endif
+
+  if (daemon->edns_pktsz < PACKETSZ)
+    daemon->edns_pktsz = PACKETSZ;
+
+  /* Min buffer size: we check after adding each record, so there must be 
+     memory for the largest packet, and the largest record so the
+     min for DNS is PACKETSZ+MAXDNAME+RRFIXEDSZ which is < 1000.
+     This might be increased is EDNS packet size if greater than the minimum. */ 
+  daemon->packet_buff_sz = daemon->edns_pktsz + MAXDNAME + RRFIXEDSZ;
+  daemon->packet = safe_malloc(daemon->packet_buff_sz);
+  
+  if (option_bool(OPT_EXTRALOG))
+    daemon->addrbuff2 = safe_malloc(ADDRSTRLEN);
+  
+#ifdef HAVE_DNSSEC
+  if (option_bool(OPT_DNSSEC_VALID))
+    {
+      /* Note that both /000 and '.' are allowed within labels. These get
+	 represented in presentation format using NAME_ESCAPE as an escape
+	 character when in DNSSEC mode. 
+	 In theory, if all the characters in a name were /000 or
+	 '.' or NAME_ESCAPE then all would have to be escaped, so the 
+	 presentation format would be twice as long as the spec.
+
+	 daemon->namebuff was previously allocated by the option-reading
+	 code before we knew if we're in DNSSEC mode, so reallocate here. */
+      free(daemon->namebuff);
+      daemon->namebuff = safe_malloc(MAXDNAME * 2);
+      daemon->keyname = safe_malloc(MAXDNAME * 2);
+      daemon->workspacename = safe_malloc(MAXDNAME * 2);
+      /* one char flag per possible RR in answer section (may get extended). */
+      daemon->rr_status_sz = 64;
+      daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
+    }
+#endif
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+  /* CONNTRACK UBUS code uses this buffer, so if not allocated above,
+     we need to allocate it here. */
+  if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
+    daemon->workspacename = safe_malloc(MAXDNAME);
+#endif
+  
+#ifdef HAVE_DHCP
+  if (!daemon->lease_file)
+    {
+      if (daemon->dhcp || daemon->dhcp6)
+	daemon->lease_file = LEASEFILE;
+    }
+#endif
+  
+  /* Ensure that at least stdin, stdout and stderr (fd 0, 1, 2) exist,
+     otherwise file descriptors we create can end up being 0, 1, or 2 
+     and then get accidentally closed later when we make 0, 1, and 2 
+     open to /dev/null. Normally we'll be started with 0, 1 and 2 open, 
+     but it's not guaranteed. By opening /dev/null three times, we 
+     ensure that we're not using those fds for real stuff. */
+  for (i = 0; i < 3; i++)
+    open("/dev/null", O_RDWR); 
+  
+  /* Close any file descriptors we inherited apart from std{in|out|err} */
+  close_fds(max_fd, -1, -1, -1);
+  
+#ifndef HAVE_LINUX_NETWORK
+#  if !(defined(IP_RECVDSTADDR) && defined(IP_RECVIF) && defined(IP_SENDSRCADDR))
+  if (!option_bool(OPT_NOWILD))
+    {
+      bind_fallback = 1;
+      set_option_bool(OPT_NOWILD);
+    }
+#  endif
+  
+  /* -- bind-dynamic not supported on !Linux, fall back to --bind-interfaces */
+  if (option_bool(OPT_CLEVERBIND))
+    {
+      bind_fallback = 1;
+      set_option_bool(OPT_NOWILD);
+      reset_option_bool(OPT_CLEVERBIND);
+    }
+#endif
+
+#ifndef HAVE_INOTIFY
+  if (daemon->dynamic_dirs)
+    die(_("dhcp-hostsdir, dhcp-optsdir and hostsdir are not supported on this platform"), NULL, EC_BADCONF);
+#endif
+  
+  if (option_bool(OPT_DNSSEC_VALID))
+    {
+#ifdef HAVE_DNSSEC
+      struct ds_config *ds;
+
+      /* Must have at least a root trust anchor, or the DNSSEC code
+	 can loop forever. */
+      for (ds = daemon->ds; ds; ds = ds->next)
+	if (ds->name[0] == 0)
+	  break;
+
+      if (!ds)
+	die(_("no root trust anchor provided for DNSSEC"), NULL, EC_BADCONF);
+      
+      if (daemon->cachesize < CACHESIZ)
+	die(_("cannot reduce cache size from default when DNSSEC enabled"), NULL, EC_BADCONF);
+#else 
+      die(_("DNSSEC not available: set HAVE_DNSSEC in src/config.h"), NULL, EC_BADCONF);
+#endif
+    }
+
+#ifndef HAVE_TFTP
+  if (option_bool(OPT_TFTP))
+    die(_("TFTP server not available: set HAVE_TFTP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifdef HAVE_CONNTRACK
+  if (option_bool(OPT_CONNTRACK))
+    {
+      if (daemon->query_port != 0 || daemon->osport)
+	die (_("cannot use --conntrack AND --query-port"), NULL, EC_BADCONF);
+
+      need_cap_net_admin = 1;
+    }
+#else
+  if (option_bool(OPT_CONNTRACK))
+    die(_("conntrack support not available: set HAVE_CONNTRACK in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifdef HAVE_SOLARIS_NETWORK
+  if (daemon->max_logs != 0)
+    die(_("asynchronous logging is not available under Solaris"), NULL, EC_BADCONF);
+#endif
+  
+#ifdef __ANDROID__
+  if (daemon->max_logs != 0)
+    die(_("asynchronous logging is not available under Android"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_AUTH
+  if (daemon->auth_zones)
+    die(_("authoritative DNS not available: set HAVE_AUTH in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_LOOP
+  if (option_bool(OPT_LOOP_DETECT))
+    die(_("loop detection not available: set HAVE_LOOP in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+#ifndef HAVE_UBUS
+  if (option_bool(OPT_UBUS))
+    die(_("Ubus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
+#endif
+  
+  /* Handle only one of min_port/max_port being set. */
+  if (daemon->min_port != 0 && daemon->max_port == 0)
+    daemon->max_port = MAX_PORT;
+  
+  if (daemon->max_port != 0 && daemon->min_port == 0)
+    daemon->min_port = MIN_PORT;
+   
+  if (daemon->max_port < daemon->min_port)
+    die(_("max_port cannot be smaller than min_port"), NULL, EC_BADCONF);
+  
+  now = dnsmasq_time();
+
+  if (daemon->auth_zones)
+    {
+      if (!daemon->authserver)
+	die(_("--auth-server required when an auth zone is defined."), NULL, EC_BADCONF);
+
+      /* Create a serial at startup if not configured. */
+#ifdef HAVE_BROKEN_RTC
+      if (daemon->soa_sn == 0)
+	die(_("zone serial must be configured in --auth-soa"), NULL, EC_BADCONF);
+#else
+      if (daemon->soa_sn == 0)
+	daemon->soa_sn = now;
+#endif
+    }
+  
+#ifdef HAVE_DHCP6
+  if (daemon->dhcp6)
+    {
+      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 !defined(HAVE_LINUX_NETWORK) && !defined(HAVE_BSD_NETWORK)
+	  if (context->flags & CONTEXT_TEMPLATE)
+	    die (_("dhcp-range constructor not available on this platform"), NULL, EC_BADCONF);
+#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.
+     The script subsystem relies on DHCP buffers, hence the last two
+     conditions below. */  
+  if (daemon->dhcp || daemon->doing_dhcp6 || daemon->relay4 || 
+      daemon->relay6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP))
+    {
+      dhcp_common_init();
+      if (daemon->dhcp || daemon->doing_dhcp6)
+	lease_init(now);
+    }
+  
+  if (daemon->dhcp || daemon->relay4)
+    {
+      dhcp_init();
+#   ifdef HAVE_LINUX_NETWORK
+      if (!option_bool(OPT_NO_PING))
+	need_cap_net_raw = 1;
+      need_cap_net_admin = 1;
+#   endif
+    }
+  
+#  ifdef HAVE_DHCP6
+  if (daemon->doing_ra || daemon->doing_dhcp6 || daemon->relay6)
+    {
+      ra_init(now);
+#   ifdef HAVE_LINUX_NETWORK
+      need_cap_net_raw = 1;
+      need_cap_net_admin = 1;
+#   endif
+    }
+  
+  if (daemon->doing_dhcp6 || daemon->relay6)
+    dhcp6_init();
+#  endif
+
+#endif
+
+#ifdef HAVE_IPSET
+  if (daemon->ipsets)
+    {
+      ipset_init();
+#  ifdef HAVE_LINUX_NETWORK
+      need_cap_net_admin = 1;
+#  endif
+    }
+#endif
+
+#if  defined(HAVE_LINUX_NETWORK)
+  netlink_warn = netlink_init();
+#elif defined(HAVE_BSD_NETWORK)
+  route_init();
+#endif
+
+  if (option_bool(OPT_NOWILD) && option_bool(OPT_CLEVERBIND))
+    die(_("cannot set --bind-interfaces and --bind-dynamic"), NULL, EC_BADCONF);
+  
+  if (!enumerate_interfaces(1) || !enumerate_interfaces(0))
+    die(_("failed to find list of interfaces: %s"), NULL, EC_MISC);
+  
+  if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND)) 
+    {
+      create_bound_listeners(1);
+      
+      if (!option_bool(OPT_CLEVERBIND))
+	for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+	  if (if_tmp->name && !if_tmp->used)
+	    die(_("unknown interface %s"), if_tmp->name, EC_BADNET);
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP)
+      /* after enumerate_interfaces()  */
+      bound_device = whichdevice();
+      
+      if (daemon->dhcp)
+	{
+	  if (!daemon->relay4 && bound_device)
+	    {
+	      bindtodevice(bound_device, daemon->dhcpfd);
+	      did_bind = 1;
+	    }
+	  if (daemon->enable_pxe && bound_device)
+	    {
+	      bindtodevice(bound_device, daemon->pxefd);
+	      did_bind = 1;
+	    }
+	}
+#endif
+
+#if defined(HAVE_LINUX_NETWORK) && defined(HAVE_DHCP6)
+      if (daemon->doing_dhcp6 && !daemon->relay6 && bound_device)
+	{
+	  bindtodevice(bound_device, daemon->dhcp6fd);
+	  did_bind = 1;
+	}
+#endif
+    }
+  else 
+    create_wildcard_listeners();
+ 
+#ifdef HAVE_DHCP6
+  /* after enumerate_interfaces() */
+  if (daemon->doing_dhcp6 || daemon->relay6 || daemon->doing_ra)
+    join_multicast(1);
+
+  /* After netlink_init() and before create_helper() */
+  lease_make_duid(now);
+#endif
+  
+  if (daemon->port != 0)
+    {
+      cache_init();
+      blockdata_init();
+      hash_questions_init();
+
+      /* Scale random socket pool by ftabsize, but
+	 limit it based on available fds. */
+      daemon->numrrand = daemon->ftabsize/2;
+      if (daemon->numrrand > max_fd/3)
+	daemon->numrrand = max_fd/3;
+      /* safe_malloc returns zero'd memory */
+      daemon->randomsocks = safe_malloc(daemon->numrrand * sizeof(struct randfd));
+    }
+
+#ifdef HAVE_INOTIFY
+  if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6)
+      && (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs))
+    inotify_dnsmasq_init();
+  else
+    daemon->inotifyfd = -1;
+#endif
+
+  if (daemon->dump_file)
+#ifdef HAVE_DUMPFILE
+    dump_init();
+  else 
+    daemon->dumpfd = -1;
+#else
+  die(_("Packet dumps not available: set HAVE_DUMP in src/config.h"), NULL, EC_BADCONF);
+#endif
+  
+  if (option_bool(OPT_DBUS))
+#ifdef HAVE_DBUS
+    {
+      char *err;
+      if ((err = dbus_init()))
+	die(_("DBus error: %s"), err, EC_MISC);
+    }
+#else
+  die(_("DBus not available: set HAVE_DBUS in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+  if (option_bool(OPT_UBUS))
+#ifdef HAVE_UBUS
+    {
+      char *err;
+      if ((err = ubus_init()))
+	die(_("UBus error: %s"), err, EC_MISC);
+    }
+#else
+  die(_("UBus not available: set HAVE_UBUS in src/config.h"), NULL, EC_BADCONF);
+#endif
+
+  if (daemon->port != 0)
+    pre_allocate_sfds();
+
+#if defined(HAVE_SCRIPT)
+  /* Note getpwnam returns static storage */
+  if ((daemon->dhcp || daemon->dhcp6) && 
+      daemon->scriptuser && 
+      (daemon->lease_change_command || daemon->luascript))
+    {
+      struct passwd *scr_pw;
+      
+      if ((scr_pw = getpwnam(daemon->scriptuser)))
+	{
+	  script_uid = scr_pw->pw_uid;
+	  script_gid = scr_pw->pw_gid;
+	 }
+      else
+	baduser = daemon->scriptuser;
+    }
+#endif
+  
+  if (daemon->username && !(ent_pw = getpwnam(daemon->username)))
+    baduser = daemon->username;
+  else if (daemon->groupname && !(gp = getgrnam(daemon->groupname)))
+    baduser = daemon->groupname;
+
+  //if (baduser)
+    //die(_("unknown user or group: %s"), baduser, EC_BADCONF);
+
+  /* implement group defaults, "dip" if available, or group associated with uid */
+  if (!daemon->group_set && !gp)
+    {
+      if (!(gp = getgrnam(CHGRP)) && ent_pw)
+	gp = getgrgid(ent_pw->pw_gid);
+      
+      /* for error message */
+      if (gp)
+	daemon->groupname = gp->gr_name; 
+    }
+
+#if defined(HAVE_LINUX_NETWORK)
+  /* We keep CAP_NETADMIN (for ARP-injection) and
+     CAP_NET_RAW (for icmp) if we're doing dhcp,
+     if we have yet to bind ports because of DAD, 
+     or we're doing it dynamically, we need CAP_NET_BIND_SERVICE. */
+  if ((is_dad_listeners() || option_bool(OPT_CLEVERBIND)) &&
+      (option_bool(OPT_TFTP) || (daemon->port != 0 && daemon->port <= 1024)))
+    need_cap_net_bind_service = 1;
+
+  /* usptream servers which bind to an interface call SO_BINDTODEVICE
+     for each TCP connection, so need CAP_NET_RAW */
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (serv->interface[0] != 0)
+      need_cap_net_raw = 1;
+
+  /* If we're doing Dbus or UBus, the above can be set dynamically,
+     (as can ports) so always (potentially) needed. */
+#ifdef HAVE_DBUS
+  if (option_bool(OPT_DBUS))
+    {
+      need_cap_net_bind_service = 1;
+      need_cap_net_raw = 1;
+    }
+#endif
+
+#ifdef HAVE_UBUS
+  if (option_bool(OPT_UBUS))
+    {
+      need_cap_net_bind_service = 1;
+      need_cap_net_raw = 1;
+    }
+#endif
+  
+  /* determine capability API version here, while we can still
+     call safe_malloc */
+  int capsize = 1; /* for header version 1 */
+  char *fail = NULL;
+  
+  hdr = safe_malloc(sizeof(*hdr));
+  
+  /* find version supported by kernel */
+  memset(hdr, 0, sizeof(*hdr));
+  capget(hdr, NULL);
+  
+  if (hdr->version != LINUX_CAPABILITY_VERSION_1)
+    {
+      /* if unknown version, use largest supported version (3) */
+      if (hdr->version != LINUX_CAPABILITY_VERSION_2)
+	hdr->version = LINUX_CAPABILITY_VERSION_3;
+      capsize = 2;
+    }
+  
+  data = safe_malloc(sizeof(*data) * capsize);
+  capget(hdr, data); /* Get current values, for verification */
+
+  if (need_cap_net_admin && !(data->permitted & (1 << CAP_NET_ADMIN)))
+    fail = "NET_ADMIN";
+  else if (need_cap_net_raw && !(data->permitted & (1 << CAP_NET_RAW)))
+    fail = "NET_RAW";
+  else if (need_cap_net_bind_service && !(data->permitted & (1 << CAP_NET_BIND_SERVICE)))
+    fail = "NET_BIND_SERVICE";
+  
+  if (fail)
+    die(_("process is missing required capability %s"), fail, EC_MISC);
+
+  /* Now set bitmaps to set caps after daemonising */
+  memset(data, 0, sizeof(*data) * capsize);
+  
+  if (need_cap_net_admin)
+    data->effective |= (1 << CAP_NET_ADMIN);
+  if (need_cap_net_raw)
+    data->effective |= (1 << CAP_NET_RAW);
+  if (need_cap_net_bind_service)
+    data->effective |= (1 << CAP_NET_BIND_SERVICE);
+  
+  data->permitted = data->effective;  
+#endif
+
+  /* Use a pipe to carry signals and other events back to the event loop 
+     in a race-free manner and another to carry errors to daemon-invoking process */
+  safe_pipe(pipefd, 1);
+  
+  piperead = pipefd[0];
+  pipewrite = pipefd[1];
+  /* prime the pipe to load stuff first time. */
+  send_event(pipewrite, EVENT_INIT, 0, NULL); 
+
+  err_pipe[1] = -1;
+  
+  if (!option_bool(OPT_DEBUG))   
+    {
+      /* The following code "daemonizes" the process. 
+	 See Stevens section 12.4 */
+      
+      if (chdir("/") != 0)
+	die(_("cannot chdir to filesystem root: %s"), NULL, EC_MISC); 
+
+      if (!option_bool(OPT_NO_FORK))
+	{
+	  pid_t pid;
+	  
+	  /* pipe to carry errors back to original process.
+	     When startup is complete we close this and the process terminates. */
+	  safe_pipe(err_pipe, 0);
+	  
+	  if ((pid = fork()) == -1)
+	    /* fd == -1 since we've not forked, never returns. */
+	    send_event(-1, EVENT_FORK_ERR, errno, NULL);
+	   
+	  if (pid != 0)
+	    {
+	      struct event_desc ev;
+	      char *msg;
+
+	      /* close our copy of write-end */
+	      close(err_pipe[1]);
+	      
+	      /* check for errors after the fork */
+	      if (read_event(err_pipe[0], &ev, &msg))
+		fatal_event(&ev, msg);
+	      
+	      _exit(EC_GOOD);
+	    } 
+	  
+	  close(err_pipe[0]);
+
+	  /* NO calls to die() from here on. */
+	  
+	  setsid();
+	 
+	  if ((pid = fork()) == -1)
+	    send_event(err_pipe[1], EVENT_FORK_ERR, errno, NULL);
+	 
+	  if (pid != 0)
+	    _exit(0);
+	}
+            
+      /* write pidfile _after_ forking ! */
+      if (daemon->runfile)
+	{
+	  int fd, err = 0;
+
+	  sprintf(daemon->namebuff, "%d\n", (int) getpid());
+
+	  /* Explanation: Some installations of dnsmasq (eg Debian/Ubuntu) locate the pid-file
+	     in a directory which is writable by the non-privileged user that dnsmasq runs as. This
+	     allows the daemon to delete the file as part of its shutdown. This is a security hole to the 
+	     extent that an attacker running as the unprivileged  user could replace the pidfile with a 
+	     symlink, and have the target of that symlink overwritten as root next time dnsmasq starts. 
+
+	     The following code first deletes any existing file, and then opens it with the O_EXCL flag,
+	     ensuring that the open() fails should there be any existing file (because the unlink() failed, 
+	     or an attacker exploited the race between unlink() and open()). This ensures that no symlink
+	     attack can succeed. 
+
+	     Any compromise of the non-privileged user still theoretically allows the pid-file to be
+	     replaced whilst dnsmasq is running. The worst that could allow is that the usual 
+	     "shutdown dnsmasq" shell command could be tricked into stopping any other process.
+
+	     Note that if dnsmasq is started as non-root (eg for testing) it silently ignores 
+	     failure to write the pid-file.
+	  */
+
+	  unlink(daemon->runfile); 
+	  
+	  if ((fd = open(daemon->runfile, O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)) == -1)
+	    {
+	      /* only complain if started as root */
+	      if (getuid() == 0)
+		err = 1;
+	    }
+	  else
+	    {
+	      /* We're still running as root here. Change the ownership of the PID file
+		 to the user we will be running as. Note that this is not to allow
+		 us to delete the file, since that depends on the permissions 
+		 of the directory containing the file. That directory will
+		 need to by owned by the dnsmasq user, and the ownership of the
+		 file has to match, to keep systemd >273 happy. */
+	      if (getuid() == 0 && ent_pw && ent_pw->pw_uid != 0 && fchown(fd, ent_pw->pw_uid, ent_pw->pw_gid) == -1)
+		chown_warn = errno;
+
+	      if (!read_write(fd, (unsigned char *)daemon->namebuff, strlen(daemon->namebuff), 0))
+		err = 1;
+	      else
+		{
+		  if (close(fd) == -1)
+		    err = 1;
+		}
+	    }
+
+	  if (err)
+	    {
+	      send_event(err_pipe[1], EVENT_PIDFILE, errno, daemon->runfile);
+	      _exit(0);
+	    }
+	}
+    }
+  
+   log_err = log_start(ent_pw, err_pipe[1]);
+
+   if (!option_bool(OPT_DEBUG)) 
+     {       
+       /* open  stdout etc to /dev/null */
+       int nullfd = open("/dev/null", O_RDWR);
+       if (nullfd != -1)
+	 {
+	   dup2(nullfd, STDOUT_FILENO);
+	   dup2(nullfd, STDERR_FILENO);
+	   dup2(nullfd, STDIN_FILENO);
+	   close(nullfd);
+	 }
+     }
+   
+   /* if we are to run scripts, we need to fork a helper before dropping root. */
+  daemon->helperfd = -1;
+#ifdef HAVE_SCRIPT 
+  if ((daemon->dhcp || daemon->dhcp6 || option_bool(OPT_TFTP) || option_bool(OPT_SCRIPT_ARP)) && 
+      (daemon->lease_change_command || daemon->luascript))
+      daemon->helperfd = create_helper(pipewrite, err_pipe[1], script_uid, script_gid, max_fd);
+#endif
+
+  if (!option_bool(OPT_DEBUG) && getuid() == 0)   
+    {
+      int bad_capabilities = 0;
+      gid_t dummy;
+      
+      /* remove all supplementary groups */
+      if (gp && 
+	  (setgroups(0, &dummy) == -1 ||
+	   setgid(gp->gr_gid) == -1))
+	{
+	  send_event(err_pipe[1], EVENT_GROUP_ERR, errno, daemon->groupname);
+	  _exit(0);
+	}
+  
+      if (ent_pw && ent_pw->pw_uid != 0)
+	{     
+#if defined(HAVE_LINUX_NETWORK)	  
+	  /* Need to be able to drop root. */
+	  data->effective |= (1 << CAP_SETUID);
+	  data->permitted |= (1 << CAP_SETUID);
+	  /* Tell kernel to not clear capabilities when dropping root */
+	  if (capset(hdr, data) == -1 || prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) == -1)
+	    bad_capabilities = errno;
+			  
+#elif defined(HAVE_SOLARIS_NETWORK)
+	  /* http://developers.sun.com/solaris/articles/program_privileges.html */
+	  priv_set_t *priv_set;
+	  
+	  if (!(priv_set = priv_str_to_set("basic", ",", NULL)) ||
+	      priv_addset(priv_set, PRIV_NET_ICMPACCESS) == -1 ||
+	      priv_addset(priv_set, PRIV_SYS_NET_CONFIG) == -1)
+	    bad_capabilities = errno;
+
+	  if (priv_set && bad_capabilities == 0)
+	    {
+	      priv_inverse(priv_set);
+	  
+	      if (setppriv(PRIV_OFF, PRIV_LIMIT, priv_set) == -1)
+		bad_capabilities = errno;
+	    }
+
+	  if (priv_set)
+	    priv_freeset(priv_set);
+
+#endif    
+
+	  if (bad_capabilities != 0)
+	    {
+	      send_event(err_pipe[1], EVENT_CAP_ERR, bad_capabilities, NULL);
+	      _exit(0);
+	    }
+	  
+	  /* finally drop root */
+	  if (setuid(ent_pw->pw_uid) == -1)
+	    {
+	      send_event(err_pipe[1], EVENT_USER_ERR, errno, daemon->username);
+	      _exit(0);
+	    }     
+
+#ifdef HAVE_LINUX_NETWORK
+	  data->effective &= ~(1 << CAP_SETUID);
+	  data->permitted &= ~(1 << CAP_SETUID);
+	  
+	  /* lose the setuid capability */
+	  if (capset(hdr, data) == -1)
+	    {
+	      send_event(err_pipe[1], EVENT_CAP_ERR, errno, NULL);
+	      _exit(0);
+	    }
+#endif
+	  
+	}
+    }
+  
+#ifdef HAVE_LINUX_NETWORK
+  free(hdr);
+  free(data);
+  if (option_bool(OPT_DEBUG)) 
+    prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
+#endif
+
+#ifdef HAVE_TFTP
+  if (option_bool(OPT_TFTP))
+    {
+      DIR *dir;
+      struct tftp_prefix *p;
+      
+      if (daemon->tftp_prefix)
+	{
+	  if (!((dir = opendir(daemon->tftp_prefix))))
+	    {
+	      tftp_prefix_missing = 1;
+	      if (!option_bool(OPT_TFTP_NO_FAIL))
+	        {
+	          send_event(err_pipe[1], EVENT_TFTP_ERR, errno, daemon->tftp_prefix);
+	          _exit(0);
+	        }
+	    }
+	  else
+	    closedir(dir);
+	}
+
+      for (p = daemon->if_prefix; p; p = p->next)
+	{
+	  p->missing = 0;
+	  if (!((dir = opendir(p->prefix))))
+	    {
+	      p->missing = 1;
+	      if (!option_bool(OPT_TFTP_NO_FAIL))
+		{
+		  send_event(err_pipe[1], EVENT_TFTP_ERR, errno, p->prefix);
+		  _exit(0);
+		}
+	    }
+	  else
+	    closedir(dir);
+	}
+    }
+#endif
+
+  if (daemon->port == 0)
+    my_syslog(LOG_INFO, _("started, version %s DNS disabled"), VERSION);
+  else 
+    {
+      if (daemon->cachesize != 0)
+	{
+	  my_syslog(LOG_INFO, _("started, version %s cachesize %d"), VERSION, daemon->cachesize);
+	  if (daemon->cachesize > 10000)
+	    my_syslog(LOG_WARNING, _("cache size greater than 10000 may cause performance issues, and is unlikely to be useful."));
+	}
+      else
+	my_syslog(LOG_INFO, _("started, version %s cache disabled"), VERSION);
+
+      if (option_bool(OPT_LOCAL_SERVICE))
+	my_syslog(LOG_INFO, _("DNS service limited to local subnets"));
+    }
+  
+  my_syslog(LOG_INFO, _("compile time options: %s"), compile_opts);
+
+  if (chown_warn != 0)
+    my_syslog(LOG_WARNING, "chown of PID file %s failed: %s", daemon->runfile, strerror(chown_warn));
+  
+#ifdef HAVE_DBUS
+  if (option_bool(OPT_DBUS))
+    {
+      if (daemon->dbus)
+	my_syslog(LOG_INFO, _("DBus support enabled: connected to system bus"));
+      else
+	my_syslog(LOG_INFO, _("DBus support enabled: bus connection pending"));
+    }
+#endif
+
+#ifdef HAVE_UBUS
+  if (option_bool(OPT_UBUS))
+    {
+      if (daemon->ubus)
+        my_syslog(LOG_INFO, _("UBus support enabled: connected to system bus"));
+      else
+        my_syslog(LOG_INFO, _("UBus support enabled: bus connection pending"));
+    }
+#endif
+
+#ifdef HAVE_DNSSEC
+  if (option_bool(OPT_DNSSEC_VALID))
+    {
+      int rc;
+      struct ds_config *ds;
+      
+      /* Delay creating the timestamp file until here, after we've changed user, so that
+	 it has the correct owner to allow updating the mtime later. 
+	 This means we have to report fatal errors via the pipe. */
+      if ((rc = setup_timestamp()) == -1)
+	{
+	  send_event(err_pipe[1], EVENT_TIME_ERR, errno, daemon->timestamp_file);
+	  _exit(0);
+	}
+      
+      if (option_bool(OPT_DNSSEC_IGN_NS))
+	my_syslog(LOG_INFO, _("DNSSEC validation enabled but all unsigned answers are trusted"));
+      else
+	my_syslog(LOG_INFO, _("DNSSEC validation enabled"));
+      
+      daemon->dnssec_no_time_check = option_bool(OPT_DNSSEC_TIME);
+      if (option_bool(OPT_DNSSEC_TIME) && !daemon->back_to_the_future)
+	my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until receipt of SIGINT"));
+      
+      if (rc == 1)
+	my_syslog(LOG_INFO, _("DNSSEC signature timestamps not checked until system time valid"));
+
+      for (ds = daemon->ds; ds; ds = ds->next)
+	my_syslog(LOG_INFO, _("configured with trust anchor for %s keytag %u"),
+		  ds->name[0] == 0 ? "<root>" : ds->name, ds->keytag);
+    }
+#endif
+
+  if (log_err != 0)
+    my_syslog(LOG_WARNING, _("warning: failed to change owner of %s: %s"), 
+	      daemon->log_file, strerror(log_err));
+  
+  if (bind_fallback)
+    my_syslog(LOG_WARNING, _("setting --bind-interfaces option because of OS limitations"));
+
+  if (option_bool(OPT_NOWILD))
+    warn_bound_listeners();
+  else if (!option_bool(OPT_CLEVERBIND))
+    warn_wild_labels();
+
+  warn_int_names();
+  
+  if (!option_bool(OPT_NOWILD)) 
+    for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+      if (if_tmp->name && !if_tmp->used)
+	my_syslog(LOG_WARNING, _("warning: interface %s does not currently exist"), if_tmp->name);
+   
+  if (daemon->port != 0 && option_bool(OPT_NO_RESOLV))
+    {
+      if (daemon->resolv_files && !daemon->resolv_files->is_default)
+	my_syslog(LOG_WARNING, _("warning: ignoring resolv-file flag because no-resolv is set"));
+      daemon->resolv_files = NULL;
+      if (!daemon->servers)
+	my_syslog(LOG_WARNING, _("warning: no upstream servers configured"));
+    } 
+
+  if (daemon->max_logs != 0)
+    my_syslog(LOG_INFO, _("asynchronous logging enabled, queue limit is %d messages"), daemon->max_logs);
+  
+
+#ifdef HAVE_DHCP
+  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);
+  
+  if (option_bool(OPT_RA))
+    my_syslog(MS_DHCP | LOG_INFO, _("IPv6 router advertisement enabled"));
+#  endif
+
+#  ifdef HAVE_LINUX_NETWORK
+  if (did_bind)
+    my_syslog(MS_DHCP | LOG_INFO, _("DHCP, sockets bound exclusively to interface %s"), bound_device);
+
+  if (netlink_warn)
+    my_syslog(LOG_WARNING, netlink_warn);
+#  endif
+
+  /* after dhcp_construct_contexts */
+  if (daemon->dhcp || daemon->doing_dhcp6)
+    lease_find_interfaces(now);
+#endif
+
+#ifdef HAVE_TFTP
+  if (option_bool(OPT_TFTP))
+    {
+      struct tftp_prefix *p;
+
+      my_syslog(MS_TFTP | LOG_INFO, "TFTP %s%s %s %s", 
+		daemon->tftp_prefix ? _("root is ") : _("enabled"),
+		daemon->tftp_prefix ? daemon->tftp_prefix : "",
+		option_bool(OPT_TFTP_SECURE) ? _("secure mode") : "",
+		option_bool(OPT_SINGLE_PORT) ? _("single port mode") : "");
+
+      if (tftp_prefix_missing)
+	my_syslog(MS_TFTP | LOG_WARNING, _("warning: %s inaccessible"), daemon->tftp_prefix);
+
+      for (p = daemon->if_prefix; p; p = p->next)
+	if (p->missing)
+	   my_syslog(MS_TFTP | LOG_WARNING, _("warning: TFTP directory %s inaccessible"), p->prefix);
+
+      /* This is a guess, it assumes that for small limits, 
+	 disjoint files might be served, but for large limits, 
+	 a single file will be sent to may clients (the file only needs
+	 one fd). */
+
+      max_fd -= 30 + daemon->numrrand; /* use other than TFTP */
+      
+      if (max_fd < 0)
+	max_fd = 5;
+      else if (max_fd < 100 && !option_bool(OPT_SINGLE_PORT))
+	max_fd = max_fd/2;
+      else
+	max_fd = max_fd - 20;
+      
+      /* if we have to use a limited range of ports, 
+	 that will limit the number of transfers */
+      if (daemon->start_tftp_port != 0 &&
+	  daemon->end_tftp_port - daemon->start_tftp_port + 1 < max_fd)
+	max_fd = daemon->end_tftp_port - daemon->start_tftp_port + 1;
+
+      if (daemon->tftp_max > max_fd)
+	{
+	  daemon->tftp_max = max_fd;
+	  my_syslog(MS_TFTP | LOG_WARNING, 
+		    _("restricting maximum simultaneous TFTP transfers to %d"), 
+		    daemon->tftp_max);
+	}
+    }
+#endif
+
+  /* finished start-up - release original process */
+  if (err_pipe[1] != -1)
+    close(err_pipe[1]);
+  
+  if (daemon->port != 0)
+    check_servers(0);
+  
+  pid = getpid();
+
+  daemon->pipe_to_parent = -1;
+  for (i = 0; i < MAX_PROCS; i++)
+    daemon->tcp_pipes[i] = -1;
+  
+#ifdef HAVE_INOTIFY
+  /* Using inotify, have to select a resolv file at startup */
+  poll_resolv(1, 0, now);
+#endif
+  
+  while (1)
+    {
+      int timeout = -1;
+      
+      poll_reset();
+      
+      /* Whilst polling for the dbus, or doing a tftp transfer, wake every quarter second */
+      if (daemon->tftp_trans ||
+	  (option_bool(OPT_DBUS) && !daemon->dbus))
+	timeout = 250;
+
+      /* Wake every second whilst waiting for DAD to complete */
+      else if (is_dad_listeners())
+	timeout = 1000;
+
+      set_dns_listeners();
+
+#ifdef HAVE_DBUS
+      if (option_bool(OPT_DBUS))
+	set_dbus_listeners();
+#endif
+      
+#ifdef HAVE_UBUS
+      if (option_bool(OPT_UBUS))
+        set_ubus_listeners();
+#endif
+      
+#ifdef HAVE_DHCP
+      if (daemon->dhcp || daemon->relay4)
+	{
+	  poll_listen(daemon->dhcpfd, POLLIN);
+	  if (daemon->pxefd != -1)
+	    poll_listen(daemon->pxefd, POLLIN);
+	}
+#endif
+
+#ifdef HAVE_DHCP6
+      if (daemon->doing_dhcp6 || daemon->relay6)
+	poll_listen(daemon->dhcp6fd, POLLIN);
+	
+      if (daemon->doing_ra)
+	poll_listen(daemon->icmp6fd, POLLIN); 
+#endif
+    
+#ifdef HAVE_INOTIFY
+      if (daemon->inotifyfd != -1)
+	poll_listen(daemon->inotifyfd, POLLIN);
+#endif
+
+#if defined(HAVE_LINUX_NETWORK)
+      poll_listen(daemon->netlinkfd, POLLIN);
+#elif defined(HAVE_BSD_NETWORK)
+      poll_listen(daemon->routefd, POLLIN);
+#endif
+      
+      poll_listen(piperead, POLLIN);
+
+#ifdef HAVE_SCRIPT
+#    ifdef HAVE_DHCP
+      while (helper_buf_empty() && do_script_run(now)); 
+#    endif
+
+      /* Refresh cache */
+      if (option_bool(OPT_SCRIPT_ARP))
+	find_mac(NULL, NULL, 0, now);
+      while (helper_buf_empty() && do_arp_script_run());
+
+#    ifdef HAVE_TFTP
+      while (helper_buf_empty() && do_tftp_script_run());
+#    endif
+
+      if (!helper_buf_empty())
+	poll_listen(daemon->helperfd, POLLOUT);
+#else
+      /* need this for other side-effects */
+#    ifdef HAVE_DHCP
+      while (do_script_run(now));
+#    endif
+
+      while (do_arp_script_run());
+
+#    ifdef HAVE_TFTP 
+      while (do_tftp_script_run());
+#    endif
+
+#endif
+
+   
+      /* must do this just before do_poll(), when we know no
+	 more calls to my_syslog() can occur */
+      set_log_writer();
+      
+      if (do_poll(timeout) < 0)
+	continue;
+      
+      now = dnsmasq_time();
+
+      check_log_writer(0);
+
+      /* prime. */
+      enumerate_interfaces(1);
+
+      /* Check the interfaces to see if any have exited DAD state
+	 and if so, bind the address. */
+      if (is_dad_listeners())
+	{
+	  enumerate_interfaces(0);
+	  /* NB, is_dad_listeners() == 1 --> we're binding interfaces */
+	  create_bound_listeners(0);
+	  warn_bound_listeners();
+	}
+
+#if defined(HAVE_LINUX_NETWORK)
+      if (poll_check(daemon->netlinkfd, POLLIN))
+	netlink_multicast();
+#elif defined(HAVE_BSD_NETWORK)
+      if (poll_check(daemon->routefd, POLLIN))
+	route_sock();
+#endif
+
+#ifdef HAVE_INOTIFY
+      if  (daemon->inotifyfd != -1 && poll_check(daemon->inotifyfd, POLLIN) && inotify_check(now))
+	{
+	  if (daemon->port != 0 && !option_bool(OPT_NO_POLL))
+	    poll_resolv(1, 1, now);
+	} 	  
+#else
+      /* Check for changes to resolv files once per second max. */
+      /* Don't go silent for long periods if the clock goes backwards. */
+      if (daemon->last_resolv == 0 || 
+	  difftime(now, daemon->last_resolv) > 1.0 || 
+	  difftime(now, daemon->last_resolv) < -1.0)
+	{
+	  /* poll_resolv doesn't need to reload first time through, since 
+	     that's queued anyway. */
+
+	  poll_resolv(0, daemon->last_resolv != 0, now); 	  
+	  daemon->last_resolv = now;
+	}
+#endif
+
+      if (poll_check(piperead, POLLIN))
+	async_event(piperead, now);
+      
+#ifdef HAVE_DBUS
+      /* if we didn't create a DBus connection, retry now. */ 
+      if (option_bool(OPT_DBUS))
+	{
+	  if (!daemon->dbus)
+	    {
+	      char *err  = dbus_init();
+
+	      if (daemon->dbus)
+		my_syslog(LOG_INFO, _("connected to system DBus"));
+	      else if (err)
+		{
+		  my_syslog(LOG_ERR, _("DBus error: %s"), err);
+		  reset_option_bool(OPT_DBUS); /* fatal error, stop trying. */
+		}
+	    }
+	  
+	  check_dbus_listeners();
+	}
+#endif
+
+#ifdef HAVE_UBUS
+      /* if we didn't create a UBus connection, retry now. */
+      if (option_bool(OPT_UBUS))
+	{
+	  if (!daemon->ubus)
+	    {
+	      char *err = ubus_init();
+
+	      if (daemon->ubus)
+		my_syslog(LOG_INFO, _("connected to system UBus"));
+	      else if (err)
+		{
+		  my_syslog(LOG_ERR, _("UBus error: %s"), err);
+		  reset_option_bool(OPT_UBUS); /* fatal error, stop trying. */
+		}
+	    }
+	  
+	  check_ubus_listeners();
+	}
+#endif
+
+      check_dns_listeners(now);
+
+#ifdef HAVE_TFTP
+      check_tftp_listeners(now);
+#endif      
+
+#ifdef HAVE_DHCP
+      if (daemon->dhcp || daemon->relay4)
+	{
+	  if (poll_check(daemon->dhcpfd, POLLIN))
+	    dhcp_packet(now, 0);
+	  if (daemon->pxefd != -1 && poll_check(daemon->pxefd, POLLIN))
+	    dhcp_packet(now, 1);
+	}
+
+#ifdef HAVE_DHCP6
+      if ((daemon->doing_dhcp6 || daemon->relay6) && poll_check(daemon->dhcp6fd, POLLIN))
+	dhcp6_packet(now);
+
+      if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN))
+	icmp6_packet(now);
+#endif
+
+#  ifdef HAVE_SCRIPT
+      if (daemon->helperfd != -1 && poll_check(daemon->helperfd, POLLOUT))
+	helper_write();
+#  endif
+#endif
+
+    }
+}
+
+static void sig_handler(int sig)
+{
+  if (pid == 0)
+    {
+      /* ignore anything other than TERM during startup
+	 and in helper proc. (helper ignore TERM too) */
+      if (sig == SIGTERM || sig == SIGINT)
+	exit(EC_MISC);
+    }
+  else if (pid != getpid())
+    {
+      /* alarm is used to kill TCP children after a fixed time. */
+      if (sig == SIGALRM)
+	_exit(0);
+    }
+  else
+    {
+      /* master process */
+      int event, errsave = errno;
+      
+      if (sig == SIGHUP)
+	event = EVENT_RELOAD;
+      else if (sig == SIGCHLD)
+	event = EVENT_CHILD;
+      else if (sig == SIGALRM)
+	event = EVENT_ALARM;
+      else if (sig == SIGTERM)
+	event = EVENT_TERM;
+      else if (sig == SIGUSR1)
+	event = EVENT_DUMP;
+      else if (sig == SIGUSR2)
+	event = EVENT_REOPEN;
+      else if (sig == SIGINT)
+	{
+	  /* Handle SIGINT normally in debug mode, so
+	     ctrl-c continues to operate. */
+	  if (option_bool(OPT_DEBUG))
+	    exit(EC_MISC);
+	  else
+	    event = EVENT_TIME;
+	}
+      else
+	return;
+
+      send_event(pipewrite, event, 0, NULL); 
+      errno = errsave;
+    }
+}
+
+/* now == 0 -> queue immediate callback */
+void send_alarm(time_t event, time_t now)
+{
+  if (now == 0 || event != 0)
+    {
+      /* alarm(0) or alarm(-ve) doesn't do what we want.... */
+      if ((now == 0 || difftime(event, now) <= 0.0))
+	send_event(pipewrite, EVENT_ALARM, 0, NULL);
+      else 
+	alarm((unsigned)difftime(event, now)); 
+    }
+}
+
+void queue_event(int event)
+{
+  send_event(pipewrite, event, 0, NULL);
+}
+
+void send_event(int fd, int event, int data, char *msg)
+{
+  struct event_desc ev;
+  struct iovec iov[2];
+
+  ev.event = event;
+  ev.data = data;
+  ev.msg_sz = msg ? strlen(msg) : 0;
+  
+  iov[0].iov_base = &ev;
+  iov[0].iov_len = sizeof(ev);
+  iov[1].iov_base = msg;
+  iov[1].iov_len = ev.msg_sz;
+  
+  /* error pipe, debug mode. */
+  if (fd == -1)
+    fatal_event(&ev, msg);
+  else
+    /* pipe is non-blocking and struct event_desc is smaller than
+       PIPE_BUF, so this either fails or writes everything */
+    while (writev(fd, iov, msg ? 2 : 1) == -1 && errno == EINTR);
+}
+
+/* NOTE: the memory used to return msg is leaked: use msgs in events only
+   to describe fatal errors. */
+static int read_event(int fd, struct event_desc *evp, char **msg)
+{
+  char *buf;
+
+  if (!read_write(fd, (unsigned char *)evp, sizeof(struct event_desc), 1))
+    return 0;
+  
+  *msg = NULL;
+  
+  if (evp->msg_sz != 0 && 
+      (buf = malloc(evp->msg_sz + 1)) &&
+      read_write(fd, (unsigned char *)buf, evp->msg_sz, 1))
+    {
+      buf[evp->msg_sz] = 0;
+      *msg = buf;
+    }
+
+  return 1;
+}
+    
+static void fatal_event(struct event_desc *ev, char *msg)
+{
+  errno = ev->data;
+  
+  switch (ev->event)
+    {
+    case EVENT_DIE:
+      exit(0);
+
+    case EVENT_FORK_ERR:
+      die(_("cannot fork into background: %s"), NULL, EC_MISC);
+
+      /* fall through */
+    case EVENT_PIPE_ERR:
+      die(_("failed to create helper: %s"), NULL, EC_MISC);
+
+      /* fall through */
+    case EVENT_CAP_ERR:
+      die(_("setting capabilities failed: %s"), NULL, EC_MISC);
+
+      /* fall through */
+    case EVENT_USER_ERR:
+      die(_("failed to change user-id to %s: %s"), msg, EC_MISC);
+
+      /* fall through */
+    case EVENT_GROUP_ERR:
+      die(_("failed to change group-id to %s: %s"), msg, EC_MISC);
+
+      /* fall through */
+    case EVENT_PIDFILE:
+      die(_("failed to open pidfile %s: %s"), msg, EC_FILE);
+
+      /* fall through */
+    case EVENT_LOG_ERR:
+      die(_("cannot open log %s: %s"), msg, EC_FILE);
+
+      /* fall through */
+    case EVENT_LUA_ERR:
+      die(_("failed to load Lua script: %s"), msg, EC_MISC);
+
+      /* fall through */
+    case EVENT_TFTP_ERR:
+      die(_("TFTP directory %s inaccessible: %s"), msg, EC_FILE);
+
+      /* fall through */
+    case EVENT_TIME_ERR:
+      die(_("cannot create timestamp file %s: %s" ), msg, EC_BADCONF);
+    }
+}	
+      
+static void async_event(int pipe, time_t now)
+{
+  pid_t p;
+  struct event_desc ev;
+  int i, check = 0;
+  char *msg;
+  
+  /* NOTE: the memory used to return msg is leaked: use msgs in events only
+     to describe fatal errors. */
+  
+  if (read_event(pipe, &ev, &msg))
+    switch (ev.event)
+      {
+      case EVENT_RELOAD:
+	daemon->soa_sn++; /* Bump zone serial, as it may have changed. */
+	
+	/* fall through */
+	
+      case EVENT_INIT:
+	clear_cache_and_reload(now);
+	
+	if (daemon->port != 0)
+	  {
+	    if (daemon->resolv_files && option_bool(OPT_NO_POLL))
+	      {
+		reload_servers(daemon->resolv_files->name);
+		check = 1;
+	      }
+
+	    if (daemon->servers_file)
+	      {
+		read_servers_file();
+		check = 1;
+	      }
+
+	    if (check)
+	      check_servers(0);
+	  }
+
+#ifdef HAVE_DHCP
+	rerun_scripts();
+#endif
+	break;
+	
+      case EVENT_DUMP:
+	if (daemon->port != 0)
+	  dump_cache(now);
+	break;
+	
+      case EVENT_ALARM:
+#ifdef HAVE_DHCP
+	if (daemon->dhcp || daemon->doing_dhcp6)
+	  {
+	    lease_prune(NULL, now);
+	    lease_update_file(now);
+	  }
+#ifdef HAVE_DHCP6
+	else if (daemon->doing_ra)
+	  /* Not doing DHCP, so no lease system, manage alarms for ra only */
+	    send_alarm(periodic_ra(now), now);
+#endif
+#endif
+	break;
+		
+      case EVENT_CHILD:
+	/* See Stevens 5.10 */
+	while ((p = waitpid(-1, NULL, WNOHANG)) != 0)
+	  if (p == -1)
+	    {
+	      if (errno != EINTR)
+		break;
+	    }      
+	  else 
+	    for (i = 0 ; i < MAX_PROCS; i++)
+	      if (daemon->tcp_pids[i] == p)
+		daemon->tcp_pids[i] = 0;
+	break;
+	
+#if defined(HAVE_SCRIPT)	
+      case EVENT_KILLED:
+	my_syslog(LOG_WARNING, _("script process killed by signal %d"), ev.data);
+	break;
+
+      case EVENT_EXITED:
+	my_syslog(LOG_WARNING, _("script process exited with status %d"), ev.data);
+	break;
+
+      case EVENT_EXEC_ERR:
+	my_syslog(LOG_ERR, _("failed to execute %s: %s"), 
+		  daemon->lease_change_command, strerror(ev.data));
+	break;
+
+      case EVENT_SCRIPT_LOG:
+	my_syslog(MS_SCRIPT | LOG_DEBUG, "%s", msg ? msg : "");
+        free(msg);
+	msg = NULL;
+	break;
+
+	/* necessary for fatal errors in helper */
+      case EVENT_USER_ERR:
+      case EVENT_DIE:
+      case EVENT_LUA_ERR:
+	fatal_event(&ev, msg);
+	break;
+#endif
+
+      case EVENT_REOPEN:
+	/* Note: this may leave TCP-handling processes with the old file still open.
+	   Since any such process will die in CHILD_LIFETIME or probably much sooner,
+	   we leave them logging to the old file. */
+	if (daemon->log_file != NULL)
+	  log_reopen(daemon->log_file);
+	break;
+
+      case EVENT_NEWADDR:
+	newaddress(now);
+	break;
+
+      case EVENT_NEWROUTE:
+	resend_query();
+	/* Force re-reading resolv file right now, for luck. */
+	poll_resolv(0, 1, now);
+	break;
+
+      case EVENT_TIME:
+#ifdef HAVE_DNSSEC
+	if (daemon->dnssec_no_time_check && option_bool(OPT_DNSSEC_VALID) && option_bool(OPT_DNSSEC_TIME))
+	  {
+	    my_syslog(LOG_INFO, _("now checking DNSSEC signature timestamps"));
+	    daemon->dnssec_no_time_check = 0;
+	    clear_cache_and_reload(now);
+	  }
+#endif
+	break;
+	
+      case EVENT_TERM:
+	/* Knock all our children on the head. */
+	for (i = 0; i < MAX_PROCS; i++)
+	  if (daemon->tcp_pids[i] != 0)
+	    kill(daemon->tcp_pids[i], SIGALRM);
+	
+#if defined(HAVE_SCRIPT) && defined(HAVE_DHCP)
+	/* handle pending lease transitions */
+	if (daemon->helperfd != -1)
+	  {
+	    /* block in writes until all done */
+	    if ((i = fcntl(daemon->helperfd, F_GETFL)) != -1)
+	      fcntl(daemon->helperfd, F_SETFL, i & ~O_NONBLOCK); 
+	    do {
+	      helper_write();
+	    } while (!helper_buf_empty() || do_script_run(now));
+	    close(daemon->helperfd);
+	  }
+#endif
+	
+	if (daemon->lease_stream)
+	  fclose(daemon->lease_stream);
+
+#ifdef HAVE_DNSSEC
+	/* update timestamp file on TERM if time is considered valid */
+	if (daemon->back_to_the_future)
+	  {
+	     if (utimes(daemon->timestamp_file, NULL) == -1)
+		my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+	  }
+#endif
+
+	if (daemon->runfile)
+	  unlink(daemon->runfile);
+
+#ifdef HAVE_DUMPFILE
+	if (daemon->dumpfd != -1)
+	  close(daemon->dumpfd);
+#endif
+	
+	my_syslog(LOG_INFO, _("exiting on receipt of SIGTERM"));
+	flush_log();
+	exit(EC_GOOD);
+      }
+}
+
+static void poll_resolv(int force, int do_reload, time_t now)
+{
+  struct resolvc *res, *latest;
+  struct stat statbuf;
+  time_t last_change = 0;
+  /* There may be more than one possible file. 
+     Go through and find the one which changed _last_.
+     Warn of any which can't be read. */
+
+  if (daemon->port == 0 || option_bool(OPT_NO_POLL))
+    return;
+  
+  for (latest = NULL, res = daemon->resolv_files; res; res = res->next)
+    if (stat(res->name, &statbuf) == -1)
+      {
+	if (force)
+	  {
+	    res->mtime = 0; 
+	    continue;
+	  }
+
+	if (!res->logged)
+	  my_syslog(LOG_WARNING, _("failed to access %s: %s"), res->name, strerror(errno));
+	res->logged = 1;
+	
+	if (res->mtime != 0)
+	  { 
+	    /* existing file evaporated, force selection of the latest
+	       file even if its mtime hasn't changed since we last looked */
+	    poll_resolv(1, do_reload, now);
+	    return;
+	  }
+      }
+    else
+      {
+	res->logged = 0;
+	if (force || (statbuf.st_mtime != res->mtime))
+          {
+            res->mtime = statbuf.st_mtime;
+	    if (difftime(statbuf.st_mtime, last_change) > 0.0)
+	      {
+		last_change = statbuf.st_mtime;
+		latest = res;
+	      }
+	  }
+      }
+  
+  if (latest)
+    {
+      static int warned = 0;
+      if (reload_servers(latest->name))
+	{
+	  my_syslog(LOG_INFO, _("reading %s"), latest->name);
+	  warned = 0;
+	  check_servers(0);
+	  if (option_bool(OPT_RELOAD) && do_reload)
+	    clear_cache_and_reload(now);
+	}
+      else 
+	{
+	  latest->mtime = 0;
+	  if (!warned)
+	    {
+	      my_syslog(LOG_WARNING, _("no servers found in %s, will retry"), latest->name);
+	      warned = 1;
+	    }
+	}
+    }
+}       
+
+void clear_cache_and_reload(time_t now)
+{
+  (void)now;
+
+  if (daemon->port != 0)
+    cache_reload();
+  
+#ifdef HAVE_DHCP
+  if (daemon->dhcp || daemon->doing_dhcp6)
+    {
+      if (option_bool(OPT_ETHERS))
+	dhcp_read_ethers();
+      reread_dhcp();
+      dhcp_update_configs(daemon->dhcp_conf);
+      lease_update_from_configs(); 
+      lease_update_file(now); 
+      lease_update_dns(1);
+    }
+#ifdef HAVE_DHCP6
+  else if (daemon->doing_ra)
+    /* Not doing DHCP, so no lease system, manage 
+       alarms for ra only */
+    send_alarm(periodic_ra(now), now);
+#endif
+#endif
+}
+
+static void set_dns_listeners(void)
+{
+  struct serverfd *serverfdp;
+  struct listener *listener;
+  struct randfd_list *rfl;
+  int i;
+  
+#ifdef HAVE_TFTP
+  int  tftp = 0;
+  struct tftp_transfer *transfer;
+  if (!option_bool(OPT_SINGLE_PORT))
+    for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
+      {
+	tftp++;
+	poll_listen(transfer->sockfd, POLLIN);
+      }
+#endif
+  
+  for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+    poll_listen(serverfdp->fd, POLLIN);
+    
+  for (i = 0; i < daemon->numrrand; i++)
+    if (daemon->randomsocks[i].refcount != 0)
+      poll_listen(daemon->randomsocks[i].fd, POLLIN);
+
+  /* Check overflow random sockets too. */
+  for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
+    poll_listen(rfl->rfd->fd, POLLIN);
+  
+  /* check to see if we have free tcp process slots. */
+  for (i = MAX_PROCS - 1; i >= 0; i--)
+    if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
+      break;
+
+  for (listener = daemon->listeners; listener; listener = listener->next)
+    {
+      if (listener->fd != -1)
+	poll_listen(listener->fd, POLLIN);
+      
+      /* Only listen for TCP connections when a process slot
+	 is available. Death of a child goes through the select loop, so
+	 we don't need to explicitly arrange to wake up here,
+	 we'll be called again when a slot becomes available. */
+      if  (listener->tcpfd != -1 && i >= 0)
+	poll_listen(listener->tcpfd, POLLIN);
+      
+#ifdef HAVE_TFTP
+      /* tftp == 0 in single-port mode. */
+      if (tftp <= daemon->tftp_max && listener->tftpfd != -1)
+	poll_listen(listener->tftpfd, POLLIN);
+#endif
+    }
+  
+  if (!option_bool(OPT_DEBUG))
+    for (i = 0; i < MAX_PROCS; i++)
+      if (daemon->tcp_pipes[i] != -1)
+	poll_listen(daemon->tcp_pipes[i], POLLIN);
+}
+
+static void check_dns_listeners(time_t now)
+{
+  struct serverfd *serverfdp;
+  struct listener *listener;
+  struct randfd_list *rfl;
+  int i;
+  int pipefd[2];
+  
+  for (serverfdp = daemon->sfds; serverfdp; serverfdp = serverfdp->next)
+    if (poll_check(serverfdp->fd, POLLIN))
+      reply_query(serverfdp->fd, now);
+  
+  for (i = 0; i < daemon->numrrand; i++)
+    if (daemon->randomsocks[i].refcount != 0 && 
+	poll_check(daemon->randomsocks[i].fd, POLLIN))
+      reply_query(daemon->randomsocks[i].fd, now);
+
+  /* Check overflow random sockets too. */
+  for (rfl = daemon->rfl_poll; rfl; rfl = rfl->next)
+    if (poll_check(rfl->rfd->fd, POLLIN))
+      reply_query(rfl->rfd->fd, now);
+
+  /* Races. The child process can die before we read all of the data from the
+     pipe, or vice versa. Therefore send tcp_pids to zero when we wait() the 
+     process, and tcp_pipes to -1 and close the FD when we read the last
+     of the data - indicated by cache_recv_insert returning zero.
+     The order of these events is indeterminate, and both are needed
+     to free the process slot. Once the child process has gone, poll()
+     returns POLLHUP, not POLLIN, so have to check for both here. */
+  if (!option_bool(OPT_DEBUG))
+    for (i = 0; i < MAX_PROCS; i++)
+      if (daemon->tcp_pipes[i] != -1 &&
+	  poll_check(daemon->tcp_pipes[i], POLLIN | POLLHUP) &&
+	  !cache_recv_insert(now, daemon->tcp_pipes[i]))
+	{
+	  close(daemon->tcp_pipes[i]);
+	  daemon->tcp_pipes[i] = -1;	
+	}
+	
+  for (listener = daemon->listeners; listener; listener = listener->next)
+    {
+      if (listener->fd != -1 && poll_check(listener->fd, POLLIN))
+	receive_query(listener, now); 
+      
+#ifdef HAVE_TFTP     
+      if (listener->tftpfd != -1 && poll_check(listener->tftpfd, POLLIN))
+	tftp_request(listener, now);
+#endif
+
+      /* check to see if we have a free tcp process slot.
+	 Note that we can't assume that because we had
+	 at least one a poll() time, that we still do.
+	 There may be more waiting connections after
+	 poll() returns then free process slots. */
+      for (i = MAX_PROCS - 1; i >= 0; i--)
+	if (daemon->tcp_pids[i] == 0 && daemon->tcp_pipes[i] == -1)
+	  break;
+
+      if (listener->tcpfd != -1 && i >= 0 && poll_check(listener->tcpfd, POLLIN))
+	{
+	  int confd, client_ok = 1;
+	  struct irec *iface = NULL;
+	  pid_t p;
+	  union mysockaddr tcp_addr;
+	  socklen_t tcp_len = sizeof(union mysockaddr);
+
+	  while ((confd = accept(listener->tcpfd, NULL, NULL)) == -1 && errno == EINTR);
+	  
+	  if (confd == -1)
+	    continue;
+	  
+	  if (getsockname(confd, (struct sockaddr *)&tcp_addr, &tcp_len) == -1)
+	    {
+	      close(confd);
+	      continue;
+	    }
+	  
+	  /* Make sure that the interface list is up-to-date.
+	     
+	     We do this here as we may need the results below, and
+	     the DNS code needs them for --interface-name stuff.
+
+	     Multiple calls to enumerate_interfaces() per select loop are
+	     inhibited, so calls to it in the child process (which doesn't select())
+	     have no effect. This avoids two processes reading from the same
+	     netlink fd and screwing the pooch entirely.
+	  */
+ 
+	  enumerate_interfaces(0);
+	  
+	  if (option_bool(OPT_NOWILD))
+	    iface = listener->iface; /* May be NULL */
+	  else 
+	    {
+	      int if_index;
+	      char intr_name[IF_NAMESIZE];
+	      
+	      /* if we can find the arrival interface, check it's one that's allowed */
+	      if ((if_index = tcp_interface(confd, tcp_addr.sa.sa_family)) != 0 &&
+		  indextoname(listener->tcpfd, if_index, intr_name))
+		{
+		  union all_addr addr;
+		  
+		  if (tcp_addr.sa.sa_family == AF_INET6)
+		    addr.addr6 = tcp_addr.in6.sin6_addr;
+		  else
+		    addr.addr4 = tcp_addr.in.sin_addr;
+		  
+		  for (iface = daemon->interfaces; iface; iface = iface->next)
+		    if (iface->index == if_index &&
+		        iface->addr.sa.sa_family == tcp_addr.sa.sa_family)
+		      break;
+		  
+		  if (!iface && !loopback_exception(listener->tcpfd, tcp_addr.sa.sa_family, &addr, intr_name))
+		    client_ok = 0;
+		}
+	      
+	      if (option_bool(OPT_CLEVERBIND))
+		iface = listener->iface; /* May be NULL */
+	      else
+		{
+		  /* Check for allowed interfaces when binding the wildcard address:
+		     we do this by looking for an interface with the same address as 
+		     the local address of the TCP connection, then looking to see if that's
+		     an allowed interface. As a side effect, we get the netmask of the
+		     interface too, for localisation. */
+		  
+		  for (iface = daemon->interfaces; iface; iface = iface->next)
+		    if (sockaddr_isequal(&iface->addr, &tcp_addr))
+		      break;
+		  
+		  if (!iface)
+		    client_ok = 0;
+		}
+	    }
+	  
+	  if (!client_ok)
+	    {
+	      shutdown(confd, SHUT_RDWR);
+	      close(confd);
+	    }
+	  else if (!option_bool(OPT_DEBUG) && pipe(pipefd) == 0 && (p = fork()) != 0)
+	    {
+	      close(pipefd[1]); /* parent needs read pipe end. */
+	      if (p == -1)
+		close(pipefd[0]);
+	      else
+		{
+#ifdef HAVE_LINUX_NETWORK
+		  /* The child process inherits the netlink socket, 
+		     which it never uses, but when the parent (us) 
+		     uses it in the future, the answer may go to the 
+		     child, resulting in the parent blocking
+		     forever awaiting the result. To avoid this
+		     the child closes the netlink socket, but there's
+		     a nasty race, since the parent may use netlink
+		     before the child has done the close.
+		     
+		     To avoid this, the parent blocks here until a 
+		     single byte comes back up the pipe, which
+		     is sent by the child after it has closed the
+		     netlink socket. */
+		  
+		  unsigned char a;
+		  read_write(pipefd[0], &a, 1, 1);
+#endif
+
+		  /* i holds index of free slot */
+		  daemon->tcp_pids[i] = p;
+		  daemon->tcp_pipes[i] = pipefd[0];
+		}
+	      close(confd);
+
+	      /* The child can use up to TCP_MAX_QUERIES ids, so skip that many. */
+	      daemon->log_id += TCP_MAX_QUERIES;
+	    }
+	  else
+	    {
+	      unsigned char *buff;
+	      struct server *s; 
+	      int flags;
+	      struct in_addr netmask;
+	      int auth_dns;
+	   
+	      if (iface)
+		{
+		  netmask = iface->netmask;
+		  auth_dns = iface->dns_auth;
+		}
+	      else
+		{
+		  netmask.s_addr = 0;
+		  auth_dns = 0;
+		}
+
+	      /* Arrange for SIGALRM after CHILD_LIFETIME seconds to
+		 terminate the process. */
+	      if (!option_bool(OPT_DEBUG))
+		{
+#ifdef HAVE_LINUX_NETWORK
+		  /* See comment above re: netlink socket. */
+		  unsigned char a = 0;
+
+		  close(daemon->netlinkfd);
+		  read_write(pipefd[1], &a, 1, 0);
+#endif		  
+		  alarm(CHILD_LIFETIME);
+		  close(pipefd[0]); /* close read end in child. */
+		  daemon->pipe_to_parent = pipefd[1];
+		}
+
+	      /* start with no upstream connections. */
+	      for (s = daemon->servers; s; s = s->next)
+		 s->tcpfd = -1; 
+	      
+	      /* The connected socket inherits non-blocking
+		 attribute from the listening socket. 
+		 Reset that here. */
+	      if ((flags = fcntl(confd, F_GETFL, 0)) != -1)
+		fcntl(confd, F_SETFL, flags & ~O_NONBLOCK);
+	      
+	      buff = tcp_request(confd, now, &tcp_addr, netmask, auth_dns);
+	       
+	      shutdown(confd, SHUT_RDWR);
+	      close(confd);
+	      
+	      if (buff)
+		free(buff);
+	      
+	      for (s = daemon->servers; s; s = s->next)
+		if (s->tcpfd != -1)
+		  {
+		    shutdown(s->tcpfd, SHUT_RDWR);
+		    close(s->tcpfd);
+		  }
+	      
+	      if (!option_bool(OPT_DEBUG))
+		{
+		  close(daemon->pipe_to_parent);
+		  flush_log();
+		  _exit(0);
+		}
+	    }
+	}
+    }
+}
+
+#ifdef HAVE_DHCP
+int make_icmp_sock(void)
+{
+  int fd;
+  int zeroopt = 0;
+
+  if ((fd = socket (AF_INET, SOCK_RAW, IPPROTO_ICMP)) != -1)
+    {
+      if (!fix_fd(fd) ||
+	  setsockopt(fd, SOL_SOCKET, SO_DONTROUTE, &zeroopt, sizeof(zeroopt)) == -1)
+	{
+	  close(fd);
+	  fd = -1;
+	}
+    }
+
+  return fd;
+}
+
+int icmp_ping(struct in_addr addr)
+{
+  /* Try and get an ICMP echo from a machine. */
+
+  int fd;
+  struct sockaddr_in saddr;
+  struct { 
+    struct ip ip;
+    struct icmp icmp;
+  } packet;
+  unsigned short id = rand16();
+  unsigned int i, j;
+  int gotreply = 0;
+
+#if defined(HAVE_LINUX_NETWORK) || defined (HAVE_SOLARIS_NETWORK)
+  if ((fd = make_icmp_sock()) == -1)
+    return 0;
+#else
+  int opt = 2000;
+  fd = daemon->dhcp_icmp_fd;
+  setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
+#endif
+
+  saddr.sin_family = AF_INET;
+  saddr.sin_port = 0;
+  saddr.sin_addr = addr;
+#ifdef HAVE_SOCKADDR_SA_LEN
+  saddr.sin_len = sizeof(struct sockaddr_in);
+#endif
+  
+  memset(&packet.icmp, 0, sizeof(packet.icmp));
+  packet.icmp.icmp_type = ICMP_ECHO;
+  packet.icmp.icmp_id = id;
+  for (j = 0, i = 0; i < sizeof(struct icmp) / 2; i++)
+    j += ((u16 *)&packet.icmp)[i];
+  while (j>>16)
+    j = (j & 0xffff) + (j >> 16);  
+  packet.icmp.icmp_cksum = (j == 0xffff) ? j : ~j;
+  
+  while (retry_send(sendto(fd, (char *)&packet.icmp, sizeof(struct icmp), 0, 
+			   (struct sockaddr *)&saddr, sizeof(saddr))));
+  
+  gotreply = delay_dhcp(dnsmasq_time(), PING_WAIT, fd, addr.s_addr, id);
+
+#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_SOLARIS_NETWORK)
+  close(fd);
+#else
+  opt = 1;
+  setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt));
+#endif
+
+  return gotreply;
+}
+
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id)
+{
+  /* Delay processing DHCP packets for "sec" seconds counting from "start".
+     If "fd" is not -1 it will stop waiting if an ICMP echo reply is received
+     from "addr" with ICMP ID "id" and return 1 */
+
+  /* Note that whilst waiting, we check for
+     (and service) events on the DNS and TFTP  sockets, (so doing that
+     better not use any resources our caller has in use...)
+     but we remain deaf to signals or further DHCP packets. */
+
+  /* There can be a problem using dnsmasq_time() to end the loop, since
+     it's not monotonic, and can go backwards if the system clock is
+     tweaked, leading to the code getting stuck in this loop and
+     ignoring DHCP requests. To fix this, we check to see if select returned
+     as a result of a timeout rather than a socket becoming available. We
+     only allow this to happen as many times as it takes to get to the wait time
+     in quarter-second chunks. This provides a fallback way to end loop. */
+
+  int rc, timeout_count;
+  time_t now;
+
+  for (now = dnsmasq_time(), timeout_count = 0;
+       (difftime(now, start) <= (float)sec) && (timeout_count < sec * 4);)
+    {
+      poll_reset();
+      if (fd != -1)
+        poll_listen(fd, POLLIN);
+      set_dns_listeners();
+      set_log_writer();
+      
+#ifdef HAVE_DHCP6
+      if (daemon->doing_ra)
+	poll_listen(daemon->icmp6fd, POLLIN); 
+#endif
+      
+      rc = do_poll(250);
+      
+      if (rc < 0)
+	continue;
+      else if (rc == 0)
+	timeout_count++;
+
+      now = dnsmasq_time();
+      
+      check_log_writer(0);
+      check_dns_listeners(now);
+      
+#ifdef HAVE_DHCP6
+      if (daemon->doing_ra && poll_check(daemon->icmp6fd, POLLIN))
+	icmp6_packet(now);
+#endif
+      
+#ifdef HAVE_TFTP
+      check_tftp_listeners(now);
+#endif
+
+      if (fd != -1)
+        {
+          struct {
+            struct ip ip;
+            struct icmp icmp;
+          } packet;
+          struct sockaddr_in faddr;
+          socklen_t len = sizeof(faddr);
+	  
+          if (poll_check(fd, POLLIN) &&
+	      recvfrom(fd, &packet, sizeof(packet), 0, (struct sockaddr *)&faddr, &len) == sizeof(packet) &&
+	      addr == faddr.sin_addr.s_addr &&
+	      packet.icmp.icmp_type == ICMP_ECHOREPLY &&
+	      packet.icmp.icmp_seq == 0 &&
+	      packet.icmp.icmp_id == id)
+	    return 1;
+	}
+    }
+
+  return 0;
+}
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h
new file mode 100755
index 0000000..8674823
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnsmasq.h
@@ -0,0 +1,1777 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+#define COPYRIGHT "Copyright (c) 2000-2021 Simon Kelley"
+
+/* We do defines that influence behavior of stdio.h, so complain
+   if included too early. */
+#ifdef _STDIO_H
+#  error "Header file stdio.h included too early!"
+#endif 
+
+#ifndef NO_LARGEFILE
+/* Ensure we can use files >2GB (log files may grow this big) */
+#  define _LARGEFILE_SOURCE 1
+#  define _FILE_OFFSET_BITS 64
+#endif
+
+/* Get linux C library versions and define _GNU_SOURCE for kFreeBSD. */
+#if defined(__linux__) || defined(__GLIBC__)
+#  ifndef __ANDROID__
+#      define _GNU_SOURCE
+#  endif
+#  include <features.h> 
+#endif
+
+/* Need these defined early */
+#if defined(__sun) || defined(__sun__)
+#  define _XPG4_2
+#  define __EXTENSIONS__
+#endif
+
+#if (defined(__GNUC__) && __GNUC__ >= 3) || defined(__clang__)
+#define ATTRIBUTE_NORETURN __attribute__ ((noreturn))
+#else
+#define ATTRIBUTE_NORETURN
+#endif
+
+/* get these before config.h  for IPv6 stuff... */
+#include <sys/types.h> 
+#include <sys/socket.h>
+
+#ifdef __APPLE__
+/* Define before netinet/in.h to select API. OSX Lion onwards. */
+#  define __APPLE_USE_RFC_3542
+#endif
+#include <netinet/in.h>
+
+/* Also needed before config.h. */
+#include <getopt.h>
+
+#include "config.h"
+#include "ip6addr.h"
+#include "metrics.h"
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+#define countof(x)      (long)(sizeof(x) / sizeof(x[0]))
+#define MIN(a,b)        ((a) < (b) ? (a) : (b))
+
+#include "dns-protocol.h"
+#include "dhcp-protocol.h"
+#ifdef HAVE_DHCP6
+#include "dhcp6-protocol.h"
+#include "radv-protocol.h"
+#endif
+
+#define gettext_noop(S) (S)
+#ifndef LOCALEDIR
+#  define _(S) (S)
+#else
+#  include <libintl.h>
+#  include <locale.h>   
+#  define _(S) gettext(S)
+#endif
+
+#include <arpa/inet.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#if defined(HAVE_SOLARIS_NETWORK)
+#  include <sys/sockio.h>
+#endif
+#include <poll.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <limits.h>
+#include <net/if.h>
+#if defined(HAVE_SOLARIS_NETWORK) && !defined(ifr_mtu)
+/* Some solaris net/if./h omit this. */
+#  define ifr_mtu  ifr_ifru.ifru_metric
+#endif
+#include <unistd.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <stddef.h>
+#include <time.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <stdarg.h>
+#if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__sun__) || defined (__sun) || defined (__ANDROID__)
+#  include <netinet/if_ether.h>
+#else
+#  include <net/ethernet.h>
+#endif
+#include <net/if_arp.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <sys/uio.h>
+#include <syslog.h>
+#include <dirent.h>
+#ifndef HAVE_LINUX_NETWORK
+#  include <net/if_dl.h>
+#endif
+
+#if defined(HAVE_LINUX_NETWORK)
+#include <linux/version.h>
+#include <linux/sockios.h>
+#include <linux/capability.h>
+/* There doesn't seem to be a universally-available 
+   userspace header for these. */
+extern int capset(cap_user_header_t header, cap_user_data_t data);
+extern int capget(cap_user_header_t header, cap_user_data_t data);
+#define LINUX_CAPABILITY_VERSION_1  0x19980330
+#define LINUX_CAPABILITY_VERSION_2  0x20071026
+#define LINUX_CAPABILITY_VERSION_3  0x20080522
+
+#include <sys/prctl.h>
+#elif defined(HAVE_SOLARIS_NETWORK)
+#include <priv.h>
+#endif
+
+/* Backwards compat with 2.83 */
+#if defined(HAVE_NETTLEHASH)
+#  define HAVE_CRYPTOHASH
+#endif
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+#  include <nettle/nettle-meta.h>
+#endif
+
+/* daemon is function in the C library.... */
+#define daemon dnsmasq_daemon
+
+#define ADDRSTRLEN INET6_ADDRSTRLEN
+
+/* Async event queue */
+struct event_desc {
+  int event, data, msg_sz;
+};
+
+#define EVENT_RELOAD     1
+#define EVENT_DUMP       2
+#define EVENT_ALARM      3
+#define EVENT_TERM       4
+#define EVENT_CHILD      5
+#define EVENT_REOPEN     6
+#define EVENT_EXITED     7
+#define EVENT_KILLED     8
+#define EVENT_EXEC_ERR   9
+#define EVENT_PIPE_ERR   10
+#define EVENT_USER_ERR   11
+#define EVENT_CAP_ERR    12
+#define EVENT_PIDFILE    13
+#define EVENT_HUSER_ERR  14
+#define EVENT_GROUP_ERR  15
+#define EVENT_DIE        16
+#define EVENT_LOG_ERR    17
+#define EVENT_FORK_ERR   18
+#define EVENT_LUA_ERR    19
+#define EVENT_TFTP_ERR   20
+#define EVENT_INIT       21
+#define EVENT_NEWADDR    22
+#define EVENT_NEWROUTE   23
+#define EVENT_TIME_ERR   24
+#define EVENT_SCRIPT_LOG 25
+#define EVENT_TIME       26
+
+/* Exit codes. */
+#define EC_GOOD        0
+#define EC_BADCONF     1
+#define EC_BADNET      2
+#define EC_FILE        3
+#define EC_NOMEM       4
+#define EC_MISC        5
+#define EC_INIT_OFFSET 10
+
+#define OPT_BOGUSPRIV      0
+#define OPT_FILTER         1
+#define OPT_LOG            2
+#define OPT_SELFMX         3
+#define OPT_NO_HOSTS       4
+#define OPT_NO_POLL        5
+#define OPT_DEBUG          6
+#define OPT_ORDER          7
+#define OPT_NO_RESOLV      8
+#define OPT_EXPAND         9
+#define OPT_LOCALMX        10
+#define OPT_NO_NEG         11
+#define OPT_NODOTS_LOCAL   12
+#define OPT_NOWILD         13
+#define OPT_ETHERS         14
+#define OPT_RESOLV_DOMAIN  15
+#define OPT_NO_FORK        16
+#define OPT_AUTHORITATIVE  17
+#define OPT_LOCALISE       18
+#define OPT_DBUS           19
+#define OPT_DHCP_FQDN      20
+#define OPT_NO_PING        21
+#define OPT_LEASE_RO       22
+#define OPT_ALL_SERVERS    23
+#define OPT_RELOAD         24
+#define OPT_LOCAL_REBIND   25  
+#define OPT_TFTP_SECURE    26
+#define OPT_TFTP_NOBLOCK   27
+#define OPT_LOG_OPTS       28
+#define OPT_TFTP_APREF_IP  29
+#define OPT_NO_OVERRIDE    30
+#define OPT_NO_REBIND      31
+#define OPT_ADD_MAC        32
+#define OPT_DNSSEC_PROXY   33
+#define OPT_CONSEC_ADDR    34
+#define OPT_CONNTRACK      35
+#define OPT_FQDN_UPDATE    36
+#define OPT_RA             37
+#define OPT_TFTP_LC        38
+#define OPT_CLEVERBIND     39
+#define OPT_TFTP           40
+#define OPT_CLIENT_SUBNET  41
+#define OPT_QUIET_DHCP     42
+#define OPT_QUIET_DHCP6    43
+#define OPT_QUIET_RA	   44
+#define OPT_DNSSEC_VALID   45
+#define OPT_DNSSEC_TIME    46
+#define OPT_DNSSEC_DEBUG   47
+#define OPT_DNSSEC_IGN_NS  48 
+#define OPT_LOCAL_SERVICE  49
+#define OPT_LOOP_DETECT    50
+#define OPT_EXTRALOG       51
+#define OPT_TFTP_NO_FAIL   52
+#define OPT_SCRIPT_ARP     53
+#define OPT_MAC_B64        54
+#define OPT_MAC_HEX        55
+#define OPT_TFTP_APREF_MAC 56
+#define OPT_RAPID_COMMIT   57
+#define OPT_UBUS           58
+#define OPT_IGNORE_CLID    59
+#define OPT_SINGLE_PORT    60
+#define OPT_LEASE_RENEW    61
+#define OPT_LOG_DEBUG      62
+#define OPT_UMBRELLA       63
+#define OPT_UMBRELLA_DEVID 64
+#define OPT_CMARK_ALST_EN  65
+#define OPT_QUIET_TFTP     66
+#define OPT_LAST           67
+
+#define OPTION_BITS (sizeof(unsigned int)*8)
+#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
+#define option_var(x) (daemon->options[(x) / OPTION_BITS])
+#define option_val(x) ((1u) << ((x) % OPTION_BITS))
+#define option_bool(x) (option_var(x) & option_val(x))
+
+/* extra flags for my_syslog, we use facilities since they are known 
+   not to occupy the same bits as priorities, no matter how syslog.h is set up. 
+   MS_DEBUG messages are suppressed unless --log-debug is set. */
+#define MS_TFTP   LOG_USER
+#define MS_DHCP   LOG_DAEMON
+#define MS_SCRIPT LOG_MAIL
+#define MS_DEBUG  LOG_NEWS
+
+/* Note that this is used widely as a container for IPv4/IPv6 addresses,
+   so for that reason, was well as to avoid wasting memory in almost every
+   cache entry, the other variants should not be larger than
+   sizeof(struct in6_addr) - 16 bytes.
+*/
+union all_addr {
+  struct in_addr addr4;
+  struct in6_addr addr6;
+  struct {
+    union {
+      struct crec *cache;
+      char *name;
+    } target;
+    unsigned int uid;
+    int is_name_ptr;  /* disciminates target union */
+  } cname;
+  struct {
+    struct blockdata *keydata;
+    unsigned short keylen, flags, keytag;
+    unsigned char algo;
+  } key; 
+  struct {
+    struct blockdata *keydata;
+    unsigned short keylen, keytag;
+    unsigned char algo;
+    unsigned char digest; 
+  } ds;
+  struct {
+    struct blockdata *target;
+    unsigned short targetlen, srvport, priority, weight;
+  } srv;
+  /* for log_query */
+  struct {
+    unsigned short keytag, algo, digest, rcode;
+    int ede;
+  } log;
+};
+
+
+struct bogus_addr {
+  int is6, prefix;
+  union all_addr addr;
+  struct bogus_addr *next;
+};
+
+/* dns doctor param */
+struct doctor {
+  struct in_addr in, end, out, mask;
+  struct doctor *next;
+};
+
+struct mx_srv_record {
+  char *name, *target;
+  int issrv, srvport, priority, weight;
+  unsigned int offset;
+  struct mx_srv_record *next;
+};
+
+struct naptr {
+  char *name, *replace, *regexp, *services, *flags;
+  unsigned int order, pref;
+  struct naptr *next;
+};
+
+#ifndef NO_ID
+#define TXT_STAT_CACHESIZE     1
+#define TXT_STAT_INSERTS       2
+#define TXT_STAT_EVICTIONS     3
+#define TXT_STAT_MISSES        4
+#define TXT_STAT_HITS          5
+#define TXT_STAT_AUTH          6
+#define TXT_STAT_SERVERS       7
+#endif
+
+struct txt_record {
+  char *name;
+  unsigned char *txt;
+  unsigned short class, len;
+  int stat;
+  struct txt_record *next;
+};
+
+struct ptr_record {
+  char *name, *ptr;
+  struct ptr_record *next;
+};
+
+struct cname {
+  int ttl, flag;
+  char *alias, *target;
+  struct cname *next, *targetp;
+}; 
+
+struct ds_config {
+  char *name, *digest;
+  int digestlen, class, algo, keytag, digest_type;
+  struct ds_config *next;
+};
+
+#define ADDRLIST_LITERAL  1
+#define ADDRLIST_IPV6     2
+#define ADDRLIST_REVONLY  4
+#define ADDRLIST_PREFIX   8
+#define ADDRLIST_WILDCARD 16
+#define ADDRLIST_DECLINED 32
+
+struct addrlist {
+  union all_addr addr;
+  int flags, prefixlen;
+  time_t decline_time;
+  struct addrlist *next;
+};
+
+#define AUTH6     1
+#define AUTH4     2
+
+struct auth_zone {
+  char *domain;
+  struct auth_name_list {
+    char *name;
+    int flags;
+    struct auth_name_list *next;
+  } *interface_names;
+  struct addrlist *subnet;
+  struct addrlist *exclude;
+  struct auth_zone *next;
+};
+
+#define HR_6 1
+#define HR_4 2
+
+struct host_record {
+  int ttl, flags;
+  struct name_list {
+    char *name;
+    struct name_list *next;
+  } *names;
+  struct in_addr addr;
+  struct in6_addr addr6;
+  struct host_record *next;
+};
+
+#define IN4  1
+#define IN6  2
+#define INP4 4
+#define INP6 8
+
+struct interface_name {
+  char *name; /* domain name */
+  char *intr; /* interface name */
+  int flags;
+  struct in_addr proto4;
+  struct in6_addr proto6;
+  struct addrlist *addr;
+  struct interface_name *next;
+};
+
+union bigname {
+  char name[MAXDNAME];
+  union bigname *next; /* freelist */
+};
+
+struct blockdata {
+  struct blockdata *next;
+  unsigned char key[KEYBLOCK_LEN];
+};
+
+struct crec { 
+  struct crec *next, *prev, *hash_next;
+  union all_addr addr;
+  time_t ttd; /* time to die */
+  /* used as class if DNSKEY/DS, index to source for F_HOSTS */
+  unsigned int uid; 
+  unsigned int flags;
+  union {
+    char sname[SMALLDNAME];
+    union bigname *bname;
+    char *namep;
+  } name;
+};
+
+#define SIZEOF_BARE_CREC (sizeof(struct crec) - SMALLDNAME)
+#define SIZEOF_POINTER_CREC (sizeof(struct crec) + sizeof(char *) - SMALLDNAME)
+
+#define F_IMMORTAL  (1u<<0)
+#define F_NAMEP     (1u<<1)
+#define F_REVERSE   (1u<<2)
+#define F_FORWARD   (1u<<3)
+#define F_DHCP      (1u<<4)
+#define F_NEG       (1u<<5)       
+#define F_HOSTS     (1u<<6)
+#define F_IPV4      (1u<<7)
+#define F_IPV6      (1u<<8)
+#define F_BIGNAME   (1u<<9)
+#define F_NXDOMAIN  (1u<<10)
+#define F_CNAME     (1u<<11)
+#define F_DNSKEY    (1u<<12)
+#define F_CONFIG    (1u<<13)
+#define F_DS        (1u<<14)
+#define F_DNSSECOK  (1u<<15)
+#define F_UPSTREAM  (1u<<16)
+#define F_RRNAME    (1u<<17)
+#define F_SERVER    (1u<<18)
+#define F_QUERY     (1u<<19)
+#define F_NOERR     (1u<<20)
+#define F_AUTH      (1u<<21)
+#define F_DNSSEC    (1u<<22)
+#define F_KEYTAG    (1u<<23)
+#define F_SECSTAT   (1u<<24)
+#define F_NO_RR     (1u<<25)
+#define F_IPSET     (1u<<26)
+#define F_NOEXTRA   (1u<<27)
+#define F_DOMAINSRV (1u<<28)
+#define F_RCODE     (1u<<29)
+#define F_SRV       (1u<<30)
+
+#define UID_NONE      0
+/* Values of uid in crecs with F_CONFIG bit set. */
+#define SRC_CONFIG    1
+#define SRC_HOSTS     2
+#define SRC_AH        3
+
+
+/* struct sockaddr is not large enough to hold any address,
+   and specifically not big enough to hold an IPv6 address.
+   Blech. Roll our own. */
+union mysockaddr {
+  struct sockaddr sa;
+  struct sockaddr_in in;
+  struct sockaddr_in6 in6;
+};
+
+/* bits in flag param to IPv6 callbacks from iface_enumerate() */
+#define IFACE_TENTATIVE   1
+#define IFACE_DEPRECATED  2
+#define IFACE_PERMANENT   4
+
+
+/* The actual values here matter, since we sort on them to get records in the order
+   IPv6 addr, IPv4 addr, all zero return, no-data return, send upstream. */
+#define SERV_LITERAL_ADDRESS   1  /* addr is the answer, or NoDATA is the answer, depending on the next three flags */
+#define SERV_ALL_ZEROS         2  /* return all zeros for A and AAAA */
+#define SERV_4ADDR             4  /* addr is IPv4 */
+#define SERV_6ADDR             8  /* addr is IPv6 */
+#define SERV_HAS_SOURCE       16  /* source address defined */
+#define SERV_FOR_NODOTS       32  /* server for names with no domain part only */
+#define SERV_WARNED_RECURSIVE 64  /* avoid warning spam */
+#define SERV_FROM_DBUS       128  /* 1 if source is DBus */
+#define SERV_MARK            256  /* for mark-and-delete and log code */
+#define SERV_WILDCARD        512  /* domain has leading '*' */ 
+#define SERV_USE_RESOLV     1024  /* forward this domain in the normal way */
+#define SERV_FROM_RESOLV    2048  /* 1 for servers from resolv, 0 for command line. */
+#define SERV_FROM_FILE      4096  /* read from --servers-file */
+#define SERV_LOOP           8192  /* server causes forwarding loop */
+#define SERV_DO_DNSSEC     16384  /* Validate DNSSEC when using this server */
+#define SERV_GOT_TCP       32768  /* Got some data from the TCP connection */
+
+struct serverfd {
+  int fd;
+  union mysockaddr source_addr;
+  char interface[IF_NAMESIZE+1];
+  unsigned int ifindex, used, preallocated;
+  struct serverfd *next;
+};
+
+struct randfd {
+  struct server *serv;
+  int fd;
+  unsigned short refcount; /* refcount == 0xffff means overflow record. */
+};
+
+struct randfd_list {
+  struct randfd *rfd;
+  struct randfd_list *next;
+};
+
+
+struct server {
+  u16 flags, domain_len;
+  char *domain;
+  struct server *next;
+  int serial, arrayposn;
+  int last_server;
+  union mysockaddr addr, source_addr;
+  char interface[IF_NAMESIZE+1];
+  unsigned int ifindex; /* corresponding to interface, above */
+  struct serverfd *sfd; 
+  int tcpfd, edns_pktsz;
+  time_t pktsz_reduced;
+  unsigned int queries, failed_queries;
+  time_t forwardtime;
+  int forwardcount;
+#ifdef HAVE_LOOP
+  u32 uid;
+#endif
+};
+
+/* First four fields must match struct server in next three definitions.. */
+struct serv_addr4 {
+  u16 flags, domain_len;
+  char *domain;
+  struct server *next;
+  struct in_addr addr;
+};
+
+struct serv_addr6 {
+  u16 flags, domain_len;
+  char *domain;
+  struct server *next;
+  struct in6_addr addr;
+};
+
+struct serv_local {
+  u16 flags, domain_len;
+  char *domain;
+  struct server *next;
+};
+
+struct ipsets {
+  char **sets;
+  char *domain;
+  struct ipsets *next;
+};
+
+struct allowlist {
+  u32 mark, mask;
+  char **patterns;
+  struct allowlist *next;
+};
+
+struct irec {
+  union mysockaddr addr;
+  struct in_addr netmask; /* only valid for IPv4 */
+  int tftp_ok, dhcp_ok, mtu, done, warned, dad, dns_auth, index, multicast_done, found, label;
+  char *name; 
+  struct irec *next;
+};
+
+struct listener {
+  int fd, tcpfd, tftpfd, used;
+  union mysockaddr addr;
+  struct irec *iface; /* only sometimes valid for non-wildcard */
+  struct listener *next;
+};
+
+/* interface and address parms from command line. */
+struct iname {
+  char *name;
+  union mysockaddr addr;
+  int used;
+  struct iname *next;
+};
+
+/* subnet parameters from command line */
+struct mysubnet {
+  union mysockaddr addr;
+  int addr_used;
+  int mask;
+};
+
+/* resolv-file parms from command-line */
+struct resolvc {
+  struct resolvc *next;
+  int is_default, logged;
+  time_t mtime;
+  char *name;
+#ifdef HAVE_INOTIFY
+  int wd; /* inotify watch descriptor */
+  char *file; /* pointer to file part if path */
+#endif
+};
+
+/* adn-hosts parms from command-line (also dhcp-hostsfile and dhcp-optsfile and dhcp-hostsdir*/
+#define AH_DIR      1
+#define AH_INACTIVE 2
+#define AH_WD_DONE  4
+#define AH_HOSTS    8
+#define AH_DHCP_HST 16
+#define AH_DHCP_OPT 32
+struct hostsfile {
+  struct hostsfile *next;
+  int flags;
+  char *fname;
+#ifdef HAVE_INOTIFY
+  int wd; /* inotify watch descriptor */
+#endif
+  unsigned int index; /* matches to cache entries for logging */
+};
+
+/* packet-dump flags */
+#define DUMP_QUERY     0x0001
+#define DUMP_REPLY     0x0002
+#define DUMP_UP_QUERY  0x0004
+#define DUMP_UP_REPLY  0x0008
+#define DUMP_SEC_QUERY 0x0010
+#define DUMP_SEC_REPLY 0x0020
+#define DUMP_BOGUS     0x0040
+#define DUMP_SEC_BOGUS 0x0080
+
+/* DNSSEC status values. */
+#define STAT_SECURE             0x10000
+#define STAT_INSECURE           0x20000
+#define STAT_BOGUS              0x30000
+#define STAT_NEED_DS            0x40000
+#define STAT_NEED_KEY           0x50000
+#define STAT_TRUNCATED          0x60000
+#define STAT_SECURE_WILDCARD    0x70000
+#define STAT_OK                 0x80000
+#define STAT_ABANDONED          0x90000
+
+#define DNSSEC_FAIL_NYV         0x0001 /* key not yet valid */
+#define DNSSEC_FAIL_EXP         0x0002 /* key expired */
+#define DNSSEC_FAIL_INDET       0x0004 /* indetermined */
+#define DNSSEC_FAIL_NOKEYSUP    0x0008 /* no supported key algo. */
+#define DNSSEC_FAIL_NOSIG       0x0010 /* No RRsigs */
+#define DNSSEC_FAIL_NOZONE      0x0020 /* No Zone bit set */
+#define DNSSEC_FAIL_NONSEC      0x0040 /* No NSEC */
+#define DNSSEC_FAIL_NODSSUP     0x0080 /* no supported DS algo. */
+#define DNSSEC_FAIL_NOKEY       0x0100 /* no DNSKEY */
+
+#define STAT_ISEQUAL(a, b)  (((a) & 0xffff0000) == (b))
+
+#define FREC_NOREBIND           1
+#define FREC_CHECKING_DISABLED  2
+#define FREC_HAS_SUBNET         4
+#define FREC_DNSKEY_QUERY       8
+#define FREC_DS_QUERY          16
+#define FREC_AD_QUESTION       32
+#define FREC_DO_QUESTION       64
+#define FREC_ADDED_PHEADER    128
+#define FREC_TEST_PKTSZ       256
+#define FREC_HAS_EXTRADATA    512
+#define FREC_HAS_PHEADER     1024
+#define FREC_NO_CACHE        2048
+
+#define HASH_SIZE 32 /* SHA-256 digest size */
+
+struct frec {
+  struct frec_src {
+    union mysockaddr source;
+    union all_addr dest;
+    unsigned int iface, log_id;
+    int fd;
+    unsigned short orig_id;
+    struct frec_src *next;
+  } frec_src;
+  struct server *sentto; /* NULL means free */
+  struct randfd_list *rfds;
+  unsigned short new_id;
+  int forwardall, flags;
+  time_t time;
+  unsigned char *hash[HASH_SIZE];
+#ifdef HAVE_DNSSEC 
+  int class, work_counter;
+  struct blockdata *stash; /* Saved reply, whilst we validate */
+  size_t stash_len;
+  struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
+  struct frec *next_dependent; /* list of above. */
+  struct frec *blocking_query; /* Query which is blocking us. */
+#endif
+  struct frec *next;
+};
+
+/* flags in top of length field for DHCP-option tables */
+#define OT_ADDR_LIST    0x8000
+#define OT_RFC1035_NAME 0x4000
+#define OT_INTERNAL     0x2000
+#define OT_NAME         0x1000
+#define OT_CSTRING      0x0800
+#define OT_DEC          0x0400 
+#define OT_TIME         0x0200
+
+/* actions in the daemon->helper RPC */
+#define ACTION_DEL           1
+#define ACTION_OLD_HOSTNAME  2
+#define ACTION_OLD           3
+#define ACTION_ADD           4
+#define ACTION_TFTP          5
+#define ACTION_ARP           6
+#define ACTION_ARP_DEL       7
+
+#define LEASE_NEW            1  /* newly created */
+#define LEASE_CHANGED        2  /* modified */
+#define LEASE_AUX_CHANGED    4  /* CLID or expiry changed */
+#define LEASE_AUTH_NAME      8  /* hostname came from config, not from client */
+#define LEASE_USED          16  /* used this DHCPv6 transaction */
+#define LEASE_NA            32  /* IPv6 no-temporary lease */
+#define LEASE_TA            64  /* IPv6 temporary lease */
+#define LEASE_HAVE_HWADDR  128  /* Have set hwaddress */
+#define LEASE_EXP_CHANGED  256  /* Lease expiry time changed */
+
+struct dhcp_lease {
+  int clid_len;          /* length of client identifier */
+  unsigned char *clid;   /* clientid */
+  char *hostname, *fqdn; /* name from client-hostname option or config */
+  char *old_hostname;    /* hostname before it moved to another lease */
+  int flags;
+  time_t expires;        /* lease expiry */
+#ifdef HAVE_BROKEN_RTC
+  unsigned int length;
+#endif
+  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;
+  int new_interface;     /* save possible originated interface */
+  int new_prefixlen;     /* and its prefix length */
+#ifdef HAVE_DHCP6
+  struct in6_addr addr6;
+  unsigned int iaid;
+  struct slaac_address {
+    struct in6_addr addr;
+    time_t ping_time;
+    int backoff; /* zero -> confirmed */
+    struct slaac_address *next;
+  } *slaac_address;
+  int vendorclass_count;
+#endif
+  struct dhcp_lease *next;
+};
+
+struct dhcp_netid {
+  char *net;
+  struct dhcp_netid *next;
+};
+
+struct dhcp_netid_list {
+  struct dhcp_netid *list;
+  struct dhcp_netid_list *next;
+};
+
+struct tag_if {
+  struct dhcp_netid_list *set;
+  struct dhcp_netid *tag;
+  struct tag_if *next;
+};
+
+struct delay_config {
+  int delay;
+  struct dhcp_netid *netid;
+  struct delay_config *next;
+};
+
+struct hwaddr_config {
+  int hwaddr_len, hwaddr_type;
+  unsigned char hwaddr[DHCP_CHADDR_MAX];
+  unsigned int wildcard_mask;
+  struct hwaddr_config *next;
+};
+
+struct dhcp_config {
+  unsigned int flags;
+  int clid_len;          /* length of client identifier */
+  unsigned char *clid;   /* clientid */
+  char *hostname, *domain;
+  struct dhcp_netid_list *netid;
+  struct dhcp_netid *filter;
+#ifdef HAVE_DHCP6
+  struct addrlist *addr6;
+#endif
+  struct in_addr addr;
+  time_t decline_time;
+  unsigned int lease_time;
+  struct hwaddr_config *hwaddr;
+  struct dhcp_config *next;
+};
+
+#define have_config(config, mask) ((config) && ((config)->flags & (mask))) 
+
+#define CONFIG_DISABLE           1
+#define CONFIG_CLID              2
+#define CONFIG_TIME              8
+#define CONFIG_NAME             16
+#define CONFIG_ADDR             32
+#define CONFIG_NOCLID          128
+#define CONFIG_FROM_ETHERS     256    /* entry created by /etc/ethers */
+#define CONFIG_ADDR_HOSTS      512    /* address added by from /etc/hosts */
+#define CONFIG_DECLINED       1024    /* address declined by client */
+#define CONFIG_BANK           2048    /* from dhcp hosts file */
+#define CONFIG_ADDR6          4096
+#define CONFIG_ADDR6_HOSTS   16384    /* address added by from /etc/hosts */
+
+struct dhcp_opt {
+  int opt, len, flags;
+  union {
+    int encap;
+    unsigned int wildcard_mask;
+    unsigned char *vendor_class;
+  } u;
+  unsigned char *val;
+  struct dhcp_netid *netid;
+  struct dhcp_opt *next;
+};
+
+#define DHOPT_ADDR               1
+#define DHOPT_STRING             2
+#define DHOPT_ENCAPSULATE        4
+#define DHOPT_ENCAP_MATCH        8
+#define DHOPT_FORCE             16
+#define DHOPT_BANK              32
+#define DHOPT_ENCAP_DONE        64
+#define DHOPT_MATCH            128
+#define DHOPT_VENDOR           256
+#define DHOPT_HEX              512
+#define DHOPT_VENDOR_MATCH    1024
+#define DHOPT_RFC3925         2048
+#define DHOPT_TAGOK           4096
+#define DHOPT_ADDR6           8192
+#define DHOPT_VENDOR_PXE     16384
+
+struct dhcp_boot {
+  char *file, *sname, *tftp_sname;
+  struct in_addr next_server;
+  struct dhcp_netid *netid;
+  struct dhcp_boot *next;
+};
+
+struct dhcp_match_name {
+  char *name;
+  int wildcard;
+  struct dhcp_netid *netid;
+  struct dhcp_match_name *next;
+};
+
+struct pxe_service {
+  unsigned short CSA, type; 
+  char *menu, *basename, *sname;
+  struct in_addr server;
+  struct dhcp_netid *netid;
+  struct pxe_service *next;
+};
+
+#define DHCP_PXE_DEF_VENDOR      "PXEClient"
+
+#define MATCH_VENDOR     1
+#define MATCH_USER       2
+#define MATCH_CIRCUIT    3
+#define MATCH_REMOTE     4
+#define MATCH_SUBSCRIBER 5
+
+/* vendorclass, userclass, remote-id or circuit-id */
+struct dhcp_vendor {
+  int len, match_type;
+  unsigned int enterprise;
+  char *data;
+  struct dhcp_netid netid;
+  struct dhcp_vendor *next;
+};
+
+struct dhcp_pxe_vendor {
+  char *data;
+  struct dhcp_pxe_vendor *next;
+};
+
+struct dhcp_mac {
+  unsigned int mask;
+  int hwaddr_len, hwaddr_type;
+  unsigned char hwaddr[DHCP_CHADDR_MAX];
+  struct dhcp_netid netid;
+  struct dhcp_mac *next;
+};
+
+struct dhcp_bridge {
+  char iface[IF_NAMESIZE];
+  struct dhcp_bridge *alias, *next;
+};
+
+struct cond_domain {
+  char *domain, *prefix; /* prefix is text-prefix on domain name */
+  struct in_addr start, end;
+  struct in6_addr start6, end6;
+  int is6, indexed, prefixlen;
+  struct cond_domain *next;
+}; 
+
+struct ra_interface {
+  char *name;
+  char *mtu_name;
+  int interval, lifetime, prio, mtu;
+  struct ra_interface *next;
+};
+
+struct dhcp_context {
+  unsigned int lease_time, addr_epoch;
+  struct in_addr netmask, broadcast;
+  struct in_addr local, router;
+  struct in_addr start, end; /* range of available addresses */
+#ifdef HAVE_DHCP6
+  struct in6_addr start6, end6; /* range of available addresses */
+  struct in6_addr local6;
+  int prefix, if_index;
+  unsigned int valid, preferred, saved_valid;
+  time_t ra_time, ra_short_period_start, address_lost_time;
+  char *template_interface;
+#endif
+  int flags;
+  struct dhcp_netid netid, *filter;
+  struct dhcp_context *next, *current;
+};
+
+struct shared_network {
+  int if_index;
+  struct in_addr match_addr, shared_addr;
+#ifdef HAVE_DHCP6
+  /* shared_addr == 0 for IP6 entries. */
+  struct in6_addr match_addr6, shared_addr6;
+#endif
+  struct shared_network *next;
+};
+
+#define CONTEXT_STATIC         (1u<<0)
+#define CONTEXT_NETMASK        (1u<<1)
+#define CONTEXT_BRDCAST        (1u<<2)
+#define CONTEXT_PROXY          (1u<<3)
+#define CONTEXT_RA_ROUTER      (1u<<4)
+#define CONTEXT_RA_DONE        (1u<<5)
+#define CONTEXT_RA_NAME        (1u<<6)
+#define CONTEXT_RA_STATELESS   (1u<<7)
+#define CONTEXT_DHCP           (1u<<8)
+#define CONTEXT_DEPRECATE      (1u<<9)
+#define CONTEXT_TEMPLATE       (1u<<10)    /* create contexts using addresses */
+#define CONTEXT_CONSTRUCTED    (1u<<11)
+#define CONTEXT_GC             (1u<<12)
+#define CONTEXT_RA             (1u<<13)
+#define CONTEXT_CONF_USED      (1u<<14)
+#define CONTEXT_USED           (1u<<15)
+#define CONTEXT_OLD            (1u<<16)
+#define CONTEXT_V6             (1u<<17)
+#define CONTEXT_RA_OFF_LINK    (1u<<18)
+#define CONTEXT_SETLEASE       (1u<<19)
+
+struct ping_result {
+  struct in_addr addr;
+  time_t time;
+  unsigned int hash;
+  struct ping_result *next;
+};
+
+struct tftp_file {
+  int refcount, fd;
+  off_t size;
+  dev_t dev;
+  ino_t inode;
+  char filename[];
+};
+
+struct tftp_transfer {
+  int sockfd;
+  time_t timeout;
+  int backoff;
+  unsigned int block, blocksize, expansion;
+  off_t offset;
+  union mysockaddr peer;
+  union all_addr source;
+  int if_index;
+  char opt_blocksize, opt_transize, netascii, carrylf;
+  struct tftp_file *file;
+  struct tftp_transfer *next;
+};
+
+struct addr_list {
+  struct in_addr addr;
+  struct addr_list *next;
+};
+
+struct tftp_prefix {
+  char *interface;
+  char *prefix;
+  int missing;
+  struct tftp_prefix *next;
+};
+
+struct dhcp_relay {
+  union 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 
+     config file arguments. All set (including defaults)
+     in option.c */
+
+  unsigned int options[OPTION_SIZE];
+  struct resolvc default_resolv, *resolv_files;
+  time_t last_resolv;
+  char *servers_file;
+  struct mx_srv_record *mxnames;
+  struct naptr *naptr;
+  struct txt_record *txt, *rr;
+  struct ptr_record *ptr;
+  struct host_record *host_records, *host_records_tail;
+  struct cname *cnames;
+  struct auth_zone *auth_zones;
+  struct interface_name *int_names;
+  char *mxtarget;
+  struct mysubnet *add_subnet4;
+  struct mysubnet *add_subnet6;
+  char *lease_file;
+  char *username, *groupname, *scriptuser;
+  char *luascript;
+  char *authserver, *hostmaster;
+  struct iname *authinterface;
+  struct name_list *secondary_forward_server;
+  int group_set, osport;
+  char *domain_suffix;
+  struct cond_domain *cond_domain, *synth_domains;
+  char *runfile; 
+  char *lease_change_command;
+  struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces;
+  struct bogus_addr *bogus_addr, *ignore_addr;
+  struct server *servers, *local_domains, **serverarray, *no_rebind;
+  int server_has_wildcard;
+  int serverarraysz, serverarrayhwm;
+  struct ipsets *ipsets;
+  u32 allowlist_mask;
+  struct allowlist *allowlists;
+  int log_fac; /* log facility */
+  char *log_file; /* optional log file */
+  int max_logs;  /* queue limit */
+  int cachesize, ftabsize;
+  int port, query_port, min_port, max_port;
+  unsigned long local_ttl, neg_ttl, max_ttl, min_cache_ttl, max_cache_ttl, auth_ttl, dhcp_ttl, use_dhcp_ttl;
+  char *dns_client_id;
+  u32 umbrella_org;
+  u32 umbrella_asset;
+  u8 umbrella_device[8];
+  struct hostsfile *addn_hosts;
+  struct dhcp_context *dhcp, *dhcp6;
+  struct ra_interface *ra_interfaces;
+  struct dhcp_config *dhcp_conf;
+  struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
+  struct dhcp_match_name *dhcp_name_match;
+  struct dhcp_pxe_vendor *dhcp_pxe_vendors;
+  struct dhcp_vendor *dhcp_vendors;
+  struct dhcp_mac *dhcp_macs;
+  struct dhcp_boot *boot_config;
+  struct pxe_service *pxe_services;
+  struct tag_if *tag_if; 
+  struct addr_list *override_relays;
+  struct dhcp_relay *relay4, *relay6;
+  struct delay_config *delay_conf;
+  int override;
+  int enable_pxe;
+  int doing_ra, doing_dhcp6;
+  struct dhcp_netid_list *dhcp_ignore, *dhcp_ignore_names, *dhcp_gen_names; 
+  struct dhcp_netid_list *force_broadcast, *bootp_dynamic;
+  struct hostsfile *dhcp_hosts_file, *dhcp_opts_file, *dynamic_dirs;
+  int dhcp_max, tftp_max, tftp_mtu;
+  int dhcp_server_port, dhcp_client_port;
+  int start_tftp_port, end_tftp_port; 
+  unsigned int min_leasetime;
+  struct doctor *doctors;
+  unsigned short edns_pktsz;
+  char *tftp_prefix; 
+  struct tftp_prefix *if_prefix; /* per-interface TFTP prefixes */
+  unsigned int duid_enterprise, duid_config_len;
+  unsigned char *duid_config;
+  char *dbus_name;
+  char *ubus_name;
+  char *dump_file;
+  int dump_mask;
+  unsigned long soa_sn, soa_refresh, soa_retry, soa_expiry;
+  u32 metrics[__METRIC_MAX];
+#ifdef HAVE_DNSSEC
+  struct ds_config *ds;
+  char *timestamp_file;
+#endif
+
+  /* globally used stuff for DNS */
+  char *packet; /* packet buffer */
+  int packet_buff_sz; /* size of above */
+  char *namebuff; /* MAXDNAME size buffer */
+#ifdef HAVE_DNSSEC
+  char *keyname; /* MAXDNAME size buffer */
+  char *workspacename; /* ditto */
+  unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
+  int rr_status_sz;
+  int dnssec_no_time_check;
+  int back_to_the_future;
+#endif
+  struct frec *frec_list;
+  struct frec_src *free_frec_src;
+  int frec_src_count;
+  struct serverfd *sfds;
+  struct irec *interfaces;
+  struct listener *listeners;
+  struct server *srv_save; /* Used for resend on DoD */
+  size_t packet_len;       /*      "        "        */
+  int    fd_save;          /*      "        "        */
+  pid_t tcp_pids[MAX_PROCS];
+  int tcp_pipes[MAX_PROCS];
+  int pipe_to_parent;
+  int numrrand;
+  struct randfd *randomsocks;
+  struct randfd_list *rfl_spare, *rfl_poll;
+  int v6pktinfo; 
+  struct addrlist *interface_addrs; /* list of all addresses/prefix lengths associated with all local interfaces */
+  int log_id, log_display_id; /* ids of transactions for logging */
+  union mysockaddr *log_source_addr;
+
+  /* DHCP state */
+  int dhcpfd, helperfd, pxefd; 
+#ifdef HAVE_INOTIFY
+  int inotifyfd;
+#endif
+#if defined(HAVE_LINUX_NETWORK)
+  int netlinkfd, kernel_version;
+#elif defined(HAVE_BSD_NETWORK)
+  int dhcp_raw_fd, dhcp_icmp_fd, routefd;
+#endif
+  struct iovec dhcp_packet;
+  char *dhcp_buff, *dhcp_buff2, *dhcp_buff3;
+  struct ping_result *ping_results;
+  FILE *lease_stream;
+  struct dhcp_bridge *bridges;
+  struct shared_network *shared_networks;
+#ifdef HAVE_DHCP6
+  int duid_len;
+  unsigned char *duid;
+  struct iovec outpacket;
+  int dhcp6fd, icmp6fd;
+#endif
+  /* DBus stuff */
+  /* void * here to avoid depending on dbus headers outside dbus.c */
+  void *dbus;
+#ifdef HAVE_DBUS
+  struct watch *watches;
+#endif
+  /* UBus stuff */
+#ifdef HAVE_UBUS
+  /* void * here to avoid depending on ubus headers outside ubus.c */
+  void *ubus;
+#endif
+
+  /* TFTP stuff */
+  struct tftp_transfer *tftp_trans, *tftp_done_trans;
+
+  /* utility string buffer, hold max sized IP address as string */
+  char *addrbuff;
+  char *addrbuff2; /* only allocated when OPT_EXTRALOG */
+
+#ifdef HAVE_DUMPFILE
+  /* file for packet dumps. */
+  int dumpfd;
+#endif
+} *daemon;
+
+/* cache.c */
+void cache_init(void);
+void next_uid(struct crec *crecp);
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg); 
+char *record_source(unsigned int index);
+char *querystr(char *desc, unsigned short type);
+int cache_find_non_terminal(char *name, time_t now);
+struct crec *cache_find_by_addr(struct crec *crecp,
+				union all_addr *addr, time_t now, 
+				unsigned int prot);
+struct crec *cache_find_by_name(struct crec *crecp, 
+				char *name, time_t now, unsigned int prot);
+void cache_end_insert(void);
+void cache_start_insert(void);
+int cache_recv_insert(time_t now, int fd);
+struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class, 
+			  time_t now, unsigned long ttl, unsigned int flags);
+void cache_reload(void);
+void cache_add_dhcp_entry(char *host_name, int prot, union all_addr *host_address, time_t ttd);
+struct in_addr a_record_from_hosts(char *name, time_t now);
+void cache_unhash_dhcp(void);
+void dump_cache(time_t now);
+#ifndef NO_ID
+int cache_make_stat(struct txt_record *t);
+#endif
+char *cache_get_name(struct crec *crecp);
+char *cache_get_cname_target(struct crec *crecp);
+struct crec *cache_enumerate(int init);
+int read_hostsfile(char *filename, unsigned int index, int cache_size, 
+		   struct crec **rhash, int hashsz);
+
+/* blockdata.c */
+void blockdata_init(void);
+void blockdata_report(void);
+struct blockdata *blockdata_alloc(char *data, size_t len);
+void *blockdata_retrieve(struct blockdata *block, size_t len, void *data);
+struct blockdata *blockdata_read(int fd, size_t len);
+void blockdata_write(struct blockdata *block, size_t len, int fd);
+void blockdata_free(struct blockdata *blocks);
+
+/* domain.c */
+char *get_domain(struct in_addr addr);
+char *get_domain6(struct in6_addr *addr);
+int is_name_synthetic(int flags, char *name, union all_addr *addr);
+int is_rev_synth(int flag, union all_addr *addr, char *name);
+
+/* rfc1035.c */
+int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, 
+                 char *name, int isExtract, int extrabytes);
+unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
+unsigned char *skip_questions(struct dns_header *header, size_t plen);
+unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen);
+unsigned int extract_request(struct dns_header *header, size_t qlen, 
+			       char *name, unsigned short *typep);
+void setup_reply(struct dns_header *header, unsigned int flags, int ede);
+int extract_addresses(struct dns_header *header, size_t qlen, char *name,
+		      time_t now, char **ipsets, int is_sign, int check_rebind,
+		      int no_cache_dnssec, int secure, int *doctored);
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+void report_addresses(struct dns_header *header, size_t len, u32 mark);
+#endif
+size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
+		      struct in_addr local_addr, struct in_addr local_netmask, 
+		      time_t now, int ad_reqd, int do_bit, int have_pseudoheader);
+int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, 
+			     time_t now);
+int check_for_ignored_address(struct dns_header *header, size_t qlen);
+int check_for_local_domain(char *name, time_t now);
+size_t resize_packet(struct dns_header *header, size_t plen, 
+		  unsigned char *pheader, size_t hlen);
+int add_resource_record(struct dns_header *header, char *limit, int *truncp,
+			int nameoffset, unsigned char **pp, unsigned long ttl, 
+			int *offset, unsigned short type, unsigned short class, char *format, ...);
+int in_arpa_name_2_addr(char *namein, union all_addr *addrp);
+int private_net(struct in_addr addr, int ban_localhost);
+
+/* auth.c */
+#ifdef HAVE_AUTH
+size_t answer_auth(struct dns_header *header, char *limit, size_t qlen, 
+		   time_t now, union mysockaddr *peer_addr, int local_query,
+		   int do_bit, int have_pseudoheader);
+int in_zone(struct auth_zone *zone, char *name, char **cut);
+#endif
+
+/* dnssec.c */
+#ifdef HAVE_DNSSEC
+size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
+			  int check_unsigned, int *neganswer, int *nons, int *nsec_ttl);
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
+size_t filter_rrsigs(struct dns_header *header, size_t plen);
+int setup_timestamp(void);
+int errflags_to_ede(int status);
+#endif
+
+/* hash_questions.c */
+void hash_questions_init(void);
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name);
+
+/* crypto.c */
+const struct nettle_hash *hash_find(char *name);
+int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp);
+int verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+	   unsigned char *digest, size_t digest_len, int algo);
+char *ds_digest_name(int digest);
+char *algo_digest_name(int algo);
+char *nsec3_digest_name(int digest);
+
+/* util.c */
+void rand_init(void);
+unsigned short rand16(void);
+u32 rand32(void);
+u64 rand64(void);
+int legal_hostname(char *name);
+char *canonicalise(char *in, int *nomem);
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
+void *safe_malloc(size_t size);
+void safe_strncpy(char *dest, const char *src, size_t size);
+void safe_pipe(int *fd, int read_noblock);
+void *whine_malloc(size_t size);
+int sa_len(union mysockaddr *addr);
+int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2);
+int hostname_isequal(const char *a, const char *b);
+int hostname_issubdomain(char *a, char *b);
+time_t dnsmasq_time(void);
+int netmask_length(struct in_addr mask);
+int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask);
+int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix);
+int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen);
+u64 addr6part(struct in6_addr *addr);
+void setaddr6part(struct in6_addr *addr, u64 host);
+int retry_send(ssize_t rc);
+void prettyprint_time(char *buf, unsigned int t);
+int prettyprint_addr(union mysockaddr *addr, char *buf);
+int parse_hex(char *in, unsigned char *out, int maxlen, 
+	      unsigned int *wildcard_mask, int *mac_type);
+int memcmp_masked(unsigned char *a, unsigned char *b, int len, 
+		  unsigned int mask);
+int expand_buf(struct iovec *iov, size_t size);
+char *print_mac(char *buff, unsigned char *mac, int len);
+int read_write(int fd, unsigned char *packet, int size, int rw);
+void close_fds(long max_fd, int spare1, int spare2, int spare3);
+int wildcard_match(const char* wildcard, const char* match);
+int wildcard_matchn(const char* wildcard, const char* match, int num);
+#ifdef HAVE_LINUX_NETWORK
+int kernel_version(void);
+#endif
+
+/* log.c */
+void die(char *message, char *arg1, int exit_code) ATTRIBUTE_NORETURN;
+int log_start(struct passwd *ent_pw, int errfd);
+int log_reopen(char *log_file);
+
+void my_syslog(int priority, const char *format, ...);
+
+void set_log_writer(void);
+void check_log_writer(int force);
+void flush_log(void);
+
+/* option.c */
+void read_opts (int argc, char **argv, char *compile_opts);
+char *option_string(int prot, unsigned int opt, unsigned char *val, 
+		    int opt_len, char *buf, int buf_len);
+void reread_dhcp(void);
+void read_servers_file(void);
+void set_option_bool(unsigned int opt);
+void reset_option_bool(unsigned int opt);
+struct hostsfile *expand_filelist(struct hostsfile *list);
+char *parse_server(char *arg, union mysockaddr *addr, 
+		   union mysockaddr *source_addr, char *interface, u16 *flags);
+int option_read_dynfile(char *file, int flags);
+
+/* forward.c */
+void reply_query(int fd, time_t now);
+void receive_query(struct listener *listen, time_t now);
+unsigned char *tcp_request(int confd, time_t now,
+			   union mysockaddr *local_addr, struct in_addr netmask, int auth_dns);
+void server_gone(struct server *server);
+int send_from(int fd, int nowild, char *packet, size_t len, 
+	       union mysockaddr *to, union all_addr *source,
+	       unsigned int iface);
+void resend_query(void);
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv);
+void free_rfds(struct randfd_list **fdlp);
+
+/* network.c */
+int indextoname(int fd, int index, char *name);
+int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp);
+void pre_allocate_sfds(void);
+int reload_servers(char *fname);
+void check_servers(int no_loop_call);
+int enumerate_interfaces(int reset);
+void create_wildcard_listeners(void);
+void create_bound_listeners(int dienow);
+void warn_bound_listeners(void);
+void warn_wild_labels(void);
+void warn_int_names(void);
+int is_dad_listeners(void);
+int iface_check(int family, union all_addr *addr, char *name, int *auth);
+int loopback_exception(int fd, int family, union all_addr *addr, char *name);
+int label_exception(int index, int family, union all_addr *addr);
+int fix_fd(int fd);
+int tcp_interface(int fd, int af);
+int set_ipv6pktinfo(int fd);
+#ifdef HAVE_DHCP6
+void join_multicast(int dienow);
+#endif
+#if defined(HAVE_LINUX_NETWORK) || defined(HAVE_BSD_NETWORK)
+void newaddress(time_t now);
+#endif
+
+
+/* dhcp.c */
+#ifdef HAVE_DHCP
+void dhcp_init(void);
+void dhcp_packet(time_t now, int pxe_fd);
+struct dhcp_context *address_available(struct dhcp_context *context, 
+				       struct in_addr taddr,
+				       struct dhcp_netid *netids);
+struct dhcp_context *narrow_context(struct dhcp_context *context, 
+				    struct in_addr taddr,
+				    struct dhcp_netid *netids);
+struct ping_result *do_icmp_ping(time_t now, struct in_addr addr,
+				 unsigned int hash, int loopback);
+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 loopback);
+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);
+#endif
+
+/* lease.c */
+#ifdef HAVE_DHCP
+void lease_update_file(time_t now);
+void lease_update_dns(int force);
+void lease_init(time_t now);
+struct dhcp_lease *lease4_allocate(struct in_addr addr);
+#ifdef HAVE_DHCP6
+struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type);
+struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, 
+			       int lease_type, unsigned int iaid, struct in6_addr *addr);
+void lease6_reset(void);
+struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
+					 unsigned char *clid, int clid_len, unsigned int iaid);
+struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr);
+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, unsigned int iaid);
+void lease_make_duid(time_t now);
+#endif
+void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
+		      const unsigned char *clid, int hw_len, int hw_type,
+		      int clid_len, time_t now, int force);
+void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain);
+void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now);
+void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now);
+struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,  
+					unsigned char *clid, int clid_len);
+struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
+struct in_addr lease_find_max_addr(struct dhcp_context *context);
+void lease_prune(struct dhcp_lease *target, time_t now);
+void lease_update_from_configs(void);
+int do_script_run(time_t now);
+void rerun_scripts(void);
+void lease_find_interfaces(time_t now);
+#ifdef HAVE_SCRIPT
+void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, 
+			 unsigned int len, int delim);
+#endif
+#endif
+
+/* rfc2131.c */
+#ifdef HAVE_DHCP
+size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
+		  size_t sz, time_t now, int unicast_dest, int loopback,
+		  int *is_inform, int pxe, struct in_addr fallback, time_t recvtime);
+unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, 
+			       int clid_len, unsigned char *clid, int *len_out);
+#endif
+
+/* dnsmasq.c */
+#ifdef HAVE_DHCP
+int make_icmp_sock(void);
+int icmp_ping(struct in_addr addr);
+int delay_dhcp(time_t start, int sec, int fd, uint32_t addr, unsigned short id);
+#endif
+void queue_event(int event);
+void send_alarm(time_t event, time_t now);
+void send_event(int fd, int event, int data, char *msg);
+void clear_cache_and_reload(time_t now);
+
+/* netlink.c */
+#ifdef HAVE_LINUX_NETWORK
+char *netlink_init(void);
+void netlink_multicast(void);
+#endif
+
+/* bpf.c */
+#ifdef HAVE_BSD_NETWORK
+void init_bpf(void);
+void send_via_bpf(struct dhcp_packet *mess, size_t len,
+		  struct in_addr iface_addr, struct ifreq *ifr);
+void route_init(void);
+void route_sock(void);
+#endif
+
+/* bpf.c or netlink.c */
+int iface_enumerate(int family, void *parm, int (callback)());
+
+/* dbus.c */
+#ifdef HAVE_DBUS
+char *dbus_init(void);
+void check_dbus_listeners(void);
+void set_dbus_listeners(void);
+#  ifdef HAVE_DHCP
+void emit_dbus_signal(int action, struct dhcp_lease *lease, char *hostname);
+#  endif
+#endif
+
+/* ubus.c */
+#ifdef HAVE_UBUS
+char *ubus_init(void);
+void set_ubus_listeners(void);
+void check_ubus_listeners(void);
+void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface);
+#  ifdef HAVE_CONNTRACK
+void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name);
+void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *pattern, const char *ip, u32 ttl);
+#  endif
+#endif
+
+/* ipset.c */
+#ifdef HAVE_IPSET
+void ipset_init(void);
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove);
+#endif
+
+/* pattern.c */
+#ifdef HAVE_CONNTRACK
+int is_valid_dns_name(const char *value);
+int is_valid_dns_name_pattern(const char *value);
+int is_dns_name_matching_pattern(const char *name, const char *pattern);
+#endif
+
+/* helper.c */
+#if defined(HAVE_SCRIPT)
+int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
+void helper_write(void);
+void queue_script(int action, struct dhcp_lease *lease, 
+		  char *hostname, time_t now);
+#ifdef HAVE_TFTP
+void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer);
+#endif
+void queue_arp(int action, unsigned char *mac, int maclen,
+	       int family, union all_addr *addr);
+int helper_buf_empty(void);
+#endif
+
+/* tftp.c */
+#ifdef HAVE_TFTP
+void tftp_request(struct listener *listen, time_t now);
+void check_tftp_listeners(time_t now);
+int do_tftp_script_run(void);
+#endif
+
+/* conntrack.c */
+#ifdef HAVE_CONNTRACK
+int get_incoming_mark(union mysockaddr *peer_addr, union all_addr *local_addr,
+		      int istcp, unsigned int *markp);
+#endif
+
+/* dhcp6.c */
+#ifdef HAVE_DHCP6
+void dhcp6_init(void);
+void dhcp6_packet(time_t now);
+struct dhcp_context *address6_allocate(struct dhcp_context *context,  unsigned char *clid, int clid_len, int temp_addr,
+				       unsigned int iaid, int serial, struct dhcp_netid *netids, int plain_range, struct in6_addr *ans);
+struct dhcp_context *address6_available(struct dhcp_context *context, 
+					struct in6_addr *taddr,
+					struct dhcp_netid *netids,
+					int plain_range);
+struct dhcp_context *address6_valid(struct dhcp_context *context, 
+				    struct in6_addr *taddr,
+				    struct dhcp_netid *netids,
+				    int plain_range);
+struct dhcp_config *config_find_by_address6(struct dhcp_config *configs, struct in6_addr *net, 
+					    int prefix, struct in6_addr *addr);
+void make_duid(time_t now);
+void dhcp_construct_contexts(time_t now);
+void get_client_mac(struct in6_addr *client, int iface, unsigned char *mac, 
+		    unsigned int *maclenp, unsigned int *mactypep, time_t now);
+#endif
+  
+/* rfc3315.c */
+#ifdef HAVE_DHCP6
+unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,  
+			   struct in6_addr *fallback, struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+			   size_t sz, struct in6_addr *client_addr, time_t now);
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, struct in6_addr *peer_address, 
+		     u32 scope_id, time_t now);
+
+unsigned short relay_reply6( struct sockaddr_in6 *peer, ssize_t sz, char *arrival_interface);
+#endif
+
+/* dhcp-common.c */
+#ifdef HAVE_DHCP
+void dhcp_common_init(void);
+ssize_t recv_dhcp_packet(int fd, struct msghdr *msg);
+struct dhcp_netid *run_tag_if(struct dhcp_netid *tags);
+struct dhcp_netid *option_filter(struct dhcp_netid *tags, struct dhcp_netid *context_tags,
+				 struct dhcp_opt *opts);
+int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded);
+char *strip_hostname(char *hostname);
+void log_tags(struct dhcp_netid *netid, u32 xid);
+int match_bytes(struct dhcp_opt *o, unsigned char *p, int len);
+void dhcp_update_configs(struct dhcp_config *configs);
+void display_opts(void);
+int lookup_dhcp_opt(int prot, char *name);
+int lookup_dhcp_len(int prot, int val);
+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,
+				struct dhcp_netid *filter);
+int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type);
+#ifdef HAVE_LINUX_NETWORK
+char *whichdevice(void);
+void bindtodevice(char *device, int fd);
+#endif
+#  ifdef HAVE_DHCP6
+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 */
+#ifdef HAVE_DHCP6
+void end_opt6(int container);
+void reset_counter(void);
+int save_counter(int newval);
+void *expand(size_t headroom);
+int new_opt6(int opt);
+void *put_opt6(void *data, size_t len);
+void put_opt6_long(unsigned int val);
+void put_opt6_short(unsigned int val);
+void put_opt6_char(unsigned int val);
+void put_opt6_string(char *s);
+#endif
+
+/* radv.c */
+#ifdef HAVE_DHCP6
+void ra_init(time_t now);
+void icmp6_packet(time_t now);
+time_t periodic_ra(time_t now);
+void ra_start_unsolicited(time_t now, struct dhcp_context *context);
+#endif
+
+/* slaac.c */ 
+#ifdef HAVE_DHCP6
+void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force);
+time_t periodic_slaac(time_t now, struct dhcp_lease *leases);
+void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases);
+#endif
+
+/* loop.c */
+#ifdef HAVE_LOOP
+void loop_send_probes(void);
+int detect_loop(char *query, int type);
+#endif
+
+/* inotify.c */
+#ifdef HAVE_INOTIFY
+void inotify_dnsmasq_init(void);
+int inotify_check(time_t now);
+void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz);
+#endif
+
+/* poll.c */
+void poll_reset(void);
+int poll_check(int fd, short event);
+void poll_listen(int fd, short event);
+int do_poll(int timeout);
+
+/* rrfilter.c */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode);
+u16 *rrfilter_desc(int type);
+int expand_workspace(unsigned char ***wkspc, int *szp, int new);
+
+/* edns0.c */
+unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
+				   size_t *len, unsigned char **p, int *is_sign, int *is_last);
+size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, 
+			unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
+size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
+size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
+			union mysockaddr *source, time_t now, int *check_subnet, int *cacheable);
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+
+/* arp.c */
+int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now);
+int do_arp_script_run(void);
+
+/* dump.c */
+#ifdef HAVE_DUMPFILE
+void dump_init(void);
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst);
+#endif
+
+/* domain-match.c */
+void build_server_array(void);
+int lookup_domain(char *qdomain, int flags, int *lowout, int *highout);
+int filter_servers(int seed, int flags, int *lowout, int *highout);
+int is_local_answer(time_t now, int first, char *name);
+size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header,
+			 char *name, char *limit, int first, int last, int ede);
+int server_samegroup(struct server *a, struct server *b);
+#ifdef HAVE_DNSSEC
+int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp);
+#endif
+void mark_servers(int flag);
+void cleanup_servers(void);
+int add_update_server(int flags,
+		      union mysockaddr *addr,
+		      union mysockaddr *source_addr,
+		      const char *interface,
+		      const char *domain,
+		      union all_addr *local_addr); 
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c
new file mode 100755
index 0000000..153cac4
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dnssec.c
@@ -0,0 +1,2199 @@
+/* dnssec.c is Copyright (c) 2012 Giovanni Bajo <rasky@develer.com>
+           and Copyright (c) 2012-2020 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_DNSSEC
+
+#define SERIAL_UNDEF  -100
+#define SERIAL_EQ        0
+#define SERIAL_LT       -1
+#define SERIAL_GT        1
+
+/* Convert from presentation format to wire format, in place.
+   Also map UC -> LC.
+   Note that using extract_name to get presentation format
+   then calling to_wire() removes compression and maps case,
+   thus generating names in canonical form.
+   Calling to_wire followed by from_wire is almost an identity,
+   except that the UC remains mapped to LC. 
+
+   Note that both /000 and '.' are allowed within labels. These get
+   represented in presentation format using NAME_ESCAPE as an escape
+   character. In theory, if all the characters in a name were /000 or
+   '.' or NAME_ESCAPE then all would have to be escaped, so the 
+   presentation format would be twice as long as the spec (1024). 
+   The buffers are all declared as 2049 (allowing for the trailing zero) 
+   for this reason.
+*/
+static int to_wire(char *name)
+{
+  unsigned char *l, *p, *q, term;
+  int len;
+
+  for (l = (unsigned char*)name; *l != 0; l = p)
+    {
+      for (p = l; *p != '.' && *p != 0; p++)
+	if (*p >= 'A' && *p <= 'Z')
+	  *p = *p - 'A' + 'a';
+	else if (*p == NAME_ESCAPE)
+	  {
+	    for (q = p; *q; q++)
+	      *q = *(q+1);
+	    (*p)--;
+	  }
+      term = *p;
+      
+      if ((len = p - l) != 0)
+	memmove(l+1, l, len);
+      *l = len;
+      
+      p++;
+      
+      if (term == 0)
+	*p = 0;
+    }
+  
+  return l + 1 - (unsigned char *)name;
+}
+
+/* Note: no compression  allowed in input. */
+static void from_wire(char *name)
+{
+  unsigned char *l, *p, *last;
+  int len;
+  
+  for (last = (unsigned char *)name; *last != 0; last += *last+1);
+  
+  for (l = (unsigned char *)name; *l != 0; l += len+1)
+    {
+      len = *l;
+      memmove(l, l+1, len);
+      for (p = l; p < l + len; p++)
+	if (*p == '.' || *p == 0 || *p == NAME_ESCAPE)
+	  {
+	    memmove(p+1, p, 1 + last - p);
+	    len++;
+	    *p++ = NAME_ESCAPE; 
+	    (*p)++;
+	  }
+	
+      l[len] = '.';
+    }
+
+  if ((char *)l != name)
+    *(l-1) = 0;
+}
+
+/* Input in presentation format */
+static int count_labels(char *name)
+{
+  int i;
+  char *p;
+  
+  if (*name == 0)
+    return 0;
+
+  for (p = name, i = 0; *p; p++)
+    if (*p == '.')
+      i++;
+
+  /* Don't count empty first label. */
+  return *name == '.' ? i : i+1;
+}
+
+/* Implement RFC1982 wrapped compare for 32-bit numbers */
+static int serial_compare_32(u32 s1, u32 s2)
+{
+  if (s1 == s2)
+    return SERIAL_EQ;
+
+  if ((s1 < s2 && (s2 - s1) < (1UL<<31)) ||
+      (s1 > s2 && (s1 - s2) > (1UL<<31)))
+    return SERIAL_LT;
+  if ((s1 < s2 && (s2 - s1) > (1UL<<31)) ||
+      (s1 > s2 && (s1 - s2) < (1UL<<31)))
+    return SERIAL_GT;
+  return SERIAL_UNDEF;
+}
+
+/* Called at startup. If the timestamp file is configured and exists, put its mtime on
+   timestamp_time. If it doesn't exist, create it, and set the mtime to 1-1-2015.
+   return -1 -> Cannot create file.
+           0 -> not using timestamp, or timestamp exists and is in past.
+           1 -> timestamp exists and is in future.
+*/
+
+static time_t timestamp_time;
+
+int setup_timestamp(void)
+{
+  struct stat statbuf;
+  
+  daemon->back_to_the_future = 0;
+  
+  if (!daemon->timestamp_file)
+    return 0;
+  
+  if (stat(daemon->timestamp_file, &statbuf) != -1)
+    {
+      timestamp_time = statbuf.st_mtime;
+    check_and_exit:
+      if (difftime(timestamp_time, time(0)) <=  0)
+	{
+	  /* time already OK, update timestamp, and do key checking from the start. */
+	  if (utimes(daemon->timestamp_file, NULL) == -1)
+	    my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+	  daemon->back_to_the_future = 1;
+	  return 0;
+	}
+      return 1;
+    }
+  
+  if (errno == ENOENT)
+    {
+      /* NB. for explanation of O_EXCL flag, see comment on pidfile in dnsmasq.c */ 
+      int fd = open(daemon->timestamp_file, O_WRONLY | O_CREAT | O_NONBLOCK | O_EXCL, 0666);
+      if (fd != -1)
+	{
+	  struct timeval tv[2];
+
+	  close(fd);
+	  
+	  timestamp_time = 1420070400; /* 1-1-2015 */
+	  tv[0].tv_sec = tv[1].tv_sec = timestamp_time;
+	  tv[0].tv_usec = tv[1].tv_usec = 0;
+	  if (utimes(daemon->timestamp_file, tv) == 0)
+	    goto check_and_exit;
+	}
+    }
+
+  return -1;
+}
+
+/* Check whether today/now is between date_start and date_end */
+static int is_check_date(unsigned long curtime)
+{
+  /* Checking timestamps may be temporarily disabled */
+    
+  /* If the current time if _before_ the timestamp
+     on our persistent timestamp file, then assume the
+     time if not yet correct, and don't check the
+     key timestamps. As soon as the current time is
+     later then the timestamp, update the timestamp
+     and start checking keys */
+  if (daemon->timestamp_file)
+    {
+      if (daemon->back_to_the_future == 0 && difftime(timestamp_time, curtime) <= 0)
+	{
+	  if (utimes(daemon->timestamp_file, NULL) != 0)
+	    my_syslog(LOG_ERR, _("failed to update mtime on %s: %s"), daemon->timestamp_file, strerror(errno));
+	  
+	  my_syslog(LOG_INFO, _("system time considered valid, now checking DNSSEC signature timestamps."));
+	  daemon->back_to_the_future = 1;
+	  daemon->dnssec_no_time_check = 0;
+	  queue_event(EVENT_RELOAD); /* purge cache */
+	} 
+
+      return daemon->back_to_the_future;
+    }
+  else
+    return !daemon->dnssec_no_time_check;
+}
+
+/* Return bytes of canonicalised rrdata one by one.
+   Init state->ip with the RR, and state->end with the end of same.
+   Init state->op to NULL.
+   Init state->desc to RR descriptor.
+   Init state->buff with a MAXDNAME * 2 buffer.
+   
+   After each call which returns 1, state->op points to the next byte of data.
+   On returning 0, the end has been reached.
+*/
+struct rdata_state {
+  u16 *desc;
+  size_t c;
+  unsigned char *end, *ip, *op;
+  char *buff;
+};
+
+static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
+{
+  int d;
+  
+  if (state->op && state->c != 1)
+    {
+      state->op++;
+      state->c--;
+      return 1;
+    }
+
+  while (1)
+    {
+      d = *(state->desc);
+      
+      if (d == (u16)-1)
+	{
+	  /* all the bytes to the end. */
+	  if ((state->c = state->end - state->ip) != 0)
+	    {
+	      state->op = state->ip;
+	      state->ip = state->end;;
+	    }
+	  else
+	    return 0;
+	}
+      else
+	{
+	  state->desc++;
+	  
+	  if (d == (u16)0)
+	    {
+	      /* domain-name, canonicalise */
+	      int len;
+	      
+	      if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
+		  (len = to_wire(state->buff)) == 0)
+		continue;
+	      
+	      state->c = len;
+	      state->op = (unsigned char *)state->buff;
+	    }
+	  else
+	    {
+	      /* plain data preceding a domain-name, don't run off the end of the data */
+	      if ((state->end - state->ip) < d)
+		d = state->end - state->ip;
+	      
+	      if (d == 0)
+		continue;
+		  
+	      state->op = state->ip;
+	      state->c = d;
+	      state->ip += d;
+	    }
+	}
+      
+      return 1;
+    }
+}
+
+/* Bubble sort the RRset into the canonical order. */
+
+static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
+		      unsigned char **rrset, char *buff1, char *buff2)
+{
+  int swap, i, j;
+  
+  do
+    {
+      for (swap = 0, i = 0; i < rrsetidx-1; i++)
+	{
+	  int rdlen1, rdlen2;
+	  struct rdata_state state1, state2;
+	  
+	  /* Note that these have been determined to be OK previously,
+	     so we don't need to check for NULL return here. */
+	  state1.ip = skip_name(rrset[i], header, plen, 10);
+	  state2.ip = skip_name(rrset[i+1], header, plen, 10);
+	  state1.op = state2.op = NULL;
+	  state1.buff = buff1;
+	  state2.buff = buff2;
+	  state1.desc = state2.desc = rr_desc;
+	  
+	  state1.ip += 8; /* skip class, type, ttl */
+	  GETSHORT(rdlen1, state1.ip);
+	  if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
+	    return rrsetidx; /* short packet */
+	  state1.end = state1.ip + rdlen1;
+	  
+	  state2.ip += 8; /* skip class, type, ttl */
+	  GETSHORT(rdlen2, state2.ip);
+	  if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
+	    return rrsetidx; /* short packet */
+	  state2.end = state2.ip + rdlen2; 
+
+	  /* If the RR has no names in it then canonicalisation
+	     is the identity function and we can compare
+	     the RRs directly. If not we compare the 
+	     canonicalised RRs one byte at a time. */
+	  if (*rr_desc == (u16)-1)	  
+	    {
+	      int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
+	      int cmp = memcmp(state1.ip, state2.ip, rdmin);
+	      
+	      if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin))
+		{
+		  unsigned char *tmp = rrset[i+1];
+		  rrset[i+1] = rrset[i];
+		  rrset[i] = tmp;
+		  swap = 1;
+		}
+	      else if (cmp == 0 && (rdlen1 == rdlen2))
+		{
+		  /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+		  for (j = i+1; j < rrsetidx-1; j++)
+		    rrset[j] = rrset[j+1];
+		  rrsetidx--;
+		  i--;
+		}
+	    }
+	  else
+	    /* Comparing canonicalised RRs, byte-at-a-time. */
+	    while (1)
+	      {
+		int ok1, ok2;
+		
+		ok1 = get_rdata(header, plen, &state1);
+		ok2 = get_rdata(header, plen, &state2);
+		
+		if (!ok1 && !ok2)
+		  {
+		    /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+		    for (j = i+1; j < rrsetidx-1; j++)
+		      rrset[j] = rrset[j+1];
+		    rrsetidx--;
+		    i--;
+		    break;
+		  }
+		else if (ok1 && (!ok2 || *state1.op > *state2.op)) 
+		  {
+		    unsigned char *tmp = rrset[i+1];
+		    rrset[i+1] = rrset[i];
+		    rrset[i] = tmp;
+		    swap = 1;
+		    break;
+		  }
+		else if (ok2 && (!ok1 || *state2.op > *state1.op))
+		  break;
+		
+		/* arrive here when bytes are equal, go round the loop again
+		   and compare the next ones. */
+	      }
+	}
+    } while (swap);
+
+  return rrsetidx;
+}
+
+static unsigned char **rrset = NULL, **sigs = NULL;
+
+/* Get pointers to RRset members and signature(s) for same.
+   Check signatures, and return keyname associated in keyname. */
+static int explore_rrset(struct dns_header *header, size_t plen, int class, int type, 
+			 char *name, char *keyname, int *sigcnt, int *rrcnt)
+{
+  static int rrset_sz = 0, sig_sz = 0; 
+  unsigned char *p;
+  int rrsetidx, sigidx, j, rdlen, res;
+  int gotkey = 0;
+
+  if (!(p = skip_questions(header, plen)))
+    return 0;
+
+   /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
+  for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
+       j != 0; j--) 
+    {
+      unsigned char *pstart, *pdata;
+      int stype, sclass, type_covered;
+
+      pstart = p;
+      
+      if (!(res = extract_name(header, plen, &p, name, 0, 10)))
+	return 0; /* bad packet */
+      
+      GETSHORT(stype, p);
+      GETSHORT(sclass, p);
+           
+      pdata = p;
+
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+      
+      if (!CHECK_LEN(header, p, plen, rdlen))
+	return 0; 
+      
+      if (res == 1 && sclass == class)
+	{
+	  if (stype == type)
+	    {
+	      if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
+		return 0; 
+	      
+	      rrset[rrsetidx++] = pstart;
+	    }
+	  
+	  if (stype == T_RRSIG)
+	    {
+	      if (rdlen < 18)
+		return 0; /* bad packet */ 
+	      
+	      GETSHORT(type_covered, p);
+	      p += 16; /* algo, labels, orig_ttl, sig_expiration, sig_inception, key_tag */
+	      
+	      if (gotkey)
+		{
+		  /* If there's more than one SIG, ensure they all have same keyname */
+		  if (extract_name(header, plen, &p, keyname, 0, 0) != 1)
+		    return 0;
+		}
+	      else
+		{
+		  gotkey = 1;
+		  
+		  if (!extract_name(header, plen, &p, keyname, 1, 0))
+		    return 0;
+		  
+		  /* RFC 4035 5.3.1 says that the Signer's Name field MUST equal
+		     the name of the zone containing the RRset. We can't tell that
+		     for certain, but we can check that  the RRset name is equal to
+		     or encloses the signers name, which should be enough to stop 
+		     an attacker using signatures made with the key of an unrelated 
+		     zone he controls. Note that the root key is always allowed. */
+		  if (*keyname != 0)
+		    {
+		      char *name_start;
+		      for (name_start = name; !hostname_isequal(name_start, keyname); )
+			if ((name_start = strchr(name_start, '.')))
+			  name_start++; /* chop a label off and try again */
+			else
+			  return 0;
+		    }
+		}
+		  
+	      
+	      if (type_covered == type)
+		{
+		  if (!expand_workspace(&sigs, &sig_sz, sigidx))
+		    return 0; 
+		  
+		  sigs[sigidx++] = pdata;
+		} 
+	      
+	      p = pdata + 6; /* restore for ADD_RDLEN */
+	    }
+	}
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	return 0;
+    }
+  
+  *sigcnt = sigidx;
+  *rrcnt = rrsetidx;
+
+  return 1;
+}
+
+/* Validate a single RRset (class, type, name) in the supplied DNS reply 
+   Return code:
+   STAT_SECURE   if it validates.
+   STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
+   (In this case *wildcard_out points to the "body" of the wildcard within name.) 
+   STAT_BOGUS    signature is wrong, bad packet.
+   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
+   STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
+
+   If key is non-NULL, use that key, which has the algo and tag given in the params of those names,
+   otherwise find the key in the cache.
+
+   Name is unchanged on exit. keyname is used as workspace and trashed.
+
+   Call explore_rrset first to find and count RRs and sigs.
+
+   ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
+*/
+static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 
+			  char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
+			  int algo_in, int keytag_in, unsigned long *ttl_out)
+{
+  unsigned char *p;
+  int rdlen, j, name_labels, algo, labels, key_tag;
+  struct crec *crecp = NULL;
+  u16 *rr_desc = rrfilter_desc(type);
+  u32 sig_expiration, sig_inception;
+  int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
+  
+  unsigned long curtime = time(0);
+  int time_check = is_check_date(curtime);
+  
+  if (wildcard_out)
+    *wildcard_out = NULL;
+  
+  name_labels = count_labels(name); /* For 4035 5.3.2 check */
+
+  /* Sort RRset records into canonical order. 
+     Note that at this point keyname and daemon->workspacename buffs are
+     unused, and used as workspace by the sort. */
+  rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
+         
+  /* Now try all the sigs to try and find one which validates */
+  for (j = 0; j <sigidx; j++)
+    {
+      unsigned char *psav, *sig, *digest;
+      int i, wire_len, sig_len;
+      const struct nettle_hash *hash;
+      void *ctx;
+      char *name_start;
+      u32 nsigttl, ttl, orig_ttl;
+
+      failflags &= ~DNSSEC_FAIL_NOSIG;
+      
+      p = sigs[j];
+      GETLONG(ttl, p);
+      GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
+      psav = p;
+      
+      p += 2; /* type_covered - already checked */
+      algo = *p++;
+      labels = *p++;
+      GETLONG(orig_ttl, p);
+      GETLONG(sig_expiration, p);
+      GETLONG(sig_inception, p);
+      GETSHORT(key_tag, p);
+      
+      if (!extract_name(header, plen, &p, keyname, 1, 0))
+	return STAT_BOGUS;
+
+      if (!time_check)
+	failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
+      else
+	{
+	  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+	  if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
+	    continue;
+	  else
+	    failflags &= ~DNSSEC_FAIL_NYV;
+	  
+	  if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
+	    continue;
+	  else
+	    failflags &= ~DNSSEC_FAIL_EXP;
+	}
+
+      if (!(hash = hash_find(algo_digest_name(algo))))
+	continue;
+      else
+	failflags &= ~DNSSEC_FAIL_NOKEYSUP;
+      
+      if (labels > name_labels ||
+	  !hash_init(hash, &ctx, &digest))
+	continue;
+      
+      /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
+      if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
+	return STAT_NEED_KEY;
+
+       if (ttl_out)
+	 {
+	   /* 4035 5.3.3 rules on TTLs */
+	   if (orig_ttl < ttl)
+	     ttl = orig_ttl;
+	   
+	   if (time_check && difftime(sig_expiration, curtime) < ttl)
+	     ttl = difftime(sig_expiration, curtime);
+
+	   *ttl_out = ttl;
+	 }
+       
+      sig = p;
+      sig_len = rdlen - (p - psav);
+              
+      nsigttl = htonl(orig_ttl);
+      
+      hash->update(ctx, 18, psav);
+      wire_len = to_wire(keyname);
+      hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
+      from_wire(keyname);
+
+#define RRBUFLEN 128 /* Most RRs are smaller than this. */
+      
+      for (i = 0; i < rrsetidx; ++i)
+	{
+	  int j;
+	  struct rdata_state state;
+	  u16 len;
+	  unsigned char rrbuf[RRBUFLEN];
+	  
+	  p = rrset[i];
+	  
+	  if (!extract_name(header, plen, &p, name, 1, 10)) 
+	    return STAT_BOGUS;
+
+	  name_start = name;
+	  
+	  /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
+	  if (labels < name_labels)
+	    {
+	      for (j = name_labels - labels; j != 0; j--)
+		{
+		  while (*name_start != '.' && *name_start != 0)
+		    name_start++;
+		  if (j != 1 && *name_start == '.')
+		    name_start++;
+		}
+	      
+	      if (wildcard_out)
+		*wildcard_out = name_start+1;
+
+	      name_start--;
+	      *name_start = '*';
+	    }
+	  
+	  wire_len = to_wire(name_start);
+	  hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
+	  hash->update(ctx, 4, p); /* class and type */
+	  hash->update(ctx, 4, (unsigned char *)&nsigttl);
+
+	  p += 8; /* skip type, class, ttl */
+	  GETSHORT(rdlen, p);
+	  if (!CHECK_LEN(header, p, plen, rdlen))
+	    return STAT_BOGUS; 
+
+	  /* Optimisation for RR types which need no cannonicalisation.
+	     This includes DNSKEY DS NSEC and NSEC3, which are also long, so
+	     it saves lots of calls to get_rdata, and avoids the pessimal
+	     segmented insertion, even with a small rrbuf[].
+	     
+	     If canonicalisation is not needed, a simple insertion into the hash works.
+	  */
+	  if (*rr_desc == (u16)-1)
+	    {
+	      len = htons(rdlen);
+	      hash->update(ctx, 2, (unsigned char *)&len);
+	      hash->update(ctx, rdlen, p);
+	    }
+	  else
+	    {
+	      /* canonicalise rdata and calculate length of same, use 
+		 name buffer as workspace for get_rdata. */
+	      state.ip = p;
+	      state.op = NULL;
+	      state.desc = rr_desc;
+	      state.buff = name;
+	      state.end = p + rdlen;
+	      
+	      for (j = 0; get_rdata(header, plen, &state); j++)
+		if (j < RRBUFLEN)
+		  rrbuf[j] = *state.op;
+	      
+	      len = htons((u16)j);
+	      hash->update(ctx, 2, (unsigned char *)&len); 
+	      
+	      /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+		 then we can just digest it now. If it exceeds RRBUFLEN we have to
+		 go back to the start and do it in chunks. */
+	      if (j >= RRBUFLEN)
+		{
+		  state.ip = p;
+		  state.op = NULL;
+		  state.desc = rr_desc;
+		  
+		  for (j = 0; get_rdata(header, plen, &state); j++)
+		    {
+		      rrbuf[j] = *state.op;
+		      
+		      if (j == RRBUFLEN - 1)
+			{
+			  hash->update(ctx, RRBUFLEN, rrbuf);
+			  j = -1;
+			}
+		    }
+		}
+	      
+	      if (j != 0)
+		hash->update(ctx, j, rrbuf);
+	    }
+	}
+     
+      hash->digest(ctx, hash->digest_size, digest);
+      
+      /* namebuff used for workspace above, restore to leave unchanged on exit */
+      p = (unsigned char*)(rrset[0]);
+      extract_name(header, plen, &p, name, 1, 0);
+
+      if (key)
+	{
+	  if (algo_in == algo && keytag_in == key_tag &&
+	      verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
+	    return STAT_SECURE;
+	}
+      else
+	{
+	  /* iterate through all possible keys 4035 5.3.1 */
+	  for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
+	    if (crecp->addr.key.algo == algo && 
+		crecp->addr.key.keytag == key_tag &&
+		crecp->uid == (unsigned int)class &&
+		verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
+	      return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+	}
+    }
+
+  /* If we reach this point, no verifying key was found */
+  return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY;
+}
+ 
+
+/* The DNS packet is expected to contain the answer to a DNSKEY query.
+   Put all DNSKEYs in the answer which are valid into the cache.
+   return codes:
+         STAT_OK        Done, key(s) in cache.
+	 STAT_BOGUS     No DNSKEYs found, which  can be validated with DS,
+	                or self-sign for DNSKEY RRset is not valid, bad packet.
+	 STAT_NEED_DS   DS records to validate a key not found, name in keyname 
+	 STAT_NEED_KEY  DNSKEY records to validate a key not found, name in keyname 
+*/
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+  unsigned char *psave, *p = (unsigned char *)(header+1);
+  struct crec *crecp, *recp1;
+  int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag;
+  unsigned long ttl, sig_ttl;
+  struct blockdata *key;
+  union all_addr a;
+  int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY;
+
+  if (ntohs(header->qdcount) != 1 ||
+      RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
+      !extract_name(header, plen, &p, name, 1, 4))
+    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+  
+  if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
+    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+
+  /* See if we have cached a DS record which validates this key */
+  if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
+    {
+      strcpy(keyname, name);
+      return STAT_NEED_DS;
+    }
+  
+  /* NOTE, we need to find ONE DNSKEY which matches the DS */
+  for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) 
+    {
+      /* Ensure we have type, class  TTL and length */
+      if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	return STAT_BOGUS; /* bad packet */
+  
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+      GETLONG(ttl, p);
+      GETSHORT(rdlen, p);
+ 
+      if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+	return STAT_BOGUS; /* bad packet */
+      
+      if (qclass != class || qtype != T_DNSKEY || rc == 2)
+	{
+	  p += rdlen;
+	  continue;
+	}
+            
+      psave = p;
+      
+      GETSHORT(flags, p);
+      if (*p++ != 3)
+	return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
+      algo = *p++;
+      keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+      key = NULL;
+      
+      /* key must have zone key flag set */
+      if (flags & 0x100)
+	{
+	  key = blockdata_alloc((char*)p, rdlen - 4);
+	  failflags &= ~DNSSEC_FAIL_NOZONE;
+	}
+      
+      p = psave;
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	{
+	  if (key)
+	    blockdata_free(key);
+	  return STAT_BOGUS; /* bad packet */
+	}
+
+      /* No zone key flag or malloc failure */
+      if (!key)
+	continue;
+      
+      for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
+	{
+	  void *ctx;
+	  unsigned char *digest, *ds_digest;
+	  const struct nettle_hash *hash;
+	  int sigcnt, rrcnt;
+	  int wire_len;
+	  
+	  if (recp1->addr.ds.algo == algo && 
+	      recp1->addr.ds.keytag == keytag &&
+	      recp1->uid == (unsigned int)class)
+	    {
+	      failflags &= ~DNSSEC_FAIL_NOKEY;
+	      
+	      if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
+		continue;
+	      else
+		failflags &= ~DNSSEC_FAIL_NODSSUP;
+
+	      if (!hash_init(hash, &ctx, &digest))
+		continue;
+	      
+	      wire_len = to_wire(name);
+	      
+	      /* Note that digest may be different between DSs, so 
+		 we can't move this outside the loop. */
+	      hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
+	      hash->update(ctx, (unsigned int)rdlen, psave);
+	      hash->digest(ctx, hash->digest_size, digest);
+	      
+	      from_wire(name);
+	      
+	      if (!(recp1->flags & F_NEG) &&
+		  recp1->addr.ds.keylen == (int)hash->digest_size &&
+		  (ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
+		  memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
+		  explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
+		  rrcnt != 0)
+		{
+		  if (sigcnt == 0)
+		    continue;
+		  else
+		    failflags &= ~DNSSEC_FAIL_NOSIG;
+		  
+		  rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, 
+				      NULL, key, rdlen - 4, algo, keytag, &sig_ttl);
+
+		  failflags &= rc;
+		  
+		  if (STAT_ISEQUAL(rc, STAT_SECURE))
+		    {
+		      valid = 1;
+		      break;
+		    }
+		}
+	    }
+	}
+      blockdata_free(key);
+    }
+
+  if (valid)
+    {
+      /* DNSKEY RRset determined to be OK, now cache it. */
+      cache_start_insert();
+      
+      p = skip_questions(header, plen);
+
+      for (j = ntohs(header->ancount); j != 0; j--) 
+	{
+	  /* Ensure we have type, class  TTL and length */
+	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  GETSHORT(qtype, p); 
+	  GETSHORT(qclass, p);
+	  GETLONG(ttl, p);
+	  GETSHORT(rdlen, p);
+
+	  /* TTL may be limited by sig. */
+	  if (sig_ttl < ttl)
+	    ttl = sig_ttl;
+	    
+	  if (!CHECK_LEN(header, p, plen, rdlen))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  if (qclass == class && rc == 1)
+	    {
+	      psave = p;
+	      
+	      if (qtype == T_DNSKEY)
+		{
+		  if (rdlen < 4)
+		    return STAT_BOGUS; /* bad packet */
+		  
+		  GETSHORT(flags, p);
+		  if (*p++ != 3)
+		    return STAT_BOGUS;
+		  algo = *p++;
+		  keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+		  
+		  if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+		    {
+		      a.key.keylen = rdlen - 4;
+		      a.key.keydata = key;
+		      a.key.algo = algo;
+		      a.key.keytag = keytag;
+		      a.key.flags = flags;
+		      
+		      if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK))
+			{
+			  blockdata_free(key);
+			  return STAT_BOGUS;
+			}
+		      else
+			{
+			  a.log.keytag = keytag;
+			  a.log.algo = algo;
+			  if (algo_digest_name(algo))
+			    log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
+			  else
+			    log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
+			}
+		    }
+		}
+	      	      
+	      p = psave;
+	    }
+
+	  if (!ADD_RDLEN(header, p, plen, rdlen))
+	    return STAT_BOGUS; /* bad packet */
+	}
+      
+      /* commit cache insert. */
+      cache_end_insert();
+      return STAT_OK;
+    }
+
+  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
+  return STAT_BOGUS | failflags;
+}
+
+/* The DNS packet is expected to contain the answer to a DS query
+   Put all DSs in the answer which are valid into the cache.
+   Also handles replies which prove that there's no DS at this location, 
+   either because the zone is unsigned or this isn't a zone cut. These are
+   cached too.
+   return codes:
+   STAT_OK          At least one valid DS found and in cache.
+   STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.
+   STAT_NEED_KEY    DNSKEY records to validate a DS not found, name in keyname
+   STAT_NEED_DS     DS record needed.
+*/
+
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+{
+  unsigned char *p = (unsigned char *)(header+1);
+  int qtype, qclass, rc, i, neganswer, nons, neg_ttl = 0;
+  int aclass, atype, rdlen;
+  unsigned long ttl;
+  union all_addr a;
+
+  if (ntohs(header->qdcount) != 1 ||
+      !(p = skip_name(p, header, plen, 4)))
+    return STAT_BOGUS;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  if (qtype != T_DS || qclass != class)
+    rc = STAT_BOGUS;
+  else
+    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl);
+  
+  if (STAT_ISEQUAL(rc, STAT_INSECURE))
+    {
+      my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
+      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure");
+      return STAT_BOGUS | DNSSEC_FAIL_INDET;
+    }
+  
+  p = (unsigned char *)(header+1);
+  extract_name(header, plen, &p, name, 1, 4);
+  p += 4; /* qtype, qclass */
+  
+  /* If the key needed to validate the DS is on the same domain as the DS, we'll
+     loop getting nowhere. Stop that now. This can happen of the DS answer comes
+     from the DS's zone, and not the parent zone. */
+  if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
+    {
+      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
+      return STAT_BOGUS;
+    }
+  
+  if (!STAT_ISEQUAL(rc, STAT_SECURE))
+    return rc;
+   
+  if (!neganswer)
+    {
+      cache_start_insert();
+      
+      for (i = 0; i < ntohs(header->ancount); i++)
+	{
+	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  GETSHORT(atype, p);
+	  GETSHORT(aclass, p);
+	  GETLONG(ttl, p);
+	  GETSHORT(rdlen, p);
+	  
+	  if (!CHECK_LEN(header, p, plen, rdlen))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  if (aclass == class && atype == T_DS && rc == 1)
+	    { 
+	      int algo, digest, keytag;
+	      unsigned char *psave = p;
+	      struct blockdata *key;
+	   
+	      if (rdlen < 4)
+		return STAT_BOGUS; /* bad packet */
+	      
+	      GETSHORT(keytag, p);
+	      algo = *p++;
+	      digest = *p++;
+	      
+	      if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+		{
+		  a.ds.digest = digest;
+		  a.ds.keydata = key;
+		  a.ds.algo = algo;
+		  a.ds.keytag = keytag;
+		  a.ds.keylen = rdlen - 4;
+
+		  if (!cache_insert(name, &a, class, now, ttl, F_FORWARD | F_DS | F_DNSSECOK))
+		    {
+		      blockdata_free(key);
+		      return STAT_BOGUS;
+		    }
+		  else
+		    {
+		      a.log.keytag = keytag;
+		      a.log.algo = algo;
+		      a.log.digest = digest;
+		      if (ds_digest_name(digest) && algo_digest_name(algo))
+			log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");
+		      else
+			log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)");
+		    } 
+		}
+	      
+	      p = psave;
+	    }
+	  if (!ADD_RDLEN(header, p, plen, rdlen))
+	    return STAT_BOGUS; /* bad packet */
+	}
+
+      cache_end_insert();
+
+    }
+  else
+    {
+      int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
+            
+      if (RCODE(header) == NXDOMAIN)
+	flags |= F_NXDOMAIN;
+      
+      /* We only cache validated DS records, DNSSECOK flag hijacked 
+	 to store presence/absence of NS. */
+      if (nons)
+	flags &= ~F_DNSSECOK;
+      
+      cache_start_insert();
+	  
+      /* Use TTL from NSEC for negative cache entries */
+      if (!cache_insert(name, NULL, class, now, neg_ttl, flags))
+	return STAT_BOGUS;
+      
+      cache_end_insert();  
+      
+      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, nons ? "no DS/cut" : "no DS");
+    }
+      
+  return STAT_OK;
+}
+
+
+/* 4034 6.1 */
+static int hostname_cmp(const char *a, const char *b)
+{
+  char *sa, *ea, *ca, *sb, *eb, *cb;
+  unsigned char ac, bc;
+  
+  sa = ea = (char *)a + strlen(a);
+  sb = eb = (char *)b + strlen(b);
+ 
+  while (1)
+    {
+      while (sa != a && *(sa-1) != '.')
+	sa--;
+      
+      while (sb != b && *(sb-1) != '.')
+	sb--;
+
+      ca = sa;
+      cb = sb;
+
+      while (1) 
+	{
+	  if (ca == ea)
+	    {
+	      if (cb == eb)
+		break;
+	      
+	      return -1;
+	    }
+	  
+	  if (cb == eb)
+	    return 1;
+	  
+	  ac = (unsigned char) *ca++;
+	  bc = (unsigned char) *cb++;
+	  
+	  if (ac >= 'A' && ac <= 'Z')
+	    ac += 'a' - 'A';
+	  if (bc >= 'A' && bc <= 'Z')
+	    bc += 'a' - 'A';
+	  
+	  if (ac < bc)
+	    return -1;
+	  else if (ac != bc)
+	    return 1;
+	}
+
+     
+      if (sa == a)
+	{
+	  if (sb == b)
+	    return 0;
+	  
+	  return -1;
+	}
+      
+      if (sb == b)
+	return 1;
+      
+      ea = --sa;
+      eb = --sb;
+    }
+}
+
+static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
+				    char *workspace1_in, char *workspace2, char *name, int type, int *nons)
+{
+  int i, rc, rdlen;
+  unsigned char *p, *psave;
+  int offset = (type & 0xff) >> 3;
+  int mask = 0x80 >> (type & 0x07);
+
+  if (nons)
+    *nons = 1;
+  
+  /* Find NSEC record that proves name doesn't exist */
+  for (i = 0; i < nsec_count; i++)
+    {
+      char *workspace1 = workspace1_in;
+      int sig_labels, name_labels;
+
+      p = nsecs[i];
+      if (!extract_name(header, plen, &p, workspace1, 1, 10))
+	return 0;
+      p += 8; /* class, type, TTL */
+      GETSHORT(rdlen, p);
+      psave = p;
+      if (!extract_name(header, plen, &p, workspace2, 1, 10))
+	return 0;
+
+      /* If NSEC comes from wildcard expansion, use original wildcard
+	 as name for computation. */
+      sig_labels = *labels[i];
+      name_labels = count_labels(workspace1);
+
+      if (sig_labels < name_labels)
+	{
+	  int k;
+	  for (k = name_labels - sig_labels; k != 0; k--)
+	    {
+	      while (*workspace1 != '.' && *workspace1 != 0)
+		workspace1++;
+	      if (k != 1 && *workspace1 == '.')
+		workspace1++;
+	    }
+	  
+	  workspace1--;
+	  *workspace1 = '*';
+	}
+	  
+      rc = hostname_cmp(workspace1, name);
+      
+      if (rc == 0)
+	{
+	  /* 4035 para 5.4. Last sentence */
+	  if (type == T_NSEC || type == T_RRSIG)
+	    return 1;
+
+	  /* NSEC with the same name as the RR we're testing, check
+	     that the type in question doesn't appear in the type map */
+	  rdlen -= p - psave;
+	  /* rdlen is now length of type map, and p points to it */
+	  
+	  /* If we can prove that there's no NS record, return that information. */
+	  if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
+	    *nons = 0;
+	  
+	  if (rdlen >= 2 && p[0] == 0)
+	    {
+	      /* A CNAME answer would also be valid, so if there's a CNAME is should 
+		 have been returned. */
+	      if ((p[2] & (0x80 >> T_CNAME)) != 0)
+		return 0;
+	      
+	      /* If the SOA bit is set for a DS record, then we have the
+		 DS from the wrong side of the delegation. For the root DS, 
+		 this is expected. */
+	      if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+		return 0;
+	    }
+
+	  while (rdlen >= 2)
+	    {
+	      if (!CHECK_LEN(header, p, plen, rdlen))
+		return 0;
+	      
+	      if (p[0] == type >> 8)
+		{
+		  /* Does the NSEC say our type exists? */
+		  if (offset < p[1] && (p[offset+2] & mask) != 0)
+		    return 0;
+		  
+		  break; /* finished checking */
+		}
+	      
+	      rdlen -= p[1];
+	      p +=  p[1];
+	    }
+	  
+	  return 1;
+	}
+      else if (rc == -1)
+	{
+	  /* Normal case, name falls between NSEC name and next domain name,
+	     wrap around case, name falls between NSEC name (rc == -1) and end */
+	  if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
+	    return 1;
+	}
+      else 
+	{
+	  /* wrap around case, name falls between start and next domain name */
+	  if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
+	    return 1;
+	}
+    }
+  
+  return 0;
+}
+
+/* return digest length, or zero on error */
+static int hash_name(char *in, unsigned char **out, struct nettle_hash const *hash, 
+		     unsigned char *salt, int salt_len, int iterations)
+{
+  void *ctx;
+  unsigned char *digest;
+  int i;
+
+  if (!hash_init(hash, &ctx, &digest))
+    return 0;
+ 
+  hash->update(ctx, to_wire(in), (unsigned char *)in);
+  hash->update(ctx, salt_len, salt);
+  hash->digest(ctx, hash->digest_size, digest);
+
+  for(i = 0; i < iterations; i++)
+    {
+      hash->update(ctx, hash->digest_size, digest);
+      hash->update(ctx, salt_len, salt);
+      hash->digest(ctx, hash->digest_size, digest);
+    }
+   
+  from_wire(in);
+
+  *out = digest;
+  return hash->digest_size;
+}
+
+/* Decode base32 to first "." or end of string */
+static int base32_decode(char *in, unsigned char *out)
+{
+  int oc, on, c, mask, i;
+  unsigned char *p = out;
+ 
+  for (c = *in, oc = 0, on = 0; c != 0 && c != '.'; c = *++in) 
+    {
+      if (c >= '0' && c <= '9')
+	c -= '0';
+      else if (c >= 'a' && c <= 'v')
+	c -= 'a', c += 10;
+      else if (c >= 'A' && c <= 'V')
+	c -= 'A', c += 10;
+      else
+	return 0;
+      
+      for (mask = 0x10, i = 0; i < 5; i++)
+        {
+	  if (c & mask)
+	    oc |= 1;
+	  mask = mask >> 1;
+	  if (((++on) & 7) == 0)
+	    *p++ = oc;
+	  oc = oc << 1;
+	}
+    }
+  
+  if ((on & 7) != 0)
+    return 0;
+
+  return p - out;
+}
+
+static int check_nsec3_coverage(struct dns_header *header, size_t plen, int digest_len, unsigned char *digest, int type,
+				char *workspace1, char *workspace2, unsigned char **nsecs, int nsec_count, int *nons, int name_labels)
+{
+  int i, hash_len, salt_len, base32_len, rdlen, flags;
+  unsigned char *p, *psave;
+
+  for (i = 0; i < nsec_count; i++)
+    if ((p = nsecs[i]))
+      {
+       	if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+	    !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+	  return 0;
+	
+	p += 8; /* class, type, TTL */
+	GETSHORT(rdlen, p);
+	psave = p;
+	p++; /* algo */
+	flags = *p++; /* flags */
+	p += 2; /* iterations */
+	salt_len = *p++; /* salt_len */
+	p += salt_len; /* salt */
+	hash_len = *p++; /* p now points to next hashed name */
+	
+	if (!CHECK_LEN(header, p, plen, hash_len))
+	  return 0;
+	
+	if (digest_len == base32_len && hash_len == base32_len)
+	  {
+	    int rc = memcmp(workspace2, digest, digest_len);
+
+	    if (rc == 0)
+	      {
+		/* We found an NSEC3 whose hashed name exactly matches the query, so
+		   we just need to check the type map. p points to the RR data for the record. */
+		
+		int offset = (type & 0xff) >> 3;
+		int mask = 0x80 >> (type & 0x07);
+		
+		p += hash_len; /* skip next-domain hash */
+		rdlen -= p - psave;
+
+		if (!CHECK_LEN(header, p, plen, rdlen))
+		  return 0;
+		
+		if (rdlen >= 2 && p[0] == 0)
+		  {
+		    /* If we can prove that there's no NS record, return that information. */
+		    if (nons && (p[2] & (0x80 >> T_NS)) != 0)
+		      *nons = 0;
+		
+		    /* A CNAME answer would also be valid, so if there's a CNAME is should 
+		       have been returned. */
+		    if ((p[2] & (0x80 >> T_CNAME)) != 0)
+		      return 0;
+		    
+		    /* If the SOA bit is set for a DS record, then we have the
+		       DS from the wrong side of the delegation. For the root DS, 
+		       this is expected.  */
+		    if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
+		      return 0;
+		  }
+
+		while (rdlen >= 2)
+		  {
+		    if (p[0] == type >> 8)
+		      {
+			/* Does the NSEC3 say our type exists? */
+			if (offset < p[1] && (p[offset+2] & mask) != 0)
+			  return 0;
+			
+			break; /* finished checking */
+		      }
+		    
+		    rdlen -= p[1];
+		    p +=  p[1];
+		  }
+		
+		return 1;
+	      }
+	    else if (rc < 0)
+	      {
+		/* Normal case, hash falls between NSEC3 name-hash and next domain name-hash,
+		   wrap around case, name-hash falls between NSEC3 name-hash and end */
+		if (memcmp(p, digest, digest_len) >= 0 || memcmp(workspace2, p, digest_len) >= 0)
+		  {
+		    if ((flags & 0x01) && nons) /* opt out */
+		      *nons = 0;
+
+		    return 1;
+		  }
+	      }
+	    else 
+	      {
+		/* wrap around case, name falls between start and next domain name */
+		if (memcmp(workspace2, p, digest_len) >= 0 && memcmp(p, digest, digest_len) >= 0)
+		  {
+		    if ((flags & 0x01) && nons) /* opt out */
+		      *nons = 0;
+
+		    return 1;
+		  }
+	      }
+	  }
+      }
+
+  return 0;
+}
+
+static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
+				     char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
+{
+  unsigned char *salt, *p, *digest;
+  int digest_len, i, iterations, salt_len, base32_len, algo = 0;
+  struct nettle_hash const *hash;
+  char *closest_encloser, *next_closest, *wildcard;
+  
+  if (nons)
+    *nons = 1;
+  
+  /* Look though the NSEC3 records to find the first one with 
+     an algorithm we support.
+
+     Take the algo, iterations, and salt of that record
+     as the ones we're going to use, and prune any 
+     that don't match. */
+  
+  for (i = 0; i < nsec_count; i++)
+    {
+      if (!(p = skip_name(nsecs[i], header, plen, 15)))
+	return 0; /* bad packet */
+      
+     p += 10; /* type, class, TTL, rdlen */
+      algo = *p++;
+      
+      if ((hash = hash_find(nsec3_digest_name(algo))))
+	break; /* known algo */
+    }
+
+  /* No usable NSEC3s */
+  if (i == nsec_count)
+    return 0;
+
+  p++; /* flags */
+
+  GETSHORT (iterations, p);
+  /* Upper-bound iterations, to avoid DoS.
+     Strictly, there are lower bounds for small keys, but
+     since we don't have key size info here, at least limit
+     to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
+  if (iterations > 2500)
+    return 0;
+  
+  salt_len = *p++;
+  salt = p;
+  if (!CHECK_LEN(header, salt, plen, salt_len))
+    return 0; /* bad packet */
+    
+  /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
+  for (i = 0; i < nsec_count; i++)
+    {
+      unsigned char *nsec3p = nsecs[i];
+      int this_iter, flags;
+
+      nsecs[i] = NULL; /* Speculative, will be restored if OK. */
+      
+      if (!(p = skip_name(nsec3p, header, plen, 15)))
+	return 0; /* bad packet */
+      
+      p += 10; /* type, class, TTL, rdlen */
+      
+      if (*p++ != algo)
+	continue;
+ 
+      flags = *p++; /* flags */
+      
+      /* 5155 8.2 */
+      if (flags != 0 && flags != 1)
+	continue;
+
+      GETSHORT(this_iter, p);
+      if (this_iter != iterations)
+	continue;
+
+      if (salt_len != *p++)
+	continue;
+      
+      if (!CHECK_LEN(header, p, plen, salt_len))
+	return 0; /* bad packet */
+
+      if (memcmp(p, salt, salt_len) != 0)
+	continue;
+
+      /* All match, put the pointer back */
+      nsecs[i] = nsec3p;
+    }
+
+  if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
+    return 0;
+  
+  if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
+    return 1;
+
+  /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" 
+     or an answer inferred from a wildcard record. */
+  closest_encloser = name;
+  next_closest = NULL;
+
+  do
+    {
+      if (*closest_encloser == '.')
+	closest_encloser++;
+
+      if (wildname && hostname_isequal(closest_encloser, wildname))
+	break;
+
+      if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
+	return 0;
+      
+      for (i = 0; i < nsec_count; i++)
+	if ((p = nsecs[i]))
+	  {
+	    if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+		!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+	      return 0;
+	  
+	    if (digest_len == base32_len &&
+		memcmp(digest, workspace2, digest_len) == 0)
+	      break; /* Gotit */
+	  }
+      
+      if (i != nsec_count)
+	break;
+      
+      next_closest = closest_encloser;
+    }
+  while ((closest_encloser = strchr(closest_encloser, '.')));
+  
+  if (!closest_encloser || !next_closest)
+    return 0;
+  
+  /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
+  if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
+    return 0;
+
+  if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
+    return 0;
+  
+  /* Finally, check that there's no seat of wildcard synthesis */
+  if (!wildname)
+    {
+      if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
+	return 0;
+      
+      wildcard--;
+      *wildcard = '*';
+      
+      if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
+	return 0;
+      
+      if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
+	return 0;
+    }
+  
+  return 1;
+}
+
+static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl)
+{
+  static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
+  static int nsecset_sz = 0, rrsig_labels_sz = 0;
+  
+  int type_found = 0;
+  unsigned char *auth_start, *p = skip_questions(header, plen);
+  int type, class, rdlen, i, nsecs_found;
+  unsigned long ttl;
+  
+  /* Move to NS section */
+  if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
+    return 0;
+
+  auth_start = p;
+  
+  for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
+    {
+      unsigned char *pstart = p;
+      
+      if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
+	return 0;
+	  
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      GETLONG(ttl, p);
+      GETSHORT(rdlen, p);
+
+      if (class == qclass && (type == T_NSEC || type == T_NSEC3))
+	{
+	  if (nsec_ttl)
+	    {
+	      /* Limit TTL with sig TTL */
+	      if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
+		ttl = daemon->rr_status[ntohs(header->ancount) + i];
+	      *nsec_ttl = ttl;
+	    }
+	  
+	  /* No mixed NSECing 'round here, thankyouverymuch */
+	  if (type_found != 0 && type_found != type)
+	    return 0;
+
+	  type_found = type;
+
+	  if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+	    return 0; 
+	  
+	  if (type == T_NSEC)
+	    {
+	      /* If we're looking for NSECs, find the corresponding SIGs, to 
+		 extract the labels value, which we need in case the NSECs
+		 are the result of wildcard expansion.
+		 Note that the NSEC may not have been validated yet
+		 so if there are multiple SIGs, make sure the label value
+		 is the same in all, to avoid be duped by a rogue one.
+		 If there are no SIGs, that's an error */
+	      unsigned char *p1 = auth_start;
+	      int res, j, rdlen1, type1, class1;
+	      
+	      if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
+		return 0;
+	      
+	      rrsig_labels[nsecs_found] = NULL;
+	      
+	      for (j = ntohs(header->nscount); j != 0; j--)
+		{
+		  if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
+		    return 0;
+
+		   GETSHORT(type1, p1); 
+		   GETSHORT(class1, p1);
+		   p1 += 4; /* TTL */
+		   GETSHORT(rdlen1, p1);
+
+		   if (!CHECK_LEN(header, p1, plen, rdlen1))
+		     return 0;
+		   
+		   if (res == 1 && class1 == qclass && type1 == T_RRSIG)
+		     {
+		       int type_covered;
+		       unsigned char *psav = p1;
+		       
+		       if (rdlen1 < 18)
+			 return 0; /* bad packet */
+
+		       GETSHORT(type_covered, p1);
+
+		       if (type_covered == T_NSEC)
+			 {
+			   p1++; /* algo */
+			   
+			   /* labels field must be the same in every SIG we find. */
+			   if (!rrsig_labels[nsecs_found])
+			     rrsig_labels[nsecs_found] = p1;
+			   else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
+			     return 0;
+			   }
+		       p1 = psav;
+		     }
+		   
+		   if (!ADD_RDLEN(header, p1, plen, rdlen1))
+		     return 0;
+		}
+
+	      /* Must have found at least one sig. */
+	      if (!rrsig_labels[nsecs_found])
+		return 0;
+	    }
+
+	  nsecset[nsecs_found++] = pstart;   
+	}
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	return 0;
+    }
+  
+  if (type_found == T_NSEC)
+    return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
+  else if (type_found == T_NSEC3)
+    return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+  else
+    return 0;
+}
+
+/* Check signing status of name.
+   returns:
+   STAT_SECURE   zone is signed.
+   STAT_INSECURE zone proved unsigned.
+   STAT_NEED_DS  require DS record of name returned in keyname.
+   STAT_NEED_KEY require DNSKEY record of name returned in keyname.
+   name returned unaltered.
+*/
+static int zone_status(char *name, int class, char *keyname, time_t now)
+{
+  int name_start = strlen(name); /* for when TA is root */
+  struct crec *crecp;
+  char *p;
+
+  /* First, work towards the root, looking for a trust anchor.
+     This can either be one configured, or one previously cached.
+     We can assume, if we don't find one first, that there is
+     a trust anchor at the root. */
+  for (p = name; p; p = strchr(p, '.'))
+    {
+      if (*p == '.')
+	p++;
+
+      if (cache_find_by_name(NULL, p, now, F_DS))
+	{
+	  name_start = p - name;
+	  break;
+	}
+    }
+
+  /* Now work away from the trust anchor */
+  while (1)
+    {
+      strcpy(keyname, &name[name_start]);
+      
+      if (!(crecp = cache_find_by_name(NULL, keyname, now, F_DS)))
+	return STAT_NEED_DS;
+      
+       /* F_DNSSECOK misused in DS cache records to non-existence of NS record.
+	  F_NEG && !F_DNSSECOK implies that we've proved there's no DS record here,
+	  but that's because there's no NS record either, ie this isn't the start
+	  of a zone. We only prove that the DNS tree below a node is unsigned when
+	  we prove that we're at a zone cut AND there's no DS record. */
+      if (crecp->flags & F_NEG)
+	{
+	  if (crecp->flags & F_DNSSECOK)
+	    return STAT_INSECURE; /* proved no DS here */
+	}
+      else
+	{
+	  /* If all the DS records have digest and/or sig algos we don't support,
+	     then the zone is insecure. Note that if an algo
+	     appears in the DS, then RRSIGs for that algo MUST
+	     exist for each RRset: 4035 para 2.2  So if we find
+	     a DS here with digest and sig we can do, we're entitled
+	     to assume we can validate the zone and if we can't later,
+	     because an RRSIG is missing we return BOGUS.
+	  */
+	  do 
+	    {
+	      if (crecp->uid == (unsigned int)class &&
+		  ds_digest_name(crecp->addr.ds.digest) &&
+		  algo_digest_name(crecp->addr.ds.algo))
+		break;
+	    }
+	  while ((crecp = cache_find_by_name(crecp, keyname, now, F_DS)));
+
+	  if (!crecp)
+	    return STAT_INSECURE;
+	}
+
+      if (name_start == 0)
+	break;
+
+      for (p = &name[name_start-2]; (*p != '.') && (p != name); p--);
+      
+      if (p != name)
+        p++;
+      
+      name_start = p - name;
+    } 
+
+  return STAT_SECURE;
+}
+       
+/* Validate all the RRsets in the answer and authority sections of the reply (4035:3.2.3) 
+   Return code:
+   STAT_SECURE   if it validates.
+   STAT_INSECURE at least one RRset not validated, because in unsigned zone.
+   STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
+   STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
+   STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
+
+   daemon->rr_status points to a char array which corressponds to the RRs in the 
+   answer and auth sections. This is set to 1 for each RR which is validated, and 0 for any which aren't.
+
+   When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
+   Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
+   is the nons argument is non-NULL.
+*/
+int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
+			  int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl)
+{
+  static unsigned char **targets = NULL;
+  static int target_sz = 0;
+
+  unsigned char *ans_start, *p1, *p2;
+  int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
+  int i, j, rc = STAT_INSECURE;
+  int secure = STAT_SECURE;
+
+  /* extend rr_status if necessary */
+  if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
+    {
+      unsigned long *new = whine_malloc(sizeof(*daemon->rr_status) * (ntohs(header->ancount) + ntohs(header->nscount) + 64));
+
+      if (!new)
+	return STAT_BOGUS;
+
+      free(daemon->rr_status);
+      daemon->rr_status = new;
+      daemon->rr_status_sz = ntohs(header->ancount) + ntohs(header->nscount) + 64;
+    }
+  
+  memset(daemon->rr_status, 0, sizeof(*daemon->rr_status) * daemon->rr_status_sz);
+  
+  if (neganswer)
+    *neganswer = 0;
+  
+  if (RCODE(header) == SERVFAIL || ntohs(header->qdcount) != 1)
+    return STAT_BOGUS;
+  
+  if (RCODE(header) != NXDOMAIN && RCODE(header) != NOERROR)
+    return STAT_INSECURE;
+
+  p1 = (unsigned char *)(header+1);
+  
+   /* Find all the targets we're looking for answers to.
+     The zeroth array element is for the query, subsequent ones
+     for CNAME targets, unless the query is for a CNAME or ANY. */
+
+  if (!expand_workspace(&targets, &target_sz, 0))
+    return STAT_BOGUS;
+  
+  targets[0] = p1;
+  targetidx = 1;
+   
+  if (!extract_name(header, plen, &p1, name, 1, 4))
+    return STAT_BOGUS;
+  
+  GETSHORT(qtype, p1);
+  GETSHORT(qclass, p1);
+  ans_start = p1;
+ 
+  /* Can't validate an RRSIG query */
+  if (qtype == T_RRSIG)
+    return STAT_INSECURE;
+  
+  if (qtype != T_CNAME && qtype != T_ANY)
+    for (j = ntohs(header->ancount); j != 0; j--) 
+      {
+	if (!(p1 = skip_name(p1, header, plen, 10)))
+	  return STAT_BOGUS; /* bad packet */
+	
+	GETSHORT(type2, p1); 
+	p1 += 6; /* class, TTL */
+	GETSHORT(rdlen2, p1);  
+	
+	if (type2 == T_CNAME)
+	  {
+	    if (!expand_workspace(&targets, &target_sz, targetidx))
+	      return STAT_BOGUS;
+	    
+	    targets[targetidx++] = p1; /* pointer to target name */
+	  }
+	
+	if (!ADD_RDLEN(header, p1, plen, rdlen2))
+	  return STAT_BOGUS;
+      }
+  
+  for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+    {
+      if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
+	return STAT_BOGUS;
+      
+      if (!extract_name(header, plen, &p1, name, 1, 10))
+	return STAT_BOGUS; /* bad packet */
+      
+      GETSHORT(type1, p1);
+      GETSHORT(class1, p1);
+      p1 += 4; /* TTL */
+      GETSHORT(rdlen1, p1);
+      
+      /* Don't try and validate RRSIGs! */
+      if (type1 == T_RRSIG)
+	continue;
+      
+      /* Check if we've done this RRset already */
+      for (p2 = ans_start, j = 0; j < i; j++)
+	{
+	  if (!(rc = extract_name(header, plen, &p2, name, 0, 10)))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  GETSHORT(type2, p2);
+	  GETSHORT(class2, p2);
+	  p2 += 4; /* TTL */
+	  GETSHORT(rdlen2, p2);
+	  
+	  if (type2 == type1 && class2 == class1 && rc == 1)
+	    break; /* Done it before: name, type, class all match. */
+	  
+	  if (!ADD_RDLEN(header, p2, plen, rdlen2))
+	    return STAT_BOGUS;
+	}
+      
+      /* Done already: copy the validation status */
+      if (j != i)
+	daemon->rr_status[i] = daemon->rr_status[j];
+      else
+	{
+	  /* Not done, validate now */
+	  int sigcnt, rrcnt;
+	  char *wildname;
+	  
+	  if (!explore_rrset(header, plen, class1, type1, name, keyname, &sigcnt, &rrcnt))
+	    return STAT_BOGUS;
+	  
+	  /* No signatures for RRset. We can be configured to assume this is OK and return an INSECURE result. */
+	  if (sigcnt == 0)
+	    {
+	      /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
+	      if (type1 == T_NSEC || type1 == T_NSEC3)
+		rc = STAT_INSECURE;
+	      else if (nons && i >= ntohs(header->ancount))
+		/* If we're validating a DS reply, rather than looking for the value of AD bit,
+		   we only care that NSEC and NSEC3 RRs in the auth section are signed. 
+		   Return SECURE even if others (SOA....) are not. */
+		rc = STAT_SECURE;
+	      else
+		{
+		  /* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
+		  if (check_unsigned && i < ntohs(header->ancount))
+		    {
+		      rc = zone_status(name, class1, keyname, now);
+		      if (STAT_ISEQUAL(rc, STAT_SECURE))
+			rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+		      if (class)
+			*class = class1; /* Class for NEED_DS or NEED_KEY */
+		    }
+		  else 
+		    rc = STAT_INSECURE; 
+		  
+		  if (!STAT_ISEQUAL(rc, STAT_INSECURE))
+		    return rc;
+		}
+	    }
+	  else
+	    {
+	      /* explore_rrset() gives us key name from sigs in keyname.
+		 Can't overwrite name here. */
+	      strcpy(daemon->workspacename, keyname);
+	      rc = zone_status(daemon->workspacename, class1, keyname, now);
+	      
+	      if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
+		{
+		  if (class)
+		    *class = class1; /* Class for NEED_DS or NEED_KEY */
+		  return rc;
+		}
+	      
+	      /* Zone is insecure, don't need to validate RRset */
+	      if (STAT_ISEQUAL(rc, STAT_SECURE))
+		{
+		  unsigned long sig_ttl;
+		  rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
+				      rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl);
+		  
+		  if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS))
+		    {
+		      if (class)
+			*class = class1; /* Class for DS or DNSKEY */
+		      return rc;
+		    } 
+		  
+		  /* rc is now STAT_SECURE or STAT_SECURE_WILDCARD */
+		  
+		  /* Note that RR is validated */
+		  daemon->rr_status[i] = sig_ttl;
+		   
+		  /* Note if we've validated either the answer to the question
+		     or the target of a CNAME. Any not noted will need NSEC or
+		     to be in unsigned space. */
+		  for (j = 0; j <targetidx; j++)
+		    if ((p2 = targets[j]))
+		      {
+			int rc1;
+			if (!(rc1 = extract_name(header, plen, &p2, name, 0, 10)))
+			  return STAT_BOGUS; /* bad packet */
+			
+			if (class1 == qclass && rc1 == 1 && (type1 == T_CNAME || type1 == qtype || qtype == T_ANY ))
+			  targets[j] = NULL;
+		      }
+		  
+		  /* An attacker replay a wildcard answer with a different
+		     answer and overlay a genuine RR. To prove this
+		     hasn't happened, the answer must prove that
+		     the genuine record doesn't exist. Check that here. 
+		     Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
+		     That's not a problem since if the RRsets later fail
+		     we'll return BOGUS then. */
+		  if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
+		      !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL))
+		    return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
+
+		  rc = STAT_SECURE;
+		}
+	    }
+	}
+
+      if (STAT_ISEQUAL(rc, STAT_INSECURE))
+	secure = STAT_INSECURE;
+    }
+
+  /* OK, all the RRsets validate, now see if we have a missing answer or CNAME target. */
+  if (STAT_ISEQUAL(secure, STAT_SECURE))
+    for (j = 0; j <targetidx; j++)
+      if ((p2 = targets[j]))
+	{
+	  if (neganswer)
+	    *neganswer = 1;
+	  
+	  if (!extract_name(header, plen, &p2, name, 1, 10))
+	    return STAT_BOGUS; /* bad packet */
+	  
+	  /* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
+	  
+	  /* For anything other than a DS record, this situation is OK if either
+	     the answer is in an unsigned zone, or there's a NSEC records. */
+	  if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl))
+	    {
+	      /* Empty DS without NSECS */
+	      if (qtype == T_DS)
+		return STAT_BOGUS | DNSSEC_FAIL_NONSEC;
+	      
+	      if (STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
+		{
+		  if (class)
+		    *class = qclass; /* Class for NEED_DS or NEED_KEY */
+		  return rc;
+		} 
+	      
+	      return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */
+	    }
+	}
+  
+  return secure;
+}
+
+
+/* Compute keytag (checksum to quickly index a key). See RFC4034 */
+int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen)
+{
+  if (alg == 1)
+    {
+      /* Algorithm 1 (RSAMD5) has a different (older) keytag calculation algorithm.
+         See RFC4034, Appendix B.1 */
+      return key[keylen-4] * 256 + key[keylen-3];
+    }
+  else
+    {
+      unsigned long ac = flags + 0x300 + alg;
+      int i;
+
+      for (i = 0; i < keylen; ++i)
+        ac += (i & 1) ? key[i] : key[i] << 8;
+
+      ac += (ac >> 16) & 0xffff;
+      return ac & 0xffff;
+    }
+}
+
+size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, 
+			     int type, int edns_pktsz)
+{
+  unsigned char *p;
+  size_t ret;
+
+  header->qdcount = htons(1);
+  header->ancount = htons(0);
+  header->nscount = htons(0);
+  header->arcount = htons(0);
+
+  header->hb3 = HB3_RD; 
+  SET_OPCODE(header, QUERY);
+  /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+     this allows it to select auth servers when one is returning bad data. */
+  header->hb4 = option_bool(OPT_DNSSEC_DEBUG) ? HB4_CD : 0;
+
+  /* ID filled in later */
+
+  p = (unsigned char *)(header+1);
+	
+  p = do_rfc1035_name(p, name, NULL);
+  *p++ = 0;
+  PUTSHORT(type, p);
+  PUTSHORT(class, p);
+
+  ret = add_do_bit(header, p - (unsigned char *)header, end);
+
+  if (find_pseudoheader(header, ret, NULL, &p, NULL, NULL))
+    PUTSHORT(edns_pktsz, p);
+
+  return ret;
+}
+
+int errflags_to_ede(int status)
+{
+  /* We can end up with more than one flag set for some errors,
+     so this encodes a rough priority so the (eg) No sig is reported
+     before no-unexpired-sig. */
+
+  if (status & DNSSEC_FAIL_NYV)
+    return EDE_SIG_NYV;
+  else if (status & DNSSEC_FAIL_EXP)
+    return EDE_SIG_EXP;
+  else if (status & DNSSEC_FAIL_NOKEYSUP)
+    return EDE_USUPDNSKEY;
+  else if (status & DNSSEC_FAIL_NOZONE)
+    return EDE_NO_ZONEKEY;
+  else if (status & DNSSEC_FAIL_NOKEY)
+    return EDE_NO_DNSKEY;
+  else if (status & DNSSEC_FAIL_NODSSUP)
+    return EDE_USUPDS;
+  else if (status & DNSSEC_FAIL_NONSEC)
+    return EDE_NO_NSEC;
+  else if (status & DNSSEC_FAIL_INDET)
+    return EDE_DNSSEC_IND;
+  else if (status & DNSSEC_FAIL_NOSIG)
+    return EDE_NO_RRSIG;
+  else
+    return EDE_UNSET;
+}
+#endif /* HAVE_DNSSEC */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c b/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c
new file mode 100755
index 0000000..f8e4796
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/domain-match.c
@@ -0,0 +1,698 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+static int order(char *qdomain, size_t qlen, struct server *serv);
+static int order_qsort(const void *a, const void *b);
+static int order_servers(struct server *s, struct server *s2);
+
+/* If the server is USE_RESOLV or LITERAL_ADDRES, it lives on the local_domains chain. */
+#define SERV_IS_LOCAL (SERV_USE_RESOLV | SERV_LITERAL_ADDRESS)
+
+void build_server_array(void)
+{
+  struct server *serv;
+  int count = 0;
+  
+  for (serv = daemon->servers; serv; serv = serv->next)
+#ifdef HAVE_LOOP
+    if (!(serv->flags & SERV_LOOP))
+#endif
+      {
+	count++;
+	if (serv->flags & SERV_WILDCARD)
+	  daemon->server_has_wildcard = 1;
+      }
+  
+  for (serv = daemon->local_domains; serv; serv = serv->next)
+    {
+      count++;
+      if (serv->flags & SERV_WILDCARD)
+	daemon->server_has_wildcard = 1;
+    }
+  
+  daemon->serverarraysz = count;
+
+  if (count > daemon->serverarrayhwm)
+    {
+      struct server **new;
+
+      count += 10; /* A few extra without re-allocating. */
+
+      if ((new = whine_malloc(count * sizeof(struct server *))))
+	{
+	  if (daemon->serverarray)
+	    free(daemon->serverarray);
+	  
+	  daemon->serverarray = new;
+	  daemon->serverarrayhwm = count;
+	}
+    }
+
+  count = 0;
+  
+  for (serv = daemon->servers; serv; serv = serv->next)
+#ifdef HAVE_LOOP
+    if (!(serv->flags & SERV_LOOP))
+#endif
+      {
+	daemon->serverarray[count] = serv;
+	serv->serial = count;
+	serv->last_server = -1;
+	count++;
+      }
+  
+  for (serv = daemon->local_domains; serv; serv = serv->next, count++)
+    daemon->serverarray[count] = serv;
+  
+  qsort(daemon->serverarray, daemon->serverarraysz, sizeof(struct server *), order_qsort);
+  
+  /* servers need the location in the array to find all the whole
+     set of equivalent servers from a pointer to a single one. */
+  for (count = 0; count < daemon->serverarraysz; count++)
+    if (!(daemon->serverarray[count]->flags & SERV_IS_LOCAL))
+      daemon->serverarray[count]->arrayposn = count;
+}
+
+/* we're looking for the server whose domain is the longest exact match
+   to the RH end of qdomain, or a local address if the flags match.
+   Add '.' to the LHS of the query string so
+   server=/.example.com/ works.
+
+   A flag of F_SERVER returns an upstream server only.
+   A flag of F_DNSSECOK returns a DNSSEC capable server only and
+   also disables NODOTS servers from consideration.
+   A flag of F_DOMAINSRV returns a domain-specific server only.
+   A flag of F_CONFIG returns anything that generates a local
+   reply of IPv4 or IPV6.
+   return 0 if nothing found, 1 otherwise.
+*/
+int lookup_domain(char *domain, int flags, int *lowout, int *highout)
+{
+  int rc, crop_query, nodots;
+  ssize_t qlen;
+  int try, high, low = 0;
+  int nlow = 0, nhigh = 0;
+  char *cp, *qdomain = domain;
+
+  /* may be no configured servers. */
+  if (daemon->serverarraysz == 0)
+    return 0;
+  
+  /* find query length and presence of '.' */
+  for (cp = qdomain, nodots = 1, qlen = 0; *cp; qlen++, cp++)
+    if (*cp == '.')
+      nodots = 0;
+
+  /* Handle empty name, and searches for DNSSEC queries without
+     diverting to NODOTS servers. */
+  if (qlen == 0 || flags & F_DNSSECOK)
+    nodots = 0;
+
+  /* Search shorter and shorter RHS substrings for a match */
+  while (qlen >= 0)
+    {
+      /* Note that when we chop off a label, all the possible matches
+	 MUST be at a larger index than the nearest failing match with one more
+	 character, since the array is sorted longest to smallest. Hence 
+	 we don't reset low to zero here, we can go further below and crop the 
+	 search string to the size of the largest remaining server
+	 when this match fails. */
+      high = daemon->serverarraysz;
+      crop_query = 1;
+      
+      /* binary search */
+      while (1) 
+	{
+	  try = (low + high)/2;
+
+	  if ((rc = order(qdomain, qlen, daemon->serverarray[try])) == 0)
+	    break;
+	  
+	  if (rc < 0)
+	    {
+	      if (high == try)
+		{
+		  /* qdomain is longer or same length as longest domain, and try == 0 
+		     crop the query to the longest domain. */
+		  crop_query = qlen - daemon->serverarray[try]->domain_len;
+		  break;
+		}
+	      high = try;
+	    }
+	  else
+	    {
+	      if (low == try)
+		{
+		  /* try now points to the last domain that sorts before the query, so 
+		     we know that a substring of the query shorter than it is required to match, so
+		     find the largest domain that's shorter than try. Note that just going to
+		     try+1 is not optimal, consider searching bbb in (aaa,ccc,bb). try will point
+		     to aaa, since ccc sorts after bbb, but the first domain that has a chance to 
+		     match is bb. So find the length of the first domain later than try which is
+		     is shorter than it. 
+		     There's a nasty edge case when qdomain sorts before _any_ of the 
+		     server domains, where try _doesn't point_ to the last domain that sorts
+		     before the query, since no such domain exists. In that case, the loop 
+		     exits via the rc < 0 && high == try path above and this code is
+		     not executed. */
+		  ssize_t len, old = daemon->serverarray[try]->domain_len;
+		  while (++try != daemon->serverarraysz)
+		    {
+		      if (old != (len = daemon->serverarray[try]->domain_len))
+			{
+			  crop_query = qlen - len;
+			  break;
+			}
+		    }
+		  break;
+		}
+	      low = try;
+	    }
+	};
+      
+      if (rc == 0)
+	{
+	  int found = 1;
+
+	  if (daemon->server_has_wildcard)
+	    {
+	      /* if we have example.com and *example.com we need to check against *example.com, 
+		 but the binary search may have found either. Use the fact that example.com is sorted before *example.com
+		 We favour example.com in the case that both match (ie www.example.com) */
+	      while (try != 0 && order(qdomain, qlen, daemon->serverarray[try-1]) == 0)
+		try--;
+	      
+	      if (!(qdomain == domain || *qdomain == 0 || *(qdomain-1) == '.'))
+		{
+		  while (try < daemon->serverarraysz-1 && order(qdomain, qlen, daemon->serverarray[try+1]) == 0)
+		    try++;
+		  
+		  if (!(daemon->serverarray[try]->flags & SERV_WILDCARD))
+		     found = 0;
+		}
+	    }
+	  
+	  if (found)
+	    {
+	      /* We've matched a setting which says to use servers without a domain.
+		 Continue the search with empty query */
+	      if (daemon->serverarray[try]->flags & SERV_USE_RESOLV)
+		crop_query = qlen;
+	      else if (filter_servers(try, flags, &nlow, &nhigh))
+		/* We have a match, but it may only be (say) an IPv6 address, and
+		   if the query wasn't for an AAAA record, it's no good, and we need
+		   to continue generalising */
+		break;
+	    }
+	}
+      
+      /* crop_query must be at least one always. */
+      if (crop_query == 0)
+	crop_query = 1;
+
+      /* strip chars off the query based on the largest possible remaining match,
+	 then continue to the start of the next label unless we have a wildcard
+	 domain somewhere, in which case we have to go one at a time. */
+      qlen -= crop_query;
+      qdomain += crop_query;
+      if (!daemon->server_has_wildcard)
+	while (qlen > 0 &&  (*(qdomain-1) != '.'))
+	  qlen--, qdomain++;
+    }
+
+  /* domain has no dots, and we have at least one server configured to handle such,
+     These servers always sort to the very end of the array. 
+     A configured server eg server=/lan/ will take precdence. */
+  if (nodots &&
+      (daemon->serverarray[daemon->serverarraysz-1]->flags & SERV_FOR_NODOTS) &&
+      (nlow == nhigh || daemon->serverarray[nlow]->domain_len == 0))
+    filter_servers(daemon->serverarraysz-1, flags, &nlow, &nhigh);
+  
+  if (lowout)
+    *lowout = nlow;
+  
+  if (highout)
+    *highout = nhigh;
+
+  if (nlow == nhigh)
+    return 0;
+
+  return 1;
+}
+
+/* Return first server in group of equivalent servers; this is the "master" record. */
+int server_samegroup(struct server *a, struct server *b)
+{
+  return order_servers(a, b) == 0;
+}
+
+int filter_servers(int seed, int flags, int *lowout, int *highout)
+{
+  int nlow = seed, nhigh = seed;
+  int i;
+  
+  /* expand nlow and nhigh to cover all the records with the same domain 
+     nlow is the first, nhigh - 1 is the last. nlow=nhigh means no servers,
+     which can happen below. */
+  while (nlow > 0 && order_servers(daemon->serverarray[nlow-1], daemon->serverarray[nlow]) == 0)
+    nlow--;
+  
+  while (nhigh < daemon->serverarraysz-1 && order_servers(daemon->serverarray[nhigh], daemon->serverarray[nhigh+1]) == 0)
+	nhigh++;
+  
+  nhigh++;
+  
+#define SERV_LOCAL_ADDRESS (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)
+  
+  if (flags & F_CONFIG)
+    {
+      /* We're just lookin for any matches that return an RR. */
+      for (i = nlow; i < nhigh; i++)
+	if (daemon->serverarray[i]->flags & SERV_LOCAL_ADDRESS)
+	  break;
+      
+      /* failed, return failure. */
+      if (i == nhigh)
+	nhigh = nlow;
+    }
+  else
+    {
+      /* Now the servers are on order between low and high, in the order
+	 IPv6 addr, IPv4 addr, return zero for both, send upstream, no-data return.
+	 
+	 See which of those match our query in that priority order and narrow (low, high) */
+
+      for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_6ADDR); i++);
+      
+      if (i != nlow && (flags & F_IPV6))
+	nhigh = i;
+      else
+	{
+	  nlow = i;
+	  
+	  for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_4ADDR); i++);
+	  
+	  if (i != nlow && (flags & F_IPV4))
+	    nhigh = i;
+	  else
+	    {
+	      nlow = i;
+	      
+	      for (i = nlow; i < nhigh && (daemon->serverarray[i]->flags & SERV_ALL_ZEROS); i++);
+	      
+	      if (i != nlow && (flags & (F_IPV4 | F_IPV6)))
+		nhigh = i;
+	      else
+		{
+		  nlow = i;
+		  
+		  /* now look for a server */
+		  for (i = nlow; i < nhigh && !(daemon->serverarray[i]->flags & SERV_LITERAL_ADDRESS); i++);
+
+		  if (i != nlow)
+		    {
+		      /* If we want a server that can do DNSSEC, and this one can't, 
+			 return nothing, similarly if were looking only for a server
+			 for a particular domain. */
+		      if ((flags & F_DNSSECOK) && !(daemon->serverarray[nlow]->flags & SERV_DO_DNSSEC))
+			nlow = nhigh;
+		      else if ((flags & F_DOMAINSRV) && daemon->serverarray[nlow]->domain_len == 0)
+			nlow = nhigh;
+		      else
+			nhigh = i;
+		    }
+		  else
+		    {
+		      /* --local=/domain/, only return if we don't need a server. */
+		      if (flags & (F_DNSSECOK | F_DOMAINSRV | F_SERVER))
+			nhigh = i;
+		    }
+		}
+	    }
+	}
+    }
+  
+  *lowout = nlow;
+  *highout = nhigh;
+  
+  return (nlow != nhigh);
+}
+
+int is_local_answer(time_t now, int first, char *name)
+{
+  int flags = 0;
+  int rc = 0;
+  
+  if ((flags = daemon->serverarray[first]->flags) & SERV_LITERAL_ADDRESS)
+    {
+      if (flags & SERV_4ADDR)
+	rc = F_IPV4;
+      else if (flags & SERV_6ADDR)
+	rc = F_IPV6;
+      else if (flags & SERV_ALL_ZEROS)
+	rc = F_IPV4 | F_IPV6;
+      else
+	{
+	  /* argument first is the first struct server which matches the query type;
+	     now roll back to the server which is just the same domain, to check if that 
+	     provides an answer of a different type. */
+
+	  for (;first > 0 && order_servers(daemon->serverarray[first-1], daemon->serverarray[first]) == 0; first--);
+	  
+	  if ((daemon->serverarray[first]->flags & SERV_LOCAL_ADDRESS) ||
+	      check_for_local_domain(name, now))
+	    rc = F_NOERR;
+	  else
+	    rc = F_NXDOMAIN;
+	}
+    }
+
+  return rc;
+}
+
+size_t make_local_answer(int flags, int gotname, size_t size, struct dns_header *header, char *name, char *limit, int first, int last, int ede)
+{
+  int trunc = 0;
+  unsigned char *p;
+  int start;
+  union all_addr addr;
+  
+  if (flags & (F_NXDOMAIN | F_NOERR))
+    log_query(flags | gotname | F_NEG | F_CONFIG | F_FORWARD, name, NULL, NULL);
+	  
+  setup_reply(header, flags, ede);
+	  
+  if (!(p = skip_questions(header, size)))
+    return 0;
+	  
+  if (flags & gotname & F_IPV4)
+    for (start = first; start != last; start++)
+      {
+	struct serv_addr4 *srv = (struct serv_addr4 *)daemon->serverarray[start];
+
+	if (srv->flags & SERV_ALL_ZEROS)
+	  memset(&addr, 0, sizeof(addr));
+	else
+	  addr.addr4 = srv->addr;
+	
+	header->ancount = htons(ntohs(header->ancount) + 1);
+	add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_A, C_IN, "4", &addr);
+	log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV6, name, (union all_addr *)&addr, NULL);
+      }
+  
+  if (flags & gotname & F_IPV6)
+    for (start = first; start != last; start++)
+      {
+	struct serv_addr6 *srv = (struct serv_addr6 *)daemon->serverarray[start];
+
+	if (srv->flags & SERV_ALL_ZEROS)
+	  memset(&addr, 0, sizeof(addr));
+	else
+	  addr.addr6 = srv->addr;
+	
+	header->ancount = htons(ntohs(header->ancount) + 1);
+	add_resource_record(header, limit, &trunc, sizeof(struct dns_header), &p, daemon->local_ttl, NULL, T_AAAA, C_IN, "6", &addr);
+	log_query((flags | F_CONFIG | F_FORWARD) & ~F_IPV4, name, (union all_addr *)&addr, NULL);
+      }
+
+  if (trunc)
+    header->hb3 |= HB3_TC;
+
+  return p - (unsigned char *)header;
+}
+
+#ifdef HAVE_DNSSEC
+int dnssec_server(struct server *server, char *keyname, int *firstp, int *lastp)
+{
+  int first, last, index;
+
+  /* Find server to send DNSSEC query to. This will normally be the 
+     same as for the original query, but may be another if
+     servers for domains are involved. */		      
+  if (!lookup_domain(keyname, F_DNSSECOK, &first, &last))
+    return -1;
+
+  for (index = first; index != last; index++)
+    if (daemon->serverarray[index] == server)
+      break;
+	      
+  /* No match to server used for original query.
+     Use newly looked up set. */
+  if (index == last)
+    index =  daemon->serverarray[first]->last_server == -1 ?
+      first : daemon->serverarray[first]->last_server;
+
+  if (firstp)
+    *firstp = first;
+
+  if (lastp)
+    *lastp = last;
+   
+  return index;
+}
+#endif
+
+/* order by size, then by dictionary order */
+static int order(char *qdomain, size_t qlen, struct server *serv)
+{
+  size_t dlen = 0;
+    
+  /* servers for dotless names always sort last 
+     searched for name is never dotless. */
+  if (serv->flags & SERV_FOR_NODOTS)
+    return -1;
+
+  dlen = serv->domain_len;
+  
+  if (qlen < dlen)
+    return 1;
+  
+  if (qlen > dlen)
+    return -1;
+
+  return strcmp(qdomain, serv->domain);
+}
+
+static int order_servers(struct server *s1, struct server *s2)
+{
+  int rc;
+
+  /* need full comparison of dotless servers in 
+     order_qsort() and filter_servers() */
+
+  if (s1->flags & SERV_FOR_NODOTS)
+     return (s2->flags & SERV_FOR_NODOTS) ? 0 : 1;
+   
+  if ((rc = order(s1->domain, s1->domain_len, s2)) != 0)
+    return rc;
+
+  /* For identical domains, sort wildcard ones first */
+  if (s1->flags & SERV_WILDCARD)
+    return (s2->flags & SERV_WILDCARD) ? 0 : 1;
+
+  return (s2->flags & SERV_WILDCARD) ? -1 : 0;
+}
+  
+static int order_qsort(const void *a, const void *b)
+{
+  int rc;
+  
+  struct server *s1 = *((struct server **)a);
+  struct server *s2 = *((struct server **)b);
+  
+  rc = order_servers(s1, s2);
+
+  /* Sort all literal NODATA and local IPV4 or IPV6 responses together,
+     in a very specific order. We flip the SERV_LITERAL_ADDRESS bit
+     so the order is IPv6 literal, IPv4 literal, all-zero literal, 
+     upstream server, NXDOMAIN literal. */
+  if (rc == 0)
+    rc = ((s2->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS) -
+      ((s1->flags & (SERV_LITERAL_ADDRESS | SERV_4ADDR | SERV_6ADDR | SERV_ALL_ZEROS)) ^ SERV_LITERAL_ADDRESS);
+
+  /* Finally, order by appearance in /etc/resolv.conf etc, for --strict-order */
+  if (rc == 0)
+    if (!(s1->flags & SERV_LITERAL_ADDRESS))
+      rc = s1->serial - s2->serial;
+
+  return rc;
+}
+
+void mark_servers(int flag)
+{
+  struct server *serv;
+
+  /* mark everything with argument flag */
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (serv->flags & flag)
+      serv->flags |= SERV_MARK;
+    else
+      serv->flags &= ~SERV_MARK;
+
+  for (serv = daemon->local_domains; serv; serv = serv->next)
+    if (serv->flags & flag)
+      serv->flags |= SERV_MARK;
+    else
+      serv->flags &= ~SERV_MARK;
+}
+
+void cleanup_servers(void)
+{
+  struct server *serv, *tmp, **up;
+
+  /* unlink and free anything still marked. */
+  for (serv = daemon->servers, up = &daemon->servers; serv; serv = tmp) 
+    {
+      tmp = serv->next;
+      if (serv->flags & SERV_MARK)
+       {
+         server_gone(serv);
+         *up = serv->next;
+	 free(serv->domain);
+	 free(serv);
+       }
+      else 
+       up = &serv->next;
+    }
+  
+ for (serv = daemon->local_domains, up = &daemon->local_domains; serv; serv = tmp) 
+   {
+     tmp = serv->next;
+      if (serv->flags & SERV_MARK)
+       {
+	 *up = serv->next;
+	 free(serv->domain);
+	 free(serv);
+       }
+      else 
+	up = &serv->next;
+   }
+}
+
+int add_update_server(int flags,
+		      union mysockaddr *addr,
+		      union mysockaddr *source_addr,
+		      const char *interface,
+		      const char *domain,
+		      union all_addr *local_addr)
+{
+  struct server *serv = NULL;
+  char *alloc_domain;
+  
+  if (!domain)
+    domain = "";
+
+  /* .domain == domain, for historical reasons. */
+  if (*domain == '.')
+    while (*domain == '.') domain++;
+  else if (*domain == '*')
+    {
+      domain++;
+      if (*domain != 0)
+	flags |= SERV_WILDCARD;
+    }
+  
+  if (*domain == 0)
+    alloc_domain = whine_malloc(1);
+  else if (!(alloc_domain = canonicalise((char *)domain, NULL)))
+    return 0;
+  
+  /* See if there is a suitable candidate, and unmark
+     only do this for forwarding servers, not 
+     address or local, to avoid delays on large numbers. */
+  if (flags & SERV_IS_LOCAL)
+    for (serv = daemon->servers; serv; serv = serv->next)
+      if ((serv->flags & SERV_MARK) &&
+	  hostname_isequal(alloc_domain, serv->domain))
+	break;
+  
+  if (serv)
+    {
+      free(alloc_domain);
+      alloc_domain = serv->domain;
+    }
+  else
+    {
+      size_t size;
+
+      if (flags & SERV_LITERAL_ADDRESS)
+	{
+	  if (flags & SERV_6ADDR)
+	    size = sizeof(struct serv_addr6);
+	  else if (flags & SERV_4ADDR)
+	    size = sizeof(struct serv_addr4);
+	  else
+	    size = sizeof(struct serv_local);
+	}
+      else
+	size = sizeof(struct server);
+      
+      if (!(serv = whine_malloc(size)))
+	return 0;
+      
+      if (flags & SERV_IS_LOCAL)
+	{
+	  serv->next = daemon->local_domains;
+	  daemon->local_domains = serv;
+	}
+      else
+	{
+	  struct server *s;
+	  /* Add to the end of the chain, for order */
+	  if (!daemon->servers)
+	    daemon->servers = serv;
+	  else
+	    {
+	      for (s = daemon->servers; s->next; s = s->next);
+	      s->next = serv;
+	    }
+	  
+	  serv->next = NULL;
+	}
+    }
+  
+  if (!(flags & SERV_IS_LOCAL))
+    memset(serv, 0, sizeof(struct server));
+  
+  serv->flags = flags;
+  serv->domain = alloc_domain;
+  serv->domain_len = strlen(alloc_domain);
+  
+  if (flags & SERV_4ADDR)
+    ((struct serv_addr4*)serv)->addr = local_addr->addr4;
+
+  if (flags & SERV_6ADDR)
+    ((struct serv_addr6*)serv)->addr = local_addr->addr6;
+  
+  if (!(flags & SERV_IS_LOCAL))
+    {
+#ifdef HAVE_LOOP
+      serv->uid = rand32();
+#endif      
+      
+      if (interface)
+	safe_strncpy(serv->interface, interface, sizeof(serv->interface));
+      if (addr)
+	serv->addr = *addr;
+      if (source_addr)
+	serv->source_addr = *source_addr;
+    }
+
+  return 1;
+}
+
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c b/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c
new file mode 100755
index 0000000..91e0f22
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/domain.c
@@ -0,0 +1,297 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+
+static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c);
+static int match_domain(struct in_addr addr, struct cond_domain *c);
+static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c);
+static int match_domain6(struct in6_addr *addr, struct cond_domain *c);
+
+int is_name_synthetic(int flags, char *name, union all_addr *addr)
+{
+  char *p;
+  struct cond_domain *c = NULL;
+  int prot = (flags & F_IPV6) ? AF_INET6 : AF_INET;
+
+  for (c = daemon->synth_domains; c; c = c->next)
+    {
+      int found = 0;
+      char *tail, *pref;
+      
+      for (tail = name, pref = c->prefix; *tail != 0 && pref && *pref != 0; tail++, pref++)
+	{
+	  unsigned int c1 = (unsigned char) *pref;
+	  unsigned int c2 = (unsigned char) *tail;
+	  
+	  if (c1 >= 'A' && c1 <= 'Z')
+	    c1 += 'a' - 'A';
+	  if (c2 >= 'A' && c2 <= 'Z')
+	    c2 += 'a' - 'A';
+	  
+	  if (c1 != c2)
+	    break;
+	}
+      
+      if (pref && *pref != 0)
+	continue; /* prefix match fail */
+
+      if (c->indexed)
+	{
+	  for (p = tail; *p; p++)
+	    {
+	      char c = *p;
+	      
+	      if (c < '0' || c > '9')
+		break;
+	    }
+	  
+	  if (*p != '.')
+	    continue;
+	  
+	  *p = 0;
+	  
+	  if (hostname_isequal(c->domain, p+1))
+	    {
+	      if (prot == AF_INET)
+		{
+		  unsigned int index = atoi(tail);
+
+		   if (!c->is6 &&
+		      index <= ntohl(c->end.s_addr) - ntohl(c->start.s_addr))
+		    {
+		      addr->addr4.s_addr = htonl(ntohl(c->start.s_addr) + index);
+		      found = 1;
+		    }
+		} 
+	      else
+		{
+		  u64 index = atoll(tail);
+		  
+		  if (c->is6 &&
+		      index <= addr6part(&c->end6) - addr6part(&c->start6))
+		    {
+		      u64 start = addr6part(&c->start6);
+		      addr->addr6 = c->start6;
+		      setaddr6part(&addr->addr6, start + index);
+		      found = 1;
+		    }
+		}
+	    }
+	}
+      else
+	{
+	  /* NB, must not alter name if we return zero */
+	  for (p = tail; *p; p++)
+	    {
+	      char c = *p;
+	      
+	      if ((c >='0' && c <= '9') || c == '-')
+		continue;
+	      
+	      if (prot == AF_INET6 && ((c >='A' && c <= 'F') || (c >='a' && c <= 'f'))) 
+		continue;
+	      
+	      break;
+	    }
+	  
+	  if (*p != '.')
+	    continue;
+	  
+	  *p = 0;	
+	  
+	  if (prot == AF_INET6 && strstr(tail, "--ffff-") == tail)
+	    {
+	      /* special hack for v4-mapped. */
+	      memcpy(tail, "::ffff:", 7);
+	      for (p = tail + 7; *p; p++)
+		if (*p == '-')
+		  *p = '.';
+	    }
+	  else
+	    {
+	      /* swap . or : for - */
+	      for (p = tail; *p; p++)
+		if (*p == '-')
+		  {
+		    if (prot == AF_INET)
+		      *p = '.';
+		    else
+		      *p = ':';
+		  }
+	    }
+	  
+	  if (hostname_isequal(c->domain, p+1) && inet_pton(prot, tail, addr))
+	    found = (prot == AF_INET) ? match_domain(addr->addr4, c) : match_domain6(&addr->addr6, c);
+	}
+      
+      /* restore name */
+      for (p = tail; *p; p++)
+	if (*p == '.' || *p == ':')
+	  *p = '-';
+      
+      *p = '.';
+      
+      
+      if (found)
+	return 1;
+    }
+  
+  return 0;
+}
+
+
+int is_rev_synth(int flag, union all_addr *addr, char *name)
+{
+   struct cond_domain *c;
+
+   if (flag & F_IPV4 && (c = search_domain(addr->addr4, daemon->synth_domains))) 
+     {
+       char *p;
+       
+       *name = 0;
+       if (c->indexed)
+	 {
+	   unsigned int index = ntohl(addr->addr4.s_addr) - ntohl(c->start.s_addr);
+	   snprintf(name, MAXDNAME, "%s%u", c->prefix ? c->prefix : "", index);
+	 }
+       else
+	 {
+	   if (c->prefix)
+	     strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
+       
+       	   inet_ntop(AF_INET, &addr->addr4, name + strlen(name), ADDRSTRLEN);
+	   for (p = name; *p; p++)
+	     if (*p == '.')
+	       *p = '-';
+	 }
+       
+       strncat(name, ".", MAXDNAME);
+       strncat(name, c->domain, MAXDNAME);
+
+       return 1;
+     }
+
+   if ((flag & F_IPV6) && (c = search_domain6(&addr->addr6, daemon->synth_domains))) 
+     {
+       char *p;
+       
+       *name = 0;
+       if (c->indexed)
+	 {
+	   u64 index = addr6part(&addr->addr6) - addr6part(&c->start6);
+	   snprintf(name, MAXDNAME, "%s%llu", c->prefix ? c->prefix : "", index);
+	 }
+       else
+	 {
+	   if (c->prefix)
+	     strncpy(name, c->prefix, MAXDNAME - ADDRSTRLEN);
+       
+	   inet_ntop(AF_INET6, &addr->addr6, name + strlen(name), ADDRSTRLEN);
+
+	   /* IPv6 presentation address can start with ":", but valid domain names
+	      cannot start with "-" so prepend a zero in that case. */
+	   if (!c->prefix && *name == ':')
+	     {
+	       *name = '0';
+	       inet_ntop(AF_INET6, &addr->addr6, name+1, ADDRSTRLEN);
+	     }
+	   
+	   /* V4-mapped have periods.... */
+	   for (p = name; *p; p++)
+	     if (*p == ':' || *p == '.')
+	       *p = '-';
+	   
+	 }
+
+       strncat(name, ".", MAXDNAME);
+       strncat(name, c->domain, MAXDNAME);
+       
+       return 1;
+     }
+   
+   return 0;
+}
+
+
+static int match_domain(struct in_addr addr, struct cond_domain *c)
+{
+  if (!c->is6 &&
+      ntohl(addr.s_addr) >= ntohl(c->start.s_addr) &&
+      ntohl(addr.s_addr) <= ntohl(c->end.s_addr))
+    return 1;
+
+  return 0;
+}
+
+static struct cond_domain *search_domain(struct in_addr addr, struct cond_domain *c)
+{
+  for (; c; c = c->next)
+    if (match_domain(addr, c))
+      return c;
+  
+  return NULL;
+}
+
+char *get_domain(struct in_addr addr)
+{
+  struct cond_domain *c;
+
+  if ((c = search_domain(addr, daemon->cond_domain)))
+    return c->domain;
+
+  return daemon->domain_suffix;
+} 
+
+static int match_domain6(struct in6_addr *addr, struct cond_domain *c)
+{
+  u64 addrpart = addr6part(addr);
+  
+  if (c->is6)
+    {
+      if (c->prefixlen >= 64)
+	{
+	  if (is_same_net6(addr, &c->start6, 64) &&
+	      addrpart >= addr6part(&c->start6) &&
+	      addrpart <= addr6part(&c->end6))
+	    return 1;
+	}
+      else if (is_same_net6(addr, &c->start6, c->prefixlen))
+	return 1;
+    }
+
+  return 0;
+}
+
+static struct cond_domain *search_domain6(struct in6_addr *addr, struct cond_domain *c)
+{
+  for (; c; c = c->next)
+    if (match_domain6(addr, c))
+      return c;
+  
+  return NULL;
+}
+
+char *get_domain6(struct in6_addr *addr)
+{
+  struct cond_domain *c;
+
+  if (addr && (c = search_domain6(addr, daemon->cond_domain)))
+    return c->domain;
+
+  return daemon->domain_suffix;
+} 
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c b/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c
new file mode 100755
index 0000000..c131953
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/dump.c
@@ -0,0 +1,211 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DUMPFILE
+
+static u32 packet_count;
+
+/* https://wiki.wireshark.org/Development/LibpcapFileFormat */
+struct pcap_hdr_s {
+        u32 magic_number;   /* magic number */
+        u16 version_major;  /* major version number */
+        u16 version_minor;  /* minor version number */
+        u32 thiszone;       /* GMT to local correction */
+        u32 sigfigs;        /* accuracy of timestamps */
+        u32 snaplen;        /* max length of captured packets, in octets */
+        u32 network;        /* data link type */
+};
+
+struct pcaprec_hdr_s {
+        u32 ts_sec;         /* timestamp seconds */
+        u32 ts_usec;        /* timestamp microseconds */
+        u32 incl_len;       /* number of octets of packet saved in file */
+        u32 orig_len;       /* actual length of packet */
+};
+
+
+void dump_init(void)
+{
+  struct stat buf;
+  struct pcap_hdr_s header;
+  struct pcaprec_hdr_s pcap_header;
+
+  packet_count = 0;
+  
+  if (stat(daemon->dump_file, &buf) == -1)
+    {
+      /* doesn't exist, create and add header */
+      header.magic_number = 0xa1b2c3d4;
+      header.version_major = 2;
+      header.version_minor = 4;
+      header.thiszone = 0;
+      header.sigfigs = 0;
+      header.snaplen = daemon->edns_pktsz + 200; /* slop for IP/UDP headers */
+      header.network = 101; /* DLT_RAW http://www.tcpdump.org/linktypes.html */
+
+      if (errno != ENOENT ||
+	  (daemon->dumpfd = creat(daemon->dump_file, S_IRUSR | S_IWUSR)) == -1 ||
+	  !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 0))
+	die(_("cannot create %s: %s"), daemon->dump_file, EC_FILE);
+    }
+  else if ((daemon->dumpfd = open(daemon->dump_file, O_APPEND | O_RDWR)) == -1 ||
+	   !read_write(daemon->dumpfd, (void *)&header, sizeof(header), 1))
+    die(_("cannot access %s: %s"), daemon->dump_file, EC_FILE);
+  else if (header.magic_number != 0xa1b2c3d4)
+    die(_("bad header in %s"), daemon->dump_file, EC_FILE);
+  else
+    {
+      /* count existing records */
+      while (read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 1))
+	{
+	  lseek(daemon->dumpfd, pcap_header.incl_len, SEEK_CUR);
+	  packet_count++;
+	}
+    }
+}
+
+void dump_packet(int mask, void *packet, size_t len, union mysockaddr *src, union mysockaddr *dst)
+{
+  struct ip ip;
+  struct ip6_hdr ip6;
+  int family;
+  struct udphdr {
+    u16 uh_sport;               /* source port */
+    u16 uh_dport;               /* destination port */
+    u16 uh_ulen;                /* udp length */
+    u16 uh_sum;                 /* udp checksum */
+  } udp;
+  struct pcaprec_hdr_s pcap_header;
+  struct timeval time;
+  u32 i, sum;
+  void *iphdr;
+  size_t ipsz;
+  int rc;
+  
+  if (daemon->dumpfd == -1 || !(mask & daemon->dump_mask))
+    return;
+  
+  /* So wireshark can Id the packet. */
+  udp.uh_sport = udp.uh_dport = htons(NAMESERVER_PORT);
+
+  if (src)
+    family = src->sa.sa_family;
+  else
+    family = dst->sa.sa_family;
+
+  if (family == AF_INET6)
+    {
+      iphdr = &ip6;
+      ipsz = sizeof(ip6);
+      memset(&ip6, 0, sizeof(ip6));
+      
+      ip6.ip6_vfc = 6 << 4;
+      ip6.ip6_plen = htons(sizeof(struct udphdr) + len);
+      ip6.ip6_nxt = IPPROTO_UDP;
+      ip6.ip6_hops = 64;
+
+      if (src)
+	{
+	  memcpy(&ip6.ip6_src, &src->in6.sin6_addr, IN6ADDRSZ);
+	  udp.uh_sport = src->in6.sin6_port;
+	}
+      
+      if (dst)
+	{
+	  memcpy(&ip6.ip6_dst, &dst->in6.sin6_addr, IN6ADDRSZ);
+	  udp.uh_dport = dst->in6.sin6_port;
+	}
+            
+      /* start UDP checksum */
+      for (sum = 0, i = 0; i < IN6ADDRSZ; i+=2)
+	{
+	  sum += ip6.ip6_src.s6_addr[i] + (ip6.ip6_src.s6_addr[i+1] << 8) ;
+	  sum += ip6.ip6_dst.s6_addr[i] + (ip6.ip6_dst.s6_addr[i+1] << 8) ;
+	  
+	}
+    }
+  else
+    {
+      iphdr = &ip;
+      ipsz = sizeof(ip);
+      memset(&ip, 0, sizeof(ip));
+      
+      ip.ip_v = IPVERSION;
+      ip.ip_hl = sizeof(struct ip) / 4;
+      ip.ip_len = htons(sizeof(struct ip) + sizeof(struct udphdr) + len); 
+      ip.ip_ttl = IPDEFTTL;
+      ip.ip_p = IPPROTO_UDP;
+      
+      if (src)
+	{
+	  ip.ip_src = src->in.sin_addr;
+	  udp.uh_sport = src->in.sin_port;
+	}
+
+      if (dst)
+	{
+	  ip.ip_dst = dst->in.sin_addr;
+	  udp.uh_dport = dst->in.sin_port;
+	}
+      
+      ip.ip_sum = 0;
+      for (sum = 0, i = 0; i < sizeof(struct ip) / 2; i++)
+	sum += ((u16 *)&ip)[i];
+      while (sum >> 16)
+	sum = (sum & 0xffff) + (sum >> 16);  
+      ip.ip_sum = (sum == 0xffff) ? sum : ~sum;
+      
+      /* start UDP checksum */
+      sum = ip.ip_src.s_addr & 0xffff;
+      sum += (ip.ip_src.s_addr >> 16) & 0xffff;
+      sum += ip.ip_dst.s_addr & 0xffff;
+      sum += (ip.ip_dst.s_addr >> 16) & 0xffff;
+    }
+  
+  if (len & 1)
+    ((unsigned char *)packet)[len] = 0; /* for checksum, in case length is odd. */
+
+  udp.uh_sum = 0;
+  udp.uh_ulen = htons(sizeof(struct udphdr) + len);
+  sum += htons(IPPROTO_UDP);
+  sum += htons(sizeof(struct udphdr) + len);
+  for (i = 0; i < sizeof(struct udphdr)/2; i++)
+    sum += ((u16 *)&udp)[i];
+  for (i = 0; i < (len + 1) / 2; i++)
+    sum += ((u16 *)packet)[i];
+  while (sum >> 16)
+    sum = (sum & 0xffff) + (sum >> 16);
+  udp.uh_sum = (sum == 0xffff) ? sum : ~sum;
+
+  rc = gettimeofday(&time, NULL);
+  pcap_header.ts_sec = time.tv_sec;
+  pcap_header.ts_usec = time.tv_usec;
+  pcap_header.incl_len = pcap_header.orig_len = ipsz + sizeof(udp) + len;
+  
+  if (rc == -1 ||
+      !read_write(daemon->dumpfd, (void *)&pcap_header, sizeof(pcap_header), 0) ||
+      !read_write(daemon->dumpfd, iphdr, ipsz, 0) ||
+      !read_write(daemon->dumpfd, (void *)&udp, sizeof(udp), 0) ||
+      !read_write(daemon->dumpfd, (void *)packet, len, 0))
+    my_syslog(LOG_ERR, _("failed to write packet dump"));
+  else
+    my_syslog(LOG_INFO, _("dumping UDP packet %u mask 0x%04x"), ++packet_count, mask);
+
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c b/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c
new file mode 100755
index 0000000..7bd26b8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/edns0.c
@@ -0,0 +1,519 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t  *len, unsigned char **p, int *is_sign, int *is_last)
+{
+  /* See if packet has an RFC2671 pseudoheader, and if so return a pointer to it. 
+     also return length of pseudoheader in *len and pointer to the UDP size in *p
+     Finally, check to see if a packet is signed. If it is we cannot change a single bit before
+     forwarding. We look for TSIG in the addition section, and TKEY queries (for GSS-TSIG) */
+  
+  int i, arcount = ntohs(header->arcount);
+  unsigned char *ansp = (unsigned char *)(header+1);
+  unsigned short rdlen, type, class;
+  unsigned char *ret = NULL;
+
+  if (is_sign)
+    {
+      *is_sign = 0;
+
+      if (OPCODE(header) == QUERY)
+	{
+	  for (i = ntohs(header->qdcount); i != 0; i--)
+	    {
+	      if (!(ansp = skip_name(ansp, header, plen, 4)))
+		return NULL;
+	      
+	      GETSHORT(type, ansp); 
+	      GETSHORT(class, ansp);
+	      
+	      if (class == C_IN && type == T_TKEY)
+		*is_sign = 1;
+	    }
+	}
+    }
+  else
+    {
+      if (!(ansp = skip_questions(header, plen)))
+	return NULL;
+    }
+    
+  if (arcount == 0)
+    return NULL;
+  
+  if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount), header, plen)))
+    return NULL; 
+  
+  for (i = 0; i < arcount; i++)
+    {
+      unsigned char *save, *start = ansp;
+      if (!(ansp = skip_name(ansp, header, plen, 10)))
+	return NULL; 
+
+      GETSHORT(type, ansp);
+      save = ansp;
+      GETSHORT(class, ansp);
+      ansp += 4; /* TTL */
+      GETSHORT(rdlen, ansp);
+      if (!ADD_RDLEN(header, ansp, plen, rdlen))
+	return NULL;
+      if (type == T_OPT)
+	{
+	  if (len)
+	    *len = ansp - start;
+
+	  if (p)
+	    *p = save;
+	  
+	  if (is_last)
+	    *is_last = (i == arcount-1);
+
+	  ret = start;
+	}
+      else if (is_sign && 
+	       i == arcount - 1 && 
+	       class == C_ANY && 
+	       type == T_TSIG)
+	*is_sign = 1;
+    }
+  
+  return ret;
+}
+ 
+
+/* replace == 2 ->delete existing option only. */
+size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *limit, 
+			unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace)
+{ 
+  unsigned char *lenp, *datap, *p, *udp_len, *buff = NULL;
+  int rdlen = 0, is_sign, is_last;
+  unsigned short flags = set_do ? 0x8000 : 0, rcode = 0;
+
+  p = find_pseudoheader(header, plen, NULL, &udp_len, &is_sign, &is_last);
+  
+  if (is_sign)
+    return plen;
+
+  if (p)
+    {
+      /* Existing header */
+      int i;
+      unsigned short code, len;
+
+      p = udp_len;
+      GETSHORT(udp_sz, p);
+      GETSHORT(rcode, p);
+      GETSHORT(flags, p);
+
+      if (set_do)
+	{
+	  p -= 2;
+	  flags |= 0x8000;
+	  PUTSHORT(flags, p);
+	}
+
+      lenp = p;
+      GETSHORT(rdlen, p);
+      if (!CHECK_LEN(header, p, plen, rdlen))
+	return plen; /* bad packet */
+      datap = p;
+
+       /* no option to add */
+      if (optno == 0)
+	return plen;
+      	  
+      /* check if option already there */
+      for (i = 0; i + 4 < rdlen;)
+	{
+	  GETSHORT(code, p);
+	  GETSHORT(len, p);
+	  
+	  /* malformed option, delete the whole OPT RR and start again. */
+	  if (i + 4 + len > rdlen)
+	    {
+	      rdlen = 0;
+	      is_last = 0;
+	      break;
+	    }
+	  
+	  if (code == optno)
+	    {
+	      if (replace == 0)
+		return plen;
+
+	      /* delete option if we're to replace it. */
+	      p -= 4;
+	      rdlen -= len + 4;
+	      memmove(p, p+len+4, rdlen - i);
+	      PUTSHORT(rdlen, lenp);
+	      lenp -= 2;
+	    }
+	  else
+	    {
+	      p += len;
+	      i += len + 4;
+	    }
+	}
+
+      /* If we're going to extend the RR, it has to be the last RR in the packet */
+      if (!is_last)
+	{
+	  /* First, take a copy of the options. */
+	  if (rdlen != 0 && (buff = whine_malloc(rdlen)))
+	    memcpy(buff, datap, rdlen);	      
+	  
+	  /* now, delete OPT RR */
+	  plen = rrfilter(header, plen, 0);
+	  
+	  /* Now, force addition of a new one */
+	  p = NULL;	  
+	}
+    }
+  
+  if (!p)
+    {
+      /* We are (re)adding the pseudoheader */
+      if (!(p = skip_questions(header, plen)) ||
+	  !(p = skip_section(p, 
+			     ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount), 
+			     header, plen)))
+      {
+	free(buff);
+	return plen;
+      }
+      if (p + 11 > limit)
+      {
+        free(buff);
+        return plen; /* Too big */
+      }
+      *p++ = 0; /* empty name */
+      PUTSHORT(T_OPT, p);
+      PUTSHORT(udp_sz, p); /* max packet length, 512 if not given in EDNS0 header */
+      PUTSHORT(rcode, p);    /* extended RCODE and version */
+      PUTSHORT(flags, p); /* DO flag */
+      lenp = p;
+      PUTSHORT(rdlen, p);    /* RDLEN */
+      datap = p;
+      /* Copy back any options */
+      if (buff)
+	{
+          if (p + rdlen > limit)
+          {
+            free(buff);
+            return plen; /* Too big */
+          }
+	  memcpy(p, buff, rdlen);
+	  free(buff);
+	  p += rdlen;
+	}
+      
+      /* Only bump arcount if RR is going to fit */ 
+      if (((ssize_t)optlen) <= (limit - (p + 4)))
+	header->arcount = htons(ntohs(header->arcount) + 1);
+    }
+  
+  if (((ssize_t)optlen) > (limit - (p + 4)))
+    return plen; /* Too big */
+  
+  /* Add new option */
+  if (optno != 0 && replace != 2)
+    {
+      if (p + 4 > limit)
+       return plen; /* Too big */
+      PUTSHORT(optno, p);
+      PUTSHORT(optlen, p);
+      if (p + optlen > limit)
+       return plen; /* Too big */
+      memcpy(p, opt, optlen);
+      p += optlen;  
+      PUTSHORT(p - datap, lenp);
+    }
+  return p - (unsigned char *)header;
+}
+
+size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit)
+{
+  return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, 0, NULL, 0, 1, 0);
+}
+
+static unsigned char char64(unsigned char c)
+{
+  return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[c & 0x3f];
+}
+
+static void encoder(unsigned char *in, char *out)
+{
+  out[0] = char64(in[0]>>2);
+  out[1] = char64((in[0]<<4) | (in[1]>>4));
+  out[2] = char64((in[1]<<2) | (in[2]>>6));
+  out[3] = char64(in[2]);
+}
+
+static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
+			     union mysockaddr *l3, time_t now, int *cacheablep)
+{
+  int maclen, replace = 2; /* can't get mac address, just delete any incoming. */
+  unsigned char mac[DHCP_CHADDR_MAX];
+  char encode[18]; /* handle 6 byte MACs */
+
+  if ((maclen = find_mac(l3, mac, 1, now)) == 6)
+    {
+      replace = 1;
+      *cacheablep = 0;
+
+      if (option_bool(OPT_MAC_HEX))
+	print_mac(encode, mac, maclen);
+      else
+	{
+	  encoder(mac, encode);
+	  encoder(mac+3, encode+4);
+	  encode[8] = 0;
+	}
+    }
+
+  return add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMDEVICEID, (unsigned char *)encode, strlen(encode), 0, replace); 
+}
+
+
+static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
+		      union mysockaddr *l3, time_t now, int *cacheablep)
+{
+  int maclen;
+  unsigned char mac[DHCP_CHADDR_MAX];
+
+  if ((maclen = find_mac(l3, mac, 1, now)) != 0)
+    {
+      *cacheablep = 0;
+      plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); 
+    }
+  
+  return plen; 
+}
+
+struct subnet_opt {
+  u16 family;
+  u8 source_netmask, scope_netmask; 
+  u8 addr[IN6ADDRSZ];
+};
+
+static void *get_addrp(union mysockaddr *addr, const short family) 
+{
+  if (family == AF_INET6)
+    return &addr->in6.sin6_addr;
+
+  return &addr->in.sin_addr;
+}
+
+static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
+{
+  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+  
+  int len;
+  void *addrp = NULL;
+  int sa_family = source->sa.sa_family;
+  int cacheable = 0;
+  
+  opt->source_netmask = 0;
+  opt->scope_netmask = 0;
+    
+  if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
+    {
+      opt->source_netmask = daemon->add_subnet6->mask;
+      if (daemon->add_subnet6->addr_used) 
+	{
+	  sa_family = daemon->add_subnet6->addr.sa.sa_family;
+	  addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
+	  cacheable = 1;
+	} 
+      else 
+	addrp = &source->in6.sin6_addr;
+    }
+
+  if (source->sa.sa_family == AF_INET && daemon->add_subnet4)
+    {
+      opt->source_netmask = daemon->add_subnet4->mask;
+      if (daemon->add_subnet4->addr_used)
+	{
+	  sa_family = daemon->add_subnet4->addr.sa.sa_family;
+	  addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
+	  cacheable = 1; /* Address is constant */
+	} 
+	else 
+	  addrp = &source->in.sin_addr;
+    }
+  
+  opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
+  
+  if (addrp && opt->source_netmask != 0)
+    {
+      len = ((opt->source_netmask - 1) >> 3) + 1;
+      memcpy(opt->addr, addrp, len);
+      if (opt->source_netmask & 7)
+	opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
+    }
+  else
+    {
+      cacheable = 1; /* No address ever supplied. */
+      len = 0;
+    }
+
+  if (cacheablep)
+    *cacheablep = cacheable;
+  
+  return len + 4;
+}
+ 
+static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+{
+  /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+  
+  int len;
+  struct subnet_opt opt;
+  
+  len = calc_subnet_opt(&opt, source, cacheable);
+  return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0);
+}
+
+int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer)
+{
+  /* Section 9.2, Check that subnet option in reply matches. */
+  
+  int len, calc_len;
+  struct subnet_opt opt;
+  unsigned char *p;
+  int code, i, rdlen;
+  
+  calc_len = calc_subnet_opt(&opt, peer, NULL);
+   
+  if (!(p = skip_name(pseudoheader, header, plen, 10)))
+    return 1;
+  
+  p += 8; /* skip UDP length and RCODE */
+  
+  GETSHORT(rdlen, p);
+  if (!CHECK_LEN(header, p, plen, rdlen))
+    return 1; /* bad packet */
+  
+  /* check if option there */
+   for (i = 0; i + 4 < rdlen; i += len + 4)
+     {
+       GETSHORT(code, p);
+       GETSHORT(len, p);
+       if (code == EDNS0_OPTION_CLIENT_SUBNET)
+	 {
+	   /* make sure this doesn't mismatch. */
+	   opt.scope_netmask = p[3];
+	   if (len != calc_len || memcmp(p, &opt, len) != 0)
+	     return 0;
+	 }
+       p += len;
+     }
+   
+   return 1;
+}
+
+/* See https://docs.umbrella.com/umbrella-api/docs/identifying-dns-traffic for
+ * detailed information on packet formating.
+ */
+#define UMBRELLA_VERSION    1
+#define UMBRELLA_TYPESZ     2
+
+#define UMBRELLA_ASSET      0x0004
+#define UMBRELLA_ASSETSZ    sizeof(daemon->umbrella_asset)
+#define UMBRELLA_ORG        0x0008
+#define UMBRELLA_ORGSZ      sizeof(daemon->umbrella_org)
+#define UMBRELLA_IPV4       0x0010
+#define UMBRELLA_IPV6       0x0020
+#define UMBRELLA_DEVICE     0x0040
+#define UMBRELLA_DEVICESZ   sizeof(daemon->umbrella_device)
+
+struct umbrella_opt {
+  u8 magic[4];
+  u8 version;
+  u8 flags;
+  /* We have 4 possible fields since we'll never send both IPv4 and
+   * IPv6, so using the larger of the two to calculate max buffer size.
+   * Each field also has a type header.  So the following accounts for
+   * the type headers and each field size to get a max buffer size.
+   */
+  u8 fields[4 * UMBRELLA_TYPESZ + UMBRELLA_ORGSZ + IN6ADDRSZ + UMBRELLA_DEVICESZ + UMBRELLA_ASSETSZ];
+};
+
+static size_t add_umbrella_opt(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+{
+  *cacheable = 0;
+
+  struct umbrella_opt opt = {{"ODNS"}, UMBRELLA_VERSION, 0, {}};
+  u8 *u = &opt.fields[0];
+
+  if (daemon->umbrella_org) {
+    PUTSHORT(UMBRELLA_ORG, u);
+    PUTLONG(daemon->umbrella_org, u);
+  }
+
+  int family = source->sa.sa_family;
+  PUTSHORT(family == AF_INET ? UMBRELLA_IPV4 : UMBRELLA_IPV6, u);
+  int size = family == AF_INET ? INADDRSZ : IN6ADDRSZ;
+  memcpy(u, get_addrp(source, family), size);
+  u += size;
+
+  if (option_bool(OPT_UMBRELLA_DEVID)) {
+    PUTSHORT(UMBRELLA_DEVICE, u);
+    memcpy(u, (char *)&daemon->umbrella_device, UMBRELLA_DEVICESZ);
+    u += UMBRELLA_DEVICESZ;
+  }
+
+  if (daemon->umbrella_asset) {
+    PUTSHORT(UMBRELLA_ASSET, u);
+    PUTLONG(daemon->umbrella_asset, u);
+  }
+
+  int len = u - &opt.magic[0];
+  return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_UMBRELLA, (unsigned char *)&opt, len, 0, 1);
+}
+
+/* Set *check_subnet if we add a client subnet option, which needs to checked 
+   in the reply. Set *cacheable to zero if we add an option which the answer
+   may depend on. */
+size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
+			union mysockaddr *source, time_t now, int *check_subnet, int *cacheable)    
+{
+  *check_subnet = 0;
+  *cacheable = 1;
+  
+  if (option_bool(OPT_ADD_MAC))
+    plen  = add_mac(header, plen, limit, source, now, cacheable);
+  
+  if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX))
+    plen = add_dns_client(header, plen, limit, source, now, cacheable);
+  
+  if (daemon->dns_client_id)
+    plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, 
+			    (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
+
+  if (option_bool(OPT_UMBRELLA))
+    plen = add_umbrella_opt(header, plen, limit, source, cacheable);
+  
+  if (option_bool(OPT_CLIENT_SUBNET))
+    {
+      plen = add_source_addr(header, plen, limit, source, cacheable); 
+      *check_subnet = 1;
+    }
+	  
+  return plen;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c b/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c
new file mode 100755
index 0000000..f962f6f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/forward.c
@@ -0,0 +1,2654 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+static struct frec *get_new_frec(time_t now, struct server *serv, int force);
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp);
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask);
+
+static unsigned short get_id(void);
+static void free_frec(struct frec *f);
+static void query_full(time_t now, char *domain);
+
+static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status);
+
+/* Send a UDP packet with its source address set as "source" 
+   unless nowild is true, when we just send it with the kernel default */
+int send_from(int fd, int nowild, char *packet, size_t len, 
+	      union mysockaddr *to, union all_addr *source,
+	      unsigned int iface)
+{
+  struct msghdr msg;
+  struct iovec iov[1]; 
+  union {
+    struct cmsghdr align; /* this ensures alignment */
+#if defined(HAVE_LINUX_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#elif defined(IP_SENDSRCADDR)
+    char control[CMSG_SPACE(sizeof(struct in_addr))];
+#endif
+    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+  } control_u;
+  
+  iov[0].iov_base = packet;
+  iov[0].iov_len = len;
+
+  msg.msg_control = NULL;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+  msg.msg_name = to;
+  msg.msg_namelen = sa_len(to);
+  msg.msg_iov = iov;
+  msg.msg_iovlen = 1;
+  
+  if (!nowild)
+    {
+      struct cmsghdr *cmptr;
+      msg.msg_control = &control_u;
+      msg.msg_controllen = sizeof(control_u);
+      cmptr = CMSG_FIRSTHDR(&msg);
+
+      if (to->sa.sa_family == AF_INET)
+	{
+#if defined(HAVE_LINUX_NETWORK)
+	  struct in_pktinfo p;
+	  p.ipi_ifindex = 0;
+	  p.ipi_spec_dst = source->addr4;
+	  msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
+	  memcpy(CMSG_DATA(cmptr), &p, sizeof(p));
+	  cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+	  cmptr->cmsg_level = IPPROTO_IP;
+	  cmptr->cmsg_type = IP_PKTINFO;
+#elif defined(IP_SENDSRCADDR)
+	  msg.msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
+	  memcpy(CMSG_DATA(cmptr), &(source->addr4), sizeof(source->addr4));
+	  cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
+	  cmptr->cmsg_level = IPPROTO_IP;
+	  cmptr->cmsg_type = IP_SENDSRCADDR;
+#endif
+	}
+      else
+	{
+	  struct in6_pktinfo p;
+	  p.ipi6_ifindex = iface; /* Need iface for IPv6 to handle link-local addrs */
+	  p.ipi6_addr = source->addr6;
+	  msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
+	  memcpy(CMSG_DATA(cmptr), &p, sizeof(p));
+	  cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+	  cmptr->cmsg_type = daemon->v6pktinfo;
+	  cmptr->cmsg_level = IPPROTO_IPV6;
+	}
+    }
+  
+  while (retry_send(sendmsg(fd, &msg, 0)));
+
+  if (errno != 0)
+    {
+#ifdef HAVE_LINUX_NETWORK
+      /* If interface is still in DAD, EINVAL results - ignore that. */
+      if (errno != EINVAL)
+	my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno));
+#endif
+      return 0;
+    }
+  
+  return 1;
+}
+          
+#ifdef HAVE_CONNTRACK
+static void set_outgoing_mark(struct frec *forward, int fd)
+{
+  /* Copy connection mark of incoming query to outgoing connection. */
+  unsigned int mark;
+  if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
+    setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+}
+#endif
+
+static void log_query_mysockaddr(unsigned int flags, char *name, union mysockaddr *addr, char *arg)
+{
+  if (addr->sa.sa_family == AF_INET)
+    log_query(flags | F_IPV4, name, (union all_addr *)&addr->in.sin_addr, arg);
+  else
+    log_query(flags | F_IPV6, name, (union all_addr *)&addr->in6.sin6_addr, arg);
+}
+
+static void server_send(struct server *server, int fd,
+			const void *header, size_t plen, int flags)
+{
+  while (retry_send(sendto(fd, header, plen, flags,
+			   &server->addr.sa,
+			   sa_len(&server->addr))));
+}
+
+#ifdef HAVE_DNSSEC
+static void server_send_log(struct server *server, int fd,
+			const void *header, size_t plen, int dumpflags,
+			unsigned int logflags, char *name, char *arg)
+{
+#ifdef HAVE_DUMPFILE
+	  dump_packet(dumpflags, (void *)header, (size_t)plen, NULL, &server->addr);
+#endif
+	  log_query_mysockaddr(logflags, name, &server->addr, arg);
+	  server_send(server, fd, header, plen, 0);
+}
+#endif
+
+static int domain_no_rebind(char *domain)
+{
+  struct server *serv;
+  int dlen = (int)strlen(domain);
+  
+  for (serv = daemon->no_rebind; serv; serv = serv->next)
+    if (dlen >= serv->domain_len && strcmp(serv->domain, &domain[dlen - serv->domain_len]) == 0)
+      return 1;
+
+  return 0;
+}
+
+static int forward_query(int udpfd, union mysockaddr *udpaddr,
+			 union all_addr *dst_addr, unsigned int dst_iface,
+			 struct dns_header *header, size_t plen,  char *limit, time_t now, 
+			 struct frec *forward, int ad_reqd, int do_bit)
+{
+  unsigned int flags = 0;
+  unsigned int fwd_flags = 0;
+  int is_dnssec = forward && (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY));
+  struct server *master;
+  unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+  void *hash = hash_questions(header, plen, daemon->namebuff);
+  unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
+  int old_src = 0;
+  int first, last, start = 0;
+  int subnet, cacheable, forwarded = 0;
+  size_t edns0_len;
+  unsigned char *pheader;
+  int ede = EDE_UNSET;
+  (void)do_bit;
+  
+  if (header->hb4 & HB4_CD)
+    fwd_flags |= FREC_CHECKING_DISABLED;
+  if (ad_reqd)
+    fwd_flags |= FREC_AD_QUESTION;
+  if (oph)
+    fwd_flags |= FREC_HAS_PHEADER;
+#ifdef HAVE_DNSSEC
+  if (do_bit)
+    fwd_flags |= FREC_DO_QUESTION;
+#endif
+  
+  /* Check for retry on existing query.
+     FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below 
+     ensures that no frec created for internal DNSSEC query can be returned here.
+     
+     Similarly FREC_NO_CACHE is never set in flags, so a query which is
+     contigent on a particular source address EDNS0 option will never be matched. */
+  if (forward)
+    old_src = 1;
+  else if ((forward = lookup_frec_by_query(hash, fwd_flags,
+					   FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION |
+					   FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)))
+    {
+      struct frec_src *src;
+      
+      for (src = &forward->frec_src; src; src = src->next)
+	if (src->orig_id == ntohs(header->id) && 
+	    sockaddr_isequal(&src->source, udpaddr))
+	  break;
+      
+      if (src)
+	old_src = 1;
+      else
+	{
+	  /* Existing query, but from new source, just add this 
+	     client to the list that will get the reply.*/
+	  
+	  /* Note whine_malloc() zeros memory. */
+	  if (!daemon->free_frec_src &&
+	      daemon->frec_src_count < daemon->ftabsize &&
+	      (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+	    {
+	      daemon->frec_src_count++;
+	      daemon->free_frec_src->next = NULL;
+	    }
+	  
+	  /* If we've been spammed with many duplicates, return REFUSED. */
+	  if (!daemon->free_frec_src)
+	    {
+	      query_full(now, NULL);
+	      goto reply;
+	    }
+	  
+	  src = daemon->free_frec_src;
+	  daemon->free_frec_src = src->next;
+	  src->next = forward->frec_src.next;
+	  forward->frec_src.next = src;
+	  src->orig_id = ntohs(header->id);
+	  src->source = *udpaddr;
+	  src->dest = *dst_addr;
+	  src->log_id = daemon->log_id;
+	  src->iface = dst_iface;
+	  src->fd = udpfd;
+
+	  /* closely spaced identical queries cannot be a try and a retry, so
+	     it's safe to wait for the reply from the first without
+	     forwarding the second. */
+	  if (difftime(now, forward->time) < 2)
+	    return 0;
+	}
+    }
+
+  /* new query */
+  if (!forward)
+    {
+      if (lookup_domain(daemon->namebuff, gotname, &first, &last))
+	flags = is_local_answer(now, first, daemon->namebuff);
+      else
+	{
+	  /* no available server. */
+	  ede = EDE_NOT_READY;
+	  flags = 0;
+	}
+       
+      /* don't forward A or AAAA queries for simple names, except the empty name */
+      if (!flags &&
+	  option_bool(OPT_NODOTS_LOCAL) &&
+	  (gotname & (F_IPV4 | F_IPV6)) &&
+	  !strchr(daemon->namebuff, '.') &&
+	  strlen(daemon->namebuff) != 0)
+	flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN;
+      
+      /* Configured answer. */
+      if (flags || ede == EDE_NOT_READY)
+	goto reply;
+      
+      master = daemon->serverarray[first];
+      
+      if (!(forward = get_new_frec(now, master, 0)))
+	goto reply;
+      /* table full - flags == 0, return REFUSED */
+      
+      forward->frec_src.source = *udpaddr;
+      forward->frec_src.orig_id = ntohs(header->id);
+      forward->frec_src.dest = *dst_addr;
+      forward->frec_src.iface = dst_iface;
+      forward->frec_src.next = NULL;
+      forward->frec_src.fd = udpfd;
+      forward->new_id = get_id();
+      memcpy(forward->hash, hash, HASH_SIZE);
+      forward->forwardall = 0;
+      forward->flags = fwd_flags;
+      if (domain_no_rebind(daemon->namebuff))
+	forward->flags |= FREC_NOREBIND;
+      if (header->hb4 & HB4_CD)
+	forward->flags |= FREC_CHECKING_DISABLED;
+      if (ad_reqd)
+	forward->flags |= FREC_AD_QUESTION;
+#ifdef HAVE_DNSSEC
+      forward->work_counter = DNSSEC_WORK;
+      if (do_bit)
+	forward->flags |= FREC_DO_QUESTION;
+#endif
+      
+      start = first;
+
+      if (option_bool(OPT_ALL_SERVERS))
+	forward->forwardall = 1;
+
+      if (!option_bool(OPT_ORDER))
+	{
+	  if (master->forwardcount++ > FORWARD_TEST ||
+	      difftime(now, master->forwardtime) > FORWARD_TIME ||
+	      master->last_server == -1)
+	    {
+	      master->forwardtime = now;
+	      master->forwardcount = 0;
+	      forward->forwardall = 1;
+	    }
+	  else
+	    start = master->last_server;
+	}
+    }
+  else
+    {
+      /* retry on existing query, from original source. Send to all available servers  */
+#ifdef HAVE_DNSSEC
+      /* If we've already got an answer to this query, but we're awaiting keys for validation,
+	 there's no point retrying the query, retry the key query instead...... */
+      if (forward->blocking_query)
+	{
+	  int is_sign;
+	  unsigned char *pheader;
+	  
+	  while (forward->blocking_query)
+	    forward = forward->blocking_query;
+	   
+	  blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+	  plen = forward->stash_len;
+	  /* get query for logging. */
+	  extract_request(header, plen, daemon->namebuff, NULL);
+	  
+	  if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
+	    PUTSHORT(SAFE_PKTSZ, pheader);
+	  
+	  /* Find suitable servers: should never fail. */
+	  if (!filter_servers(forward->sentto->arrayposn, F_DNSSECOK, &first, &last))
+	    return 0;
+	  
+	  is_dnssec = 1;
+	  forward->forwardall = 1;
+	}
+      else
+#endif
+	{
+	  /* retry on existing query, from original source. Send to all available servers  */
+	  forward->sentto->failed_queries++;
+	  
+	  if (!filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last))
+	    goto reply;
+	  
+	  master = daemon->serverarray[first];
+	  
+	  /* Forward to all available servers on retry of query from same host. */
+	  if (!option_bool(OPT_ORDER) && old_src)
+	    forward->forwardall = 1;
+	  else
+	    {
+	      start = forward->sentto->arrayposn;
+	      
+	      if (option_bool(OPT_ORDER))
+		{
+		  /* In strict order mode, there must be a server later in the list
+		     left to send to, otherwise without the forwardall mechanism,
+		     code further on will cycle around the list forwever if they
+		     all return REFUSED. If at the last, give up. */
+		  if (++start == last)
+		    goto reply;
+		}
+	    }	  
+	}
+      
+      /* If we didn't get an answer advertising a maximal packet in EDNS,
+	 fall back to 1280, which should work everywhere on IPv6.
+	 If that generates an answer, it will become the new default
+	 for this server */
+      forward->flags |= FREC_TEST_PKTSZ;
+    }
+
+  /* If a query is retried, use the log_id for the retry when logging the answer. */
+  forward->frec_src.log_id = daemon->log_id;
+
+  /* We may be resending a DNSSEC query here, for which the below processing is not necessary. */
+  if (!is_dnssec)
+    {
+      header->id = htons(forward->new_id);
+      
+      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
+      
+      if (subnet)
+	forward->flags |= FREC_HAS_SUBNET;
+      
+      if (!cacheable)
+	forward->flags |= FREC_NO_CACHE;
+      
+#ifdef HAVE_DNSSEC
+      if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
+	{
+	  plen = add_do_bit(header, plen, ((unsigned char *) header) + PACKETSZ);
+	  
+	  /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+	     this allows it to select auth servers when one is returning bad data. */
+	  if (option_bool(OPT_DNSSEC_DEBUG))
+	    header->hb4 |= HB4_CD;
+	  
+	}
+#endif
+      
+      if (find_pseudoheader(header, plen, &edns0_len, &pheader, NULL, NULL))
+	{
+	  /* If there wasn't a PH before, and there is now, we added it. */
+	  if (!oph)
+	    forward->flags |= FREC_ADDED_PHEADER;
+	  
+	  /* If we're sending an EDNS0 with any options, we can't recreate the query from a reply. */
+	  if (edns0_len > 11)
+	    forward->flags |= FREC_HAS_EXTRADATA;
+	  
+	  /* Reduce udp size on retransmits. */
+	  if (forward->flags & FREC_TEST_PKTSZ)
+	    PUTSHORT(SAFE_PKTSZ, pheader);
+	}
+    }
+  
+  if (forward->forwardall)
+    start = first;
+
+  forwarded = 0;
+  
+  /* check for send errors here (no route to host) 
+     if we fail to send to all nameservers, send back an error
+     packet straight away (helps modem users when offline)  */
+
+  while (1)
+    { 
+      int fd;
+      struct server *srv = daemon->serverarray[start];
+      
+      if ((fd = allocate_rfd(&forward->rfds, srv)) != -1)
+	{
+	  
+#ifdef HAVE_CONNTRACK
+	  /* Copy connection mark of incoming query to outgoing connection. */
+	  if (option_bool(OPT_CONNTRACK))
+	    set_outgoing_mark(forward, fd);
+#endif
+	  
+#ifdef HAVE_DNSSEC
+	  if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER))
+	    {
+	      /* Difficult one here. If our client didn't send EDNS0, we will have set the UDP
+		 packet size to 512. But that won't provide space for the RRSIGS in many cases.
+		 The RRSIGS will be stripped out before the answer goes back, so the packet should
+		 shrink again. So, if we added a do-bit, bump the udp packet size to the value
+		 known to be OK for this server. We check returned size after stripping and set
+		 the truncated bit if it's still too big. */		  
+	      unsigned char *pheader;
+	      int is_sign;
+	      if (find_pseudoheader(header, plen, NULL, &pheader, &is_sign, NULL) && !is_sign)
+		PUTSHORT(srv->edns_pktsz, pheader);
+	    }
+#endif
+	  
+	  if (retry_send(sendto(fd, (char *)header, plen, 0,
+				&srv->addr.sa,
+				sa_len(&srv->addr))))
+	    continue;
+	  
+	  if (errno == 0)
+	    {
+#ifdef HAVE_DUMPFILE
+	      dump_packet(DUMP_UP_QUERY, (void *)header, plen, NULL, &srv->addr);
+#endif
+	      
+	      /* Keep info in case we want to re-send this packet */
+	      daemon->srv_save = srv;
+	      daemon->packet_len = plen;
+	      daemon->fd_save = fd;
+	      
+	      if (!(forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
+		{
+		  if (!gotname)
+		    strcpy(daemon->namebuff, "query");
+		  log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff,
+				       &srv->addr, NULL);
+		}
+#ifdef HAVE_DNSSEC
+	      else
+		log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, daemon->namebuff, &srv->addr,
+				     querystr("dnssec-retry", (forward->flags & FREC_DNSKEY_QUERY) ? T_DNSKEY : T_DS));
+#endif
+
+	      srv->queries++;
+	      forwarded = 1;
+	      forward->sentto = srv;
+	      if (!forward->forwardall) 
+		break;
+	      forward->forwardall++;
+	    }
+	}
+      
+      if (++start == last)
+	break;
+    }
+  
+  if (forwarded || is_dnssec)
+    return 1;
+  
+  /* could not send on, prepare to return */ 
+  header->id = htons(forward->frec_src.orig_id);
+  free_frec(forward); /* cancel */
+  ede = EDE_NETERR;
+  
+ reply:
+  if (udpfd != -1)
+    {
+      if (!(plen = make_local_answer(flags, gotname, plen, header, daemon->namebuff, limit, first, last, ede)))
+	return 0;
+      
+      if (oph)
+	{
+	  u16 swap = htons((u16)ede);
+
+	  if (ede != EDE_UNSET)
+	    plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+	  else
+	    plen = add_pseudoheader(header, plen, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+	}
+      
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+      if (option_bool(OPT_CMARK_ALST_EN))
+	{
+	  unsigned int mark;
+	  int have_mark = get_incoming_mark(udpaddr, dst_addr, /* istcp: */ 0, &mark);
+	  if (have_mark && ((u32)mark & daemon->allowlist_mask))
+	    report_addresses(header, plen, mark);
+	}
+#endif
+      
+      send_from(udpfd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND), (char *)header, plen, udpaddr, dst_addr, dst_iface);
+    }
+	  
+  return 0;
+}
+
+static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind, 
+			    int no_cache, int cache_secure, int bogusanswer, int ad_reqd, int do_bit, int added_pheader, 
+			    int check_subnet, union mysockaddr *query_source, unsigned char *limit, int ede)
+{
+  unsigned char *pheader, *sizep;
+  char **sets = 0;
+  int munged = 0, is_sign;
+  unsigned int rcode = RCODE(header);
+  size_t plen; 
+    
+  (void)ad_reqd;
+  (void)do_bit;
+  (void)bogusanswer;
+
+#ifdef HAVE_IPSET
+  if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
+    {
+      /* Similar algorithm to search_servers. */
+      struct ipsets *ipset_pos;
+      unsigned int namelen = strlen(daemon->namebuff);
+      unsigned int matchlen = 0;
+      for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next) 
+	{
+	  unsigned int domainlen = strlen(ipset_pos->domain);
+	  char *matchstart = daemon->namebuff + namelen - domainlen;
+	  if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
+	      (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
+	      domainlen >= matchlen) 
+	    {
+	      matchlen = domainlen;
+	      sets = ipset_pos->sets;
+	    }
+	}
+    }
+#endif
+
+  if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
+    {
+      /* Get extended RCODE. */
+      rcode |= sizep[2] << 4;
+
+      if (check_subnet && !check_source(header, plen, pheader, query_source))
+	{
+	  my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
+	  return 0;
+	}
+      
+      if (!is_sign)
+	{
+	  if (added_pheader)
+	    {
+	      /* client didn't send EDNS0, we added one, strip it off before returning answer. */
+	      n = rrfilter(header, n, 0);
+	      pheader = NULL;
+	    }
+	  else
+	    {
+	      /* If upstream is advertising a larger UDP packet size
+		 than we allow, trim it so that we don't get overlarge
+		 requests for the client. We can't do this for signed packets. */
+	      unsigned short udpsz;
+	      GETSHORT(udpsz, sizep);
+	      if (udpsz > daemon->edns_pktsz)
+		{
+		  sizep -= 2;
+		  PUTSHORT(daemon->edns_pktsz, sizep);
+		}
+
+#ifdef HAVE_DNSSEC
+	      /* If the client didn't set the do bit, but we did, reset it. */
+	      if (option_bool(OPT_DNSSEC_VALID) && !do_bit)
+		{
+		  unsigned short flags;
+		  sizep += 2; /* skip RCODE */
+		  GETSHORT(flags, sizep);
+		  flags &= ~0x8000;
+		  sizep -= 2;
+		  PUTSHORT(flags, sizep);
+		}
+#endif
+	    }
+	}
+    }
+  
+  /* RFC 4035 sect 4.6 para 3 */
+  if (!is_sign && !option_bool(OPT_DNSSEC_PROXY))
+     header->hb4 &= ~HB4_AD;
+
+  header->hb4 |= HB4_RA; /* recursion if available */
+
+  if (OPCODE(header) != QUERY)
+    return resize_packet(header, n, pheader, plen);
+
+  if (rcode != NOERROR && rcode != NXDOMAIN)
+    {
+      union all_addr a;
+      a.log.rcode = rcode;
+      a.log.ede = ede;
+      log_query(F_UPSTREAM | F_RCODE, "error", &a, NULL);
+      
+      return resize_packet(header, n, pheader, plen);
+    }
+  
+  /* Complain loudly if the upstream server is non-recursive. */
+  if (!(header->hb4 & HB4_RA) && rcode == NOERROR &&
+      server && !(server->flags & SERV_WARNED_RECURSIVE))
+    {
+      (void)prettyprint_addr(&server->addr, daemon->namebuff);
+      my_syslog(LOG_WARNING, _("nameserver %s refused to do a recursive query"), daemon->namebuff);
+      if (!option_bool(OPT_LOG))
+	server->flags |= SERV_WARNED_RECURSIVE;
+    }  
+
+  if (daemon->bogus_addr && rcode != NXDOMAIN &&
+      check_for_bogus_wildcard(header, n, daemon->namebuff, now))
+    {
+      munged = 1;
+      SET_RCODE(header, NXDOMAIN);
+      header->hb3 &= ~HB3_AA;
+      cache_secure = 0;
+      ede = EDE_BLOCKED;
+    }
+  else 
+    {
+      int doctored = 0;
+      
+      if (rcode == NXDOMAIN && 
+	  extract_request(header, n, daemon->namebuff, NULL))
+	{
+	  if (check_for_local_domain(daemon->namebuff, now) ||
+	      lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL))
+	    {
+	      /* if we forwarded a query for a locally known name (because it was for 
+		 an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
+		 since we know that the domain exists, even if upstream doesn't */
+	      munged = 1;
+	      header->hb3 |= HB3_AA;
+	      SET_RCODE(header, NOERROR);
+	      cache_secure = 0;
+	    }
+	}
+      
+      if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, no_cache, cache_secure, &doctored))
+	{
+	  my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
+	  munged = 1;
+	  cache_secure = 0;
+	  ede = EDE_BLOCKED;
+	}
+
+      if (doctored)
+	cache_secure = 0;
+    }
+  
+#ifdef HAVE_DNSSEC
+  if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG))
+    {
+      /* Bogus reply, turn into SERVFAIL */
+      SET_RCODE(header, SERVFAIL);
+      munged = 1;
+    }
+
+  if (option_bool(OPT_DNSSEC_VALID))
+    {
+      header->hb4 &= ~HB4_AD;
+      
+      if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
+	header->hb4 |= HB4_AD;
+      
+      /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+      if (!do_bit)
+	n = rrfilter(header, n, 1);
+    }
+#endif
+
+  /* do this after extract_addresses. Ensure NODATA reply and remove
+     nameserver info. */
+  if (munged)
+    {
+      header->ancount = htons(0);
+      header->nscount = htons(0);
+      header->arcount = htons(0);
+      header->hb3 &= ~HB3_TC;
+    }
+  
+  /* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide
+     sections of the packet. Find the new length here and put back pseudoheader
+     if it was removed. */
+  n = resize_packet(header, n, pheader, plen);
+
+  if (pheader && ede != EDE_UNSET)
+    {
+      u16 swap = htons((u16)ede);
+      n = add_pseudoheader(header, n, limit, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 1);
+    }
+  
+  return n;
+}
+
+#ifdef HAVE_DNSSEC
+static void dnssec_validate(struct frec *forward, struct dns_header *header,
+			    ssize_t plen, int status, time_t now)
+{
+  daemon->log_display_id = forward->frec_src.log_id;
+  
+  /* We've had a reply already, which we're validating. Ignore this duplicate */
+  if (forward->blocking_query)
+    return;
+  
+  /* Truncated answer can't be validated.
+     If this is an answer to a DNSSEC-generated query, we still
+     need to get the client to retry over TCP, so return
+     an answer with the TC bit set, even if the actual answer fits.
+  */
+  if (header->hb3 & HB3_TC)
+    status = STAT_TRUNCATED;
+
+  /* If all replies to a query are REFUSED, give up. */
+  if (RCODE(header) == REFUSED)
+    status = STAT_ABANDONED;
+  
+  /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
+     would invite infinite loops, since the answers to DNSKEY and DS queries
+     will not be cached, so they'll be repeated. */
+  if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
+    {
+      if (forward->flags & FREC_DNSKEY_QUERY)
+	status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
+      else if (forward->flags & FREC_DS_QUERY)
+	status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class);
+      else
+	status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, 
+				       !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
+				       NULL, NULL, NULL);
+#ifdef HAVE_DUMPFILE
+      if (STAT_ISEQUAL(status, STAT_BOGUS))
+	dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
+		    header, (size_t)plen, &forward->sentto->addr, NULL);
+#endif
+    }
+  
+  /* Can't validate, as we're missing key data. Put this
+     answer aside, whilst we get that. */     
+  if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
+    {
+      struct frec *new = NULL;
+      int serverind;
+      struct blockdata *stash;
+      
+      /* Now save reply pending receipt of key data */
+      if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
+	  (stash = blockdata_alloc((char *)header, plen)))
+	{
+	  struct server *server = daemon->serverarray[serverind];
+	  struct frec *orig;
+	  unsigned int flags;
+	  void *hash;
+	  size_t nn;
+
+	  /* validate routines leave name of required record in daemon->keyname */
+	  nn = dnssec_generate_query(header, ((unsigned char *) header) + server->edns_pktsz,
+				     daemon->keyname, forward->class,
+				     STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
+	  
+	  flags = STAT_ISEQUAL(status, STAT_NEED_KEY) ? FREC_DNSKEY_QUERY : FREC_DS_QUERY;
+	  hash = hash_questions(header, nn, daemon->namebuff);
+
+	  if ((new = lookup_frec_by_query(hash, flags, FREC_DNSKEY_QUERY | FREC_DS_QUERY)))
+	    {
+	      forward->next_dependent = new->dependent;
+	      new->dependent = forward;
+	      /* Make consistent, only replace query copy with unvalidated answer
+		 when we set ->blocking_query. */
+	      if (forward->stash)
+		blockdata_free(forward->stash);
+	      forward->blocking_query = new;
+	      forward->stash_len = plen;
+	      forward->stash = stash;
+	      return;
+	    }
+	    
+	  /* Find the original query that started it all.... */
+	  for (orig = forward; orig->dependent; orig = orig->dependent);
+	  
+	  /* Make sure we don't expire and free the orig frec during the
+	     allocation of a new one: third arg of get_new_frec() does that. */
+	  if (--orig->work_counter == 0 || !(new = get_new_frec(now, server, 1)))
+	    blockdata_free(stash); /* don't leak this on failure. */
+	  else
+	    {
+	      int fd;
+	      struct frec *next = new->next;
+
+	      *new = *forward; /* copy everything, then overwrite */
+	      new->next = next;
+	      new->blocking_query = NULL;
+	      
+	      new->frec_src.log_id = daemon->log_display_id = ++daemon->log_id;
+	      new->sentto = server;
+	      new->rfds = NULL;
+	      new->frec_src.next = NULL;
+	      new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
+	      new->flags |= flags;
+	      new->forwardall = 0;
+	      
+	      forward->next_dependent = NULL;
+	      new->dependent = forward; /* to find query awaiting new one. */
+
+	      /* Make consistent, only replace query copy with unvalidated answer
+		 when we set ->blocking_query. */
+	      forward->blocking_query = new; 
+	      if (forward->stash)
+		blockdata_free(forward->stash);
+	      forward->stash_len = plen;
+	      forward->stash = stash;
+	      
+	      memcpy(new->hash, hash, HASH_SIZE);
+	      new->new_id = get_id();
+	      header->id = htons(new->new_id);
+	      /* Save query for retransmission */
+	      new->stash = blockdata_alloc((char *)header, nn);
+	      new->stash_len = nn;
+	      
+	      /* Don't resend this. */
+	      daemon->srv_save = NULL;
+	      
+	      if ((fd = allocate_rfd(&new->rfds, server)) != -1)
+		{
+#ifdef HAVE_CONNTRACK
+		  if (option_bool(OPT_CONNTRACK))
+		    set_outgoing_mark(orig, fd);
+#endif
+		  server_send_log(server, fd, header, nn, DUMP_SEC_QUERY,
+				  F_NOEXTRA | F_DNSSEC, daemon->keyname,
+				  querystr("dnssec-query", STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
+		  server->queries++;
+		}
+	      
+	      return;
+	    }
+	}
+
+      /* sending DNSSEC query failed. */
+      status = STAT_ABANDONED;
+    }
+  
+  /* Validated original answer, all done. */
+  if (!forward->dependent)
+    return_reply(now, forward, header, plen, status);
+  else
+    {
+      /* validated subsidiary query/queries, (and cached result)
+	 pop that and return to the previous query/queries we were working on. */
+      struct frec *prev, *nxt = forward->dependent;
+      
+      free_frec(forward);
+      
+      while ((prev = nxt))
+	{
+	  /* ->next_dependent will have changed after return from recursive call below. */
+	  nxt = prev->next_dependent;
+	  prev->blocking_query = NULL; /* already gone */
+	  blockdata_retrieve(prev->stash, prev->stash_len, (void *)header);
+	  dnssec_validate(prev, header, prev->stash_len, status, now);
+	}
+    }
+}
+#endif
+
+/* sets new last_server */
+void reply_query(int fd, time_t now)
+{
+  /* packet from peer server, extract data for cache, and send to
+     original requester */
+  struct dns_header *header;
+  union mysockaddr serveraddr;
+  struct frec *forward;
+  socklen_t addrlen = sizeof(serveraddr);
+  ssize_t n = recvfrom(fd, daemon->packet, daemon->packet_buff_sz, 0, &serveraddr.sa, &addrlen);
+  struct server *server;
+  void *hash;
+  int first, last, c;
+    
+  /* packet buffer overwritten */
+  daemon->srv_save = NULL;
+
+  /* Determine the address of the server replying  so that we can mark that as good */
+  if (serveraddr.sa.sa_family == AF_INET6)
+    serveraddr.in6.sin6_flowinfo = 0;
+  
+  header = (struct dns_header *)daemon->packet;
+
+  if (n < (int)sizeof(struct dns_header) || !(header->hb3 & HB3_QR))
+    return;
+
+  hash = hash_questions(header, n, daemon->namebuff);
+  
+  if (!(forward = lookup_frec(ntohs(header->id), fd, hash, &first, &last)))
+    return;
+  
+  /* spoof check: answer must come from known server, also
+     we may have sent the same query to multiple servers from
+     the same local socket, and would like to know which one has answered. */
+  for (c = first; c != last; c++)
+    if (sockaddr_isequal(&daemon->serverarray[c]->addr, &serveraddr))
+      break;
+  
+  if (c == last)
+    return;
+
+  server = daemon->serverarray[c];
+
+  if (RCODE(header) != REFUSED)
+    daemon->serverarray[first]->last_server = c;
+  else if (daemon->serverarray[first]->last_server == c)
+    daemon->serverarray[first]->last_server = -1;
+
+  /* If sufficient time has elapsed, try and expand UDP buffer size again. */
+  if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME)
+    server->edns_pktsz = daemon->edns_pktsz;
+
+#ifdef HAVE_DUMPFILE
+  dump_packet((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_REPLY : DUMP_UP_REPLY,
+	      (void *)header, n, &serveraddr, NULL);
+#endif
+
+  /* log_query gets called indirectly all over the place, so 
+     pass these in global variables - sorry. */
+  daemon->log_display_id = forward->frec_src.log_id;
+  daemon->log_source_addr = &forward->frec_src.source;
+  
+  if (daemon->ignore_addr && RCODE(header) == NOERROR &&
+      check_for_ignored_address(header, n))
+    return;
+
+  /* Note: if we send extra options in the EDNS0 header, we can't recreate
+     the query from the reply. */
+  if ((RCODE(header) == REFUSED || RCODE(header) == SERVFAIL) &&
+      forward->forwardall == 0 &&
+      !(forward->flags & FREC_HAS_EXTRADATA))
+    /* for broken servers, attempt to send to another one. */
+    {
+      unsigned char *pheader, *udpsz;
+      unsigned short udp_size =  PACKETSZ; /* default if no EDNS0 */
+      size_t plen;
+      int is_sign;
+      size_t nn = 0;
+      
+#ifdef HAVE_DNSSEC
+      /* DNSSEC queries have a copy of the original query stashed. 
+	 The query MAY have got a good answer, and be awaiting
+	 the results of further queries, in which case
+	 The Stash contains something else and we don't need to retry anyway. */
+      if ((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && !forward->blocking_query)
+	{
+	  blockdata_retrieve(forward->stash, forward->stash_len, (void *)header);
+	  nn = forward->stash_len;
+	  udp_size = daemon->edns_pktsz;
+	}
+      else
+#endif
+	{
+	  /* recreate query from reply */
+	  if ((pheader = find_pseudoheader(header, (size_t)n, &plen, &udpsz, &is_sign, NULL)))
+	    GETSHORT(udp_size, udpsz);
+	  
+	  /* If the client provides an EDNS0 UDP size, use that to limit our reply.
+	     (bounded by the maximum configured). If no EDNS0, then it
+	     defaults to 512 */
+	  if (udp_size > daemon->edns_pktsz)
+	    udp_size = daemon->edns_pktsz;
+	  else if (udp_size < PACKETSZ)
+	    udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
+	  
+	  if (!is_sign &&
+	      (nn = resize_packet(header, (size_t)n, pheader, plen)) &&
+	      (forward->flags & FREC_DO_QUESTION))
+	    add_do_bit(header, nn,  (unsigned char *)pheader + plen);
+
+	  header->ancount = htons(0);
+	  header->nscount = htons(0);
+	  header->arcount = htons(0);
+	  header->hb3 &= ~(HB3_QR | HB3_AA | HB3_TC);
+	  header->hb4 &= ~(HB4_RA | HB4_RCODE | HB4_CD | HB4_AD);
+	  if (forward->flags & FREC_CHECKING_DISABLED)
+	    header->hb4 |= HB4_CD;
+	  if (forward->flags & FREC_AD_QUESTION)
+	    header->hb4 |= HB4_AD;
+	}
+
+      if (nn)
+	{
+	  forward_query(-1, NULL, NULL, 0, header, nn, ((char *) header) + udp_size, now, forward,
+			forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION);
+	  return;
+	}
+    }
+
+  /* If the answer is an error, keep the forward record in place in case
+     we get a good reply from another server. Kill it when we've
+     had replies from all to avoid filling the forwarding table when
+     everything is broken */
+
+  /* decrement count of replies recieved if we sent to more than one server. */
+  if (forward->forwardall && (--forward->forwardall > 1) && RCODE(header) == REFUSED)
+    return;
+
+  /* We tried resending to this server with a smaller maximum size and got an answer.
+     Make that permanent. To avoid reduxing the packet size for a single dropped packet,
+     only do this when we get a truncated answer, or one larger than the safe size. */
+  if (server->edns_pktsz > SAFE_PKTSZ && (forward->flags & FREC_TEST_PKTSZ) && 
+      ((header->hb3 & HB3_TC) || n >= SAFE_PKTSZ))
+    {
+      server->edns_pktsz = SAFE_PKTSZ;
+      server->pktsz_reduced = now;
+      (void)prettyprint_addr(&server->addr, daemon->addrbuff);
+      my_syslog(LOG_WARNING, _("reducing DNS packet size for nameserver %s to %d"), daemon->addrbuff, SAFE_PKTSZ);
+    }
+
+  forward->sentto = server;
+  
+#ifdef HAVE_DNSSEC
+  if ((forward->sentto->flags & SERV_DO_DNSSEC) && 
+      option_bool(OPT_DNSSEC_VALID) &&
+      !(forward->flags & FREC_CHECKING_DISABLED))
+    dnssec_validate(forward, header, n, STAT_OK, now);
+  else
+#endif
+    return_reply(now, forward, header, n, STAT_OK); 
+}
+
+static void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status)
+{
+  int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+  size_t nn;
+  int ede = EDE_UNSET;
+
+  (void)status;
+
+  daemon->log_display_id = forward->frec_src.log_id;
+  daemon->log_source_addr = &forward->frec_src.source;
+  
+  /* Don't cache replies where DNSSEC validation was turned off, either
+     the upstream server told us so, or the original query specified it.  */
+  if ((header->hb4 & HB4_CD) || (forward->flags & FREC_CHECKING_DISABLED))
+    no_cache_dnssec = 1;
+
+#ifdef HAVE_DNSSEC
+  if (!STAT_ISEQUAL(status, STAT_OK))
+    {
+      /* status is STAT_OK when validation not turned on. */
+      no_cache_dnssec = 0;
+      
+      if (STAT_ISEQUAL(status, STAT_TRUNCATED))
+	header->hb3 |= HB3_TC;
+      else
+	{
+	  char *result, *domain = "result";
+	  union all_addr a;
+
+	  a.log.ede = ede = errflags_to_ede(status);
+
+	  if (STAT_ISEQUAL(status, STAT_ABANDONED))
+	    {
+	      result = "ABANDONED";
+	      status = STAT_BOGUS;
+	    }
+	  else
+	    result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
+	  
+	  if (STAT_ISEQUAL(status, STAT_SECURE))
+	    cache_secure = 1;
+	  else if (STAT_ISEQUAL(status, STAT_BOGUS))
+	    {
+	      no_cache_dnssec = 1;
+	      bogusanswer = 1;
+	      
+	      if (extract_request(header, n, daemon->namebuff, NULL))
+		domain = daemon->namebuff;
+	    }
+	  
+	  log_query(F_SECSTAT, domain, &a, result);
+	}
+    }
+#endif
+  
+  if (option_bool(OPT_NO_REBIND))
+    check_rebind = !(forward->flags & FREC_NOREBIND);
+  
+  /* restore CD bit to the value in the query */
+  if (forward->flags & FREC_CHECKING_DISABLED)
+    header->hb4 |= HB4_CD;
+  else
+    header->hb4 &= ~HB4_CD;
+  
+  /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+     since the cache is ignorant of such things. */
+  if (forward->flags & FREC_NO_CACHE)
+    no_cache_dnssec = 1;
+  
+  if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, 
+			  forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
+			  forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source,
+			  ((unsigned char *)header) + daemon->edns_pktsz, ede)))
+    {
+      struct frec_src *src;
+      
+      header->id = htons(forward->frec_src.orig_id);
+#ifdef HAVE_DNSSEC
+      /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
+	 greater than the no-EDNS0-implied 512 to have space for the RRSIGS. If, having stripped them and the EDNS0
+	 header, the answer is still bigger than 512, truncate it and mark it so. The client then retries with TCP. */
+      if (option_bool(OPT_DNSSEC_VALID) && (forward->flags & FREC_ADDED_PHEADER) && (nn > PACKETSZ))
+	{
+	  header->ancount = htons(0);
+	  header->nscount = htons(0);
+	  header->arcount = htons(0);
+	  header->hb3 |= HB3_TC;
+	  nn = resize_packet(header, nn, NULL, 0);
+	}
+#endif
+      
+      for (src = &forward->frec_src; src; src = src->next)
+	{
+	  header->id = htons(src->orig_id);
+	  
+#ifdef HAVE_DUMPFILE
+	  dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
+#endif
+	  
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (option_bool(OPT_CMARK_ALST_EN))
+	    {
+	      unsigned int mark;
+	      int have_mark = get_incoming_mark(&src->source, &src->dest, /* istcp: */ 0, &mark);
+	      if (have_mark && ((u32)mark & daemon->allowlist_mask))
+		report_addresses(header, nn, mark);
+	    }
+#endif
+	  
+	  send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
+		    &src->source, &src->dest, src->iface);
+	  
+	  if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
+	    {
+	      daemon->log_display_id = src->log_id;
+	      daemon->log_source_addr = &src->source;
+	      log_query(F_UPSTREAM, "query", NULL, "duplicate");
+	    }
+	}
+    }
+
+  free_frec(forward); /* cancel */
+}
+
+
+#ifdef HAVE_CONNTRACK
+static int is_query_allowed_for_mark(u32 mark, const char *name)
+{
+  int is_allowable_name, did_validate_name = 0;
+  struct allowlist *allowlists;
+  char **patterns_pos;
+  
+  for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+    if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
+      for (patterns_pos = allowlists->patterns; *patterns_pos; patterns_pos++)
+	{
+	  if (!strcmp(*patterns_pos, "*"))
+	    return 1;
+	  if (!did_validate_name)
+	    {
+	      is_allowable_name = name ? is_valid_dns_name(name) : 0;
+	      did_validate_name = 1;
+	    }
+	  if (is_allowable_name && is_dns_name_matching_pattern(name, *patterns_pos))
+	    return 1;
+	}
+  return 0;
+}
+
+static size_t answer_disallowed(struct dns_header *header, size_t qlen, u32 mark, const char *name)
+{
+  unsigned char *p;
+  (void)name;
+  (void)mark;
+  
+#ifdef HAVE_UBUS
+  if (name)
+    ubus_event_bcast_connmark_allowlist_refused(mark, name);
+#endif
+  
+  setup_reply(header, /* flags: */ 0, EDE_BLOCKED);
+  
+  if (!(p = skip_questions(header, qlen)))
+    return 0;
+  return p - (unsigned char *)header;
+}
+#endif
+
+void receive_query(struct listener *listen, time_t now)
+{
+  struct dns_header *header = (struct dns_header *)daemon->packet;
+  union mysockaddr source_addr;
+  unsigned char *pheader;
+  unsigned short type, udp_size = PACKETSZ; /* default if no EDNS0 */
+  union all_addr dst_addr;
+  struct in_addr netmask, dst_addr_4;
+  size_t m,plen;
+  ssize_t n,num;
+  int if_index = 0, auth_dns = 0, do_bit = 0, have_pseudoheader = 0;
+#ifdef HAVE_CONNTRACK
+  unsigned int mark = 0;
+  int have_mark = 0;
+  int is_single_query = 0, allowed = 1;
+#endif
+#ifdef HAVE_AUTH
+  int local_auth = 0;
+#endif
+  struct iovec iov[1];
+  struct msghdr msg;
+  struct cmsghdr *cmptr;
+  union {
+    struct cmsghdr align; /* this ensures alignment */
+    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#if defined(HAVE_LINUX_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#elif defined(IP_RECVDSTADDR) && defined(HAVE_SOLARIS_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_addr)) +
+		 CMSG_SPACE(sizeof(unsigned int))];
+#elif defined(IP_RECVDSTADDR)
+    char control[CMSG_SPACE(sizeof(struct in_addr)) +
+		 CMSG_SPACE(sizeof(struct sockaddr_dl))];
+#endif
+  } control_u;
+  int family = listen->addr.sa.sa_family;
+   /* Can always get recvd interface for IPv6 */
+  int check_dst = !option_bool(OPT_NOWILD) || family == AF_INET6;
+
+  /* packet buffer overwritten */
+  daemon->srv_save = NULL;
+
+  dst_addr_4.s_addr = dst_addr.addr4.s_addr = 0;
+  netmask.s_addr = 0;
+  
+  if (option_bool(OPT_NOWILD) && listen->iface)
+    {
+      auth_dns = listen->iface->dns_auth;
+     
+      if (family == AF_INET)
+	{
+	  dst_addr_4 = dst_addr.addr4 = listen->iface->addr.in.sin_addr;
+	  netmask = listen->iface->netmask;
+	}
+    }
+  
+  iov[0].iov_base = daemon->packet;
+  iov[0].iov_len = daemon->edns_pktsz;
+    
+  msg.msg_control = control_u.control;
+  msg.msg_controllen = sizeof(control_u);
+  msg.msg_flags = 0;
+  msg.msg_name = &source_addr;
+  msg.msg_namelen = sizeof(source_addr);
+  msg.msg_iov = iov;
+  msg.msg_iovlen = 1;
+  
+  if ((n = recvmsg(listen->fd, &msg, 0)) == -1)
+    return;
+  
+  if (n < (int)sizeof(struct dns_header) || 
+      (msg.msg_flags & MSG_TRUNC) ||
+      (header->hb3 & HB3_QR))
+    return;
+	num=n;
+  /* Clear buffer beyond request to avoid risk of
+     information disclosure. */
+  memset(daemon->packet + n, 0, daemon->edns_pktsz - n);
+  
+  source_addr.sa.sa_family = family;
+  
+  if (family == AF_INET)
+    {
+       /* Source-port == 0 is an error, we can't send back to that. 
+	  http://www.ietf.org/mail-archive/web/dnsop/current/msg11441.html */
+      if (source_addr.in.sin_port == 0)
+	return;
+    }
+  else
+    {
+      /* Source-port == 0 is an error, we can't send back to that. */
+      if (source_addr.in6.sin6_port == 0)
+	return;
+      source_addr.in6.sin6_flowinfo = 0;
+    }
+  
+  /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+  if (option_bool(OPT_LOCAL_SERVICE))
+    {
+      struct addrlist *addr;
+
+      if (family == AF_INET6) 
+	{
+	  for (addr = daemon->interface_addrs; addr; addr = addr->next)
+	    if ((addr->flags & ADDRLIST_IPV6) &&
+		is_same_net6(&addr->addr.addr6, &source_addr.in6.sin6_addr, addr->prefixlen))
+	      break;
+	}
+      else
+	{
+	  struct in_addr netmask;
+	  for (addr = daemon->interface_addrs; addr; addr = addr->next)
+	    {
+	      netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen));
+	      if (!(addr->flags & ADDRLIST_IPV6) &&
+		  is_same_net(addr->addr.addr4, source_addr.in.sin_addr, netmask))
+		break;
+	    }
+	}
+      if (!addr)
+	{
+	  static int warned = 0;
+	  if (!warned)
+	    {
+	      my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+	      warned = 1;
+	    }
+	  return;
+	}
+    }
+		
+  if (check_dst)
+    {
+      struct ifreq ifr;
+
+      if (msg.msg_controllen < sizeof(struct cmsghdr))
+	return;
+
+#if defined(HAVE_LINUX_NETWORK)
+      if (family == AF_INET)
+	for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	  if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+	    {
+	      union {
+		unsigned char *c;
+		struct in_pktinfo *p;
+	      } p;
+	      p.c = CMSG_DATA(cmptr);
+	      dst_addr_4 = dst_addr.addr4 = p.p->ipi_spec_dst;
+	      if_index = p.p->ipi_ifindex;
+	    }
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+      if (family == AF_INET)
+	{
+	  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	    {
+	      union {
+		unsigned char *c;
+		unsigned int *i;
+		struct in_addr *a;
+#ifndef HAVE_SOLARIS_NETWORK
+		struct sockaddr_dl *s;
+#endif
+	      } p;
+	       p.c = CMSG_DATA(cmptr);
+	       if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
+		 dst_addr_4 = dst_addr.addr4 = *(p.a);
+	       else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
+#ifdef HAVE_SOLARIS_NETWORK
+		 if_index = *(p.i);
+#else
+  	         if_index = p.s->sdl_index;
+#endif
+	    }
+	}
+#endif
+      
+      if (family == AF_INET6)
+	{
+	  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);
+		  
+		dst_addr.addr6 = p.p->ipi6_addr;
+		if_index = p.p->ipi6_ifindex;
+	      }
+	}
+      
+      /* enforce available interface configuration */
+      
+      if (!indextoname(listen->fd, if_index, ifr.ifr_name))
+	return;
+      
+      if (!iface_check(family, &dst_addr, ifr.ifr_name, &auth_dns))
+	{
+	   if (!option_bool(OPT_CLEVERBIND))
+	     enumerate_interfaces(0); 
+	   if (!loopback_exception(listen->fd, family, &dst_addr, ifr.ifr_name) &&
+	       !label_exception(if_index, family, &dst_addr))
+	     return;
+	}
+
+      if (family == AF_INET && option_bool(OPT_LOCALISE))
+	{
+	  struct irec *iface;
+	  
+	  /* get the netmask of the interface which has the address we were sent to.
+	     This is no necessarily the interface we arrived on. */
+	  
+	  for (iface = daemon->interfaces; iface; iface = iface->next)
+	    if (iface->addr.sa.sa_family == AF_INET &&
+		iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr)
+	      break;
+	  
+	  /* interface may be new */
+	  if (!iface && !option_bool(OPT_CLEVERBIND))
+	    enumerate_interfaces(0); 
+	  
+	  for (iface = daemon->interfaces; iface; iface = iface->next)
+	    if (iface->addr.sa.sa_family == AF_INET &&
+		iface->addr.in.sin_addr.s_addr == dst_addr_4.s_addr)
+	      break;
+	  
+	  /* If we failed, abandon localisation */
+	  if (iface)
+	    netmask = iface->netmask;
+	  else
+	    dst_addr_4.s_addr = 0;
+	}
+    }
+   
+  /* log_query gets called indirectly all over the place, so 
+     pass these in global variables - sorry. */
+  daemon->log_display_id = ++daemon->log_id;
+  daemon->log_source_addr = &source_addr;
+
+#ifdef HAVE_DUMPFILE
+  dump_packet(DUMP_QUERY, daemon->packet, (size_t)n, &source_addr, NULL);
+#endif
+  
+#ifdef HAVE_CONNTRACK
+  if (option_bool(OPT_CMARK_ALST_EN))
+    have_mark = get_incoming_mark(&source_addr, &dst_addr, /* istcp: */ 0, &mark);
+#endif
+	  
+  if (extract_request(header, (size_t)n, daemon->namebuff, &type))
+    {
+#ifdef HAVE_AUTH
+      struct auth_zone *zone;
+#endif
+      char *types = querystr(auth_dns ? "auth" : "query", type);
+
+      log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
+			   &source_addr, types);
+      
+#ifdef HAVE_CONNTRACK
+      is_single_query = 1;
+#endif
+
+#ifdef HAVE_AUTH
+      /* find queries for zones we're authoritative for, and answer them directly */
+      if (!auth_dns && !option_bool(OPT_LOCALISE))
+	for (zone = daemon->auth_zones; zone; zone = zone->next)
+	  if (in_zone(zone, daemon->namebuff, NULL))
+	    {
+	      auth_dns = 1;
+	      local_auth = 1;
+	      break;
+	    }
+#endif
+      
+#ifdef HAVE_LOOP
+      /* Check for forwarding loop */
+      if (detect_loop(daemon->namebuff, type))
+	return;
+#endif
+    }
+  
+  if (find_pseudoheader(header, (size_t)n, NULL, &pheader, NULL, NULL))
+    { 
+      unsigned short flags;
+      
+      have_pseudoheader = 1;
+      GETSHORT(udp_size, pheader);
+      pheader += 2; /* ext_rcode */
+      GETSHORT(flags, pheader);
+      
+      if (flags & 0x8000)
+	do_bit = 1;/* do bit */ 
+	
+      /* If the client provides an EDNS0 UDP size, use that to limit our reply.
+	 (bounded by the maximum configured). If no EDNS0, then it
+	 defaults to 512 */
+      if (udp_size > daemon->edns_pktsz)
+	udp_size = daemon->edns_pktsz;
+      else if (udp_size < PACKETSZ)
+	udp_size = PACKETSZ; /* Sanity check - can't reduce below default. RFC 6891 6.2.3 */
+    }
+#if 1  
+	//printf("@!@dnsmasq %s len=%d\n",daemon->namebuff,strlen(daemon->namebuff));
+	if(strlen(daemon->namebuff) == 0)//nessus DNS Server Spoofed Request Amplification DDoS
+	  return;
+	{
+	char cfg_buf[32];
+	memset(cfg_buf, 0x00, sizeof(cfg_buf));
+	sc_cfg_get("lan_domain_Enabled", cfg_buf, sizeof(cfg_buf));
+	if(0 == strcmp(cfg_buf, "1"))
+	{
+		static char name_buf[512];
+		memset(name_buf,0x00, sizeof(name_buf));
+		sc_cfg_get("LocalDomain", name_buf, sizeof(name_buf));
+		if(!strcmp(daemon->namebuff,name_buf))  
+		{
+
+			if (type != T_A)
+			{
+				plen = (size_t)n;
+				setup_reply(daemon->packet, 0, 0);
+				unsigned char *p = skip_questions(header, plen);
+				if(p)
+				{
+					plen = p - (unsigned char *)header;
+					//send_from(listen->fd, daemon->options & OPT_NOWILD, daemon->packet, plen, &source_addr, &dst_addr, if_index);
+					send_from(listen->fd, option_bool(OPT_NOWILD), daemon->packet, plen, &source_addr, &dst_addr, if_index);
+				}
+				return;
+			}
+
+			daemon->packet[2] = daemon->packet[2]|0x80;
+			daemon->packet[7] = daemon->packet[7]|0x01;
+			daemon->packet[num] = 0xc0;
+			daemon->packet[num+1] = 0x0c;
+			daemon->packet[num+2] = 0x00;
+			daemon->packet[num+3] = 0x01;
+			daemon->packet[num+4] = 0x00;
+			daemon->packet[num+5] = 0x01;
+			daemon->packet[num+6] = 0x00;
+			daemon->packet[num+7] = 0x00;
+			daemon->packet[num+8] = 0x00;
+			daemon->packet[num+9] = 0x0a;
+			daemon->packet[num+10] = 0x00;
+			daemon->packet[num+11] = 0x04;
+			daemon->packet[num+12] = *((char *)(&(dst_addr.addr4)) + 0)& 0xff;
+			daemon->packet[num+13] = *((char *)(&(dst_addr.addr4)) + 1)& 0xff;
+			daemon->packet[num+14] = *((char *)(&(dst_addr.addr4)) + 2)& 0xff;
+			daemon->packet[num+15] = *((char *)(&(dst_addr.addr4)) + 3)& 0xff;
+			//send_from(listen->fd, daemon->options & OPT_NOWILD, daemon->packet, num+16, &source_addr, &dst_addr, if_index);
+			send_from(listen->fd, option_bool(OPT_NOWILD), daemon->packet, num+16, &source_addr, &dst_addr, if_index);
+			return;
+		}
+	}
+	{
+	char web_buf[16] = {0};
+	sc_cfg_get("DNS_proxy", web_buf, sizeof(web_buf));
+	if(0 == strcmp(web_buf, "disable"))
+	{
+		return;
+	}
+	}
+	}
+#endif
+#ifdef HAVE_CONNTRACK
+#ifdef HAVE_AUTH
+  if (!auth_dns || local_auth)
+#endif
+    if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+      allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
+  
+  if (0);
+#ifdef HAVE_CONNTRACK
+  else if (!allowed)
+    {
+      u16 swap = htons(EDE_BLOCKED);
+
+      m = answer_disallowed(header, (size_t)n, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+      
+      if (have_pseudoheader && m != 0)
+	m = add_pseudoheader(header,  m,  ((unsigned char *) header) + udp_size, daemon->edns_pktsz,
+			     EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+      
+      if (m >= 1)
+	{
+#ifdef HAVE_DUMPFILE
+	  dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+		    (char *)header, m, &source_addr, &dst_addr, if_index);
+	  daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+	}
+    }
+#endif
+#ifdef HAVE_AUTH
+  else if (auth_dns)
+    {
+      m = answer_auth(header, ((char *) header) + udp_size, (size_t)n, now, &source_addr, 
+		      local_auth, do_bit, have_pseudoheader);
+      if (m >= 1)
+	{
+#ifdef HAVE_DUMPFILE
+	  dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (local_auth)
+	    if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	      report_addresses(header, m, mark);
+#endif
+	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+		    (char *)header, m, &source_addr, &dst_addr, if_index);
+	  daemon->metrics[METRIC_DNS_AUTH_ANSWERED]++;
+	}
+    }
+#endif
+  else
+    {
+      int ad_reqd = do_bit;
+      /* RFC 6840 5.7 */
+      if (header->hb4 & HB4_AD)
+	ad_reqd = 1;
+      
+      m = answer_request(header, ((char *) header) + udp_size, (size_t)n, 
+			 dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader);
+      
+      if (m >= 1)
+	{
+#ifdef HAVE_DUMPFILE
+	  dump_packet(DUMP_REPLY, daemon->packet, m, NULL, &source_addr);
+#endif
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+	  if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	    report_addresses(header, m, mark);
+#endif
+	  send_from(listen->fd, option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND),
+		    (char *)header, m, &source_addr, &dst_addr, if_index);
+	  daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+	}
+      else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
+			     header, (size_t)n,  ((char *) header) + udp_size, now, NULL, ad_reqd, do_bit))
+	daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]++;
+      else
+	daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]++;
+    }
+}
+
+/* Send query in packet, qsize to a server determined by first,last,start and
+   get the reply. return reply size. */
+static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,  size_t qsize,
+			int have_mark, unsigned int mark, struct server **servp)
+{
+  int firstsendto = -1;
+  u16 *length = (u16 *)packet;
+  unsigned char *payload = &packet[2];
+  struct dns_header *header = (struct dns_header *)payload;
+  unsigned char c1, c2;
+  unsigned char hash[HASH_SIZE];
+  unsigned int rsize;
+  
+  (void)mark;
+  (void)have_mark;
+
+  memcpy(hash, hash_questions(header, (unsigned int)qsize, daemon->namebuff), HASH_SIZE);
+  
+  while (1) 
+    {
+      int data_sent = 0;
+      struct server *serv;
+      
+      if (firstsendto == -1)
+	firstsendto = start;
+      else
+	{
+	  start++;
+	  
+	  if (start == last)
+	    start = first;
+	  
+	  if (start == firstsendto)
+	    break;
+	}
+      
+      serv = daemon->serverarray[start];
+      
+    retry:
+      *length = htons(qsize);
+      
+      if (serv->tcpfd == -1)
+	{
+	  if ((serv->tcpfd = socket(serv->addr.sa.sa_family, SOCK_STREAM, 0)) == -1)
+	    continue;
+	  
+#ifdef HAVE_CONNTRACK
+	  /* Copy connection mark of incoming query to outgoing connection. */
+	  if (have_mark)
+	    setsockopt(serv->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+#endif			  
+	  
+	  if ((!local_bind(serv->tcpfd,  &serv->source_addr, serv->interface, 0, 1)))
+	    {
+	      close(serv->tcpfd);
+	      serv->tcpfd = -1;
+	      continue;
+	    }
+	  
+#ifdef MSG_FASTOPEN
+	  server_send(serv, serv->tcpfd, packet, qsize + sizeof(u16), MSG_FASTOPEN);
+	  
+	  if (errno == 0)
+	    data_sent = 1;
+#endif
+	  
+	  if (!data_sent && connect(serv->tcpfd, &serv->addr.sa, sa_len(&serv->addr)) == -1)
+	    {
+	      close(serv->tcpfd);
+	      serv->tcpfd = -1;
+	      continue;
+	    }
+	  
+	  daemon->serverarray[first]->last_server = start;
+	  serv->flags &= ~SERV_GOT_TCP;
+	}
+      
+      if ((!data_sent && !read_write(serv->tcpfd, packet, qsize + sizeof(u16), 0)) ||
+	  !read_write(serv->tcpfd, &c1, 1, 1) ||
+	  !read_write(serv->tcpfd, &c2, 1, 1) ||
+	  !read_write(serv->tcpfd, payload, (rsize = (c1 << 8) | c2), 1))
+	{
+	  close(serv->tcpfd);
+	  serv->tcpfd = -1;
+	  /* We get data then EOF, reopen connection to same server,
+	     else try next. This avoids DoS from a server which accepts
+	     connections and then closes them. */
+	  if (serv->flags & SERV_GOT_TCP)
+	    goto retry;
+	  else
+	    continue;
+	}
+
+      /* If the hash of the question section doesn't match the crc we sent, then
+	 someone might be attempting to insert bogus values into the cache by 
+	 sending replies containing questions and bogus answers. 
+	 Try another server, or give up */
+      if (memcmp(hash, hash_questions(header, rsize, daemon->namebuff), HASH_SIZE) != 0)
+	continue;
+      
+      serv->flags |= SERV_GOT_TCP;
+      
+      *servp = serv;
+      return rsize;
+    }
+
+  return 0;
+}
+		  
+#ifdef HAVE_DNSSEC
+/* Recurse down the key hierarchy */
+static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
+			   int class, char *name, char *keyname, struct server *server, 
+			   int have_mark, unsigned int mark, int *keycount)
+{
+  int first, last, start, new_status;
+  unsigned char *packet = NULL;
+  struct dns_header *new_header = NULL;
+  
+  while (1)
+    {
+      size_t m;
+      int log_save;
+            
+      /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
+      if (--(*keycount) == 0)
+	new_status = STAT_ABANDONED;
+      else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
+	new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
+      else if (STAT_ISEQUAL(status, STAT_NEED_DS))
+	new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+      else 
+	new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
+					   !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
+					   NULL, NULL, NULL);
+      
+      if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY))
+	break;
+
+      /* Can't validate because we need a key/DS whose name now in keyname.
+	 Make query for same, and recurse to validate */
+      if (!packet)
+	{
+	  packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+	  new_header = (struct dns_header *)&packet[2];
+	}
+      
+      if (!packet)
+	{
+	  new_status = STAT_ABANDONED;
+	  break;
+	}
+
+      m = dnssec_generate_query(new_header, ((unsigned char *) new_header) + 65536, keyname, class, 
+				STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz);
+      
+      if ((start = dnssec_server(server, daemon->keyname, &first, &last)) == -1 ||
+	  (m = tcp_talk(first, last, start, packet, m, have_mark, mark, &server)) == 0)
+	{
+	  new_status = STAT_ABANDONED;
+	  break;
+	}
+
+      log_save = daemon->log_display_id;
+      daemon->log_display_id = ++daemon->log_id;
+      
+      log_query_mysockaddr(F_NOEXTRA | F_DNSSEC, keyname, &server->addr,
+			   querystr("dnssec-query", STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? T_DNSKEY : T_DS));
+            
+      new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
+
+      daemon->log_display_id = log_save;
+      
+      if (!STAT_ISEQUAL(new_status, STAT_OK))
+	break;
+    }
+    
+  if (packet)
+    free(packet);
+    
+  return new_status;
+}
+#endif
+
+
+/* The daemon forks before calling this: it should deal with one connection,
+   blocking as necessary, and then return. Note, need to be a bit careful
+   about resources for debug mode, when the fork is suppressed: that's
+   done by the caller. */
+unsigned char *tcp_request(int confd, time_t now,
+			   union mysockaddr *local_addr, struct in_addr netmask, int auth_dns)
+{
+  size_t size = 0;
+  int norebind;
+#ifdef HAVE_CONNTRACK
+  int is_single_query = 0, allowed = 1;
+#endif
+#ifdef HAVE_AUTH
+  int local_auth = 0;
+#endif
+  int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
+  int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+  size_t m;
+  unsigned short qtype;
+  unsigned int gotname;
+  /* Max TCP packet + slop + size */
+  unsigned char *packet = whine_malloc(65536 + MAXDNAME + RRFIXEDSZ + sizeof(u16));
+  unsigned char *payload = &packet[2];
+  unsigned char c1, c2;
+  /* largest field in header is 16-bits, so this is still sufficiently aligned */
+  struct dns_header *header = (struct dns_header *)payload;
+  u16 *length = (u16 *)packet;
+  struct server *serv;
+  struct in_addr dst_addr_4;
+  union mysockaddr peer_addr;
+  socklen_t peer_len = sizeof(union mysockaddr);
+  int query_count = 0;
+  unsigned char *pheader;
+  unsigned int mark = 0;
+  int have_mark = 0;
+  int first, last;
+  unsigned int flags = 0;
+    
+  if (getpeername(confd, (struct sockaddr *)&peer_addr, &peer_len) == -1)
+    return packet;
+
+#ifdef HAVE_CONNTRACK
+  /* Get connection mark of incoming query to set on outgoing connections. */
+  if (option_bool(OPT_CONNTRACK) || option_bool(OPT_CMARK_ALST_EN))
+    {
+      union all_addr local;
+		      
+      if (local_addr->sa.sa_family == AF_INET6)
+	local.addr6 = local_addr->in6.sin6_addr;
+      else
+	local.addr4 = local_addr->in.sin_addr;
+      
+      have_mark = get_incoming_mark(&peer_addr, &local, 1, &mark);
+    }
+#endif	
+
+  /* We can be configured to only accept queries from at-most-one-hop-away addresses. */
+  if (option_bool(OPT_LOCAL_SERVICE))
+    {
+      struct addrlist *addr;
+
+      if (peer_addr.sa.sa_family == AF_INET6) 
+	{
+	  for (addr = daemon->interface_addrs; addr; addr = addr->next)
+	    if ((addr->flags & ADDRLIST_IPV6) &&
+		is_same_net6(&addr->addr.addr6, &peer_addr.in6.sin6_addr, addr->prefixlen))
+	      break;
+	}
+      else
+	{
+	  struct in_addr netmask;
+	  for (addr = daemon->interface_addrs; addr; addr = addr->next)
+	    {
+	      netmask.s_addr = htonl(~(in_addr_t)0 << (32 - addr->prefixlen));
+	      if (!(addr->flags & ADDRLIST_IPV6) && 
+		  is_same_net(addr->addr.addr4, peer_addr.in.sin_addr, netmask))
+		break;
+	    }
+	}
+      if (!addr)
+	{
+	  my_syslog(LOG_WARNING, _("Ignoring query from non-local network"));
+	  return packet;
+	}
+    }
+
+  while (1)
+    {
+      int ede = EDE_UNSET;
+
+      if (query_count == TCP_MAX_QUERIES ||
+	  !packet ||
+	  !read_write(confd, &c1, 1, 1) || !read_write(confd, &c2, 1, 1) ||
+	  !(size = c1 << 8 | c2) ||
+	  !read_write(confd, payload, size, 1))
+       	return packet; 
+  
+      if (size < (int)sizeof(struct dns_header))
+	continue;
+
+      /* Clear buffer beyond request to avoid risk of
+	 information disclosure. */
+      memset(payload + size, 0, 65536 - size);
+      
+      query_count++;
+
+      /* log_query gets called indirectly all over the place, so 
+	 pass these in global variables - sorry. */
+      daemon->log_display_id = ++daemon->log_id;
+      daemon->log_source_addr = &peer_addr;
+      
+      /* save state of "cd" flag in query */
+      if ((checking_disabled = header->hb4 & HB4_CD))
+	no_cache_dnssec = 1;
+       
+      if ((gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
+	{
+#ifdef HAVE_AUTH
+	  struct auth_zone *zone;
+#endif
+	  char *types = querystr(auth_dns ? "auth" : "query", qtype);
+	  
+	  log_query_mysockaddr(F_QUERY | F_FORWARD, daemon->namebuff,
+			       &peer_addr, types);
+	  
+#ifdef HAVE_CONNTRACK
+	  is_single_query = 1;
+#endif
+	  
+#ifdef HAVE_AUTH
+	  /* find queries for zones we're authoritative for, and answer them directly */
+	  if (!auth_dns && !option_bool(OPT_LOCALISE))
+	    for (zone = daemon->auth_zones; zone; zone = zone->next)
+	      if (in_zone(zone, daemon->namebuff, NULL))
+		{
+		  auth_dns = 1;
+		  local_auth = 1;
+		  break;
+		}
+#endif
+	}
+      
+      norebind = domain_no_rebind(daemon->namebuff);
+      
+      if (local_addr->sa.sa_family == AF_INET)
+	dst_addr_4 = local_addr->in.sin_addr;
+      else
+	dst_addr_4.s_addr = 0;
+      
+      do_bit = 0;
+
+      if (find_pseudoheader(header, (size_t)size, NULL, &pheader, NULL, NULL))
+	{ 
+	  unsigned short flags;
+	  
+	  have_pseudoheader = 1;
+	  pheader += 4; /* udp_size, ext_rcode */
+	  GETSHORT(flags, pheader);
+      
+	  if (flags & 0x8000)
+	    do_bit = 1; /* do bit */ 
+	}
+      
+#ifdef HAVE_CONNTRACK
+#ifdef HAVE_AUTH
+      if (!auth_dns || local_auth)
+#endif
+	if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	  allowed = is_query_allowed_for_mark((u32)mark, is_single_query ? daemon->namebuff : NULL);
+#endif
+
+      if (0);
+#ifdef HAVE_CONNTRACK
+      else if (!allowed)
+	{
+	  u16 swap = htons(EDE_BLOCKED);
+
+	  m = answer_disallowed(header, size, (u32)mark, is_single_query ? daemon->namebuff : NULL);
+	  
+	  if (have_pseudoheader && m != 0)
+	    m = add_pseudoheader(header,  m, ((unsigned char *) header) + 65536, daemon->edns_pktsz,
+				 EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+	}
+#endif
+#ifdef HAVE_AUTH
+      else if (auth_dns)
+	m = answer_auth(header, ((char *) header) + 65536, (size_t)size, now, &peer_addr, 
+			local_auth, do_bit, have_pseudoheader);
+#endif
+      else
+	{
+	   int ad_reqd = do_bit;
+	   /* RFC 6840 5.7 */
+	   if (header->hb4 & HB4_AD)
+	     ad_reqd = 1;
+	   
+	   /* m > 0 if answered from cache */
+	   m = answer_request(header, ((char *) header) + 65536, (size_t)size, 
+			      dst_addr_4, netmask, now, ad_reqd, do_bit, have_pseudoheader);
+	  
+	  /* Do this by steam now we're not in the select() loop */
+	  check_log_writer(1); 
+	  
+	  if (m == 0)
+	    {
+	      struct server *master;
+	      int start;
+
+	      if (lookup_domain(daemon->namebuff, gotname, &first, &last))
+		flags = is_local_answer(now, first, daemon->namebuff);
+	      else
+		{
+		  /* No configured servers */
+		  ede = EDE_NOT_READY;
+		  flags = 0;
+		}
+	      
+	      /* don't forward A or AAAA queries for simple names, except the empty name */
+	      if (!flags &&
+		  option_bool(OPT_NODOTS_LOCAL) &&
+		  (gotname & (F_IPV4 | F_IPV6)) &&
+		  !strchr(daemon->namebuff, '.') &&
+		  strlen(daemon->namebuff) != 0)
+		flags = check_for_local_domain(daemon->namebuff, now) ? F_NOERR : F_NXDOMAIN;
+		
+	      if (!flags && ede != EDE_NOT_READY)
+		{
+		  master = daemon->serverarray[first];
+		  
+		  if (option_bool(OPT_ORDER) || master->last_server == -1)
+		    start = first;
+		  else
+		    start = master->last_server;
+		  
+		  size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable);
+		  
+#ifdef HAVE_DNSSEC
+		  if (option_bool(OPT_DNSSEC_VALID) && (master->flags & SERV_DO_DNSSEC))
+		    {
+		      size = add_do_bit(header, size, ((unsigned char *) header) + 65536);
+		      
+		      /* For debugging, set Checking Disabled, otherwise, have the upstream check too,
+			 this allows it to select auth servers when one is returning bad data. */
+		      if (option_bool(OPT_DNSSEC_DEBUG))
+			header->hb4 |= HB4_CD;
+		    }
+#endif
+		  
+		  /* Check if we added a pheader on forwarding - may need to
+		     strip it from the reply. */
+		  if (!have_pseudoheader && find_pseudoheader(header, size, NULL, NULL, NULL, NULL))
+		    added_pheader = 1;
+		  
+		  /* Loop round available servers until we succeed in connecting to one. */
+		  if ((m = tcp_talk(first, last, start, packet, size, have_mark, mark, &serv)) == 0)
+		    {
+		      ede = EDE_NETERR;
+		      break;
+		    }
+		  
+		  /* get query name again for logging - may have been overwritten */
+		  if (!(gotname = extract_request(header, (unsigned int)size, daemon->namebuff, &qtype)))
+		    strcpy(daemon->namebuff, "query");
+		  log_query_mysockaddr(F_SERVER | F_FORWARD, daemon->namebuff, &serv->addr, NULL);
+		  
+#ifdef HAVE_DNSSEC
+		  if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
+		    {
+		      int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
+		      int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, 
+						   serv, have_mark, mark, &keycount);
+		      char *result, *domain = "result";
+		      
+		      union all_addr a;
+		      a.log.ede = ede = errflags_to_ede(status);
+		      
+		      if (STAT_ISEQUAL(status, STAT_ABANDONED))
+			{
+			  result = "ABANDONED";
+			  status = STAT_BOGUS;
+			}
+		      else
+			result = (STAT_ISEQUAL(status, STAT_SECURE) ? "SECURE" : (STAT_ISEQUAL(status, STAT_INSECURE) ? "INSECURE" : "BOGUS"));
+		      
+		      if (STAT_ISEQUAL(status, STAT_SECURE))
+			cache_secure = 1;
+		      else if (STAT_ISEQUAL(status, STAT_BOGUS))
+			{
+			  no_cache_dnssec = 1;
+			  bogusanswer = 1;
+			  
+			  if (extract_request(header, m, daemon->namebuff, NULL))
+			    domain = daemon->namebuff;
+			}
+		      
+		      log_query(F_SECSTAT, domain, &a, result);
+		    }
+#endif
+		  
+		  /* restore CD bit to the value in the query */
+		  if (checking_disabled)
+		    header->hb4 |= HB4_CD;
+		  else
+		    header->hb4 &= ~HB4_CD;
+		  
+		  /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+		     since the cache is ignorant of such things. */
+		  if (!cacheable)
+		    no_cache_dnssec = 1;
+		  
+		  m = process_reply(header, now, serv, (unsigned int)m, 
+				    option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
+				    ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr, ((unsigned char *)header) + 65536, ede); 
+		}
+	    }
+	}
+	
+      /* In case of local answer or no connections made. */
+      if (m == 0)
+	{
+	  if (!(m = make_local_answer(flags, gotname, size, header, daemon->namebuff,
+				      ((char *) header) + 65536, first, last, ede)))
+	    break;
+	  
+	  if (have_pseudoheader)
+	    {
+	      u16 swap = htons((u16)ede);
+
+	       if (ede != EDE_UNSET)
+		 m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, EDNS0_OPTION_EDE, (unsigned char *)&swap, 2, do_bit, 0);
+	       else
+		 m = add_pseudoheader(header, m, ((unsigned char *) header) + 65536, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+	    }
+	}
+      
+      check_log_writer(1);
+      
+      *length = htons(m);
+      
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+#ifdef HAVE_AUTH
+      if (!auth_dns || local_auth)
+#endif
+	if (option_bool(OPT_CMARK_ALST_EN) && have_mark && ((u32)mark & daemon->allowlist_mask))
+	  report_addresses(header, m, mark);
+#endif
+      if (!read_write(confd, packet, m + sizeof(u16), 0))
+	break;
+    }
+  
+  return packet;
+}
+
+/* return a UDP socket bound to a random port, have to cope with straying into
+   occupied port nos and reserved ones. */
+static int random_sock(struct server *s)
+{
+  int fd;
+
+  if ((fd = socket(s->source_addr.sa.sa_family, SOCK_DGRAM, 0)) != -1)
+    {
+      if (local_bind(fd, &s->source_addr, s->interface, s->ifindex, 0))
+	return fd;
+
+      if (s->interface[0] == 0)
+	(void)prettyprint_addr(&s->source_addr, daemon->namebuff);
+      else
+	strcpy(daemon->namebuff, s->interface);
+
+      my_syslog(LOG_ERR, _("failed to bind server socket to %s: %s"),
+		daemon->namebuff, strerror(errno));
+      close(fd);
+    }
+  
+  return -1;
+}
+
+/* compare source addresses and interface, serv2 can be null. */
+static int server_isequal(const struct server *serv1,
+			 const struct server *serv2)
+{
+  return (serv2 &&
+    serv2->ifindex == serv1->ifindex &&
+    sockaddr_isequal(&serv2->source_addr, &serv1->source_addr) &&
+    strncmp(serv2->interface, serv1->interface, IF_NAMESIZE) == 0);
+}
+
+/* fdlp points to chain of randomfds already in use by transaction.
+   If there's already a suitable one, return it, else allocate a 
+   new one and add it to the list. 
+
+   Not leaking any resources in the face of allocation failures
+   is rather convoluted here.
+   
+   Note that rfd->serv may be NULL, when a server goes away.
+*/
+int allocate_rfd(struct randfd_list **fdlp, struct server *serv)
+{
+  static int finger = 0;
+  int i, j = 0;
+  struct randfd_list *rfl;
+  struct randfd *rfd = NULL;
+  int fd = 0;
+  
+  /* If server has a pre-allocated fd, use that. */
+  if (serv->sfd)
+    return serv->sfd->fd;
+  
+  /* existing suitable random port socket linked to this transaction? */
+  for (rfl = *fdlp; rfl; rfl = rfl->next)
+    if (server_isequal(serv, rfl->rfd->serv))
+      return rfl->rfd->fd;
+
+  /* No. need new link. */
+  if ((rfl = daemon->rfl_spare))
+    daemon->rfl_spare = rfl->next;
+  else if (!(rfl = whine_malloc(sizeof(struct randfd_list))))
+    return -1;
+   
+  /* limit the number of sockets we have open to avoid starvation of 
+     (eg) TFTP. Once we have a reasonable number, randomness should be OK */
+  for (i = 0; i < daemon->numrrand; i++)
+    if (daemon->randomsocks[i].refcount == 0)
+      {
+	if ((fd = random_sock(serv)) != -1)
+    	  {
+	    rfd = &daemon->randomsocks[i];
+	    rfd->serv = serv;
+	    rfd->fd = fd;
+	    rfd->refcount = 1;
+	  }
+	break;
+      }
+  
+  /* No free ones or cannot get new socket, grab an existing one */
+  if (!rfd)
+    for (j = 0; j < daemon->numrrand; j++)
+      {
+	i = (j + finger) % daemon->numrrand;
+	if (daemon->randomsocks[i].refcount != 0 &&
+	    server_isequal(serv, daemon->randomsocks[i].serv) &&
+	    daemon->randomsocks[i].refcount != 0xfffe)
+	  {
+	    finger = i + 1;
+	    rfd = &daemon->randomsocks[i];
+	    rfd->refcount++;
+	    break;
+	  }
+      }
+
+  if (j == daemon->numrrand)
+    {
+      struct randfd_list *rfl_poll;
+
+      /* there are no free slots, and non with the same parameters we can piggy-back on. 
+	 We're going to have to allocate a new temporary record, distinguished by
+	 refcount == 0xffff. This will exist in the frec randfd list, never be shared,
+	 and be freed when no longer in use. It will also be held on 
+	 the daemon->rfl_poll list so the poll system can find it. */
+
+      if ((rfl_poll = daemon->rfl_spare))
+	daemon->rfl_spare = rfl_poll->next;
+      else
+	rfl_poll = whine_malloc(sizeof(struct randfd_list));
+      
+      if (!rfl_poll ||
+	  !(rfd = whine_malloc(sizeof(struct randfd))) ||
+	  (fd = random_sock(serv)) == -1)
+	{
+	  
+	  /* Don't leak anything we may already have */
+	  rfl->next = daemon->rfl_spare;
+	  daemon->rfl_spare = rfl;
+
+	  if (rfl_poll)
+	    {
+	      rfl_poll->next = daemon->rfl_spare;
+	      daemon->rfl_spare = rfl_poll;
+	    }
+	  
+	  if (rfd)
+	    free(rfd);
+	  
+	  return -1; /* doom */
+	}
+
+      /* Note rfd->serv not set here, since it's not reused */
+      rfd->fd = fd;
+      rfd->refcount = 0xffff; /* marker for temp record */
+
+      rfl_poll->rfd = rfd;
+      rfl_poll->next = daemon->rfl_poll;
+      daemon->rfl_poll = rfl_poll;
+    }
+  
+  rfl->rfd = rfd;
+  rfl->next = *fdlp;
+  *fdlp = rfl;
+  
+  return rfl->rfd->fd;
+}
+
+void free_rfds(struct randfd_list **fdlp)
+{
+  struct randfd_list *tmp, *rfl, *poll, *next, **up;
+  
+  for (rfl = *fdlp; rfl; rfl = tmp)
+    {
+      if (rfl->rfd->refcount == 0xffff || --(rfl->rfd->refcount) == 0)
+	close(rfl->rfd->fd);
+
+      /* temporary overflow record */
+      if (rfl->rfd->refcount == 0xffff)
+	{
+	  free(rfl->rfd);
+	  
+	  /* go through the link of all these by steam to delete.
+	     This list is expected to be almost always empty. */
+	  for (poll = daemon->rfl_poll, up = &daemon->rfl_poll; poll; poll = next)
+	    {
+	      next = poll->next;
+	      
+	      if (poll->rfd == rfl->rfd)
+		{
+		  *up = poll->next;
+		  poll->next = daemon->rfl_spare;
+		  daemon->rfl_spare = poll;
+		}
+	      else
+		up = &poll->next;
+	    }
+	}
+
+      tmp = rfl->next;
+      rfl->next = daemon->rfl_spare;
+      daemon->rfl_spare = rfl;
+    }
+
+  *fdlp = NULL;
+}
+
+static void free_frec(struct frec *f)
+{
+  struct frec_src *last;
+  
+  /* add back to freelist if not the record builtin to every frec. */
+  for (last = f->frec_src.next; last && last->next; last = last->next) ;
+  if (last)
+    {
+      last->next = daemon->free_frec_src;
+      daemon->free_frec_src = f->frec_src.next;
+    }
+    
+  f->frec_src.next = NULL;    
+  free_rfds(&f->rfds);
+  f->sentto = NULL;
+  f->flags = 0;
+
+#ifdef HAVE_DNSSEC
+  if (f->stash)
+    {
+      blockdata_free(f->stash);
+      f->stash = NULL;
+    }
+
+  /* Anything we're waiting on is pointless now, too */
+  if (f->blocking_query)
+    {
+      struct frec *n, **up;
+
+      /* unlink outselves from the blocking query's dependents list. */
+      for (n = f->blocking_query->dependent, up = &f->blocking_query->dependent; n; n = n->next_dependent)
+	if (n == f)
+	  {
+	    *up = n->next_dependent;
+	    break;
+	  }
+	else
+	  up = &n->next_dependent;
+
+      /* If we were the only/last dependent, free the blocking query too. */
+      if (!f->blocking_query->dependent)
+	free_frec(f->blocking_query);
+    }
+  
+  f->blocking_query = NULL;
+  f->dependent = NULL;
+  f->next_dependent = NULL;
+#endif
+}
+
+
+
+/* Impose an absolute
+   limit of 4*TIMEOUT before we wipe things (for random sockets).
+   If force is set, always return a result, even if we have
+   to allocate above the limit, and don'y free any records.
+   This is set when allocating for DNSSEC to avoid cutting off
+   the branch we are sitting on. */
+static struct frec *get_new_frec(time_t now, struct server *master, int force)
+{
+  struct frec *f, *oldest, *target;
+  int count;
+  
+  /* look for free records, garbage collect old records and count number in use by our server-group. */
+  for (f = daemon->frec_list, oldest = NULL, target =  NULL, count = 0; f; f = f->next)
+    {
+      if (!f->sentto)
+	target = f;
+      else
+	{
+#ifdef HAVE_DNSSEC
+	  /* Don't free DNSSEC sub-queries here, as we may end up with
+	     dangling references to them. They'll go when their "real" query 
+	     is freed. */
+	  if (!f->dependent && !force)
+#endif
+	    {
+	      if (difftime(now, f->time) >= 4*TIMEOUT)
+		{
+		  free_frec(f);
+		  target = f;
+		}
+	      else if (!oldest || difftime(f->time, oldest->time) <= 0)
+		oldest = f;
+	    }
+	}
+      
+      if (f->sentto && ((int)difftime(now, f->time)) < TIMEOUT && server_samegroup(f->sentto, master))
+	count++;
+    }
+
+  if (!force && count >= daemon->ftabsize)
+    {
+      query_full(now, master->domain);
+      return NULL;
+    }
+  
+  if (!target && oldest && ((int)difftime(now, oldest->time)) >= TIMEOUT)
+    { 
+      /* can't find empty one, use oldest if there is one and it's older than timeout */
+      free_frec(oldest);
+      target = oldest;
+    }
+  
+  if (!target && (target = (struct frec *)whine_malloc(sizeof(struct frec))))
+    {
+      target->next = daemon->frec_list;
+      daemon->frec_list = target;
+    }
+
+  if (target)
+    target->time = now;
+
+  return target;
+}
+
+static void query_full(time_t now, char *domain)
+{
+  static time_t last_log = 0;
+  
+  if ((int)difftime(now, last_log) > 5)
+    {
+      last_log = now;
+      if (!domain || strlen(domain) == 0)
+	my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize);
+      else
+	my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize);
+    }
+}
+
+
+static struct frec *lookup_frec(unsigned short id, int fd, void *hash, int *firstp, int *lastp)
+{
+  struct frec *f;
+  struct server *s;
+  int first, last;
+  struct randfd_list *fdl;
+  
+  for(f = daemon->frec_list; f; f = f->next)
+    if (f->sentto && f->new_id == id && 
+	(memcmp(hash, f->hash, HASH_SIZE) == 0))
+      {
+	filter_servers(f->sentto->arrayposn, F_SERVER, firstp, lastp);
+
+	/* sent from random port */
+	for (fdl = f->rfds; fdl; fdl = fdl->next)
+	  if (fdl->rfd->fd == fd)
+	  return f;
+	
+	/* Sent to upstream from socket associated with a server. 
+	   Note we have to iterate over all the possible servers, since they may
+	   have different bound sockets. */
+	for (first = *firstp, last = *lastp; first != last; first++)
+	  {
+	    s = daemon->serverarray[first];
+	    if (s->sfd && s->sfd->fd == fd)
+	      return f;
+	  }
+      }
+  
+  return NULL;
+}
+
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags, unsigned int flagmask)
+{
+  struct frec *f;
+
+  for(f = daemon->frec_list; f; f = f->next)
+    if (f->sentto &&
+	(f->flags & flagmask) == flags &&
+	memcmp(hash, f->hash, HASH_SIZE) == 0)
+      return f;
+  
+  return NULL;
+}
+
+/* Send query packet again, if we can. */
+void resend_query()
+{
+  if (daemon->srv_save)
+    server_send(daemon->srv_save, daemon->fd_save,
+		daemon->packet, daemon->packet_len, 0);
+}
+
+/* A server record is going away, remove references to it */
+void server_gone(struct server *server)
+{
+  struct frec *f;
+  int i;
+  
+  for (f = daemon->frec_list; f; f = f->next)
+    if (f->sentto && f->sentto == server)
+      free_frec(f);
+
+  /* If any random socket refers to this server, NULL the reference.
+     No more references to the socket will be created in the future. */
+  for (i = 0; i < daemon->numrrand; i++)
+    if (daemon->randomsocks[i].refcount != 0 && daemon->randomsocks[i].serv == server)
+      daemon->randomsocks[i].serv = NULL;
+  
+  if (daemon->srv_save == server)
+    daemon->srv_save = NULL;
+}
+
+/* return unique random ids. */
+static unsigned short get_id(void)
+{
+  unsigned short ret = 0;
+  struct frec *f;
+  
+  while (1)
+    {
+      ret = rand16();
+
+      /* ensure id is unique. */
+      for (f = daemon->frec_list; f; f = f->next)
+	if (f->sentto && f->new_id == ret)
+	  break;
+
+      if (!f)
+	return ret;
+    }
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c b/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c
new file mode 100755
index 0000000..f41023b
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/hash-questions.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2012-2020 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/>.
+*/
+
+
+/* Hash the question section. This is used to safely detect query 
+   retransmission and to detect answers to questions we didn't ask, which 
+   might be poisoning attacks. Note that we decode the name rather 
+   than CRC the raw bytes, since replies might be compressed differently. 
+   We ignore case in the names for the same reason. 
+
+   The hash used is SHA-256. If we're building with DNSSEC support,
+   we use the Nettle cypto library. If not, we prefer not to
+   add a dependency on Nettle, and use a stand-alone implementaion. 
+*/
+
+#include "dnsmasq.h"
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_CRYPTOHASH)
+
+static const struct nettle_hash *hash;
+static void *ctx;
+static unsigned char *digest;
+
+void hash_questions_init(void)
+{
+  if (!(hash = hash_find("sha256")))
+    die(_("Failed to create SHA-256 hash object"), NULL, EC_MISC);
+
+  ctx = safe_malloc(hash->context_size);
+  digest = safe_malloc(hash->digest_size);
+}
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+  int q;
+  unsigned char *p = (unsigned char *)(header+1);
+
+  hash->init(ctx);
+
+  for (q = ntohs(header->qdcount); q != 0; q--) 
+    {
+      char *cp, c;
+
+      if (!extract_name(header, plen, &p, name, 1, 4))
+	break; /* bad packet */
+
+      for (cp = name; (c = *cp); cp++)
+	 if (c >= 'A' && c <= 'Z')
+	   *cp += 'a' - 'A';
+
+      hash->update(ctx, cp - name, (unsigned char *)name);
+      /* CRC the class and type as well */
+      hash->update(ctx, 4, p);
+
+      p += 4;
+      if (!CHECK_LEN(header, p, plen, 0))
+	break; /* bad packet */
+    }
+  
+  hash->digest(ctx, hash->digest_size, digest);
+  return digest;
+}
+
+#else /* HAVE_DNSSEC  || HAVE_CRYPTOHASH */
+
+#define SHA256_BLOCK_SIZE 32            /* SHA256 outputs a 32 byte digest */
+typedef unsigned char BYTE;             /* 8-bit byte */
+typedef unsigned int  WORD;             /* 32-bit word, change to "long" for 16-bit machines */
+
+typedef struct {
+  BYTE data[64];
+  WORD datalen;
+  unsigned long long bitlen;
+  WORD state[8];
+} SHA256_CTX;
+
+static void sha256_init(SHA256_CTX *ctx);
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
+
+void hash_questions_init(void)
+{
+}
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+  int q;
+  unsigned char *p = (unsigned char *)(header+1);
+  SHA256_CTX ctx;
+  static BYTE digest[SHA256_BLOCK_SIZE];
+  
+  sha256_init(&ctx);
+    
+  for (q = ntohs(header->qdcount); q != 0; q--) 
+    {
+      char *cp, c;
+
+      if (!extract_name(header, plen, &p, name, 1, 4))
+	break; /* bad packet */
+
+      for (cp = name; (c = *cp); cp++)
+	 if (c >= 'A' && c <= 'Z')
+	   *cp += 'a' - 'A';
+
+      sha256_update(&ctx, (BYTE *)name, cp - name);
+      /* CRC the class and type as well */
+      sha256_update(&ctx, (BYTE *)p, 4);
+
+      p += 4;
+      if (!CHECK_LEN(header, p, plen, 0))
+	break; /* bad packet */
+    }
+  
+  sha256_final(&ctx, digest);
+  return (unsigned char *)digest;
+}
+
+/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms
+   and was written by Brad Conte (brad@bradconte.com), to whom all credit is given.
+
+   This code is in the public domain, and the copyright notice at the head of this 
+   file does not apply to it.
+*/
+
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+/**************************** VARIABLES *****************************/
+static const WORD k[64] = {
+			   0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+			   0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+			   0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+			   0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+			   0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+			   0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+			   0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+			   0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+static void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
+{
+  WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+  
+  for (i = 0, j = 0; i < 16; ++i, j += 4)
+    m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
+  for ( ; i < 64; ++i)
+    m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
+
+  a = ctx->state[0];
+  b = ctx->state[1];
+  c = ctx->state[2];
+  d = ctx->state[3];
+  e = ctx->state[4];
+  f = ctx->state[5];
+  g = ctx->state[6];
+  h = ctx->state[7];
+
+  for (i = 0; i < 64; ++i)
+    {
+      t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+      t2 = EP0(a) + MAJ(a,b,c);
+      h = g;
+      g = f;
+      f = e;
+      e = d + t1;
+      d = c;
+      c = b;
+      b = a;
+      a = t1 + t2;
+    }
+  
+  ctx->state[0] += a;
+  ctx->state[1] += b;
+  ctx->state[2] += c;
+  ctx->state[3] += d;
+  ctx->state[4] += e;
+  ctx->state[5] += f;
+  ctx->state[6] += g;
+  ctx->state[7] += h;
+}
+
+static void sha256_init(SHA256_CTX *ctx)
+{
+  ctx->datalen = 0;
+  ctx->bitlen = 0;
+  ctx->state[0] = 0x6a09e667;
+  ctx->state[1] = 0xbb67ae85;
+  ctx->state[2] = 0x3c6ef372;
+  ctx->state[3] = 0xa54ff53a;
+  ctx->state[4] = 0x510e527f;
+  ctx->state[5] = 0x9b05688c;
+  ctx->state[6] = 0x1f83d9ab;
+  ctx->state[7] = 0x5be0cd19;
+}
+
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
+{
+  WORD i;
+  
+  for (i = 0; i < len; ++i)
+    {
+      ctx->data[ctx->datalen] = data[i];
+      ctx->datalen++;
+      if (ctx->datalen == 64) {
+	sha256_transform(ctx, ctx->data);
+	ctx->bitlen += 512;
+	ctx->datalen = 0;
+      }
+    }
+}
+
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[])
+{
+  WORD i;
+  
+  i = ctx->datalen;
+
+  /* Pad whatever data is left in the buffer. */
+  if (ctx->datalen < 56)
+    {
+      ctx->data[i++] = 0x80;
+      while (i < 56)
+	ctx->data[i++] = 0x00;
+    }
+  else
+    {
+      ctx->data[i++] = 0x80;
+      while (i < 64)
+	ctx->data[i++] = 0x00;
+      sha256_transform(ctx, ctx->data);
+      memset(ctx->data, 0, 56);
+    }
+  
+  /* Append to the padding the total message's length in bits and transform. */
+  ctx->bitlen += ctx->datalen * 8;
+  ctx->data[63] = ctx->bitlen;
+  ctx->data[62] = ctx->bitlen >> 8;
+  ctx->data[61] = ctx->bitlen >> 16;
+  ctx->data[60] = ctx->bitlen >> 24;
+  ctx->data[59] = ctx->bitlen >> 32;
+  ctx->data[58] = ctx->bitlen >> 40;
+  ctx->data[57] = ctx->bitlen >> 48;
+  ctx->data[56] = ctx->bitlen >> 56;
+  sha256_transform(ctx, ctx->data);
+  
+  /* Since this implementation uses little endian byte ordering and SHA uses big endian,
+     reverse all the bytes when copying the final state to the output hash. */
+  for (i = 0; i < 4; ++i)
+    {
+      hash[i]      = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 4]  = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 8]  = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
+      hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
+    }
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c b/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c
new file mode 100755
index 0000000..02340a0
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/helper.c
@@ -0,0 +1,890 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_SCRIPT
+
+/* This file has code to fork a helper process which receives data via a pipe 
+   shared with the main process and which is responsible for calling a script when
+   DHCP leases change.
+
+   The helper process is forked before the main process drops root, so it retains root 
+   privs to pass on to the script. For this reason it tries to be paranoid about 
+   data received from the main process, in case that has been compromised. We don't
+   want the helper to give an attacker root. In particular, the script to be run is
+   not settable via the pipe, once the fork has taken place it is not alterable by the 
+   main process.
+*/
+
+static void my_setenv(const char *name, const char *value, int *error);
+static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end,  char *env, int *err);
+
+#ifdef HAVE_LUASCRIPT
+#define LUA_COMPAT_ALL
+#include <lua.h>  
+#include <lualib.h>  
+#include <lauxlib.h>  
+
+#ifndef lua_open
+#define lua_open()     luaL_newstate()
+#endif
+
+lua_State *lua;
+
+static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field);
+#endif
+
+
+struct script_data
+{
+  int flags;
+  int action, hwaddr_len, hwaddr_type;
+  int clid_len, hostname_len, ed_len;
+  struct in_addr addr, giaddr;
+  unsigned int remaining_time;
+#ifdef HAVE_BROKEN_RTC
+  unsigned int length;
+#else
+  time_t expires;
+#endif
+#ifdef HAVE_TFTP
+  off_t file_len;
+#endif
+  struct in6_addr addr6;
+#ifdef HAVE_DHCP6
+  int vendorclass_count;
+  unsigned int iaid;
+#endif
+  unsigned char hwaddr[DHCP_CHADDR_MAX];
+  char interface[IF_NAMESIZE];
+};
+
+static struct script_data *buf = NULL;
+static size_t bytes_in_buf = 0, buf_size = 0;
+
+int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd)
+{
+  pid_t pid;
+  int i, pipefd[2];
+  struct sigaction sigact;
+  unsigned char *alloc_buff = NULL;
+  
+  /* create the pipe through which the main program sends us commands,
+     then fork our process. */
+  if (pipe(pipefd) == -1 || !fix_fd(pipefd[1]) || (pid = fork()) == -1)
+    {
+      send_event(err_fd, EVENT_PIPE_ERR, errno, NULL);
+      _exit(0);
+    }
+
+  if (pid != 0)
+    {
+      close(pipefd[0]); /* close reader side */
+      return pipefd[1];
+    }
+
+  /* ignore SIGTERM and SIGINT, so that we can clean up when the main process gets hit
+     and SIGALRM so that we can use sleep() */
+  sigact.sa_handler = SIG_IGN;
+  sigact.sa_flags = 0;
+  sigemptyset(&sigact.sa_mask);
+  sigaction(SIGTERM, &sigact, NULL);
+  sigaction(SIGALRM, &sigact, NULL);
+  sigaction(SIGINT, &sigact, NULL);
+
+  if (!option_bool(OPT_DEBUG) && uid != 0)
+    {
+      gid_t dummy;
+      if (setgroups(0, &dummy) == -1 || 
+	  setgid(gid) == -1 || 
+	  setuid(uid) == -1)
+	{
+	  if (option_bool(OPT_NO_FORK))
+	    /* send error to daemon process if no-fork */
+	    send_event(event_fd, EVENT_USER_ERR, errno, daemon->scriptuser);
+	  else
+	    {
+	      /* kill daemon */
+	      send_event(event_fd, EVENT_DIE, 0, NULL);
+	      /* return error */
+	      send_event(err_fd, EVENT_USER_ERR, errno, daemon->scriptuser);
+	    }
+	  _exit(0);
+	}
+    }
+
+  /* close all the sockets etc, we don't need them here. 
+     Don't close err_fd, in case the lua-init fails.
+     Note that we have to do this before lua init
+     so we don't close any lua fds. */
+  close_fds(max_fd, pipefd[0], event_fd, err_fd);
+  
+#ifdef HAVE_LUASCRIPT
+  if (daemon->luascript)
+    {
+      const char *lua_err = NULL;
+      lua = lua_open();
+      luaL_openlibs(lua);
+      
+      /* get Lua to load our script file */
+      if (luaL_dofile(lua, daemon->luascript) != 0)
+	lua_err = lua_tostring(lua, -1);
+      else
+	{
+	  lua_getglobal(lua, "lease");
+	  if (lua_type(lua, -1) != LUA_TFUNCTION) 
+	    lua_err = _("lease() function missing in Lua script");
+	}
+      
+      if (lua_err)
+	{
+	  if (option_bool(OPT_NO_FORK) || option_bool(OPT_DEBUG))
+	    /* send error to daemon process if no-fork */
+	    send_event(event_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
+	  else
+	    {
+	      /* kill daemon */
+	      send_event(event_fd, EVENT_DIE, 0, NULL);
+	      /* return error */
+	      send_event(err_fd, EVENT_LUA_ERR, 0, (char *)lua_err);
+	    }
+	  _exit(0);
+	}
+      
+      lua_pop(lua, 1);  /* remove nil from stack */
+      lua_getglobal(lua, "init");
+      if (lua_type(lua, -1) == LUA_TFUNCTION)
+	lua_call(lua, 0, 0);
+      else
+	lua_pop(lua, 1);  /* remove nil from stack */	
+    }
+#endif
+
+  /* All init done, close our copy of the error pipe, so that main process can return */
+  if (err_fd != -1)
+    close(err_fd);
+    
+  /* loop here */
+  while(1)
+    {
+      struct script_data data;
+      char *p, *action_str, *hostname = NULL, *domain = NULL;
+      unsigned char *buf = (unsigned char *)daemon->namebuff;
+      unsigned char *end, *extradata;
+      int is6, err = 0;
+      int pipeout[2];
+
+      /* Free rarely-allocated memory from previous iteration. */
+      if (alloc_buff)
+	{
+	  free(alloc_buff);
+	  alloc_buff = NULL;
+	}
+      
+      /* we read zero bytes when pipe closed: this is our signal to exit */ 
+      if (!read_write(pipefd[0], (unsigned char *)&data, sizeof(data), 1))
+	{
+#ifdef HAVE_LUASCRIPT
+	  if (daemon->luascript)
+	    {
+	      lua_getglobal(lua, "shutdown");
+	      if (lua_type(lua, -1) == LUA_TFUNCTION)
+		lua_call(lua, 0, 0);
+	    }
+#endif
+	  _exit(0);
+	}
+ 
+      is6 = !!(data.flags & (LEASE_TA | LEASE_NA));
+      
+      if (data.action == ACTION_DEL)
+	action_str = "del";
+      else if (data.action == ACTION_ADD)
+	action_str = "add";
+      else if (data.action == ACTION_OLD || data.action == ACTION_OLD_HOSTNAME)
+	action_str = "old";
+      else if (data.action == ACTION_TFTP)
+	{
+	  action_str = "tftp";
+	  is6 = (data.flags != AF_INET);
+	}
+      else if (data.action == ACTION_ARP)
+	{
+	  action_str = "arp-add";
+	  is6 = (data.flags != AF_INET);
+	}
+       else if (data.action == ACTION_ARP_DEL)
+	{
+	  action_str = "arp-del";
+	  is6 = (data.flags != AF_INET);
+	  data.action = ACTION_ARP;
+	}
+       else 
+	continue;
+      	
+      /* stringify MAC into dhcp_buff */
+      p = daemon->dhcp_buff;
+      if (data.hwaddr_type != ARPHRD_ETHER || data.hwaddr_len == 0) 
+	p += sprintf(p, "%.2x-", data.hwaddr_type);
+      for (i = 0; (i < data.hwaddr_len) && (i < DHCP_CHADDR_MAX); i++)
+	{
+	  p += sprintf(p, "%.2x", data.hwaddr[i]);
+	  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 && 
+	  !(alloc_buff = buf = malloc(data.hostname_len + data.ed_len + data.clid_len)))
+	continue;
+      
+      if (!read_write(pipefd[0], buf, 
+		      data.hostname_len + data.ed_len + data.clid_len, 1))
+	continue;
+
+      /* CLID into packet */
+      for (p = daemon->packet, i = 0; i < data.clid_len; i++)
+	{
+	  p += sprintf(p, "%.2x", buf[i]);
+	  if (i != data.clid_len - 1) 
+	      p += sprintf(p, ":");
+	}
+
+#ifdef HAVE_DHCP6
+      if (is6)
+	{
+	  /* or IAID and server DUID for IPv6 */
+	  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, ":");
+	    }
+
+	}
+#endif
+
+      buf += data.clid_len;
+
+      if (data.hostname_len != 0)
+	{
+	  char *dot;
+	  hostname = (char *)buf;
+	  hostname[data.hostname_len - 1] = 0;
+	  if (data.action != ACTION_TFTP)
+	    {
+	      if (!legal_hostname(hostname))
+		hostname = NULL;
+	      else if ((dot = strchr(hostname, '.')))
+		{
+		  domain = dot+1;
+		  *dot = 0;
+		} 
+	    }
+	}
+    
+      extradata = buf + data.hostname_len;
+    
+      if (!is6)
+	inet_ntop(AF_INET, &data.addr, daemon->addrbuff, ADDRSTRLEN);
+      else
+	inet_ntop(AF_INET6, &data.addr6, daemon->addrbuff, ADDRSTRLEN);
+
+#ifdef HAVE_TFTP
+      /* file length */
+      if (data.action == ACTION_TFTP)
+	sprintf(is6 ? daemon->packet : daemon->dhcp_buff, "%lu", (unsigned long)data.file_len);
+#endif
+
+#ifdef HAVE_LUASCRIPT
+      if (daemon->luascript)
+	{
+	  if (data.action == ACTION_TFTP)
+	    {
+	      lua_getglobal(lua, "tftp"); 
+	      if (lua_type(lua, -1) != LUA_TFUNCTION)
+		lua_pop(lua, 1); /* tftp function optional */
+	      else
+		{
+		  lua_pushstring(lua, action_str); /* arg1 - action */
+		  lua_newtable(lua);               /* arg2 - data table */
+		  lua_pushstring(lua, daemon->addrbuff);
+		  lua_setfield(lua, -2, "destination_address");
+		  lua_pushstring(lua, hostname);
+		  lua_setfield(lua, -2, "file_name"); 
+		  lua_pushstring(lua, is6 ? daemon->packet : daemon->dhcp_buff);
+		  lua_setfield(lua, -2, "file_size");
+		  lua_call(lua, 2, 0);	/* pass 2 values, expect 0 */
+		}
+	    }
+	  else if (data.action == ACTION_ARP)
+	    {
+	      lua_getglobal(lua, "arp"); 
+	      if (lua_type(lua, -1) != LUA_TFUNCTION)
+		lua_pop(lua, 1); /* arp function optional */
+	      else
+		{
+		  lua_pushstring(lua, action_str); /* arg1 - action */
+		  lua_newtable(lua);               /* arg2 - data table */
+		  lua_pushstring(lua, daemon->addrbuff);
+		  lua_setfield(lua, -2, "client_address");
+		  lua_pushstring(lua, daemon->dhcp_buff);
+		  lua_setfield(lua, -2, "mac_address");
+		  lua_call(lua, 2, 0);	/* pass 2 values, expect 0 */
+		}
+	    }
+	  else
+	    {
+	      lua_getglobal(lua, "lease");     /* function to call */
+	      lua_pushstring(lua, action_str); /* arg1 - action */
+	      lua_newtable(lua);               /* arg2 - data table */
+	      
+	      if (is6)
+		{
+		  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");
+		}
+	      
+	      if (!is6 && data.clid_len != 0)
+		{
+		  lua_pushstring(lua, daemon->packet);
+		  lua_setfield(lua, -2, "client_id");
+		}
+	      
+	      if (strlen(data.interface) != 0)
+		{
+		  lua_pushstring(lua, data.interface);
+		  lua_setfield(lua, -2, "interface");
+		}
+	      
+#ifdef HAVE_BROKEN_RTC	
+	      lua_pushnumber(lua, data.length);
+	      lua_setfield(lua, -2, "lease_length");
+#else
+	      lua_pushnumber(lua, data.expires);
+	      lua_setfield(lua, -2, "lease_expires");
+#endif
+	      
+	      if (hostname)
+		{
+		  lua_pushstring(lua, hostname);
+		  lua_setfield(lua, -2, "hostname");
+		}
+	      
+	      if (domain)
+		{
+		  lua_pushstring(lua, domain);
+		  lua_setfield(lua, -2, "domain");
+		}
+	      
+	      end = extradata + data.ed_len;
+	      buf = extradata;
+	      
+	      if (!is6)
+		buf = grab_extradata_lua(buf, end, "vendor_class");
+#ifdef HAVE_DHCP6
+	      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");
+	      
+	      if (!is6)
+		{
+		  buf = grab_extradata_lua(buf, end, "cpewan_oui");
+		  buf = grab_extradata_lua(buf, end, "cpewan_serial");   
+		  buf = grab_extradata_lua(buf, end, "cpewan_class");
+		  buf = grab_extradata_lua(buf, end, "circuit_id");
+		  buf = grab_extradata_lua(buf, end, "subscriber_id");
+		  buf = grab_extradata_lua(buf, end, "remote_id");
+		}
+	      
+	      buf = grab_extradata_lua(buf, end, "tags");
+	      
+	      if (is6)
+		buf = grab_extradata_lua(buf, end, "relay_address");
+	      else if (data.giaddr.s_addr != 0)
+		{
+		  inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN);
+		  lua_pushstring(lua, daemon->addrbuff);
+		  lua_setfield(lua, -2, "relay_address");
+		}
+	      
+	      for (i = 0; buf; i++)
+		{
+		  sprintf(daemon->dhcp_buff2, "user_class%i", i);
+		  buf = grab_extradata_lua(buf, end, daemon->dhcp_buff2);
+		}
+	      
+	      if (data.action != ACTION_DEL && data.remaining_time != 0)
+		{
+		  lua_pushnumber(lua, data.remaining_time);
+		  lua_setfield(lua, -2, "time_remaining");
+		}
+	      
+	      if (data.action == ACTION_OLD_HOSTNAME && hostname)
+		{
+		  lua_pushstring(lua, hostname);
+		  lua_setfield(lua, -2, "old_hostname");
+		}
+	      
+	      if (!is6 || data.hwaddr_len != 0)
+		{
+		  lua_pushstring(lua, daemon->dhcp_buff);
+		  lua_setfield(lua, -2, "mac_address");
+		}
+	      
+	      lua_pushstring(lua, daemon->addrbuff);
+	      lua_setfield(lua, -2, "ip_address");
+	    
+	      lua_call(lua, 2, 0);	/* pass 2 values, expect 0 */
+	    }
+	}
+#endif
+
+      /* no script, just lua */
+      if (!daemon->lease_change_command)
+	continue;
+
+      /* Pipe to capture stdout and stderr from script */
+      if (!option_bool(OPT_DEBUG) && pipe(pipeout) == -1)
+	continue;
+      
+      /* possible fork errors are all temporary resource problems */
+      while ((pid = fork()) == -1 && (errno == EAGAIN || errno == ENOMEM))
+	sleep(2);
+
+      if (pid == -1)
+        {
+	  if (!option_bool(OPT_DEBUG))
+	    {
+	      close(pipeout[0]);
+	      close(pipeout[1]);
+	    }
+	  continue;
+        }
+      
+      /* wait for child to complete */
+      if (pid != 0)
+	{
+	  if (!option_bool(OPT_DEBUG))
+	    {
+	      FILE *fp;
+	  
+	      close(pipeout[1]);
+	      
+	      /* Read lines sent to stdout/err by the script and pass them back to be logged */
+	      if (!(fp = fdopen(pipeout[0], "r")))
+		close(pipeout[0]);
+	      else
+		{
+		  while (fgets(daemon->packet, daemon->packet_buff_sz, fp))
+		    {
+		      /* do not include new lines, log will append them */
+		      size_t len = strlen(daemon->packet);
+		      if (len > 0)
+			{
+			  --len;
+			  if (daemon->packet[len] == '\n')
+			    daemon->packet[len] = 0;
+			}
+		      send_event(event_fd, EVENT_SCRIPT_LOG, 0, daemon->packet);
+		    }
+		  fclose(fp);
+		}
+	    }
+	  
+	  /* reap our children's children, if necessary */
+	  while (1)
+	    {
+	      int status;
+	      pid_t rc = wait(&status);
+	      
+	      if (rc == pid)
+		{
+		  /* On error send event back to main process for logging */
+		  if (WIFSIGNALED(status))
+		    send_event(event_fd, EVENT_KILLED, WTERMSIG(status), NULL);
+		  else if (WIFEXITED(status) && WEXITSTATUS(status) != 0)
+		    send_event(event_fd, EVENT_EXITED, WEXITSTATUS(status), NULL);
+		  break;
+		}
+	      
+	      if (rc == -1 && errno != EINTR)
+		break;
+	    }
+	  
+	  continue;
+	}
+
+      if (!option_bool(OPT_DEBUG))
+	{
+	  /* map stdout/stderr of script to pipeout */
+	  close(pipeout[0]);
+	  dup2(pipeout[1], STDOUT_FILENO);
+	  dup2(pipeout[1], STDERR_FILENO);
+	  close(pipeout[1]);
+	}
+      
+      if (data.action != ACTION_TFTP && data.action != ACTION_ARP)
+	{
+#ifdef HAVE_DHCP6
+	  my_setenv("DNSMASQ_IAID", is6 ? daemon->dhcp_buff3 : NULL, &err);
+	  my_setenv("DNSMASQ_SERVER_DUID", is6 ? daemon->dhcp_packet.iov_base : NULL, &err); 
+	  my_setenv("DNSMASQ_MAC", is6 && data.hwaddr_len != 0 ? daemon->dhcp_buff : NULL, &err);
+#endif
+	  
+	  my_setenv("DNSMASQ_CLIENT_ID", !is6 && data.clid_len != 0 ? daemon->packet : NULL, &err);
+	  my_setenv("DNSMASQ_INTERFACE", strlen(data.interface) != 0 ? data.interface : NULL, &err);
+	  
+#ifdef HAVE_BROKEN_RTC
+	  sprintf(daemon->dhcp_buff2, "%u", data.length);
+	  my_setenv("DNSMASQ_LEASE_LENGTH", daemon->dhcp_buff2, &err);
+#else
+	  sprintf(daemon->dhcp_buff2, "%lu", (unsigned long)data.expires);
+	  my_setenv("DNSMASQ_LEASE_EXPIRES", daemon->dhcp_buff2, &err); 
+#endif
+	  
+	  my_setenv("DNSMASQ_DOMAIN", domain, &err);
+	  
+	  end = extradata + data.ed_len;
+	  buf = extradata;
+	  
+	  if (!is6)
+	    buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS", &err);
+#ifdef HAVE_DHCP6
+	  else
+	    {
+	      if (data.vendorclass_count != 0)
+		{
+		  buf = grab_extradata(buf, end, "DNSMASQ_VENDOR_CLASS_ID", &err);
+		  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);
+		    }
+		}
+	    }
+#endif
+	  
+	  buf = grab_extradata(buf, end, "DNSMASQ_SUPPLIED_HOSTNAME", &err);
+	  
+	  if (!is6)
+	    {
+	      buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_OUI", &err);
+	      buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_SERIAL", &err);   
+	      buf = grab_extradata(buf, end, "DNSMASQ_CPEWAN_CLASS", &err);
+	      buf = grab_extradata(buf, end, "DNSMASQ_CIRCUIT_ID", &err);
+	      buf = grab_extradata(buf, end, "DNSMASQ_SUBSCRIBER_ID", &err);
+	      buf = grab_extradata(buf, end, "DNSMASQ_REMOTE_ID", &err);
+	      buf = grab_extradata(buf, end, "DNSMASQ_REQUESTED_OPTIONS", &err);
+	    }
+	  
+	  buf = grab_extradata(buf, end, "DNSMASQ_TAGS", &err);
+
+	  if (is6)
+	    buf = grab_extradata(buf, end, "DNSMASQ_RELAY_ADDRESS", &err);
+	  else
+	    {
+	      const char *giaddr = NULL;
+	      if (data.giaddr.s_addr != 0)
+		  giaddr = inet_ntop(AF_INET, &data.giaddr, daemon->addrbuff, ADDRSTRLEN);
+	      my_setenv("DNSMASQ_RELAY_ADDRESS", giaddr, &err);
+	    }
+	  
+	  for (i = 0; buf; i++)
+	    {
+	      sprintf(daemon->dhcp_buff2, "DNSMASQ_USER_CLASS%i", i);
+	      buf = grab_extradata(buf, end, daemon->dhcp_buff2, &err);
+	    }
+	  
+	  sprintf(daemon->dhcp_buff2, "%u", data.remaining_time);
+	  my_setenv("DNSMASQ_TIME_REMAINING", data.action != ACTION_DEL && data.remaining_time != 0 ? daemon->dhcp_buff2 : NULL, &err);
+	  
+	  my_setenv("DNSMASQ_OLD_HOSTNAME", data.action == ACTION_OLD_HOSTNAME ? hostname : NULL, &err);
+	  if (data.action == ACTION_OLD_HOSTNAME)
+	    hostname = NULL;
+	  
+	  my_setenv("DNSMASQ_LOG_DHCP", option_bool(OPT_LOG_OPTS) ? "1" : NULL, &err);
+	}
+      
+      /* we need to have the event_fd around if exec fails */
+      if ((i = fcntl(event_fd, F_GETFD)) != -1)
+	fcntl(event_fd, F_SETFD, i | FD_CLOEXEC);
+      close(pipefd[0]);
+
+      p =  strrchr(daemon->lease_change_command, '/');
+      if (err == 0)
+	{
+	  execl(daemon->lease_change_command, 
+		p ? p+1 : daemon->lease_change_command, action_str, 
+		(is6 && data.action != ACTION_ARP) ? daemon->packet : daemon->dhcp_buff, 
+		daemon->addrbuff, hostname, (char*)NULL);
+	  err = errno;
+	}
+      /* failed, send event so the main process logs the problem */
+      send_event(event_fd, EVENT_EXEC_ERR, err, NULL);
+      _exit(0); 
+    }
+}
+
+static void my_setenv(const char *name, const char *value, int *error)
+{
+  if (*error == 0)
+    {
+      if (!value)
+	unsetenv(name);
+      else if (setenv(name, value, 1) != 0)
+	*error = errno;
+    }
+}
+ 
+static unsigned char *grab_extradata(unsigned char *buf, unsigned char *end,  char *env, int *err)
+{
+  unsigned char *next = NULL;
+  char *val = NULL;
+
+  if (buf && (buf != end))
+    {
+      for (next = buf; ; next++)
+	if (next == end)
+	  {
+	    next = NULL;
+	    break;
+	  }
+	else if (*next == 0)
+	  break;
+
+      if (next && (next != buf))
+	{
+	  char *p;
+	  /* No "=" in value */
+	  if ((p = strchr((char *)buf, '=')))
+	    *p = 0;
+	  val = (char *)buf;
+	}
+    }
+  
+  my_setenv(env, val, err);
+   
+  return next ? next + 1 : NULL;
+}
+
+#ifdef HAVE_LUASCRIPT
+static unsigned char *grab_extradata_lua(unsigned char *buf, unsigned char *end, char *field)
+{
+  unsigned char *next;
+
+  if (!buf || (buf == end))
+    return NULL;
+
+  for (next = buf; *next != 0; next++)
+    if (next == end)
+      return NULL;
+  
+  if (next != buf)
+    {
+      lua_pushstring(lua,  (char *)buf);
+      lua_setfield(lua, -2, field);
+    }
+
+  return next + 1;
+}
+#endif
+
+static void buff_alloc(size_t size)
+{
+  if (size > buf_size)
+    {
+      struct script_data *new;
+      
+      /* start with reasonable size, will almost never need extending. */
+      if (size < sizeof(struct script_data) + 200)
+	size = sizeof(struct script_data) + 200;
+
+      if (!(new = whine_malloc(size)))
+	return;
+      if (buf)
+	free(buf);
+      buf = new;
+      buf_size = size;
+    }
+}
+
+/* pack up lease data into a buffer */    
+void queue_script(int action, struct dhcp_lease *lease, char *hostname, time_t now)
+{
+  unsigned char *p;
+  unsigned int hostname_len = 0, clid_len = 0, ed_len = 0;
+  int fd = daemon->dhcpfd;
+#ifdef HAVE_DHCP6 
+  if (!daemon->dhcp)
+    fd = daemon->dhcp6fd;
+#endif
+
+  /* no script */
+  if (daemon->helperfd == -1)
+    return;
+
+  if (lease->extradata)
+    ed_len = lease->extradata_len;
+  if (lease->clid)
+    clid_len = lease->clid_len;
+  if (hostname)
+    hostname_len = strlen(hostname) + 1;
+
+  buff_alloc(sizeof(struct script_data) +  clid_len + ed_len + hostname_len);
+
+  buf->action = action;
+  buf->flags = lease->flags;
+#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;
+  buf->clid_len = clid_len;
+  buf->ed_len = ed_len;
+  buf->hostname_len = hostname_len;
+  buf->addr = lease->addr;
+  buf->giaddr = lease->giaddr;
+  memcpy(buf->hwaddr, lease->hwaddr, DHCP_CHADDR_MAX);
+  if (!indextoname(fd, lease->last_interface, buf->interface))
+    buf->interface[0] = 0;
+  
+#ifdef HAVE_BROKEN_RTC 
+  buf->length = lease->length;
+#else
+  buf->expires = lease->expires;
+#endif
+
+  if (lease->expires != 0)
+    buf->remaining_time = (unsigned int)difftime(lease->expires, now);
+  else
+    buf->remaining_time = 0;
+
+  p = (unsigned char *)(buf+1);
+  if (clid_len != 0)
+    {
+      memcpy(p, lease->clid, clid_len);
+      p += clid_len;
+    }
+  if (hostname_len != 0)
+    {
+      memcpy(p, hostname, hostname_len);
+      p += hostname_len;
+    }
+  if (ed_len != 0)
+    {
+      memcpy(p, lease->extradata, ed_len);
+      p += ed_len;
+    }
+  bytes_in_buf = p - (unsigned char *)buf;
+}
+
+#ifdef HAVE_TFTP
+/* This nastily re-uses DHCP-fields for TFTP stuff */
+void queue_tftp(off_t file_len, char *filename, union mysockaddr *peer)
+{
+  unsigned int filename_len;
+
+  /* no script */
+  if (daemon->helperfd == -1)
+    return;
+  
+  filename_len = strlen(filename) + 1;
+  buff_alloc(sizeof(struct script_data) +  filename_len);
+  memset(buf, 0, sizeof(struct script_data));
+
+  buf->action = ACTION_TFTP;
+  buf->hostname_len = filename_len;
+  buf->file_len = file_len;
+
+  if ((buf->flags = peer->sa.sa_family) == AF_INET)
+    buf->addr = peer->in.sin_addr;
+  else
+    buf->addr6 = peer->in6.sin6_addr;
+
+  memcpy((unsigned char *)(buf+1), filename, filename_len);
+  
+  bytes_in_buf = sizeof(struct script_data) +  filename_len;
+}
+#endif
+
+void queue_arp(int action, unsigned char *mac, int maclen, int family, union all_addr *addr)
+{
+  /* no script */
+  if (daemon->helperfd == -1)
+    return;
+  
+  buff_alloc(sizeof(struct script_data));
+  memset(buf, 0, sizeof(struct script_data));
+
+  buf->action = action;
+  buf->hwaddr_len = maclen;
+  buf->hwaddr_type =  ARPHRD_ETHER; 
+  if ((buf->flags = family) == AF_INET)
+    buf->addr = addr->addr4;
+  else
+    buf->addr6 = addr->addr6;
+  
+  memcpy(buf->hwaddr, mac, maclen);
+  
+  bytes_in_buf = sizeof(struct script_data);
+}
+
+int helper_buf_empty(void)
+{
+  return bytes_in_buf == 0;
+}
+
+void helper_write(void)
+{
+  ssize_t rc;
+
+  if (bytes_in_buf == 0)
+    return;
+  
+  if ((rc = write(daemon->helperfd, buf, bytes_in_buf)) != -1)
+    {
+      if (bytes_in_buf != (size_t)rc)
+	memmove(buf, buf + rc, bytes_in_buf - rc); 
+      bytes_in_buf -= rc;
+    }
+  else
+    {
+      if (errno == EAGAIN || errno == EINTR)
+	return;
+      bytes_in_buf = 0;
+    }
+}
+
+#endif /* HAVE_SCRIPT */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c b/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c
new file mode 100755
index 0000000..5776feb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/inotify.c
@@ -0,0 +1,297 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_INOTIFY
+
+#include <sys/inotify.h>
+#include <sys/param.h> /* For MAXSYMLINKS */
+
+/* the strategy is to set an inotify on the directories containing
+   resolv files, for any files in the directory which are close-write 
+   or moved into the directory.
+   
+   When either of those happen, we look to see if the file involved
+   is actually a resolv-file, and if so, call poll-resolv with
+   the "force" argument, to ensure it's read.
+
+   This adds one new error condition: the directories containing
+   all specified resolv-files must exist at start-up, even if the actual
+   files don't. 
+*/
+
+static char *inotify_buffer;
+#define INOTIFY_SZ (sizeof(struct inotify_event) + NAME_MAX + 1)
+
+/* If path is a symbolic link, return the path it
+   points to, made absolute if relative.
+   If path doesn't exist or is not a symlink, return NULL.
+   Return value is malloc'ed */
+static char *my_readlink(char *path)
+{
+  ssize_t rc, size = 64;
+  char *buf;
+
+  while (1)
+    {
+      buf = safe_malloc(size);
+      rc = readlink(path, buf, (size_t)size);
+      
+      if (rc == -1)
+	{
+	  /* Not link or doesn't exist. */
+	  if (errno == EINVAL || errno == ENOENT)
+	    {
+	      free(buf);
+	      return NULL;
+	    }
+	  else
+	    die(_("cannot access path %s: %s"), path, EC_MISC);
+	}
+      else if (rc < size-1)
+	{
+	  char *d;
+	  
+	  buf[rc] = 0;
+	  if (buf[0] != '/' && ((d = strrchr(path, '/'))))
+	    {
+	      /* Add path to relative link */
+	      char *new_buf = safe_malloc((d - path) + strlen(buf) + 2);
+	      *(d+1) = 0;
+	      strcpy(new_buf, path);
+	      strcat(new_buf, buf);
+	      free(buf);
+	      buf = new_buf;
+	    }
+	  return buf;
+	}
+
+      /* Buffer too small, increase and retry */
+      size += 64;
+      free(buf);
+    }
+}
+
+void inotify_dnsmasq_init()
+{
+  struct resolvc *res;
+  inotify_buffer = safe_malloc(INOTIFY_SZ);
+  daemon->inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+  
+  if (daemon->inotifyfd == -1)
+    die(_("failed to create inotify: %s"), NULL, EC_MISC);
+
+  if (option_bool(OPT_NO_RESOLV))
+    return;
+  
+  for (res = daemon->resolv_files; res; res = res->next)
+    {
+      char *d, *new_path, *path = safe_malloc(strlen(res->name) + 1);
+      int links = MAXSYMLINKS;
+
+      strcpy(path, res->name);
+
+      /* Follow symlinks until we reach a non-symlink, or a non-existent file. */
+      while ((new_path = my_readlink(path)))
+	{
+	  if (links-- == 0)
+	    die(_("too many symlinks following %s"), res->name, EC_MISC);
+	  free(path);
+	  path = new_path;
+	}
+
+      res->wd = -1;
+
+      if ((d = strrchr(path, '/')))
+	{
+	  *d = 0; /* make path just directory */
+	  res->wd = inotify_add_watch(daemon->inotifyfd, path, IN_CLOSE_WRITE | IN_MOVED_TO);
+
+	  res->file = d+1; /* pointer to filename */
+	  *d = '/';
+	  
+	  if (res->wd == -1 && errno == ENOENT)
+	    die(_("directory %s for resolv-file is missing, cannot poll"), res->name, EC_MISC);
+	}	  
+	 
+      if (res->wd == -1)
+	die(_("failed to create inotify for %s: %s"), res->name, EC_MISC);
+	
+    }
+}
+
+
+/* initialisation for dynamic-dir. Set inotify watch for each directory, and read pre-existing files */
+void set_dynamic_inotify(int flag, int total_size, struct crec **rhash, int revhashsz)
+{
+  struct hostsfile *ah;
+  
+  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+    {
+      DIR *dir_stream = NULL;
+      struct dirent *ent;
+      struct stat buf;
+     
+      if (!(ah->flags & flag))
+	continue;
+ 
+      if (stat(ah->fname, &buf) == -1 || !(S_ISDIR(buf.st_mode)))
+	{
+	  my_syslog(LOG_ERR, _("bad dynamic directory %s: %s"), 
+		    ah->fname, strerror(errno));
+	  continue;
+	}
+      
+       if (!(ah->flags & AH_WD_DONE))
+	 {
+	   ah->wd = inotify_add_watch(daemon->inotifyfd, ah->fname, IN_CLOSE_WRITE | IN_MOVED_TO);
+	   ah->flags |= AH_WD_DONE;
+	 }
+
+       /* Read contents of dir _after_ calling add_watch, in the hope of avoiding
+	  a race which misses files being added as we start */
+       if (ah->wd == -1 || !(dir_stream = opendir(ah->fname)))
+	 {
+	   my_syslog(LOG_ERR, _("failed to create inotify for %s: %s"),
+		     ah->fname, strerror(errno));
+	   continue;
+	 }
+
+       while ((ent = readdir(dir_stream)))
+	 {
+	   size_t lendir = strlen(ah->fname);
+	   size_t lenfile = strlen(ent->d_name);
+	   char *path;
+	   
+	   /* ignore emacs backups and dotfiles */
+	   if (lenfile == 0 || 
+	       ent->d_name[lenfile - 1] == '~' ||
+	       (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
+	       ent->d_name[0] == '.')
+	     continue;
+	   
+	   if ((path = whine_malloc(lendir + lenfile + 2)))
+	     {
+	       strcpy(path, ah->fname);
+	       strcat(path, "/");
+	       strcat(path, ent->d_name);
+	       
+	       /* ignore non-regular files */
+	       if (stat(path, &buf) != -1 && S_ISREG(buf.st_mode))
+		 {
+		   if (ah->flags & AH_HOSTS)
+		     total_size = read_hostsfile(path, ah->index, total_size, rhash, revhashsz);
+#ifdef HAVE_DHCP
+		   else if (ah->flags & (AH_DHCP_HST | AH_DHCP_OPT))
+		     option_read_dynfile(path, ah->flags);
+#endif		   
+		 }
+
+	       free(path);
+	     }
+	 }
+
+       closedir(dir_stream);
+    }
+}
+
+int inotify_check(time_t now)
+{
+  int hit = 0;
+  struct hostsfile *ah;
+
+  while (1)
+    {
+      int rc;
+      char *p;
+      struct resolvc *res;
+      struct inotify_event *in;
+
+      while ((rc = read(daemon->inotifyfd, inotify_buffer, INOTIFY_SZ)) == -1 && errno == EINTR);
+      
+      if (rc <= 0)
+	break;
+      
+      for (p = inotify_buffer; rc - (p - inotify_buffer) >= (int)sizeof(struct inotify_event); p += sizeof(struct inotify_event) + in->len) 
+	{
+	  size_t namelen;
+
+	  in = (struct inotify_event*)p;
+	  
+	  /* ignore emacs backups and dotfiles */
+	  if (in->len == 0 || (namelen = strlen(in->name)) == 0 ||
+	      in->name[namelen - 1] == '~' ||
+	      (in->name[0] == '#' && in->name[namelen - 1] == '#') ||
+	      in->name[0] == '.')
+	    continue;
+
+	  for (res = daemon->resolv_files; res; res = res->next)
+	    if (res->wd == in->wd && strcmp(res->file, in->name) == 0)
+	      hit = 1;
+
+	  for (ah = daemon->dynamic_dirs; ah; ah = ah->next)
+	    if (ah->wd == in->wd)
+	      {
+		size_t lendir = strlen(ah->fname);
+		char *path;
+		
+		if ((path = whine_malloc(lendir + in->len + 2)))
+		  {
+		    strcpy(path, ah->fname);
+		    strcat(path, "/");
+		    strcat(path, in->name);
+		     
+		    my_syslog(LOG_INFO, _("inotify, new or changed file %s"), path);
+
+		    if (ah->flags & AH_HOSTS)
+		      {
+			read_hostsfile(path, ah->index, 0, NULL, 0);
+#ifdef HAVE_DHCP
+			if (daemon->dhcp || daemon->doing_dhcp6) 
+			  {
+			    /* Propagate the consequences of loading a new dhcp-host */
+			    dhcp_update_configs(daemon->dhcp_conf);
+			    lease_update_from_configs(); 
+			    lease_update_file(now); 
+			    lease_update_dns(1);
+			  }
+#endif
+		      }
+#ifdef HAVE_DHCP
+		    else if (ah->flags & AH_DHCP_HST)
+		      {
+			if (option_read_dynfile(path, AH_DHCP_HST))
+			  {
+			    /* Propagate the consequences of loading a new dhcp-host */
+			    dhcp_update_configs(daemon->dhcp_conf);
+			    lease_update_from_configs(); 
+			    lease_update_file(now); 
+			    lease_update_dns(1);
+			  }
+		      }
+		    else if (ah->flags & AH_DHCP_OPT)
+		      option_read_dynfile(path, AH_DHCP_OPT);
+#endif
+		    
+		    free(path);
+		  }
+	      }
+	}
+    }
+  return hit;
+}
+
+#endif  /* INOTIFY */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h b/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h
new file mode 100755
index 0000000..6388c7d
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ip6addr.h
@@ -0,0 +1,33 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+
+
+#define IN6_IS_ADDR_ULA(a) \
+        ((((__const uint32_t *) (a))[0] & htonl (0xff000000))                 \
+         == htonl (0xfd000000))
+
+#define IN6_IS_ADDR_ULA_ZERO(a) \
+        (((__const uint32_t *) (a))[0] == htonl (0xfd000000)                        \
+         && ((__const uint32_t *) (a))[1] == 0                                \
+         && ((__const uint32_t *) (a))[2] == 0                                \
+         && ((__const uint32_t *) (a))[3] == 0)
+
+#define IN6_IS_ADDR_LINK_LOCAL_ZERO(a) \
+        (((__const uint32_t *) (a))[0] == htonl (0xfe800000)                  \
+         && ((__const uint32_t *) (a))[1] == 0                                \
+         && ((__const uint32_t *) (a))[2] == 0                                \
+         && ((__const uint32_t *) (a))[3] == 0)
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c b/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c
new file mode 100755
index 0000000..0c014cb
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ipset.c
@@ -0,0 +1,216 @@
+/* ipset.c is Copyright (c) 2013 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+
+   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"
+
+#if defined(HAVE_IPSET) && defined(HAVE_LINUX_NETWORK)
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <linux/netlink.h>
+
+/* We want to be able to compile against old header files
+   Kernel version is handled at run-time. */
+
+#define NFNL_SUBSYS_IPSET 6
+
+#define IPSET_ATTR_DATA 7
+#define IPSET_ATTR_IP 1
+#define IPSET_ATTR_IPADDR_IPV4 1
+#define IPSET_ATTR_IPADDR_IPV6 2
+#define IPSET_ATTR_PROTOCOL 1
+#define IPSET_ATTR_SETNAME 2
+#define IPSET_CMD_ADD 9
+#define IPSET_CMD_DEL 10
+#define IPSET_MAXNAMELEN 32
+#define IPSET_PROTOCOL 6
+
+#ifndef NFNETLINK_V0
+#define NFNETLINK_V0    0
+#endif
+
+#ifndef NLA_F_NESTED
+#define NLA_F_NESTED		(1 << 15)
+#endif
+
+#ifndef NLA_F_NET_BYTEORDER
+#define NLA_F_NET_BYTEORDER	(1 << 14)
+#endif
+
+struct my_nlattr {
+        __u16           nla_len;
+        __u16           nla_type;
+};
+
+struct my_nfgenmsg {
+        __u8  nfgen_family;             /* AF_xxx */
+        __u8  version;          /* nfnetlink version */
+        __be16    res_id;               /* resource id */
+};
+
+
+/* data structure size in here is fixed */
+#define BUFF_SZ 256
+
+#define NL_ALIGN(len) (((len)+3) & ~(3))
+static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
+static int ipset_sock, old_kernel;
+static char *buffer;
+
+static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data)
+{
+  struct my_nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len);
+  uint16_t payload_len = NL_ALIGN(sizeof(struct my_nlattr)) + len;
+  attr->nla_type = type;
+  attr->nla_len = payload_len;
+  memcpy((void *)attr + NL_ALIGN(sizeof(struct my_nlattr)), data, len);
+  nlh->nlmsg_len += NL_ALIGN(payload_len);
+}
+
+void ipset_init(void)
+{
+  old_kernel = (daemon->kernel_version < KERNEL_VERSION(2,6,32));
+  
+  if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1)
+    return;
+  
+  if (!old_kernel && 
+      (buffer = safe_malloc(BUFF_SZ)) &&
+      (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 &&
+      (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1))
+    return;
+  
+  die (_("failed to create IPset control socket: %s"), NULL, EC_MISC);
+}
+
+static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr, int af, int remove)
+{
+  struct nlmsghdr *nlh;
+  struct my_nfgenmsg *nfg;
+  struct my_nlattr *nested[2];
+  uint8_t proto;
+  int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ;
+
+  if (strlen(setname) >= IPSET_MAXNAMELEN) 
+    {
+      errno = ENAMETOOLONG;
+      return -1;
+    }
+  
+  memset(buffer, 0, BUFF_SZ);
+
+  nlh = (struct nlmsghdr *)buffer;
+  nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr));
+  nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8);
+  nlh->nlmsg_flags = NLM_F_REQUEST;
+  
+  nfg = (struct my_nfgenmsg *)(buffer + nlh->nlmsg_len);
+  nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nfgenmsg));
+  nfg->nfgen_family = af;
+  nfg->version = NFNETLINK_V0;
+  nfg->res_id = htons(0);
+  
+  proto = IPSET_PROTOCOL;
+  add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto);
+  add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname);
+  nested[0] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+  nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
+  nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA;
+  nested[1] = (struct my_nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+  nlh->nlmsg_len += NL_ALIGN(sizeof(struct my_nlattr));
+  nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP;
+  add_attr(nlh, 
+	   (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER,
+	   addrsz, ipaddr);
+  nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1];
+  nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0];
+	
+  while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0,
+			   (struct sockaddr *)&snl, sizeof(snl))));
+								    
+  return errno == 0 ? 0 : -1;
+}
+
+
+static int old_add_to_ipset(const char *setname, const union all_addr *ipaddr, int remove)
+{
+  socklen_t size;
+  struct ip_set_req_adt_get {
+    unsigned op;
+    unsigned version;
+    union {
+      char name[IPSET_MAXNAMELEN];
+      uint16_t index;
+    } set;
+    char typename[IPSET_MAXNAMELEN];
+  } req_adt_get;
+  struct ip_set_req_adt {
+    unsigned op;
+    uint16_t index;
+    uint32_t ip;
+  } req_adt;
+  
+  if (strlen(setname) >= sizeof(req_adt_get.set.name)) 
+    {
+      errno = ENAMETOOLONG;
+      return -1;
+    }
+  
+  req_adt_get.op = 0x10;
+  req_adt_get.version = 3;
+  strcpy(req_adt_get.set.name, setname);
+  size = sizeof(req_adt_get);
+  if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0)
+    return -1;
+  req_adt.op = remove ? 0x102 : 0x101;
+  req_adt.index = req_adt_get.set.index;
+  req_adt.ip = ntohl(ipaddr->addr4.s_addr);
+  if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0)
+    return -1;
+  
+  return 0;
+}
+
+
+
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove)
+{
+  int ret = 0, af = AF_INET;
+
+  if (flags & F_IPV6)
+    {
+      af = AF_INET6;
+      /* old method only supports IPv4 */
+      if (old_kernel)
+	{
+	  errno = EAFNOSUPPORT ;
+	  ret = -1;
+	}
+    }
+  
+  if (ret != -1) 
+    ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove);
+
+  if (ret == -1)
+     my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno));
+
+  return ret;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c b/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c
new file mode 100755
index 0000000..1a9f1c6
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/lease.c
@@ -0,0 +1,1205 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP
+
+static struct dhcp_lease *leases = NULL, *old_leases = NULL;
+static int dns_dirty, file_dirty, leases_left;
+
+static int read_leases(time_t now, FILE *leasestream)
+{
+  unsigned long ei;
+  union all_addr addr;
+  struct dhcp_lease *lease;
+  int clid_len, hw_len, hw_type;
+  int items;
+  char *domain = NULL;
+
+  *daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
+
+  /* client-id max length is 255 which is 255*2 digits + 254 colons
+     borrow DNS packet buffer which is always larger than 1000 bytes
+
+     Check various buffers are big enough for the code below */
+
+#if (DHCP_BUFF_SZ < 255) || (MAXDNAME < 64) || (PACKETSZ+MAXDNAME+RRFIXEDSZ  < 764)
+# error Buffer size breakage in leasefile parsing.
+#endif
+
+    while ((items=fscanf(leasestream, "%255s %255s", daemon->dhcp_buff3, daemon->dhcp_buff2)) == 2)
+      {
+	*daemon->namebuff = *daemon->dhcp_buff = *daemon->packet = '\0';
+	hw_len = hw_type = clid_len = 0;
+	
+#ifdef HAVE_DHCP6
+	if (strcmp(daemon->dhcp_buff3, "duid") == 0)
+	  {
+	    daemon->duid_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, 130, NULL, NULL);
+	    if (daemon->duid_len < 0)
+	      return 0;
+	    daemon->duid = safe_malloc(daemon->duid_len);
+	    memcpy(daemon->duid, daemon->dhcp_buff2, daemon->duid_len);
+	    continue;
+	  }
+#endif
+	
+	if (fscanf(leasestream, " %64s %255s %764s",
+		   daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3)
+	  {
+	    my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database: %s %s %s %s ..."),
+		      daemon->dhcp_buff3, daemon->dhcp_buff2,
+		      daemon->namebuff, daemon->dhcp_buff);
+	    continue;
+	  }
+		
+	if (inet_pton(AF_INET, daemon->namebuff, &addr.addr4))
+	  {
+	    if ((lease = lease4_allocate(addr.addr4)))
+	      domain = get_domain(lease->addr);
+	    
+	    hw_len = parse_hex(daemon->dhcp_buff2, (unsigned char *)daemon->dhcp_buff2, DHCP_CHADDR_MAX, NULL, &hw_type);
+	    /* For backwards compatibility, no explicit MAC address type means ether. */
+	    if (hw_type == 0 && hw_len != 0)
+	      hw_type = ARPHRD_ETHER; 
+	  }
+#ifdef HAVE_DHCP6
+	else if (inet_pton(AF_INET6, daemon->namebuff, &addr.addr6))
+	  {
+	    char *s = daemon->dhcp_buff2;
+	    int lease_type = LEASE_NA;
+
+	    if (s[0] == 'T')
+	      {
+		lease_type = LEASE_TA;
+		s++;
+	      }
+	    
+	    if ((lease = lease6_allocate(&addr.addr6, lease_type)))
+	      {
+		lease_set_iaid(lease, strtoul(s, NULL, 10));
+		domain = get_domain6(&lease->addr6);
+	      }
+	  }
+#endif
+	else
+	  {
+	    my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database, bad address: %s"),
+		      daemon->namebuff);
+	    continue;
+	  }
+	
+
+	if (!lease)
+	  die (_("too many stored leases"), NULL, EC_MISC);
+
+	if (strcmp(daemon->packet, "*") != 0)
+	  clid_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
+	
+	lease_set_hwaddr(lease, (unsigned char *)daemon->dhcp_buff2, (unsigned char *)daemon->packet, 
+			 hw_len, hw_type, clid_len, now, 0);
+	
+	if (strcmp(daemon->dhcp_buff, "*") !=  0)
+	  lease_set_hostname(lease, daemon->dhcp_buff, 0, domain, NULL);
+
+	ei = atol(daemon->dhcp_buff3);
+
+#ifdef HAVE_BROKEN_RTC
+	if (ei != 0)
+	  lease->expires = (time_t)ei + now;
+	else
+	  lease->expires = (time_t)0;
+	lease->length = ei;
+#else
+	/* strictly time_t is opaque, but this hack should work on all sane systems,
+	   even when sizeof(time_t) == 8 */
+	lease->expires = (time_t)ei;
+#endif
+	
+	/* set these correctly: the "old" events are generated later from
+	   the startup synthesised SIGHUP. */
+	lease->flags &= ~(LEASE_NEW | LEASE_CHANGED);
+	
+	*daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
+      }
+    
+    return (items == 0 || items == EOF);
+}
+
+void lease_init(time_t now)
+{
+  FILE *leasestream;
+
+  leases_left = daemon->dhcp_max;
+
+  if (option_bool(OPT_LEASE_RO))
+    {
+      /* run "<lease_change_script> init" once to get the
+	 initial state of the database. If leasefile-ro is
+	 set without a script, we just do without any
+	 lease database. */
+#ifdef HAVE_SCRIPT
+      if (daemon->lease_change_command)
+	{
+	  strcpy(daemon->dhcp_buff, daemon->lease_change_command);
+	  strcat(daemon->dhcp_buff, " init");
+	  leasestream = popen(daemon->dhcp_buff, "r");
+	}
+      else
+#endif
+	{
+          file_dirty = dns_dirty = 0;
+          return;
+        }
+
+    }
+  else
+    {
+      /* NOTE: need a+ mode to create file if it doesn't exist */
+      leasestream = daemon->lease_stream = fopen(daemon->lease_file, "a+");
+
+      if (!leasestream)
+	die(_("cannot open or create lease file %s: %s"), daemon->lease_file, EC_FILE);
+
+      /* a+ mode leaves pointer at end. */
+      rewind(leasestream);
+    }
+
+  if (leasestream)
+    {
+      if (!read_leases(now, leasestream))
+	my_syslog(MS_DHCP | LOG_ERR, _("failed to parse lease database cleanly"));
+      
+      if (ferror(leasestream))
+	die(_("failed to read lease file %s: %s"), daemon->lease_file, EC_FILE);
+    }
+  
+#ifdef HAVE_SCRIPT
+  if (!daemon->lease_stream)
+    {
+      int rc = 0;
+
+      /* shell returns 127 for "command not found", 126 for bad permissions. */
+      if (!leasestream || (rc = pclose(leasestream)) == -1 || WEXITSTATUS(rc) == 127 || WEXITSTATUS(rc) == 126)
+	{
+	  if (WEXITSTATUS(rc) == 127)
+	    errno = ENOENT;
+	  else if (WEXITSTATUS(rc) == 126)
+	    errno = EACCES;
+
+	  die(_("cannot run lease-init script %s: %s"), daemon->lease_change_command, EC_FILE);
+	}
+      
+      if (WEXITSTATUS(rc) != 0)
+	{
+	  sprintf(daemon->dhcp_buff, "%d", WEXITSTATUS(rc));
+	  die(_("lease-init script returned exit code %s"), daemon->dhcp_buff, WEXITSTATUS(rc) + EC_INIT_OFFSET);
+	}
+    }
+#endif
+
+  /* Some leases may have expired */
+  file_dirty = 0;
+  lease_prune(NULL, now);
+  dns_dirty = 1;
+}
+
+void lease_update_from_configs(void)
+{
+  /* changes to the config may change current leases. */
+  
+  struct dhcp_lease *lease;
+  struct dhcp_config *config;
+  char *name;
+  
+  for (lease = leases; lease; lease = lease->next)
+    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, NULL)) && 
+	     (config->flags & CONFIG_NAME) &&
+	     (!(config->flags & CONFIG_ADDR) || config->addr.s_addr == lease->addr.s_addr))
+      lease_set_hostname(lease, config->hostname, 1, get_domain(lease->addr), NULL);
+    else if ((name = host_from_dns(lease->addr)))
+      lease_set_hostname(lease, name, 1, get_domain(lease->addr), NULL); /* updates auth flag only */
+}
+
+static void ourprintf(int *errp, char *format, ...)
+{
+  va_list ap;
+  
+  va_start(ap, format);
+  if (!(*errp) && vfprintf(daemon->lease_stream, format, ap) < 0)
+    *errp = errno;
+  va_end(ap);
+}
+
+void lease_update_file(time_t now)
+{
+  struct dhcp_lease *lease;
+  time_t next_event;
+  int i, err = 0;
+
+  if (file_dirty != 0 && daemon->lease_stream)
+    {
+      errno = 0;
+      rewind(daemon->lease_stream);
+      if (errno != 0 || ftruncate(fileno(daemon->lease_stream), 0) != 0)
+	err = errno;
+      
+      for (lease = leases; lease; lease = lease->next)
+	{
+
+#ifdef HAVE_DHCP6
+	  if (lease->flags & (LEASE_TA | LEASE_NA))
+	    continue;
+#endif
+
+#ifdef HAVE_BROKEN_RTC
+	  ourprintf(&err, "%u ", lease->length);
+#else
+	  ourprintf(&err, "%lu ", (unsigned long)lease->expires);
+#endif
+
+	  if (lease->hwaddr_type != ARPHRD_ETHER || lease->hwaddr_len == 0) 
+	    ourprintf(&err, "%.2x-", lease->hwaddr_type);
+	  for (i = 0; i < lease->hwaddr_len; i++)
+	    {
+	      ourprintf(&err, "%.2x", lease->hwaddr[i]);
+	      if (i != lease->hwaddr_len - 1)
+		ourprintf(&err, ":");
+	    }
+	  
+	  inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN); 
+
+	  ourprintf(&err, " %s ", daemon->addrbuff);
+	  ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
+	  	  
+	  if (lease->clid && lease->clid_len != 0)
+	    {
+	      for (i = 0; i < lease->clid_len - 1; i++)
+		ourprintf(&err, "%.2x:", lease->clid[i]);
+	      ourprintf(&err, "%.2x\n", lease->clid[i]);
+	    }
+	  else
+	    ourprintf(&err, "*\n");	  
+	}
+      
+#ifdef HAVE_DHCP6  
+      if (daemon->duid)
+	{
+	  ourprintf(&err, "duid ");
+	  for (i = 0; i < daemon->duid_len - 1; i++)
+	    ourprintf(&err, "%.2x:", daemon->duid[i]);
+	  ourprintf(&err, "%.2x\n", daemon->duid[i]);
+	  
+	  for (lease = leases; lease; lease = lease->next)
+	    {
+	      
+	      if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+		continue;
+
+#ifdef HAVE_BROKEN_RTC
+	      ourprintf(&err, "%u ", lease->length);
+#else
+	      ourprintf(&err, "%lu ", (unsigned long)lease->expires);
+#endif
+    
+	      inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+	 
+	      ourprintf(&err, "%s%u %s ", (lease->flags & LEASE_TA) ? "T" : "",
+			lease->iaid, daemon->addrbuff);
+	      ourprintf(&err, "%s ", lease->hostname ? lease->hostname : "*");
+	      
+	      if (lease->clid && lease->clid_len != 0)
+		{
+		  for (i = 0; i < lease->clid_len - 1; i++)
+		    ourprintf(&err, "%.2x:", lease->clid[i]);
+		  ourprintf(&err, "%.2x\n", lease->clid[i]);
+		}
+	      else
+		ourprintf(&err, "*\n");	  
+	    }
+	}
+#endif      
+	  
+      if (fflush(daemon->lease_stream) != 0 ||
+	  fsync(fileno(daemon->lease_stream)) < 0)
+	err = errno;
+      
+      if (!err)
+	file_dirty = 0;
+    }
+  
+  /* Set alarm for when the first lease expires. */
+  next_event = 0;
+
+#ifdef HAVE_DHCP6
+  /* do timed RAs and determine when the next is, also pings to potential SLAAC addresses */
+  if (daemon->doing_ra)
+    {
+      time_t event;
+      
+      if ((event = periodic_slaac(now, leases)) != 0)
+	{
+	  if (next_event == 0 || difftime(next_event, event) > 0.0)
+	    next_event = event;
+	}
+      
+      if ((event = periodic_ra(now)) != 0)
+	{
+	  if (next_event == 0 || difftime(next_event, event) > 0.0)
+	    next_event = event;
+	}
+    }
+#endif
+
+  for (lease = leases; lease; lease = lease->next)
+    if (lease->expires != 0 &&
+	(next_event == 0 || difftime(next_event, lease->expires) > 0.0))
+      next_event = lease->expires;
+   
+  if (err)
+    {
+      if (next_event == 0 || difftime(next_event, LEASE_RETRY + now) > 0.0)
+	next_event = LEASE_RETRY + now;
+      
+      my_syslog(MS_DHCP | LOG_ERR, _("failed to write %s: %s (retry in %u s)"), 
+		daemon->lease_file, strerror(err),
+		(unsigned int)difftime(next_event, now));
+    }
+
+  send_alarm(next_event, now);
+}
+
+
+static int find_interface_v4(struct in_addr local, int if_index, char *label,
+			     struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+  struct dhcp_lease *lease;
+  int prefix = netmask_length(netmask);
+
+  (void) label;
+  (void) broadcast;
+  (void) vparam;
+
+  for (lease = leases; lease; lease = lease->next)
+    if (!(lease->flags & (LEASE_TA | LEASE_NA)) &&
+	is_same_net(local, lease->addr, netmask) && 
+	prefix > lease->new_prefixlen) 
+      {
+	lease->new_interface = if_index;
+        lease->new_prefixlen = prefix;
+      }
+
+  return 1;
+}
+
+#ifdef HAVE_DHCP6
+static int find_interface_v6(struct in6_addr *local,  int prefix,
+			     int scope, int if_index, int flags, 
+			     int preferred, int valid, void *vparam)
+{
+  struct dhcp_lease *lease;
+
+  (void)scope;
+  (void)flags;
+  (void)preferred;
+  (void)valid;
+  (void)vparam;
+
+  for (lease = leases; lease; lease = lease->next)
+    if ((lease->flags & (LEASE_TA | LEASE_NA)))
+      if (is_same_net6(local, &lease->addr6, prefix) && prefix > lease->new_prefixlen) {
+        /* save prefix length for comparison, as we might get shorter matching
+         * prefix in upcoming netlink GETADDR responses
+         * */
+        lease->new_interface = if_index;
+        lease->new_prefixlen = prefix;
+      }
+
+  return 1;
+}
+
+void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface)
+{
+  /* We may be doing RA but not DHCPv4, in which case the lease
+     database may not exist and we have nothing to do anyway */
+  if (daemon->dhcp)
+    slaac_ping_reply(sender, packet, interface, leases);
+}
+
+void lease_update_slaac(time_t now)
+{
+  /* Called when we construct a new RA-names context, to add putative
+     new SLAAC addresses to existing leases. */
+
+  struct dhcp_lease *lease;
+  
+  if (daemon->dhcp)
+    for (lease = leases; lease; lease = lease->next)
+      slaac_add_addrs(lease, now, 0);
+}
+
+#endif
+
+
+/* Find interfaces associated with leases at start-up. This gets updated as
+   we do DHCP transactions, but information about directly-connected subnets
+   is useful from scrips and necessary for determining SLAAC addresses from
+   start-time. */
+void lease_find_interfaces(time_t now)
+{
+  struct dhcp_lease *lease;
+  
+  for (lease = leases; lease; lease = lease->next)
+    lease->new_prefixlen = lease->new_interface = 0;
+
+  iface_enumerate(AF_INET, &now, find_interface_v4);
+#ifdef HAVE_DHCP6
+  iface_enumerate(AF_INET6, &now, find_interface_v6);
+#endif
+
+  for (lease = leases; lease; lease = lease->next)
+    if (lease->new_interface != 0) 
+      lease_set_interface(lease, lease->new_interface, now);
+}
+
+#ifdef HAVE_DHCP6
+void lease_make_duid(time_t now)
+{
+  /* If we're not doing DHCPv6, and there are not v6 leases, don't add the DUID to the database */
+  if (!daemon->duid && daemon->doing_dhcp6)
+    {
+      file_dirty = 1;
+      make_duid(now);
+    }
+}
+#endif
+
+
+
+
+void lease_update_dns(int force)
+{
+  struct dhcp_lease *lease;
+
+  if (daemon->port != 0 && (dns_dirty || force))
+    {
+#ifndef HAVE_BROKEN_RTC
+      /* force transfer to authoritative secondaries */
+      daemon->soa_sn++;
+#endif
+      
+      cache_unhash_dhcp();
+
+      for (lease = leases; lease; lease = lease->next)
+	{
+	  int prot = AF_INET;
+	  
+#ifdef HAVE_DHCP6
+	  if (lease->flags & (LEASE_TA | LEASE_NA))
+	    prot = AF_INET6;
+	  else if (lease->hostname || lease->fqdn)
+	    {
+	      struct slaac_address *slaac;
+
+	      for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+		if (slaac->backoff == 0)
+		  {
+		    if (lease->fqdn)
+		      cache_add_dhcp_entry(lease->fqdn, AF_INET6, (union all_addr *)&slaac->addr, lease->expires);
+		    if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+		      cache_add_dhcp_entry(lease->hostname, AF_INET6, (union all_addr *)&slaac->addr, lease->expires);
+		  }
+	    }
+	  
+	  if (lease->fqdn)
+	    cache_add_dhcp_entry(lease->fqdn, prot, 
+				 prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6,
+				 lease->expires);
+	     
+	  if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+	    cache_add_dhcp_entry(lease->hostname, prot, 
+				 prot == AF_INET ? (union all_addr *)&lease->addr : (union all_addr *)&lease->addr6, 
+				 lease->expires);
+       
+#else
+	  if (lease->fqdn)
+	    cache_add_dhcp_entry(lease->fqdn, prot, (union all_addr *)&lease->addr, lease->expires);
+	  
+	  if (!option_bool(OPT_DHCP_FQDN) && lease->hostname)
+	    cache_add_dhcp_entry(lease->hostname, prot, (union all_addr *)&lease->addr, lease->expires);
+#endif
+	}
+      
+      dns_dirty = 0;
+    }
+}
+
+void lease_prune(struct dhcp_lease *target, time_t now)
+{
+  struct dhcp_lease *lease, *tmp, **up;
+
+  for (lease = leases, up = &leases; lease; lease = tmp)
+    {
+      tmp = lease->next;
+      if ((lease->expires != 0 && difftime(now, lease->expires) >= 0) || lease == target)
+	{
+	  file_dirty = 1;
+	  if (lease->hostname)
+	    dns_dirty = 1;
+
+	  daemon->metrics[lease->addr.s_addr ? METRIC_LEASES_PRUNED_4 : METRIC_LEASES_PRUNED_6]++;
+
+ 	  *up = lease->next; /* unlink */
+	  
+	  /* Put on old_leases list 'till we
+	     can run the script */
+	  lease->next = old_leases;
+	  old_leases = lease;
+	  
+	  leases_left++;
+	}
+      else
+	up = &lease->next;
+    }
+} 
+	
+  
+struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,
+					unsigned char *clid, int clid_len)
+{
+  struct dhcp_lease *lease;
+
+  if (clid)
+    for (lease = leases; lease; lease = lease->next)
+      {
+#ifdef HAVE_DHCP6
+	if (lease->flags & (LEASE_TA | LEASE_NA))
+	  continue;
+#endif
+	if (lease->clid && clid_len == lease->clid_len &&
+	    memcmp(clid, lease->clid, clid_len) == 0)
+	  return lease;
+      }
+  
+  for (lease = leases; lease; lease = lease->next)	
+    {
+#ifdef HAVE_DHCP6
+      if (lease->flags & (LEASE_TA | LEASE_NA))
+	continue;
+#endif   
+      if ((!lease->clid || !clid) && 
+	  hw_len != 0 && 
+	  lease->hwaddr_len == hw_len &&
+	  lease->hwaddr_type == hw_type &&
+	  memcmp(hwaddr, lease->hwaddr, hw_len) == 0)
+	return lease;
+    }
+
+  return NULL;
+}
+
+struct dhcp_lease *lease_find_by_addr(struct in_addr addr)
+{
+  struct dhcp_lease *lease;
+
+  for (lease = leases; lease; lease = lease->next)
+    {
+#ifdef HAVE_DHCP6
+      if (lease->flags & (LEASE_TA | LEASE_NA))
+	continue;
+#endif  
+      if (lease->addr.s_addr == addr.s_addr)
+	return lease;
+    }
+
+  return NULL;
+}
+
+#ifdef HAVE_DHCP6
+/* find address for {CLID, IAID, address} */
+struct dhcp_lease *lease6_find(unsigned char *clid, int clid_len, 
+			       int lease_type, unsigned int iaid,
+			       struct in6_addr *addr)
+{
+  struct dhcp_lease *lease;
+  
+  for (lease = leases; lease; lease = lease->next)
+    {
+      if (!(lease->flags & lease_type) || lease->iaid != iaid)
+	continue;
+
+      if (!IN6_ARE_ADDR_EQUAL(&lease->addr6, addr))
+	continue;
+      
+      if ((clid_len != lease->clid_len ||
+	   memcmp(clid, lease->clid, clid_len) != 0))
+	continue;
+      
+      return lease;
+    }
+  
+  return NULL;
+}
+
+/* reset "USED flags */
+void lease6_reset(void)
+{
+  struct dhcp_lease *lease;
+  
+  for (lease = leases; lease; lease = lease->next)
+    lease->flags &= ~LEASE_USED;
+}
+
+/* enumerate all leases belonging to {CLID, IAID} */
+struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
+					 unsigned char *clid, int clid_len,
+					 unsigned int iaid)
+{
+  struct dhcp_lease *lease;
+
+  if (!first)
+    first = leases;
+  else
+    first = first->next;
+
+  for (lease = first; lease; lease = lease->next)
+    {
+      if (lease->flags & LEASE_USED)
+	continue;
+
+      if (!(lease->flags & lease_type) || lease->iaid != iaid)
+	continue;
+ 
+      if ((clid_len != lease->clid_len ||
+	   memcmp(clid, lease->clid, clid_len) != 0))
+	continue;
+
+      return lease;
+    }
+  
+  return NULL;
+}
+
+struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr)
+{
+  struct dhcp_lease *lease;
+    
+  for (lease = leases; lease; lease = lease->next)
+    {
+      if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+	continue;
+      
+      if (is_same_net6(&lease->addr6, net, prefix) &&
+	  (prefix == 128 || addr6part(&lease->addr6) == addr))
+	return lease;
+    }
+  
+  return NULL;
+} 
+
+/* Find largest assigned address in context */
+u64 lease_find_max_addr6(struct dhcp_context *context)
+{
+  struct dhcp_lease *lease;
+  u64 addr = addr6part(&context->start6);
+  
+  if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY)))
+    for (lease = leases; lease; lease = lease->next)
+      {
+	if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+	  continue;
+
+	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;
+}
+
+#endif
+
+/* Find largest assigned address in context */
+struct in_addr lease_find_max_addr(struct dhcp_context *context)
+{
+  struct dhcp_lease *lease;
+  struct in_addr addr = context->start;
+  
+  if (!(context->flags & (CONTEXT_STATIC | CONTEXT_PROXY)))
+    for (lease = leases; lease; lease = lease->next)
+      {
+#ifdef HAVE_DHCP6
+	if (lease->flags & (LEASE_TA | LEASE_NA))
+	  continue;
+#endif
+	if (((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(context->start.s_addr)) &&
+	    ((unsigned)ntohl(lease->addr.s_addr)) <= ((unsigned)ntohl(context->end.s_addr)) &&
+	    ((unsigned)ntohl(lease->addr.s_addr)) > ((unsigned)ntohl(addr.s_addr)))
+	  addr = lease->addr;
+      }
+  
+  return addr;
+}
+
+static struct dhcp_lease *lease_allocate(void)
+{
+  struct dhcp_lease *lease;
+  if (!leases_left || !(lease = whine_malloc(sizeof(struct dhcp_lease))))
+    return NULL;
+
+  memset(lease, 0, sizeof(struct dhcp_lease));
+  lease->flags = LEASE_NEW;
+  lease->expires = 1;
+#ifdef HAVE_BROKEN_RTC
+  lease->length = 0xffffffff; /* illegal value */
+#endif
+  lease->hwaddr_len = 256; /* illegal value */
+  lease->next = leases;
+  leases = lease;
+  
+  file_dirty = 1;
+  leases_left--;
+
+  return lease;
+}
+
+struct dhcp_lease *lease4_allocate(struct in_addr addr)
+{
+  struct dhcp_lease *lease = lease_allocate();
+  if (lease)
+    {
+      lease->addr = addr;
+      daemon->metrics[METRIC_LEASES_ALLOCATED_4]++;
+    }
+  
+  return lease;
+}
+
+#ifdef HAVE_DHCP6
+struct dhcp_lease *lease6_allocate(struct in6_addr *addrp, int lease_type)
+{
+  struct dhcp_lease *lease = lease_allocate();
+
+  if (lease)
+    {
+      lease->addr6 = *addrp;
+      lease->flags |= lease_type;
+      lease->iaid = 0;
+
+      daemon->metrics[METRIC_LEASES_ALLOCATED_6]++;
+    }
+
+  return lease;
+}
+#endif
+
+void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now)
+{
+  time_t exp;
+
+  if (len == 0xffffffff)
+    {
+      exp = 0;
+      len = 0;
+    }
+  else
+    {
+      exp = now + (time_t)len;
+      /* Check for 2038 overflow. Make the lease
+	 infinite in that case, as the least disruptive
+	 thing we can do. */
+      if (difftime(exp, now) <= 0.0)
+	exp = 0;
+    }
+
+  if (exp != lease->expires)
+    {
+      dns_dirty = 1;
+      lease->expires = exp;
+#ifndef HAVE_BROKEN_RTC
+      lease->flags |= LEASE_AUX_CHANGED | LEASE_EXP_CHANGED;
+      file_dirty = 1;
+#endif
+    }
+  
+#ifdef HAVE_BROKEN_RTC
+  if (len != lease->length)
+    {
+      lease->length = len;
+      lease->flags |= LEASE_AUX_CHANGED;
+      file_dirty = 1; 
+    }
+#endif
+} 
+
+#ifdef HAVE_DHCP6
+void lease_set_iaid(struct dhcp_lease *lease, unsigned int iaid)
+{
+  if (lease->iaid != iaid)
+    {
+      lease->iaid = iaid;
+      lease->flags |= LEASE_CHANGED;
+    }
+}
+#endif
+
+void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
+		      const unsigned char *clid, int hw_len, int hw_type,
+		      int clid_len, time_t now, int force)
+{
+#ifdef HAVE_DHCP6
+  int change = force;
+  lease->flags |= LEASE_HAVE_HWADDR;
+#endif
+
+  (void)force;
+  (void)now;
+
+  if (hw_len != lease->hwaddr_len ||
+      hw_type != lease->hwaddr_type || 
+      (hw_len != 0 && memcmp(lease->hwaddr, hwaddr, hw_len) != 0))
+    {
+      if (hw_len != 0)
+	memcpy(lease->hwaddr, hwaddr, hw_len);
+      lease->hwaddr_len = hw_len;
+      lease->hwaddr_type = hw_type;
+      lease->flags |= LEASE_CHANGED;
+      file_dirty = 1; /* run script on change */
+    }
+
+  /* only update clid when one is available, stops packets
+     without a clid removing the record. Lease init uses
+     clid_len == 0 for no clid. */
+  if (clid_len != 0 && clid)
+    {
+      if (!lease->clid)
+	lease->clid_len = 0;
+
+      if (lease->clid_len != clid_len)
+	{
+	  lease->flags |= LEASE_AUX_CHANGED;
+	  file_dirty = 1;
+	  free(lease->clid);
+	  if (!(lease->clid = whine_malloc(clid_len)))
+	    return;
+#ifdef HAVE_DHCP6
+	  change = 1;
+#endif	   
+	}
+      else if (memcmp(lease->clid, clid, clid_len) != 0)
+	{
+	  lease->flags |= LEASE_AUX_CHANGED;
+	  file_dirty = 1;
+#ifdef HAVE_DHCP6
+	  change = 1;
+#endif	
+	}
+      
+      lease->clid_len = clid_len;
+      memcpy(lease->clid, clid, clid_len);
+    }
+  
+#ifdef HAVE_DHCP6
+  if (change)
+    slaac_add_addrs(lease, now, force);
+#endif
+}
+
+static void kill_name(struct dhcp_lease *lease)
+{
+  /* run script to say we lost our old name */
+  
+  /* this shouldn't happen unless updates are very quick and the
+     script very slow, we just avoid a memory leak if it does. */
+  free(lease->old_hostname);
+  
+  /* If we know the fqdn, pass that. The helper will derive the
+     unqualified name from it, free the unqualified name here. */
+
+  if (lease->fqdn)
+    {
+      lease->old_hostname = lease->fqdn;
+      free(lease->hostname);
+    }
+  else
+    lease->old_hostname = lease->hostname;
+
+  lease->hostname = lease->fqdn = NULL;
+}
+
+void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain)
+{
+  struct dhcp_lease *lease_tmp;
+  char *new_name = NULL, *new_fqdn = NULL;
+
+  if (config_domain && (!domain || !hostname_isequal(domain, config_domain)))
+    my_syslog(MS_DHCP | LOG_WARNING, _("Ignoring domain %s for DHCP host name %s"), config_domain, name);
+  
+  if (lease->hostname && name && hostname_isequal(lease->hostname, name))
+    {
+      if (auth)
+	lease->flags |= LEASE_AUTH_NAME;
+      return;
+    }
+  
+  if (!name && !lease->hostname)
+    return;
+
+  /* If a machine turns up on a new net without dropping the old lease,
+     or two machines claim the same name, then we end up with two interfaces with
+     the same name. Check for that here and remove the name from the old lease.
+     Note that IPv6 leases are different. All the leases to the same DUID are 
+     allowed the same name.
+
+     Don't allow a name from the client to override a name from dnsmasq config. */
+  
+  if (name)
+    {
+      if ((new_name = whine_malloc(strlen(name) + 1)))
+	{
+	  strcpy(new_name, name);
+	  if (domain && (new_fqdn = whine_malloc(strlen(new_name) + strlen(domain) + 2)))
+	    {
+	      strcpy(new_fqdn, name);
+	      strcat(new_fqdn, ".");
+	      strcat(new_fqdn, domain);
+	    }
+	}
+	  
+      /* Depending on mode, we check either unqualified name or FQDN. */
+      for (lease_tmp = leases; lease_tmp; lease_tmp = lease_tmp->next)
+	{
+	  if (option_bool(OPT_DHCP_FQDN))
+	    {
+	      if (!new_fqdn || !lease_tmp->fqdn || !hostname_isequal(lease_tmp->fqdn, new_fqdn))
+		continue;
+	    }
+	  else
+	    {
+	      if (!new_name || !lease_tmp->hostname || !hostname_isequal(lease_tmp->hostname, new_name) )
+		continue; 
+	    }
+
+	  if (lease->flags & (LEASE_TA | LEASE_NA))
+	    {
+	      if (!(lease_tmp->flags & (LEASE_TA | LEASE_NA)))
+		continue;
+
+	      /* another lease for the same DUID is OK for IPv6 */
+	      if (lease->clid_len == lease_tmp->clid_len &&
+		  lease->clid && lease_tmp->clid &&
+		  memcmp(lease->clid, lease_tmp->clid, lease->clid_len) == 0)
+		continue;	      
+	    }
+	  else if (lease_tmp->flags & (LEASE_TA | LEASE_NA))
+	    continue;
+		   
+	  if ((lease_tmp->flags & LEASE_AUTH_NAME) && !auth)
+	    {
+	      free(new_name);
+	      free(new_fqdn);
+	      return;
+	    }
+	
+	  kill_name(lease_tmp);
+	  lease_tmp->flags |= LEASE_CHANGED; /* run script on change */
+	  break;
+	}
+    }
+
+  if (lease->hostname)
+    kill_name(lease);
+
+  lease->hostname = new_name;
+  lease->fqdn = new_fqdn;
+  
+  if (auth)
+    lease->flags |= LEASE_AUTH_NAME;
+  
+  file_dirty = 1;
+  dns_dirty = 1; 
+  lease->flags |= LEASE_CHANGED; /* run script on change */
+}
+
+void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
+{
+  (void)now;
+
+  if (lease->last_interface == interface)
+    return;
+
+  lease->last_interface = interface;
+  lease->flags |= LEASE_CHANGED; 
+
+#ifdef HAVE_DHCP6
+  slaac_add_addrs(lease, now, 0);
+#endif
+}
+
+void rerun_scripts(void)
+{
+  struct dhcp_lease *lease;
+  
+  for (lease = leases; lease; lease = lease->next)
+    lease->flags |= LEASE_CHANGED; 
+}
+
+/* deleted leases get transferred to the old_leases list.
+   remove them here, after calling the lease change
+   script. Also run the lease change script on new/modified leases.
+
+   Return zero if nothing to do. */
+int do_script_run(time_t now)
+{
+  struct dhcp_lease *lease;
+
+  (void)now;
+
+#ifdef HAVE_DBUS
+  /* If we're going to be sending DBus signals, but the connection is not yet up,
+     delay everything until it is. */
+  if (option_bool(OPT_DBUS) && !daemon->dbus)
+    return 0;
+#endif
+
+  if (old_leases)
+    {
+      lease = old_leases;
+                  
+      /* If the lease still has an old_hostname, do the "old" action on that first */
+      if (lease->old_hostname)
+	{
+#ifdef HAVE_SCRIPT
+	  queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now);
+#endif
+	  free(lease->old_hostname);
+	  lease->old_hostname = NULL;
+	  return 1;
+	}
+      else 
+	{
+#ifdef HAVE_DHCP6
+	  struct slaac_address *slaac, *tmp;
+	  for (slaac = lease->slaac_address; slaac; slaac = tmp)
+	    {
+	      tmp = slaac->next;
+	      free(slaac);
+	    }
+#endif
+	  kill_name(lease);
+#ifdef HAVE_SCRIPT
+	  queue_script(ACTION_DEL, lease, lease->old_hostname, now);
+#endif
+#ifdef HAVE_DBUS
+	  emit_dbus_signal(ACTION_DEL, lease, lease->old_hostname);
+#endif
+	  old_leases = lease->next;
+	  
+	  free(lease->old_hostname); 
+	  free(lease->clid);
+	  free(lease->extradata);
+	  free(lease);
+	    
+	  return 1; 
+	}
+    }
+  
+  /* make sure we announce the loss of a hostname before its new location. */
+  for (lease = leases; lease; lease = lease->next)
+    if (lease->old_hostname)
+      {	
+#ifdef HAVE_SCRIPT
+	queue_script(ACTION_OLD_HOSTNAME, lease, lease->old_hostname, now);
+#endif
+	free(lease->old_hostname);
+	lease->old_hostname = NULL;
+	return 1;
+      }
+  
+  for (lease = leases; lease; lease = lease->next)
+    if ((lease->flags & (LEASE_NEW | LEASE_CHANGED)) || 
+	((lease->flags & LEASE_AUX_CHANGED) && option_bool(OPT_LEASE_RO)) ||
+	((lease->flags & LEASE_EXP_CHANGED) && option_bool(OPT_LEASE_RENEW)))
+      {
+#ifdef HAVE_SCRIPT
+	queue_script((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease, 
+		     lease->fqdn ? lease->fqdn : lease->hostname, now);
+#endif
+#ifdef HAVE_DBUS
+	emit_dbus_signal((lease->flags & LEASE_NEW) ? ACTION_ADD : ACTION_OLD, lease,
+			 lease->fqdn ? lease->fqdn : lease->hostname);
+#endif
+	lease->flags &= ~(LEASE_NEW | LEASE_CHANGED | LEASE_AUX_CHANGED | LEASE_EXP_CHANGED);
+	
+	/* this is used for the "add" call, then junked, since they're not in the database */
+	free(lease->extradata);
+	lease->extradata = NULL;
+	
+	return 1;
+      }
+
+  return 0; /* nothing to do */
+}
+
+#ifdef HAVE_SCRIPT
+/* delim == -1 -> delim = 0, but embedded 0s, creating extra records, are OK. */
+void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data, unsigned int len, int delim)
+{
+  unsigned int i;
+  
+  if (delim == -1)
+    delim = 0;
+  else
+    /* check for embedded NULLs */
+    for (i = 0; i < len; i++)
+      if (data[i] == 0)
+	{
+	  len = i;
+	  break;
+	}
+  
+  if ((lease->extradata_size - lease->extradata_len) < (len + 1))
+    {
+      size_t newsz = lease->extradata_len + len + 100;
+      unsigned char *new = whine_malloc(newsz);
+  
+      if (!new)
+	return;
+      
+      if (lease->extradata)
+	{
+	  memcpy(new, lease->extradata, lease->extradata_len);
+	  free(lease->extradata);
+	}
+
+      lease->extradata = new;
+      lease->extradata_size = newsz;
+    }
+
+  if (len != 0)
+    memcpy(lease->extradata + lease->extradata_len, data, len);
+  lease->extradata[lease->extradata_len + len] = delim;
+  lease->extradata_len += len + 1; 
+}
+#endif
+
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/log.c b/ap/app/dnsmasq/dnsmasq-2.86/src/log.c
new file mode 100755
index 0000000..1ec3447
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/log.c
@@ -0,0 +1,481 @@
+/* dnsmasq is Copyright (c) 2000-2021 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 __ANDROID__
+#  include <android/log.h>
+#endif
+
+/* Implement logging to /dev/log asynchronously. If syslogd is 
+   making DNS lookups through dnsmasq, and dnsmasq blocks awaiting
+   syslogd, then the two daemons can deadlock. We get around this
+   by not blocking when talking to syslog, instead we queue up to 
+   MAX_LOGS messages. If more are queued, they will be dropped,
+   and the drop event itself logged. */
+
+/* The "wire" protocol for logging is defined in RFC 3164 */
+
+/* From RFC 3164 */
+#define MAX_MESSAGE 1024
+
+/* defaults in case we die() before we log_start() */
+static int log_fac = LOG_DAEMON;
+static int log_stderr = 0;
+static int echo_stderr = 0;
+static int log_fd = -1;
+static int log_to_file = 0;
+static int entries_alloced = 0;
+static int entries_lost = 0;
+static int connection_good = 1;
+static int max_logs = 0;
+static int connection_type = SOCK_DGRAM;
+
+struct log_entry {
+  int offset, length;
+  pid_t pid; /* to avoid duplicates over a fork */
+  struct log_entry *next;
+  char payload[MAX_MESSAGE];
+};
+
+static struct log_entry *entries = NULL;
+static struct log_entry *free_entries = NULL;
+
+
+int log_start(struct passwd *ent_pw, int errfd)
+{
+  int ret = 0;
+
+  echo_stderr = option_bool(OPT_DEBUG);
+
+  if (daemon->log_fac != -1)
+    log_fac = daemon->log_fac;
+#ifdef LOG_LOCAL0
+  else if (option_bool(OPT_DEBUG))
+    log_fac = LOG_LOCAL0;
+#endif
+
+  if (daemon->log_file)
+    { 
+      log_to_file = 1;
+      daemon->max_logs = 0;
+      if (strcmp(daemon->log_file, "-") == 0)
+	{
+	  log_stderr = 1;
+	  echo_stderr = 0;
+	  log_fd = dup(STDERR_FILENO);
+	}
+    }
+  
+  max_logs = daemon->max_logs;
+
+  if (!log_reopen(daemon->log_file))
+    {
+      send_event(errfd, EVENT_LOG_ERR, errno, daemon->log_file ? daemon->log_file : "");
+      _exit(0);
+    }
+
+  /* if queuing is inhibited, make sure we allocate
+     the one required buffer now. */
+  if (max_logs == 0)
+    {  
+      free_entries = safe_malloc(sizeof(struct log_entry));
+      free_entries->next = NULL;
+      entries_alloced = 1;
+    }
+
+  /* If we're running as root and going to change uid later,
+     change the ownership here so that the file is always owned by
+     the dnsmasq user. Then logrotate can just copy the owner.
+     Failure of the chown call is OK, (for instance when started as non-root) */
+  if (log_to_file && !log_stderr && ent_pw && ent_pw->pw_uid != 0 && 
+      fchown(log_fd, ent_pw->pw_uid, -1) != 0)
+    ret = errno;
+
+  return ret;
+}
+
+int log_reopen(char *log_file)
+{
+  if (!log_stderr)
+    {      
+      if (log_fd != -1)
+	close(log_fd);
+      
+      /* NOTE: umask is set to 022 by the time this gets called */
+      
+      if (log_file)
+	log_fd = open(log_file, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR|S_IRGRP);      
+      else
+	{
+#if defined(HAVE_SOLARIS_NETWORK) || defined(__ANDROID__)
+	  /* Solaris logging is "different", /dev/log is not unix-domain socket.
+	     Just leave log_fd == -1 and use the vsyslog call for everything.... */
+#   define _PATH_LOG ""  /* dummy */
+	  return 1;
+#else
+	  int flags;
+	  log_fd = socket(AF_UNIX, connection_type, 0);
+	  
+	  /* if max_logs is zero, leave the socket blocking */
+	  if (log_fd != -1 && max_logs != 0 && (flags = fcntl(log_fd, F_GETFL)) != -1)
+	    fcntl(log_fd, F_SETFL, flags | O_NONBLOCK);
+#endif
+	}
+    }
+  
+  return log_fd != -1;
+}
+
+static void free_entry(void)
+{
+  struct log_entry *tmp = entries;
+  entries = tmp->next;
+  tmp->next = free_entries;
+  free_entries = tmp;
+}      
+
+static void log_write(void)
+{
+  ssize_t rc;
+   
+  while (entries)
+    {
+      /* The data in the payload is written with a terminating zero character 
+	 and the length reflects this. For a stream connection we need to 
+	 send the zero as a record terminator, but this isn't done for a 
+	 datagram connection, so treat the length as one less than reality 
+	 to elide the zero. If we're logging to a file, turn the zero into 
+	 a newline, and leave the length alone. */
+      int len_adjust = 0;
+
+      if (log_to_file)
+	entries->payload[entries->offset + entries->length - 1] = '\n';
+      else if (connection_type == SOCK_DGRAM)
+	len_adjust = 1;
+
+      /* Avoid duplicates over a fork() */
+      if (entries->pid != getpid())
+	{
+	  free_entry();
+	  continue;
+	}
+
+      connection_good = 1;
+
+      if ((rc = write(log_fd, entries->payload + entries->offset, entries->length - len_adjust)) != -1)
+	{
+	  entries->length -= rc;
+	  entries->offset += rc;
+	  if (entries->length == len_adjust)
+	    {
+	      free_entry();
+	      if (entries_lost != 0)
+		{
+		  int e = entries_lost;
+		  entries_lost = 0; /* avoid wild recursion */
+		  my_syslog(LOG_WARNING, _("overflow: %d log entries lost"), e);
+		}	  
+	    }
+	  continue;
+	}
+      
+      if (errno == EINTR)
+	continue;
+
+      if (errno == EAGAIN || errno == EWOULDBLOCK)
+	return; /* syslogd busy, go again when select() or poll() says so */
+      
+      if (errno == ENOBUFS)
+	{
+	  connection_good = 0;
+	  return;
+	}
+
+      /* errors handling after this assumes sockets */ 
+      if (!log_to_file)
+	{
+	  /* Once a stream socket hits EPIPE, we have to close and re-open
+	     (we ignore SIGPIPE) */
+	  if (errno == EPIPE)
+	    {
+	      if (log_reopen(NULL))
+		continue;
+	    }
+	  else if (errno == ECONNREFUSED || 
+		   errno == ENOTCONN || 
+		   errno == EDESTADDRREQ || 
+		   errno == ECONNRESET)
+	    {
+	      /* socket went (syslogd down?), try and reconnect. If we fail,
+		 stop trying until the next call to my_syslog() 
+		 ECONNREFUSED -> connection went down
+		 ENOTCONN -> nobody listening
+		 (ECONNRESET, EDESTADDRREQ are *BSD equivalents) */
+	      
+	      struct sockaddr_un logaddr;
+	      
+#ifdef HAVE_SOCKADDR_SA_LEN
+	      logaddr.sun_len = sizeof(logaddr) - sizeof(logaddr.sun_path) + strlen(_PATH_LOG) + 1; 
+#endif
+	      logaddr.sun_family = AF_UNIX;
+	      safe_strncpy(logaddr.sun_path, _PATH_LOG, sizeof(logaddr.sun_path));
+	      
+	      /* Got connection back? try again. */
+	      if (connect(log_fd, (struct sockaddr *)&logaddr, sizeof(logaddr)) != -1)
+		continue;
+	      
+	      /* errors from connect which mean we should keep trying */
+	      if (errno == ENOENT || 
+		  errno == EALREADY || 
+		  errno == ECONNREFUSED ||
+		  errno == EISCONN || 
+		  errno == EINTR ||
+		  errno == EAGAIN || 
+		  errno == EWOULDBLOCK)
+		{
+		  /* try again on next syslog() call */
+		  connection_good = 0;
+		  return;
+		}
+	      
+	      /* try the other sort of socket... */
+	      if (errno == EPROTOTYPE)
+		{
+		  connection_type = connection_type == SOCK_DGRAM ? SOCK_STREAM : SOCK_DGRAM;
+		  if (log_reopen(NULL))
+		    continue;
+		}
+	    }
+	}
+
+      /* give up - fall back to syslog() - this handles out-of-space
+	 when logging to a file, for instance. */
+      log_fd = -1;
+      my_syslog(LOG_CRIT, _("log failed: %s"), strerror(errno));
+      return;
+    }
+}
+
+/* priority is one of LOG_DEBUG, LOG_INFO, LOG_NOTICE, etc. See sys/syslog.h.
+   OR'd to priority can be MS_TFTP, MS_DHCP, ... to be able to do log separation between
+   DNS, DHCP and TFTP services.
+   If OR'd with MS_DEBUG, the messages are suppressed unless --log-debug is set. */
+void my_syslog(int priority, const char *format, ...)
+{
+  va_list ap;
+  struct log_entry *entry;
+  time_t time_now;
+  char *p;
+  size_t len;
+  pid_t pid = getpid();
+  char *func = "";
+
+  if ((LOG_FACMASK & priority) == MS_TFTP)
+    func = "-tftp";
+  else if ((LOG_FACMASK & priority) == MS_DHCP)
+    func = "-dhcp";
+  else if ((LOG_FACMASK & priority) == MS_SCRIPT)
+    func = "-script";
+  else if ((LOG_FACMASK & priority) == MS_DEBUG)
+    {
+      if (!option_bool(OPT_LOG_DEBUG))
+	return;
+      func = "-debug";
+    }
+  
+#ifdef LOG_PRI
+  priority = LOG_PRI(priority);
+#else
+  /* Solaris doesn't have LOG_PRI */
+  priority &= LOG_PRIMASK;
+#endif
+
+  if (echo_stderr) 
+    {
+      fprintf(stderr, "dnsmasq%s: ", func);
+      va_start(ap, format);
+      vfprintf(stderr, format, ap);
+      va_end(ap);
+      fputc('\n', stderr);
+    }
+
+  if (log_fd == -1)
+    {
+#ifdef __ANDROID__
+      /* do android-specific logging. 
+	 log_fd is always -1 on Android except when logging to a file. */
+      int alog_lvl;
+      
+      if (priority <= LOG_ERR)
+	alog_lvl = ANDROID_LOG_ERROR;
+      else if (priority == LOG_WARNING)
+	alog_lvl = ANDROID_LOG_WARN;
+      else if (priority <= LOG_INFO)
+	alog_lvl = ANDROID_LOG_INFO;
+      else
+	alog_lvl = ANDROID_LOG_DEBUG;
+
+      va_start(ap, format);
+      __android_log_vprint(alog_lvl, "dnsmasq", format, ap);
+      va_end(ap);
+#else
+      /* fall-back to syslog if we die during startup or 
+	 fail during running (always on Solaris). */
+      static int isopen = 0;
+
+      if (!isopen)
+	{
+	  openlog("dnsmasq", LOG_PID, log_fac);
+	  isopen = 1;
+	}
+      va_start(ap, format);  
+      vsyslog(priority, format, ap);
+      va_end(ap);
+#endif
+
+      return;
+    }
+  
+  if ((entry = free_entries))
+    free_entries = entry->next;
+  else if (entries_alloced < max_logs && (entry = malloc(sizeof(struct log_entry))))
+    entries_alloced++;
+  
+  if (!entry)
+    entries_lost++;
+  else
+    {
+      /* add to end of list, consumed from the start */
+      entry->next = NULL;
+      if (!entries)
+	entries = entry;
+      else
+	{
+	  struct log_entry *tmp;
+	  for (tmp = entries; tmp->next; tmp = tmp->next);
+	  tmp->next = entry;
+	}
+      
+      time(&time_now);
+      p = entry->payload;
+      if (!log_to_file)
+	p += sprintf(p, "<%d>", priority | log_fac);
+
+      /* Omit timestamp for default daemontools situation */
+      if (!log_stderr || !option_bool(OPT_NO_FORK)) 
+	p += sprintf(p, "%.15s ", ctime(&time_now) + 4);
+      
+      p += sprintf(p, "dnsmasq%s[%d]: ", func, (int)pid);
+        
+      len = p - entry->payload;
+      va_start(ap, format);  
+      len += vsnprintf(p, MAX_MESSAGE - len, format, ap) + 1; /* include zero-terminator */
+      va_end(ap);
+      entry->length = len > MAX_MESSAGE ? MAX_MESSAGE : len;
+      entry->offset = 0;
+      entry->pid = pid;
+    }
+  
+  /* almost always, logging won't block, so try and write this now,
+     to save collecting too many log messages during a select loop. */
+  log_write();
+  
+  /* Since we're doing things asynchronously, a cache-dump, for instance,
+     can now generate log lines very fast. With a small buffer (desirable),
+     that means it can overflow the log-buffer very quickly,
+     so that the cache dump becomes mainly a count of how many lines 
+     overflowed. To avoid this, we delay here, the delay is controlled 
+     by queue-occupancy, and grows exponentially. The delay is limited to (2^8)ms.
+     The scaling stuff ensures that when the queue is bigger than 8, the delay
+     only occurs for the last 8 entries. Once the queue is full, we stop delaying
+     to preserve performance.
+  */
+
+  if (entries && max_logs != 0)
+    {
+      int d;
+      
+      for (d = 0,entry = entries; entry; entry = entry->next, d++);
+      
+      if (d == max_logs)
+	d = 0;
+      else if (max_logs > 8)
+	d -= max_logs - 8;
+
+      if (d > 0)
+	{
+	  struct timespec waiter;
+	  waiter.tv_sec = 0;
+	  waiter.tv_nsec = 1000000 << (d - 1); /* 1 ms */
+	  nanosleep(&waiter, NULL);
+      
+	  /* Have another go now */
+	  log_write();
+	}
+    } 
+}
+
+void set_log_writer(void)
+{
+  if (entries && log_fd != -1 && connection_good)
+    poll_listen(log_fd, POLLOUT);
+}
+
+void check_log_writer(int force)
+{
+  if (log_fd != -1 && (force || poll_check(log_fd, POLLOUT)))
+    log_write();
+}
+
+void flush_log(void)
+{
+  /* write until queue empty, but don't loop forever if there's
+   no connection to the syslog in existence */
+  while (log_fd != -1)
+    {
+      struct timespec waiter;
+      log_write();
+      if (!entries || !connection_good)
+	{
+	  close(log_fd);	
+	  break;
+	}
+      waiter.tv_sec = 0;
+      waiter.tv_nsec = 1000000; /* 1 ms */
+      nanosleep(&waiter, NULL);
+    }
+}
+
+void die(char *message, char *arg1, int exit_code)
+{
+  char *errmess = strerror(errno);
+  
+  if (!arg1)
+    arg1 = errmess;
+
+  if (!log_stderr)
+    {
+      echo_stderr = 1; /* print as well as log when we die.... */
+      fputc('\n', stderr); /* prettyfy  startup-script message */
+    }
+  my_syslog(LOG_CRIT, message, arg1, errmess);
+  echo_stderr = 0;
+  my_syslog(LOG_CRIT, _("FAILED to start up"));
+  flush_log();
+  
+  exit(exit_code);
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c b/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c
new file mode 100755
index 0000000..01f0c28
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/loop.c
@@ -0,0 +1,113 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_LOOP
+static ssize_t loop_make_probe(u32 uid);
+
+void loop_send_probes()
+{
+   struct server *serv;
+   struct randfd_list *rfds = NULL;
+   
+   if (!option_bool(OPT_LOOP_DETECT))
+     return;
+
+   /* Loop through all upstream servers not for particular domains, and send a query to that server which is
+      identifiable, via the uid. If we see that query back again, then the server is looping, and we should not use it. */
+   for (serv = daemon->servers; serv; serv = serv->next)
+     if (strlen(serv->domain) == 0 &&
+	 !(serv->flags & (SERV_FOR_NODOTS)))
+       {
+	 ssize_t len = loop_make_probe(serv->uid);
+	 int fd;
+	 
+	 serv->flags &= ~SERV_LOOP;
+
+	 if ((fd = allocate_rfd(&rfds, serv)) == -1)
+	   continue;
+	 
+	 while (retry_send(sendto(fd, daemon->packet, len, 0, 
+				  &serv->addr.sa, sa_len(&serv->addr))));
+       }
+
+   free_rfds(&rfds);
+}
+  
+static ssize_t loop_make_probe(u32 uid)
+{
+  struct dns_header *header = (struct dns_header *)daemon->packet;
+  unsigned char *p = (unsigned char *)(header+1);
+  
+  /* packet buffer overwritten */
+  daemon->srv_save = NULL;
+  
+  header->id = rand16();
+  header->ancount = header->nscount = header->arcount = htons(0);
+  header->qdcount = htons(1);
+  header->hb3 = HB3_RD;
+  header->hb4 = 0;
+  SET_OPCODE(header, QUERY);
+
+  *p++ = 8;
+  sprintf((char *)p, "%.8x", uid);
+  p += 8;
+  *p++ = strlen(LOOP_TEST_DOMAIN);
+  strcpy((char *)p, LOOP_TEST_DOMAIN); /* Add terminating zero */
+  p += strlen(LOOP_TEST_DOMAIN) + 1;
+
+  PUTSHORT(LOOP_TEST_TYPE, p);
+  PUTSHORT(C_IN, p);
+
+  return p - (unsigned char *)header;
+}
+  
+
+int detect_loop(char *query, int type)
+{
+  int i;
+  u32 uid;
+  struct server *serv;
+  
+  if (!option_bool(OPT_LOOP_DETECT))
+    return 0;
+
+  if (type != LOOP_TEST_TYPE ||
+      strlen(LOOP_TEST_DOMAIN) + 9 != strlen(query) ||
+      strstr(query, LOOP_TEST_DOMAIN) != query + 9)
+    return 0;
+
+  for (i = 0; i < 8; i++)
+    if (!isxdigit(query[i]))
+      return 0;
+
+  uid = strtol(query, NULL, 16);
+
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (strlen(serv->domain) == 0 &&
+	!(serv->flags & SERV_LOOP) &&
+	uid == serv->uid)
+      {
+	serv->flags |= SERV_LOOP;
+	check_servers(1); /* log new state - don't send more probes. */
+	return 1;
+      }
+  
+  return 0;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c
new file mode 100755
index 0000000..fac5b00
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.c
@@ -0,0 +1,44 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+const char * metric_names[] = {
+    "dns_cache_inserted",
+    "dns_cache_live_freed",
+    "dns_queries_forwarded",
+    "dns_auth_answered",
+    "dns_local_answered",
+    "bootp",
+    "pxe",
+    "dhcp_ack",
+    "dhcp_decline",
+    "dhcp_discover",
+    "dhcp_inform",
+    "dhcp_nak",
+    "dhcp_offer",
+    "dhcp_release",
+    "dhcp_request",
+    "noanswer",
+    "leases_allocated_4",
+    "leases_pruned_4",
+    "leases_allocated_6",
+    "leases_pruned_6",
+};
+
+const char* get_metric_name(int i) {
+    return metric_names[i];
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h
new file mode 100755
index 0000000..cecf8c8
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/metrics.h
@@ -0,0 +1,43 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* If you modify this list, please keep the labels in metrics.c in sync. */
+enum {
+  METRIC_DNS_CACHE_INSERTED,
+  METRIC_DNS_CACHE_LIVE_FREED,
+  METRIC_DNS_QUERIES_FORWARDED,
+  METRIC_DNS_AUTH_ANSWERED,
+  METRIC_DNS_LOCAL_ANSWERED,
+  METRIC_BOOTP,
+  METRIC_PXE,
+  METRIC_DHCPACK,
+  METRIC_DHCPDECLINE,
+  METRIC_DHCPDISCOVER,
+  METRIC_DHCPINFORM,
+  METRIC_DHCPNAK,
+  METRIC_DHCPOFFER,
+  METRIC_DHCPRELEASE,
+  METRIC_DHCPREQUEST,
+  METRIC_NOANSWER,
+  METRIC_LEASES_ALLOCATED_4,
+  METRIC_LEASES_PRUNED_4,
+  METRIC_LEASES_ALLOCATED_6,
+  METRIC_LEASES_PRUNED_6,
+  
+  __METRIC_MAX,
+};
+
+const char* get_metric_name(int);
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c b/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c
new file mode 100755
index 0000000..7840ef9
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/netlink.c
@@ -0,0 +1,405 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_LINUX_NETWORK
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+/* Blergh. Radv does this, so that's our excuse. */
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#ifndef NETLINK_NO_ENOBUFS
+#define NETLINK_NO_ENOBUFS 5
+#endif
+
+/* linux 2.6.19 buggers up the headers, patch it up here. */ 
+#ifndef IFA_RTA
+#  define IFA_RTA(r)  \
+       ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifaddrmsg))))
+
+#  include <linux/if_addr.h>
+#endif
+
+#ifndef NDA_RTA
+#  define NDA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ndmsg)))) 
+#endif
+
+/* Used to request refresh of addresses or routes just once,
+ * when multiple changes might be announced. */
+enum async_states {
+  STATE_NEWADDR = (1 << 0),
+  STATE_NEWROUTE = (1 << 1),
+};
+
+
+static struct iovec iov;
+static u32 netlink_pid;
+
+static unsigned nl_async(struct nlmsghdr *h, unsigned state);
+static void nl_multicast_state(unsigned state);
+
+char *netlink_init(void)
+{
+  struct sockaddr_nl addr;
+  socklen_t slen = sizeof(addr);
+
+  addr.nl_family = AF_NETLINK;
+  addr.nl_pad = 0;
+  addr.nl_pid = 0; /* autobind */
+  addr.nl_groups = RTMGRP_IPV4_ROUTE;
+  if (option_bool(OPT_CLEVERBIND))
+    addr.nl_groups |= RTMGRP_IPV4_IFADDR;  
+  addr.nl_groups |= RTMGRP_IPV6_ROUTE;
+  if (option_bool(OPT_CLEVERBIND))
+    addr.nl_groups |= RTMGRP_IPV6_IFADDR;
+
+#ifdef HAVE_DHCP6
+  if (daemon->doing_ra || daemon->doing_dhcp6)
+    addr.nl_groups |= RTMGRP_IPV6_IFADDR;
+#endif
+  
+  /* May not be able to have permission to set multicast groups don't die in that case */
+  if ((daemon->netlinkfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) != -1)
+    {
+      if (bind(daemon->netlinkfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+	{
+	  addr.nl_groups = 0;
+	  if (errno != EPERM || bind(daemon->netlinkfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+	    daemon->netlinkfd = -1;
+	}
+    }
+  
+  if (daemon->netlinkfd == -1 || 
+      getsockname(daemon->netlinkfd, (struct sockaddr *)&addr, &slen) == -1)
+    die(_("cannot create netlink socket: %s"), NULL, EC_MISC);
+  
+  
+  /* save pid assigned by bind() and retrieved by getsockname() */ 
+  netlink_pid = addr.nl_pid;
+  
+  iov.iov_len = 100;
+  iov.iov_base = safe_malloc(iov.iov_len);
+  
+  return NULL;
+}
+
+static ssize_t netlink_recv(int flags)
+{
+  struct msghdr msg;
+  struct sockaddr_nl nladdr;
+  ssize_t rc;
+
+  while (1)
+    {
+      msg.msg_control = NULL;
+      msg.msg_controllen = 0;
+      msg.msg_name = &nladdr;
+      msg.msg_namelen = sizeof(nladdr);
+      msg.msg_iov = &iov;
+      msg.msg_iovlen = 1;
+      msg.msg_flags = 0;
+      
+      while ((rc = recvmsg(daemon->netlinkfd, &msg, flags | MSG_PEEK | MSG_TRUNC)) == -1 &&
+	     errno == EINTR);
+      
+      /* make buffer big enough */
+      if (rc != -1 && (msg.msg_flags & MSG_TRUNC))
+	{
+	  /* Very new Linux kernels return the actual size needed, older ones always return truncated size */
+	  if ((size_t)rc == iov.iov_len)
+	    {
+	      if (expand_buf(&iov, rc + 100))
+		continue;
+	    }
+	  else
+	    expand_buf(&iov, rc);
+	}
+
+      /* read it for real */
+      msg.msg_flags = 0;
+      while ((rc = recvmsg(daemon->netlinkfd, &msg, flags)) == -1 && errno == EINTR);
+      
+      /* Make sure this is from the kernel */
+      if (rc == -1 || nladdr.nl_pid == 0)
+	break;
+    }
+      
+  /* discard stuff which is truncated at this point (expand_buf() may fail) */
+  if (msg.msg_flags & MSG_TRUNC)
+    {
+      rc = -1;
+      errno = ENOMEM;
+    }
+  
+  return rc;
+}
+  
+
+/* family = AF_UNSPEC finds ARP table entries.
+   family = AF_LOCAL finds MAC addresses.
+   returns 0 on failure, 1 on success, -1 when restart is required
+*/
+int iface_enumerate(int family, void *parm, int (*callback)())
+{
+  struct sockaddr_nl addr;
+  struct nlmsghdr *h;
+  ssize_t len;
+  static unsigned int seq = 0;
+  int callback_ok = 1;
+  unsigned state = 0;
+
+  struct {
+    struct nlmsghdr nlh;
+    struct rtgenmsg g; 
+  } req;
+
+  memset(&req, 0, sizeof(req));
+  memset(&addr, 0, sizeof(addr));
+
+  addr.nl_family = AF_NETLINK;
+ 
+  if (family == AF_UNSPEC)
+    req.nlh.nlmsg_type = RTM_GETNEIGH;
+  else if (family == AF_LOCAL)
+    req.nlh.nlmsg_type = RTM_GETLINK;
+  else
+    req.nlh.nlmsg_type = RTM_GETADDR;
+
+  req.nlh.nlmsg_len = sizeof(req);
+  req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST | NLM_F_ACK; 
+  req.nlh.nlmsg_pid = 0;
+  req.nlh.nlmsg_seq = ++seq;
+  req.g.rtgen_family = family; 
+
+  /* Don't block in recvfrom if send fails */
+  while(retry_send(sendto(daemon->netlinkfd, (void *)&req, sizeof(req), 0, 
+			  (struct sockaddr *)&addr, sizeof(addr))));
+
+  if (errno != 0)
+    return 0;
+    
+  while (1)
+    {
+      if ((len = netlink_recv(0)) == -1)
+	{
+	  if (errno == ENOBUFS)
+	    {
+	      nl_multicast_state(state);
+	      return -1;
+	    }
+	  return 0;
+	}
+
+      for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
+	if (h->nlmsg_pid != netlink_pid || h->nlmsg_type == NLMSG_ERROR)
+	  {
+	    /* May be multicast arriving async */
+	    state = nl_async(h, state);
+	  }
+	else if (h->nlmsg_seq != seq)
+	  {
+	    /* May be part of incomplete response to previous request after
+	       ENOBUFS. Drop it. */
+	    continue;
+	  }
+	else if (h->nlmsg_type == NLMSG_DONE)
+	  return callback_ok;
+	else if (h->nlmsg_type == RTM_NEWADDR && family != AF_UNSPEC && family != AF_LOCAL)
+	  {
+	    struct ifaddrmsg *ifa = NLMSG_DATA(h);  
+	    struct rtattr *rta = IFA_RTA(ifa);
+	    unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa));
+	    
+	    if (ifa->ifa_family == family)
+	      {
+		if (ifa->ifa_family == AF_INET)
+		  {
+		    struct in_addr netmask, addr, broadcast;
+		    char *label = NULL;
+
+		    netmask.s_addr = htonl(~(in_addr_t)0 << (32 - ifa->ifa_prefixlen));
+
+		    addr.s_addr = 0;
+		    broadcast.s_addr = 0;
+		    
+		    while (RTA_OK(rta, len1))
+		      {
+			if (rta->rta_type == IFA_LOCAL)
+			  addr = *((struct in_addr *)(rta+1));
+			else if (rta->rta_type == IFA_BROADCAST)
+			  broadcast = *((struct in_addr *)(rta+1));
+			else if (rta->rta_type == IFA_LABEL)
+			  label = RTA_DATA(rta);
+			
+			rta = RTA_NEXT(rta, len1);
+		      }
+		    
+		    if (addr.s_addr && callback_ok)
+		      if (!((*callback)(addr, ifa->ifa_index, label,  netmask, broadcast, parm)))
+			callback_ok = 0;
+		  }
+		else if (ifa->ifa_family == AF_INET6)
+		  {
+		    struct in6_addr *addrp = NULL;
+		    u32 valid = 0, preferred = 0;
+		    int flags = 0;
+		    
+		    while (RTA_OK(rta, len1))
+		      {
+			if (rta->rta_type == IFA_ADDRESS)
+			  addrp = ((struct in6_addr *)(rta+1)); 
+			else if (rta->rta_type == IFA_CACHEINFO)
+			  {
+			    struct ifa_cacheinfo *ifc = (struct ifa_cacheinfo *)(rta+1);
+			    preferred = ifc->ifa_prefered;
+			    valid = ifc->ifa_valid;
+			  }
+			rta = RTA_NEXT(rta, len1);
+		      }
+		    
+		    if (ifa->ifa_flags & IFA_F_TENTATIVE)
+		      flags |= IFACE_TENTATIVE;
+		    
+		    if (ifa->ifa_flags & IFA_F_DEPRECATED)
+		      flags |= IFACE_DEPRECATED;
+		    
+		    if (!(ifa->ifa_flags & IFA_F_TEMPORARY))
+		      flags |= IFACE_PERMANENT;
+    		    
+		    if (addrp && callback_ok)
+		      if (!((*callback)(addrp, (int)(ifa->ifa_prefixlen), (int)(ifa->ifa_scope), 
+					(int)(ifa->ifa_index), flags, 
+					(int) preferred, (int)valid, parm)))
+			callback_ok = 0;
+		  }
+	      }
+	  }
+	else if (h->nlmsg_type == RTM_NEWNEIGH && family == AF_UNSPEC)
+	  {
+	    struct ndmsg *neigh = NLMSG_DATA(h);  
+	    struct rtattr *rta = NDA_RTA(neigh);
+	    unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*neigh));
+	    size_t maclen = 0;
+	    char *inaddr = NULL, *mac = NULL;
+	    
+	    while (RTA_OK(rta, len1))
+	      {
+		if (rta->rta_type == NDA_DST)
+		  inaddr = (char *)(rta+1);
+		else if (rta->rta_type == NDA_LLADDR)
+		  {
+		    maclen = rta->rta_len - sizeof(struct rtattr);
+		    mac = (char *)(rta+1);
+		  }
+		
+		rta = RTA_NEXT(rta, len1);
+	      }
+
+	    if (!(neigh->ndm_state & (NUD_NOARP | NUD_INCOMPLETE | NUD_FAILED)) &&
+		inaddr && mac && callback_ok)
+	      if (!((*callback)(neigh->ndm_family, inaddr, mac, maclen, parm)))
+		callback_ok = 0;
+	  }
+#ifdef HAVE_DHCP6
+	else if (h->nlmsg_type == RTM_NEWLINK && family == AF_LOCAL)
+	  {
+	    struct ifinfomsg *link =  NLMSG_DATA(h);
+	    struct rtattr *rta = IFLA_RTA(link);
+	    unsigned int len1 = h->nlmsg_len - NLMSG_LENGTH(sizeof(*link));
+	    char *mac = NULL;
+	    size_t maclen = 0;
+
+	    while (RTA_OK(rta, len1))
+	      {
+		if (rta->rta_type == IFLA_ADDRESS)
+		  {
+		    maclen = rta->rta_len - sizeof(struct rtattr);
+		    mac = (char *)(rta+1);
+		  }
+		
+		rta = RTA_NEXT(rta, len1);
+	      }
+
+	    if (mac && callback_ok && !((link->ifi_flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) && 
+		!((*callback)((int)link->ifi_index, (unsigned int)link->ifi_type, mac, maclen, parm)))
+	      callback_ok = 0;
+	  }
+#endif
+    }
+}
+
+static void nl_multicast_state(unsigned state)
+{
+  ssize_t len;
+  struct nlmsghdr *h;
+
+  do {
+    /* don't risk blocking reading netlink messages here. */
+    while ((len = netlink_recv(MSG_DONTWAIT)) != -1)
+  
+      for (h = (struct nlmsghdr *)iov.iov_base; NLMSG_OK(h, (size_t)len); h = NLMSG_NEXT(h, len))
+	state = nl_async(h, state);
+  } while (errno == ENOBUFS);
+}
+
+void netlink_multicast(void)
+{
+  unsigned state = 0;
+  nl_multicast_state(state);
+}
+
+
+static unsigned nl_async(struct nlmsghdr *h, unsigned state)
+{
+  if (h->nlmsg_type == NLMSG_ERROR)
+    {
+      struct nlmsgerr *err = NLMSG_DATA(h);
+      if (err->error != 0)
+	my_syslog(LOG_ERR, _("netlink returns error: %s"), strerror(-(err->error)));
+    }
+  else if (h->nlmsg_pid == 0 && h->nlmsg_type == RTM_NEWROUTE &&
+	   (state & STATE_NEWROUTE)==0)
+    {
+      /* We arrange to receive netlink multicast messages whenever the network route is added.
+	 If this happens and we still have a DNS packet in the buffer, we re-send it.
+	 This helps on DoD links, where frequently the packet which triggers dialling is
+	 a DNS query, which then gets lost. By re-sending, we can avoid the lookup
+	 failing. */ 
+      struct rtmsg *rtm = NLMSG_DATA(h);
+      
+      if (rtm->rtm_type == RTN_UNICAST && rtm->rtm_scope == RT_SCOPE_LINK &&
+	  (rtm->rtm_table == RT_TABLE_MAIN ||
+	   rtm->rtm_table == RT_TABLE_LOCAL))
+	{
+	  queue_event(EVENT_NEWROUTE);
+	  state |= STATE_NEWROUTE;
+	}
+    }
+  else if ((h->nlmsg_type == RTM_NEWADDR || h->nlmsg_type == RTM_DELADDR) &&
+	   (state & STATE_NEWADDR)==0)
+    {
+      queue_event(EVENT_NEWADDR);
+      state |= STATE_NEWADDR;
+    }
+  return state;
+}
+#endif /* HAVE_LINUX_NETWORK */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/network.c b/ap/app/dnsmasq/dnsmasq-2.86/src/network.c
new file mode 100755
index 0000000..3fc179d
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/network.c
@@ -0,0 +1,1761 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_LINUX_NETWORK
+
+int indextoname(int fd, int index, char *name)
+{
+  struct ifreq ifr;
+  
+  if (index == 0)
+    return 0;
+
+  ifr.ifr_ifindex = index;
+  if (ioctl(fd, SIOCGIFNAME, &ifr) == -1)
+    return 0;
+
+  safe_strncpy(name, ifr.ifr_name, IF_NAMESIZE);
+
+ return 1;
+}
+
+
+#elif defined(HAVE_SOLARIS_NETWORK)
+
+#include <zone.h>
+#include <alloca.h>
+#ifndef LIFC_UNDER_IPMP
+#  define LIFC_UNDER_IPMP 0
+#endif
+
+int indextoname(int fd, int index, char *name)
+{
+  int64_t lifc_flags;
+  struct lifnum lifn;
+  int numifs, bufsize, i;
+  struct lifconf lifc;
+  struct lifreq *lifrp;
+  
+  if (index == 0)
+    return 0;
+  
+  if (getzoneid() == GLOBAL_ZONEID) 
+    {
+      if (!if_indextoname(index, name))
+	return 0;
+      return 1;
+    }
+  
+  lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES | LIFC_UNDER_IPMP;
+  lifn.lifn_family = AF_UNSPEC;
+  lifn.lifn_flags = lifc_flags;
+  if (ioctl(fd, SIOCGLIFNUM, &lifn) < 0) 
+    return 0;
+  
+  numifs = lifn.lifn_count;
+  bufsize = numifs * sizeof(struct lifreq);
+  
+  lifc.lifc_family = AF_UNSPEC;
+  lifc.lifc_flags = lifc_flags;
+  lifc.lifc_len = bufsize;
+  lifc.lifc_buf = alloca(bufsize);
+  
+  if (ioctl(fd, SIOCGLIFCONF, &lifc) < 0)  
+    return 0;
+  
+  lifrp = lifc.lifc_req;
+  for (i = lifc.lifc_len / sizeof(struct lifreq); i; i--, lifrp++) 
+    {
+      struct lifreq lifr;
+      safe_strncpy(lifr.lifr_name, lifrp->lifr_name, IF_NAMESIZE);
+      if (ioctl(fd, SIOCGLIFINDEX, &lifr) < 0) 
+	return 0;
+      
+      if (lifr.lifr_index == index) {
+	safe_strncpy(name, lifr.lifr_name, IF_NAMESIZE);
+	return 1;
+      }
+    }
+  return 0;
+}
+
+
+#else
+
+int indextoname(int fd, int index, char *name)
+{ 
+  (void)fd;
+
+  if (index == 0 || !if_indextoname(index, name))
+    return 0;
+
+  return 1;
+}
+
+#endif
+
+int iface_check(int family, union all_addr *addr, char *name, int *auth)
+{
+  struct iname *tmp;
+  int ret = 1, match_addr = 0;
+
+  /* Note: have to check all and not bail out early, so that we set the
+     "used" flags.
+
+     May be called with family == AF_LOCALto check interface by name only. */
+  
+  if (auth)
+    *auth = 0;
+  
+  if (daemon->if_names || daemon->if_addrs)
+    {
+      ret = 0;
+
+      for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+	if (tmp->name && wildcard_match(tmp->name, name))
+	  ret = tmp->used = 1;
+	        
+      if (addr)
+	for (tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+	  if (tmp->addr.sa.sa_family == family)
+	    {
+	      if (family == AF_INET &&
+		  tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+		ret = match_addr = tmp->used = 1;
+	      else if (family == AF_INET6 &&
+		       IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, 
+					  &addr->addr6))
+		ret = match_addr = tmp->used = 1;
+	    }          
+    }
+  
+  if (!match_addr)
+    for (tmp = daemon->if_except; tmp; tmp = tmp->next)
+      if (tmp->name && wildcard_match(tmp->name, name))
+	ret = 0;
+    
+
+  for (tmp = daemon->authinterface; tmp; tmp = tmp->next)
+    if (tmp->name)
+      {
+	if (strcmp(tmp->name, name) == 0 &&
+	    (tmp->addr.sa.sa_family == 0 || tmp->addr.sa.sa_family == family))
+	  break;
+      }
+    else if (addr && tmp->addr.sa.sa_family == AF_INET && family == AF_INET &&
+	     tmp->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+      break;
+    else if (addr && tmp->addr.sa.sa_family == AF_INET6 && family == AF_INET6 &&
+	     IN6_ARE_ADDR_EQUAL(&tmp->addr.in6.sin6_addr, &addr->addr6))
+      break;
+
+  if (tmp && auth) 
+    {
+      *auth = 1;
+      ret = 1;
+    }
+
+  return ret; 
+}
+
+
+/* Fix for problem that the kernel sometimes reports the loopback interface as the
+   arrival interface when a packet originates locally, even when sent to address of 
+   an interface other than the loopback. Accept packet if it arrived via a loopback 
+   interface, even when we're not accepting packets that way, as long as the destination
+   address is one we're believing. Interface list must be up-to-date before calling. */
+int loopback_exception(int fd, int family, union all_addr *addr, char *name)    
+{
+  struct ifreq ifr;
+  struct irec *iface;
+
+  safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+  if (ioctl(fd, SIOCGIFFLAGS, &ifr) != -1 &&
+      ifr.ifr_flags & IFF_LOOPBACK)
+    {
+      for (iface = daemon->interfaces; iface; iface = iface->next)
+	if (iface->addr.sa.sa_family == family)
+	  {
+	    if (family == AF_INET)
+	      {
+		if (iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+		  return 1;
+	      }
+	    else if (IN6_ARE_ADDR_EQUAL(&iface->addr.in6.sin6_addr, &addr->addr6))
+	      return 1;
+	  }
+    }
+  return 0;
+}
+
+/* If we're configured with something like --interface=eth0:0 then we'll listen correctly
+   on the relevant address, but the name of the arrival interface, derived from the
+   index won't match the config. Check that we found an interface address for the arrival 
+   interface: daemon->interfaces must be up-to-date. */
+int label_exception(int index, int family, union all_addr *addr)
+{
+  struct irec *iface;
+
+  /* labels only supported on IPv4 addresses. */
+  if (family != AF_INET)
+    return 0;
+
+  for (iface = daemon->interfaces; iface; iface = iface->next)
+    if (iface->index == index && iface->addr.sa.sa_family == AF_INET &&
+	iface->addr.in.sin_addr.s_addr == addr->addr4.s_addr)
+      return 1;
+
+  return 0;
+}
+
+struct iface_param {
+  struct addrlist *spare;
+  int fd;
+};
+
+static int iface_allowed(struct iface_param *param, int if_index, char *label,
+			 union mysockaddr *addr, struct in_addr netmask, int prefixlen, int iface_flags) 
+{
+  struct irec *iface;
+  int loopback;
+  struct ifreq ifr;
+  int tftp_ok = !!option_bool(OPT_TFTP);
+  int dhcp_ok = 1;
+  int auth_dns = 0;
+  int is_label = 0;
+#if defined(HAVE_DHCP) || defined(HAVE_TFTP)
+  struct iname *tmp;
+#endif
+
+  (void)prefixlen;
+
+  if (!indextoname(param->fd, if_index, ifr.ifr_name) ||
+      ioctl(param->fd, SIOCGIFFLAGS, &ifr) == -1)
+    return 0;
+   
+  loopback = ifr.ifr_flags & IFF_LOOPBACK;
+  
+  if (loopback)
+    dhcp_ok = 0;
+  
+  if (!label)
+    label = ifr.ifr_name;
+  else
+    is_label = strcmp(label, ifr.ifr_name);
+ 
+  /* maintain a list of all addresses on all interfaces for --local-service option */
+  if (option_bool(OPT_LOCAL_SERVICE))
+    {
+      struct addrlist *al;
+
+      if (param->spare)
+	{
+	  al = param->spare;
+	  param->spare = al->next;
+	}
+      else
+	al = whine_malloc(sizeof(struct addrlist));
+      
+      if (al)
+	{
+	  al->next = daemon->interface_addrs;
+	  daemon->interface_addrs = al;
+	  al->prefixlen = prefixlen;
+	  
+	  if (addr->sa.sa_family == AF_INET)
+	    {
+	      al->addr.addr4 = addr->in.sin_addr;
+	      al->flags = 0;
+	    }
+	  else
+	    {
+	      al->addr.addr6 = addr->in6.sin6_addr;
+	      al->flags = ADDRLIST_IPV6;
+	    } 
+	}
+    }
+  
+  if (addr->sa.sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&addr->in6.sin6_addr))
+    {
+      struct interface_name *int_name;
+      struct addrlist *al;
+#ifdef HAVE_AUTH
+      struct auth_zone *zone;
+      struct auth_name_list *name;
+
+      /* Find subnets in auth_zones */
+      for (zone = daemon->auth_zones; zone; zone = zone->next)
+	for (name = zone->interface_names; name; name = name->next)
+	  if (wildcard_match(name->name, label))
+	    {
+	      if (addr->sa.sa_family == AF_INET && (name->flags & AUTH4))
+		{
+		  if (param->spare)
+		    {
+		      al = param->spare;
+		      param->spare = al->next;
+		    }
+		  else
+		    al = whine_malloc(sizeof(struct addrlist));
+		  
+		  if (al)
+		    {
+		      al->next = zone->subnet;
+		      zone->subnet = al;
+		      al->prefixlen = prefixlen;
+		      al->addr.addr4 = addr->in.sin_addr;
+		      al->flags = 0;
+		    }
+		}
+	      
+	      if (addr->sa.sa_family == AF_INET6 && (name->flags & AUTH6))
+		{
+		  if (param->spare)
+		    {
+		      al = param->spare;
+		      param->spare = al->next;
+		    }
+		  else
+		    al = whine_malloc(sizeof(struct addrlist));
+		  
+		  if (al)
+		    {
+		      al->next = zone->subnet;
+		      zone->subnet = al;
+		      al->prefixlen = prefixlen;
+		      al->addr.addr6 = addr->in6.sin6_addr;
+		      al->flags = ADDRLIST_IPV6;
+		    }
+		} 
+	    }
+#endif
+       
+      /* Update addresses from interface_names. These are a set independent
+	 of the set we're listening on. */  
+      for (int_name = daemon->int_names; int_name; int_name = int_name->next)
+	if (strncmp(label, int_name->intr, IF_NAMESIZE) == 0)
+	  {
+	    struct addrlist *lp;
+
+	    al = NULL;
+	    
+	    if (addr->sa.sa_family == AF_INET && (int_name->flags & (IN4 | INP4)))
+	      {
+		struct in_addr newaddr = addr->in.sin_addr;
+		
+		if (int_name->flags & INP4)
+		  {
+		    if (netmask.s_addr == 0xffff)
+		      continue;
+
+		    newaddr.s_addr = (addr->in.sin_addr.s_addr & netmask.s_addr) |
+		      (int_name->proto4.s_addr & ~netmask.s_addr);
+		  }
+		
+		/* check for duplicates. */
+		for (lp = int_name->addr; lp; lp = lp->next)
+		  if (lp->flags == 0 && lp->addr.addr4.s_addr == newaddr.s_addr)
+		    break;
+		
+		if (!lp)
+		  {
+		    if (param->spare)
+		      {
+			al = param->spare;
+			param->spare = al->next;
+		      }
+		    else
+		      al = whine_malloc(sizeof(struct addrlist));
+
+		    if (al)
+		      {
+			al->flags = 0;
+			al->addr.addr4 = newaddr;
+		      }
+		  }
+	      }
+
+	    if (addr->sa.sa_family == AF_INET6 && (int_name->flags & (IN6 | INP6)))
+	      {
+		struct in6_addr newaddr = addr->in6.sin6_addr;
+		
+		if (int_name->flags & INP6)
+		  {
+		    int i;
+
+		    /* No sense in doing /128. */
+		    if (prefixlen == 128)
+		      continue;
+		    
+		    for (i = 0; i < 16; i++)
+		      {
+			int bits = ((i+1)*8) - prefixlen;
+		       
+			if (bits >= 8)
+			  newaddr.s6_addr[i] = int_name->proto6.s6_addr[i];
+			else if (bits >= 0)
+			  {
+			    unsigned char mask = 0xff << bits;
+			    newaddr.s6_addr[i] =
+			      (addr->in6.sin6_addr.s6_addr[i] & mask) |
+			      (int_name->proto6.s6_addr[i] & ~mask);
+			  }
+		      }
+		  }
+		
+		/* check for duplicates. */
+		for (lp = int_name->addr; lp; lp = lp->next)
+		  if ((lp->flags & ADDRLIST_IPV6) &&
+		      IN6_ARE_ADDR_EQUAL(&lp->addr.addr6, &newaddr))
+		    break;
+					
+		if (!lp)
+		  {
+		    if (param->spare)
+		      {
+			al = param->spare;
+			param->spare = al->next;
+		      }
+		    else
+		      al = whine_malloc(sizeof(struct addrlist));
+		    
+		    if (al)
+		      {
+			al->flags = ADDRLIST_IPV6;
+			al->addr.addr6 = newaddr;
+
+			/* Privacy addresses and addresses still undergoing DAD and deprecated addresses
+			   don't appear in forward queries, but will in reverse ones. */
+			if (!(iface_flags & IFACE_PERMANENT) || (iface_flags & (IFACE_DEPRECATED | IFACE_TENTATIVE)))
+			  al->flags |= ADDRLIST_REVONLY;
+		      }
+		  }
+	      }
+	    
+	    if (al)
+	      {
+		al->next = int_name->addr;
+		int_name->addr = al;
+	      }
+	  }
+    }
+ 
+  /* check whether the interface IP has been added already 
+     we call this routine multiple times. */
+  for (iface = daemon->interfaces; iface; iface = iface->next) 
+    if (sockaddr_isequal(&iface->addr, addr) && iface->index == if_index)
+      {
+	iface->dad = !!(iface_flags & IFACE_TENTATIVE);
+	iface->found = 1; /* for garbage collection */
+	iface->netmask = netmask;
+	return 1;
+      }
+
+ /* If we are restricting the set of interfaces to use, make
+     sure that loopback interfaces are in that set. */
+  if (daemon->if_names && loopback)
+    {
+      struct iname *lo;
+      for (lo = daemon->if_names; lo; lo = lo->next)
+	if (lo->name && strcmp(lo->name, ifr.ifr_name) == 0)
+	  break;
+      
+      if (!lo && (lo = whine_malloc(sizeof(struct iname)))) 
+	{
+	  if ((lo->name = whine_malloc(strlen(ifr.ifr_name)+1)))
+	    {
+	      strcpy(lo->name, ifr.ifr_name);
+	      lo->used = 1;
+	      lo->next = daemon->if_names;
+	      daemon->if_names = lo;
+	    }
+	  else
+	    free(lo);
+	}
+    }
+  
+  if (addr->sa.sa_family == AF_INET &&
+      !iface_check(AF_INET, (union all_addr *)&addr->in.sin_addr, label, &auth_dns))
+    return 1;
+
+  if (addr->sa.sa_family == AF_INET6 &&
+      !iface_check(AF_INET6, (union all_addr *)&addr->in6.sin6_addr, label, &auth_dns))
+    return 1;
+    
+#ifdef HAVE_DHCP
+  /* No DHCP where we're doing auth DNS. */
+  if (auth_dns)
+    {
+      tftp_ok = 0;
+      dhcp_ok = 0;
+    }
+  else
+    for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+      if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+	{
+	  tftp_ok = 0;
+	  dhcp_ok = 0;
+	}
+#endif
+ 
+  
+#ifdef HAVE_TFTP
+  if (daemon->tftp_interfaces)
+    {
+      /* dedicated tftp interface list */
+      tftp_ok = 0;
+      for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
+	if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name))
+	  tftp_ok = 1;
+    }
+#endif
+  
+  /* add to list */
+  if ((iface = whine_malloc(sizeof(struct irec))))
+    {
+      int mtu = 0;
+
+      if (ioctl(param->fd, SIOCGIFMTU, &ifr) != -1)
+	mtu = ifr.ifr_mtu;
+
+      iface->addr = *addr;
+      iface->netmask = netmask;
+      iface->tftp_ok = tftp_ok;
+      iface->dhcp_ok = dhcp_ok;
+      iface->dns_auth = auth_dns;
+      iface->mtu = mtu;
+      iface->dad = !!(iface_flags & IFACE_TENTATIVE);
+      iface->found = 1;
+      iface->done = iface->multicast_done = iface->warned = 0;
+      iface->index = if_index;
+      iface->label = is_label;
+      if ((iface->name = whine_malloc(strlen(ifr.ifr_name)+1)))
+	{
+	  strcpy(iface->name, ifr.ifr_name);
+	  iface->next = daemon->interfaces;
+	  daemon->interfaces = iface;
+	  return 1;
+	}
+      free(iface);
+
+    }
+  
+  errno = ENOMEM; 
+  return 0;
+}
+
+static int iface_allowed_v6(struct in6_addr *local, int prefix, 
+			    int scope, int if_index, int flags, 
+			    int preferred, int valid, void *vparam)
+{
+  union mysockaddr addr;
+  struct in_addr netmask; /* dummy */
+  netmask.s_addr = 0;
+
+  (void)scope; /* warning */
+  (void)preferred;
+  (void)valid;
+  
+  memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+  addr.in6.sin6_len = sizeof(addr.in6);
+#endif
+  addr.in6.sin6_family = AF_INET6;
+  addr.in6.sin6_addr = *local;
+  addr.in6.sin6_port = htons(daemon->port);
+  /* FreeBSD insists this is zero for non-linklocal addresses */
+  if (IN6_IS_ADDR_LINKLOCAL(local))
+    addr.in6.sin6_scope_id = if_index;
+  else
+    addr.in6.sin6_scope_id = 0;
+  
+  return iface_allowed((struct iface_param *)vparam, if_index, NULL, &addr, netmask, prefix, flags);
+}
+
+static int iface_allowed_v4(struct in_addr local, int if_index, char *label,
+			    struct in_addr netmask, struct in_addr broadcast, void *vparam)
+{
+  union mysockaddr addr;
+  int prefix, bit;
+ 
+  (void)broadcast; /* warning */
+
+  memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+  addr.in.sin_len = sizeof(addr.in);
+#endif
+  addr.in.sin_family = AF_INET;
+  addr.in.sin_addr = local;
+  addr.in.sin_port = htons(daemon->port);
+
+  /* determine prefix length from netmask */
+  for (prefix = 32, bit = 1; (bit & ntohl(netmask.s_addr)) == 0 && prefix != 0; bit = bit << 1, prefix--);
+
+  return iface_allowed((struct iface_param *)vparam, if_index, label, &addr, netmask, prefix, 0);
+}
+
+/*
+ * Clean old interfaces no longer found.
+ */
+static void clean_interfaces()
+{
+  struct irec *iface;
+  struct irec **up = &daemon->interfaces;
+
+  for (iface = *up; iface; iface = *up)
+  {
+    if (!iface->found && !iface->done)
+      {
+        *up = iface->next;
+        free(iface->name);
+        free(iface);
+      }
+    else
+      {
+        up = &iface->next;
+      }
+  }
+}
+
+/** Release listener if no other interface needs it.
+ *
+ * @return 1 if released, 0 if still required
+ */
+static int release_listener(struct listener *l)
+{
+  if (l->used > 1)
+    {
+      struct irec *iface;
+      for (iface = daemon->interfaces; iface; iface = iface->next)
+	if (iface->done && sockaddr_isequal(&l->addr, &iface->addr))
+	  {
+	    if (iface->found)
+	      {
+		/* update listener to point to active interface instead */
+		if (!l->iface->found)
+		  l->iface = iface;
+	      }
+	    else
+	      {
+		l->used--;
+		iface->done = 0;
+	      }
+	  }
+
+      /* Someone is still using this listener, skip its deletion */
+      if (l->used > 0)
+	return 0;
+    }
+
+  if (l->iface->done)
+    {
+      int port;
+
+      port = prettyprint_addr(&l->iface->addr, daemon->addrbuff);
+      my_syslog(LOG_DEBUG|MS_DEBUG, _("stopped listening on %s(#%d): %s port %d"),
+		l->iface->name, l->iface->index, daemon->addrbuff, port);
+      /* In case it ever returns */
+      l->iface->done = 0;
+    }
+
+  if (l->fd != -1)
+    close(l->fd);
+  if (l->tcpfd != -1)
+    close(l->tcpfd);
+  if (l->tftpfd != -1)
+    close(l->tftpfd);
+
+  free(l);
+  return 1;
+}
+
+int enumerate_interfaces(int reset)
+{
+  static struct addrlist *spare = NULL;
+  static int done = 0;
+  struct iface_param param;
+  int errsave, ret = 1;
+  struct addrlist *addr, *tmp;
+  struct interface_name *intname;
+  struct irec *iface;
+#ifdef HAVE_AUTH
+  struct auth_zone *zone;
+#endif
+  struct server *serv;
+  
+  /* Do this max once per select cycle  - also inhibits netlink socket use
+   in TCP child processes. */
+
+  if (reset)
+    {
+      done = 0;
+      return 1;
+    }
+
+  if (done)
+    return 1;
+
+  done = 1;
+
+  if ((param.fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+    return 0;
+
+  /* iface indexes can change when interfaces are created/destroyed. 
+     We use them in the main forwarding control path, when the path
+     to a server is specified by an interface, so cache them.
+     Update the cache here. */
+  for (serv = daemon->servers; serv; serv = serv->next)
+    if (serv->interface[0] != 0)
+      {
+#ifdef HAVE_LINUX_NETWORK
+	struct ifreq ifr;
+	
+	safe_strncpy(ifr.ifr_name, serv->interface, IF_NAMESIZE);
+	if (ioctl(param.fd, SIOCGIFINDEX, &ifr) != -1) 
+	  serv->ifindex = ifr.ifr_ifindex;
+#else
+	serv->ifindex = if_nametoindex(serv->interface);
+#endif
+      }
+    
+again:
+  /* Mark interfaces for garbage collection */
+  for (iface = daemon->interfaces; iface; iface = iface->next) 
+    iface->found = 0;
+
+  /* remove addresses stored against interface_names */
+  for (intname = daemon->int_names; intname; intname = intname->next)
+    {
+      for (addr = intname->addr; addr; addr = tmp)
+	{
+	  tmp = addr->next;
+	  addr->next = spare;
+	  spare = addr;
+	}
+      
+      intname->addr = NULL;
+    }
+
+  /* Remove list of addresses of local interfaces */
+  for (addr = daemon->interface_addrs; addr; addr = tmp)
+    {
+      tmp = addr->next;
+      addr->next = spare;
+      spare = addr;
+    }
+  daemon->interface_addrs = NULL;
+  
+#ifdef HAVE_AUTH
+  /* remove addresses stored against auth_zone subnets, but not 
+   ones configured as address literals */
+  for (zone = daemon->auth_zones; zone; zone = zone->next)
+    if (zone->interface_names)
+      {
+	struct addrlist **up;
+	for (up = &zone->subnet, addr = zone->subnet; addr; addr = tmp)
+	  {
+	    tmp = addr->next;
+	    if (addr->flags & ADDRLIST_LITERAL)
+	      up = &addr->next;
+	    else
+	      {
+		*up = addr->next;
+		addr->next = spare;
+		spare = addr;
+	      }
+	  }
+      }
+#endif
+
+  param.spare = spare;
+  
+  ret = iface_enumerate(AF_INET6, &param, iface_allowed_v6);
+  if (ret < 0)
+    goto again;
+  else if (ret)
+    {
+      ret = iface_enumerate(AF_INET, &param, iface_allowed_v4);
+      if (ret < 0)
+	goto again;
+    }
+ 
+  errsave = errno;
+  close(param.fd);
+  
+  if (option_bool(OPT_CLEVERBIND))
+    { 
+      /* Garbage-collect listeners listening on addresses that no longer exist.
+	 Does nothing when not binding interfaces or for listeners on localhost, 
+	 since the ->iface field is NULL. Note that this needs the protections
+	 against reentrancy, hence it's here.  It also means there's a possibility,
+	 in OPT_CLEVERBIND mode, that at listener will just disappear after
+	 a call to enumerate_interfaces, this is checked OK on all calls. */
+      struct listener *l, *tmp, **up;
+      int freed = 0;
+      
+      for (up = &daemon->listeners, l = daemon->listeners; l; l = tmp)
+	{
+	  tmp = l->next;
+	  
+	  if (!l->iface || l->iface->found)
+	    up = &l->next;
+	  else if (release_listener(l))
+	    {
+	      *up = tmp;
+	      freed = 1;
+	    }
+	}
+
+      if (freed)
+	clean_interfaces();
+    }
+
+  errno = errsave;
+  spare = param.spare;
+  
+  return ret;
+}
+
+/* set NONBLOCK bit on fd: See Stevens 16.6 */
+int fix_fd(int fd)
+{
+  int flags;
+
+  if ((flags = fcntl(fd, F_GETFL)) == -1 ||
+      fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
+    return 0;
+  
+  return 1;
+}
+
+static int make_sock(union mysockaddr *addr, int type, int dienow)
+{
+  int family = addr->sa.sa_family;
+  int fd, rc, opt = 1;
+  
+  if ((fd = socket(family, type, 0)) == -1)
+    {
+      int port, errsave;
+      char *s;
+
+      /* No error if the kernel just doesn't support this IP flavour */
+      if (errno == EPROTONOSUPPORT ||
+	  errno == EAFNOSUPPORT ||
+	  errno == EINVAL)
+	return -1;
+      
+    err:
+      errsave = errno;
+      port = prettyprint_addr(addr, daemon->addrbuff);
+      if (!option_bool(OPT_NOWILD) && !option_bool(OPT_CLEVERBIND))
+	sprintf(daemon->addrbuff, "port %d", port);
+      s = _("failed to create listening socket for %s: %s");
+      
+      if (fd != -1)
+	close (fd);
+	
+      errno = errsave;
+
+      if (dienow)
+	{
+	  /* failure to bind addresses given by --listen-address at this point
+	     is OK if we're doing bind-dynamic */
+	  if (!option_bool(OPT_CLEVERBIND))
+	    die(s, daemon->addrbuff, EC_BADNET);
+	}
+      else
+	my_syslog(LOG_WARNING, s, daemon->addrbuff, strerror(errno));
+      
+      return -1;
+    }	
+  
+  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1 || !fix_fd(fd))
+    goto err;
+  
+  if (family == AF_INET6 && setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1)
+    goto err;
+  
+  if ((rc = bind(fd, (struct sockaddr *)addr, sa_len(addr))) == -1)
+    goto err;
+  
+  if (type == SOCK_STREAM)
+    {
+#ifdef TCP_FASTOPEN
+      int qlen = 5;                           
+      setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen));
+#endif
+      
+      if (listen(fd, TCP_BACKLOG) == -1)
+	goto err;
+    }
+  else if (family == AF_INET)
+    {
+      if (!option_bool(OPT_NOWILD))
+	{
+#if defined(HAVE_LINUX_NETWORK) 
+	  if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) == -1)
+	    goto err;
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+	  if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt)) == -1 ||
+	      setsockopt(fd, IPPROTO_IP, IP_RECVIF, &opt, sizeof(opt)) == -1)
+	    goto err;
+#endif
+	}
+    }
+  else if (!set_ipv6pktinfo(fd))
+    goto err;
+  
+  return fd;
+}
+
+int set_ipv6pktinfo(int fd)
+{
+  int opt = 1;
+
+  /* The API changed around Linux 2.6.14 but the old ABI is still supported:
+     handle all combinations of headers and kernel.
+     OpenWrt note that this fixes the problem addressed by your very broken patch. */
+  daemon->v6pktinfo = IPV6_PKTINFO;
+  
+#ifdef IPV6_RECVPKTINFO
+  if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof(opt)) != -1)
+    return 1;
+# ifdef IPV6_2292PKTINFO
+  else if (errno == ENOPROTOOPT && setsockopt(fd, IPPROTO_IPV6, IPV6_2292PKTINFO, &opt, sizeof(opt)) != -1)
+    {
+      daemon->v6pktinfo = IPV6_2292PKTINFO;
+      return 1;
+    }
+# endif 
+#else
+  if (setsockopt(fd, IPPROTO_IPV6, IPV6_PKTINFO, &opt, sizeof(opt)) != -1)
+    return 1;
+#endif
+
+  return 0;
+}
+
+
+/* Find the interface on which a TCP connection arrived, if possible, or zero otherwise. */
+int tcp_interface(int fd, int af)
+{ 
+  (void)fd; /* suppress potential unused warning */
+  (void)af; /* suppress potential unused warning */
+  int if_index = 0;
+
+#ifdef HAVE_LINUX_NETWORK
+  int opt = 1;
+  struct cmsghdr *cmptr;
+  struct msghdr msg;
+  socklen_t len;
+  
+  /* use mshdr so that the CMSDG_* macros are available */
+  msg.msg_control = daemon->packet;
+  msg.msg_controllen = len = daemon->packet_buff_sz;
+
+  /* we overwrote the buffer... */
+  daemon->srv_save = NULL; 
+
+  if (af == AF_INET)
+    {
+      if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt)) != -1 &&
+	  getsockopt(fd, IPPROTO_IP, IP_PKTOPTIONS, msg.msg_control, &len) != -1)
+	{
+	  msg.msg_controllen = len;
+	  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	    if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+	      {
+		union {
+		  unsigned char *c;
+		  struct in_pktinfo *p;
+		} p;
+		
+		p.c = CMSG_DATA(cmptr);
+		if_index = p.p->ipi_ifindex;
+	      }
+	}
+    }
+  else
+    {
+      /* Only the RFC-2292 API has the ability to find the interface for TCP connections,
+	 it was removed in RFC-3542 !!!! 
+
+	 Fortunately, Linux kept the 2292 ABI when it moved to 3542. The following code always
+	 uses the old ABI, and should work with pre- and post-3542 kernel headers */
+
+#ifdef IPV6_2292PKTOPTIONS   
+#  define PKTOPTIONS IPV6_2292PKTOPTIONS
+#else
+#  define PKTOPTIONS IPV6_PKTOPTIONS
+#endif
+
+      if (set_ipv6pktinfo(fd) &&
+	  getsockopt(fd, IPPROTO_IPV6, PKTOPTIONS, msg.msg_control, &len) != -1)
+	{
+          msg.msg_controllen = len;
+	  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;
+              }
+	}
+    }
+#endif /* Linux */
+ 
+  return if_index;
+}
+      
+static struct listener *create_listeners(union mysockaddr *addr, int do_tftp, int dienow)
+{
+  struct listener *l = NULL;
+  int fd = -1, tcpfd = -1, tftpfd = -1;
+
+  (void)do_tftp;
+
+  if (daemon->port != 0)
+    {
+      fd = make_sock(addr, SOCK_DGRAM, dienow);
+      tcpfd = make_sock(addr, SOCK_STREAM, dienow);
+    }
+  
+#ifdef HAVE_TFTP
+  if (do_tftp)
+    {
+      if (addr->sa.sa_family == AF_INET)
+	{
+	  /* port must be restored to DNS port for TCP code */
+	  short save = addr->in.sin_port;
+	  addr->in.sin_port = htons(TFTP_PORT);
+	  tftpfd = make_sock(addr, SOCK_DGRAM, dienow);
+	  addr->in.sin_port = save;
+	}
+      else
+	{
+	  short save = addr->in6.sin6_port;
+	  addr->in6.sin6_port = htons(TFTP_PORT);
+	  tftpfd = make_sock(addr, SOCK_DGRAM, dienow);
+	  addr->in6.sin6_port = save;
+	}  
+    }
+#endif
+
+  if (fd != -1 || tcpfd != -1 || tftpfd != -1)
+    {
+      l = safe_malloc(sizeof(struct listener));
+      l->next = NULL;
+      l->fd = fd;
+      l->tcpfd = tcpfd;
+      l->tftpfd = tftpfd;
+      l->addr = *addr;
+      l->used = 1;
+      l->iface = NULL;
+    }
+
+  return l;
+}
+
+void create_wildcard_listeners(void)
+{
+  union mysockaddr addr;
+  struct listener *l, *l6;
+
+  memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+  addr.in.sin_len = sizeof(addr.in);
+#endif
+  addr.in.sin_family = AF_INET;
+  addr.in.sin_addr.s_addr = INADDR_ANY;
+  addr.in.sin_port = htons(daemon->port);
+
+  l = create_listeners(&addr, !!option_bool(OPT_TFTP), 1);
+
+  memset(&addr, 0, sizeof(addr));
+#ifdef HAVE_SOCKADDR_SA_LEN
+  addr.in6.sin6_len = sizeof(addr.in6);
+#endif
+  addr.in6.sin6_family = AF_INET6;
+  addr.in6.sin6_addr = in6addr_any;
+  addr.in6.sin6_port = htons(daemon->port);
+ 
+  l6 = create_listeners(&addr, !!option_bool(OPT_TFTP), 1);
+  if (l) 
+    l->next = l6;
+  else 
+    l = l6;
+
+  daemon->listeners = l;
+}
+
+static struct listener *find_listener(union mysockaddr *addr)
+{
+  struct listener *l;
+  for (l = daemon->listeners; l; l = l->next)
+    if (sockaddr_isequal(&l->addr, addr))
+      return l;
+  return NULL;
+}
+
+void create_bound_listeners(int dienow)
+{
+  struct listener *new;
+  struct irec *iface;
+  struct iname *if_tmp;
+  struct listener *existing;
+
+  for (iface = daemon->interfaces; iface; iface = iface->next)
+    if (!iface->done && !iface->dad && iface->found)
+      {
+	existing = find_listener(&iface->addr);
+	if (existing)
+	  {
+	    iface->done = 1;
+	    existing->used++; /* increase usage counter */
+	  }
+	else if ((new = create_listeners(&iface->addr, iface->tftp_ok, dienow)))
+	  {
+	    new->iface = iface;
+	    new->next = daemon->listeners;
+	    daemon->listeners = new;
+	    iface->done = 1;
+
+	    /* Don't log the initial set of listen addresses created
+               at startup, since this is happening before the logging
+               system is initialised and the sign-on printed. */
+            if (!dienow)
+              {
+		int port = prettyprint_addr(&iface->addr, daemon->addrbuff);
+		my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s(#%d): %s port %d"),
+			  iface->name, iface->index, daemon->addrbuff, port);
+	      }
+	  }
+      }
+
+  /* Check for --listen-address options that haven't been used because there's
+     no interface with a matching address. These may be valid: eg it's possible
+     to listen on 127.0.1.1 even if the loopback interface is 127.0.0.1
+
+     If the address isn't valid the bind() will fail and we'll die() 
+     (except in bind-dynamic mode, when we'll complain but keep trying.)
+
+     The resulting listeners have the ->iface field NULL, and this has to be
+     handled by the DNS and TFTP code. It disables --localise-queries processing
+     (no netmask) and some MTU login the tftp code. */
+
+  for (if_tmp = daemon->if_addrs; if_tmp; if_tmp = if_tmp->next)
+    if (!if_tmp->used && 
+	(new = create_listeners(&if_tmp->addr, !!option_bool(OPT_TFTP), dienow)))
+      {
+	new->next = daemon->listeners;
+	daemon->listeners = new;
+
+	if (!dienow)
+	  {
+	    int port = prettyprint_addr(&if_tmp->addr, daemon->addrbuff);
+	    my_syslog(LOG_DEBUG|MS_DEBUG, _("listening on %s port %d"), daemon->addrbuff, port);
+	  }
+      }
+}
+
+/* In --bind-interfaces, the only access control is the addresses we're listening on. 
+   There's nothing to avoid a query to the address of an internal interface arriving via
+   an external interface where we don't want to accept queries, except that in the usual 
+   case the addresses of internal interfaces are RFC1918. When bind-interfaces in use, 
+   and we listen on an address that looks like it's probably globally routeable, shout.
+
+   The fix is to use --bind-dynamic, which actually checks the arrival interface too.
+   Tough if your platform doesn't support this.
+
+   Note that checking the arrival interface is supported in the standard IPv6 API and
+   always done, so we don't warn about any IPv6 addresses here.
+*/
+
+void warn_bound_listeners(void)
+{
+  struct irec *iface; 	
+  int advice = 0;
+
+  for (iface = daemon->interfaces; iface; iface = iface->next)
+    if (!iface->dns_auth)
+      {
+	if (iface->addr.sa.sa_family == AF_INET)
+	  {
+	    if (!private_net(iface->addr.in.sin_addr, 1))
+	      {
+		inet_ntop(AF_INET, &iface->addr.in.sin_addr, daemon->addrbuff, ADDRSTRLEN);
+		iface->warned = advice = 1;
+		my_syslog(LOG_WARNING, 
+			  _("LOUD WARNING: listening on %s may accept requests via interfaces other than %s"),
+			  daemon->addrbuff, iface->name);
+	      }
+	  }
+      }
+  
+  if (advice)
+    my_syslog(LOG_WARNING, _("LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification attacks via these interface(s)")); 
+}
+
+void warn_wild_labels(void)
+{
+  struct irec *iface;
+
+  for (iface = daemon->interfaces; iface; iface = iface->next)
+    if (iface->found && iface->name && iface->label)
+      my_syslog(LOG_WARNING, _("warning: using interface %s instead"), iface->name);
+}
+
+void warn_int_names(void)
+{
+  struct interface_name *intname;
+ 
+  for (intname = daemon->int_names; intname; intname = intname->next)
+    if (!intname->addr)
+      my_syslog(LOG_WARNING, _("warning: no addresses found for interface %s"), intname->intr);
+}
+ 
+int is_dad_listeners(void)
+{
+  struct irec *iface;
+  
+  if (option_bool(OPT_NOWILD))
+    for (iface = daemon->interfaces; iface; iface = iface->next)
+      if (iface->dad && !iface->done)
+	return 1;
+  
+  return 0;
+}
+
+#ifdef HAVE_DHCP6
+void join_multicast(int dienow)      
+{
+  struct irec *iface, *tmp;
+
+  for (iface = daemon->interfaces; iface; iface = iface->next)
+    if (iface->addr.sa.sa_family == AF_INET6 && iface->dhcp_ok && !iface->multicast_done)
+      {
+	/* There's an irec per address but we only want to join for multicast 
+	   once per interface. Weed out duplicates. */
+	for (tmp = daemon->interfaces; tmp; tmp = tmp->next)
+	  if (tmp->multicast_done && tmp->index == iface->index)
+	    break;
+	
+	iface->multicast_done = 1;
+	
+	if (!tmp)
+	  {
+	    struct ipv6_mreq mreq;
+	    int err = 0;
+
+	    mreq.ipv6mr_interface = iface->index;
+	    
+	    inet_pton(AF_INET6, ALL_RELAY_AGENTS_AND_SERVERS, &mreq.ipv6mr_multiaddr);
+	    
+	    if ((daemon->doing_dhcp6 || daemon->relay6) &&
+		setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+	      err = errno;
+	    
+	    inet_pton(AF_INET6, ALL_SERVERS, &mreq.ipv6mr_multiaddr);
+	    
+	    if (daemon->doing_dhcp6 && 
+		setsockopt(daemon->dhcp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+	      err = errno;
+	    
+	    inet_pton(AF_INET6, ALL_ROUTERS, &mreq.ipv6mr_multiaddr);
+	    
+	    if (daemon->doing_ra &&
+		setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == -1)
+	      err = errno;
+	    
+	    if (err)
+	      {
+		char *s = _("interface %s failed to join DHCPv6 multicast group: %s");
+		errno = err;
+
+#ifdef HAVE_LINUX_NETWORK
+		if (errno == ENOMEM)
+		  my_syslog(LOG_ERR, _("try increasing /proc/sys/net/core/optmem_max"));
+#endif
+
+		if (dienow)
+		  die(s, iface->name, EC_BADNET);
+		else
+		  my_syslog(LOG_ERR, s, iface->name, strerror(errno));
+	      }
+	  }
+      }
+}
+#endif
+
+int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
+{
+  union mysockaddr addr_copy = *addr;
+  unsigned short port;
+  int tries = 1;
+  unsigned short ports_avail = 1;
+
+  if (addr_copy.sa.sa_family == AF_INET)
+    port = addr_copy.in.sin_port;
+  else
+    port = addr_copy.in6.sin6_port;
+
+  /* cannot set source _port_ for TCP connections. */
+  if (is_tcp)
+    port = 0;
+  else if (port == 0 && daemon->max_port != 0)
+    {
+      /* Bind a random port within the range given by min-port and max-port if either
+	 or both are set. Otherwise use the OS's random ephemeral port allocation by
+	 leaving port == 0 and tries == 1 */
+      ports_avail = daemon->max_port - daemon->min_port + 1;
+      tries = ports_avail < 30 ? 3 * ports_avail : 100;
+      port = htons(daemon->min_port + (rand16() % ports_avail));
+    }
+  
+  while (1)
+    {
+      /* elide bind() call if it's to port 0, address 0 */
+      if (addr_copy.sa.sa_family == AF_INET)
+	{
+	  if (port == 0 && addr_copy.in.sin_addr.s_addr == 0)
+	    break;
+	  addr_copy.in.sin_port = port;
+	}
+      else
+	{
+	  if (port == 0 && IN6_IS_ADDR_UNSPECIFIED(&addr_copy.in6.sin6_addr))
+	    break;
+	  addr_copy.in6.sin6_port = port;
+	}
+      
+      if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
+	break;
+      
+       if (errno != EADDRINUSE && errno != EACCES) 
+	 return 0;
+
+      if (--tries == 0)
+	return 0;
+
+      port = htons(daemon->min_port + (rand16() % ports_avail));
+    }
+
+  if (!is_tcp && ifindex > 0)
+    {
+#if defined(IP_UNICAST_IF)
+      if (addr_copy.sa.sa_family == AF_INET)
+        {
+          uint32_t ifindex_opt = htonl(ifindex);
+          return setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
+        }
+#endif
+#if defined (IPV6_UNICAST_IF)
+      if (addr_copy.sa.sa_family == AF_INET6)
+        {
+          uint32_t ifindex_opt = htonl(ifindex);
+          return setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex_opt, sizeof(ifindex_opt)) == 0;
+        }
+#endif
+    }
+
+  (void)intname; /* suppress potential unused warning */
+#if defined(SO_BINDTODEVICE)
+  if (intname[0] != 0 &&
+      setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, intname, IF_NAMESIZE) == -1)
+    return 0;
+#endif
+
+  return 1;
+}
+
+static struct serverfd *allocate_sfd(union mysockaddr *addr, char *intname, unsigned int ifindex)
+{
+  struct serverfd *sfd;
+  int errsave;
+  int opt = 1;
+  
+  /* when using random ports, servers which would otherwise use
+     the INADDR_ANY/port0 socket have sfd set to NULL, this is 
+     anything without an explictly set source port. */
+  if (!daemon->osport)
+    {
+      errno = 0;
+      
+      if (addr->sa.sa_family == AF_INET &&
+	  addr->in.sin_port == htons(0)) 
+	return NULL;
+
+      if (addr->sa.sa_family == AF_INET6 &&
+	  addr->in6.sin6_port == htons(0)) 
+	return NULL;
+    }
+
+  /* may have a suitable one already */
+  for (sfd = daemon->sfds; sfd; sfd = sfd->next )
+    if (ifindex == sfd->ifindex &&
+	sockaddr_isequal(&sfd->source_addr, addr) &&
+	strcmp(intname, sfd->interface) == 0)
+      return sfd;
+  
+  /* need to make a new one. */
+  errno = ENOMEM; /* in case malloc fails. */
+  if (!(sfd = whine_malloc(sizeof(struct serverfd))))
+    return NULL;
+  
+  if ((sfd->fd = socket(addr->sa.sa_family, SOCK_DGRAM, 0)) == -1)
+    {
+      free(sfd);
+      return NULL;
+    }
+
+  if ((addr->sa.sa_family == AF_INET6 && setsockopt(sfd->fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) == -1) ||
+      !local_bind(sfd->fd, addr, intname, ifindex, 0) || !fix_fd(sfd->fd))
+    { 
+      errsave = errno; /* save error from bind/setsockopt. */
+      close(sfd->fd);
+      free(sfd);
+      errno = errsave;
+      return NULL;
+    }
+
+  safe_strncpy(sfd->interface, intname, sizeof(sfd->interface)); 
+  sfd->source_addr = *addr;
+  sfd->next = daemon->sfds;
+  sfd->ifindex = ifindex;
+  sfd->preallocated = 0;
+  daemon->sfds = sfd;
+
+  return sfd; 
+}
+
+/* create upstream sockets during startup, before root is dropped which may be needed
+   this allows query_port to be a low port and interface binding */
+void pre_allocate_sfds(void)
+{
+  struct server *srv;
+  struct serverfd *sfd;
+  
+  if (daemon->query_port != 0)
+    {
+      union  mysockaddr addr;
+      memset(&addr, 0, sizeof(addr));
+      addr.in.sin_family = AF_INET;
+      addr.in.sin_addr.s_addr = INADDR_ANY;
+      addr.in.sin_port = htons(daemon->query_port);
+#ifdef HAVE_SOCKADDR_SA_LEN
+      addr.in.sin_len = sizeof(struct sockaddr_in);
+#endif
+      if ((sfd = allocate_sfd(&addr, "", 0)))
+	sfd->preallocated = 1;
+
+      memset(&addr, 0, sizeof(addr));
+      addr.in6.sin6_family = AF_INET6;
+      addr.in6.sin6_addr = in6addr_any;
+      addr.in6.sin6_port = htons(daemon->query_port);
+#ifdef HAVE_SOCKADDR_SA_LEN
+      addr.in6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+      if ((sfd = allocate_sfd(&addr, "", 0)))
+	sfd->preallocated = 1;
+    }
+  
+  for (srv = daemon->servers; srv; srv = srv->next)
+    if (!allocate_sfd(&srv->source_addr, srv->interface, srv->ifindex) &&
+	errno != 0 &&
+	option_bool(OPT_NOWILD))
+      {
+	(void)prettyprint_addr(&srv->source_addr, daemon->namebuff);
+	if (srv->interface[0] != 0)
+	  {
+	    strcat(daemon->namebuff, " ");
+	    strcat(daemon->namebuff, srv->interface);
+	  }
+	die(_("failed to bind server socket for %s: %s"),
+	    daemon->namebuff, EC_BADNET);
+      }  
+}
+
+void check_servers(int no_loop_check)
+{
+  struct irec *iface;
+  struct server *serv;
+  struct serverfd *sfd, *tmp, **up;
+  int port = 0, count;
+  int locals = 0;
+  
+#ifdef HAVE_LOOP
+  if (!no_loop_check)
+    loop_send_probes();
+#endif
+
+  /* clear all marks. */
+  mark_servers(0);
+  
+ /* interface may be new since startup */
+  if (!option_bool(OPT_NOWILD))
+    enumerate_interfaces(0);
+
+  /* don't garbage collect pre-allocated sfds. */
+  for (sfd = daemon->sfds; sfd; sfd = sfd->next)
+    sfd->used = sfd->preallocated;
+
+  for (count = 0, serv = daemon->servers; serv; serv = serv->next)
+    {
+      /* Init edns_pktsz for newly created server records. */
+      if (serv->edns_pktsz == 0)
+	serv->edns_pktsz = daemon->edns_pktsz;
+      
+#ifdef HAVE_DNSSEC
+      if (option_bool(OPT_DNSSEC_VALID))
+	{ 
+	  if (!(serv->flags & SERV_FOR_NODOTS))
+	    serv->flags |= SERV_DO_DNSSEC;
+	  
+	  /* Disable DNSSEC validation when using server=/domain/.... servers
+	     unless there's a configured trust anchor. */
+	  if (strlen(serv->domain) != 0)
+	    {
+	      struct ds_config *ds;
+	      char *domain = serv->domain;
+	      
+	      /* .example.com is valid */
+	      while (*domain == '.')
+		domain++;
+	      
+	      for (ds = daemon->ds; ds; ds = ds->next)
+		if (ds->name[0] != 0 && hostname_isequal(domain, ds->name))
+		  break;
+	      
+	      if (!ds)
+		serv->flags &= ~SERV_DO_DNSSEC;
+	    }
+	}
+#endif
+      
+      port = prettyprint_addr(&serv->addr, daemon->namebuff);
+      
+      /* 0.0.0.0 is nothing, the stack treats it like 127.0.0.1 */
+      if (serv->addr.sa.sa_family == AF_INET &&
+	  serv->addr.in.sin_addr.s_addr == 0)
+	{
+	  serv->flags |= SERV_MARK;
+	  continue;
+	}
+      
+      for (iface = daemon->interfaces; iface; iface = iface->next)
+	if (sockaddr_isequal(&serv->addr, &iface->addr))
+	  break;
+      if (iface)
+	{
+	  my_syslog(LOG_WARNING, _("ignoring nameserver %s - local interface"), daemon->namebuff);
+	  serv->flags |= SERV_MARK;
+	  continue;
+	}
+      
+      /* Do we need a socket set? */
+      if (!serv->sfd && 
+	  !(serv->sfd = allocate_sfd(&serv->source_addr, serv->interface, serv->ifindex)) &&
+	  errno != 0)
+	{
+	  my_syslog(LOG_WARNING, 
+		    _("ignoring nameserver %s - cannot make/bind socket: %s"),
+		    daemon->namebuff, strerror(errno));
+	  serv->flags |= SERV_MARK;
+	  continue;
+	}
+      
+      if (serv->sfd)
+	serv->sfd->used = 1;
+      
+      if (++count > SERVERS_LOGGED)
+	continue;
+      
+      if (strlen(serv->domain) != 0 || (serv->flags & SERV_FOR_NODOTS))
+	{
+	  char *s1, *s2, *s3 = "", *s4 = "";
+
+#ifdef HAVE_DNSSEC
+	  if (option_bool(OPT_DNSSEC_VALID) && !(serv->flags & SERV_DO_DNSSEC))
+	    s3 = _("(no DNSSEC)");
+#endif
+	  if (serv->flags & SERV_FOR_NODOTS)
+	    s1 = _("unqualified"), s2 = _("names");
+	  else if (strlen(serv->domain) == 0)
+	    s1 = _("default"), s2 = "";
+	  else
+	    s1 = _("domain"), s2 = serv->domain, s4 = (serv->flags & SERV_WILDCARD) ? "*" : "";
+	  
+	  my_syslog(LOG_INFO, _("using nameserver %s#%d for %s %s%s %s"), daemon->namebuff, port, s1, s4, s2, s3);
+	}
+#ifdef HAVE_LOOP
+      else if (serv->flags & SERV_LOOP)
+	my_syslog(LOG_INFO, _("NOT using nameserver %s#%d - query loop detected"), daemon->namebuff, port); 
+#endif
+      else if (serv->interface[0] != 0)
+	my_syslog(LOG_INFO, _("using nameserver %s#%d(via %s)"), daemon->namebuff, port, serv->interface); 
+      else
+	my_syslog(LOG_INFO, _("using nameserver %s#%d"), daemon->namebuff, port); 
+
+    }
+  
+  for (count = 0, serv = daemon->local_domains; serv; serv = serv->next)
+    {
+       if (++count > SERVERS_LOGGED)
+	 continue;
+       
+       if ((serv->flags & SERV_LITERAL_ADDRESS) &&
+	   !(serv->flags & (SERV_6ADDR | SERV_4ADDR | SERV_ALL_ZEROS)))
+	 {
+	   count--;
+	   if (++locals <= LOCALS_LOGGED)
+	     my_syslog(LOG_INFO, _("using only locally-known addresses for %s"), serv->domain);
+	 }
+       else if (serv->flags & SERV_USE_RESOLV)
+	 my_syslog(LOG_INFO, _("using standard nameservers for %s"), serv->domain);
+    }
+  
+  if (locals > LOCALS_LOGGED)
+    my_syslog(LOG_INFO, _("using %d more local addresses"), locals - LOCALS_LOGGED);
+  if (count - 1 > SERVERS_LOGGED)
+    my_syslog(LOG_INFO, _("using %d more nameservers"), count - SERVERS_LOGGED - 1);
+
+  /* Remove unused sfds */
+  for (sfd = daemon->sfds, up = &daemon->sfds; sfd; sfd = tmp)
+    {
+       tmp = sfd->next;
+       if (!sfd->used) 
+	{
+	  *up = sfd->next;
+	  close(sfd->fd);
+	  free(sfd);
+	} 
+      else
+	up = &sfd->next;
+    }
+  
+  cleanup_servers(); /* remove servers we just deleted. */
+  build_server_array(); 
+}
+
+/* Return zero if no servers found, in that case we keep polling.
+   This is a protection against an update-time/write race on resolv.conf */
+int reload_servers(char *fname)
+{
+  FILE *f;
+  char *line;
+  int gotone = 0;
+
+  /* buff happens to be MAXDNAME long... */
+  if (!(f = fopen(fname, "r")))
+    {
+      my_syslog(LOG_ERR, _("failed to read %s: %s"), fname, strerror(errno));
+      return 0;
+    }
+   
+  mark_servers(SERV_FROM_RESOLV);
+    
+  while ((line = fgets(daemon->namebuff, MAXDNAME, f)))
+    {
+      union mysockaddr addr, source_addr;
+      char *token = strtok(line, " \t\n\r");
+      
+      if (!token)
+	continue;
+      if (strcmp(token, "nameserver") != 0 && strcmp(token, "server") != 0)
+	continue;
+      if (!(token = strtok(NULL, " \t\n\r")))
+	continue;
+      
+      memset(&addr, 0, sizeof(addr));
+      memset(&source_addr, 0, sizeof(source_addr));
+      
+      if (inet_pton(AF_INET, token, &addr.in.sin_addr) > 0)
+	{
+#ifdef HAVE_SOCKADDR_SA_LEN
+	  source_addr.in.sin_len = addr.in.sin_len = sizeof(source_addr.in);
+#endif
+	  source_addr.in.sin_family = addr.in.sin_family = AF_INET;
+	  addr.in.sin_port = htons(NAMESERVER_PORT);
+	  source_addr.in.sin_addr.s_addr = INADDR_ANY;
+	  source_addr.in.sin_port = htons(daemon->query_port);
+	}
+      else 
+	{	
+	  int scope_index = 0;
+	  char *scope_id = strchr(token, '%');
+	  
+	  if (scope_id)
+	    {
+	      *(scope_id++) = 0;
+	      scope_index = if_nametoindex(scope_id);
+	    }
+	  
+	  if (inet_pton(AF_INET6, token, &addr.in6.sin6_addr) > 0)
+	    {
+#ifdef HAVE_SOCKADDR_SA_LEN
+	      source_addr.in6.sin6_len = addr.in6.sin6_len = sizeof(source_addr.in6);
+#endif
+	      source_addr.in6.sin6_family = addr.in6.sin6_family = AF_INET6;
+	      source_addr.in6.sin6_flowinfo = addr.in6.sin6_flowinfo = 0;
+	      addr.in6.sin6_port = htons(NAMESERVER_PORT);
+	      addr.in6.sin6_scope_id = scope_index;
+	      source_addr.in6.sin6_addr = in6addr_any;
+	      source_addr.in6.sin6_port = htons(daemon->query_port);
+	      source_addr.in6.sin6_scope_id = 0;
+	    }
+	  else
+	    continue;
+	}
+
+      add_update_server(SERV_FROM_RESOLV, &addr, &source_addr, NULL, NULL, NULL);
+      gotone = 1;
+    }
+  
+  fclose(f);
+  cleanup_servers();
+
+  return gotone;
+}
+
+/* Called when addresses are added or deleted from an interface */
+void newaddress(time_t now)
+{
+  (void)now;
+  
+  if (option_bool(OPT_CLEVERBIND) || option_bool(OPT_LOCAL_SERVICE) ||
+      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)
+    dhcp_construct_contexts(now);
+  
+  if (daemon->doing_dhcp6)
+    lease_find_interfaces(now);
+#endif
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/option.c b/ap/app/dnsmasq/dnsmasq-2.86/src/option.c
new file mode 100755
index 0000000..f657a08
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/option.c
@@ -0,0 +1,5584 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* define this to get facilitynames */
+#define SYSLOG_NAMES
+#include "dnsmasq.h"
+#include <setjmp.h>
+
+static volatile int mem_recover = 0;
+static jmp_buf mem_jmp;
+static int one_file(char *file, int hard_opt);
+
+/* Solaris headers don't have facility names. */
+#ifdef HAVE_SOLARIS_NETWORK
+static const struct {
+  char *c_name;
+  unsigned int c_val;
+}  facilitynames[] = {
+  { "kern",   LOG_KERN },
+  { "user",   LOG_USER },
+  { "mail",   LOG_MAIL },
+  { "daemon", LOG_DAEMON },
+  { "auth",   LOG_AUTH },
+  { "syslog", LOG_SYSLOG },
+  { "lpr",    LOG_LPR },
+  { "news",   LOG_NEWS },
+  { "uucp",   LOG_UUCP },
+  { "audit",  LOG_AUDIT },
+  { "cron",   LOG_CRON },
+  { "local0", LOG_LOCAL0 },
+  { "local1", LOG_LOCAL1 },
+  { "local2", LOG_LOCAL2 },
+  { "local3", LOG_LOCAL3 },
+  { "local4", LOG_LOCAL4 },
+  { "local5", LOG_LOCAL5 },
+  { "local6", LOG_LOCAL6 },
+  { "local7", LOG_LOCAL7 },
+  { NULL, 0 }
+};
+#endif
+
+#ifndef HAVE_GETOPT_LONG
+struct myoption {
+  const char *name;
+  int has_arg;
+  int *flag;
+  int val;
+};
+#endif
+
+#define OPTSTRING "951yZDNLERKzowefnbvhdkqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:j:P:J:W:Y:2:4:6:7:8:0:3:"
+
+/* options which don't have a one-char version */
+#define LOPT_RELOAD        256
+#define LOPT_NO_NAMES      257
+#define LOPT_TFTP          258
+#define LOPT_SECURE        259
+#define LOPT_PREFIX        260
+#define LOPT_PTR           261
+#define LOPT_BRIDGE        262
+#define LOPT_TFTP_MAX      263
+#define LOPT_FORCE         264
+#define LOPT_NOBLOCK       265
+#define LOPT_LOG_OPTS      266
+#define LOPT_MAX_LOGS      267
+#define LOPT_CIRCUIT       268
+#define LOPT_REMOTE        269
+#define LOPT_SUBSCR        270
+#define LOPT_INTNAME       271
+#define LOPT_BANK          272
+#define LOPT_DHCP_HOST     273
+#define LOPT_APREF         274
+#define LOPT_OVERRIDE      275
+#define LOPT_TFTPPORTS     276
+#define LOPT_REBIND        277
+#define LOPT_NOLAST        278
+#define LOPT_OPTS          279
+#define LOPT_DHCP_OPTS     280
+#define LOPT_MATCH         281
+#define LOPT_BROADCAST     282
+#define LOPT_NEGTTL        283
+#define LOPT_ALTPORT       284
+#define LOPT_SCRIPTUSR     285
+#define LOPT_LOCAL         286
+#define LOPT_NAPTR         287
+#define LOPT_MINPORT       288
+#define LOPT_DHCP_FQDN     289
+#define LOPT_CNAME         290
+#define LOPT_PXE_PROMT     291
+#define LOPT_PXE_SERV      292
+#define LOPT_TEST          293
+#define LOPT_TAG_IF        294
+#define LOPT_PROXY         295
+#define LOPT_GEN_NAMES     296
+#define LOPT_MAXTTL        297
+#define LOPT_NO_REBIND     298
+#define LOPT_LOC_REBND     299
+#define LOPT_ADD_MAC       300
+#define LOPT_DNSSEC        301
+#define LOPT_INCR_ADDR     302
+#define LOPT_CONNTRACK     303
+#define LOPT_FQDN          304
+#define LOPT_LUASCRIPT     305
+#define LOPT_RA            306
+#define LOPT_DUID          307
+#define LOPT_HOST_REC      308
+#define LOPT_TFTP_LC       309
+#define LOPT_RR            310
+#define LOPT_CLVERBIND     311
+#define LOPT_MAXCTTL       312
+#define LOPT_AUTHZONE      313
+#define LOPT_AUTHSERV      314
+#define LOPT_AUTHTTL       315
+#define LOPT_AUTHSOA       316
+#define LOPT_AUTHSFS       317
+#define LOPT_AUTHPEER      318
+#define LOPT_IPSET         319
+#define LOPT_SYNTH         320
+#define LOPT_RELAY         323
+#define LOPT_RA_PARAM      324
+#define LOPT_ADD_SBNET     325
+#define LOPT_QUIET_DHCP    326
+#define LOPT_QUIET_DHCP6   327
+#define LOPT_QUIET_RA      328
+#define LOPT_SEC_VALID     329
+#define LOPT_TRUST_ANCHOR  330
+#define LOPT_DNSSEC_DEBUG  331
+#define LOPT_REV_SERV      332
+#define LOPT_SERVERS_FILE  333
+#define LOPT_DNSSEC_CHECK  334
+#define LOPT_LOCAL_SERVICE 335
+#define LOPT_DNSSEC_TIME   336
+#define LOPT_LOOP_DETECT   337
+#define LOPT_IGNORE_ADDR   338
+#define LOPT_MINCTTL       339
+#define LOPT_DHCP_INOTIFY  340
+#define LOPT_DHOPT_INOTIFY 341
+#define LOPT_HOST_INOTIFY  342
+#define LOPT_DNSSEC_STAMP  343
+#define LOPT_TFTP_NO_FAIL  344
+#define LOPT_MAXPORT       345
+#define LOPT_CPE_ID        346
+#define LOPT_SCRIPT_ARP    347
+#define LOPT_DHCPTTL       348
+#define LOPT_TFTP_MTU      349
+#define LOPT_REPLY_DELAY   350
+#define LOPT_RAPID_COMMIT  351
+#define LOPT_DUMPFILE      352
+#define LOPT_DUMPMASK      353
+#define LOPT_UBUS          354
+#define LOPT_NAME_MATCH    355
+#define LOPT_CAA           356
+#define LOPT_SHARED_NET    357
+#define LOPT_IGNORE_CLID   358
+#define LOPT_SINGLE_PORT   359
+#define LOPT_SCRIPT_TIME   360
+#define LOPT_PXE_VENDOR    361
+#define LOPT_DYNHOST       362
+#define LOPT_LOG_DEBUG     363
+#define LOPT_UMBRELLA	   364
+#define LOPT_CMARK_ALST_EN 365
+#define LOPT_CMARK_ALST    366
+#define LOPT_QUIET_TFTP    367
+ 
+#ifdef HAVE_GETOPT_LONG
+static const struct option opts[] =  
+#else
+static const struct myoption opts[] = 
+#endif
+  { 
+    { "version", 0, 0, 'v' },
+    { "no-hosts", 0, 0, 'h' },
+    { "no-poll", 0, 0, 'n' },
+    { "help", 0, 0, 'w' },
+    { "no-daemon", 0, 0, 'd' },
+    { "log-queries", 2, 0, 'q' },
+    { "user", 2, 0, 'u' },
+    { "group", 2, 0, 'g' },
+    { "resolv-file", 2, 0, 'r' },
+    { "servers-file", 1, 0, LOPT_SERVERS_FILE },
+    { "mx-host", 1, 0, 'm' },
+    { "mx-target", 1, 0, 't' },
+    { "cache-size", 2, 0, 'c' },
+    { "port", 1, 0, 'p' },
+    { "dhcp-leasefile", 2, 0, 'l' },
+    { "dhcp-lease", 1, 0, 'l' },
+    { "dhcp-host", 1, 0, 'G' },
+    { "dhcp-range", 1, 0, 'F' },
+    { "dhcp-option", 1, 0, 'O' },
+    { "dhcp-boot", 1, 0, 'M' },
+    { "domain", 1, 0, 's' },
+    { "domain-suffix", 1, 0, 's' },
+    { "interface", 1, 0, 'i' },
+    { "listen-address", 1, 0, 'a' },
+    { "local-service", 0, 0, LOPT_LOCAL_SERVICE },
+    { "bogus-priv", 0, 0, 'b' },
+    { "bogus-nxdomain", 1, 0, 'B' },
+    { "ignore-address", 1, 0, LOPT_IGNORE_ADDR },
+    { "selfmx", 0, 0, 'e' },
+    { "filterwin2k", 0, 0, 'f' },
+    { "pid-file", 2, 0, 'x' },
+    { "strict-order", 0, 0, 'o' },
+    { "server", 1, 0, 'S' },
+    { "rev-server", 1, 0, LOPT_REV_SERV },
+    { "local", 1, 0, LOPT_LOCAL },
+    { "address", 1, 0, 'A' },
+    { "conf-file", 2, 0, 'C' },
+    { "no-resolv", 0, 0, 'R' },
+    { "expand-hosts", 0, 0, 'E' },
+    { "localmx", 0, 0, 'L' },
+    { "local-ttl", 1, 0, 'T' },
+    { "no-negcache", 0, 0, 'N' },
+    { "addn-hosts", 1, 0, 'H' },
+    { "hostsdir", 1, 0, LOPT_HOST_INOTIFY },
+    { "query-port", 1, 0, 'Q' },
+    { "except-interface", 1, 0, 'I' },
+    { "no-dhcp-interface", 1, 0, '2' },
+    { "domain-needed", 0, 0, 'D' },
+    { "dhcp-lease-max", 1, 0, 'X' },
+    { "bind-interfaces", 0, 0, 'z' },
+    { "read-ethers", 0, 0, 'Z' },
+    { "alias", 1, 0, 'V' },
+    { "dhcp-vendorclass", 1, 0, 'U' },
+    { "dhcp-userclass", 1, 0, 'j' },
+    { "dhcp-ignore", 1, 0, 'J' },
+    { "edns-packet-max", 1, 0, 'P' },
+    { "keep-in-foreground", 0, 0, 'k' },
+    { "dhcp-authoritative", 0, 0, 'K' },
+    { "srv-host", 1, 0, 'W' },
+    { "localise-queries", 0, 0, 'y' },
+    { "txt-record", 1, 0, 'Y' },
+    { "caa-record", 1, 0 , LOPT_CAA },
+    { "dns-rr", 1, 0, LOPT_RR },
+    { "enable-dbus", 2, 0, '1' },
+    { "enable-ubus", 2, 0, LOPT_UBUS },
+    { "bootp-dynamic", 2, 0, '3' },
+    { "dhcp-mac", 1, 0, '4' },
+    { "no-ping", 0, 0, '5' },
+    { "dhcp-script", 1, 0, '6' },
+    { "conf-dir", 1, 0, '7' },
+    { "log-facility", 1, 0 ,'8' },
+    { "leasefile-ro", 0, 0, '9' },
+    { "script-on-renewal", 0, 0, LOPT_SCRIPT_TIME},
+    { "dns-forward-max", 1, 0, '0' },
+    { "clear-on-reload", 0, 0, LOPT_RELOAD },
+    { "dhcp-ignore-names", 2, 0, LOPT_NO_NAMES },
+    { "enable-tftp", 2, 0, LOPT_TFTP },
+    { "tftp-secure", 0, 0, LOPT_SECURE },
+    { "tftp-no-fail", 0, 0, LOPT_TFTP_NO_FAIL },
+    { "tftp-unique-root", 2, 0, LOPT_APREF },
+    { "tftp-root", 1, 0, LOPT_PREFIX },
+    { "tftp-max", 1, 0, LOPT_TFTP_MAX },
+    { "tftp-mtu", 1, 0, LOPT_TFTP_MTU },
+    { "tftp-lowercase", 0, 0, LOPT_TFTP_LC },
+    { "tftp-single-port", 0, 0, LOPT_SINGLE_PORT },
+    { "ptr-record", 1, 0, LOPT_PTR },
+    { "naptr-record", 1, 0, LOPT_NAPTR },
+    { "bridge-interface", 1, 0 , LOPT_BRIDGE },
+    { "shared-network", 1, 0, LOPT_SHARED_NET },
+    { "dhcp-option-force", 1, 0, LOPT_FORCE },
+    { "tftp-no-blocksize", 0, 0, LOPT_NOBLOCK },
+    { "log-dhcp", 0, 0, LOPT_LOG_OPTS },
+    { "log-async", 2, 0, LOPT_MAX_LOGS },
+    { "dhcp-circuitid", 1, 0, LOPT_CIRCUIT },
+    { "dhcp-remoteid", 1, 0, LOPT_REMOTE },
+    { "dhcp-subscrid", 1, 0, LOPT_SUBSCR },
+    { "dhcp-pxe-vendor", 1, 0, LOPT_PXE_VENDOR },
+    { "interface-name", 1, 0, LOPT_INTNAME },
+    { "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
+    { "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
+    { "dhcp-hostsdir", 1, 0, LOPT_DHCP_INOTIFY },
+    { "dhcp-optsdir", 1, 0, LOPT_DHOPT_INOTIFY },
+    { "dhcp-no-override", 0, 0, LOPT_OVERRIDE },
+    { "tftp-port-range", 1, 0, LOPT_TFTPPORTS },
+    { "stop-dns-rebind", 0, 0, LOPT_REBIND },
+    { "rebind-domain-ok", 1, 0, LOPT_NO_REBIND },
+    { "all-servers", 0, 0, LOPT_NOLAST }, 
+    { "dhcp-match", 1, 0, LOPT_MATCH },
+    { "dhcp-name-match", 1, 0, LOPT_NAME_MATCH },
+    { "dhcp-broadcast", 2, 0, LOPT_BROADCAST },
+    { "neg-ttl", 1, 0, LOPT_NEGTTL },
+    { "max-ttl", 1, 0, LOPT_MAXTTL },
+    { "min-cache-ttl", 1, 0, LOPT_MINCTTL },
+    { "max-cache-ttl", 1, 0, LOPT_MAXCTTL },
+    { "dhcp-alternate-port", 2, 0, LOPT_ALTPORT },
+    { "dhcp-scriptuser", 1, 0, LOPT_SCRIPTUSR },
+    { "min-port", 1, 0, LOPT_MINPORT },
+    { "max-port", 1, 0, LOPT_MAXPORT },
+    { "dhcp-fqdn", 0, 0, LOPT_DHCP_FQDN },
+    { "cname", 1, 0, LOPT_CNAME },
+    { "pxe-prompt", 1, 0, LOPT_PXE_PROMT },
+    { "pxe-service", 1, 0, LOPT_PXE_SERV },
+    { "test", 0, 0, LOPT_TEST },
+    { "tag-if", 1, 0, LOPT_TAG_IF },
+    { "dhcp-proxy", 2, 0, LOPT_PROXY },
+    { "dhcp-generate-names", 2, 0, LOPT_GEN_NAMES },
+    { "rebind-localhost-ok", 0, 0,  LOPT_LOC_REBND },
+    { "add-mac", 2, 0, LOPT_ADD_MAC },
+    { "add-subnet", 2, 0, LOPT_ADD_SBNET },
+    { "add-cpe-id", 1, 0 , LOPT_CPE_ID },
+    { "proxy-dnssec", 0, 0, LOPT_DNSSEC },
+    { "dhcp-sequential-ip", 0, 0,  LOPT_INCR_ADDR },
+    { "conntrack", 0, 0, LOPT_CONNTRACK },
+    { "dhcp-client-update", 0, 0, LOPT_FQDN },
+    { "dhcp-luascript", 1, 0, LOPT_LUASCRIPT },
+    { "enable-ra", 0, 0, LOPT_RA },
+    { "dhcp-duid", 1, 0, LOPT_DUID },
+    { "host-record", 1, 0, LOPT_HOST_REC },
+    { "bind-dynamic", 0, 0, LOPT_CLVERBIND },
+    { "auth-zone", 1, 0, LOPT_AUTHZONE },
+    { "auth-server", 1, 0, LOPT_AUTHSERV },
+    { "auth-ttl", 1, 0, LOPT_AUTHTTL },
+    { "auth-soa", 1, 0, LOPT_AUTHSOA },
+    { "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
+    { "auth-peer", 1, 0, LOPT_AUTHPEER }, 
+    { "ipset", 1, 0, LOPT_IPSET },
+    { "connmark-allowlist-enable", 2, 0, LOPT_CMARK_ALST_EN },
+    { "connmark-allowlist", 1, 0, LOPT_CMARK_ALST },
+    { "synth-domain", 1, 0, LOPT_SYNTH },
+    { "dnssec", 0, 0, LOPT_SEC_VALID },
+    { "trust-anchor", 1, 0, LOPT_TRUST_ANCHOR },
+    { "dnssec-debug", 0, 0, LOPT_DNSSEC_DEBUG },
+    { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
+    { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
+    { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
+    { "dhcp-relay", 1, 0, LOPT_RELAY },
+    { "ra-param", 1, 0, LOPT_RA_PARAM },
+    { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
+    { "quiet-dhcp6", 0, 0, LOPT_QUIET_DHCP6 },
+    { "quiet-ra", 0, 0, LOPT_QUIET_RA },
+    { "dns-loop-detect", 0, 0, LOPT_LOOP_DETECT },
+    { "script-arp", 0, 0, LOPT_SCRIPT_ARP },
+    { "dhcp-ttl", 1, 0 , LOPT_DHCPTTL },
+    { "dhcp-reply-delay", 1, 0, LOPT_REPLY_DELAY },
+    { "dhcp-rapid-commit", 0, 0, LOPT_RAPID_COMMIT },
+    { "dumpfile", 1, 0, LOPT_DUMPFILE },
+    { "dumpmask", 1, 0, LOPT_DUMPMASK },
+    { "dhcp-ignore-clid", 0, 0,  LOPT_IGNORE_CLID },
+    { "dynamic-host", 1, 0, LOPT_DYNHOST },
+    { "log-debug", 0, 0, LOPT_LOG_DEBUG },
+	{ "umbrella", 2, 0, LOPT_UMBRELLA },
+    { "quiet-tftp", 0, 0, LOPT_QUIET_TFTP },
+    { NULL, 0, 0, 0 }
+  };
+
+
+#define ARG_DUP       OPT_LAST
+#define ARG_ONE       OPT_LAST + 1
+#define ARG_USED_CL   OPT_LAST + 2
+#define ARG_USED_FILE OPT_LAST + 3
+
+static struct {
+  int opt;
+  unsigned int rept;
+  char * const flagdesc;
+  char * const desc;
+  char * const arg;
+} usage[] = {
+  { 'a', ARG_DUP, "<ipaddr>",  gettext_noop("Specify local address(es) to listen on."), NULL },
+  { 'A', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Return ipaddr for all hosts in specified domains."), NULL },
+  { 'b', OPT_BOGUSPRIV, NULL, gettext_noop("Fake reverse lookups for RFC1918 private address ranges."), NULL },
+  { 'B', ARG_DUP, "<ipaddr>", gettext_noop("Treat ipaddr as NXDOMAIN (defeats Verisign wildcard)."), NULL }, 
+  { 'c', ARG_ONE, "<integer>", gettext_noop("Specify the size of the cache in entries (defaults to %s)."), "$" },
+  { 'C', ARG_DUP, "<path>", gettext_noop("Specify configuration file (defaults to %s)."), CONFFILE },
+  { 'd', OPT_DEBUG, NULL, gettext_noop("Do NOT fork into the background: run in debug mode."), NULL },
+  { 'D', OPT_NODOTS_LOCAL, NULL, gettext_noop("Do NOT forward queries with no domain part."), NULL }, 
+  { 'e', OPT_SELFMX, NULL, gettext_noop("Return self-pointing MX records for local hosts."), NULL },
+  { 'E', OPT_EXPAND, NULL, gettext_noop("Expand simple names in /etc/hosts with domain-suffix."), NULL },
+  { 'f', OPT_FILTER, NULL, gettext_noop("Don't forward spurious DNS requests from Windows hosts."), NULL },
+  { 'F', ARG_DUP, "<ipaddr>,...", gettext_noop("Enable DHCP in the range given with lease duration."), NULL },
+  { 'g', ARG_ONE, "<groupname>", gettext_noop("Change to this group after startup (defaults to %s)."), CHGRP },
+  { 'G', ARG_DUP, "<hostspec>", gettext_noop("Set address or hostname for a specified machine."), NULL },
+  { LOPT_DHCP_HOST, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from file."), NULL },
+  { LOPT_DHCP_OPTS, ARG_DUP, "<path>", gettext_noop("Read DHCP option specs from file."), NULL },
+  { LOPT_DHCP_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP host specs from a directory."), NULL }, 
+  { LOPT_DHOPT_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read DHCP options from a directory."), NULL }, 
+  { LOPT_TAG_IF, ARG_DUP, "tag-expression", gettext_noop("Evaluate conditional tag expression."), NULL },
+  { 'h', OPT_NO_HOSTS, NULL, gettext_noop("Do NOT load %s file."), HOSTSFILE },
+  { 'H', ARG_DUP, "<path>", gettext_noop("Specify a hosts file to be read in addition to %s."), HOSTSFILE },
+  { LOPT_HOST_INOTIFY, ARG_DUP, "<path>", gettext_noop("Read hosts files from a directory."), NULL },
+  { 'i', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to listen on."), NULL },
+  { 'I', ARG_DUP, "<interface>", gettext_noop("Specify interface(s) NOT to listen on.") , NULL },
+  { 'j', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP user class to tag."), NULL },
+  { LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL },
+  { LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL },
+  { LOPT_SUBSCR, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3993 subscriber-id to tag."), NULL },
+  { LOPT_PXE_VENDOR, ARG_DUP, "<vendor>[,...]", gettext_noop("Specify vendor class to match for PXE requests."), NULL },
+  { 'J', ARG_DUP, "tag:<tag>...", gettext_noop("Don't do DHCP for hosts with tag set."), NULL },
+  { LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL }, 
+  { 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL },
+  { 'K', OPT_AUTHORITATIVE, NULL, gettext_noop("Assume we are the only DHCP server on the local network."), NULL },
+  { 'l', ARG_ONE, "<path>", gettext_noop("Specify where to store DHCP leases (defaults to %s)."), LEASEFILE },
+  { 'L', OPT_LOCALMX, NULL, gettext_noop("Return MX records for local hosts."), NULL },
+  { 'm', ARG_DUP, "<host_name>,<target>,<pref>", gettext_noop("Specify an MX record."), NULL },
+  { 'M', ARG_DUP, "<bootp opts>", gettext_noop("Specify BOOTP options to DHCP server."), NULL },
+  { 'n', OPT_NO_POLL, NULL, gettext_noop("Do NOT poll %s file, reload only on SIGHUP."), RESOLVFILE }, 
+  { 'N', OPT_NO_NEG, NULL, gettext_noop("Do NOT cache failed search results."), NULL },
+  { 'o', OPT_ORDER, NULL, gettext_noop("Use nameservers strictly in the order given in %s."), RESOLVFILE },
+  { 'O', ARG_DUP, "<optspec>", gettext_noop("Specify options to be sent to DHCP clients."), NULL },
+  { LOPT_FORCE, ARG_DUP, "<optspec>", gettext_noop("DHCP option sent even if the client does not request it."), NULL},
+  { 'p', ARG_ONE, "<integer>", gettext_noop("Specify port to listen for DNS requests on (defaults to 53)."), NULL },
+  { 'P', ARG_ONE, "<integer>", gettext_noop("Maximum supported UDP packet size for EDNS.0 (defaults to %s)."), "*" },
+  { 'q', ARG_DUP, NULL, gettext_noop("Log DNS queries."), NULL },
+  { 'Q', ARG_ONE, "<integer>", gettext_noop("Force the originating port for upstream DNS queries."), NULL },
+  { 'R', OPT_NO_RESOLV, NULL, gettext_noop("Do NOT read resolv.conf."), NULL },
+  { 'r', ARG_DUP, "<path>", gettext_noop("Specify path to resolv.conf (defaults to %s)."), RESOLVFILE }, 
+  { LOPT_SERVERS_FILE, ARG_ONE, "<path>", gettext_noop("Specify path to file with server= options"), NULL },
+  { 'S', ARG_DUP, "/<domain>/<ipaddr>", gettext_noop("Specify address(es) of upstream servers with optional domains."), NULL },
+  { LOPT_REV_SERV, ARG_DUP, "<addr>/<prefix>,<ipaddr>", gettext_noop("Specify address of upstream servers for reverse address queries"), NULL },
+  { LOPT_LOCAL, ARG_DUP, "/<domain>/", gettext_noop("Never forward queries to specified domains."), NULL },
+  { 's', ARG_DUP, "<domain>[,<range>]", gettext_noop("Specify the domain to be assigned in DHCP leases."), NULL },
+  { 't', ARG_ONE, "<host_name>", gettext_noop("Specify default target in an MX record."), NULL },
+  { 'T', ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for replies from /etc/hosts."), NULL },
+  { LOPT_NEGTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for negative caching."), NULL },
+  { LOPT_MAXTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live in seconds for maximum TTL to send to clients."), NULL },
+  { LOPT_MAXCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live ceiling for cache."), NULL },
+  { LOPT_MINCTTL, ARG_ONE, "<integer>", gettext_noop("Specify time-to-live floor for cache."), NULL },
+  { 'u', ARG_ONE, "<username>", gettext_noop("Change to this user after startup. (defaults to %s)."), CHUSER }, 
+  { 'U', ARG_DUP, "set:<tag>,<class>", gettext_noop("Map DHCP vendor class to tag."), NULL },
+  { 'v', 0, NULL, gettext_noop("Display dnsmasq version and copyright information."), NULL },
+  { 'V', ARG_DUP, "<ipaddr>,<ipaddr>,<netmask>", gettext_noop("Translate IPv4 addresses from upstream servers."), NULL },
+  { 'W', ARG_DUP, "<name>,<target>,...", gettext_noop("Specify a SRV record."), NULL },
+  { 'w', 0, NULL, gettext_noop("Display this message. Use --help dhcp or --help dhcp6 for known DHCP options."), NULL },
+  { 'x', ARG_ONE, "<path>", gettext_noop("Specify path of PID file (defaults to %s)."), RUNFILE },
+  { 'X', ARG_ONE, "<integer>", gettext_noop("Specify maximum number of DHCP leases (defaults to %s)."), "&" },
+  { 'y', OPT_LOCALISE, NULL, gettext_noop("Answer DNS queries based on the interface a query was sent to."), NULL },
+  { 'Y', ARG_DUP, "<name>,<txt>[,<txt]", gettext_noop("Specify TXT DNS record."), NULL },
+  { LOPT_PTR, ARG_DUP, "<name>,<target>", gettext_noop("Specify PTR DNS record."), NULL },
+  { LOPT_INTNAME, ARG_DUP, "<name>,<interface>", gettext_noop("Give DNS name to IPv4 address of interface."), NULL },
+  { 'z', OPT_NOWILD, NULL, gettext_noop("Bind only to interfaces in use."), NULL },
+  { 'Z', OPT_ETHERS, NULL, gettext_noop("Read DHCP static host information from %s."), ETHERSFILE },
+  { '1', ARG_ONE, "[=<busname>]", gettext_noop("Enable the DBus interface for setting upstream servers, etc."), NULL },
+  { LOPT_UBUS, ARG_ONE, "[=<busname>]", gettext_noop("Enable the UBus interface."), NULL },
+  { '2', ARG_DUP, "<interface>", gettext_noop("Do not provide DHCP on this interface, only provide DNS."), NULL },
+  { '3', ARG_DUP, "[=tag:<tag>]...", gettext_noop("Enable dynamic address allocation for bootp."), NULL },
+  { '4', ARG_DUP, "set:<tag>,<mac address>", gettext_noop("Map MAC address (with wildcards) to option set."), NULL },
+  { LOPT_BRIDGE, ARG_DUP, "<iface>,<alias>..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL },
+  { LOPT_SHARED_NET, ARG_DUP, "<iface>|<addr>,<addr>", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL},
+  { '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL },
+  { '6', ARG_ONE, "<path>", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL },
+  { LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL },
+  { LOPT_SCRIPTUSR, ARG_ONE, "<username>", gettext_noop("Run lease-change scripts as this user."), NULL },
+  { LOPT_SCRIPT_ARP, OPT_SCRIPT_ARP, NULL, gettext_noop("Call dhcp-script with changes to local ARP table."), NULL },
+  { '7', ARG_DUP, "<path>", gettext_noop("Read configuration from all the files in this directory."), NULL },
+  { '8', ARG_ONE, "<facility>|<file>", gettext_noop("Log to this syslog facility or file. (defaults to DAEMON)"), NULL },
+  { '9', OPT_LEASE_RO, NULL, gettext_noop("Do not use leasefile."), NULL },
+  { '0', ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent DNS queries. (defaults to %s)"), "!" }, 
+  { LOPT_RELOAD, OPT_RELOAD, NULL, gettext_noop("Clear DNS cache when reloading %s."), RESOLVFILE },
+  { LOPT_NO_NAMES, ARG_DUP, "[=tag:<tag>]...", gettext_noop("Ignore hostnames provided by DHCP clients."), NULL },
+  { LOPT_OVERRIDE, OPT_NO_OVERRIDE, NULL, gettext_noop("Do NOT reuse filename and server fields for extra DHCP options."), NULL },
+  { LOPT_TFTP, ARG_DUP, "[=<intr>[,<intr>]]", gettext_noop("Enable integrated read-only TFTP server."), NULL },
+  { LOPT_PREFIX, ARG_DUP, "<dir>[,<iface>]", gettext_noop("Export files by TFTP only from the specified subtree."), NULL },
+  { LOPT_APREF, ARG_DUP, "[=ip|mac]", gettext_noop("Add client IP or hardware address to tftp-root."), NULL },
+  { LOPT_SECURE, OPT_TFTP_SECURE, NULL, gettext_noop("Allow access only to files owned by the user running dnsmasq."), NULL },
+  { LOPT_TFTP_NO_FAIL, OPT_TFTP_NO_FAIL, NULL, gettext_noop("Do not terminate the service if TFTP directories are inaccessible."), NULL },
+  { LOPT_TFTP_MAX, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent TFTP transfers (defaults to %s)."), "#" },
+  { LOPT_TFTP_MTU, ARG_ONE, "<integer>", gettext_noop("Maximum MTU to use for TFTP transfers."), NULL },
+  { LOPT_NOBLOCK, OPT_TFTP_NOBLOCK, NULL, gettext_noop("Disable the TFTP blocksize extension."), NULL },
+  { LOPT_TFTP_LC, OPT_TFTP_LC, NULL, gettext_noop("Convert TFTP filenames to lowercase"), NULL },
+  { LOPT_TFTPPORTS, ARG_ONE, "<start>,<end>", gettext_noop("Ephemeral port range for use by TFTP transfers."), NULL },
+  { LOPT_SINGLE_PORT, OPT_SINGLE_PORT, NULL, gettext_noop("Use only one port for TFTP server."), NULL },
+  { LOPT_LOG_OPTS, OPT_LOG_OPTS, NULL, gettext_noop("Extra logging for DHCP."), NULL },
+  { LOPT_MAX_LOGS, ARG_ONE, "[=<integer>]", gettext_noop("Enable async. logging; optionally set queue length."), NULL },
+  { LOPT_REBIND, OPT_NO_REBIND, NULL, gettext_noop("Stop DNS rebinding. Filter private IP ranges when resolving."), NULL },
+  { LOPT_LOC_REBND, OPT_LOCAL_REBIND, NULL, gettext_noop("Allow rebinding of 127.0.0.0/8, for RBL servers."), NULL },
+  { LOPT_NO_REBIND, ARG_DUP, "/<domain>/", gettext_noop("Inhibit DNS-rebind protection on this domain."), NULL },
+  { LOPT_NOLAST, OPT_ALL_SERVERS, NULL, gettext_noop("Always perform DNS queries to all servers."), NULL },
+  { LOPT_MATCH, ARG_DUP, "set:<tag>,<optspec>", gettext_noop("Set tag if client includes matching option in request."), NULL },
+  { LOPT_NAME_MATCH, ARG_DUP, "set:<tag>,<string>[*]", gettext_noop("Set tag if client provides given name."), NULL },
+  { LOPT_ALTPORT, ARG_ONE, "[=<ports>]", gettext_noop("Use alternative ports for DHCP."), NULL },
+  { LOPT_NAPTR, ARG_DUP, "<name>,<naptr>", gettext_noop("Specify NAPTR DNS record."), NULL },
+  { LOPT_MINPORT, ARG_ONE, "<port>", gettext_noop("Specify lowest port available for DNS query transmission."), NULL },
+  { LOPT_MAXPORT, ARG_ONE, "<port>", gettext_noop("Specify highest port available for DNS query transmission."), NULL },
+  { 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>[,<iface>]", gettext_noop("Relay DHCP requests to a remote server"), NULL},
+  { LOPT_CNAME, ARG_DUP, "<alias>,<target>[,<ttl>]", 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 },
+  { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
+  { LOPT_ADD_MAC, ARG_DUP, "[=base64|text]", gettext_noop("Add requestor's MAC address to forwarded DNS queries."), NULL },
+  { LOPT_ADD_SBNET, ARG_ONE, "<v4 pref>[,<v6 pref>]", gettext_noop("Add specified IP subnet to forwarded DNS queries."), NULL },
+  { LOPT_CPE_ID, ARG_ONE, "<text>", gettext_noop("Add client identification to forwarded DNS queries."), NULL },
+  { LOPT_DNSSEC, OPT_DNSSEC_PROXY, NULL, gettext_noop("Proxy DNSSEC validation results from upstream nameservers."), NULL },
+  { LOPT_INCR_ADDR, OPT_CONSEC_ADDR, NULL, gettext_noop("Attempt to allocate sequential IP addresses to DHCP clients."), NULL },
+  { LOPT_IGNORE_CLID, OPT_IGNORE_CLID, NULL, gettext_noop("Ignore client identifier option sent by DHCP clients."), NULL },
+  { LOPT_CONNTRACK, OPT_CONNTRACK, NULL, gettext_noop("Copy connection-track mark from queries to upstream connections."), NULL },
+  { LOPT_FQDN, OPT_FQDN_UPDATE, NULL, gettext_noop("Allow DHCP clients to do their own DDNS updates."), NULL },
+  { LOPT_RA, OPT_RA, NULL, gettext_noop("Send router-advertisements for interfaces doing DHCPv6"), NULL },
+  { LOPT_DUID, ARG_ONE, "<enterprise>,<duid>", gettext_noop("Specify DUID_EN-type DHCPv6 server DUID"), NULL },
+  { LOPT_HOST_REC, ARG_DUP, "<name>,<address>[,<ttl>]", gettext_noop("Specify host (A/AAAA and PTR) records"), NULL },
+  { LOPT_DYNHOST, ARG_DUP, "<name>,[<IPv4>][,<IPv6>],<interface-name>", gettext_noop("Specify host record in interface subnet"), NULL },
+  { LOPT_CAA, ARG_DUP, "<name>,<flags>,<tag>,<value>", gettext_noop("Specify certification authority authorization record"), NULL },  
+  { LOPT_RR, ARG_DUP, "<name>,<RR-number>,[<data>]", gettext_noop("Specify arbitrary DNS resource record"), NULL },
+  { LOPT_CLVERBIND, OPT_CLEVERBIND, NULL, gettext_noop("Bind to interfaces in use - check for new interfaces"), NULL },
+  { LOPT_AUTHSERV, ARG_ONE, "<NS>,<interface>", gettext_noop("Export local names to global DNS"), NULL },
+  { LOPT_AUTHZONE, ARG_DUP, "<domain>,[<subnet>...]", gettext_noop("Domain to export to global DNS"), NULL },
+  { LOPT_AUTHTTL, ARG_ONE, "<integer>", gettext_noop("Set TTL for authoritative replies"), NULL },
+  { LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritative zone information"), NULL },
+  { LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
+  { LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
+  { LOPT_IPSET, ARG_DUP, "/<domain>[/<domain>...]/<ipset>...", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
+  { LOPT_CMARK_ALST_EN, ARG_ONE, "[=<mask>]", gettext_noop("Enable filtering of DNS queries with connection-track marks."), NULL },
+  { LOPT_CMARK_ALST, ARG_DUP, "<connmark>[/<mask>][,<pattern>[/<pattern>...]]", gettext_noop("Set allowed DNS patterns for a connection-track mark."), NULL },
+  { LOPT_SYNTH, ARG_DUP, "<domain>,<range>,[<prefix>]", gettext_noop("Specify a domain and address range for synthesised names"), NULL },
+  { LOPT_SEC_VALID, OPT_DNSSEC_VALID, NULL, gettext_noop("Activate DNSSEC validation"), NULL },
+  { LOPT_TRUST_ANCHOR, ARG_DUP, "<domain>,[<class>],...", gettext_noop("Specify trust anchor key digest."), NULL },
+  { LOPT_DNSSEC_DEBUG, OPT_DNSSEC_DEBUG, NULL, gettext_noop("Disable upstream checking for DNSSEC debugging."), NULL },
+  { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
+  { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
+  { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
+  { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
+  { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
+  { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
+  { LOPT_QUIET_RA, OPT_QUIET_RA, NULL, gettext_noop("Do not log RA."), NULL },
+  { LOPT_LOG_DEBUG, OPT_LOG_DEBUG, NULL, gettext_noop("Log debugging information."), NULL }, 
+  { LOPT_LOCAL_SERVICE, OPT_LOCAL_SERVICE, NULL, gettext_noop("Accept queries only from directly-connected networks."), NULL },
+  { LOPT_LOOP_DETECT, OPT_LOOP_DETECT, NULL, gettext_noop("Detect and remove DNS forwarding loops."), NULL },
+  { LOPT_IGNORE_ADDR, ARG_DUP, "<ipaddr>", gettext_noop("Ignore DNS responses containing ipaddr."), NULL }, 
+  { LOPT_DHCPTTL, ARG_ONE, "<ttl>", gettext_noop("Set TTL in DNS responses with DHCP-derived addresses."), NULL }, 
+  { LOPT_REPLY_DELAY, ARG_ONE, "<integer>", gettext_noop("Delay DHCP replies for at least number of seconds."), NULL },
+  { LOPT_RAPID_COMMIT, OPT_RAPID_COMMIT, NULL, gettext_noop("Enables DHCPv4 Rapid Commit option."), NULL },
+  { LOPT_DUMPFILE, ARG_ONE, "<path>", gettext_noop("Path to debug packet dump file"), NULL },
+  { LOPT_DUMPMASK, ARG_ONE, "<hex>", gettext_noop("Mask which packets to dump"), NULL },
+  { LOPT_SCRIPT_TIME, OPT_LEASE_RENEW, NULL, gettext_noop("Call dhcp-script when lease expiry changes."), NULL },
+  { LOPT_UMBRELLA, ARG_ONE, "[=<optspec>]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL },
+  { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL },
+  { 0, 0, NULL, NULL, NULL }
+}; 
+
+/* We hide metacharacters in quoted strings by mapping them into the ASCII control
+   character space. Note that the \0, \t \b \r \033 and \n characters are carefully placed in the
+   following sequence so that they map to themselves: it is therefore possible to call
+   unhide_metas repeatedly on string without breaking things.
+   The transformation gets undone by opt_canonicalise, atoi_check and opt_string_alloc, and a 
+   couple of other places. 
+   Note that space is included here so that
+   --dhcp-option=3, string
+   has five characters, whilst
+   --dhcp-option=3," string"
+   has six.
+*/
+
+static const char meta[] = "\000123456 \b\t\n78\r90abcdefABCDE\033F:,.";
+
+static char hide_meta(char c)
+{
+  unsigned int i;
+
+  for (i = 0; i < (sizeof(meta) - 1); i++)
+    if (c == meta[i])
+      return (char)i;
+  
+  return c;
+}
+
+static char unhide_meta(char cr)
+{ 
+  unsigned int c = cr;
+  
+  if (c < (sizeof(meta) - 1))
+    cr = meta[c];
+  
+  return cr;
+}
+
+static void unhide_metas(char *cp)
+{
+  if (cp)
+    for(; *cp; cp++)
+      *cp = unhide_meta(*cp);
+}
+
+static void *opt_malloc(size_t size)
+{
+  void *ret;
+
+  if (mem_recover)
+    {
+      ret = whine_malloc(size);
+      if (!ret)
+	longjmp(mem_jmp, 1);
+    }
+  else
+    ret = safe_malloc(size);
+  
+  return ret;
+}
+
+static char *opt_string_alloc(const char *cp)
+{
+  char *ret = NULL;
+  size_t len;
+  
+  if (cp && (len = strlen(cp)) != 0)
+    {
+      ret = opt_malloc(len+1);
+      memcpy(ret, cp, len+1); 
+      
+      /* restore hidden metachars */
+      unhide_metas(ret);
+    }
+    
+  return ret;
+}
+
+
+/* find next comma, split string with zero and eliminate spaces.
+   return start of string following comma */
+
+static char *split_chr(char *s, char c)
+{
+  char *comma, *p;
+
+  if (!s || !(comma = strchr(s, c)))
+    return NULL;
+  
+  p = comma;
+  *comma = ' ';
+  
+  for (; *comma == ' '; comma++);
+ 
+  for (; (p >= s) && *p == ' '; p--)
+    *p = 0;
+    
+  return comma;
+}
+
+static char *split(char *s)
+{
+  return split_chr(s, ',');
+}
+
+static char *canonicalise_opt(char *s)
+{
+  char *ret;
+  int nomem;
+
+  if (!s)
+    return 0;
+
+  if (strlen(s) == 0)
+    return opt_string_alloc("");
+
+  unhide_metas(s);
+  if (!(ret = canonicalise(s, &nomem)) && nomem)
+    {
+      if (mem_recover)
+	longjmp(mem_jmp, 1);
+      else
+	die(_("could not get memory"), NULL, EC_NOMEM);
+    }
+
+  return ret;
+}
+
+static int numeric_check(char *a)
+{
+  char *p;
+
+  if (!a)
+    return 0;
+
+  unhide_metas(a);
+  
+  for (p = a; *p; p++)
+     if (*p < '0' || *p > '9')
+       return 0;
+
+  return 1;
+}
+
+static int atoi_check(char *a, int *res)
+{
+  if (!numeric_check(a))
+    return 0;
+  *res = atoi(a);
+  return 1;
+}
+
+static int strtoul_check(char *a, u32 *res)
+{
+  unsigned long x;
+  
+  if (!numeric_check(a))
+    return 0;
+  x = strtoul(a, NULL, 10);
+  if (errno || x > UINT32_MAX) {
+    errno = 0;
+    return 0;
+  }
+  *res = (u32)x;
+  return 1;
+}
+
+static int atoi_check16(char *a, int *res)
+{
+  if (!(atoi_check(a, res)) ||
+      *res < 0 ||
+      *res > 0xffff)
+    return 0;
+
+  return 1;
+}
+
+#ifdef HAVE_DNSSEC
+static int atoi_check8(char *a, int *res)
+{
+  if (!(atoi_check(a, res)) ||
+      *res < 0 ||
+      *res > 0xff)
+    return 0;
+
+  return 1;
+}
+#endif
+
+#ifndef NO_ID
+static void add_txt(char *name, char *txt, int stat)
+{
+  struct txt_record *r = opt_malloc(sizeof(struct txt_record));
+
+  if (txt)
+    {
+      size_t len = strlen(txt);
+      r->txt = opt_malloc(len+1);
+      r->len = len+1;
+      *(r->txt) = len;
+      memcpy((r->txt)+1, txt, len);
+    }
+
+  r->stat = stat;
+  r->name = opt_string_alloc(name);
+  r->next = daemon->txt;
+  daemon->txt = r;
+  r->class = C_CHAOS;
+}
+#endif
+
+static void do_usage(void)
+{
+  char buff[100];
+  int i, j;
+
+  struct {
+    char handle;
+    int val;
+  } tab[] = {
+    { '$', CACHESIZ },
+    { '*', EDNS_PKTSZ },
+    { '&', MAXLEASES },
+    { '!', FTABSIZ },
+    { '#', TFTP_MAX_CONNECTIONS },
+    { '\0', 0 }
+  };
+
+  printf(_("Usage: dnsmasq [options]\n\n"));
+#ifndef HAVE_GETOPT_LONG
+  printf(_("Use short options only on the command line.\n"));
+#endif
+  printf(_("Valid options are:\n"));
+  
+  for (i = 0; usage[i].opt != 0; i++)
+    {
+      char *desc = usage[i].flagdesc; 
+      char *eq = "=";
+      
+      if (!desc || *desc == '[')
+	eq = "";
+      
+      if (!desc)
+	desc = "";
+
+      for ( j = 0; opts[j].name; j++)
+	if (opts[j].val == usage[i].opt)
+	  break;
+      if (usage[i].opt < 256)
+	sprintf(buff, "-%c, ", usage[i].opt);
+      else
+	sprintf(buff, "    ");
+      
+      sprintf(buff+4, "--%s%s%s", opts[j].name, eq, desc);
+      printf("%-55.55s", buff);
+	     
+      if (usage[i].arg)
+	{
+	  strcpy(buff, usage[i].arg);
+	  for (j = 0; tab[j].handle; j++)
+	    if (tab[j].handle == *(usage[i].arg))
+	      sprintf(buff, "%d", tab[j].val);
+	}
+      printf(_(usage[i].desc), buff);
+      printf("\n");
+    }
+}
+
+#define ret_err(x) do { strcpy(errstr, (x)); return 0; } while (0)
+#define ret_err_free(x,m) do { strcpy(errstr, (x)); free((m)); return 0; } while (0)
+#define goto_err(x) do { strcpy(errstr, (x)); goto on_error; } while (0)
+
+static char *parse_mysockaddr(char *arg, union mysockaddr *addr) 
+{
+  if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+    addr->sa.sa_family = AF_INET;
+  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+    addr->sa.sa_family = AF_INET6;
+  else
+    return _("bad address");
+   
+  return NULL;
+}
+
+char *parse_server(char *arg, union mysockaddr *addr, union mysockaddr *source_addr, char *interface, u16 *flags)
+{
+  int source_port = 0, serv_port = NAMESERVER_PORT;
+  char *portno, *source;
+  char *interface_opt = NULL;
+  int scope_index = 0;
+  char *scope_id;
+
+  *interface = 0;
+
+  if (strcmp(arg, "#") == 0)
+    {
+      if (flags)
+	*flags |= SERV_USE_RESOLV;
+      return NULL;
+    }
+  
+  if ((source = split_chr(arg, '@')) && /* is there a source. */
+      (portno = split_chr(source, '#')) &&
+      !atoi_check16(portno, &source_port))
+    return _("bad port");
+  
+  if ((portno = split_chr(arg, '#')) && /* is there a port no. */
+      !atoi_check16(portno, &serv_port))
+    return _("bad port");
+  
+  scope_id = split_chr(arg, '%');
+  
+  if (source) {
+    interface_opt = split_chr(source, '@');
+
+    if (interface_opt)
+      {
+#if defined(SO_BINDTODEVICE)
+	safe_strncpy(interface, source, IF_NAMESIZE);
+	source = interface_opt;
+#else
+	return _("interface binding not supported");
+#endif
+      }
+  }
+
+  if (inet_pton(AF_INET, arg, &addr->in.sin_addr) > 0)
+    {
+      addr->in.sin_port = htons(serv_port);	
+      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET;
+#ifdef HAVE_SOCKADDR_SA_LEN
+      source_addr->in.sin_len = addr->in.sin_len = sizeof(struct sockaddr_in);
+#endif
+      source_addr->in.sin_addr.s_addr = INADDR_ANY;
+      source_addr->in.sin_port = htons(daemon->query_port);
+      
+      if (source)
+	{
+	  if (flags)
+	    *flags |= SERV_HAS_SOURCE;
+	  source_addr->in.sin_port = htons(source_port);
+	  if (!(inet_pton(AF_INET, source, &source_addr->in.sin_addr) > 0))
+	    {
+#if defined(SO_BINDTODEVICE)
+	      if (interface_opt)
+		return _("interface can only be specified once");
+	      
+	      source_addr->in.sin_addr.s_addr = INADDR_ANY;
+	      safe_strncpy(interface, source, IF_NAMESIZE);
+#else
+	      return _("interface binding not supported");
+#endif
+	    }
+	}
+    }
+  else if (inet_pton(AF_INET6, arg, &addr->in6.sin6_addr) > 0)
+    {
+      if (scope_id && (scope_index = if_nametoindex(scope_id)) == 0)
+	return _("bad interface name");
+      
+      addr->in6.sin6_port = htons(serv_port);
+      addr->in6.sin6_scope_id = scope_index;
+      source_addr->in6.sin6_addr = in6addr_any; 
+      source_addr->in6.sin6_port = htons(daemon->query_port);
+      source_addr->in6.sin6_scope_id = 0;
+      addr->sa.sa_family = source_addr->sa.sa_family = AF_INET6;
+      addr->in6.sin6_flowinfo = source_addr->in6.sin6_flowinfo = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+      addr->in6.sin6_len = source_addr->in6.sin6_len = sizeof(addr->in6);
+#endif
+      if (source)
+	{
+	  if (flags)
+	    *flags |= SERV_HAS_SOURCE;
+	  source_addr->in6.sin6_port = htons(source_port);
+	  if (inet_pton(AF_INET6, source, &source_addr->in6.sin6_addr) == 0)
+	    {
+#if defined(SO_BINDTODEVICE)
+	      if (interface_opt)
+		return _("interface can only be specified once");
+	      
+	      source_addr->in6.sin6_addr = in6addr_any;
+	      safe_strncpy(interface, source, IF_NAMESIZE);
+#else
+	      return _("interface binding not supported");
+#endif
+	    }
+	}
+    }
+  else
+    return _("bad address");
+
+  return NULL;
+}
+
+static int domain_rev4(char *domain, struct in_addr addr, int msize)
+{
+  in_addr_t a = ntohl(addr.s_addr);
+ 
+  *domain = 0;
+  
+  switch (msize)
+    {
+    case 32:
+      domain += sprintf(domain, "%u.", a & 0xff);
+      /* fall through */
+    case 24:
+      domain += sprintf(domain, "%d.", (a >> 8) & 0xff);
+      /* fall through */
+    case 16:
+      domain += sprintf(domain, "%d.", (a >> 16) & 0xff);
+      /* fall through */
+    case 8:
+      domain += sprintf(domain, "%d.", (a >> 24) & 0xff);
+      break;
+    default:
+      return 0;
+    }
+  
+  domain += sprintf(domain, "in-addr.arpa");
+  
+  return 1;
+}
+
+static int domain_rev6(char *domain, struct in6_addr *addr, int msize)
+{
+  int i;
+
+  if (msize > 128 || msize%4)
+    return 0;
+  
+  *domain = 0;
+
+  for (i = msize-1; i >= 0; i -= 4)
+    { 
+      int dig = ((unsigned char *)addr)[i>>3];
+      domain += sprintf(domain, "%.1x.", (i>>2) & 1 ? dig & 15 : dig >> 4);
+    }
+  domain += sprintf(domain, "ip6.arpa");
+  
+  return 1;
+}
+
+#ifdef HAVE_DHCP
+
+static int is_tag_prefix(char *arg)
+{
+  if (arg && (strstr(arg, "net:") == arg || strstr(arg, "tag:") == arg))
+    return 1;
+  
+  return 0;
+}
+
+static char *set_prefix(char *arg)
+{
+   if (strstr(arg, "set:") == arg)
+     return arg+4;
+   
+   return arg;
+}
+
+static struct dhcp_netid *dhcp_netid_create(const char *net, struct dhcp_netid *next)
+{
+  struct dhcp_netid *tt;
+  tt = opt_malloc(sizeof (struct dhcp_netid));
+  tt->net = opt_string_alloc(net);
+  tt->next = next;
+  return tt;
+}
+
+static void dhcp_netid_free(struct dhcp_netid *nid)
+{
+  while (nid)
+    {
+      struct dhcp_netid *tmp = nid;
+      nid = nid->next;
+      free(tmp->net);
+      free(tmp);
+    }
+}
+
+/* Parse one or more tag:s before parameters.
+ * Moves arg to the end of tags. */
+static struct dhcp_netid * dhcp_tags(char **arg)
+{
+  struct dhcp_netid *id = NULL;
+
+  while (is_tag_prefix(*arg))
+    {
+      char *comma = split(*arg);
+      id = dhcp_netid_create((*arg)+4, id);
+      *arg = comma;
+    };
+  if (!*arg)
+    {
+      dhcp_netid_free(id);
+      id = NULL;
+    }
+  return id;
+}
+
+static void dhcp_netid_list_free(struct dhcp_netid_list *netid)
+{
+  while (netid)
+    {
+      struct dhcp_netid_list *tmplist = netid;
+      netid = netid->next;
+      dhcp_netid_free(tmplist->list);
+      free(tmplist);
+    }
+}
+
+static void dhcp_config_free(struct dhcp_config *config)
+{
+  if (config)
+    {
+      struct hwaddr_config *hwaddr = config->hwaddr;
+      
+      while (hwaddr)
+        {
+	  struct hwaddr_config *tmp = hwaddr;
+          hwaddr = hwaddr->next;
+	  free(tmp);
+        }
+      
+      dhcp_netid_list_free(config->netid);
+      dhcp_netid_free(config->filter);
+      
+      if (config->flags & CONFIG_CLID)
+        free(config->clid);
+      if (config->flags & CONFIG_NAME)
+	free(config->hostname);
+
+#ifdef HAVE_DHCP6
+      if (config->flags & CONFIG_ADDR6)
+	{
+	  struct addrlist *addr, *tmp;
+	  
+	  for (addr = config->addr6; addr; addr = tmp)
+	    {
+	      tmp = addr->next;
+	      free(addr);
+	    }
+	}
+#endif
+
+      free(config);
+    }
+}
+
+static void dhcp_context_free(struct dhcp_context *ctx)
+{
+  if (ctx)
+    {
+      dhcp_netid_free(ctx->filter);
+      free(ctx->netid.net);
+#ifdef HAVE_DHCP6
+      free(ctx->template_interface);
+#endif
+      free(ctx);
+    }
+}
+
+static void dhcp_opt_free(struct dhcp_opt *opt)
+{
+  if (opt->flags & DHOPT_VENDOR)
+    free(opt->u.vendor_class);
+  dhcp_netid_free(opt->netid);
+  free(opt->val);
+  free(opt);
+}
+
+
+/* This is too insanely large to keep in-line in the switch */
+static int parse_dhcp_opt(char *errstr, char *arg, int flags)
+{
+  struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt));
+  char lenchar = 0, *cp;
+  int addrs, digs, is_addr, is_addr6, is_hex, is_dec, is_string, dots;
+  char *comma = NULL;
+  u16 opt_len = 0;
+  int is6 = 0;
+  int option_ok = 0;
+
+  new->len = 0;
+  new->flags = flags;
+  new->netid = NULL;
+  new->val = NULL;
+  new->opt = 0;
+  
+  while (arg)
+    {
+      comma = split(arg);      
+
+      for (cp = arg; *cp; cp++)
+	if (*cp < '0' || *cp > '9')
+	  break;
+      
+      if (!*cp)
+	{
+	  new->opt = atoi(arg);
+	  opt_len = 0;
+	  option_ok = 1;
+	  break;
+	}
+      
+      if (strstr(arg, "option:") == arg)
+	{
+	  if ((new->opt = lookup_dhcp_opt(AF_INET, arg+7)) != -1)
+	    {
+	      opt_len = lookup_dhcp_len(AF_INET, new->opt);
+	      /* option:<optname> must follow tag and vendor string. */
+	      if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
+		option_ok = 1;
+	    }
+	  break;
+	}
+#ifdef HAVE_DHCP6
+      else if (strstr(arg, "option6:") == arg)
+	{
+	  for (cp = arg+8; *cp; cp++)
+	    if (*cp < '0' || *cp > '9')
+	      break;
+	 
+	  if (!*cp)
+	    {
+	      new->opt = atoi(arg+8);
+	      opt_len = 0;
+	      option_ok = 1;
+	    }
+	  else
+	    {
+	      if ((new->opt = lookup_dhcp_opt(AF_INET6, arg+8)) != -1)
+		{
+		  opt_len = lookup_dhcp_len(AF_INET6, new->opt);
+		  if (!(opt_len & OT_INTERNAL) || flags == DHOPT_MATCH)
+		    option_ok = 1;
+		}
+	    }
+	  /* option6:<opt>|<optname> must follow tag and vendor string. */
+	  is6 = 1;
+	  break;
+	}
+#endif
+      else if (strstr(arg, "vendor:") == arg)
+	{
+	  new->u.vendor_class = (unsigned char *)opt_string_alloc(arg+7);
+	  new->flags |= DHOPT_VENDOR;
+	  if ((new->flags & DHOPT_ENCAPSULATE) || flags == DHOPT_MATCH)
+	    goto_err(_("inappropriate vendor:"));
+	}
+      else if (strstr(arg, "encap:") == arg)
+	{
+	  new->u.encap = atoi(arg+6);
+	  new->flags |= DHOPT_ENCAPSULATE;
+	  if ((new->flags & DHOPT_VENDOR) || flags == DHOPT_MATCH)
+	    goto_err(_("inappropriate encap:"));
+	}
+      else if (strstr(arg, "vi-encap:") == arg)
+	{
+	  new->u.encap = atoi(arg+9);
+	  new->flags |= DHOPT_RFC3925;
+	  if (flags == DHOPT_MATCH)
+	    {
+	      option_ok = 1;
+	      break;
+	    }
+	}
+      else
+	{
+	  /* allow optional "net:" or "tag:" for consistency */
+	  const char *name = (is_tag_prefix(arg)) ? arg+4 : set_prefix(arg);
+	  new->netid = dhcp_netid_create(name, new->netid);
+	}
+      
+      arg = comma; 
+    }
+
+#ifdef HAVE_DHCP6
+  if (is6)
+    {
+      if (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))
+	goto_err(_("unsupported encapsulation for IPv6 option"));
+      
+      if (opt_len == 0 &&
+	  !(new->flags & DHOPT_RFC3925))
+	opt_len = lookup_dhcp_len(AF_INET6, new->opt);
+    }
+  else
+#endif
+    if (opt_len == 0 &&
+	!(new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE | DHOPT_RFC3925)))
+      opt_len = lookup_dhcp_len(AF_INET, new->opt);
+  
+  /* option may be missing with rfc3925 match */
+  if (!option_ok)
+    goto_err(_("bad dhcp-option"));
+  
+  if (comma)
+    {
+      /* characterise the value */
+      char c;
+      int found_dig = 0, found_colon = 0;
+      is_addr = is_addr6 = is_hex = is_dec = is_string = 1;
+      addrs = digs = 1;
+      dots = 0;
+      for (cp = comma; (c = *cp); cp++)
+	if (c == ',')
+	  {
+	    addrs++;
+	    is_dec = is_hex = 0;
+	  }
+	else if (c == ':')
+	  {
+	    digs++;
+	    is_dec = is_addr = 0;
+	    found_colon = 1;
+	  }
+	else if (c == '/') 
+	  {
+	    is_addr6 = is_dec = is_hex = 0;
+	    if (cp == comma) /* leading / means a pathname */
+	      is_addr = 0;
+	  } 
+	else if (c == '.')	
+	  {
+	    is_dec = is_hex = 0;
+	    dots++;
+	  }
+	else if (c == '-')
+	  is_hex = is_addr = is_addr6 = 0;
+	else if (c == ' ')
+	  is_dec = is_hex = 0;
+	else if (!(c >='0' && c <= '9'))
+	  {
+	    is_addr = 0;
+	    if (cp[1] == 0 && is_dec &&
+		(c == 'b' || c == 's' || c == 'i'))
+	      {
+		lenchar = c;
+		*cp = 0;
+	      }
+	    else
+	      is_dec = 0;
+	    if (!((c >='A' && c <= 'F') ||
+		  (c >='a' && c <= 'f') || 
+		  (c == '*' && (flags & DHOPT_MATCH))))
+	      {
+		is_hex = 0;
+		if (c != '[' && c != ']')
+		  is_addr6 = 0;
+	      }
+	  }
+	else
+	  found_dig = 1;
+     
+      if (!found_dig)
+	is_dec = is_addr = 0;
+
+      if (!found_colon)
+	is_addr6 = 0;
+
+#ifdef HAVE_DHCP6
+      /* NTP server option takes hex, addresses or FQDN */
+      if (is6 && new->opt == OPTION6_NTP_SERVER && !is_hex)
+	opt_len |= is_addr6 ? OT_ADDR_LIST : OT_RFC1035_NAME;
+#endif
+     
+      /* We know that some options take addresses */
+      if (opt_len & OT_ADDR_LIST)
+	{
+	  is_string = is_dec = is_hex = 0;
+	  
+	  if (!is6 && (!is_addr || dots == 0))
+	    goto_err(_("bad IP address"));
+
+	   if (is6 && !is_addr6)
+	     goto_err(_("bad IPv6 address"));
+	}
+      /* or names */
+      else if (opt_len & (OT_NAME | OT_RFC1035_NAME | OT_CSTRING))
+	is_addr6 = is_addr = is_dec = is_hex = 0;
+      
+      if (found_dig && (opt_len & OT_TIME) && strlen(comma) > 0)
+	{
+	  int val, fac = 1;
+
+	  switch (comma[strlen(comma) - 1])
+	    {
+	    case 'w':
+	    case 'W':
+	      fac *= 7;
+	      /* fall through */
+	    case 'd':
+	    case 'D':
+	      fac *= 24;
+	      /* fall through */
+	    case 'h':
+	    case 'H':
+	      fac *= 60;
+	      /* fall through */
+	    case 'm':
+	    case 'M':
+	      fac *= 60;
+	      /* fall through */
+	    case 's':
+	    case 'S':
+	      comma[strlen(comma) - 1] = 0;
+	    }
+	  
+	  new->len = 4;
+	  new->val = opt_malloc(4);
+	  val = atoi(comma);
+	  *((int *)new->val) = htonl(val * fac);	  
+	}  
+      else if (is_hex && digs > 1)
+	{
+	  new->len = digs;
+	  new->val = opt_malloc(new->len);
+	  parse_hex(comma, new->val, digs, (flags & DHOPT_MATCH) ? &new->u.wildcard_mask : NULL, NULL);
+	  new->flags |= DHOPT_HEX;
+	}
+      else if (is_dec)
+	{
+	  int i, val = atoi(comma);
+	  /* assume numeric arg is 1 byte except for
+	     options where it is known otherwise.
+	     For vendor class option, we have to hack. */
+	  if (opt_len != 0)
+	    new->len = opt_len;
+	  else if (val & 0xffff0000)
+	    new->len = 4;
+	  else if (val & 0xff00)
+	    new->len = 2;
+	  else
+	    new->len = 1;
+
+	  if (lenchar == 'b')
+	    new->len = 1;
+	  else if (lenchar == 's')
+	    new->len = 2;
+	  else if (lenchar == 'i')
+	    new->len = 4;
+	  
+	  new->val = opt_malloc(new->len);
+	  for (i=0; i<new->len; i++)
+	    new->val[i] = val>>((new->len - i - 1)*8);
+	}
+      else if (is_addr && !is6)	
+	{
+	  struct in_addr in;
+	  unsigned char *op;
+	  char *slash;
+	  /* max length of address/subnet descriptor is five bytes,
+	     add one for the option 120 enc byte too */
+	  new->val = op = opt_malloc((5 * addrs) + 1);
+	  new->flags |= DHOPT_ADDR;
+
+	  if (!(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)) && 
+	      new->opt == OPTION_SIP_SERVER)
+	    {
+	      *(op++) = 1; /* RFC 3361 "enc byte" */
+	      new->flags &= ~DHOPT_ADDR;
+	    }
+	  while (addrs--) 
+	    {
+	      cp = comma;
+	      comma = split(cp);
+	      slash = split_chr(cp, '/');
+	      if (!inet_pton(AF_INET, cp, &in))
+		goto_err(_("bad IPv4 address"));
+	      if (!slash)
+		{
+		  memcpy(op, &in, INADDRSZ);
+		  op += INADDRSZ;
+		}
+	      else
+		{
+		  unsigned char *p = (unsigned char *)&in;
+		  int netsize = atoi(slash);
+		  *op++ = netsize;
+		  if (netsize > 0)
+		    *op++ = *p++;
+		  if (netsize > 8)
+		    *op++ = *p++;
+		  if (netsize > 16)
+		    *op++ = *p++;
+		  if (netsize > 24)
+		    *op++ = *p++;
+		  new->flags &= ~DHOPT_ADDR; /* cannot re-write descriptor format */
+		} 
+	    }
+	  new->len = op - new->val;
+	}
+      else if (is_addr6 && is6)
+	{
+	  unsigned char *op;
+	  new->val = op = opt_malloc(16 * addrs);
+	  new->flags |= DHOPT_ADDR6;
+	  while (addrs--) 
+	    {
+	      cp = comma;
+	      comma = split(cp);
+	      
+	      /* check for [1234::7] */
+	      if (*cp == '[')
+		cp++;
+	      if (strlen(cp) > 1 && cp[strlen(cp)-1] == ']')
+		cp[strlen(cp)-1] = 0;
+	      
+	      if (inet_pton(AF_INET6, cp, op))
+		{
+		  op += IN6ADDRSZ;
+		  continue;
+		}
+
+	      goto_err(_("bad IPv6 address"));
+	    } 
+	  new->len = op - new->val;
+	}
+      else if (is_string)
+	{
+ 	  /* text arg */
+	  if ((new->opt == OPTION_DOMAIN_SEARCH || new->opt == OPTION_SIP_SERVER) &&
+	      !is6 && !(new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR | DHOPT_RFC3925)))
+	    {
+	      /* dns search, RFC 3397, or SIP, RFC 3361 */
+	      unsigned char *q, *r, *tail;
+	      unsigned char *p, *m = NULL, *newp;
+	      size_t newlen, len = 0;
+	      int header_size = (new->opt == OPTION_DOMAIN_SEARCH) ? 0 : 1;
+	      
+	      arg = comma;
+	      comma = split(arg);
+	      
+	      while (arg && *arg)
+		{
+		  char *in, *dom = NULL;
+		  size_t domlen = 1;
+		  /* Allow "." as an empty domain */
+		  if (strcmp (arg, ".") != 0)
+		    {
+		      if (!(dom = canonicalise_opt(arg)))
+			goto_err(_("bad domain in dhcp-option"));
+			
+		      domlen = strlen(dom) + 2;
+		    }
+		      
+		  newp = opt_malloc(len + domlen + header_size);
+		  if (m)
+		    {
+		      memcpy(newp, m, header_size + len);
+		      free(m);
+		    }
+		  m = newp;
+		  p = m + header_size;
+		  q = p + len;
+		  
+		  /* add string on the end in RFC1035 format */
+		  for (in = dom; in && *in;) 
+		    {
+		      unsigned char *cp = q++;
+		      int j;
+		      for (j = 0; *in && (*in != '.'); in++, j++)
+			*q++ = *in;
+		      *cp = j;
+		      if (*in)
+			in++;
+		    }
+		  *q++ = 0;
+		  free(dom);
+		  
+		  /* Now tail-compress using earlier names. */
+		  newlen = q - p;
+		  for (tail = p + len; *tail; tail += (*tail) + 1)
+		    for (r = p; r - p < (int)len; r += (*r) + 1)
+		      if (strcmp((char *)r, (char *)tail) == 0)
+			{
+			  PUTSHORT((r - p) | 0xc000, tail); 
+			  newlen = tail - p;
+			  goto end;
+			}
+		end:
+		  len = newlen;
+		  
+		  arg = comma;
+		  comma = split(arg);
+		}
+      
+	      /* RFC 3361, enc byte is zero for names */
+	      if (new->opt == OPTION_SIP_SERVER && m)
+		m[0] = 0;
+	      new->len = (int) len + header_size;
+	      new->val = m;
+	    }
+#ifdef HAVE_DHCP6
+	  else if (comma && (opt_len & OT_CSTRING))
+	    {
+	      /* length fields are two bytes so need 16 bits for each string */
+	      int i, commas = 1;
+	      unsigned char *p, *newp;
+
+	      for (i = 0; comma[i]; i++)
+		if (comma[i] == ',')
+		  commas++;
+	      
+	      newp = opt_malloc(strlen(comma)+(2*commas));	  
+	      p = newp;
+	      arg = comma;
+	      comma = split(arg);
+	      
+	      while (arg && *arg)
+		{
+		  u16 len = strlen(arg);
+		  unhide_metas(arg);
+		  PUTSHORT(len, p);
+		  memcpy(p, arg, len);
+		  p += len; 
+
+		  arg = comma;
+		  comma = split(arg);
+		}
+
+	      new->val = newp;
+	      new->len = p - newp;
+	    }
+	  else if (comma && (opt_len & OT_RFC1035_NAME))
+	    {
+	      unsigned char *p = NULL, *q, *newp, *end;
+	      int len = 0;
+	      int header_size = (is6 && new->opt == OPTION6_NTP_SERVER) ? 4 : 0;
+	      arg = comma;
+	      comma = split(arg);
+	      
+	      while (arg && *arg)
+		{
+		  char *dom = canonicalise_opt(arg);
+		  if (!dom)
+		    goto_err(_("bad domain in dhcp-option"));
+		    		  
+		  newp = opt_malloc(len + header_size + strlen(dom) + 2);
+		  
+		  if (p)
+		    {
+		      memcpy(newp, p, len);
+		      free(p);
+		    }
+		  
+		  p = newp;
+		  q = p + len;
+		  end = do_rfc1035_name(q + header_size, dom, NULL);
+		  *end++ = 0;
+		  if (is6 && new->opt == OPTION6_NTP_SERVER)
+		    {
+		      PUTSHORT(NTP_SUBOPTION_SRV_FQDN, q);
+		      PUTSHORT(end - q - 2, q);
+		    }
+		  len = end - p;
+		  free(dom);
+
+		  arg = comma;
+		  comma = split(arg);
+		}
+	      
+	      new->val = p;
+	      new->len = len;
+	    }
+#endif
+	  else
+	    {
+	      new->len = strlen(comma);
+	      /* keep terminating zero on string */
+	      new->val = (unsigned char *)opt_string_alloc(comma);
+	      new->flags |= DHOPT_STRING;
+	    }
+	}
+    }
+
+  if (!is6 && 
+      ((new->len > 255) || 
+      (new->len > 253 && (new->flags & (DHOPT_VENDOR | DHOPT_ENCAPSULATE))) ||
+       (new->len > 250 && (new->flags & DHOPT_RFC3925))))
+    goto_err(_("dhcp-option too long"));
+  
+  if (flags == DHOPT_MATCH)
+    {
+      if ((new->flags & (DHOPT_ENCAPSULATE | DHOPT_VENDOR)) ||
+	  !new->netid ||
+	  new->netid->next)
+	goto_err(_("illegal dhcp-match"));
+       
+      if (is6)
+	{
+	  new->next = daemon->dhcp_match6;
+	  daemon->dhcp_match6 = new;
+	}
+      else
+	{
+	  new->next = daemon->dhcp_match;
+	  daemon->dhcp_match = new;
+	}
+    }
+  else if (is6)
+    {
+      new->next = daemon->dhcp_opts6;
+      daemon->dhcp_opts6 = new;
+    }
+  else
+    {
+      new->next = daemon->dhcp_opts;
+      daemon->dhcp_opts = new;
+    }
+    
+  return 1;
+on_error:
+  dhcp_opt_free(new);
+  return 0;
+}
+
+#endif
+
+void set_option_bool(unsigned int opt)
+{
+  option_var(opt) |= option_val(opt);
+}
+
+void reset_option_bool(unsigned int opt)
+{
+  option_var(opt) &= ~(option_val(opt));
+}
+
+static int one_opt(int option, char *arg, char *errstr, char *gen_err, int command_line, int servers_only)
+{      
+  int i;
+  char *comma;
+
+  if (option == '?')
+    ret_err(gen_err);
+  
+  for (i=0; usage[i].opt != 0; i++)
+    if (usage[i].opt == option)
+      {
+	 int rept = usage[i].rept;
+	 
+	 if (command_line)
+	   {
+	     /* command line */
+	     if (rept == ARG_USED_CL)
+	       ret_err(_("illegal repeated flag"));
+	     if (rept == ARG_ONE)
+	       usage[i].rept = ARG_USED_CL;
+	   }
+	 else
+	   {
+	     /* allow file to override command line */
+	     if (rept == ARG_USED_FILE)
+	       ret_err(_("illegal repeated keyword"));
+	     if (rept == ARG_USED_CL || rept == ARG_ONE)
+	       usage[i].rept = ARG_USED_FILE;
+	   }
+
+	 if (rept != ARG_DUP && rept != ARG_ONE && rept != ARG_USED_CL) 
+	   {
+	     set_option_bool(rept);
+	     return 1;
+	   }
+       
+	 break;
+      }
+  
+  switch (option)
+    { 
+    case 'C': /* --conf-file */
+      {
+	char *file = opt_string_alloc(arg);
+	if (file)
+	  {
+	    one_file(file, 0);
+	    free(file);
+	  }
+	break;
+      }
+
+    case '7': /* --conf-dir */	      
+      {
+	DIR *dir_stream;
+	struct dirent *ent;
+	char *directory, *path;
+	struct list {
+	  char *name;
+	  struct list *next;
+	} *ignore_suffix = NULL, *match_suffix = NULL, *files = NULL, *li;
+	
+	comma = split(arg);
+	if (!(directory = opt_string_alloc(arg)))
+	  break;
+	
+	for (arg = comma; arg; arg = comma) 
+	  {
+	    comma = split(arg);
+	    if (strlen(arg) != 0)
+	      {
+		li = opt_malloc(sizeof(struct list));
+		if (*arg == '*')
+		  {
+		    /* "*" with no suffix is a no-op */
+		    if (arg[1] == 0)
+		      free(li);
+		    else
+		      {
+			li->next = match_suffix;
+			match_suffix = li;
+			/* Have to copy: buffer is overwritten */
+			li->name = opt_string_alloc(arg+1);
+		      }
+		  }
+		else
+		  {
+		    li->next = ignore_suffix;
+		    ignore_suffix = li;
+		    /* Have to copy: buffer is overwritten */
+		    li->name = opt_string_alloc(arg);
+		  }
+	      }
+	  }
+	
+	if (!(dir_stream = opendir(directory)))
+	  die(_("cannot access directory %s: %s"), directory, EC_FILE);
+	
+	while ((ent = readdir(dir_stream)))
+	  {
+	    size_t len = strlen(ent->d_name);
+	    struct stat buf;
+	    
+	    /* ignore emacs backups and dotfiles */
+	    if (len == 0 ||
+		ent->d_name[len - 1] == '~' ||
+		(ent->d_name[0] == '#' && ent->d_name[len - 1] == '#') ||
+		ent->d_name[0] == '.')
+	      continue;
+
+	    if (match_suffix)
+	      {
+		for (li = match_suffix; li; li = li->next)
+		  {
+		    /* check for required suffices */
+		    size_t ls = strlen(li->name);
+		    if (len > ls &&
+			strcmp(li->name, &ent->d_name[len - ls]) == 0)
+		      break;
+		  }
+		if (!li)
+		  continue;
+	      }
+	    
+	    for (li = ignore_suffix; li; li = li->next)
+	      {
+		/* check for proscribed suffices */
+		size_t ls = strlen(li->name);
+		if (len > ls &&
+		    strcmp(li->name, &ent->d_name[len - ls]) == 0)
+		  break;
+	      }
+	    if (li)
+	      continue;
+	    
+	    path = opt_malloc(strlen(directory) + len + 2);
+	    strcpy(path, directory);
+	    strcat(path, "/");
+	    strcat(path, ent->d_name);
+
+	    /* files must be readable */
+	    if (stat(path, &buf) == -1)
+	      die(_("cannot access %s: %s"), path, EC_FILE);
+	    
+	    /* only reg files allowed. */
+	    if (S_ISREG(buf.st_mode))
+	      {
+		/* sort files into order. */
+		struct list **up, *new = opt_malloc(sizeof(struct list));
+		new->name = path;
+		
+		for (up = &files, li = files; li; up = &li->next, li = li->next)
+		  if (strcmp(li->name, path) >=0)
+		    break;
+
+		new->next = li;
+		*up = new;
+	      }
+
+	  }
+
+	for (li = files; li; li = li->next)
+	  one_file(li->name, 0);
+      	
+	closedir(dir_stream);
+	free(directory);
+	for(; ignore_suffix; ignore_suffix = li)
+	  {
+	    li = ignore_suffix->next;
+	    free(ignore_suffix->name);
+	    free(ignore_suffix);
+	  }
+	for(; match_suffix; match_suffix = li)
+	  {
+	    li = match_suffix->next;
+	    free(match_suffix->name);
+	    free(match_suffix);
+	  }
+	for(; files; files = li)
+	  {
+	    li = files->next;
+	    free(files->name);
+	    free(files);
+	  }
+	break;
+      }
+
+    case LOPT_ADD_SBNET: /* --add-subnet */
+      set_option_bool(OPT_CLIENT_SUBNET);
+      if (arg)
+	{
+          char *err, *end;
+	  comma = split(arg);
+
+          struct mysubnet* new = opt_malloc(sizeof(struct mysubnet));
+          if ((end = split_chr(arg, '/')))
+	    {
+	      /* has subnet+len */
+	      err = parse_mysockaddr(arg, &new->addr);
+	      if (err)
+		ret_err_free(err, new);
+	      if (!atoi_check(end, &new->mask))
+		ret_err_free(gen_err, new);
+	      new->addr_used = 1;
+	    } 
+	  else if (!atoi_check(arg, &new->mask))
+	    ret_err_free(gen_err, new);
+	    
+          daemon->add_subnet4 = new;
+
+          if (comma)
+            {
+	      new = opt_malloc(sizeof(struct mysubnet));
+	      if ((end = split_chr(comma, '/')))
+		{
+		  /* has subnet+len */
+                  err = parse_mysockaddr(comma, &new->addr);
+                  if (err)
+                    ret_err_free(err, new);
+                  if (!atoi_check(end, &new->mask))
+                    ret_err_free(gen_err, new);
+                  new->addr_used = 1;
+                }
+              else
+                {
+                  if (!atoi_check(comma, &new->mask))
+                    ret_err_free(gen_err, new);
+                }
+          
+	      daemon->add_subnet6 = new;
+	    }
+	}
+      break;
+
+    case '1': /* --enable-dbus */
+      set_option_bool(OPT_DBUS);
+      if (arg)
+	daemon->dbus_name = opt_string_alloc(arg);
+      else
+	daemon->dbus_name = DNSMASQ_SERVICE;
+      break;
+
+    case LOPT_UBUS: /* --enable-ubus */
+      set_option_bool(OPT_UBUS);
+      if (arg)
+	daemon->ubus_name = opt_string_alloc(arg);
+      else
+	daemon->ubus_name = DNSMASQ_UBUS_NAME;
+      break;
+
+    case '8': /* --log-facility */
+      /* may be a filename */
+      if (strchr(arg, '/') || strcmp (arg, "-") == 0)
+	daemon->log_file = opt_string_alloc(arg);
+      else
+	{	  
+#ifdef __ANDROID__
+	  ret_err(_("setting log facility is not possible under Android"));
+#else
+	  for (i = 0; facilitynames[i].c_name; i++)
+	    if (hostname_isequal((char *)facilitynames[i].c_name, arg))
+	      break;
+	  
+	  if (facilitynames[i].c_name)
+	    daemon->log_fac = facilitynames[i].c_val;
+	  else
+	    ret_err(_("bad log facility"));
+#endif
+	}
+      break;
+
+    case 'x': /* --pid-file */
+      daemon->runfile = opt_string_alloc(arg);
+      break;
+
+    case 'r': /* --resolv-file */
+      {
+	char *name = opt_string_alloc(arg);
+	struct resolvc *new, *list = daemon->resolv_files;
+	
+	if (list && list->is_default)
+	  {
+	    /* replace default resolv file - possibly with nothing */
+	    if (name)
+	      {
+		list->is_default = 0;
+		list->name = name;
+	      }
+	    else
+	      list = NULL;
+	  }
+	else if (name)
+	  {
+	    new = opt_malloc(sizeof(struct resolvc));
+	    new->next = list;
+	    new->name = name;
+	    new->is_default = 0;
+	    new->mtime = 0;
+	    new->logged = 0;
+	    list = new;
+	  }
+	daemon->resolv_files = list;
+	break;
+      }
+
+    case LOPT_SERVERS_FILE:
+      daemon->servers_file = opt_string_alloc(arg);
+      break;
+      
+    case 'm':  /* --mx-host */
+      {
+	int pref = 1;
+	struct mx_srv_record *new;
+	char *name, *target = NULL;
+
+	if ((comma = split(arg)))
+	  {
+	    char *prefstr;
+	    if ((prefstr = split(comma)) && !atoi_check16(prefstr, &pref))
+	      ret_err(_("bad MX preference"));
+	  }
+	
+	if (!(name = canonicalise_opt(arg)) || 
+	    (comma && !(target = canonicalise_opt(comma))))
+	  ret_err(_("bad MX name"));
+	
+	new = opt_malloc(sizeof(struct mx_srv_record));
+	new->next = daemon->mxnames;
+	daemon->mxnames = new;
+	new->issrv = 0;
+	new->name = name;
+	new->target = target; /* may be NULL */
+	new->weight = pref;
+	break;
+      }
+      
+    case 't': /*  --mx-target */
+      if (!(daemon->mxtarget = canonicalise_opt(arg)))
+	ret_err(_("bad MX target"));
+      break;
+
+    case LOPT_DUMPFILE:  /* --dumpfile */
+      daemon->dump_file = opt_string_alloc(arg);
+      break;
+
+    case LOPT_DUMPMASK:  /* --dumpmask */
+      daemon->dump_mask = strtol(arg, NULL, 0);
+      break;
+      
+#ifdef HAVE_DHCP      
+    case 'l':  /* --dhcp-leasefile */
+      daemon->lease_file = opt_string_alloc(arg);
+      break;
+      
+      /* Sorry about the gross pre-processor abuse */
+    case '6':             /* --dhcp-script */
+    case LOPT_LUASCRIPT:  /* --dhcp-luascript */
+#  if !defined(HAVE_SCRIPT)
+      ret_err(_("recompile with HAVE_SCRIPT defined to enable lease-change scripts"));
+#  else
+      if (option == LOPT_LUASCRIPT)
+#    if !defined(HAVE_LUASCRIPT)
+	ret_err(_("recompile with HAVE_LUASCRIPT defined to enable Lua scripts"));
+#    else
+        daemon->luascript = opt_string_alloc(arg);
+#    endif
+      else
+        daemon->lease_change_command = opt_string_alloc(arg);
+#  endif
+      break;
+#endif /* HAVE_DHCP */
+
+    case LOPT_DHCP_HOST:     /* --dhcp-hostsfile */
+    case LOPT_DHCP_OPTS:     /* --dhcp-optsfile */
+    case LOPT_DHCP_INOTIFY:  /* --dhcp-hostsdir */
+    case LOPT_DHOPT_INOTIFY: /* --dhcp-optsdir */
+    case LOPT_HOST_INOTIFY:  /* --hostsdir */
+    case 'H':                /* --addn-hosts */
+      {
+	struct hostsfile *new = opt_malloc(sizeof(struct hostsfile));
+	static unsigned int hosts_index = SRC_AH;
+	new->fname = opt_string_alloc(arg);
+	new->index = hosts_index++;
+	new->flags = 0;
+	if (option == 'H')
+	  {
+	    new->next = daemon->addn_hosts;
+	    daemon->addn_hosts = new;
+	  }
+	else if (option == LOPT_DHCP_HOST)
+	  {
+	    new->next = daemon->dhcp_hosts_file;
+	    daemon->dhcp_hosts_file = new;
+	  }
+	else if (option == LOPT_DHCP_OPTS)
+	  {
+	    new->next = daemon->dhcp_opts_file;
+	    daemon->dhcp_opts_file = new;
+	  } 	  
+	else 
+	  {
+	    new->next = daemon->dynamic_dirs;
+	    daemon->dynamic_dirs = new; 
+	    if (option == LOPT_DHCP_INOTIFY)
+	      new->flags |= AH_DHCP_HST;
+	    else if (option == LOPT_DHOPT_INOTIFY)
+	      new->flags |= AH_DHCP_OPT;
+	    else if (option == LOPT_HOST_INOTIFY)
+	      new->flags |= AH_HOSTS;
+	  }
+	
+	break;
+      }
+      
+    case LOPT_AUTHSERV: /* --auth-server */
+      comma = split(arg);
+      
+      daemon->authserver = opt_string_alloc(arg);
+      
+      while ((arg = comma))
+	{
+	  struct iname *new = opt_malloc(sizeof(struct iname));
+	  comma = split(arg);
+	  new->name = NULL;
+	  unhide_metas(arg);
+	  if (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0)
+	    new->addr.sa.sa_family = AF_INET;
+	  else if (inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0)
+	    new->addr.sa.sa_family = AF_INET6;
+	  else
+	    {
+	      char *fam = split_chr(arg, '/');
+	      new->name = opt_string_alloc(arg);
+	      new->addr.sa.sa_family = 0;
+	      if (fam)
+		{
+		  if (strcmp(fam, "4") == 0)
+		    new->addr.sa.sa_family = AF_INET;
+		  else if (strcmp(fam, "6") == 0)
+		    new->addr.sa.sa_family = AF_INET6;
+		  else
+		  {
+		    free(new->name);
+		    ret_err_free(gen_err, new);
+		  }
+		} 
+	    }
+	  new->next = daemon->authinterface;
+	  daemon->authinterface = new;
+	};
+            
+      break;
+
+    case LOPT_AUTHSFS: /* --auth-sec-servers */
+      {
+	struct name_list *new;
+
+	do {
+	  comma = split(arg);
+	  new = opt_malloc(sizeof(struct name_list));
+	  new->name = opt_string_alloc(arg);
+	  new->next = daemon->secondary_forward_server;
+	  daemon->secondary_forward_server = new;
+	  arg = comma;
+	} while (arg);
+	break;
+      }
+	
+    case LOPT_AUTHZONE: /* --auth-zone */
+      {
+	struct auth_zone *new;
+	
+	comma = split(arg);
+		
+	new = opt_malloc(sizeof(struct auth_zone));
+	new->domain = opt_string_alloc(arg);
+	new->subnet = NULL;
+	new->exclude = NULL;
+	new->interface_names = NULL;
+	new->next = daemon->auth_zones;
+	daemon->auth_zones = new;
+
+	while ((arg = comma))
+	  {
+	    int prefixlen = 0;
+	    int is_exclude = 0;
+	    char *prefix;
+	    struct addrlist *subnet =  NULL;
+	    union all_addr addr;
+
+	    comma = split(arg);
+	    prefix = split_chr(arg, '/');
+	    
+	    if (prefix && !atoi_check(prefix, &prefixlen))
+	      ret_err(gen_err);
+	    
+	    if (strstr(arg, "exclude:") == arg)
+	      {
+		    is_exclude = 1;
+		    arg = arg+8;
+	      }
+
+	    if (inet_pton(AF_INET, arg, &addr.addr4))
+	      {
+		subnet = opt_malloc(sizeof(struct addrlist));
+		subnet->prefixlen = (prefixlen == 0) ? 24 : prefixlen;
+		subnet->flags = ADDRLIST_LITERAL;
+	      }
+	    else if (inet_pton(AF_INET6, arg, &addr.addr6))
+	      {
+		subnet = opt_malloc(sizeof(struct addrlist));
+		subnet->prefixlen = (prefixlen == 0) ? 64 : prefixlen;
+		subnet->flags = ADDRLIST_LITERAL | ADDRLIST_IPV6;
+	      }
+	    else 
+	      {
+		struct auth_name_list *name =  opt_malloc(sizeof(struct auth_name_list));
+		name->name = opt_string_alloc(arg);
+		name->flags = AUTH4 | AUTH6;
+		name->next = new->interface_names;
+		new->interface_names = name;
+		if (prefix)
+		  {
+		    if (prefixlen == 4)
+		      name->flags &= ~AUTH6;
+		    else if (prefixlen == 6)
+		      name->flags &= ~AUTH4;
+		    else
+		      ret_err(gen_err);
+		  }
+	      }
+	    
+	    if (subnet)
+	      {
+		subnet->addr = addr;
+
+		if (is_exclude)
+		  {
+		    subnet->next = new->exclude;
+		    new->exclude = subnet;
+		  }
+		else
+		  {
+		    subnet->next = new->subnet;
+		    new->subnet = subnet;
+		  }
+	      }
+	  }
+	break;
+      }
+      
+    case  LOPT_AUTHSOA: /* --auth-soa */
+      comma = split(arg);
+      daemon->soa_sn = (u32)atoi(arg);
+      if (comma)
+	{
+	  char *cp;
+	  arg = comma;
+	  comma = split(arg);
+	  daemon->hostmaster = opt_string_alloc(arg);
+	  for (cp = daemon->hostmaster; cp && *cp; cp++)
+	    if (*cp == '@')
+	      *cp = '.';
+
+	  if (comma)
+	    {
+	      arg = comma;
+	      comma = split(arg); 
+	      daemon->soa_refresh = (u32)atoi(arg);
+	      if (comma)
+		{
+		  arg = comma;
+		  comma = split(arg); 
+		  daemon->soa_retry = (u32)atoi(arg);
+		  if (comma)
+		    daemon->soa_expiry = (u32)atoi(comma);
+		}
+	    }
+	}
+
+      break;
+
+    case 's':         /* --domain */
+    case LOPT_SYNTH:  /* --synth-domain */
+      if (strcmp (arg, "#") == 0)
+	set_option_bool(OPT_RESOLV_DOMAIN);
+      else
+	{
+	  char *d, *d_raw = arg;
+	  comma = split(arg);
+	  if (!(d = canonicalise_opt(d_raw)))
+	    ret_err(gen_err);
+	  else
+	    {
+	      free(d); /* allocate this again below. */
+	      if (comma)
+		{
+		  struct cond_domain *new = opt_malloc(sizeof(struct cond_domain));
+		  char *netpart;
+		  
+		  new->prefix = NULL;
+		  new->indexed = 0;
+		  new->prefixlen = 0;
+		  
+		  unhide_metas(comma);
+		  if ((netpart = split_chr(comma, '/')))
+		    {
+		      int msize;
+
+		      arg = split(netpart);
+		      if (!atoi_check(netpart, &msize))
+			ret_err_free(gen_err, new);
+		      else if (inet_pton(AF_INET, comma, &new->start))
+			{
+			  int mask;
+
+			  if (msize > 32)
+			     ret_err_free(_("bad prefix length"), new);
+			  
+			  mask = (1 << (32 - msize)) - 1;
+			  new->is6 = 0; 			  
+			  new->start.s_addr = ntohl(htonl(new->start.s_addr) & ~mask);
+			  new->end.s_addr = new->start.s_addr | htonl(mask);
+			  if (arg)
+			    {
+			      if (option != 's')
+				{
+				  if (!(new->prefix = canonicalise_opt(arg)) ||
+				      strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
+				    ret_err_free(_("bad prefix"), new);
+				}
+			      else if (strcmp(arg, "local") != 0 ||
+				       (msize != 8 && msize != 16 && msize != 24))
+				ret_err_free(gen_err, new);
+			      else
+				{
+				  char domain[29]; /* strlen("xxx.yyy.zzz.ttt.in-addr.arpa")+1 */
+				  /* local=/xxx.yyy.zzz.in-addr.arpa/ */
+				  /* domain_rev4 can't fail here, msize checked above. */
+				  domain_rev4(domain, new->start, msize);
+				  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
+				  
+				  /* local=/<domain>/ */
+				  /* d_raw can't failed to canonicalise here, checked above. */
+				  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
+				}
+			    }
+			}
+		      else if (inet_pton(AF_INET6, comma, &new->start6))
+			{
+			  u64 mask, addrpart = addr6part(&new->start6);
+
+			  if (msize > 128)
+			    ret_err_free(_("bad prefix length"), new);
+
+			  mask = (1LLU << (128 - msize)) - 1LLU;
+
+			  new->is6 = 1;
+			  new->prefixlen = msize;
+			  
+			  /* prefix==64 overflows the mask calculation above */
+			  if (msize <= 64)
+			    mask = (u64)-1LL;
+			  
+			  new->end6 = new->start6;
+			  setaddr6part(&new->start6, addrpart & ~mask);
+			  setaddr6part(&new->end6, addrpart | mask);
+			  
+			  if (arg)
+			    {
+			      if (option != 's')
+				{
+				  if (!(new->prefix = canonicalise_opt(arg)) ||
+				      strlen(new->prefix) > MAXLABEL - INET6_ADDRSTRLEN)
+				    ret_err_free(_("bad prefix"), new);
+				}	
+			      else if (strcmp(arg, "local") != 0 || ((msize & 4) != 0))
+				ret_err_free(gen_err, new);
+			      else 
+				{
+				  char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
+				  /* generate the equivalent of
+				     local=/xxx.yyy.zzz.ip6.arpa/ */
+				  domain_rev6(domain, &new->start6, msize);
+				  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, domain, NULL);
+
+				  /* local=/<domain>/ */
+				  /* d_raw can't failed to canonicalise here, checked above. */
+				  add_update_server(SERV_LITERAL_ADDRESS, NULL, NULL, NULL, d_raw, NULL);
+				}
+			    }
+			}
+		      else
+			ret_err_free(gen_err, new);
+		    }
+		  else
+		    {
+		      char *prefstr;
+		      arg = split(comma);
+		      prefstr = split(arg);
+
+		      if (inet_pton(AF_INET, comma, &new->start))
+			{
+			  new->is6 = 0;
+			  if (!arg)
+			    new->end.s_addr = new->start.s_addr;
+			  else if (!inet_pton(AF_INET, arg, &new->end))
+			    ret_err_free(gen_err, new);
+			}
+		      else if (inet_pton(AF_INET6, comma, &new->start6))
+			{
+			  new->is6 = 1;
+			  if (!arg)
+			    memcpy(&new->end6, &new->start6, IN6ADDRSZ);
+			  else if (!inet_pton(AF_INET6, arg, &new->end6))
+			    ret_err_free(gen_err, new);
+			}
+		      else 
+			ret_err_free(gen_err, new);
+
+		      if (option != 's' && prefstr)
+			{
+			  if (!(new->prefix = canonicalise_opt(prefstr)) ||
+			      strlen(new->prefix) > MAXLABEL - INET_ADDRSTRLEN)
+			    ret_err_free(_("bad prefix"), new);
+			}
+		    }
+
+		  new->domain = canonicalise_opt(d_raw);
+		  if (option  == 's')
+		    {
+		      new->next = daemon->cond_domain;
+		      daemon->cond_domain = new;
+		    }
+		  else
+		    {
+		      char *star;
+		      if (new->prefix &&
+			  (star = strrchr(new->prefix, '*'))
+			  && *(star+1) == 0)
+			{
+			  *star = 0;
+			  new->indexed = 1;
+			  if (new->is6 && new->prefixlen < 64)
+			    ret_err_free(_("prefix length too small"), new);
+			}
+		      new->next = daemon->synth_domains;
+		      daemon->synth_domains = new;
+		    }
+		}
+	      else if (option == 's')
+		daemon->domain_suffix = canonicalise_opt(d_raw);
+	      else 
+		ret_err(gen_err);
+	    }
+	}
+      break;
+      
+    case LOPT_CPE_ID: /* --add-dns-client */
+      if (arg)
+	daemon->dns_client_id = opt_string_alloc(arg);
+      break;
+
+    case LOPT_UMBRELLA: /* --umbrella */
+      set_option_bool(OPT_UMBRELLA);
+      while (arg) {
+        comma = split(arg);
+        if (strstr(arg, "deviceid:")) {
+          arg += 9;
+          if (strlen(arg) != 16)
+              ret_err(gen_err);
+		  char *p;	  
+          for (p = arg; *p; p++) {
+            if (!isxdigit((int)*p))
+              ret_err(gen_err);
+          }
+          set_option_bool(OPT_UMBRELLA_DEVID);
+
+          u8 *u = daemon->umbrella_device;
+          char word[3];
+		  u8 i;
+          for (i = 0; i < sizeof(daemon->umbrella_device); i++, arg+=2) {
+            memcpy(word, &(arg[0]), 2);
+            *u++ = strtoul(word, NULL, 16);
+          }
+        }
+        else if (strstr(arg, "orgid:")) {
+          if (!strtoul_check(arg+6, &daemon->umbrella_org)) {
+            ret_err(gen_err);
+          }
+        }
+        else if (strstr(arg, "assetid:")) {
+          if (!strtoul_check(arg+8, &daemon->umbrella_asset)) {
+            ret_err(gen_err);
+          }
+        }
+        arg = comma;
+      }
+      break;
+
+    case LOPT_ADD_MAC: /* --add-mac */
+      if (!arg)
+	set_option_bool(OPT_ADD_MAC);
+      else
+	{
+	  unhide_metas(arg);
+	  if (strcmp(arg, "base64") == 0)
+	    set_option_bool(OPT_MAC_B64);
+	  else if (strcmp(arg, "text") == 0)
+	    set_option_bool(OPT_MAC_HEX);
+	  else
+	    ret_err(gen_err);
+	}
+      break;
+
+    case 'u':  /* --user */
+      daemon->username = opt_string_alloc(arg);
+      break;
+      
+    case 'g':  /* --group */
+      daemon->groupname = opt_string_alloc(arg);
+      daemon->group_set = 1;
+      break;
+
+#ifdef HAVE_DHCP
+    case LOPT_SCRIPTUSR: /* --scriptuser */
+      daemon->scriptuser = opt_string_alloc(arg);
+      break;
+#endif
+      
+    case 'i':  /* --interface */
+      do {
+	struct iname *new = opt_malloc(sizeof(struct iname));
+	comma = split(arg);
+	new->next = daemon->if_names;
+	daemon->if_names = new;
+	/* new->name may be NULL if someone does
+	   "interface=" to disable all interfaces except loop. */
+	new->name = opt_string_alloc(arg);
+	new->used = 0;
+	arg = comma;
+      } while (arg);
+      break;
+      
+    case LOPT_TFTP: /* --enable-tftp */
+      set_option_bool(OPT_TFTP);
+      if (!arg)
+	break;
+      /* fall through */
+
+    case 'I':  /* --except-interface */
+    case '2':  /* --no-dhcp-interface */
+      do {
+	struct iname *new = opt_malloc(sizeof(struct iname));
+	comma = split(arg);
+	new->name = opt_string_alloc(arg);
+	if (option == 'I')
+	  {
+	    new->next = daemon->if_except;
+	    daemon->if_except = new;
+	  }
+	else if (option == LOPT_TFTP)
+	   {
+	    new->next = daemon->tftp_interfaces;
+	    daemon->tftp_interfaces = new;
+	  }
+	else
+	  {
+	    new->next = daemon->dhcp_except;
+	    daemon->dhcp_except = new;
+	  }
+	arg = comma;
+      } while (arg);
+      break;
+      
+    case 'B':  /* --bogus-nxdomain */
+    case LOPT_IGNORE_ADDR: /* --ignore-address */
+     {
+	union all_addr addr;
+	int prefix, is6 = 0;
+	struct bogus_addr *baddr;
+	
+	unhide_metas(arg);
+
+	if (!arg ||
+	    ((comma = split_chr(arg, '/')) && !atoi_check(comma, &prefix)))
+	  ret_err(gen_err);
+
+	if (inet_pton(AF_INET6, arg, &addr.addr6) == 1)
+	  is6 = 1;
+	else if (inet_pton(AF_INET, arg, &addr.addr4) != 1)
+	  ret_err(gen_err);
+
+	if (!comma)
+	  {
+	    if (is6)
+	      prefix = 128;
+	    else
+	      prefix = 32;
+	  }
+
+	if (prefix > 128 || (!is6 && prefix > 32))
+	  ret_err(gen_err);
+	
+	baddr = opt_malloc(sizeof(struct bogus_addr));
+	if (option == 'B')
+	  {
+	    baddr->next = daemon->bogus_addr;
+	    daemon->bogus_addr = baddr;
+	  }
+	else
+	  {
+	    baddr->next = daemon->ignore_addr;
+	    daemon->ignore_addr = baddr;
+	  }
+
+	baddr->prefix = prefix;
+	baddr->is6 = is6;
+	baddr->addr = addr;
+	break;
+     }
+      
+    case 'a':  /* --listen-address */
+    case LOPT_AUTHPEER: /* --auth-peer */
+      do {
+	struct iname *new = opt_malloc(sizeof(struct iname));
+	comma = split(arg);
+	unhide_metas(arg);
+	if (arg && (inet_pton(AF_INET, arg, &new->addr.in.sin_addr) > 0))
+	  {
+	    new->addr.sa.sa_family = AF_INET;
+	    new->addr.in.sin_port = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+	    new->addr.in.sin_len = sizeof(new->addr.in);
+#endif
+	  }
+	else if (arg && inet_pton(AF_INET6, arg, &new->addr.in6.sin6_addr) > 0)
+	  {
+	    new->addr.sa.sa_family = AF_INET6;
+	    new->addr.in6.sin6_flowinfo = 0;
+	    new->addr.in6.sin6_scope_id = 0;
+	    new->addr.in6.sin6_port = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+	    new->addr.in6.sin6_len = sizeof(new->addr.in6);
+#endif
+	  }
+	else
+	  ret_err_free(gen_err, new);
+
+	new->used = 0;
+	if (option == 'a')
+	  {
+	    new->next = daemon->if_addrs;
+	    daemon->if_addrs = new;
+	  }
+	else
+	  {
+	    new->next = daemon->auth_peers;
+	    daemon->auth_peers = new;
+	  } 
+	arg = comma;
+      } while (arg);
+      break;
+      
+    case LOPT_NO_REBIND: /*  --rebind-domain-ok */
+      {
+	struct server *new;
+
+	unhide_metas(arg);
+
+	if (*arg == '/')
+	  arg++;
+	
+	do {
+	  comma = split_chr(arg, '/');
+	  new = opt_malloc(sizeof(struct serv_local));
+	  new->domain = opt_string_alloc(arg);
+	  new->domain_len = strlen(arg);
+	  new->next = daemon->no_rebind;
+	  daemon->no_rebind = new;
+	  arg = comma;
+	} while (arg && *arg);
+
+	break;
+      }
+      
+    case 'S':            /*  --server */
+    case LOPT_LOCAL:     /*  --local */
+    case 'A':            /*  --address */
+      {
+	char *lastdomain = NULL, *domain = "";
+	u16 flags = 0;
+	char *err;
+	union all_addr addr;
+	union mysockaddr serv_addr, source_addr;
+	char interface[IF_NAMESIZE+1];
+
+	unhide_metas(arg);
+	
+	/* split the domain args, if any and skip to the end of them. */
+	if (arg && *arg == '/')
+	  {
+	    char *last;
+
+	    domain = lastdomain = ++arg;
+	    
+	    while ((last = split_chr(arg, '/')))
+	      {
+		lastdomain = arg;
+		arg = last;
+	      }
+	  }
+	
+	if (!arg || !*arg)
+	  flags = SERV_LITERAL_ADDRESS;
+	else if (option == 'A')
+	  {
+	    /* # as literal address means return zero address for 4 and 6 */
+	    if (strcmp(arg, "#") == 0)
+	      flags = SERV_ALL_ZEROS | SERV_LITERAL_ADDRESS;
+	    else if (inet_pton(AF_INET, arg, &addr.addr4) > 0)
+	      flags = SERV_4ADDR | SERV_LITERAL_ADDRESS;
+	    else if (inet_pton(AF_INET6, arg, &addr.addr6) > 0)
+	      flags = SERV_6ADDR | SERV_LITERAL_ADDRESS;
+	    else
+	      ret_err(_("Bad address in --address"));
+	  }
+	else
+	  {
+	    if ((err = parse_server(arg, &serv_addr, &source_addr, interface, &flags)))
+	      ret_err(err);
+	  }
+
+	if (servers_only && option == 'S')
+	  flags |= SERV_FROM_FILE;
+	
+	while (1)
+	  {
+	    /* server=//1.2.3.4 is special. */
+	    if (strlen(domain) == 0 && lastdomain)
+	      flags |= SERV_FOR_NODOTS;
+	    else
+	      flags &= ~SERV_FOR_NODOTS;
+
+	    if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, &addr))
+	      ret_err(gen_err);
+	    
+	    if (!lastdomain || domain == lastdomain)
+	      break;
+	    
+	    domain += strlen(domain) + 1;
+	  }
+	
+     	break;
+      }
+
+    case LOPT_REV_SERV: /* --rev-server */
+      {
+	char *string;
+	int size;
+	u16 flags = 0;
+	char domain[73]; /* strlen("32*<n.>ip6.arpa")+1 */
+	struct in_addr addr4;
+	struct in6_addr addr6;
+ 	union mysockaddr serv_addr, source_addr;
+	char interface[IF_NAMESIZE+1];
+	
+	unhide_metas(arg);
+	if (!arg)
+	  ret_err(gen_err);
+	
+	comma=split(arg);
+
+	if (!(string = split_chr(arg, '/')) || !atoi_check(string, &size))
+	  ret_err(gen_err);
+	
+	if (inet_pton(AF_INET, arg, &addr4))
+	  {
+	    if (!domain_rev4(domain, addr4, size))
+	      ret_err(_("bad IPv4 prefix"));
+	  }
+	else if (inet_pton(AF_INET6, arg, &addr6))
+	  {
+	    if (!domain_rev6(domain, &addr6, size))
+	      ret_err(_("bad IPv6 prefix"));
+	  }
+	else
+	  ret_err(gen_err);
+	
+	if (!comma)
+	  flags |= SERV_LITERAL_ADDRESS;
+	else if ((string = parse_server(comma, &serv_addr, &source_addr, interface, &flags)))
+	  ret_err(string);
+	
+	if (servers_only)
+	  flags |= SERV_FROM_FILE;
+
+	if (!add_update_server(flags, &serv_addr, &source_addr, interface, domain, NULL))
+	  ret_err(gen_err);
+	
+	break;
+      }
+
+    case LOPT_IPSET: /* --ipset */
+#ifndef HAVE_IPSET
+      ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
+      break;
+#else
+      {
+	 struct ipsets ipsets_head;
+	 struct ipsets *ipsets = &ipsets_head;
+	 int size;
+	 char *end;
+	 char **sets, **sets_pos;
+	 memset(ipsets, 0, sizeof(struct ipsets));
+	 unhide_metas(arg);
+	 if (arg && *arg == '/') 
+	   {
+	     arg++;
+	     while ((end = split_chr(arg, '/'))) 
+	       {
+		 char *domain = NULL;
+		 /* elide leading dots - they are implied in the search algorithm */
+		 while (*arg == '.')
+		   arg++;
+		 /* # matches everything and becomes a zero length domain string */
+		 if (strcmp(arg, "#") == 0 || !*arg)
+		   domain = "";
+		 else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg)))
+		   ret_err(gen_err);
+		 ipsets->next = opt_malloc(sizeof(struct ipsets));
+		 ipsets = ipsets->next;
+		 memset(ipsets, 0, sizeof(struct ipsets));
+		 ipsets->domain = domain;
+		 arg = end;
+	       }
+	   } 
+	 else 
+	   {
+	     ipsets->next = opt_malloc(sizeof(struct ipsets));
+	     ipsets = ipsets->next;
+	     memset(ipsets, 0, sizeof(struct ipsets));
+	     ipsets->domain = "";
+	   }
+	 
+	 if (!arg || !*arg)
+	   ret_err(gen_err);
+	 
+	 for (size = 2, end = arg; *end; ++end) 
+	   if (*end == ',')
+	       ++size;
+     
+	 sets = sets_pos = opt_malloc(sizeof(char *) * size);
+	 
+	 do {
+	   end = split(arg);
+	   *sets_pos++ = opt_string_alloc(arg);
+	   arg = end;
+	 } while (end);
+	 *sets_pos = 0;
+	 for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next)
+	   ipsets->next->sets = sets;
+	 ipsets->next = daemon->ipsets;
+	 daemon->ipsets = ipsets_head.next;
+	 
+	 break;
+      }
+#endif
+      
+    case LOPT_CMARK_ALST_EN: /* --connmark-allowlist-enable */
+#ifndef HAVE_CONNTRACK
+      ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
+      break;
+#else
+      {
+	u32 mask = UINT32_MAX;
+	
+	if (arg)
+	  if (!strtoul_check(arg, &mask) || mask < 1)
+	    ret_err(gen_err);
+	
+	set_option_bool(OPT_CMARK_ALST_EN);
+	daemon->allowlist_mask = mask;
+	break;
+      }
+#endif
+      
+    case LOPT_CMARK_ALST: /* --connmark-allowlist */
+#ifndef HAVE_CONNTRACK
+	ret_err(_("recompile with HAVE_CONNTRACK defined to enable connmark-allowlist directives"));
+	break;
+#else
+      {
+	struct allowlist *allowlists;
+	char **patterns, **patterns_pos;
+	u32 mark, mask = UINT32_MAX;
+	size_t num_patterns = 0;
+	
+	char *c, *m = NULL;
+	char *separator;
+	unhide_metas(arg);
+	if (!arg)
+	  ret_err(gen_err);
+	c = arg;
+	if (*c < '0' || *c > '9')
+	  ret_err(gen_err);
+	while (*c && *c != ',')
+	  {
+	    if (*c == '/')
+	      {
+		if (m)
+		  ret_err(gen_err);
+	        *c = '\0';
+		m = ++c;
+	      }
+	    if (*c < '0' || *c > '9')
+	      ret_err(gen_err);
+	    c++;
+	  }
+	separator = c;
+	if (!*separator)
+	  break;
+	while (c && *c)
+	  {
+	    char *end = strchr(++c, '/');
+	    if (end)
+	      *end = '\0';
+	    if (strcmp(c, "*") && !is_valid_dns_name_pattern(c))
+	      ret_err(gen_err);
+	    if (end)
+	      *end = '/';
+	    if (num_patterns >= UINT16_MAX - 1)
+	      ret_err(gen_err);
+	    num_patterns++;
+	    c = end;
+	  }
+	
+	*separator = '\0';
+	if (!strtoul_check(arg, &mark) || mark < 1 || mark > UINT32_MAX)
+	  ret_err(gen_err);
+	if (m)
+	  if (!strtoul_check(m, &mask) || mask < 1 || mask > UINT32_MAX || (mark & ~mask))
+	    ret_err(gen_err);
+	if (num_patterns)
+	  *separator = ',';
+	for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+	  if (allowlists->mark == mark && allowlists->mask == mask)
+	    ret_err(gen_err);
+	
+	patterns = opt_malloc((num_patterns + 1) * sizeof(char *));
+	if (!patterns)
+	  goto fail_cmark_allowlist;
+	patterns_pos = patterns;
+	c = separator;
+	while (c && *c)
+	{
+	  char *end = strchr(++c, '/');
+	  if (end)
+	    *end = '\0';
+	  if (!(*patterns_pos++ = opt_string_alloc(c)))
+	    goto fail_cmark_allowlist;
+	  if (end)
+	    *end = '/';
+	  c = end;
+	}
+	*patterns_pos++ = NULL;
+	
+	allowlists = opt_malloc(sizeof(struct allowlist));
+	if (!allowlists)
+	  goto fail_cmark_allowlist;
+	memset(allowlists, 0, sizeof(struct allowlist));
+	allowlists->mark = mark;
+	allowlists->mask = mask;
+	allowlists->patterns = patterns;
+	allowlists->next = daemon->allowlists;
+	daemon->allowlists = allowlists;
+	break;
+	
+      fail_cmark_allowlist:
+	if (patterns)
+	  {
+	    for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
+	      {
+		free(*patterns_pos);
+		*patterns_pos = NULL;
+	      }
+	    free(patterns);
+	    patterns = NULL;
+	  }
+	if (allowlists)
+	  {
+	    free(allowlists);
+	    allowlists = NULL;
+	  }
+	ret_err(gen_err);
+      }
+#endif
+      
+    case 'c':  /* --cache-size */
+      {
+	int size;
+	
+	if (!atoi_check(arg, &size))
+	  ret_err(gen_err);
+	else
+	  {
+	    /* zero is OK, and means no caching. */
+	    
+	    if (size < 0)
+	      size = 0;
+
+	    /* Note that for very large cache sizes, the malloc()
+	       will overflow. For the size of the cache record
+	       at the time this was noted, the value of "very large"
+               was 46684428. Limit to an order of magnitude less than
+	       that to be safe from changes to the cache record. */
+	    if (size > 5000000)
+	      size = 5000000;
+	    
+	    daemon->cachesize = size;
+	  }
+	break;
+      }
+      
+    case 'p':  /* --port */
+      if (!atoi_check16(arg, &daemon->port))
+	ret_err(gen_err);
+      break;
+    
+    case LOPT_MINPORT:  /* --min-port */
+      if (!atoi_check16(arg, &daemon->min_port))
+	ret_err(gen_err);
+      break;
+
+    case LOPT_MAXPORT:  /* --max-port */
+      if (!atoi_check16(arg, &daemon->max_port))
+	ret_err(gen_err);
+      break;
+
+    case '0':  /* --dns-forward-max */
+      if (!atoi_check(arg, &daemon->ftabsize))
+	ret_err(gen_err);
+      break;  
+    
+    case 'q': /* --log-queries */
+      set_option_bool(OPT_LOG);
+      if (arg && strcmp(arg, "extra") == 0)
+	set_option_bool(OPT_EXTRALOG);
+      break;
+
+    case LOPT_MAX_LOGS:  /* --log-async */
+      daemon->max_logs = LOG_MAX; /* default */
+      if (arg && !atoi_check(arg, &daemon->max_logs))
+	ret_err(gen_err);
+      else if (daemon->max_logs > 100)
+	daemon->max_logs = 100;
+      break;  
+
+    case 'P': /* --edns-packet-max */
+      {
+	int i;
+	if (!atoi_check(arg, &i))
+	  ret_err(gen_err);
+	daemon->edns_pktsz = (unsigned short)i;	
+	break;
+      }
+      
+    case 'Q':  /* --query-port */
+      if (!atoi_check16(arg, &daemon->query_port))
+	ret_err(gen_err);
+      /* if explicitly set to zero, use single OS ephemeral port
+	 and disable random ports */
+      if (daemon->query_port == 0)
+	daemon->osport = 1;
+      break;
+      
+    case 'T':         /* --local-ttl */
+    case LOPT_NEGTTL: /* --neg-ttl */
+    case LOPT_MAXTTL: /* --max-ttl */
+    case LOPT_MINCTTL: /* --min-cache-ttl */
+    case LOPT_MAXCTTL: /* --max-cache-ttl */
+    case LOPT_AUTHTTL: /* --auth-ttl */
+    case LOPT_DHCPTTL: /* --dhcp-ttl */
+      {
+	int ttl;
+	if (!atoi_check(arg, &ttl))
+	  ret_err(gen_err);
+	else if (option == LOPT_NEGTTL)
+	  daemon->neg_ttl = (unsigned long)ttl;
+	else if (option == LOPT_MAXTTL)
+	  daemon->max_ttl = (unsigned long)ttl;
+	else if (option == LOPT_MINCTTL)
+	  {
+	    if (ttl > TTL_FLOOR_LIMIT)
+	      ttl = TTL_FLOOR_LIMIT;
+	    daemon->min_cache_ttl = (unsigned long)ttl;
+	  }
+	else if (option == LOPT_MAXCTTL)
+	  daemon->max_cache_ttl = (unsigned long)ttl;
+	else if (option == LOPT_AUTHTTL)
+	  daemon->auth_ttl = (unsigned long)ttl;
+	else if (option == LOPT_DHCPTTL)
+	  {
+	    daemon->dhcp_ttl = (unsigned long)ttl;
+	    daemon->use_dhcp_ttl = 1;
+	  }
+	else
+	  daemon->local_ttl = (unsigned long)ttl;
+	break;
+      }
+      
+#ifdef HAVE_DHCP
+    case 'X': /* --dhcp-lease-max */
+      if (!atoi_check(arg, &daemon->dhcp_max))
+	ret_err(gen_err);
+      break;
+#endif
+      
+#ifdef HAVE_TFTP
+    case LOPT_TFTP_MAX:  /*  --tftp-max */
+      if (!atoi_check(arg, &daemon->tftp_max))
+	ret_err(gen_err);
+      break;  
+
+    case LOPT_TFTP_MTU:  /*  --tftp-mtu */
+      if (!atoi_check(arg, &daemon->tftp_mtu))
+	ret_err(gen_err);
+      break;
+
+    case LOPT_PREFIX: /* --tftp-prefix */
+      comma = split(arg);
+      if (comma)
+	{
+	  struct tftp_prefix *new = opt_malloc(sizeof(struct tftp_prefix));
+	  new->interface = opt_string_alloc(comma);
+	  new->prefix = opt_string_alloc(arg);
+	  new->next = daemon->if_prefix;
+	  daemon->if_prefix = new;
+	}
+      else
+	daemon->tftp_prefix = opt_string_alloc(arg);
+      break;
+
+    case LOPT_TFTPPORTS: /* --tftp-port-range */
+      if (!(comma = split(arg)) || 
+	  !atoi_check16(arg, &daemon->start_tftp_port) ||
+	  !atoi_check16(comma, &daemon->end_tftp_port))
+	ret_err(_("bad port range"));
+      
+      if (daemon->start_tftp_port > daemon->end_tftp_port)
+	{
+	  int tmp = daemon->start_tftp_port;
+	  daemon->start_tftp_port = daemon->end_tftp_port;
+	  daemon->end_tftp_port = tmp;
+	} 
+      
+      break;
+
+    case LOPT_APREF: /* --tftp-unique-root */
+      if (!arg || strcasecmp(arg, "ip") == 0)
+        set_option_bool(OPT_TFTP_APREF_IP);
+      else if (strcasecmp(arg, "mac") == 0)
+        set_option_bool(OPT_TFTP_APREF_MAC);
+      else
+        ret_err(gen_err);
+      break;
+#endif
+	      
+    case LOPT_BRIDGE:   /* --bridge-interface */
+      {
+	struct dhcp_bridge *new;
+
+	if (!(comma = split(arg)) || strlen(arg) > IF_NAMESIZE - 1 )
+	  ret_err(_("bad bridge-interface"));
+
+	for (new = daemon->bridges; new; new = new->next)
+	  if (strcmp(new->iface, arg) == 0)
+	    break;
+
+	if (!new)
+	  {
+	     new = opt_malloc(sizeof(struct dhcp_bridge));
+	     strcpy(new->iface, arg);
+	     new->alias = NULL;
+	     new->next = daemon->bridges;
+	     daemon->bridges = new;
+	  }
+	
+	do {
+	  arg = comma;
+	  comma = split(arg);
+	  if (strlen(arg) != 0 && strlen(arg) <= IF_NAMESIZE - 1)
+	    {
+	      struct dhcp_bridge *b = opt_malloc(sizeof(struct dhcp_bridge)); 
+	      b->next = new->alias;
+	      new->alias = b;
+	      strcpy(b->iface, arg);
+	    }
+	} while (comma);
+	
+	break;
+      }
+
+#ifdef HAVE_DHCP
+    case LOPT_SHARED_NET: /* --shared-network */
+      {
+	struct shared_network *new = opt_malloc(sizeof(struct shared_network));
+
+#ifdef HAVE_DHCP6
+	new->shared_addr.s_addr = 0;
+#endif
+	new->if_index = 0;
+	
+	if (!(comma = split(arg)))
+	  {
+	  snerr:
+	    free(new);
+	    ret_err(_("bad shared-network"));
+	  }
+	
+	if (inet_pton(AF_INET, comma, &new->shared_addr))
+	  {
+	    if (!inet_pton(AF_INET, arg, &new->match_addr) &&
+		!(new->if_index = if_nametoindex(arg)))
+	      goto snerr;
+	  }
+#ifdef HAVE_DHCP6
+	else if (inet_pton(AF_INET6, comma, &new->shared_addr6))
+	  {
+	    if (!inet_pton(AF_INET6, arg, &new->match_addr6) &&
+		!(new->if_index = if_nametoindex(arg)))
+	      goto snerr;
+	  }
+#endif
+	else
+	  goto snerr;
+
+	new->next = daemon->shared_networks;
+	daemon->shared_networks = new;
+	break;
+      }
+	  
+    case 'F':  /* --dhcp-range */
+      {
+	int k, leasepos = 2;
+	char *cp, *a[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+	struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context));
+	
+	memset (new, 0, sizeof(*new));
+	
+	while(1)
+	  {
+	    for (cp = arg; *cp; cp++)
+	      if (!(*cp == ' ' || *cp == '.' || *cp == ':' || 
+		    (*cp >= 'a' && *cp <= 'f') || (*cp >= 'A' && *cp <= 'F') ||
+		    (*cp >='0' && *cp <= '9')))
+		break;
+	    
+	    if (*cp != ',' && (comma = split(arg)))
+	      {
+		if (is_tag_prefix(arg))
+		  {
+		    /* ignore empty tag */
+		    if (arg[4])
+		      new->filter = dhcp_netid_create(arg+4, new->filter);
+		  }
+		else
+		  {
+		    if (new->netid.net)
+		      {
+			dhcp_context_free(new);
+			ret_err(_("only one tag allowed"));
+		      }
+		    else
+		      new->netid.net = opt_string_alloc(set_prefix(arg));
+		  }
+		arg = comma;
+	      }
+	    else
+	      {
+		a[0] = arg;
+		break;
+	      }
+	  }
+	
+	for (k = 1; k < 8; k++)
+	  if (!(a[k] = split(a[k-1])))
+	    break;
+	
+	if (k < 2)
+	  {
+	    dhcp_context_free(new);
+	    ret_err(_("bad dhcp-range"));
+	  }
+	
+	if (inet_pton(AF_INET, a[0], &new->start))
+	  {
+	    new->next = daemon->dhcp;
+	    new->lease_time = DEFLEASE;
+	    daemon->dhcp = new;
+	    new->end = new->start;
+	    if (strcmp(a[1], "static") == 0)
+	      new->flags |= CONTEXT_STATIC;
+	    else if (strcmp(a[1], "proxy") == 0)
+	      new->flags |= CONTEXT_PROXY;
+	    else if (!inet_pton(AF_INET, a[1], &new->end))
+	      {
+		dhcp_context_free(new);
+		ret_err(_("bad dhcp-range"));
+	      }
+	    
+	    if (ntohl(new->start.s_addr) > ntohl(new->end.s_addr))
+	      {
+		struct in_addr tmp = new->start;
+		new->start = new->end;
+		new->end = tmp;
+	      }
+	    
+	    if (k >= 3 && strchr(a[2], '.') &&  
+		(inet_pton(AF_INET, a[2], &new->netmask) > 0))
+	      {
+		new->flags |= CONTEXT_NETMASK;
+		leasepos = 3;
+		if (!is_same_net(new->start, new->end, new->netmask))
+		  {
+		    dhcp_context_free(new);
+		    ret_err(_("inconsistent DHCP range"));
+		  }
+		
+	    
+		if (k >= 4 && strchr(a[3], '.') &&  
+		    (inet_pton(AF_INET, a[3], &new->broadcast) > 0))
+		  {
+		    new->flags |= CONTEXT_BRDCAST;
+		    leasepos = 4;
+		  }
+	      }
+	  }
+#ifdef HAVE_DHCP6
+	else if (inet_pton(AF_INET6, a[0], &new->start6))
+	  {
+	    const char *err = NULL;
+
+	    new->flags |= CONTEXT_V6; 
+	    new->prefix = 64; /* default */
+	    new->end6 = new->start6;
+	    new->lease_time = DEFLEASE6;
+	    new->next = daemon->dhcp6;
+	    daemon->dhcp6 = new;
+
+	    for (leasepos = 1; leasepos < k; leasepos++)
+	      {
+		if (strcmp(a[leasepos], "static") == 0)
+		  new->flags |= CONTEXT_STATIC | CONTEXT_DHCP;
+		else if (strcmp(a[leasepos], "ra-only") == 0 || strcmp(a[leasepos], "slaac") == 0 )
+		  new->flags |= CONTEXT_RA;
+		else if (strcmp(a[leasepos], "ra-names") == 0)
+		  new->flags |= CONTEXT_RA_NAME | CONTEXT_RA;
+		else if (strcmp(a[leasepos], "ra-advrouter") == 0)
+		  new->flags |= CONTEXT_RA_ROUTER | CONTEXT_RA;
+		else if (strcmp(a[leasepos], "ra-stateless") == 0)
+		  new->flags |= CONTEXT_RA_STATELESS | CONTEXT_DHCP | CONTEXT_RA;
+		else if (strcmp(a[leasepos], "off-link") == 0)
+		  new->flags |= CONTEXT_RA_OFF_LINK;
+		else if (leasepos == 1 && inet_pton(AF_INET6, a[leasepos], &new->end6))
+		  new->flags |= CONTEXT_DHCP; 
+		else if (strstr(a[leasepos], "constructor:") == a[leasepos])
+		  {
+		    new->template_interface = opt_string_alloc(a[leasepos] + 12);
+		    new->flags |= CONTEXT_TEMPLATE;
+		  }
+		else  
+		  break;
+	      }
+	   	    	     
+	    /* bare integer < 128 is prefix value */
+	    if (leasepos < k)
+	      {
+		int pref;
+		for (cp = a[leasepos]; *cp; cp++)
+		  if (!(*cp >= '0' && *cp <= '9'))
+		    break;
+		if (!*cp && (pref = atoi(a[leasepos])) <= 128)
+		  {
+		    new->prefix = pref;
+		    leasepos++;
+		  }
+	      }
+	    
+	    if (new->prefix > 64)
+	      {
+		if (new->flags & CONTEXT_RA)
+		  err=(_("prefix length must be exactly 64 for RA subnets"));
+		else if (new->flags & CONTEXT_TEMPLATE)
+		  err=(_("prefix length must be exactly 64 for subnet constructors"));
+	      }
+	    else if (new->prefix < 64)
+	      err=(_("prefix length must be at least 64"));
+	    
+	    if (!err && !is_same_net6(&new->start6, &new->end6, new->prefix))
+	      err=(_("inconsistent DHCPv6 range"));
+
+	    if (err)
+	      {
+		dhcp_context_free(new);
+		ret_err(err);
+	      }
+
+	    /* dhcp-range=:: enables DHCP stateless on any interface */
+	    if (IN6_IS_ADDR_UNSPECIFIED(&new->start6) && !(new->flags & CONTEXT_TEMPLATE))
+	      new->prefix = 0;
+	    
+	    if (new->flags & CONTEXT_TEMPLATE)
+	      {
+		struct in6_addr zero;
+		memset(&zero, 0, sizeof(zero));
+		if (!is_same_net6(&zero, &new->start6, new->prefix))
+		  {
+		    dhcp_context_free(new);
+		    ret_err(_("prefix must be zero with \"constructor:\" argument"));
+		  }
+	      }
+	    
+	    if (addr6part(&new->start6) > addr6part(&new->end6))
+	      {
+		struct in6_addr tmp = new->start6;
+		new->start6 = new->end6;
+		new->end6 = tmp;
+	      }
+	  }
+#endif
+	else
+	  {
+	    dhcp_context_free(new);
+	    ret_err(_("bad dhcp-range"));
+	  }
+	
+	if (leasepos < k)
+	  {
+	    if (leasepos != k-1)
+	      {
+		dhcp_context_free(new);
+		ret_err(_("bad dhcp-range"));
+	      }
+	    
+	    if (strcmp(a[leasepos], "infinite") == 0)
+	      {
+		new->lease_time = 0xffffffff;
+		new->flags |= CONTEXT_SETLEASE;
+	      }
+	    else if (strcmp(a[leasepos], "deprecated") == 0)
+	      new->flags |= CONTEXT_DEPRECATE;
+	    else
+	      {
+		int fac = 1;
+		if (strlen(a[leasepos]) > 0)
+		  {
+		    switch (a[leasepos][strlen(a[leasepos]) - 1])
+		      {
+		      case 'w':
+		      case 'W':
+			fac *= 7;
+			/* fall through */
+		      case 'd':
+		      case 'D':
+			fac *= 24;
+			/* fall through */
+		      case 'h':
+		      case 'H':
+			fac *= 60;
+			/* fall through */
+		      case 'm':
+		      case 'M':
+			fac *= 60;
+			/* fall through */
+		      case 's':
+		      case 'S':
+			a[leasepos][strlen(a[leasepos]) - 1] = 0;
+		      }
+		    
+		    for (cp = a[leasepos]; *cp; cp++)
+		      if (!(*cp >= '0' && *cp <= '9'))
+			break;
+
+		    if (*cp || (leasepos+1 < k))
+		      ret_err_free(_("bad dhcp-range"), new);
+		    
+		    new->lease_time = atoi(a[leasepos]) * fac;
+		    new->flags |= CONTEXT_SETLEASE;
+		    /* Leases of a minute or less confuse
+		       some clients, notably Apple's */
+		    if (new->lease_time < 120)
+		      new->lease_time = 120;
+		  }
+	      }
+	  }
+
+	break;
+      }
+
+    case LOPT_BANK:
+    case 'G':  /* --dhcp-host */
+      {
+	struct dhcp_config *new;
+	struct in_addr in;
+	
+	new = opt_malloc(sizeof(struct dhcp_config));
+	
+	new->next = daemon->dhcp_conf;
+	new->flags = (option == LOPT_BANK) ? CONFIG_BANK : 0;
+	new->hwaddr = NULL;
+	new->netid = NULL;
+	new->filter = NULL;
+	new->clid = NULL;
+#ifdef HAVE_DHCP6
+	new->addr6 = NULL;
+#endif
+
+	while (arg)
+	  {
+	    comma = split(arg);
+	    if (strchr(arg, ':')) /* ethernet address, netid or binary CLID */
+	      {
+		if ((arg[0] == 'i' || arg[0] == 'I') &&
+		    (arg[1] == 'd' || arg[1] == 'D') &&
+		    arg[2] == ':')
+		  {
+		    if (arg[3] == '*')
+		      new->flags |= CONFIG_NOCLID;
+		    else
+		      {
+			int len;
+			arg += 3; /* dump id: */
+			if (strchr(arg, ':'))
+			  len = parse_hex(arg, (unsigned char *)arg, -1, NULL, NULL);
+			else
+			  {
+			    unhide_metas(arg);
+			    len = (int) strlen(arg);
+			  }
+			
+			if (len == -1)
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad hex constant"));
+			  }
+			else if ((new->clid = opt_malloc(len)))
+			  {
+			    new->flags |= CONFIG_CLID;
+			    new->clid_len = len;
+			    memcpy(new->clid, arg, len);
+			  }
+		      }
+		  }
+		/* dhcp-host has strange backwards-compat needs. */
+		else if (strstr(arg, "net:") == arg || strstr(arg, "set:") == arg)
+		  {
+		    struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
+		    newlist->next = new->netid;
+		    new->netid = newlist;
+		    newlist->list = dhcp_netid_create(arg+4, NULL);
+		  }
+		else if (strstr(arg, "tag:") == arg)
+		  new->filter = dhcp_netid_create(arg+4, new->filter);
+		  
+#ifdef HAVE_DHCP6
+		else if (arg[0] == '[' && arg[strlen(arg)-1] == ']')
+		  {
+		    char *pref;
+		    struct in6_addr in6;
+		    struct addrlist *new_addr;
+		    
+		    arg[strlen(arg)-1] = 0;
+		    arg++;
+		    pref = split_chr(arg, '/');
+		    
+		    if (!inet_pton(AF_INET6, arg, &in6))
+		      {
+			dhcp_config_free(new);
+			ret_err(_("bad IPv6 address"));
+		      }
+
+		    new_addr = opt_malloc(sizeof(struct addrlist));
+		    new_addr->next = new->addr6;
+		    new_addr->flags = 0;
+		    new_addr->addr.addr6 = in6;
+		    new->addr6 = new_addr;
+		    
+		    if (pref)
+		      {
+			u64 addrpart = addr6part(&in6);
+			
+			if (!atoi_check(pref, &new_addr->prefixlen) ||
+			    new_addr->prefixlen > 128 ||
+			    ((((u64)1<<(128-new_addr->prefixlen))-1) & addrpart) != 0)
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad IPv6 prefix"));
+			  }
+			
+			new_addr->flags |= ADDRLIST_PREFIX;
+		      }
+		  
+		    for (i= 0; i < 8; i++)
+		      if (in6.s6_addr[i] != 0)
+			break;
+		    
+		    /* set WILDCARD if network part all zeros */
+		    if (i == 8)
+		      new_addr->flags |= ADDRLIST_WILDCARD;
+		    
+		    new->flags |= CONFIG_ADDR6;
+		  }
+#endif
+		else
+		  {
+		    struct hwaddr_config *newhw = opt_malloc(sizeof(struct hwaddr_config));
+		    if ((newhw->hwaddr_len = parse_hex(arg, newhw->hwaddr, DHCP_CHADDR_MAX, 
+						       &newhw->wildcard_mask, &newhw->hwaddr_type)) == -1)
+		      {
+			free(newhw);
+			dhcp_config_free(new);
+			ret_err(_("bad hex constant"));
+		      }
+		    else
+		      {
+			newhw->next = new->hwaddr;
+			new->hwaddr = newhw;
+		      }		    
+		  }
+	      }
+	    else if (strchr(arg, '.') && (inet_pton(AF_INET, arg, &in) > 0))
+	      {
+		struct dhcp_config *configs;
+		
+		new->addr = in;
+		new->flags |= CONFIG_ADDR;
+		
+		/* If the same IP appears in more than one host config, then DISCOVER
+		   for one of the hosts will get the address, but REQUEST will be NAKed,
+		   since the address is reserved by the other one -> protocol loop. */
+		for (configs = daemon->dhcp_conf; configs; configs = configs->next) 
+		  if ((configs->flags & CONFIG_ADDR) && configs->addr.s_addr == in.s_addr)
+		    {
+		      inet_ntop(AF_INET, &in, daemon->addrbuff, ADDRSTRLEN);
+		      sprintf(errstr, _("duplicate dhcp-host IP address %s"),
+			      daemon->addrbuff);
+		      return 0;
+		    }	      
+	      }
+	    else
+	      {
+		char *cp, *lastp = NULL, last = 0;
+		int fac = 1, isdig = 0;
+		
+		if (strlen(arg) > 1)
+		  {
+		    lastp = arg + strlen(arg) - 1;
+		    last = *lastp;
+		    switch (last)
+		      {
+		      case 'w':
+		      case 'W':
+			fac *= 7;
+			/* fall through */
+		      case 'd':
+		      case 'D':
+			fac *= 24;
+			/* fall through */
+		      case 'h':
+		      case 'H':
+			fac *= 60;
+			/* fall through */
+		      case 'm':
+		      case 'M':
+			fac *= 60;
+			/* fall through */
+		      case 's':
+		      case 'S':
+			*lastp = 0;
+		      }
+		  }
+		
+		for (cp = arg; *cp; cp++)
+		  if (isdigit((unsigned char)*cp))
+		    isdig = 1;
+		  else if (*cp != ' ')
+		    break;
+
+		if (*cp)
+		  {
+		    if (lastp)
+		      *lastp = last;
+		    if (strcmp(arg, "infinite") == 0)
+		      {
+			new->lease_time = 0xffffffff;
+			new->flags |= CONFIG_TIME;
+		      }
+		    else if (strcmp(arg, "ignore") == 0)
+		      new->flags |= CONFIG_DISABLE;
+		    else
+		      {
+			if (!(new->hostname = canonicalise_opt(arg)) ||
+			    !legal_hostname(new->hostname))
+			  {
+			    dhcp_config_free(new);
+			    ret_err(_("bad DHCP host name"));
+			  }
+			
+			new->flags |= CONFIG_NAME;
+			new->domain = strip_hostname(new->hostname);			
+		      }
+		  }
+		else if (isdig)
+		  {
+		    new->lease_time = atoi(arg) * fac; 
+		    /* Leases of a minute or less confuse
+		       some clients, notably Apple's */
+		    if (new->lease_time < 120)
+		      new->lease_time = 120;
+		    new->flags |= CONFIG_TIME;
+		  }
+	      }
+
+	    arg = comma;
+	  }
+
+	daemon->dhcp_conf = new;
+	break;
+      }
+      
+    case LOPT_TAG_IF:  /* --tag-if */
+      {
+	struct tag_if *new = opt_malloc(sizeof(struct tag_if));
+		
+	new->tag = NULL;
+	new->set = NULL;
+	new->next = NULL;
+	
+	/* preserve order */
+	if (!daemon->tag_if)
+	  daemon->tag_if = new;
+	else
+	  {
+	    struct tag_if *tmp;
+	    for (tmp = daemon->tag_if; tmp->next; tmp = tmp->next);
+	    tmp->next = new;
+	  }
+
+	while (arg)
+	  {
+	    size_t len;
+
+	    comma = split(arg);
+	    len = strlen(arg);
+
+	    if (len < 5)
+	      {
+		new->set = NULL;
+		break;
+	      }
+	    else
+	      {
+		struct dhcp_netid *newtag = dhcp_netid_create(arg+4, NULL);
+
+		if (strstr(arg, "set:") == arg)
+		  {
+		    struct dhcp_netid_list *newlist = opt_malloc(sizeof(struct dhcp_netid_list));
+		    newlist->next = new->set;
+		    new->set = newlist;
+		    newlist->list = newtag;
+		  }
+		else if (strstr(arg, "tag:") == arg)
+		  {
+		    newtag->next = new->tag;
+		    new->tag = newtag;
+		  }
+		else 
+		  {
+		    new->set = NULL;
+		    dhcp_netid_free(newtag);
+		    break;
+		  }
+	      }
+	    
+	    arg = comma;
+	  }
+
+	if (!new->set)
+	  {
+	    dhcp_netid_free(new->tag);
+	    dhcp_netid_list_free(new->set);
+	    ret_err_free(_("bad tag-if"), new);
+	  }
+	  
+	break;
+      }
+
+      
+    case 'O':           /* --dhcp-option */
+    case LOPT_FORCE:    /* --dhcp-option-force */
+    case LOPT_OPTS:
+    case LOPT_MATCH:    /* --dhcp-match */
+      return parse_dhcp_opt(errstr, arg, 
+			    option == LOPT_FORCE ? DHOPT_FORCE : 
+			    (option == LOPT_MATCH ? DHOPT_MATCH :
+			     (option == LOPT_OPTS ? DHOPT_BANK : 0)));
+
+    case LOPT_NAME_MATCH: /* --dhcp-name-match */
+      {
+	struct dhcp_match_name *new = opt_malloc(sizeof(struct dhcp_match_name));
+	struct dhcp_netid *id = opt_malloc(sizeof(struct dhcp_netid));
+	ssize_t len;
+	
+	if (!(comma = split(arg)) || (len = strlen(comma)) == 0)
+	  ret_err(gen_err);
+
+	new->wildcard = 0;
+	new->netid = id;
+	id->net = opt_string_alloc(set_prefix(arg));
+
+	if (comma[len-1] == '*')
+	  {
+	    comma[len-1] = 0;
+	    new->wildcard = 1;
+	  }
+	new->name = opt_string_alloc(comma);
+
+	new->next = daemon->dhcp_name_match;
+	daemon->dhcp_name_match = new;
+
+	break;
+      }
+      
+    case 'M': /* --dhcp-boot */
+      {
+	struct dhcp_netid *id = dhcp_tags(&arg);
+	
+	if (!arg)
+	  {
+	    ret_err(gen_err);
+	  }
+	else 
+	  {
+	    char *dhcp_file, *dhcp_sname = NULL, *tftp_sname = NULL;
+	    struct in_addr dhcp_next_server;
+	    struct dhcp_boot *new;
+	    comma = split(arg);
+	    dhcp_file = opt_string_alloc(arg);
+	    dhcp_next_server.s_addr = 0;
+	    if (comma)
+	      {
+		arg = comma;
+		comma = split(arg);
+		dhcp_sname = opt_string_alloc(arg);
+		if (comma)
+		  {
+		    unhide_metas(comma);
+		    if (!(inet_pton(AF_INET, comma, &dhcp_next_server) > 0))
+		      {
+			/*
+			 * The user may have specified the tftp hostname here.
+			 * save it so that it can be resolved/looked up during
+			 * actual dhcp_reply().
+			 */	
+			
+			tftp_sname = opt_string_alloc(comma);
+			dhcp_next_server.s_addr = 0;
+		      }
+		  }
+	      }
+	    
+	    new = opt_malloc(sizeof(struct dhcp_boot));
+	    new->file = dhcp_file;
+	    new->sname = dhcp_sname;
+	    new->tftp_sname = tftp_sname;
+	    new->next_server = dhcp_next_server;
+	    new->netid = id;
+	    new->next = daemon->boot_config;
+	    daemon->boot_config = new;
+	  }
+      
+	break;
+      }
+
+    case LOPT_REPLY_DELAY: /* --dhcp-reply-delay */
+      {
+	struct dhcp_netid *id = dhcp_tags(&arg);
+	
+	if (!arg)
+	  {
+	    ret_err(gen_err);
+	  }
+	else
+	  {
+	    struct delay_config *new;
+	    int delay;
+	    if (!atoi_check(arg, &delay))
+              ret_err(gen_err);
+	    
+	    new = opt_malloc(sizeof(struct delay_config));
+	    new->delay = delay;
+	    new->netid = id;
+            new->next = daemon->delay_conf;
+            daemon->delay_conf = new;
+	  }
+	
+	break;
+      }
+      
+    case LOPT_PXE_PROMT:  /* --pxe-prompt */
+       {
+	 struct dhcp_opt *new = opt_malloc(sizeof(struct dhcp_opt));
+	 int timeout;
+	 
+	 new->netid = NULL;
+	 new->opt = 10; /* PXE_MENU_PROMPT */
+	 new->netid = dhcp_tags(&arg);
+	 
+	 if (!arg)
+	   {
+	     dhcp_opt_free(new);
+	     ret_err(gen_err);
+	   }
+	 else
+	   {
+	     comma = split(arg);
+	     unhide_metas(arg);
+	     new->len = strlen(arg) + 1;
+	     new->val = opt_malloc(new->len);
+	     memcpy(new->val + 1, arg, new->len - 1);
+	     
+	     new->u.vendor_class = NULL;
+	     new->flags = DHOPT_VENDOR | DHOPT_VENDOR_PXE;
+	     
+	     if (comma && atoi_check(comma, &timeout))
+	       *(new->val) = timeout;
+	     else
+	       *(new->val) = 255;
+
+	     new->next = daemon->dhcp_opts;
+	     daemon->dhcp_opts = new;
+	     daemon->enable_pxe = 1;
+	   }
+	 
+	 break;
+       }
+       
+    case LOPT_PXE_SERV:  /* --pxe-service */
+       {
+	 struct pxe_service *new = opt_malloc(sizeof(struct pxe_service));
+	 char *CSA[] = { "x86PC", "PC98", "IA64_EFI", "Alpha", "Arc_x86", "Intel_Lean_Client",
+			 "IA32_EFI", "x86-64_EFI", "Xscale_EFI", "BC_EFI",
+			 "ARM32_EFI", "ARM64_EFI", NULL };  
+	 static int boottype = 32768;
+	 
+	 new->netid = NULL;
+	 new->sname = NULL;
+	 new->server.s_addr = 0;
+	 new->netid = dhcp_tags(&arg);
+
+	 if (arg && (comma = split(arg)))
+	   {
+	     for (i = 0; CSA[i]; i++)
+	       if (strcasecmp(CSA[i], arg) == 0)
+		 break;
+	     
+	     if (CSA[i] || atoi_check(arg, &i))
+	       {
+		 arg = comma;
+		 comma = split(arg);
+		 
+		 new->CSA = i;
+		 new->menu = opt_string_alloc(arg);
+		 
+		 if (!comma)
+		   {
+		     new->type = 0; /* local boot */
+		     new->basename = NULL;
+		   }
+		 else
+		   {
+		     arg = comma;
+		     comma = split(arg);
+		     if (atoi_check(arg, &i))
+		       {
+			 new->type = i;
+			 new->basename = NULL;
+		       }
+		     else
+		       {
+			 new->type = boottype++;
+			 new->basename = opt_string_alloc(arg);
+		       }
+		     
+		     if (comma)
+		       {
+			 if (!inet_pton(AF_INET, comma, &new->server))
+			   {
+			     new->server.s_addr = 0;
+			     new->sname = opt_string_alloc(comma);
+			   }
+		       
+		       }
+		   }
+		 
+		 /* Order matters */
+		 new->next = NULL;
+		 if (!daemon->pxe_services)
+		   daemon->pxe_services = new; 
+		 else
+		   {
+		     struct pxe_service *s;
+		     for (s = daemon->pxe_services; s->next; s = s->next);
+		     s->next = new;
+		   }
+		 
+		 daemon->enable_pxe = 1;
+		 break;
+		
+	       }
+	   }
+	 
+	 ret_err(gen_err);
+       }
+	 
+    case '4':  /* --dhcp-mac */
+      {
+	if (!(comma = split(arg)))
+	  ret_err(gen_err);
+	else
+	  {
+	    struct dhcp_mac *new = opt_malloc(sizeof(struct dhcp_mac));
+	    new->netid.net = opt_string_alloc(set_prefix(arg));
+	    unhide_metas(comma);
+	    new->hwaddr_len = parse_hex(comma, new->hwaddr, DHCP_CHADDR_MAX, &new->mask, &new->hwaddr_type);
+	    if (new->hwaddr_len == -1)
+	      {
+		free(new->netid.net);
+		ret_err_free(gen_err, new);
+	      }
+	    else
+	      {
+		new->next = daemon->dhcp_macs;
+		daemon->dhcp_macs = new;
+	      }
+	  }
+      }
+      break;
+
+    case 'U':           /* --dhcp-vendorclass */
+    case 'j':           /* --dhcp-userclass */
+    case LOPT_CIRCUIT:  /* --dhcp-circuitid */
+    case LOPT_REMOTE:   /* --dhcp-remoteid */
+    case LOPT_SUBSCR:   /* --dhcp-subscrid */
+      {
+	 unsigned char *p;
+	 int dig = 0;
+	 struct dhcp_vendor *new = opt_malloc(sizeof(struct dhcp_vendor));
+	 
+	 if (!(comma = split(arg)))
+	   ret_err_free(gen_err, new);
+	
+	 new->netid.net = opt_string_alloc(set_prefix(arg));
+	 /* check for hex string - must digits may include : must not have nothing else, 
+	    only allowed for agent-options. */
+	 
+	 arg = comma;
+	 if ((comma = split(arg)))
+	   {
+	     if (option  != 'U' || strstr(arg, "enterprise:") != arg)
+	       {
+	         free(new->netid.net);
+	         ret_err_free(gen_err, new);
+	       }
+	     else
+	       new->enterprise = atoi(arg+11);
+	   }
+	 else
+	   comma = arg;
+	 
+	 for (p = (unsigned char *)comma; *p; p++)
+	   if (isxdigit(*p))
+	     dig = 1;
+	   else if (*p != ':')
+	     break;
+	 unhide_metas(comma);
+	 if (option == 'U' || option == 'j' || *p || !dig)
+	   {
+	     new->len = strlen(comma);  
+	     new->data = opt_malloc(new->len);
+	     memcpy(new->data, comma, new->len);
+	   }
+	 else
+	   {
+	     new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL);
+	     new->data = opt_malloc(new->len);
+	     memcpy(new->data, comma, new->len);
+	   }
+	 
+	 switch (option)
+	   {
+	   case 'j':
+	     new->match_type = MATCH_USER;
+	     break;
+	   case 'U':
+	     new->match_type = MATCH_VENDOR;
+	     break; 
+	   case LOPT_CIRCUIT:
+	     new->match_type = MATCH_CIRCUIT;
+	     break;
+	   case LOPT_REMOTE:
+	     new->match_type = MATCH_REMOTE;
+	     break;
+	   case LOPT_SUBSCR:
+	     new->match_type = MATCH_SUBSCRIBER;
+	     break;
+	   }
+	 new->next = daemon->dhcp_vendors;
+	 daemon->dhcp_vendors = new;
+
+	 break;
+      }
+      
+    case LOPT_ALTPORT:   /* --dhcp-alternate-port */
+      if (!arg)
+	{
+	  daemon->dhcp_server_port = DHCP_SERVER_ALTPORT;
+	  daemon->dhcp_client_port = DHCP_CLIENT_ALTPORT;
+	}
+      else
+	{
+	  comma = split(arg);
+	  if (!atoi_check16(arg, &daemon->dhcp_server_port) || 
+	      (comma && !atoi_check16(comma, &daemon->dhcp_client_port)))
+	    ret_err(_("invalid port number"));
+	  if (!comma)
+	    daemon->dhcp_client_port = daemon->dhcp_server_port+1; 
+	}
+      break;
+
+    case 'J':            /* --dhcp-ignore */
+    case LOPT_NO_NAMES:  /* --dhcp-ignore-names */
+    case LOPT_BROADCAST: /* --dhcp-broadcast */
+    case '3':            /* --bootp-dynamic */
+    case LOPT_GEN_NAMES: /* --dhcp-generate-names */
+      {
+	struct dhcp_netid_list *new = opt_malloc(sizeof(struct dhcp_netid_list));
+	struct dhcp_netid *list = NULL;
+	if (option == 'J')
+	  {
+	    new->next = daemon->dhcp_ignore;
+	    daemon->dhcp_ignore = new;
+	  }
+	else if (option == LOPT_BROADCAST)
+	  {
+	    new->next = daemon->force_broadcast;
+	    daemon->force_broadcast = new;
+	  }
+	else if (option == '3')
+	  {
+	    new->next = daemon->bootp_dynamic;
+	    daemon->bootp_dynamic = new;
+	  }
+	else if (option == LOPT_GEN_NAMES)
+	  {
+	    new->next = daemon->dhcp_gen_names;
+	    daemon->dhcp_gen_names = new;
+	  }
+	else
+	  {
+	    new->next = daemon->dhcp_ignore_names;
+	    daemon->dhcp_ignore_names = new;
+	  }
+	
+	while (arg) {
+	  comma = split(arg);
+	  list = dhcp_netid_create(is_tag_prefix(arg) ? arg+4 :arg, list);
+	  arg = comma;
+	}
+	
+	new->list = list;
+	break;
+      }
+
+    case LOPT_PROXY: /* --dhcp-proxy */
+      daemon->override = 1;
+      while (arg) {
+	struct addr_list *new = opt_malloc(sizeof(struct addr_list));
+	comma = split(arg);
+	if (!(inet_pton(AF_INET, arg, &new->addr) > 0))
+	  ret_err_free(_("bad dhcp-proxy address"), new);
+	new->next = daemon->override_relays;
+	daemon->override_relays = new;
+	arg = comma;
+	}
+	  break;
+
+    case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */
+      {
+        while (arg) {
+	  struct dhcp_pxe_vendor *new = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+	  comma = split(arg);
+          new->data = opt_string_alloc(arg);
+	  new->next = daemon->dhcp_pxe_vendors;
+	  daemon->dhcp_pxe_vendors = new;
+	  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 (comma && 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 (comma && inet_pton(AF_INET6, arg, &new->local) && inet_pton(AF_INET6, comma, &new->server))
+	  {
+	    new->next = daemon->relay6;
+	    daemon->relay6 = new;
+	  }
+#endif
+	else
+	  {
+	    free(new->interface);
+	    ret_err_free(_("Bad dhcp-relay"), new);
+	  }
+	
+	break;
+      }
+
+#endif
+      
+#ifdef HAVE_DHCP6
+    case LOPT_RA_PARAM: /* --ra-param */
+      if ((comma = split(arg)))
+	{
+	  struct ra_interface *new = opt_malloc(sizeof(struct ra_interface));
+	  new->lifetime = -1;
+	  new->prio = 0;
+	  new->mtu = 0;
+	  new->mtu_name = NULL;
+	  new->name = opt_string_alloc(arg);
+	  if (strcasestr(comma, "mtu:") == comma)
+	    {
+	      arg = comma + 4;
+	      if (!(comma = split(comma)))
+	        goto err;
+	      if (!strcasecmp(arg, "off"))
+	        new->mtu = -1;
+	      else if (!atoi_check(arg, &new->mtu))
+	        new->mtu_name = opt_string_alloc(arg);
+	      else if (new->mtu < 1280)
+	        goto err;
+	    }
+	  if (strcasestr(comma, "high") == comma || strcasestr(comma, "low") == comma)
+	    {
+	      if (*comma == 'l' || *comma == 'L')
+		new->prio = 0x18;
+	      else
+		new->prio = 0x08;
+	      comma = split(comma);
+	    }
+	   arg = split(comma);
+	   if (!atoi_check(comma, &new->interval) || 
+	      (arg && !atoi_check(arg, &new->lifetime)))
+             {
+err:
+	       free(new->name);
+	       ret_err_free(_("bad RA-params"), new);
+             }
+	  
+	  new->next = daemon->ra_interfaces;
+	  daemon->ra_interfaces = new;
+	}
+      break;
+      
+    case LOPT_DUID: /* --dhcp-duid */
+      if (!(comma = split(arg)) || !atoi_check(arg, (int *)&daemon->duid_enterprise))
+	ret_err(_("bad DUID"));
+      else
+	{
+	  daemon->duid_config_len = parse_hex(comma,(unsigned char *)comma, strlen(comma), NULL, NULL);
+	  daemon->duid_config = opt_malloc(daemon->duid_config_len);
+	  memcpy(daemon->duid_config, comma, daemon->duid_config_len);
+	}
+      break;
+#endif
+
+    case 'V':  /* --alias */
+      {
+	char *dash, *a[3] = { NULL, NULL, NULL };
+	int k = 0;
+	struct doctor *new = opt_malloc(sizeof(struct doctor));
+	new->next = daemon->doctors;
+	daemon->doctors = new;
+	new->mask.s_addr = 0xffffffff;
+	new->end.s_addr = 0;
+
+	if ((a[0] = arg))
+	  for (k = 1; k < 3; k++)
+	    {
+	      if (!(a[k] = split(a[k-1])))
+		break;
+	      unhide_metas(a[k]);
+	    }
+	
+	dash = split_chr(a[0], '-');
+
+	if ((k < 2) || 
+	    (!(inet_pton(AF_INET, a[0], &new->in) > 0)) ||
+	    (!(inet_pton(AF_INET, a[1], &new->out) > 0)) ||
+	    (k == 3 && !inet_pton(AF_INET, a[2], &new->mask)))
+	  ret_err(_("missing address in alias"));
+	
+	if (dash && 
+	    (!(inet_pton(AF_INET, dash, &new->end) > 0) ||
+	     !is_same_net(new->in, new->end, new->mask) ||
+	     ntohl(new->in.s_addr) > ntohl(new->end.s_addr)))
+	  ret_err_free(_("invalid alias range"), new);
+	
+	break;
+      }
+      
+    case LOPT_INTNAME:  /* --interface-name */
+    case LOPT_DYNHOST:  /* --dynamic-host */
+      {
+	struct interface_name *new, **up;
+	char *domain = arg;
+	
+	arg = split(arg);
+	
+	new = opt_malloc(sizeof(struct interface_name));
+	memset(new, 0, sizeof(struct interface_name));
+	new->flags = IN4 | IN6;
+	
+	/* Add to the end of the list, so that first name
+	   of an interface is used for PTR lookups. */
+	for (up = &daemon->int_names; *up; up = &((*up)->next));
+	*up = new;
+	
+	while ((comma = split(arg)))
+	  {
+	    if (inet_pton(AF_INET, arg, &new->proto4))
+	      new->flags |= INP4;
+	    else if (inet_pton(AF_INET6, arg, &new->proto6))
+	      new->flags |= INP6;
+	    else
+	      break;
+	    
+	    arg = comma;
+	  }
+
+	if ((comma = split_chr(arg, '/')))
+	  {
+	    if (strcmp(comma, "4") == 0)
+	      new->flags &= ~IN6;
+	    else if (strcmp(comma, "6") == 0)
+	      new->flags &= ~IN4;
+	    else
+	      ret_err_free(gen_err, new);
+	  }
+
+	new->intr = opt_string_alloc(arg);
+
+	if (option == LOPT_DYNHOST)
+	  {
+	    if (!(new->flags & (INP4 | INP6)))
+	      ret_err(_("missing address in dynamic host"));
+
+	    if (!(new->flags & IN4) || !(new->flags & IN6))
+	      arg = NULL; /* provoke error below */
+
+	    new->flags &= ~(IN4 | IN6);
+	  }
+	else
+	  {
+	    if (new->flags & (INP4 | INP6))
+	      arg = NULL; /* provoke error below */
+	  }
+	
+	if (!domain || !arg || !(new->name = canonicalise_opt(domain)))
+	  ret_err(option == LOPT_DYNHOST ?
+		  _("bad dynamic host") : _("bad interface name"));
+	
+	break;
+      }
+      
+    case LOPT_CNAME: /* --cname */
+      {
+	struct cname *new;
+	char *alias, *target, *last, *pen;
+	int ttl = -1;
+
+	for (last = pen = NULL, comma = arg; comma; comma = split(comma))
+	  {
+	    pen = last;
+	    last = comma;
+	  }
+
+	if (!pen)
+	  ret_err(_("bad CNAME"));
+	
+	if (pen != arg && atoi_check(last, &ttl))
+	  last = pen;
+	  	
+    	target = canonicalise_opt(last);
+
+	while (arg != last)
+	  {
+	    int arglen = strlen(arg);
+	    alias = canonicalise_opt(arg);
+
+	    if (!alias || !target)
+	      {
+		free(target);
+		free(alias);
+		ret_err(_("bad CNAME"));
+	      }
+	    
+	    for (new = daemon->cnames; new; new = new->next)
+	      if (hostname_isequal(new->alias, alias))
+		{
+		  free(target);
+		  free(alias);
+		  ret_err(_("duplicate CNAME"));
+		}
+	    new = opt_malloc(sizeof(struct cname));
+	    new->next = daemon->cnames;
+	    daemon->cnames = new;
+	    new->alias = alias;
+	    new->target = target;
+	    new->ttl = ttl;
+
+	    for (arg += arglen+1; *arg && isspace(*arg); arg++);
+	  }
+      
+	break;
+      }
+
+    case LOPT_PTR:  /* --ptr-record */
+      {
+	struct ptr_record *new;
+	char *dom, *target = NULL;
+
+	comma = split(arg);
+	
+	if (!(dom = canonicalise_opt(arg)) ||
+	    (comma && !(target = canonicalise_opt(comma))))
+	  {
+	    free(dom);
+	    free(target);
+	    ret_err(_("bad PTR record"));
+	  }
+	else
+	  {
+	    new = opt_malloc(sizeof(struct ptr_record));
+	    new->next = daemon->ptr;
+	    daemon->ptr = new;
+	    new->name = dom;
+	    new->ptr = target;
+	  }
+	break;
+      }
+
+    case LOPT_NAPTR: /* --naptr-record */
+      {
+	char *a[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+	int k = 0;
+	struct naptr *new;
+	int order, pref;
+	char *name=NULL, *replace = NULL;
+
+	if ((a[0] = arg))
+	  for (k = 1; k < 7; k++)
+	    if (!(a[k] = split(a[k-1])))
+	      break;
+	
+	
+	if (k < 6 || 
+	    !(name = canonicalise_opt(a[0])) ||
+	    !atoi_check16(a[1], &order) || 
+	    !atoi_check16(a[2], &pref) ||
+	    (k == 7 && !(replace = canonicalise_opt(a[6]))))
+          {
+	    free(name);
+	    free(replace);
+	    ret_err(_("bad NAPTR record"));
+          }
+	else
+	  {
+	    new = opt_malloc(sizeof(struct naptr));
+	    new->next = daemon->naptr;
+	    daemon->naptr = new;
+	    new->name = name;
+	    new->flags = opt_string_alloc(a[3]);
+	    new->services = opt_string_alloc(a[4]);
+	    new->regexp = opt_string_alloc(a[5]);
+	    new->replace = replace;
+	    new->order = order;
+	    new->pref = pref;
+	  }
+	break;
+      }
+
+    case LOPT_RR: /* dns-rr */
+      {
+       	struct txt_record *new;
+	size_t len = 0;
+	char *data;
+	int class;
+
+	comma = split(arg);
+	data = split(comma);
+		
+	new = opt_malloc(sizeof(struct txt_record));
+	new->name = NULL;
+	
+	if (!atoi_check(comma, &class) || 
+	    !(new->name = canonicalise_opt(arg)) ||
+	    (data && (len = parse_hex(data, (unsigned char *)data, -1, NULL, NULL)) == -1U))
+          {
+            free(new->name);
+	    ret_err_free(_("bad RR record"), new);
+          }
+
+	new->len = 0;
+	new->class = class;
+	new->next = daemon->rr;
+	daemon->rr = new;
+	
+	if (data)
+	  {
+	    new->txt = opt_malloc(len);
+	    new->len = len;
+	    memcpy(new->txt, data, len);
+	  }
+	
+	break;
+      }
+
+    case LOPT_CAA: /* --caa-record */
+      {
+       	struct txt_record *new;
+	char *tag, *value;
+	int flags;
+	
+	comma = split(arg);
+	tag = split(comma);
+	value = split(tag);
+	
+	new = opt_malloc(sizeof(struct txt_record));
+	new->next = daemon->rr;
+	daemon->rr = new;
+
+	if (!atoi_check(comma, &flags) || !tag || !value || !(new->name = canonicalise_opt(arg)))
+	  ret_err(_("bad CAA record"));
+	
+	unhide_metas(tag);
+	unhide_metas(value);
+
+	new->len = strlen(tag) + strlen(value) + 2;
+	new->txt = opt_malloc(new->len);
+	new->txt[0] = flags;
+	new->txt[1] = strlen(tag);
+	memcpy(&new->txt[2], tag, strlen(tag));
+	memcpy(&new->txt[2 + strlen(tag)], value, strlen(value));
+	new->class = T_CAA;
+	
+	break;
+      }
+	
+    case 'Y':  /* --txt-record */
+      {
+	struct txt_record *new;
+	unsigned char *p, *cnt;
+	size_t len;
+
+	comma = split(arg);
+		
+	new = opt_malloc(sizeof(struct txt_record));
+	new->class = C_IN;
+	new->stat = 0;
+
+	if (!(new->name = canonicalise_opt(arg)))
+	  ret_err_free(_("bad TXT record"), new);
+	
+	new->next = daemon->txt;
+	daemon->txt = new;
+	len = comma ? strlen(comma) : 0;
+	len += (len/255) + 1; /* room for extra counts */
+	new->txt = p = opt_malloc(len);
+
+	cnt = p++;
+	*cnt = 0;
+	
+	while (comma && *comma)
+	  {
+	    unsigned char c = (unsigned char)*comma++;
+
+	    if (c == ',' || *cnt == 255)
+	      {
+		if (c != ',')
+		  comma--;
+		cnt = p++;
+		*cnt = 0;
+	      }
+	    else
+	      {
+		*p++ = unhide_meta(c);
+		(*cnt)++;
+	      }
+	  }
+
+	new->len = p - new->txt;
+
+	break;
+      }
+      
+    case 'W':  /* --srv-host */
+      {
+	int port = 1, priority = 0, weight = 0;
+	char *name, *target = NULL;
+	struct mx_srv_record *new;
+	
+	comma = split(arg);
+	
+	if (!(name = canonicalise_opt(arg)))
+	  ret_err(_("bad SRV record"));
+	
+	if (comma)
+	  {
+	    arg = comma;
+	    comma = split(arg);
+	    if (!(target = canonicalise_opt(arg)))
+	      ret_err_free(_("bad SRV target"), name);
+		
+	    if (comma)
+	      {
+		arg = comma;
+		comma = split(arg);
+		if (!atoi_check16(arg, &port))
+                  {
+                    free(name);
+		    ret_err_free(_("invalid port number"), target);
+                  }
+		
+		if (comma)
+		  {
+		    arg = comma;
+		    comma = split(arg);
+		    if (!atoi_check16(arg, &priority))
+                      {
+                        free(name);
+		        ret_err_free(_("invalid priority"), target);
+		      }
+		    if (comma && !atoi_check16(comma, &weight))
+                      {
+                        free(name);
+		        ret_err_free(_("invalid weight"), target);
+                      }
+		  }
+	      }
+	  }
+	
+	new = opt_malloc(sizeof(struct mx_srv_record));
+	new->next = daemon->mxnames;
+	daemon->mxnames = new;
+	new->issrv = 1;
+	new->name = name;
+	new->target = target;
+	new->srvport = port;
+	new->priority = priority;
+	new->weight = weight;
+	break;
+      }
+      
+    case LOPT_HOST_REC: /* --host-record */
+      {
+	struct host_record *new;
+
+	if (!arg || !(comma = split(arg)))
+	  ret_err(_("Bad host-record"));
+	
+	new = opt_malloc(sizeof(struct host_record));
+	memset(new, 0, sizeof(struct host_record));
+	new->ttl = -1;
+	new->flags = 0;
+
+	while (arg)
+	  {
+	    union all_addr addr;
+	    char *dig;
+
+	    for (dig = arg; *dig != 0; dig++)
+	      if (*dig < '0' || *dig > '9')
+		break;
+	    if (*dig == 0)
+	      new->ttl = atoi(arg);
+	    else if (inet_pton(AF_INET, arg, &addr.addr4))
+	      {
+		new->addr = addr.addr4;
+		new->flags |= HR_4;
+	      }
+	    else if (inet_pton(AF_INET6, arg, &addr.addr6))
+	      {
+		new->addr6 = addr.addr6;
+		new->flags |= HR_6;
+	      }
+	    else
+	      {
+		char *canon = canonicalise_opt(arg);
+		struct name_list *nl;
+		if (!canon)
+                  {
+		    struct name_list *tmp = new->names, *next;
+		    for (tmp = new->names; tmp; tmp = next)
+		      {
+			next = tmp->next;
+			free(tmp);
+		      }
+		    ret_err_free(_("Bad name in host-record"), new);
+                  }
+
+		nl = opt_malloc(sizeof(struct name_list));
+		nl->name = canon;
+		/* keep order, so that PTR record goes to first name */
+		nl->next = NULL;
+		if (!new->names)
+		  new->names = nl;
+		else
+		  { 
+		    struct name_list *tmp;
+		    for (tmp = new->names; tmp->next; tmp = tmp->next);
+		    tmp->next = nl;
+		  }
+	      }
+	    
+	    arg = comma;
+	    comma = split(arg);
+	  }
+
+	/* Keep list order */
+	if (!daemon->host_records_tail)
+	  daemon->host_records = new;
+	else
+	  daemon->host_records_tail->next = new;
+	new->next = NULL;
+	daemon->host_records_tail = new;
+	break;
+      }
+
+#ifdef HAVE_DNSSEC
+    case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
+      daemon->timestamp_file = opt_string_alloc(arg); 
+      break;
+
+    case LOPT_DNSSEC_CHECK: /* --dnssec-check-unsigned */
+      if (arg)
+	{
+	  if (strcmp(arg, "no") == 0)
+	    set_option_bool(OPT_DNSSEC_IGN_NS);
+	  else
+	    ret_err(_("bad value for dnssec-check-unsigned"));
+	}
+      break;
+      
+    case LOPT_TRUST_ANCHOR: /* --trust-anchor */
+      {
+	struct ds_config *new = opt_malloc(sizeof(struct ds_config));
+      	char *cp, *cp1, *keyhex, *digest, *algo = NULL;
+	int len;
+	
+	new->class = C_IN;
+	new->name = NULL;
+
+	if ((comma = split(arg)) && (algo = split(comma)))
+	  {
+	    int class = 0;
+	    if (strcmp(comma, "IN") == 0)
+	      class = C_IN;
+	    else if (strcmp(comma, "CH") == 0)
+	      class = C_CHAOS;
+	    else if (strcmp(comma, "HS") == 0)
+	      class = C_HESIOD;
+	    
+	    if (class != 0)
+	      {
+		new->class = class;
+		comma = algo;
+		algo = split(comma);
+	      }
+	  }
+		  
+       	if (!comma || !algo || !(digest = split(algo)) || !(keyhex = split(digest)) ||
+	    !atoi_check16(comma, &new->keytag) || 
+	    !atoi_check8(algo, &new->algo) ||
+	    !atoi_check8(digest, &new->digest_type) ||
+	    !(new->name = canonicalise_opt(arg)))
+	  ret_err_free(_("bad trust anchor"), new);
+	    
+	/* Upper bound on length */
+	len = (2*strlen(keyhex))+1;
+	new->digest = opt_malloc(len);
+	unhide_metas(keyhex);
+	/* 4034: "Whitespace is allowed within digits" */
+	for (cp = keyhex; *cp; )
+	  if (isspace(*cp))
+	    for (cp1 = cp; *cp1; cp1++)
+	      *cp1 = *(cp1+1);
+	  else
+	    cp++;
+	if ((new->digestlen = parse_hex(keyhex, (unsigned char *)new->digest, len, NULL, NULL)) == -1)
+	  {
+	    free(new->name);
+	    ret_err_free(_("bad HEX in trust anchor"), new);
+	  }
+	
+	new->next = daemon->ds;
+	daemon->ds = new;
+	
+	break;
+      }
+#endif
+		
+    default:
+      ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
+      
+    }
+  
+  return 1;
+}
+
+static void read_file(char *file, FILE *f, int hard_opt)	
+{
+  volatile int lineno = 0;
+  char *buff = daemon->namebuff;
+  
+  while (fgets(buff, MAXDNAME, f))
+    {
+      int white, i;
+      volatile int option = (hard_opt == LOPT_REV_SERV) ? 0 : hard_opt;
+      char *errmess, *p, *arg, *start;
+      size_t len;
+
+      /* Memory allocation failure longjmps here if mem_recover == 1 */ 
+      if (option != 0 || hard_opt == LOPT_REV_SERV)
+	{
+	  if (setjmp(mem_jmp))
+	    continue;
+	  mem_recover = 1;
+	}
+      
+      arg = NULL;
+      lineno++;
+      errmess = NULL;
+      
+      /* Implement quotes, inside quotes we allow \\ \" \n and \t 
+	 metacharacters get hidden also strip comments */
+      for (white = 1, p = buff; *p; p++)
+	{
+	  if (*p == '"')
+	    {
+	      memmove(p, p+1, strlen(p+1)+1);
+
+	      for(; *p && *p != '"'; p++)
+		{
+		  if (*p == '\\' && strchr("\"tnebr\\", p[1]))
+		    {
+		      if (p[1] == 't')
+			p[1] = '\t';
+		      else if (p[1] == 'n')
+			p[1] = '\n';
+		      else if (p[1] == 'b')
+			p[1] = '\b';
+		      else if (p[1] == 'r')
+			p[1] = '\r';
+		      else if (p[1] == 'e') /* escape */
+			p[1] = '\033';
+		      memmove(p, p+1, strlen(p+1)+1);
+		    }
+		  *p = hide_meta(*p);
+		}
+
+	      if (*p == 0) 
+		{
+		  errmess = _("missing \"");
+		  goto oops; 
+		}
+
+	      memmove(p, p+1, strlen(p+1)+1);
+	    }
+
+	  if (isspace(*p))
+	    {
+	      *p = ' ';
+	      white = 1;
+	    }
+	  else 
+	    {
+	      if (white && *p == '#')
+		{ 
+		  *p = 0;
+		  break;
+		}
+	      white = 0;
+	    } 
+	}
+
+      
+      /* strip leading spaces */
+      for (start = buff; *start && *start == ' '; start++);
+      
+      /* strip trailing spaces */
+      for (len = strlen(start); (len != 0) && (start[len-1] == ' '); len--);
+      
+      if (len == 0)
+	continue; 
+      else
+	start[len] = 0;
+      
+      if (option != 0)
+	arg = start;
+      else if ((p=strchr(start, '=')))
+	{
+	  /* allow spaces around "=" */
+	  for (arg = p+1; *arg == ' '; arg++);
+	  for (; p >= start && (*p == ' ' || *p == '='); p--)
+	    *p = 0;
+	}
+      else
+	arg = NULL;
+
+      if (option == 0)
+	{
+	  for (option = 0, i = 0; opts[i].name; i++) 
+	    if (strcmp(opts[i].name, start) == 0)
+	      {
+		option = opts[i].val;
+		break;
+	      }
+	  
+	  if (!option)
+	    errmess = _("bad option");
+	  else if (opts[i].has_arg == 0 && arg)
+	    errmess = _("extraneous parameter");
+	  else if (opts[i].has_arg == 1 && !arg)
+	    errmess = _("missing parameter");
+	  else if (hard_opt == LOPT_REV_SERV && option != 'S' && option != LOPT_REV_SERV)
+	    errmess = _("illegal option");
+	}
+
+    oops:
+      if (errmess)
+	strcpy(daemon->namebuff, errmess);
+	  
+      if (errmess || !one_opt(option, arg, daemon->namebuff, _("error"), 0, hard_opt == LOPT_REV_SERV))
+	{
+	  sprintf(daemon->namebuff + strlen(daemon->namebuff), _(" at line %d of %s"), lineno, file);
+	  if (hard_opt != 0)
+	    my_syslog(LOG_ERR, "%s", daemon->namebuff);
+	  else
+	    die("%s", daemon->namebuff, EC_BADCONF);
+	}
+    }
+
+  mem_recover = 0;
+  fclose(f);
+}
+
+#if defined(HAVE_DHCP) && defined(HAVE_INOTIFY)
+int option_read_dynfile(char *file, int flags)
+{
+  my_syslog(MS_DHCP | LOG_INFO, _("read %s"), file);
+  
+  if (flags & AH_DHCP_HST)
+    return one_file(file, LOPT_BANK);
+  else if (flags & AH_DHCP_OPT)
+    return one_file(file, LOPT_OPTS);
+  
+  return 0;
+}
+#endif
+
+static int one_file(char *file, int hard_opt)
+{
+  FILE *f;
+  int nofile_ok = 0;
+  static int read_stdin = 0;
+  static struct fileread {
+    dev_t dev;
+    ino_t ino;
+    struct fileread *next;
+  } *filesread = NULL;
+  
+  if (hard_opt == '7')
+    {
+      /* default conf-file reading */
+      hard_opt = 0;
+      nofile_ok = 1;
+    }
+
+  if (hard_opt == 0 && strcmp(file, "-") == 0)
+    {
+      if (read_stdin == 1)
+	return 1;
+      read_stdin = 1;
+      file = "stdin";
+      f = stdin;
+    }
+  else
+    {
+      /* ignore repeated files. */
+      struct stat statbuf;
+    
+      if (hard_opt == 0 && stat(file, &statbuf) == 0)
+	{
+	  struct fileread *r;
+	  
+	  for (r = filesread; r; r = r->next)
+	    if (r->dev == statbuf.st_dev && r->ino == statbuf.st_ino)
+	      return 1;
+	  
+	  r = safe_malloc(sizeof(struct fileread));
+	  r->next = filesread;
+	  filesread = r;
+	  r->dev = statbuf.st_dev;
+	  r->ino = statbuf.st_ino;
+	}
+      
+      if (!(f = fopen(file, "r")))
+	{   
+	  if (errno == ENOENT && nofile_ok)
+	    return 1; /* No conffile, all done. */
+	  else
+	    {
+	      char *str = _("cannot read %s: %s");
+	      if (hard_opt != 0)
+		{
+		  my_syslog(LOG_ERR, str, file, strerror(errno));
+		  return 0;
+		}
+	      else
+		die(str, file, EC_FILE);
+	    }
+	} 
+    }
+  
+  read_file(file, f, hard_opt);
+  return 1;
+}
+
+static int file_filter(const struct dirent *ent)
+{
+  size_t lenfile = strlen(ent->d_name);
+
+  /* ignore emacs backups and dotfiles */
+
+  if (lenfile == 0 || 
+      ent->d_name[lenfile - 1] == '~' ||
+      (ent->d_name[0] == '#' && ent->d_name[lenfile - 1] == '#') ||
+      ent->d_name[0] == '.')
+    return 0;
+
+  return 1;
+}
+/* expand any name which is a directory */
+struct hostsfile *expand_filelist(struct hostsfile *list)
+{
+  unsigned int i;
+  int entcnt, n;
+  struct hostsfile *ah, *last, *next, **up;
+  struct dirent **namelist;
+
+  /* find largest used index */
+  for (i = SRC_AH, ah = list; ah; ah = ah->next)
+    {
+      last = ah;
+      
+      if (i <= ah->index)
+	i = ah->index + 1;
+
+      if (ah->flags & AH_DIR)
+	ah->flags |= AH_INACTIVE;
+      else
+	ah->flags &= ~AH_INACTIVE;
+    }
+
+  for (ah = list; ah; ah = ah->next)
+    if (!(ah->flags & AH_INACTIVE))
+      {
+	struct stat buf;
+	if (stat(ah->fname, &buf) != -1 && S_ISDIR(buf.st_mode))
+	  {
+	    struct dirent *ent;
+	    
+	    /* don't read this as a file */
+	    ah->flags |= AH_INACTIVE;
+	    
+	    entcnt = scandir(ah->fname, &namelist, file_filter, alphasort);
+	    if (entcnt < 0)
+	      my_syslog(LOG_ERR, _("cannot access directory %s: %s"), 
+			ah->fname, strerror(errno));
+	    else
+	      {
+		for (n = 0; n < entcnt; n++)
+		  {
+		    ent = namelist[n];
+		    size_t lendir = strlen(ah->fname);
+		    size_t lenfile = strlen(ent->d_name);
+		    struct hostsfile *ah1;
+		    char *path;
+		    
+		    /* see if we have an existing record.
+		       dir is ah->fname 
+		       file is ent->d_name
+		       path to match is ah1->fname */
+		    
+		    for (up = &list, ah1 = list; ah1; ah1 = next)
+		      {
+			next = ah1->next;
+
+			if (lendir < strlen(ah1->fname) &&
+			    strstr(ah1->fname, ah->fname) == ah1->fname &&
+			    ah1->fname[lendir] == '/' &&
+			    strcmp(ah1->fname + lendir + 1, ent->d_name) == 0)
+			  {
+			    ah1->flags &= ~AH_INACTIVE;
+			    /* If found, remove from list to re-insert at the end.
+			       Unless it's already at the end. */
+			    if (last != ah1)
+			      *up = next;
+			    break;
+			  }
+
+			up = &ah1->next;
+		      }
+		    
+		    /* make new record */
+		    if (!ah1)
+		      {
+			if (!(ah1 = whine_malloc(sizeof(struct hostsfile))))
+			  continue;
+			
+			if (!(path = whine_malloc(lendir + lenfile + 2)))
+			  {
+			    free(ah1);
+			    continue;
+			  }
+		      	
+			strcpy(path, ah->fname);
+			strcat(path, "/");
+			strcat(path, ent->d_name);
+			ah1->fname = path;
+			ah1->index = i++;
+			ah1->flags = AH_DIR;
+		      }
+
+		    /* Edge case, may be the last in the list anyway */
+		    if (last != ah1)
+		      last->next = ah1;
+		    ah1->next = NULL;
+		    last = ah1;
+		    
+		    /* inactivate record if not regular file */
+		    if ((ah1->flags & AH_DIR) && stat(ah1->fname, &buf) != -1 && !S_ISREG(buf.st_mode))
+		      ah1->flags |= AH_INACTIVE; 
+		    
+		  }
+	      }
+	    free(namelist);
+	  }
+      }
+  
+  return list;
+}
+
+void read_servers_file(void)
+{
+  FILE *f;
+
+  if (!(f = fopen(daemon->servers_file, "r")))
+    {
+       my_syslog(LOG_ERR, _("cannot read %s: %s"), daemon->servers_file, strerror(errno));
+       return;
+    }
+  
+  mark_servers(SERV_FROM_FILE);
+  read_file(daemon->servers_file, f, LOPT_REV_SERV);
+  cleanup_servers();
+  check_servers(0);
+}
+ 
+
+#ifdef HAVE_DHCP
+static void clear_dynamic_conf(void)
+{
+  struct dhcp_config *configs, *cp, **up;
+  
+  /* remove existing... */
+  for (up = &daemon->dhcp_conf, configs = daemon->dhcp_conf; configs; configs = cp)
+    {
+      cp = configs->next;
+      
+      if (configs->flags & CONFIG_BANK)
+	{
+	  *up = cp;
+	  dhcp_config_free(configs);
+	}
+      else
+	up = &configs->next;
+    }
+}
+
+static void clear_dynamic_opt(void)
+{
+  struct dhcp_opt *opts, *cp, **up;
+
+  for (up = &daemon->dhcp_opts, opts = daemon->dhcp_opts; opts; opts = cp)
+    {
+      cp = opts->next;
+      
+      if (opts->flags & DHOPT_BANK)
+	{
+	  *up = cp;
+	  dhcp_opt_free(opts);
+	}
+      else
+	up = &opts->next;
+    }
+}
+
+void reread_dhcp(void)
+{
+   struct hostsfile *hf;
+
+   /* Do these even if there is no daemon->dhcp_hosts_file or
+      daemon->dhcp_opts_file since entries may have been created by the
+      inotify dynamic file reading system. */
+   
+   clear_dynamic_conf();
+   clear_dynamic_opt();
+
+   if (daemon->dhcp_hosts_file)
+    {
+      daemon->dhcp_hosts_file = expand_filelist(daemon->dhcp_hosts_file);
+      for (hf = daemon->dhcp_hosts_file; hf; hf = hf->next)
+	if (!(hf->flags & AH_INACTIVE))
+	  {
+	    if (one_file(hf->fname, LOPT_BANK))  
+	      my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname);
+	  }
+    }
+
+  if (daemon->dhcp_opts_file)
+    {
+      daemon->dhcp_opts_file = expand_filelist(daemon->dhcp_opts_file);
+      for (hf = daemon->dhcp_opts_file; hf; hf = hf->next)
+	if (!(hf->flags & AH_INACTIVE))
+	  {
+	    if (one_file(hf->fname, LOPT_OPTS))  
+	      my_syslog(MS_DHCP | LOG_INFO, _("read %s"), hf->fname);
+	  }
+    }
+
+#  ifdef HAVE_INOTIFY
+  /* Setup notify and read pre-existing files. */
+  set_dynamic_inotify(AH_DHCP_HST | AH_DHCP_OPT, 0, NULL, 0);
+#  endif
+}
+#endif
+
+void read_opts(int argc, char **argv, char *compile_opts)
+{
+  size_t argbuf_size = MAXDNAME;
+  char *argbuf = opt_malloc(argbuf_size);
+  char *buff = opt_malloc(MAXDNAME);
+  int option, testmode = 0;
+  char *arg, *conffile = NULL;
+      
+  opterr = 0;
+
+  daemon = opt_malloc(sizeof(struct daemon));
+  memset(daemon, 0, sizeof(struct daemon));
+  daemon->namebuff = buff;
+  daemon->addrbuff = safe_malloc(ADDRSTRLEN);
+  
+  /* Set defaults - everything else is zero or NULL */
+  daemon->cachesize = CACHESIZ;
+  daemon->ftabsize = FTABSIZ;
+  daemon->port = NAMESERVER_PORT;
+  daemon->dhcp_client_port = DHCP_CLIENT_PORT;
+  daemon->dhcp_server_port = DHCP_SERVER_PORT;
+  daemon->default_resolv.is_default = 1;
+  daemon->default_resolv.name = RESOLVFILE;
+  daemon->resolv_files = &daemon->default_resolv;
+  daemon->username = CHUSER;
+  daemon->runfile =  RUNFILE;
+  daemon->dhcp_max = MAXLEASES;
+  daemon->tftp_max = TFTP_MAX_CONNECTIONS;
+  daemon->edns_pktsz = EDNS_PKTSZ;
+  daemon->log_fac = -1;
+  daemon->auth_ttl = AUTH_TTL; 
+  daemon->soa_refresh = SOA_REFRESH;
+  daemon->soa_retry = SOA_RETRY;
+  daemon->soa_expiry = SOA_EXPIRY;
+  
+#ifndef NO_ID
+  add_txt("version.bind", "dnsmasq-" VERSION, 0 );
+  add_txt("authors.bind", "Simon Kelley", 0);
+  add_txt("copyright.bind", COPYRIGHT, 0);
+  add_txt("cachesize.bind", NULL, TXT_STAT_CACHESIZE);
+  add_txt("insertions.bind", NULL, TXT_STAT_INSERTS);
+  add_txt("evictions.bind", NULL, TXT_STAT_EVICTIONS);
+  add_txt("misses.bind", NULL, TXT_STAT_MISSES);
+  add_txt("hits.bind", NULL, TXT_STAT_HITS);
+#ifdef HAVE_AUTH
+  add_txt("auth.bind", NULL, TXT_STAT_AUTH);
+#endif
+  add_txt("servers.bind", NULL, TXT_STAT_SERVERS);
+#endif
+
+  while (1) 
+    {
+#ifdef HAVE_GETOPT_LONG
+      option = getopt_long(argc, argv, OPTSTRING, opts, NULL);
+#else
+      option = getopt(argc, argv, OPTSTRING);
+#endif
+      
+      if (option == -1)
+	{
+	  for (; optind < argc; optind++)
+	    {
+	      unsigned char *c = (unsigned char *)argv[optind];
+	      for (; *c != 0; c++)
+		if (!isspace(*c))
+		  die(_("junk found in command line"), NULL, EC_BADCONF);
+	    }
+	  break;
+	}
+
+      /* Copy optarg so that argv doesn't get changed */
+      if (optarg)
+	{
+	  if (strlen(optarg) >= argbuf_size)
+	    {
+	      free(argbuf);
+	      argbuf_size = strlen(optarg) + 1;
+	      argbuf = opt_malloc(argbuf_size);
+	    }
+	  safe_strncpy(argbuf, optarg, argbuf_size);
+	  arg = argbuf;
+	}
+      else
+	arg = NULL;
+      
+      /* command-line only stuff */
+      if (option == LOPT_TEST)
+	testmode = 1;
+      else if (option == 'w')
+	{
+#ifdef HAVE_DHCP
+	  if (argc == 3 && strcmp(argv[2], "dhcp") == 0)
+	    display_opts();
+#ifdef HAVE_DHCP6
+	  else if (argc == 3 && strcmp(argv[2], "dhcp6") == 0)
+	    display_opts6();
+#endif
+	  else
+#endif
+	    do_usage();
+
+	  exit(0);
+	}
+      else if (option == 'v')
+	{
+	  printf(_("Dnsmasq version %s  %s\n"), VERSION, COPYRIGHT);
+	  printf(_("Compile time options: %s\n\n"), compile_opts); 
+	  printf(_("This software comes with ABSOLUTELY NO WARRANTY.\n"));
+	  printf(_("Dnsmasq is free software, and you are welcome to redistribute it\n"));
+	  printf(_("under the terms of the GNU General Public License, version 2 or 3.\n"));
+          exit(0);
+        }
+      else if (option == 'C')
+	{
+          if (!conffile)
+	    conffile = opt_string_alloc(arg);
+	  else
+	    {
+	      char *extra = opt_string_alloc(arg);
+	      one_file(extra, 0);
+	      free(extra);
+	    }
+	}
+      else
+	{
+#ifdef HAVE_GETOPT_LONG
+	  if (!one_opt(option, arg, daemon->namebuff, _("try --help"), 1, 0))
+#else 
+	    if (!one_opt(option, arg, daemon->namebuff, _("try -w"), 1, 0)) 
+#endif  
+	    die(_("bad command line options: %s"), daemon->namebuff, EC_BADCONF);
+	}
+    }
+
+  free(argbuf);
+
+  if (conffile)
+    {
+      one_file(conffile, 0);
+      free(conffile);
+    }
+  else
+    one_file(CONFFILE, '7');
+
+  /* port might not be known when the address is parsed - fill in here */
+  if (daemon->servers)
+    {
+      struct server *tmp;
+      for (tmp = daemon->servers; tmp; tmp = tmp->next)
+	if (!(tmp->flags & SERV_HAS_SOURCE))
+	  {
+	    if (tmp->source_addr.sa.sa_family == AF_INET)
+	      tmp->source_addr.in.sin_port = htons(daemon->query_port);
+	    else if (tmp->source_addr.sa.sa_family == AF_INET6)
+	      tmp->source_addr.in6.sin6_port = htons(daemon->query_port);
+	  }
+    } 
+  
+  if (daemon->host_records)
+    {
+      struct host_record *hr;
+      
+      for (hr = daemon->host_records; hr; hr = hr->next)
+	if (hr->ttl == -1)
+	  hr->ttl = daemon->local_ttl;
+    }
+
+  if (daemon->cnames)
+    {
+      struct cname *cn, *cn2, *cn3;
+
+#define NOLOOP 1
+#define TESTLOOP 2      
+
+      /* Fill in TTL for CNAMES now we have local_ttl.
+	 Also prepare to do loop detection. */
+      for (cn = daemon->cnames; cn; cn = cn->next)
+	{
+	  if (cn->ttl == -1)
+	    cn->ttl = daemon->local_ttl;
+	  cn->flag = 0;
+	  cn->targetp = NULL;
+	  for (cn2 = daemon->cnames; cn2; cn2 = cn2->next)
+	    if (hostname_isequal(cn->target, cn2->alias))
+	      {
+		cn->targetp = cn2;
+		break;
+	      }
+	}
+      
+      /* Find any CNAME loops.*/
+      for (cn = daemon->cnames; cn; cn = cn->next)
+	{
+	  for (cn2 = cn->targetp; cn2; cn2 = cn2->targetp)
+	    {
+	      if (cn2->flag == NOLOOP)
+		break;
+	      
+	      if (cn2->flag == TESTLOOP)
+		die(_("CNAME loop involving %s"), cn->alias, EC_BADCONF);
+	      
+	      cn2->flag = TESTLOOP;
+	    }
+	  
+	  for (cn3 = cn->targetp; cn3 != cn2; cn3 = cn3->targetp)
+	    cn3->flag = NOLOOP;
+	}
+    }
+
+  if (daemon->if_addrs)
+    {  
+      struct iname *tmp;
+      for(tmp = daemon->if_addrs; tmp; tmp = tmp->next)
+	if (tmp->addr.sa.sa_family == AF_INET)
+	  tmp->addr.in.sin_port = htons(daemon->port);
+	else if (tmp->addr.sa.sa_family == AF_INET6)
+	  tmp->addr.in6.sin6_port = htons(daemon->port);
+    }
+	
+  /* create default, if not specified */
+  if (daemon->authserver && !daemon->hostmaster)
+    {
+      strcpy(buff, "hostmaster.");
+      strcat(buff, daemon->authserver);
+      daemon->hostmaster = opt_string_alloc(buff);
+    }
+
+  if (!daemon->dhcp_pxe_vendors)
+    {
+      daemon->dhcp_pxe_vendors = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+      daemon->dhcp_pxe_vendors->data = opt_string_alloc(DHCP_PXE_DEF_VENDOR);
+      daemon->dhcp_pxe_vendors->next = NULL;
+    }
+  
+  /* only one of these need be specified: the other defaults to the host-name */
+  if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget)
+    {
+      struct mx_srv_record *mx;
+      
+      if (gethostname(buff, MAXDNAME) == -1)
+	die(_("cannot get host-name: %s"), NULL, EC_MISC);
+      
+      for (mx = daemon->mxnames; mx; mx = mx->next)
+	if (!mx->issrv && hostname_isequal(mx->name, buff))
+	  break;
+      
+      if ((daemon->mxtarget || option_bool(OPT_LOCALMX)) && !mx)
+	{
+	  mx = opt_malloc(sizeof(struct mx_srv_record));
+	  mx->next = daemon->mxnames;
+	  mx->issrv = 0;
+	  mx->target = NULL;
+	  mx->name = opt_string_alloc(buff);
+	  daemon->mxnames = mx;
+	}
+      
+      if (!daemon->mxtarget)
+	daemon->mxtarget = opt_string_alloc(buff);
+
+      for (mx = daemon->mxnames; mx; mx = mx->next)
+	if (!mx->issrv && !mx->target)
+	  mx->target = daemon->mxtarget;
+    }
+
+  if (!option_bool(OPT_NO_RESOLV) &&
+      daemon->resolv_files && 
+      daemon->resolv_files->next && 
+      option_bool(OPT_NO_POLL))
+    die(_("only one resolv.conf file allowed in no-poll mode."), NULL, EC_BADCONF);
+  
+  if (option_bool(OPT_RESOLV_DOMAIN))
+    {
+      char *line;
+      FILE *f;
+
+      if (option_bool(OPT_NO_RESOLV) ||
+	  !daemon->resolv_files || 
+	  (daemon->resolv_files)->next)
+	die(_("must have exactly one resolv.conf to read domain from."), NULL, EC_BADCONF);
+      
+      if (!(f = fopen((daemon->resolv_files)->name, "r")))
+	die(_("failed to read %s: %s"), (daemon->resolv_files)->name, EC_FILE);
+      
+      while ((line = fgets(buff, MAXDNAME, f)))
+	{
+	  char *token = strtok(line, " \t\n\r");
+	  
+	  if (!token || strcmp(token, "search") != 0)
+	    continue;
+	  
+	  if ((token = strtok(NULL, " \t\n\r")) &&  
+	      (daemon->domain_suffix = canonicalise_opt(token)))
+	    break;
+	}
+
+      fclose(f);
+
+      if (!daemon->domain_suffix)
+	die(_("no search directive found in %s"), (daemon->resolv_files)->name, EC_MISC);
+    }
+
+  if (daemon->domain_suffix)
+    {
+       /* add domain for any srv record without one. */
+      struct mx_srv_record *srv;
+      
+      for (srv = daemon->mxnames; srv; srv = srv->next)
+	if (srv->issrv &&
+	    strchr(srv->name, '.') && 
+	    strchr(srv->name, '.') == strrchr(srv->name, '.'))
+	  {
+	    strcpy(buff, srv->name);
+	    strcat(buff, ".");
+	    strcat(buff, daemon->domain_suffix);
+	    free(srv->name);
+	    srv->name = opt_string_alloc(buff);
+	  }
+    }
+  else if (option_bool(OPT_DHCP_FQDN))
+    die(_("there must be a default domain when --dhcp-fqdn is set"), NULL, EC_BADCONF);
+
+  /* If there's access-control config, then ignore --local-service, it's intended
+     as a system default to keep otherwise unconfigured installations safe. */
+  if (daemon->if_names || daemon->if_except || daemon->if_addrs || daemon->authserver)
+    reset_option_bool(OPT_LOCAL_SERVICE); 
+
+  if (testmode)
+    {
+      fprintf(stderr, "dnsmasq: %s.\n", _("syntax check OK"));
+      exit(0);
+    }
+}  
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c b/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c
new file mode 100755
index 0000000..da6f73c
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/outpacket.c
@@ -0,0 +1,118 @@
+/* dnsmasq is Copyright (c) 2000-2021 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
+
+static size_t outpacket_counter;
+
+void end_opt6(int container)
+{
+   void *p = daemon->outpacket.iov_base + container + 2;
+   u16 len = outpacket_counter - container - 4 ;
+   
+   PUTSHORT(len, p);
+}
+
+void reset_counter(void)
+{
+  /* Clear out buffer when starting from beginning */
+  if (daemon->outpacket.iov_base)
+    memset(daemon->outpacket.iov_base, 0, daemon->outpacket.iov_len);
+ 
+  save_counter(0);
+}
+
+int save_counter(int newval)
+{
+  int ret = outpacket_counter;
+  
+  if (newval != -1)
+    outpacket_counter = newval;
+
+  return ret;
+}
+
+void *expand(size_t headroom)
+{
+  void *ret;
+
+  if (expand_buf(&daemon->outpacket, outpacket_counter + headroom))
+    {
+      ret = daemon->outpacket.iov_base + outpacket_counter;
+      outpacket_counter += headroom;
+      return ret;
+    }
+  
+  return NULL;
+}
+    
+int new_opt6(int opt)
+{
+  int ret = outpacket_counter;
+  void *p;
+
+  if ((p = expand(4)))
+    {
+      PUTSHORT(opt, p);
+      PUTSHORT(0, p);
+    }
+
+  return ret;
+}
+
+void *put_opt6(void *data, size_t len)
+{
+  void *p;
+
+  if ((p = expand(len)) && data)
+    memcpy(p, data, len);   
+  
+  return p;
+}
+  
+void put_opt6_long(unsigned int val)
+{
+  void *p;
+  
+  if ((p = expand(4)))  
+    PUTLONG(val, p);
+}
+
+void put_opt6_short(unsigned int val)
+{
+  void *p;
+
+  if ((p = expand(2)))
+    PUTSHORT(val, p);   
+}
+
+void put_opt6_char(unsigned int val)
+{
+  unsigned char *p;
+
+  if ((p = expand(1)))
+    *p = val;   
+}
+
+void put_opt6_string(char *s)
+{
+  put_opt6(s, strlen(s));
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c b/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c
new file mode 100755
index 0000000..03e23b9
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/pattern.c
@@ -0,0 +1,386 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_CONNTRACK
+
+#define LOG(...) \
+  do { \
+    my_syslog(LOG_DEBUG, __VA_ARGS__); \
+  } while (0)
+
+#define ASSERT(condition) \
+  do { \
+    if (!(condition)) \
+      my_syslog(LOG_ERR, _("[pattern.c:%d] Assertion failure: %s"), __LINE__, #condition); \
+  } while (0)
+
+/**
+ * Determines whether a given string value matches against a glob pattern
+ * which may contain zero-or-more-character wildcards denoted by '*'.
+ *
+ * Based on "Glob Matching Can Be Simple And Fast Too" by Russ Cox,
+ * See https://research.swtch.com/glob
+ *
+ * @param      value                A string value.
+ * @param      num_value_bytes      The number of bytes of the string value.
+ * @param      pattern              A glob pattern.
+ * @param      num_pattern_bytes    The number of bytes of the glob pattern.
+ *
+ * @return 1                        If the provided value matches against the glob pattern.
+ * @return 0                        Otherwise.
+ */
+static int is_string_matching_glob_pattern(
+  const char *value,
+  size_t num_value_bytes,
+  const char *pattern,
+  size_t num_pattern_bytes)
+{
+  ASSERT(value);
+  ASSERT(pattern);
+  
+  size_t value_index = 0;
+  size_t next_value_index = 0;
+  size_t pattern_index = 0;
+  size_t next_pattern_index = 0;
+  while (value_index < num_value_bytes || pattern_index < num_pattern_bytes)
+    {
+      if (pattern_index < num_pattern_bytes)
+	{
+	  char pattern_character = pattern[pattern_index];
+	  if ('a' <= pattern_character && pattern_character <= 'z')
+	    pattern_character -= 'a' - 'A';
+	  if (pattern_character == '*')
+	    {
+	      /* zero-or-more-character wildcard */
+	      /* Try to match at value_index, otherwise restart at value_index + 1 next. */
+	      next_pattern_index = pattern_index;
+	      pattern_index++;
+	      if (value_index < num_value_bytes)
+		next_value_index = value_index + 1;
+	      else
+		next_value_index = 0;
+	      continue;
+	    }
+	  else
+	    {
+	      /* ordinary character */
+	      if (value_index < num_value_bytes)
+	        {
+		  char value_character = value[value_index];
+		  if ('a' <= value_character && value_character <= 'z')
+		    value_character -= 'a' - 'A';
+		  if (value_character == pattern_character)
+		    {
+		      pattern_index++;
+		      value_index++;
+		      continue;
+		    }
+		}
+	    }
+	}
+      if (next_value_index)
+	{
+	  pattern_index = next_pattern_index;
+	  value_index = next_value_index;
+	  continue;
+	}
+      return 0;
+    }
+  return 1;
+}
+
+/**
+ * Determines whether a given string value represents a valid DNS name.
+ *
+ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
+ *   delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
+ *   ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
+ *
+ * - A valid name must be fully qualified, i.e., consist of at least two labels.
+ *   The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
+ *
+ * - Examples:
+ *   Valid: "example.com"
+ *   Invalid: "ipcamera", "ipcamera.local", "8.8.8.8"
+ *
+ * @param      value                A string value.
+ *
+ * @return 1                        If the provided string value is a valid DNS name.
+ * @return 0                        Otherwise.
+ */
+int is_valid_dns_name(const char *value)
+{
+  ASSERT(value);
+  
+  size_t num_bytes = 0;
+  size_t num_labels = 0;
+  const char *label = NULL;
+  int is_label_numeric = 1;
+  for (const char *c = value;; c++)
+    {
+      if (*c &&
+	  *c != '-' && *c != '.' &&
+	  (*c < '0' || *c > '9') &&
+	  (*c < 'A' || *c > 'Z') &&
+	  (*c < 'a' || *c > 'z'))
+	{
+	  LOG(_("Invalid DNS name: Invalid character %c."), *c);
+	  return 0;
+	}
+      if (*c)
+	num_bytes++;
+      if (!label)
+	{
+	  if (!*c || *c == '.')
+	    {
+	      LOG(_("Invalid DNS name: Empty label."));
+	      return 0;
+	    }
+	  if (*c == '-')
+	    {
+	      LOG(_("Invalid DNS name: Label starts with hyphen."));
+	      return 0;
+	    }
+	  label = c;
+	}
+      if (*c && *c != '.')
+	{
+	  if (*c < '0' || *c > '9')
+	    is_label_numeric = 0;
+	}
+      else
+	{
+	  if (c[-1] == '-')
+	    {
+	      LOG(_("Invalid DNS name: Label ends with hyphen."));
+	      return 0;
+	    }
+	  size_t num_label_bytes = (size_t) (c - label);
+	  if (num_label_bytes > 63)
+	    {
+	      LOG(_("Invalid DNS name: Label is too long (%zu)."), num_label_bytes);
+	      return 0;
+	    }
+	  num_labels++;
+	  if (!*c)
+	    {
+	      if (num_labels < 2)
+		{
+		  LOG(_("Invalid DNS name: Not enough labels (%zu)."), num_labels);
+		  return 0;
+		}
+	      if (is_label_numeric)
+		{
+		  LOG(_("Invalid DNS name: Final label is fully numeric."));
+		  return 0;
+		}
+	      if (num_label_bytes == 5 &&
+		  (label[0] == 'l' || label[0] == 'L') &&
+		  (label[1] == 'o' || label[1] == 'O') &&
+		  (label[2] == 'c' || label[2] == 'C') &&
+		  (label[3] == 'a' || label[3] == 'A') &&
+		  (label[4] == 'l' || label[4] == 'L'))
+		{
+		  LOG(_("Invalid DNS name: \"local\" pseudo-TLD."));
+		  return 0;
+		}
+	      if (num_bytes < 1 || num_bytes > 253)
+		{
+		  LOG(_("DNS name has invalid length (%zu)."), num_bytes);
+		  return 0;
+		}
+	      return 1;
+	    }
+	  label = NULL;
+	  is_label_numeric = 1;
+	}
+    }
+}
+
+/**
+ * Determines whether a given string value represents a valid DNS name pattern.
+ *
+ * - DNS names must adhere to RFC 1123: 1 to 253 characters in length, consisting of a sequence of labels
+ *   delimited by dots ("."). Each label must be 1 to 63 characters in length, contain only
+ *   ASCII letters ("a"-"Z"), digits ("0"-"9"), or hyphens ("-") and must not start or end with a hyphen.
+ *
+ * - Patterns follow the syntax of DNS names, but additionally allow the wildcard character "*" to be used up to
+ *   twice per label to match 0 or more characters within that label. Note that the wildcard never matches a dot
+ *   (e.g., "*.example.com" matches "api.example.com" but not "api.us.example.com").
+ *
+ * - A valid name or pattern must be fully qualified, i.e., consist of at least two labels.
+ *   The final label must not be fully numeric, and must not be the "local" pseudo-TLD.
+ *   A pattern must end with at least two literal (non-wildcard) labels.
+ *
+ * - Examples:
+ *   Valid: "example.com", "*.example.com", "video*.example.com", "api*.*.example.com", "*-prod-*.example.com"
+ *   Invalid: "ipcamera", "ipcamera.local", "*", "*.com", "8.8.8.8"
+ *
+ * @param      value                A string value.
+ *
+ * @return 1                        If the provided string value is a valid DNS name pattern.
+ * @return 0                        Otherwise.
+ */
+int is_valid_dns_name_pattern(const char *value)
+{
+  ASSERT(value);
+  
+  size_t num_bytes = 0;
+  size_t num_labels = 0;
+  const char *label = NULL;
+  int is_label_numeric = 1;
+  size_t num_wildcards = 0;
+  int previous_label_has_wildcard = 1;
+  for (const char *c = value;; c++)
+    {
+      if (*c &&
+	  *c != '*' && /* Wildcard. */
+	  *c != '-' && *c != '.' &&
+	  (*c < '0' || *c > '9') &&
+	  (*c < 'A' || *c > 'Z') &&
+	  (*c < 'a' || *c > 'z'))
+	{
+	  LOG(_("Invalid DNS name pattern: Invalid character %c."), *c);
+	  return 0;
+	}
+      if (*c && *c != '*')
+	num_bytes++;
+      if (!label)
+	{
+	  if (!*c || *c == '.')
+	    {
+	      LOG(_("Invalid DNS name pattern: Empty label."));
+	      return 0;
+	    }
+	  if (*c == '-')
+	    {
+	      LOG(_("Invalid DNS name pattern: Label starts with hyphen."));
+	      return 0;
+	    }
+	  label = c;
+	}
+      if (*c && *c != '.')
+	{
+	  if (*c < '0' || *c > '9')
+	    is_label_numeric = 0;
+	  if (*c == '*')
+	    {
+	      if (num_wildcards >= 2)
+		{
+		  LOG(_("Invalid DNS name pattern: Wildcard character used more than twice per label."));
+		  return 0;
+		}
+	      num_wildcards++;
+	    }
+	}
+      else
+	{
+	  if (c[-1] == '-')
+	    {
+	      LOG(_("Invalid DNS name pattern: Label ends with hyphen."));
+	      return 0;
+	    }
+	  size_t num_label_bytes = (size_t) (c - label) - num_wildcards;
+	  if (num_label_bytes > 63)
+	    {
+	      LOG(_("Invalid DNS name pattern: Label is too long (%zu)."), num_label_bytes);
+	      return 0;
+	    }
+	  num_labels++;
+	  if (!*c)
+	    {
+	      if (num_labels < 2)
+		{
+		  LOG(_("Invalid DNS name pattern: Not enough labels (%zu)."), num_labels);
+		  return 0;
+		}
+	      if (num_wildcards != 0 || previous_label_has_wildcard)
+		{
+		  LOG(_("Invalid DNS name pattern: Wildcard within final two labels."));
+		  return 0;
+		}
+	      if (is_label_numeric)
+		{
+		  LOG(_("Invalid DNS name pattern: Final label is fully numeric."));
+		  return 0;
+		}
+	      if (num_label_bytes == 5 &&
+		  (label[0] == 'l' || label[0] == 'L') &&
+		  (label[1] == 'o' || label[1] == 'O') &&
+		  (label[2] == 'c' || label[2] == 'C') &&
+		  (label[3] == 'a' || label[3] == 'A') &&
+		  (label[4] == 'l' || label[4] == 'L'))
+		{
+		  LOG(_("Invalid DNS name pattern: \"local\" pseudo-TLD."));
+		  return 0;
+		}
+	      if (num_bytes < 1 || num_bytes > 253)
+		{
+		  LOG(_("DNS name pattern has invalid length after removing wildcards (%zu)."), num_bytes);
+		  return 0;
+		}
+	      return 1;
+	    }
+	    label = NULL;
+	    is_label_numeric = 1;
+	    previous_label_has_wildcard = num_wildcards != 0;
+	    num_wildcards = 0;
+	  }
+    }
+}
+
+/**
+ * Determines whether a given DNS name matches against a DNS name pattern.
+ *
+ * @param      name                 A valid DNS name.
+ * @param      pattern              A valid DNS name pattern.
+ *
+ * @return 1                        If the provided DNS name matches against the DNS name pattern.
+ * @return 0                        Otherwise.
+ */
+int is_dns_name_matching_pattern(const char *name, const char *pattern)
+{
+  ASSERT(name);
+  ASSERT(is_valid_dns_name(name));
+  ASSERT(pattern);
+  ASSERT(is_valid_dns_name_pattern(pattern));
+  
+  const char *n = name;
+  const char *p = pattern;
+  
+  do {
+    const char *name_label = n;
+    while (*n && *n != '.')
+      n++;
+    const char *pattern_label = p;
+    while (*p && *p != '.')
+      p++;
+    if (!is_string_matching_glob_pattern(
+        name_label, (size_t) (n - name_label),
+        pattern_label, (size_t) (p - pattern_label)))
+      break;
+    if (*n)
+      n++;
+    if (*p)
+      p++;
+  } while (*n && *p);
+  
+  return !*n && !*p;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c b/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c
new file mode 100755
index 0000000..f414690
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/poll.c
@@ -0,0 +1,125 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+/* Wrapper for poll(). Allocates and extends array of struct pollfds,
+   keeps them in fd order so that we can set and test conditions on
+   fd using a simple but efficient binary chop. */
+
+/* poll_reset()
+   poll_listen(fd, event)
+   .
+   .
+   poll_listen(fd, event);
+
+   hits = do_poll(timeout);
+
+   if (poll_check(fd, event)
+    .
+    .
+
+   if (poll_check(fd, event)
+    .
+    .
+
+    event is OR of POLLIN, POLLOUT, POLLERR, etc
+*/
+
+static struct pollfd *pollfds = NULL;
+static nfds_t nfds, arrsize = 0;
+
+/* Binary search. Returns either the pollfd with fd, or
+   if the fd doesn't match, or return equals nfds, the entry
+   to the left of which a new record should be inserted. */
+static nfds_t fd_search(int fd)
+{
+  nfds_t left, right, mid;
+  
+  if ((right = nfds) == 0)
+    return 0;
+  
+  left = 0;
+  
+  while (1)
+    {
+      if (right == left + 1)
+	return (pollfds[left].fd >= fd) ? left : right;
+      
+      mid = (left + right)/2;
+      
+      if (pollfds[mid].fd > fd)
+	right = mid;
+      else 
+	left = mid;
+    }
+}
+
+void poll_reset(void)
+{
+  nfds = 0;
+}
+
+int do_poll(int timeout)
+{
+  return poll(pollfds, nfds, timeout);
+}
+
+int poll_check(int fd, short event)
+{
+  nfds_t i = fd_search(fd);
+  
+  if (i < nfds && pollfds[i].fd == fd)
+    return pollfds[i].revents & event;
+
+  return 0;
+}
+
+void poll_listen(int fd, short event)
+{
+   nfds_t i = fd_search(fd);
+  
+   if (i < nfds && pollfds[i].fd == fd)
+     pollfds[i].events |= event;
+   else
+     {
+       if (arrsize != nfds)
+	 memmove(&pollfds[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+       else
+	 {
+	   /* Array too small, extend. */
+	   struct pollfd *new;
+
+	   arrsize = (arrsize == 0) ? 64 : arrsize * 2;
+
+	   if (!(new = whine_malloc(arrsize * sizeof(struct pollfd))))
+	     return;
+
+	   if (pollfds)
+	     {
+	       memcpy(new, pollfds, i * sizeof(struct pollfd));
+	       memcpy(&new[i+1], &pollfds[i], (nfds - i) * sizeof(struct pollfd));
+	       free(pollfds);
+	     }
+	   
+	   pollfds = new;
+	 }
+       
+       pollfds[i].fd = fd;
+       pollfds[i].events = event;
+       nfds++;
+     }
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h b/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h
new file mode 100755
index 0000000..8314e8a
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/radv-protocol.h
@@ -0,0 +1,55 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+#define ALL_NODES                 "FF02::1"
+#define ALL_ROUTERS               "FF02::2"
+
+struct ping_packet {
+  u8 type, code;
+  u16 checksum;
+  u16 identifier;
+  u16 sequence_no;
+};
+
+struct ra_packet {
+  u8 type, code;
+  u16 checksum;
+  u8 hop_limit, flags;
+  u16 lifetime;
+  u32 reachable_time;
+  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;
+  struct in6_addr prefix;
+};
+
+#define ICMP6_OPT_SOURCE_MAC   1
+#define ICMP6_OPT_PREFIX       3
+#define ICMP6_OPT_MTU          5
+#define ICMP6_OPT_ADV_INTERVAL 7
+#define ICMP6_OPT_RT_INFO     24
+#define ICMP6_OPT_RDNSS       25
+#define ICMP6_OPT_DNSSL       31
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c b/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c
new file mode 100755
index 0000000..3255904
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/radv.c
@@ -0,0 +1,1008 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+
+/* NB. This code may be called during a DHCPv4 or transaction which is in ping-wait
+   It therefore cannot use any DHCP buffer resources except outpacket, which is
+   not used by DHCPv4 code. This code may also be called when DHCP 4 or 6 isn't
+   active, so we ensure that outpacket is allocated here too */
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_DHCP6
+
+#include <netinet/icmp6.h>
+
+struct ra_param {
+  time_t now;
+  int ind, managed, other, first, adv_router;
+  char *if_name;
+  struct dhcp_netid *tags;
+  struct in6_addr link_local, link_global, ula;
+  unsigned int glob_pref_time, link_pref_time, ula_pref_time, adv_interval, prio;
+  struct dhcp_context *found_context;
+};
+
+struct search_param {
+  time_t now; int iface;
+  char name[IF_NAMESIZE+1];
+};
+
+struct alias_param {
+  int iface;
+  struct dhcp_bridge *bridge;
+  int num_alias_ifs;
+  int max_alias_ifs;
+  int *alias_ifs;
+};
+
+static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest);
+static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest,
+                    int send_iface);
+static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+static int add_prefixes(struct in6_addr *local,  int prefix,
+			int scope, int if_index, int flags, 
+			unsigned int preferred, unsigned int valid, void *vparam);
+static int iface_search(struct in6_addr *local,  int prefix,
+			int scope, int if_index, int flags, 
+			int prefered, int valid, void *vparam);
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm);
+static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now);
+static unsigned int calc_lifetime(struct ra_interface *ra);
+static unsigned int calc_interval(struct ra_interface *ra);
+static unsigned int calc_prio(struct ra_interface *ra);
+static struct ra_interface *find_iface_param(char *iface);
+
+static int hop_limit;
+
+void ra_init(time_t now)
+{
+  struct icmp6_filter filter;
+  int fd;
+#if defined(IPV6_TCLASS) && defined(IPTOS_CLASS_CS6)
+  int class = IPTOS_CLASS_CS6;
+#endif
+  int val = 255; /* radvd uses this value */
+  socklen_t len = sizeof(int);
+  struct dhcp_context *context;
+  
+  /* ensure this is around even if we're not doing DHCPv6 */
+  expand_buf(&daemon->outpacket, sizeof(struct dhcp_packet));
+ 
+  /* See if we're guessing SLAAC addresses, if so we need to receive ping replies */
+  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);
+  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)
+      setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &class, sizeof(class)) == -1 ||
+#endif
+      !fix_fd(fd) ||
+      !set_ipv6pktinfo(fd) ||
+      setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
+      setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
+      setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
+    die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
+  
+   daemon->icmp6fd = fd;
+   
+   if (daemon->doing_ra)
+     ra_start_unsolicited(now, NULL);
+}
+
+void ra_start_unsolicited(time_t now, struct dhcp_context *context)
+{   
+   /* init timers so that we do ra's for some/all soon. some ra_times will end up zeroed
+     if it's not appropriate to advertise those contexts.
+     This gets re-called on a netlink route-change to re-do the advertisement
+     and pick up new interfaces */
+  
+  if (context)
+    context->ra_short_period_start = context->ra_time = now;
+  else
+    for (context = daemon->dhcp6; context; context = context->next)
+      if (!(context->flags & CONTEXT_TEMPLATE))
+	{
+	  context->ra_time = now + (rand16()/13000); /* range 0 - 5 */
+	  /* re-do frequently for a minute or so, in case the first gets lost. */
+	  context->ra_short_period_start = now;
+	}
+}
+
+void icmp6_packet(time_t now)
+{
+  char interface[IF_NAMESIZE+1];
+  ssize_t sz; 
+  int if_index = 0;
+  struct cmsghdr *cmptr;
+  struct msghdr msg;
+  union {
+    struct cmsghdr align; /* this ensures alignment */
+    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+  } control_u;
+  struct sockaddr_in6 from;
+  unsigned char *packet;
+  struct iname *tmp;
+
+  /* Note: use outpacket for input buffer */
+  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->outpacket;
+  msg.msg_iovlen = 1;
+  
+  if ((sz = recv_dhcp_packet(daemon->icmp6fd, &msg)) == -1 || sz < 8)
+    return;
+   
+  packet = (unsigned char *)daemon->outpacket.iov_base;
+  
+  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;
+      }
+  
+  if (!indextoname(daemon->icmp6fd, if_index, interface))
+    return;
+    
+  if (!iface_check(AF_LOCAL, NULL, interface, NULL))
+    return;
+  
+  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+    if (tmp->name && wildcard_match(tmp->name, interface))
+      return;
+ 
+  if (packet[1] != 0)
+    return;
+  
+  if (packet[0] == ICMP6_ECHO_REPLY)
+    lease_ping_reply(&from.sin6_addr, packet, interface); 
+  else if (packet[0] == ND_ROUTER_SOLICIT)
+    {
+      char *mac = "";
+      struct dhcp_bridge *bridge, *alias;
+      
+      /* look for link-layer address option for logging */
+      if (sz >= 16 && packet[8] == ICMP6_OPT_SOURCE_MAC && (packet[9] * 8) + 8 <= sz)
+	{
+	  if ((packet[9] * 8 - 2) * 3 - 1 >= MAXDNAME) {
+	    return;
+	  }
+	  print_mac(daemon->namebuff, &packet[10], (packet[9] * 8) - 2);
+	  mac = daemon->namebuff;
+	}
+         
+      if (!option_bool(OPT_QUIET_RA))
+	my_syslog(MS_DHCP | LOG_INFO, "RTR-SOLICIT(%s) %s", interface, mac);
+
+      /* If the incoming interface is an alias of some other one (as
+         specified by the --bridge-interface option), send an RA using
+         the context of the aliased interface. */
+      for (bridge = daemon->bridges; bridge; bridge = bridge->next)
+        {
+          int bridge_index = if_nametoindex(bridge->iface);
+          if (bridge_index)
+	    {
+	      for (alias = bridge->alias; alias; alias = alias->next)
+		if (wildcard_matchn(alias->iface, interface, IF_NAMESIZE))
+		  {
+		    /* Send an RA on if_index with information from
+		       bridge_index. */
+		    send_ra_alias(now, bridge_index, bridge->iface, NULL, if_index);
+		    break;
+		  }
+	      if (alias)
+		break;
+	    }
+        }
+
+      /* If the incoming interface wasn't an alias, send an RA using
+	 the context of the incoming interface. */
+      if (!bridge)
+	/* source address may not be valid in solicit request. */
+	send_ra(now, if_index, interface, !IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr) ? &from.sin6_addr : NULL);
+    }
+}
+
+static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, int send_iface)
+{
+  struct ra_packet *ra;
+  struct ra_param parm;
+  struct sockaddr_in6 addr;
+  struct dhcp_context *context, *tmp,  **up;
+  struct dhcp_netid iface_id;
+  struct dhcp_opt *opt_cfg;
+  struct ra_interface *ra_param = find_iface_param(iface_name);
+  int done_dns = 0, old_prefix = 0, mtu = 0;
+  unsigned int min_pref_time;
+#ifdef HAVE_LINUX_NETWORK
+  FILE *f;
+#endif
+  
+  parm.ind = iface;
+  parm.managed = 0;
+  parm.other = 0;
+  parm.found_context = NULL;
+  parm.adv_router = 0;
+  parm.if_name = iface_name;
+  parm.first = 1;
+  parm.now = now;
+  parm.glob_pref_time = parm.link_pref_time = parm.ula_pref_time = 0;
+  parm.adv_interval = calc_interval(ra_param);
+  parm.prio = calc_prio(ra_param);
+  
+  reset_counter();
+  
+  if (!(ra = expand(sizeof(struct ra_packet))))
+    return;
+  
+  ra->type = ND_ROUTER_ADVERT;
+  ra->code = 0;
+  ra->hop_limit = hop_limit;
+  ra->flags = parm.prio;
+  ra->lifetime = htons(calc_lifetime(ra_param));
+  ra->reachable_time = 0;
+  ra->retrans_time = 0;
+
+  /* set tag with name == interface */
+  iface_id.net = iface_name;
+  iface_id.next = NULL;
+  parm.tags = &iface_id; 
+  
+  for (context = daemon->dhcp6; context; context = context->next)
+    {
+      context->flags &= ~CONTEXT_RA_DONE;
+      context->netid.next = &context->netid;
+    }
+
+  /* If no link-local address then we can't advertise since source address of
+     advertisement must be link local address: RFC 4861 para 6.1.2. */
+  if (!iface_enumerate(AF_INET6, &parm, add_prefixes) ||
+      parm.link_pref_time == 0)
+    return;
+
+  /* Find smallest preferred time within address classes,
+     to use as lifetime for options. This is a rather arbitrary choice. */
+  min_pref_time = 0xffffffff;
+  if (parm.glob_pref_time != 0 && parm.glob_pref_time < min_pref_time)
+    min_pref_time = parm.glob_pref_time;
+  
+  if (parm.ula_pref_time != 0 && parm.ula_pref_time < min_pref_time)
+    min_pref_time = parm.ula_pref_time;
+
+  if (parm.link_pref_time != 0 && parm.link_pref_time < min_pref_time)
+    min_pref_time = parm.link_pref_time;
+
+  /* Look for constructed contexts associated with addresses which have gone, 
+     and advertise them with preferred_time == 0  RFC 6204 4.3 L-13 */
+  for (up = &daemon->dhcp6, context = daemon->dhcp6; context; context = tmp)
+    {
+      tmp = context->next;
+
+      if (context->if_index == iface && (context->flags & CONTEXT_OLD))
+	{
+	  unsigned int old = difftime(now, context->address_lost_time);
+	  
+	  if (old > context->saved_valid)
+	    { 
+	      /* We've advertised this enough, time to go */
+	     
+	      /* If this context held the timeout, and there's another context in use
+		 transfer the timeout there. */
+	      if (context->ra_time != 0 && parm.found_context && parm.found_context->ra_time == 0)
+		new_timeout(parm.found_context, iface_name, now);
+	      
+	      *up = context->next;
+	      free(context);
+	    }
+	  else
+	    {
+	      struct prefix_opt *opt;
+	      struct in6_addr local = context->start6;
+	      int do_slaac = 0;
+
+	      old_prefix = 1;
+
+	      /* zero net part of address */
+	      setaddr6part(&local, addr6part(&local) & ~((context->prefix == 64) ? (u64)-1LL : (1LLU << (128 - context->prefix)) - 1LLU));
+	     
+	      
+	      if (context->flags & CONTEXT_RA)
+		{
+		  do_slaac = 1;
+		  if (context->flags & CONTEXT_DHCP)
+		    {
+		      parm.other = 1; 
+		      if (!(context->flags & CONTEXT_RA_STATELESS))
+			parm.managed = 1;
+		    }
+		}
+	      else
+		{
+		  /* don't do RA for non-ra-only unless --enable-ra is set */
+		  if (option_bool(OPT_RA))
+		    {
+		      parm.managed = 1;
+		      parm.other = 1;
+		    }
+		}
+
+	      if ((opt = expand(sizeof(struct prefix_opt))))
+		{
+		  opt->type = ICMP6_OPT_PREFIX;
+		  opt->len = 4;
+		  opt->prefix_len = context->prefix;
+		  /* autonomous only if we're not doing dhcp, set
+                     "on-link" unless "off-link" was specified */
+		  opt->flags = (do_slaac ? 0x40 : 0) |
+                    ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80);
+		  opt->valid_lifetime = htonl(context->saved_valid - old);
+		  opt->preferred_lifetime = htonl(0);
+		  opt->reserved = 0; 
+		  opt->prefix = local;
+		  
+		  inet_ntop(AF_INET6, &local, daemon->addrbuff, ADDRSTRLEN);
+		  if (!option_bool(OPT_QUIET_RA))
+		    my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s old prefix", iface_name, daemon->addrbuff); 		    
+		}
+	   
+	      up = &context->next;
+	    }
+	}
+      else
+	up = &context->next;
+    }
+    
+  /* If we're advertising only old prefixes, set router lifetime to zero. */
+  if (old_prefix && !parm.found_context)
+    ra->lifetime = htons(0);
+
+  /* No prefixes to advertise. */
+  if (!old_prefix && !parm.found_context)
+    return; 
+  
+  /* If we're sending router address instead of prefix in at least on prefix,
+     include the advertisement interval option. */
+  if (parm.adv_router)
+    {
+      put_opt6_char(ICMP6_OPT_ADV_INTERVAL);
+      put_opt6_char(1);
+      put_opt6_short(0);
+      /* interval value is in milliseconds */
+      put_opt6_long(1000 * calc_interval(find_iface_param(iface_name)));
+    }
+
+  /* Set the MTU from ra_param if any, an MTU of 0 mean automatic for linux, */
+  /* an MTU of -1 prevents the option from being sent. */
+  if (ra_param)
+    mtu = ra_param->mtu;
+#ifdef HAVE_LINUX_NETWORK
+  /* Note that IPv6 MTU is not necessarily the same as the IPv4 MTU
+     available from SIOCGIFMTU */
+  if (mtu == 0)
+    {
+      char *mtu_name = ra_param ? ra_param->mtu_name : NULL;
+      sprintf(daemon->namebuff, "/proc/sys/net/ipv6/conf/%s/mtu", mtu_name ? mtu_name : iface_name);
+      if ((f = fopen(daemon->namebuff, "r")))
+        {
+          if (fgets(daemon->namebuff, MAXDNAME, f))
+            mtu = atoi(daemon->namebuff);
+          fclose(f);
+        }
+    }
+#endif
+  if (mtu > 0)
+    {
+      put_opt6_char(ICMP6_OPT_MTU);
+      put_opt6_char(1);
+      put_opt6_short(0);
+      put_opt6_long(mtu);
+    }
+     
+  iface_enumerate(AF_LOCAL, &send_iface, add_lla);
+ 
+  /* RDNSS, RFC 6106, use relevant DHCP6 options */
+  (void)option_filter(parm.tags, NULL, daemon->dhcp_opts6);
+  
+  for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+    {
+      int i;
+      
+      /* netids match and not encapsulated? */
+      if (!(opt_cfg->flags & DHOPT_TAGOK))
+        continue;
+      
+      if (opt_cfg->opt == OPTION6_DNS_SERVER)
+        {
+	  struct in6_addr *a;
+	  int len;
+
+	  done_dns = 1;
+
+          if (opt_cfg->len == 0)
+	    continue;
+	  
+	  /* reduce len for any addresses we can't substitute */
+	  for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, i = 0; 
+	       i < opt_cfg->len; i += IN6ADDRSZ, a++)
+	    if ((IN6_IS_ADDR_UNSPECIFIED(a) && parm.glob_pref_time == 0) ||
+		(IN6_IS_ADDR_ULA_ZERO(a) && parm.ula_pref_time == 0) ||
+		(IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && parm.link_pref_time == 0))
+	      len -= IN6ADDRSZ;
+
+	  if (len != 0)
+	    {
+	      put_opt6_char(ICMP6_OPT_RDNSS);
+	      put_opt6_char((len/8) + 1);
+	      put_opt6_short(0);
+	      put_opt6_long(min_pref_time);
+	 
+	      for (a = (struct in6_addr *)opt_cfg->val, i = 0; i <  opt_cfg->len; i += IN6ADDRSZ, a++)
+		if (IN6_IS_ADDR_UNSPECIFIED(a))
+		  {
+		    if (parm.glob_pref_time != 0)
+		      put_opt6(&parm.link_global, IN6ADDRSZ);
+		  }
+		else if (IN6_IS_ADDR_ULA_ZERO(a))
+		  {
+		    if (parm.ula_pref_time != 0)
+		    put_opt6(&parm.ula, IN6ADDRSZ);
+		  }
+		else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+		  {
+		    if (parm.link_pref_time != 0)
+		      put_opt6(&parm.link_local, IN6ADDRSZ);
+		  }
+		else
+		  put_opt6(a, IN6ADDRSZ);
+	    }
+	}
+      
+      if (opt_cfg->opt == OPTION6_DOMAIN_SEARCH && opt_cfg->len != 0)
+	{
+	  int len = ((opt_cfg->len+7)/8);
+	  
+	  put_opt6_char(ICMP6_OPT_DNSSL);
+	  put_opt6_char(len + 1);
+	  put_opt6_short(0);
+	  put_opt6_long(min_pref_time); 
+	  put_opt6(opt_cfg->val, opt_cfg->len);
+	  
+	  /* pad */
+	  for (i = opt_cfg->len; i < len * 8; i++)
+	    put_opt6_char(0);
+	}
+    }
+	
+  if (daemon->port == NAMESERVER_PORT && !done_dns && parm.link_pref_time != 0)
+    {
+      /* default == us, as long as we are supplying DNS service. */
+      put_opt6_char(ICMP6_OPT_RDNSS);
+      put_opt6_char(3);
+      put_opt6_short(0);
+      put_opt6_long(min_pref_time); 
+      put_opt6(&parm.link_local, IN6ADDRSZ);
+    }
+
+  /* set managed bits unless we're providing only RA on this link */
+  if (parm.managed)
+    ra->flags |= 0x80; /* M flag, managed, */
+   if (parm.other)
+    ra->flags |= 0x40; /* O flag, other */ 
+			
+  /* decide where we're sending */
+  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);
+  if (dest)
+    {
+      addr.sin6_addr = *dest;
+      if (IN6_IS_ADDR_LINKLOCAL(dest) ||
+	  IN6_IS_ADDR_MC_LINKLOCAL(dest))
+	addr.sin6_scope_id = iface;
+    }
+  else
+    {
+      inet_pton(AF_INET6, ALL_NODES, &addr.sin6_addr); 
+      setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &send_iface, sizeof(send_iface));
+    }
+  
+  while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base, 
+			   save_counter(-1), 0, (struct sockaddr *)&addr, 
+			   sizeof(addr))));
+  
+}
+
+static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest)
+{
+  /* Send an RA on the same interface that the RA content is based
+     on. */
+  send_ra_alias(now, iface, iface_name, dest, iface);
+}
+
+static int add_prefixes(struct in6_addr *local,  int prefix,
+			int scope, int if_index, int flags, 
+			unsigned int preferred, unsigned int valid, void *vparam)
+{
+  struct ra_param *param = vparam;
+
+  (void)scope; /* warning */
+  
+  if (if_index == param->ind)
+    {
+      if (IN6_IS_ADDR_LINKLOCAL(local))
+	{
+	  /* Can there be more than one LL address?
+	     Select the one with the longest preferred time 
+	     if there is. */
+	  if (preferred > param->link_pref_time)
+	    {
+	      param->link_pref_time = preferred;
+	      param->link_local = *local;
+	    }
+	}
+      else if (!IN6_IS_ADDR_LOOPBACK(local) &&
+	       !IN6_IS_ADDR_MULTICAST(local))
+	{
+	  int real_prefix = 0;
+	  int do_slaac = 0;
+	  int deprecate  = 0;
+	  int constructed = 0;
+	  int adv_router = 0;
+	  int off_link = 0;
+	  unsigned int time = 0xffffffff;
+	  struct dhcp_context *context;
+	  
+	  for (context = daemon->dhcp6; context; context = context->next)
+	    if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+		prefix <= context->prefix &&
+		is_same_net6(local, &context->start6, context->prefix) &&
+		is_same_net6(local, &context->end6, context->prefix))
+	      {
+		context->saved_valid = valid;
+
+		if (context->flags & CONTEXT_RA) 
+		  {
+		    do_slaac = 1;
+		    if (context->flags & CONTEXT_DHCP)
+		      {
+			param->other = 1; 
+			if (!(context->flags & CONTEXT_RA_STATELESS))
+			  param->managed = 1;
+		      }
+		  }
+		else
+		  {
+		    /* don't do RA for non-ra-only unless --enable-ra is set */
+		    if (!option_bool(OPT_RA))
+		      continue;
+		    param->managed = 1;
+		    param->other = 1;
+		  }
+
+		/* Configured to advertise router address, not prefix. See RFC 3775 7.2 
+		 In this case we do all addresses associated with a context, 
+		 hence the real_prefix setting here. */
+		if (context->flags & CONTEXT_RA_ROUTER)
+		  {
+		    adv_router = 1;
+		    param->adv_router = 1;
+		    real_prefix = context->prefix;
+		  }
+
+		/* find floor time, don't reduce below 3 * RA interval.
+		   If the lease time has been left as default, don't
+		   use that as a floor. */
+		if ((context->flags & CONTEXT_SETLEASE) &&
+		    time > context->lease_time)
+		  {
+		    time = context->lease_time;
+		    if (time < ((unsigned int)(3 * param->adv_interval)))
+		      time = 3 * param->adv_interval;
+		  }
+
+		if (context->flags & CONTEXT_DEPRECATE)
+		  deprecate = 1;
+		
+		if (context->flags & CONTEXT_CONSTRUCTED)
+		  constructed = 1;
+
+
+		/* collect dhcp-range tags */
+		if (context->netid.next == &context->netid && context->netid.net)
+		  {
+		    context->netid.next = param->tags;
+		    param->tags = &context->netid;
+		  }
+		  
+		/* subsequent prefixes on the same interface 
+		   and subsequent instances of this prefix don't need timers.
+		   Be careful not to find the same prefix twice with different
+		   addresses unless we're advertising the actual addresses. */
+		if (!(context->flags & CONTEXT_RA_DONE))
+		  {
+		    if (!param->first)
+		      context->ra_time = 0;
+		    context->flags |= CONTEXT_RA_DONE;
+		    real_prefix = context->prefix;
+                    off_link = (context->flags & CONTEXT_RA_OFF_LINK);
+		  }
+
+		param->first = 0;
+		/* found_context is the _last_ one we found, so if there's 
+		   more than one, it's not the first. */
+		param->found_context = context;
+	      }
+
+	  /* configured time is ceiling */
+	  if (!constructed || valid > time)
+	    valid = time;
+	  
+	  if (flags & IFACE_DEPRECATED)
+	    preferred = 0;
+	  
+	  if (deprecate)
+	    time = 0;
+	  
+	  /* configured time is ceiling */
+	  if (!constructed || preferred > time)
+	    preferred = time;
+	  
+	  if (IN6_IS_ADDR_ULA(local))
+	    {
+	      if (preferred > param->ula_pref_time)
+		{
+		  param->ula_pref_time = preferred;
+		  param->ula = *local;
+		}
+	    }
+	  else 
+	    {
+	      if (preferred > param->glob_pref_time)
+		{
+		  param->glob_pref_time = preferred;
+		  param->link_global = *local;
+		}
+	    }
+	  
+	  if (real_prefix != 0)
+	    {
+	      struct prefix_opt *opt;
+	     	      
+	      if ((opt = expand(sizeof(struct prefix_opt))))
+		{
+		  /* zero net part of address */
+		  if (!adv_router)
+		    setaddr6part(local, addr6part(local) & ~((real_prefix == 64) ? (u64)-1LL : (1LLU << (128 - real_prefix)) - 1LLU));
+		  
+		  opt->type = ICMP6_OPT_PREFIX;
+		  opt->len = 4;
+		  opt->prefix_len = real_prefix;
+		  /* autonomous only if we're not doing dhcp, set
+                     "on-link" unless "off-link" was specified */
+		  opt->flags = (off_link ? 0 : 0x80);
+		  if (do_slaac)
+		    opt->flags |= 0x40;
+		  if (adv_router)
+		    opt->flags |= 0x20;
+		  opt->valid_lifetime = htonl(valid);
+		  opt->preferred_lifetime = htonl(preferred);
+		  opt->reserved = 0; 
+		  opt->prefix = *local;
+		  
+		  inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN);
+		  if (!option_bool(OPT_QUIET_RA))
+		    my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s", param->if_name, daemon->addrbuff); 		    
+		}
+	    }
+	}
+    }          
+  return 1;
+}
+
+static int add_lla(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+  (void)type;
+
+  if (index == *((int *)parm))
+    {
+      /* size is in units of 8 octets and includes type and length (2 bytes)
+	 add 7 to round up */
+      int len = (maclen + 9) >> 3;
+      unsigned char *p = expand(len << 3);
+      memset(p, 0, len << 3);
+      *p++ = ICMP6_OPT_SOURCE_MAC;
+      *p++ = len;
+      memcpy(p, mac, maclen);
+
+      return 0;
+    }
+
+  return 1;
+}
+
+time_t periodic_ra(time_t now)
+{
+  struct search_param param;
+  struct dhcp_context *context;
+  time_t next_event;
+  struct alias_param aparam;
+    
+  param.now = now;
+  param.iface = 0;
+
+  while (1)
+    {
+      /* find overdue events, and time of first future event */
+      for (next_event = 0, context = daemon->dhcp6; context; context = context->next)
+	if (context->ra_time != 0)
+	  {
+	    if (difftime(context->ra_time, now) <= 0.0)
+	      break; /* overdue */
+	    
+	    if (next_event == 0 || difftime(next_event, context->ra_time) > 0.0)
+	      next_event = context->ra_time;
+	  }
+      
+      /* none overdue */
+      if (!context)
+	break;
+      
+      if ((context->flags & CONTEXT_OLD) && 
+	  context->if_index != 0 && 
+	  indextoname(daemon->icmp6fd, context->if_index, param.name))
+	{
+	  /* A context for an old address. We'll not find the interface by 
+	     looking for addresses, but we know it anyway, since the context is
+	     constructed */
+	  param.iface = context->if_index;
+	  new_timeout(context, param.name, now);
+	}
+      else if (iface_enumerate(AF_INET6, &param, iface_search))
+	/* There's a context overdue, but we can't find an interface
+	   associated with it, because it's for a subnet we dont 
+	   have an interface on. Probably we're doing DHCP on
+	   a remote subnet via a relay. Zero the timer, since we won't
+	   ever be able to send ra's and satisfy it. */
+	context->ra_time = 0;
+      
+      if (param.iface != 0 &&
+	  iface_check(AF_LOCAL, NULL, param.name, NULL))
+	{
+	  struct iname *tmp;
+	  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+	    if (tmp->name && wildcard_match(tmp->name, param.name))
+	      break;
+	  if (!tmp)
+            {
+              send_ra(now, param.iface, param.name, NULL); 
+
+              /* Also send on all interfaces that are aliases of this
+                 one. */
+              for (aparam.bridge = daemon->bridges;
+                   aparam.bridge;
+                   aparam.bridge = aparam.bridge->next)
+                if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
+                  {
+                    /* Count the number of alias interfaces for this
+                       'bridge', by calling iface_enumerate with
+                       send_ra_to_aliases and NULL alias_ifs. */
+                    aparam.iface = param.iface;
+                    aparam.alias_ifs = NULL;
+                    aparam.num_alias_ifs = 0;
+                    iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases);
+                    my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => %d alias(es)",
+                              param.name, daemon->addrbuff, aparam.num_alias_ifs);
+
+                    /* Allocate memory to store the alias interface
+                       indices. */
+                    aparam.alias_ifs = (int *)whine_malloc(aparam.num_alias_ifs *
+                                                           sizeof(int));
+                    if (aparam.alias_ifs)
+                      {
+                        /* Use iface_enumerate again to get the alias
+                           interface indices, then send on each of
+                           those. */
+                        aparam.max_alias_ifs = aparam.num_alias_ifs;
+                        aparam.num_alias_ifs = 0;
+                        iface_enumerate(AF_LOCAL, &aparam, send_ra_to_aliases);
+                        for (; aparam.num_alias_ifs; aparam.num_alias_ifs--)
+                          {
+                            my_syslog(MS_DHCP | LOG_INFO, "RTR-ADVERT(%s) %s => i/f %d",
+                                      param.name, daemon->addrbuff,
+                                      aparam.alias_ifs[aparam.num_alias_ifs - 1]);
+                            send_ra_alias(now,
+                                          param.iface,
+                                          param.name,
+                                          NULL,
+                                          aparam.alias_ifs[aparam.num_alias_ifs - 1]);
+                          }
+                        free(aparam.alias_ifs);
+                      }
+
+                    /* The source interface can only appear in at most
+                       one --bridge-interface. */
+                    break;
+                  }
+            }
+	}
+    }      
+  return next_event;
+}
+
+static int send_ra_to_aliases(int index, unsigned int type, char *mac, size_t maclen, void *parm)
+{
+  struct alias_param *aparam = (struct alias_param *)parm;
+  char ifrn_name[IFNAMSIZ];
+  struct dhcp_bridge *alias;
+
+  (void)type;
+  (void)mac;
+  (void)maclen;
+
+  if (if_indextoname(index, ifrn_name))
+    for (alias = aparam->bridge->alias; alias; alias = alias->next)
+      if (wildcard_matchn(alias->iface, ifrn_name, IFNAMSIZ))
+        {
+          if (aparam->alias_ifs && (aparam->num_alias_ifs < aparam->max_alias_ifs))
+            aparam->alias_ifs[aparam->num_alias_ifs] = index;
+          aparam->num_alias_ifs++;
+        }
+
+  return 1;
+}
+
+static int iface_search(struct in6_addr *local,  int prefix,
+			int scope, int if_index, int flags, 
+			int preferred, int valid, void *vparam)
+{
+  struct search_param *param = vparam;
+  struct dhcp_context *context;
+  struct iname *tmp;
+  
+  (void)scope;
+  (void)preferred;
+  (void)valid;
+
+  /* ignore interfaces we're not doing DHCP on. */
+  if (!indextoname(daemon->icmp6fd, if_index, param->name) ||
+      !iface_check(AF_LOCAL, NULL, param->name, NULL))
+    return 1;
+
+  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+    if (tmp->name && wildcard_match(tmp->name, param->name))
+      return 1;
+
+  for (context = daemon->dhcp6; context; context = context->next)
+    if (!(context->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+	prefix <= context->prefix &&
+	is_same_net6(local, &context->start6, context->prefix) &&
+	is_same_net6(local, &context->end6, context->prefix) &&
+	context->ra_time != 0 && 
+	difftime(context->ra_time, param->now) <= 0.0)
+      {
+	/* found an interface that's overdue for RA determine new 
+	   timeout value and arrange for RA to be sent unless interface is
+	   still doing DAD.*/
+	if (!(flags & IFACE_TENTATIVE))
+	  param->iface = if_index;
+	
+	new_timeout(context, param->name, param->now);
+	
+	/* zero timers for other contexts on the same subnet, so they don't timeout 
+	   independently */
+	for (context = context->next; context; context = context->next)
+	  if (prefix <= context->prefix &&
+	      is_same_net6(local, &context->start6, context->prefix) &&
+	      is_same_net6(local, &context->end6, context->prefix))
+	    context->ra_time = 0;
+	
+	return 0; /* found, abort */
+      }
+  
+  return 1; /* keep searching */
+}
+ 
+static void new_timeout(struct dhcp_context *context, char *iface_name, time_t now)
+{
+  if (difftime(now, context->ra_short_period_start) < 60.0)
+    /* range 5 - 20 */
+    context->ra_time = now + 5 + (rand16()/4400);
+  else
+    {
+      /* range 3/4 - 1 times MaxRtrAdvInterval */
+      unsigned int adv_interval = calc_interval(find_iface_param(iface_name));
+      context->ra_time = now + (3 * adv_interval)/4 + ((adv_interval * (unsigned int)rand16()) >> 18);
+    }
+}
+
+static struct ra_interface *find_iface_param(char *iface)
+{
+  struct ra_interface *ra;
+  
+  for (ra = daemon->ra_interfaces; ra; ra = ra->next)
+    if (wildcard_match(ra->name, iface))
+      return ra;
+
+  return NULL;
+}
+
+static unsigned int calc_interval(struct ra_interface *ra)
+{
+  int interval = 600;
+  
+  if (ra && ra->interval != 0)
+    {
+      interval = ra->interval;
+      if (interval > 1800)
+	interval = 1800;
+      else if (interval < 4)
+	interval = 4;
+    }
+  
+  return (unsigned int)interval;
+}
+
+static unsigned int calc_lifetime(struct ra_interface *ra)
+{
+  int lifetime, interval = (int)calc_interval(ra);
+  
+  if (!ra || ra->lifetime == -1) /* not specified */
+    lifetime = 3 * interval;
+  else
+    {
+      lifetime = ra->lifetime;
+      if (lifetime < interval && lifetime != 0)
+	lifetime = interval;
+      else if (lifetime > 9000)
+	lifetime = 9000;
+    }
+  
+  return (unsigned int)lifetime;
+}
+
+static unsigned int calc_prio(struct ra_interface *ra)
+{
+  if (ra)
+    return ra->prio;
+  
+  return 0;
+}
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c
new file mode 100755
index 0000000..6fc4f26
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc1035.c
@@ -0,0 +1,2061 @@
+/* dnsmasq is Copyright (c) 2000-2021 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"
+
+int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, 
+		 char *name, int isExtract, int extrabytes)
+{
+  unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL;
+  unsigned int j, l, namelen = 0, hops = 0;
+  int retvalue = 1;
+  
+  if (isExtract)
+    *cp = 0;
+
+  while (1)
+    { 
+      unsigned int label_type;
+
+      if (!CHECK_LEN(header, p, plen, 1))
+	return 0;
+      
+      if ((l = *p++) == 0) 
+	/* end marker */
+	{
+	  /* check that there are the correct no. of bytes after the name */
+	  if (!CHECK_LEN(header, p1 ? p1 : p, plen, extrabytes))
+	    return 0;
+	  
+	  if (isExtract)
+	    {
+	      if (cp != (unsigned char *)name)
+		cp--;
+	      *cp = 0; /* terminate: lose final period */
+	    }
+	  else if (*cp != 0)
+	    retvalue = 2;
+	  
+	  if (p1) /* we jumped via compression */
+	    *pp = p1;
+	  else
+	    *pp = p;
+	  
+	  return retvalue;
+	}
+
+      label_type = l & 0xc0;
+      
+      if (label_type == 0xc0) /* pointer */
+	{ 
+	  if (!CHECK_LEN(header, p, plen, 1))
+	    return 0;
+	      
+	  /* get offset */
+	  l = (l&0x3f) << 8;
+	  l |= *p++;
+	  
+	  if (!p1) /* first jump, save location to go back to */
+	    p1 = p;
+	      
+	  hops++; /* break malicious infinite loops */
+	  if (hops > 255)
+	    return 0;
+	  
+	  p = l + (unsigned char *)header;
+	}
+      else if (label_type == 0x00)
+	{ /* label_type = 0 -> label. */
+	  namelen += l + 1; /* include period */
+	  if (namelen >= MAXDNAME)
+	    return 0;
+	  if (!CHECK_LEN(header, p, plen, l))
+	    return 0;
+	  
+	  for(j=0; j<l; j++, p++)
+	    if (isExtract)
+	      {
+		unsigned char c = *p;
+#ifdef HAVE_DNSSEC
+		if (option_bool(OPT_DNSSEC_VALID))
+		  {
+		    if (c == 0 || c == '.' || c == NAME_ESCAPE)
+		      {
+			*cp++ = NAME_ESCAPE;
+			*cp++ = c+1;
+		      }
+		    else
+		      *cp++ = c; 
+		  }
+		else
+#endif
+		if (c != 0 && c != '.')
+		  *cp++ = c;
+		else
+		  return 0;
+	      }
+	    else 
+	      {
+		unsigned char c1 = *cp, c2 = *p;
+		
+		if (c1 == 0)
+		  retvalue = 2;
+		else 
+		  {
+		    cp++;
+		    if (c1 >= 'A' && c1 <= 'Z')
+		      c1 += 'a' - 'A';
+#ifdef HAVE_DNSSEC
+		    if (option_bool(OPT_DNSSEC_VALID) && c1 == NAME_ESCAPE)
+		      c1 = (*cp++)-1;
+#endif
+		    
+		    if (c2 >= 'A' && c2 <= 'Z')
+		      c2 += 'a' - 'A';
+		     
+		    if (c1 != c2)
+		      retvalue =  2;
+		  }
+	      }
+	    
+	  if (isExtract)
+	    *cp++ = '.';
+	  else if (*cp != 0 && *cp++ != '.')
+	    retvalue = 2;
+	}
+      else
+	return 0; /* label types 0x40 and 0x80 not supported */
+    }
+}
+ 
+/* Max size of input string (for IPv6) is 75 chars.) */
+#define MAXARPANAME 75
+int in_arpa_name_2_addr(char *namein, union all_addr *addrp)
+{
+  int j;
+  char name[MAXARPANAME+1], *cp1;
+  unsigned char *addr = (unsigned char *)addrp;
+  char *lastchunk = NULL, *penchunk = NULL;
+  
+  if (strlen(namein) > MAXARPANAME)
+    return 0;
+
+  memset(addrp, 0, sizeof(union all_addr));
+
+  /* turn name into a series of asciiz strings */
+  /* j counts no. of labels */
+  for(j = 1,cp1 = name; *namein; cp1++, namein++)
+    if (*namein == '.')
+      {
+	penchunk = lastchunk;
+        lastchunk = cp1 + 1;
+	*cp1 = 0;
+	j++;
+      }
+    else
+      *cp1 = *namein;
+  
+  *cp1 = 0;
+
+  if (j<3)
+    return 0;
+
+  if (hostname_isequal(lastchunk, "arpa") && hostname_isequal(penchunk, "in-addr"))
+    {
+      /* IP v4 */
+      /* address arrives as a name of the form
+	 www.xxx.yyy.zzz.in-addr.arpa
+	 some of the low order address octets might be missing
+	 and should be set to zero. */
+      for (cp1 = name; cp1 != penchunk; cp1 += strlen(cp1)+1)
+	{
+	  /* check for digits only (weeds out things like
+	     50.0/24.67.28.64.in-addr.arpa which are used 
+	     as CNAME targets according to RFC 2317 */
+	  char *cp;
+	  for (cp = cp1; *cp; cp++)
+	    if (!isdigit((unsigned char)*cp))
+	      return 0;
+	  
+	  addr[3] = addr[2];
+	  addr[2] = addr[1];
+	  addr[1] = addr[0];
+	  addr[0] = atoi(cp1);
+	}
+
+      return F_IPV4;
+    }
+  else if (hostname_isequal(penchunk, "ip6") && 
+	   (hostname_isequal(lastchunk, "int") || hostname_isequal(lastchunk, "arpa")))
+    {
+      /* IP v6:
+         Address arrives as 0.1.2.3.4.5.6.7.8.9.a.b.c.d.e.f.ip6.[int|arpa]
+    	 or \[xfedcba9876543210fedcba9876543210/128].ip6.[int|arpa]
+      
+	 Note that most of these the various representations are obsolete and 
+	 left-over from the many DNS-for-IPv6 wars. We support all the formats
+	 that we can since there is no reason not to.
+      */
+
+      if (*name == '\\' && *(name+1) == '[' && 
+	  (*(name+2) == 'x' || *(name+2) == 'X'))
+	{	  
+	  for (j = 0, cp1 = name+3; *cp1 && isxdigit((unsigned char) *cp1) && j < 32; cp1++, j++)
+	    {
+	      char xdig[2];
+	      xdig[0] = *cp1;
+	      xdig[1] = 0;
+	      if (j%2)
+		addr[j/2] |= strtol(xdig, NULL, 16);
+	      else
+		addr[j/2] = strtol(xdig, NULL, 16) << 4;
+	    }
+	  
+	  if (*cp1 == '/' && j == 32)
+	    return F_IPV6;
+	}
+      else
+	{
+	  for (cp1 = name; cp1 != penchunk; cp1 += strlen(cp1)+1)
+	    {
+	      if (*(cp1+1) || !isxdigit((unsigned char)*cp1))
+		return 0;
+	      
+	      for (j = sizeof(struct in6_addr)-1; j>0; j--)
+		addr[j] = (addr[j] >> 4) | (addr[j-1] << 4);
+	      addr[0] = (addr[0] >> 4) | (strtol(cp1, NULL, 16) << 4);
+	    }
+	  
+	  return F_IPV6;
+	}
+    }
+  
+  return 0;
+}
+
+unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes)
+{
+  while(1)
+    {
+      unsigned int label_type;
+      
+      if (!CHECK_LEN(header, ansp, plen, 1))
+	return NULL;
+      
+      label_type = (*ansp) & 0xc0;
+
+      if (label_type == 0xc0)
+	{
+	  /* pointer for compression. */
+	  ansp += 2;	
+	  break;
+	}
+      else if (label_type == 0x80)
+	return NULL; /* reserved */
+      else if (label_type == 0x40)
+	{
+	  /* Extended label type */
+	  unsigned int count;
+	  
+	  if (!CHECK_LEN(header, ansp, plen, 2))
+	    return NULL;
+	  
+	  if (((*ansp++) & 0x3f) != 1)
+	    return NULL; /* we only understand bitstrings */
+	  
+	  count = *(ansp++); /* Bits in bitstring */
+	  
+	  if (count == 0) /* count == 0 means 256 bits */
+	    ansp += 32;
+	  else
+	    ansp += ((count-1)>>3)+1;
+	}
+      else
+	{ /* label type == 0 Bottom six bits is length */
+	  unsigned int len = (*ansp++) & 0x3f;
+	  
+	  if (!ADD_RDLEN(header, ansp, plen, len))
+	    return NULL;
+
+	  if (len == 0)
+	    break; /* zero length label marks the end. */
+	}
+    }
+
+  if (!CHECK_LEN(header, ansp, plen, extrabytes))
+    return NULL;
+  
+  return ansp;
+}
+
+unsigned char *skip_questions(struct dns_header *header, size_t plen)
+{
+  int q;
+  unsigned char *ansp = (unsigned char *)(header+1);
+
+  for (q = ntohs(header->qdcount); q != 0; q--)
+    {
+      if (!(ansp = skip_name(ansp, header, plen, 4)))
+	return NULL;
+      ansp += 4; /* class and type */
+    }
+  
+  return ansp;
+}
+
+unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *header, size_t plen)
+{
+  int i, rdlen;
+  
+  for (i = 0; i < count; i++)
+    {
+      if (!(ansp = skip_name(ansp, header, plen, 10)))
+	return NULL; 
+      ansp += 8; /* type, class, TTL */
+      GETSHORT(rdlen, ansp);
+      if (!ADD_RDLEN(header, ansp, plen, rdlen))
+	return NULL;
+    }
+
+  return ansp;
+}
+
+size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen)
+{
+  unsigned char *ansp = skip_questions(header, plen);
+    
+  /* if packet is malformed, just return as-is. */
+  if (!ansp)
+    return plen;
+  
+  if (!(ansp = skip_section(ansp, ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount),
+			    header, plen)))
+    return plen;
+    
+  /* restore pseudoheader */
+  if (pheader && ntohs(header->arcount) == 0)
+    {
+      /* must use memmove, may overlap */
+      memmove(ansp, pheader, hlen);
+      header->arcount = htons(1);
+      ansp += hlen;
+    }
+
+  return ansp - (unsigned char *)header;
+}
+
+/* is addr in the non-globally-routed IP space? */ 
+int private_net(struct in_addr addr, int ban_localhost) 
+{
+  in_addr_t ip_addr = ntohl(addr.s_addr);
+
+  return
+    (((ip_addr & 0xFF000000) == 0x7F000000) && ban_localhost)  /* 127.0.0.0/8    (loopback) */ ||
+    (((ip_addr & 0xFF000000) == 0x00000000) && ban_localhost) /* RFC 5735 section 3. "here" network */ ||
+    ((ip_addr & 0xFF000000) == 0x0A000000)  /* 10.0.0.0/8     (private)  */ ||
+    ((ip_addr & 0xFFF00000) == 0xAC100000)  /* 172.16.0.0/12  (private)  */ ||
+    ((ip_addr & 0xFFFF0000) == 0xC0A80000)  /* 192.168.0.0/16 (private)  */ ||
+    ((ip_addr & 0xFFFF0000) == 0xA9FE0000)  /* 169.254.0.0/16 (zeroconf) */ ||
+    ((ip_addr & 0xFFFFFF00) == 0xC0000200)  /* 192.0.2.0/24   (test-net) */ ||
+    ((ip_addr & 0xFFFFFF00) == 0xC6336400)  /* 198.51.100.0/24(test-net) */ ||
+    ((ip_addr & 0xFFFFFF00) == 0xCB007100)  /* 203.0.113.0/24 (test-net) */ ||
+    ((ip_addr & 0xFFFFFFFF) == 0xFFFFFFFF)  /* 255.255.255.255/32 (broadcast)*/ ;
+}
+
+static int private_net6(struct in6_addr *a, int ban_localhost)
+{
+  /* Block IPv4-mapped IPv6 addresses in private IPv4 address space */
+  if (IN6_IS_ADDR_V4MAPPED(a))
+    {
+      struct in_addr v4;
+      v4.s_addr = ((const uint32_t *) (a))[3];
+      return private_net(v4, ban_localhost);
+    }
+
+  return
+    (IN6_IS_ADDR_UNSPECIFIED(a) && ban_localhost) || /* RFC 6303 4.3 */
+    (IN6_IS_ADDR_LOOPBACK(a) && ban_localhost) ||    /* RFC 6303 4.3 */
+    IN6_IS_ADDR_LINKLOCAL(a) ||   /* RFC 6303 4.5 */
+    IN6_IS_ADDR_SITELOCAL(a) ||
+    ((unsigned char *)a)[0] == 0xfd ||   /* RFC 6303 4.4 */
+    ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
+}
+
+static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored)
+{
+  int i, qtype, qclass, rdlen;
+
+  for (i = count; i != 0; i--)
+    {
+      if (!(p = skip_name(p, header, qlen, 10)))
+	return 0; /* bad packet */
+      
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+      p += 4; /* ttl */
+      GETSHORT(rdlen, p);
+      
+      if (qclass == C_IN && qtype == T_A)
+	{
+	  struct doctor *doctor;
+	  struct in_addr addr;
+	  
+	  if (!CHECK_LEN(header, p, qlen, INADDRSZ))
+	    return 0;
+	  
+	  /* alignment */
+	  memcpy(&addr, p, INADDRSZ);
+	  
+	  for (doctor = daemon->doctors; doctor; doctor = doctor->next)
+	    {
+	      if (doctor->end.s_addr == 0)
+		{
+		  if (!is_same_net(doctor->in, addr, doctor->mask))
+		    continue;
+		}
+	      else if (ntohl(doctor->in.s_addr) > ntohl(addr.s_addr) || 
+		       ntohl(doctor->end.s_addr) < ntohl(addr.s_addr))
+		continue;
+	      
+	      addr.s_addr &= ~doctor->mask.s_addr;
+	      addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr);
+	      /* Since we munged the data, the server it came from is no longer authoritative */
+	      header->hb3 &= ~HB3_AA;
+	      *doctored = 1;
+	      memcpy(p, &addr, INADDRSZ);
+	      break;
+	    }
+	}
+      
+      if (!ADD_RDLEN(header, p, qlen, rdlen))
+	 return 0; /* bad packet */
+    }
+  
+  return p; 
+}
+
+static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
+{
+  unsigned char *p;
+  int qtype, qclass, rdlen;
+  unsigned long ttl, minttl = ULONG_MAX;
+  int i, found_soa = 0;
+  
+  /* first move to NS section and find TTL from any SOA section */
+  if (!(p = skip_questions(header, qlen)) ||
+      !(p = do_doctor(p, ntohs(header->ancount), header, qlen, doctored)))
+    return 0;  /* bad packet */
+  
+  for (i = ntohs(header->nscount); i != 0; i--)
+    {
+      if (!(p = skip_name(p, header, qlen, 10)))
+	return 0; /* bad packet */
+      
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+      GETLONG(ttl, p);
+      GETSHORT(rdlen, p);
+      
+      if ((qclass == C_IN) && (qtype == T_SOA))
+	{
+	  found_soa = 1;
+	  if (ttl < minttl)
+	    minttl = ttl;
+
+	  /* MNAME */
+	  if (!(p = skip_name(p, header, qlen, 0)))
+	    return 0;
+	  /* RNAME */
+	  if (!(p = skip_name(p, header, qlen, 20)))
+	    return 0;
+	  p += 16; /* SERIAL REFRESH RETRY EXPIRE */
+	  
+	  GETLONG(ttl, p); /* minTTL */
+	  if (ttl < minttl)
+	    minttl = ttl;
+	}
+      else if (!ADD_RDLEN(header, p, qlen, rdlen))
+	return 0; /* bad packet */
+    }
+  
+  /* rewrite addresses in additional section too */
+  if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored))
+    return 0;
+  
+  if (!found_soa)
+    minttl = daemon->neg_ttl;
+
+  return minttl;
+}
+
+/* Print TXT reply to log */
+static int print_txt(struct dns_header *header, const size_t qlen, char *name,
+		     unsigned char *p, const int ardlen, int secflag)
+{
+  unsigned char *p1 = p;
+  if (!CHECK_LEN(header, p1, qlen, ardlen))
+    return 0;
+  /* Loop over TXT payload */
+  while ((p1 - p) < ardlen)
+    {
+      unsigned int i, len = *p1;
+      unsigned char *p3 = p1;
+      if ((p1 + len - p) >= ardlen)
+	return 0; /* bad packet */
+
+      /* make counted string zero-term and sanitise */
+      for (i = 0; i < len; i++)
+	{
+	  if (!isprint((int)*(p3+1)))
+	    break;
+	  *p3 = *(p3+1);
+	  p3++;
+	}
+
+      *p3 = 0;
+      log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, (char*)p1);
+      /* restore */
+      memmove(p1 + 1, p1, i);
+      *p1 = len;
+      p1 += len+1;
+    }
+  return 1;
+}
+
+/* Note that the following code can create CNAME chains that don't point to a real record,
+   either because of lack of memory, or lack of SOA records.  These are treated by the cache code as 
+   expired and cleaned out that way. 
+   Return 1 if we reject an address because it look like part of dns-rebinding attack. */
+int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, 
+		      char **ipsets, int is_sign, int check_rebind, int no_cache_dnssec,
+		      int secure, int *doctored)
+{
+  unsigned char *p, *p1, *endrr, *namep;
+  int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
+  unsigned long ttl = 0;
+  union all_addr addr;
+#ifdef HAVE_IPSET
+  char **ipsets_cur;
+#else
+  (void)ipsets; /* unused */
+#endif
+  int found = 0, cname_count = CNAME_CHAIN;
+  struct crec *cpp = NULL;
+  int flags = RCODE(header) == NXDOMAIN ? F_NXDOMAIN : 0;
+#ifdef HAVE_DNSSEC
+  int cname_short = 0;
+#endif
+  unsigned long cttl = ULONG_MAX, attl;
+  
+  cache_start_insert();
+
+  /* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */
+  if (daemon->doctors || option_bool(OPT_DNSSEC_VALID))
+    {
+      searched_soa = 1;
+      ttl = find_soa(header, qlen, doctored);
+
+      if (*doctored)
+	{
+	  if (secure)
+	    return 0;
+#ifdef HAVE_DNSSEC
+	  if (option_bool(OPT_DNSSEC_VALID))
+	    for (j = 0; j < ntohs(header->ancount); j++)
+	      if (daemon->rr_status[j] != 0)
+		return 0;
+#endif
+	}
+    }
+  
+  namep = p = (unsigned char *)(header+1);
+  
+  if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
+    return 0; /* bad packet */
+  
+  GETSHORT(qtype, p); 
+  GETSHORT(qclass, p);
+  
+  if (qclass != C_IN)
+    return 0;
+  
+  /* PTRs: we chase CNAMEs here, since we have no way to 
+     represent them in the cache. */
+  if (qtype == T_PTR)
+    { 
+      int insert = 1, name_encoding = in_arpa_name_2_addr(name, &addr);
+      
+      if (!(flags & F_NXDOMAIN))
+	{
+	cname_loop:
+	  if (!(p1 = skip_questions(header, qlen)))
+	    return 0;
+	  
+	  for (j = 0; j < ntohs(header->ancount); j++) 
+	    {
+	      int secflag = 0;
+	      if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
+		return 0; /* bad packet */
+	      
+	      GETSHORT(aqtype, p1); 
+	      GETSHORT(aqclass, p1);
+	      GETLONG(attl, p1);
+	      
+	      if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign)
+		{
+		  (p1) -= 4;
+		  PUTLONG(daemon->max_ttl, p1);
+		}
+	      GETSHORT(ardlen, p1);
+	      endrr = p1+ardlen;
+	      
+	      /* TTL of record is minimum of CNAMES and PTR */
+	      if (attl < cttl)
+		cttl = attl;
+	      
+	      if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR))
+		{
+#ifdef HAVE_DNSSEC
+		  if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
+		    {
+		      /* validated RR anywhere in CNAME chain, don't cache. */
+		      if (cname_short || aqtype == T_CNAME)
+			insert = 0;
+		      
+		      secflag = F_DNSSECOK;
+		      /* limit TTL based on signature. */
+		      if (daemon->rr_status[j] < cttl)
+			cttl = daemon->rr_status[j];
+		    }
+#endif
+
+		  if (aqtype == T_CNAME)
+		    log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+		  
+		  if (!extract_name(header, qlen, &p1, name, 1, 0))
+		    return 0;
+		  
+		  if (aqtype == T_CNAME)
+		    {
+		      if (!cname_count--)
+			return 0; /* looped CNAMES, we can't cache. */
+#ifdef HAVE_DNSSEC
+		      cname_short = 1;
+#endif
+		      goto cname_loop;
+		    }
+		  
+		  found = 1; 
+		  
+		  if (!name_encoding)
+		    log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+		  else
+		    {
+		      log_query(name_encoding | secflag | F_REVERSE | F_UPSTREAM, name, &addr, NULL);
+		      if (insert)
+			cache_insert(name, &addr, C_IN, now, cttl, name_encoding | secflag | F_REVERSE);
+		    }
+		}
+
+	      p1 = endrr;
+	      if (!CHECK_LEN(header, p1, qlen, 0))
+		return 0; /* bad packet */
+	    }
+	}
+      
+      if (!found && !option_bool(OPT_NO_NEG))
+	{
+	  if (!searched_soa)
+	    {
+	      searched_soa = 1;
+	      ttl = find_soa(header, qlen, doctored);
+	    }
+	  
+	  flags |= F_NEG | (secure ?  F_DNSSECOK : 0);
+	  if (name_encoding && ttl)
+	    {
+	      flags |= F_REVERSE | name_encoding;
+	      cache_insert(NULL, &addr, C_IN, now, ttl, flags);
+	    }
+	  
+	  log_query(flags | F_UPSTREAM, name, &addr, NULL);
+	}
+    }
+  else
+    {
+      /* everything other than PTR */
+      struct crec *newc;
+      int addrlen = 0, insert = 1;
+      
+      if (qtype == T_A)
+	{
+	  addrlen = INADDRSZ;
+	  flags |= F_IPV4;
+	}
+      else if (qtype == T_AAAA)
+	{
+	  addrlen = IN6ADDRSZ;
+	  flags |= F_IPV6;
+	}
+      else if (qtype == T_SRV)
+	flags |= F_SRV;
+      else
+	insert = 0; /* NOTE: do not cache data from CNAME queries. */
+      
+    cname_loop1:
+      if (!(p1 = skip_questions(header, qlen)))
+	return 0;
+      
+      for (j = 0; j < ntohs(header->ancount); j++) 
+	{
+	  int secflag = 0;
+	  
+	  if (!(res = extract_name(header, qlen, &p1, name, 0, 10)))
+	    return 0; /* bad packet */
+	  
+	  GETSHORT(aqtype, p1); 
+	  GETSHORT(aqclass, p1);
+	  GETLONG(attl, p1);
+	  if ((daemon->max_ttl != 0) && (attl > daemon->max_ttl) && !is_sign)
+	    {
+	      (p1) -= 4;
+	      PUTLONG(daemon->max_ttl, p1);
+	    }
+	  GETSHORT(ardlen, p1);
+	  endrr = p1+ardlen;
+	  
+	  /* Not what we're looking for? */
+	  if (aqclass != C_IN || res == 2)
+	    {
+	      p1 = endrr;
+	      if (!CHECK_LEN(header, p1, qlen, 0))
+		return 0; /* bad packet */
+	      continue;
+	    }
+	  
+#ifdef HAVE_DNSSEC
+	  if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0)
+	    {
+	      secflag = F_DNSSECOK;
+	      
+	      /* limit TTl based on sig. */
+	      if (daemon->rr_status[j] < attl)
+		attl = daemon->rr_status[j];
+	    }
+#endif	  
+	  
+	  if (aqtype == T_CNAME)
+	    {
+	      if (!cname_count--)
+		return 0; /* looped CNAMES */
+	      
+	      log_query(secflag | F_CNAME | F_FORWARD | F_UPSTREAM, name, NULL, NULL);
+	      
+	      if (insert)
+		{
+		  if ((newc = cache_insert(name, NULL, C_IN, now, attl, F_CNAME | F_FORWARD | secflag)))
+		    {
+		      newc->addr.cname.target.cache = NULL;
+		      newc->addr.cname.is_name_ptr = 0; 
+		      if (cpp)
+			{
+			  next_uid(newc);
+			  cpp->addr.cname.target.cache = newc;
+			  cpp->addr.cname.uid = newc->uid;
+			}
+		    }
+		  
+		  cpp = newc;
+		  if (attl < cttl)
+		    cttl = attl;
+		}
+	      
+	      namep = p1;
+	      if (!extract_name(header, qlen, &p1, name, 1, 0))
+		return 0;
+	      
+	      goto cname_loop1;
+	    }
+	  else if (aqtype != qtype)
+	    {
+#ifdef HAVE_DNSSEC
+	      if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
+#endif
+		log_query(secflag | F_FORWARD | F_UPSTREAM, name, NULL, querystr(NULL, aqtype));
+	    }
+	  else if (!(flags & F_NXDOMAIN))
+	    {
+	      found = 1;
+	      
+	      if (flags & F_SRV)
+		{
+		  unsigned char *tmp = namep;
+		  
+		  if (!CHECK_LEN(header, p1, qlen, 6))
+		    return 0; /* bad packet */
+		  GETSHORT(addr.srv.priority, p1);
+		  GETSHORT(addr.srv.weight, p1);
+		  GETSHORT(addr.srv.srvport, p1);
+		  if (!extract_name(header, qlen, &p1, name, 1, 0))
+		    return 0;
+		  addr.srv.targetlen = strlen(name) + 1; /* include terminating zero */
+		  if (!(addr.srv.target = blockdata_alloc(name, addr.srv.targetlen)))
+		    return 0;
+		  
+		  /* we overwrote the original name, so get it back here. */
+		  if (!extract_name(header, qlen, &tmp, name, 1, 0))
+		    return 0;
+		}
+	      else if (flags & (F_IPV4 | F_IPV6))
+		{
+		  /* copy address into aligned storage */
+		  if (!CHECK_LEN(header, p1, qlen, addrlen))
+		    return 0; /* bad packet */
+		  memcpy(&addr, p1, addrlen);
+		  
+		  /* check for returned address in private space */
+		  if (check_rebind)
+		    {
+		      if ((flags & F_IPV4) &&
+			  private_net(addr.addr4, !option_bool(OPT_LOCAL_REBIND)))
+			return 1;
+		      
+		      if ((flags & F_IPV6) &&
+			  private_net6(&addr.addr6, !option_bool(OPT_LOCAL_REBIND)))
+			return 1;
+		    }
+		  
+#ifdef HAVE_IPSET
+		  if (ipsets && (flags & (F_IPV4 | F_IPV6)))
+		    {
+		      ipsets_cur = ipsets;
+		      while (*ipsets_cur)
+			{
+			  log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, name, &addr, *ipsets_cur);
+			  add_to_ipset(*ipsets_cur++, &addr, flags, 0);
+			}
+		    }
+#endif
+		}
+	      
+	      if (insert)
+		{
+		  newc = cache_insert(name, &addr, C_IN, now, attl, flags | F_FORWARD | secflag);
+		  if (newc && cpp)
+		    {
+		      next_uid(newc);
+		      cpp->addr.cname.target.cache = newc;
+		      cpp->addr.cname.uid = newc->uid;
+		    }
+		  cpp = NULL;
+		}
+	      
+	      if (aqtype == T_TXT)
+		{
+		  if (!print_txt(header, qlen, name, p1, ardlen, secflag))
+		    return 0;
+		}
+	      else
+		log_query(flags | F_FORWARD | secflag | F_UPSTREAM, name, &addr, querystr(NULL, aqtype));
+	    }
+	  
+	  p1 = endrr;
+	  if (!CHECK_LEN(header, p1, qlen, 0))
+	    return 0; /* bad packet */
+	}
+      
+      if (!found && !option_bool(OPT_NO_NEG))
+	{
+	  if (!searched_soa)
+	    {
+	      searched_soa = 1;
+	      ttl = find_soa(header, qlen, doctored);
+	    }
+	  
+	  /* If there's no SOA to get the TTL from, but there is a CNAME 
+	     pointing at this, inherit its TTL */
+	  if (ttl || cpp)
+	    {
+	      if (ttl == 0)
+		ttl = cttl;
+	      
+	      log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL);
+	      
+	      if (insert)
+		{
+		  newc = cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));	
+		  if (newc && cpp)
+		    {
+		      next_uid(newc);
+		      cpp->addr.cname.target.cache = newc;
+		      cpp->addr.cname.uid = newc->uid;
+		    }
+		}
+	    }
+	}
+    }
+  
+  /* Don't put stuff from a truncated packet into the cache.
+     Don't cache replies from non-recursive nameservers, since we may get a 
+     reply containing a CNAME but not its target, even though the target 
+     does exist. */
+  if (!(header->hb3 & HB3_TC) && 
+      !(header->hb4 & HB4_CD) &&
+      (header->hb4 & HB4_RA) &&
+      !no_cache_dnssec)
+    cache_end_insert();
+
+  return 0;
+}
+
+#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
+/* Don't pass control chars and weird escapes to UBus. */
+static int safe_name(char *name)
+{
+  unsigned char *r;
+  
+  for (r = (unsigned char *)name; *r; r++)
+    if (!isprint((int)*r))
+      return 0;
+  
+  return 1;
+}
+
+void report_addresses(struct dns_header *header, size_t len, u32 mark)
+{
+  unsigned char *p, *endrr;
+  int i;
+  unsigned long attl;
+  struct allowlist *allowlists;
+  char **pattern_pos;
+  
+  if (RCODE(header) != NOERROR)
+    return;
+  
+  for (allowlists = daemon->allowlists; allowlists; allowlists = allowlists->next)
+    if (allowlists->mark == (mark & daemon->allowlist_mask & allowlists->mask))
+      for (pattern_pos = allowlists->patterns; *pattern_pos; pattern_pos++)
+	if (!strcmp(*pattern_pos, "*"))
+	  return;
+  
+  if (!(p = skip_questions(header, len)))
+    return;
+  for (i = ntohs(header->ancount); i != 0; i--)
+    {
+      int aqtype, aqclass, ardlen;
+      
+      if (!extract_name(header, len, &p, daemon->namebuff, 1, 10))
+	return;
+      
+      if (!CHECK_LEN(header, p, len, 10))
+	return;
+      GETSHORT(aqtype, p);
+      GETSHORT(aqclass, p);
+      GETLONG(attl, p);
+      GETSHORT(ardlen, p);
+      
+      if (!CHECK_LEN(header, p, len, ardlen))
+	return;
+      endrr = p+ardlen;
+      
+      if (aqclass == C_IN)
+	{
+	  if (aqtype == T_CNAME)
+	    {
+	      if (!extract_name(header, len, &p, daemon->workspacename, 1, 0))
+		return;
+	      if (safe_name(daemon->namebuff) && safe_name(daemon->workspacename))
+		ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, daemon->workspacename, attl);
+	    }
+	  if (aqtype == T_A)
+	    {
+	      struct in_addr addr;
+	      char ip[INET_ADDRSTRLEN];
+	      if (ardlen != INADDRSZ)
+		return;
+	      memcpy(&addr, p, ardlen);
+	      if (inet_ntop(AF_INET, &addr, ip, sizeof ip) && safe_name(daemon->namebuff))
+		ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
+	    }
+	  else if (aqtype == T_AAAA)
+	    {
+	      struct in6_addr addr;
+	      char ip[INET6_ADDRSTRLEN];
+	      if (ardlen != IN6ADDRSZ)
+		return;
+	      memcpy(&addr, p, ardlen);
+	      if (inet_ntop(AF_INET6, &addr, ip, sizeof ip) && safe_name(daemon->namebuff))
+		ubus_event_bcast_connmark_allowlist_resolved(mark, daemon->namebuff, ip, attl);
+	    }
+	}
+      
+      p = endrr;
+    }
+}
+#endif
+
+/* If the packet holds exactly one query
+   return F_IPV4 or F_IPV6  and leave the name from the query in name */
+unsigned int extract_request(struct dns_header *header, size_t qlen, char *name, unsigned short *typep)
+{
+  unsigned char *p = (unsigned char *)(header+1);
+  int qtype, qclass;
+
+  if (typep)
+    *typep = 0;
+
+  *name = 0; /* return empty name if no query found. */
+  
+  if (ntohs(header->qdcount) != 1 || OPCODE(header) != QUERY)
+    return 0; /* must be exactly one query. */
+  
+  if (!(header->hb3 & HB3_QR) && (ntohs(header->ancount) != 0 || ntohs(header->nscount) != 0))
+    return 0; /* non-standard query. */
+  
+  if (!extract_name(header, qlen, &p, name, 1, 4))
+    return 0; /* bad packet */
+   
+  GETSHORT(qtype, p); 
+  GETSHORT(qclass, p);
+
+  if (typep)
+    *typep = qtype;
+
+  if (qclass == C_IN)
+    {
+      if (qtype == T_A)
+	return F_IPV4;
+      if (qtype == T_AAAA)
+	return F_IPV6;
+      if (qtype == T_ANY)
+	return  F_IPV4 | F_IPV6;
+    }
+
+#ifdef HAVE_DNSSEC
+  /* F_DNSSECOK as agument to search_servers() inhibits forwarding
+     to servers for domains without a trust anchor. This make the
+     behaviour for DS and DNSKEY queries we forward the same
+     as for DS and DNSKEY queries we originate. */
+  if (option_bool(OPT_DNSSEC_VALID) && (qtype == T_DS || qtype == T_DNSKEY))
+    return F_DNSSECOK;
+#endif
+  
+  return F_QUERY;
+}
+
+void setup_reply(struct dns_header *header, unsigned int flags, int ede)
+{
+  /* clear authoritative and truncated flags, set QR flag */
+  header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC )) | HB3_QR;
+  /* clear AD flag, set RA flag */
+  header->hb4 = (header->hb4 & ~HB4_AD) | HB4_RA;
+
+  header->nscount = htons(0);
+  header->arcount = htons(0);
+  header->ancount = htons(0); /* no answers unless changed below */
+  if (flags == F_NOERR)
+    SET_RCODE(header, NOERROR); /* empty domain */
+  else if (flags == F_NXDOMAIN)
+    SET_RCODE(header, NXDOMAIN);
+  else if (flags & ( F_IPV4 | F_IPV6))
+    {
+      SET_RCODE(header, NOERROR);
+      header->hb3 |= HB3_AA;
+    }
+  else /* nowhere to forward to */
+    {
+      union all_addr a;
+      a.log.rcode = REFUSED;
+      a.log.ede = ede;
+      log_query(F_CONFIG | F_RCODE, "error", &a, NULL);
+      SET_RCODE(header, REFUSED);
+    }
+}
+
+/* check if name matches local names ie from /etc/hosts or DHCP or local mx names. */
+int check_for_local_domain(char *name, time_t now)
+{
+  struct mx_srv_record *mx;
+  struct txt_record *txt;
+  struct interface_name *intr;
+  struct ptr_record *ptr;
+  struct naptr *naptr;
+
+  for (naptr = daemon->naptr; naptr; naptr = naptr->next)
+     if (hostname_issubdomain(name, naptr->name))
+      return 1;
+
+   for (mx = daemon->mxnames; mx; mx = mx->next)
+    if (hostname_issubdomain(name, mx->name))
+      return 1;
+
+  for (txt = daemon->txt; txt; txt = txt->next)
+    if (hostname_issubdomain(name, txt->name))
+      return 1;
+
+  for (intr = daemon->int_names; intr; intr = intr->next)
+    if (hostname_issubdomain(name, intr->name))
+      return 1;
+
+  for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+    if (hostname_issubdomain(name, ptr->name))
+      return 1;
+
+  if (cache_find_non_terminal(name, now))
+    return 1;
+
+  return 0;
+}
+
+static int check_bad_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr, char *name, unsigned long *ttlp)
+{
+  unsigned char *p;
+  int i, qtype, qclass, rdlen;
+  unsigned long ttl;
+  struct bogus_addr *baddrp;
+  
+  /* skip over questions */
+  if (!(p = skip_questions(header, qlen)))
+    return 0; /* bad packet */
+
+  for (i = ntohs(header->ancount); i != 0; i--)
+    {
+      if (name && !extract_name(header, qlen, &p, name, 1, 10))
+	return 0; /* bad packet */
+
+      if (!name && !(p = skip_name(p, header, qlen, 10)))
+	return 0;
+      
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+      GETLONG(ttl, p);
+      GETSHORT(rdlen, p);
+
+      if (ttlp)
+	*ttlp = ttl;
+      
+      if (qclass == C_IN)
+	{
+	  if (qtype == T_A)
+	    {
+	      struct in_addr addr;
+	      
+	      if (!CHECK_LEN(header, p, qlen, INADDRSZ))
+		return 0;
+
+	      memcpy(&addr, p, INADDRSZ);
+
+	      for (baddrp = baddr; baddrp; baddrp = baddrp->next)
+		if (!baddrp->is6 && is_same_net_prefix(addr, baddrp->addr.addr4, baddrp->prefix))
+		  return 1;
+	    }
+	  else if (qtype == T_AAAA)
+	    {
+	      struct in6_addr addr;
+	      
+	      if (!CHECK_LEN(header, p, qlen, IN6ADDRSZ))
+		return 0;
+
+	      memcpy(&addr, p, IN6ADDRSZ);
+
+	      for (baddrp = baddr; baddrp; baddrp = baddrp->next)
+		if (baddrp->is6 && is_same_net6(&addr, &baddrp->addr.addr6, baddrp->prefix))
+		  return 1;
+	    }
+	}
+      
+      if (!ADD_RDLEN(header, p, qlen, rdlen))
+	return 0;
+    }
+  
+  return 0;
+}
+
+/* Is the packet a reply with the answer address equal to addr?
+   If so mung is into an NXDOMAIN reply and also put that information
+   in the cache. */
+int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, time_t now)
+{
+  unsigned long ttl;
+
+  if (check_bad_address(header, qlen, daemon->bogus_addr, name, &ttl))
+    {
+      /* Found a bogus address. Insert that info here, since there no SOA record
+	 to get the ttl from in the normal processing */
+      cache_start_insert();
+      cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN);
+      cache_end_insert();
+
+      return 1;
+    }
+
+  return 0;
+}
+
+int check_for_ignored_address(struct dns_header *header, size_t qlen)
+{
+  return check_bad_address(header, qlen, daemon->ignore_addr, NULL, NULL);
+}
+
+int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, 
+			unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
+{
+  va_list ap;
+  unsigned char *sav, *p = *pp;
+  int j;
+  unsigned short usval;
+  long lval;
+  char *sval;
+  
+#define CHECK_LIMIT(size) \
+  if (limit && p + (size) > (unsigned char*)limit) goto truncated;
+
+  va_start(ap, format);   /* make ap point to 1st unamed argument */
+  
+  if (truncp && *truncp)
+    goto truncated;
+  
+  if (nameoffset > 0)
+    {
+      CHECK_LIMIT(2);
+      PUTSHORT(nameoffset | 0xc000, p);
+    }
+  else
+    {
+      char *name = va_arg(ap, char *);
+      if (name && !(p = do_rfc1035_name(p, name, limit)))
+	goto truncated;
+      
+      if (nameoffset < 0)
+	{
+	  CHECK_LIMIT(2);
+	  PUTSHORT(-nameoffset | 0xc000, p);
+	}
+      else
+	{
+	  CHECK_LIMIT(1);
+	  *p++ = 0;
+	}
+    }
+
+  /* type (2) + class (2) + ttl (4) + rdlen (2) */
+  CHECK_LIMIT(10);
+  
+  PUTSHORT(type, p);
+  PUTSHORT(class, p);
+  PUTLONG(ttl, p);      /* TTL */
+
+  sav = p;              /* Save pointer to RDLength field */
+  PUTSHORT(0, p);       /* Placeholder RDLength */
+
+  for (; *format; format++)
+    switch (*format)
+      {
+      case '6':
+        CHECK_LIMIT(IN6ADDRSZ);
+	sval = va_arg(ap, char *); 
+	memcpy(p, sval, IN6ADDRSZ);
+	p += IN6ADDRSZ;
+	break;
+	
+      case '4':
+        CHECK_LIMIT(INADDRSZ);
+	sval = va_arg(ap, char *); 
+	memcpy(p, sval, INADDRSZ);
+	p += INADDRSZ;
+	break;
+	
+      case 'b':
+        CHECK_LIMIT(1);
+	usval = va_arg(ap, int);
+	*p++ = usval;
+	break;
+	
+      case 's':
+        CHECK_LIMIT(2);
+	usval = va_arg(ap, int);
+	PUTSHORT(usval, p);
+	break;
+	
+      case 'l':
+        CHECK_LIMIT(4);
+	lval = va_arg(ap, long);
+	PUTLONG(lval, p);
+	break;
+	
+      case 'd':
+        /* get domain-name answer arg and store it in RDATA field */
+        if (offset)
+          *offset = p - (unsigned char *)header;
+        if (!(p = do_rfc1035_name(p, va_arg(ap, char *), limit)))
+	  goto truncated;
+	CHECK_LIMIT(1);
+        *p++ = 0;
+	break;
+	
+      case 't':
+	usval = va_arg(ap, int);
+        CHECK_LIMIT(usval);
+	sval = va_arg(ap, char *);
+	if (usval != 0)
+	  memcpy(p, sval, usval);
+	p += usval;
+	break;
+
+      case 'z':
+	sval = va_arg(ap, char *);
+	usval = sval ? strlen(sval) : 0;
+	if (usval > 255)
+	  usval = 255;
+        CHECK_LIMIT(usval + 1);
+	*p++ = (unsigned char)usval;
+	memcpy(p, sval, usval);
+	p += usval;
+	break;
+      }
+
+  va_end(ap);	/* clean up variable argument pointer */
+  
+  /* Now, store real RDLength. sav already checked against limit. */
+  j = p - sav - 2;
+  PUTSHORT(j, sav);
+  
+  *pp = p;
+  return 1;
+  
+ truncated:
+  va_end(ap);
+  if (truncp)
+    *truncp = 1;
+  return 0;
+
+#undef CHECK_LIMIT
+}
+
+static unsigned long crec_ttl(struct crec *crecp, time_t now)
+{
+  /* Return 0 ttl for DHCP entries, which might change
+     before the lease expires, unless configured otherwise. */
+
+  if (crecp->flags & F_DHCP)
+    {
+      int conf_ttl = daemon->use_dhcp_ttl ? daemon->dhcp_ttl : daemon->local_ttl;
+      
+      /* Apply ceiling of actual lease length to configured TTL. */
+      if (!(crecp->flags & F_IMMORTAL) && (crecp->ttd - now) < conf_ttl)
+	return crecp->ttd - now;
+      
+      return conf_ttl;
+    }	  
+  
+  /* Immortal entries other than DHCP are local, and hold TTL in TTD field. */
+  if (crecp->flags & F_IMMORTAL)
+    return crecp->ttd;
+
+  /* Return the Max TTL value if it is lower than the actual TTL */
+  if (daemon->max_ttl == 0 || ((unsigned)(crecp->ttd - now) < daemon->max_ttl))
+    return crecp->ttd - now;
+  else
+    return daemon->max_ttl;
+}
+
+static int cache_validated(const struct crec *crecp)
+{
+  return (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK));
+}
+
+/* return zero if we can't answer from cache, or packet size if we can */
+size_t answer_request(struct dns_header *header, char *limit, size_t qlen,  
+		      struct in_addr local_addr, struct in_addr local_netmask, 
+		      time_t now, int ad_reqd, int do_bit, int have_pseudoheader) 
+{
+  char *name = daemon->namebuff;
+  unsigned char *p, *ansp;
+  unsigned int qtype, qclass;
+  union all_addr addr;
+  int nameoffset;
+  unsigned short flag;
+  int q, ans, anscount = 0, addncount = 0;
+  int dryrun = 0;
+  struct crec *crecp;
+  int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1;
+  struct mx_srv_record *rec;
+  size_t len;
+  int rd_bit = (header->hb3 & HB3_RD);
+
+  /* never answer queries with RD unset, to avoid cache snooping. */
+  if (ntohs(header->ancount) != 0 ||
+      ntohs(header->nscount) != 0 ||
+      ntohs(header->qdcount) == 0 ||
+      OPCODE(header) != QUERY )
+    return 0;
+
+  /* Don't return AD set if checking disabled. */
+  if (header->hb4 & HB4_CD)
+    sec_data = 0;
+  
+  /* If there is an  additional data section then it will be overwritten by
+     partial replies, so we have to do a dry run to see if we can answer
+     the query. */
+  if (ntohs(header->arcount) != 0)
+    dryrun = 1;
+
+  for (rec = daemon->mxnames; rec; rec = rec->next)
+    rec->offset = 0;
+  
+ rerun:
+  /* determine end of question section (we put answers there) */
+  if (!(ansp = skip_questions(header, qlen)))
+    return 0; /* bad packet */
+   
+  /* now process each question, answers go in RRs after the question */
+  p = (unsigned char *)(header+1);
+
+  for (q = ntohs(header->qdcount); q != 0; q--)
+    {
+      int count = 255; /* catch loops */
+      
+      /* save pointer to name for copying into answers */
+      nameoffset = p - (unsigned char *)header;
+
+      /* now extract name as .-concatenated string into name */
+      if (!extract_name(header, qlen, &p, name, 1, 4))
+	return 0; /* bad packet */
+            
+      GETSHORT(qtype, p); 
+      GETSHORT(qclass, p);
+
+      ans = 0; /* have we answered this question */
+
+      while (--count != 0 && (crecp = cache_find_by_name(NULL, name, now, F_CNAME)))
+	{
+	  char *cname_target = cache_get_cname_target(crecp);
+
+	  /* If the client asked for DNSSEC  don't use cached data. */
+	  if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+	      (rd_bit && (!do_bit || cache_validated(crecp))))
+	    {
+	      if (crecp->flags & F_CONFIG || qtype == T_CNAME)
+		ans = 1;
+
+	      if (!(crecp->flags & F_DNSSECOK))
+		sec_data = 0;
+
+	      if (!dryrun)
+		{
+		  log_query(crecp->flags, name, NULL, record_source(crecp->uid));
+		  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					  crec_ttl(crecp, now), &nameoffset,
+					  T_CNAME, C_IN, "d", cname_target))
+		    anscount++;
+		}
+
+	    }
+	  else
+	    return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
+
+	  strcpy(name, cname_target);
+	}
+	  
+      if (qtype == T_TXT || qtype == T_ANY)
+	{
+	  struct txt_record *t;
+	  for(t = daemon->txt; t ; t = t->next)
+	    {
+	      if (t->class == qclass && hostname_isequal(name, t->name))
+		{
+		  ans = 1, sec_data = 0;
+		  if (!dryrun)
+		    {
+		      unsigned long ttl = daemon->local_ttl;
+		      int ok = 1;
+#ifndef NO_ID
+		      /* Dynamically generate stat record */
+		      if (t->stat != 0)
+			{
+			  ttl = 0;
+			  if (!cache_make_stat(t))
+			    ok = 0;
+			}
+#endif
+		      if (ok)
+			{
+			  log_query(F_CONFIG | F_RRNAME, name, NULL, "<TXT>");
+			  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+						  ttl, NULL,
+						  T_TXT, t->class, "t", t->len, t->txt))
+			    anscount++;
+			}
+		    }
+		}
+	    }
+	}
+
+      if (qclass == C_CHAOS)
+	{
+	  /* don't forward *.bind and *.server chaos queries - always reply with NOTIMP */
+	  if (hostname_issubdomain("bind", name) || hostname_issubdomain("server", name))
+	    {
+	      if (!ans)
+		{
+		  notimp = 1, auth = 0;
+		  if (!dryrun)
+		    {
+		       addr.log.rcode = NOTIMP;
+		       log_query(F_CONFIG | F_RCODE, name, &addr, NULL);
+		    }
+		  ans = 1, sec_data = 0;
+		}
+	    }
+	}
+
+      if (qclass == C_IN)
+	{
+	  struct txt_record *t;
+
+	  for (t = daemon->rr; t; t = t->next)
+	    if ((t->class == qtype || qtype == T_ANY) && hostname_isequal(name, t->name))
+	      {
+		ans = 1;
+		sec_data = 0;
+		if (!dryrun)
+		  {
+		    log_query(F_CONFIG | F_RRNAME, name, NULL, querystr(NULL, t->class));
+		    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					    daemon->local_ttl, NULL,
+					    t->class, C_IN, "t", t->len, t->txt))
+		      anscount++;
+		  }
+	      }
+		
+	  if (qtype == T_PTR || qtype == T_ANY)
+	    {
+	      /* see if it's w.z.y.z.in-addr.arpa format */
+	      int is_arpa = in_arpa_name_2_addr(name, &addr);
+	      struct ptr_record *ptr;
+	      struct interface_name* intr = NULL;
+
+	      for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+		if (hostname_isequal(name, ptr->name))
+		  break;
+
+	      if (is_arpa == F_IPV4)
+		for (intr = daemon->int_names; intr; intr = intr->next)
+		  {
+		    struct addrlist *addrlist;
+		    
+		    for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+		      if (!(addrlist->flags & ADDRLIST_IPV6) && addr.addr4.s_addr == addrlist->addr.addr4.s_addr)
+			break;
+		    
+		    if (addrlist)
+		      break;
+		    else
+		      while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+			intr = intr->next;
+		  }
+	      else if (is_arpa == F_IPV6)
+		for (intr = daemon->int_names; intr; intr = intr->next)
+		  {
+		    struct addrlist *addrlist;
+		    
+		    for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+		      if ((addrlist->flags & ADDRLIST_IPV6) && IN6_ARE_ADDR_EQUAL(&addr.addr6, &addrlist->addr.addr6))
+			break;
+		    
+		    if (addrlist)
+		      break;
+		    else
+		      while (intr->next && strcmp(intr->intr, intr->next->intr) == 0)
+			intr = intr->next;
+		  }
+	      
+	      if (intr)
+		{
+		  sec_data = 0;
+		  ans = 1;
+		  if (!dryrun)
+		    {
+		      log_query(is_arpa | F_REVERSE | F_CONFIG, intr->name, &addr, NULL);
+		      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					      daemon->local_ttl, NULL,
+					      T_PTR, C_IN, "d", intr->name))
+			anscount++;
+		    }
+		}
+	      else if (ptr)
+		{
+		  ans = 1;
+		  sec_data = 0;
+		  if (!dryrun)
+		    {
+		      log_query(F_CONFIG | F_RRNAME, name, NULL, "<PTR>");
+		      for (ptr = daemon->ptr; ptr; ptr = ptr->next)
+			if (hostname_isequal(name, ptr->name) &&
+			    add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+						daemon->local_ttl, NULL,
+						T_PTR, C_IN, "d", ptr->ptr))
+			  anscount++;
+			 
+		    }
+		}
+	      else if ((crecp = cache_find_by_addr(NULL, &addr, now, is_arpa)))
+		{
+		  /* Don't use cache when DNSSEC data required, unless we know that
+		     the zone is unsigned, which implies that we're doing
+		     validation. */
+		  if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+		      (rd_bit && (!do_bit || cache_validated(crecp)) ))
+		    {
+		      do 
+			{ 
+			  /* don't answer wildcard queries with data not from /etc/hosts or dhcp leases */
+			  if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP)))
+			    continue;
+			  
+			  if (!(crecp->flags & F_DNSSECOK))
+			    sec_data = 0;
+			   
+			  ans = 1;
+			   
+			  if (crecp->flags & F_NEG)
+			    {
+			      auth = 0;
+			      if (crecp->flags & F_NXDOMAIN)
+				nxdomain = 1;
+			      if (!dryrun)
+				log_query(crecp->flags & ~F_FORWARD, name, &addr, NULL);
+			    }
+			  else
+			    {
+			      if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+				auth = 0;
+			      if (!dryrun)
+				{
+				  log_query(crecp->flags & ~F_FORWARD, cache_get_name(crecp), &addr, 
+					    record_source(crecp->uid));
+				  
+				  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+							  crec_ttl(crecp, now), NULL,
+							  T_PTR, C_IN, "d", cache_get_name(crecp)))
+				    anscount++;
+				}
+			    }
+			} while ((crecp = cache_find_by_addr(crecp, &addr, now, is_arpa)));
+		    }
+		}
+	      else if (is_rev_synth(is_arpa, &addr, name))
+		{
+		  ans = 1;
+		  sec_data = 0;
+		  if (!dryrun)
+		    {
+		      log_query(F_CONFIG | F_REVERSE | is_arpa, name, &addr, NULL); 
+		      
+		      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					      daemon->local_ttl, NULL,
+					      T_PTR, C_IN, "d", name))
+			      anscount++;
+		    }
+		}
+	      else if (option_bool(OPT_BOGUSPRIV) &&
+		       ((is_arpa == F_IPV6 && private_net6(&addr.addr6, 1)) || (is_arpa == F_IPV4 && private_net(addr.addr4, 1))) &&
+		       !lookup_domain(name, F_DOMAINSRV, NULL, NULL))
+		{
+		  /* if no configured server, not in cache, enabled and private IPV4 address, return NXDOMAIN */
+		  ans = 1;
+		  sec_data = 0;
+		  nxdomain = 1;
+		  if (!dryrun)
+		    log_query(F_CONFIG | F_REVERSE | is_arpa | F_NEG | F_NXDOMAIN,
+			      name, &addr, NULL);
+		}
+	    }
+
+	  for (flag = F_IPV4; flag; flag = (flag == F_IPV4) ? F_IPV6 : 0)
+	    {
+	      unsigned short type = (flag == F_IPV6) ? T_AAAA : T_A;
+	      struct interface_name *intr;
+
+	      if (qtype != type && qtype != T_ANY)
+		continue;
+	      
+	      /* interface name stuff */
+	      for (intr = daemon->int_names; intr; intr = intr->next)
+		if (hostname_isequal(name, intr->name))
+		  break;
+	      
+	      if (intr)
+		{
+		  struct addrlist *addrlist;
+		  int gotit = 0, localise = 0;
+
+		  enumerate_interfaces(0);
+		    
+		  /* See if a putative address is on the network from which we received
+		     the query, is so we'll filter other answers. */
+		  if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && type == T_A)
+		    for (intr = daemon->int_names; intr; intr = intr->next)
+		      if (hostname_isequal(name, intr->name))
+			for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+			  if (!(addrlist->flags & ADDRLIST_IPV6) && 
+			      is_same_net(addrlist->addr.addr4, local_addr, local_netmask))
+			    {
+			      localise = 1;
+			      break;
+			    }
+		  
+		  for (intr = daemon->int_names; intr; intr = intr->next)
+		    if (hostname_isequal(name, intr->name))
+		      {
+			for (addrlist = intr->addr; addrlist; addrlist = addrlist->next)
+			  if (((addrlist->flags & ADDRLIST_IPV6) ? T_AAAA : T_A) == type)
+			    {
+			      if (localise && 
+				  !is_same_net(addrlist->addr.addr4, local_addr, local_netmask))
+				continue;
+
+			      if (addrlist->flags & ADDRLIST_REVONLY)
+				continue;
+
+			      ans = 1;	
+			      sec_data = 0;
+			      if (!dryrun)
+				{
+				  gotit = 1;
+				  log_query(F_FORWARD | F_CONFIG | flag, name, &addrlist->addr, NULL);
+				  if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+							  daemon->local_ttl, NULL, type, C_IN, 
+							  type == T_A ? "4" : "6", &addrlist->addr))
+				    anscount++;
+				}
+			    }
+		      }
+		  
+		  if (!dryrun && !gotit)
+		    log_query(F_FORWARD | F_CONFIG | flag | F_NEG, name, NULL, NULL);
+		     
+		  continue;
+		}
+
+	      if ((crecp = cache_find_by_name(NULL, name, now, flag | (dryrun ? F_NO_RR : 0))))
+		{
+		  int localise = 0;
+		  
+		  /* See if a putative address is on the network from which we received
+		     the query, is so we'll filter other answers. */
+		  if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
+		    {
+		      struct crec *save = crecp;
+		      do {
+			if ((crecp->flags & F_HOSTS) &&
+			    is_same_net(crecp->addr.addr4, local_addr, local_netmask))
+			  {
+			    localise = 1;
+			    break;
+			  } 
+			} while ((crecp = cache_find_by_name(crecp, name, now, flag)));
+		      crecp = save;
+		    }
+
+		  /* If the client asked for DNSSEC  don't use cached data. */
+		  if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) ||
+		      (rd_bit && (!do_bit || cache_validated(crecp)) ))
+		    do
+		      { 
+			/* don't answer wildcard queries with data not from /etc/hosts
+			   or DHCP leases */
+			if (qtype == T_ANY && !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)))
+			  break;
+			
+			if (!(crecp->flags & F_DNSSECOK))
+			  sec_data = 0;
+			
+			if (crecp->flags & F_NEG)
+			  {
+			    ans = 1;
+			    auth = 0;
+			    if (crecp->flags & F_NXDOMAIN)
+			      nxdomain = 1;
+			    if (!dryrun)
+			      log_query(crecp->flags, name, NULL, NULL);
+			  }
+			else 
+			  {
+			    /* If we are returning local answers depending on network,
+			       filter here. */
+			    if (localise && 
+				(crecp->flags & F_HOSTS) &&
+				!is_same_net(crecp->addr.addr4, local_addr, local_netmask))
+			      continue;
+			    
+			    if (!(crecp->flags & (F_HOSTS | F_DHCP)))
+			      auth = 0;
+			    
+			    ans = 1;
+			    if (!dryrun)
+			      {
+				log_query(crecp->flags & ~F_REVERSE, name, &crecp->addr,
+					  record_source(crecp->uid));
+				
+				if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+							crec_ttl(crecp, now), NULL, type, C_IN, 
+							type == T_A ? "4" : "6", &crecp->addr))
+				  anscount++;
+			      }
+			  }
+		      } while ((crecp = cache_find_by_name(crecp, name, now, flag)));
+		}
+	      else if (is_name_synthetic(flag, name, &addr))
+		{
+		  ans = 1, sec_data = 0;
+		  if (!dryrun)
+		    {
+		      log_query(F_FORWARD | F_CONFIG | flag, name, &addr, NULL);
+		      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+					      daemon->local_ttl, NULL, type, C_IN, type == T_A ? "4" : "6", &addr))
+			anscount++;
+		    }
+		}
+	    }
+
+	  if (qtype == T_MX || qtype == T_ANY)
+	    {
+	      int found = 0;
+	      for (rec = daemon->mxnames; rec; rec = rec->next)
+		if (!rec->issrv && hostname_isequal(name, rec->name))
+		  {
+		    ans = found = 1;
+		    sec_data = 0;
+		    if (!dryrun)
+		      {
+			int offset;
+			log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
+			if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl,
+						&offset, T_MX, C_IN, "sd", rec->weight, rec->target))
+			  {
+			    anscount++;
+			    if (rec->target)
+			      rec->offset = offset;
+			  }
+		      }
+		  }
+	      
+	      if (!found && (option_bool(OPT_SELFMX) || option_bool(OPT_LOCALMX)) &&
+		  cache_find_by_name(NULL, name, now, F_HOSTS | F_DHCP | F_NO_RR))
+		{ 
+		  ans = 1;
+		  sec_data = 0;
+		  if (!dryrun)
+		    {
+		      log_query(F_CONFIG | F_RRNAME, name, NULL, "<MX>");
+		      if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, NULL, 
+					      T_MX, C_IN, "sd", 1, 
+					      option_bool(OPT_SELFMX) ? name : daemon->mxtarget))
+			anscount++;
+		    }
+		}
+	    }
+	  	  
+	  if (qtype == T_SRV || qtype == T_ANY)
+	    {
+	      int found = 0;
+	      struct mx_srv_record *move = NULL, **up = &daemon->mxnames;
+
+	      for (rec = daemon->mxnames; rec; rec = rec->next)
+		if (rec->issrv && hostname_isequal(name, rec->name))
+		  {
+		    found = ans = 1;
+		    sec_data = 0;
+		    if (!dryrun)
+		      {
+			int offset;
+			log_query(F_CONFIG | F_RRNAME, name, NULL, "<SRV>");
+			if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, 
+						&offset, T_SRV, C_IN, "sssd", 
+						rec->priority, rec->weight, rec->srvport, rec->target))
+			  {
+			    anscount++;
+			    if (rec->target)
+			      rec->offset = offset;
+			  }
+		      }
+		    
+		    /* unlink first SRV record found */
+		    if (!move)
+		      {
+			move = rec;
+			*up = rec->next;
+		      }
+		    else
+		      up = &rec->next;      
+		  }
+		else
+		  up = &rec->next;
+
+	      /* put first SRV record back at the end. */
+	      if (move)
+		{
+		  *up = move;
+		  move->next = NULL;
+		}
+
+	      if (!found)
+		{
+		  if ((crecp = cache_find_by_name(NULL, name, now, F_SRV | (dryrun ? F_NO_RR : 0))) &&
+		      rd_bit && (!do_bit || (option_bool(OPT_DNSSEC_VALID) && !(crecp->flags & F_DNSSECOK))))
+		    {
+		      if (!(crecp->flags & F_DNSSECOK))
+			sec_data = 0;
+		      
+		      auth = 0;
+		      found = ans = 1;
+		      
+		      do {
+			if (crecp->flags & F_NEG)
+			  {
+			    if (crecp->flags & F_NXDOMAIN)
+			      nxdomain = 1;
+			    if (!dryrun)
+			      log_query(crecp->flags, name, NULL, NULL);
+			  }
+			else if (!dryrun)
+			  {
+			    char *target = blockdata_retrieve(crecp->addr.srv.target, crecp->addr.srv.targetlen, NULL);
+			    log_query(crecp->flags, name, NULL, 0);
+			    
+			    if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, 
+						    crec_ttl(crecp, now), NULL, T_SRV, C_IN, "sssd",
+						    crecp->addr.srv.priority, crecp->addr.srv.weight, crecp->addr.srv.srvport,
+						    target))
+			      anscount++;
+			  }
+		      } while ((crecp = cache_find_by_name(crecp, name, now, F_SRV)));
+		    }
+		}
+
+	      if (!found && option_bool(OPT_FILTER) && (qtype == T_SRV || (qtype == T_ANY && strchr(name, '_'))))
+		{
+		  ans = 1;
+		  sec_data = 0;
+		  if (!dryrun)
+		    log_query(F_CONFIG | F_NEG, name, NULL, NULL);
+		}
+	    }
+
+	  if (qtype == T_NAPTR || qtype == T_ANY)
+	    {
+	      struct naptr *na;
+	      for (na = daemon->naptr; na; na = na->next)
+		if (hostname_isequal(name, na->name))
+		  {
+		    ans = 1;
+		    sec_data = 0;
+		    if (!dryrun)
+		      {
+			log_query(F_CONFIG | F_RRNAME, name, NULL, "<NAPTR>");
+			if (add_resource_record(header, limit, &trunc, nameoffset, &ansp, daemon->local_ttl, 
+						NULL, T_NAPTR, C_IN, "sszzzd", 
+						na->order, na->pref, na->flags, na->services, na->regexp, na->replace))
+			  anscount++;
+		      }
+		  }
+	    }
+	  
+	  if (qtype == T_MAILB)
+	    ans = 1, nxdomain = 1, sec_data = 0;
+
+	  if (qtype == T_SOA && option_bool(OPT_FILTER))
+	    {
+	      ans = 1;
+	      sec_data = 0;
+	      if (!dryrun)
+		log_query(F_CONFIG | F_NEG, name, &addr, NULL);
+	    }
+	}
+
+      if (!ans)
+	return 0; /* failed to answer a question */
+    }
+  
+  if (dryrun)
+    {
+      dryrun = 0;
+      goto rerun;
+    }
+  
+  /* create an additional data section, for stuff in SRV and MX record replies. */
+  for (rec = daemon->mxnames; rec; rec = rec->next)
+    if (rec->offset != 0)
+      {
+	/* squash dupes */
+	struct mx_srv_record *tmp;
+	for (tmp = rec->next; tmp; tmp = tmp->next)
+	  if (tmp->offset != 0 && hostname_isequal(rec->target, tmp->target))
+	    tmp->offset = 0;
+	
+	crecp = NULL;
+	while ((crecp = cache_find_by_name(crecp, rec->target, now, F_IPV4 | F_IPV6)))
+	  {
+	    int type =  crecp->flags & F_IPV4 ? T_A : T_AAAA;
+
+	    if (crecp->flags & F_NEG)
+	      continue;
+
+	    if (add_resource_record(header, limit, NULL, rec->offset, &ansp, 
+				    crec_ttl(crecp, now), NULL, type, C_IN, 
+				    crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr))
+	      addncount++;
+	  }
+      }
+  
+  /* done all questions, set up header and return length of result */
+  /* clear authoritative and truncated flags, set QR flag */
+  header->hb3 = (header->hb3 & ~(HB3_AA | HB3_TC)) | HB3_QR;
+  /* set RA flag */
+  header->hb4 |= HB4_RA;
+   
+  /* authoritative - only hosts and DHCP derived names. */
+  if (auth)
+    header->hb3 |= HB3_AA;
+  
+  /* truncation */
+  if (trunc)
+    header->hb3 |= HB3_TC;
+  
+  if (nxdomain)
+    SET_RCODE(header, NXDOMAIN);
+  else if (notimp)
+    SET_RCODE(header, NOTIMP);
+  else
+    SET_RCODE(header, NOERROR); /* no error */
+  header->ancount = htons(anscount);
+  header->nscount = htons(0);
+  header->arcount = htons(addncount);
+
+  len = ansp - (unsigned char *)header;
+  
+  /* Advertise our packet size limit in our reply */
+  if (have_pseudoheader)
+    len = add_pseudoheader(header, len, (unsigned char *)limit, daemon->edns_pktsz, 0, NULL, 0, do_bit, 0);
+  
+  if (ad_reqd && sec_data)
+    header->hb4 |= HB4_AD;
+  else
+    header->hb4 &= ~HB4_AD;
+  
+  return len;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c
new file mode 100755
index 0000000..c902eb7
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc2131.c
@@ -0,0 +1,2810 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_DHCP
+
+#define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
+#define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
+
+#ifdef HAVE_SCRIPT
+static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt);
+#endif
+
+static int sanitise(unsigned char *opt, char *buf);
+static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback);
+static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
+static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val);
+static void option_put_string(struct dhcp_packet *mess, unsigned char *end, 
+			      int opt, const char *string, int null_term);
+static struct in_addr option_addr(unsigned char *opt);
+static unsigned int option_uint(unsigned char *opt, int offset, int size);
+static void log_packet(char *type, void *addr, unsigned char *ext_mac, 
+		       int mac_len, char *interface, char *string, char *err, u32 xid);
+static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize);
+static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize);
+static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end);
+static void clear_packet(struct dhcp_packet *mess, unsigned char *end);
+static int in_list(unsigned char *list, int opt);
+static void do_options(struct dhcp_context *context,
+		       struct dhcp_packet *mess,
+		       unsigned char *end,
+		       unsigned char *req_options,
+		       char *hostname, 
+		       char *domain,
+		       struct dhcp_netid *netid,
+		       struct in_addr subnet_addr, 
+		       unsigned char fqdn_flags,
+		       int null_term, int pxe_arch,
+		       unsigned char *uuid,
+		       int vendor_class_len,
+		       time_t now,
+		       unsigned int lease_time,
+		       unsigned short fuzz,
+		       const char *pxevendor);
+
+
+static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); 
+static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor);
+static int prune_vendor_opts(struct dhcp_netid *netid);
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now);
+struct dhcp_boot *find_boot(struct dhcp_netid *netid);
+static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
+static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
+
+size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
+		  size_t sz, time_t now, int unicast_dest, int loopback,
+		  int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
+{
+  unsigned char *opt, *clid = NULL;
+  struct dhcp_lease *ltmp, *lease = NULL;
+  struct dhcp_vendor *vendor;
+  struct dhcp_mac *mac;
+  struct dhcp_netid_list *id_list;
+  int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1;
+  const char *pxevendor = NULL;
+  struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+  unsigned char *end = (unsigned char *)(mess + 1); 
+  unsigned char *real_end = (unsigned char *)(mess + 1); 
+  char *hostname = NULL, *offer_hostname = NULL, *client_hostname = NULL, *domain = NULL;
+  int hostname_auth = 0, borken_opt = 0;
+  unsigned char *req_options = NULL;
+  char *message = NULL;
+  unsigned int time;
+  struct dhcp_config *config;
+  struct dhcp_netid *netid, *tagif_netid;
+  struct in_addr subnet_addr, override;
+  unsigned short fuzz = 0;
+  unsigned int mess_type = 0;
+  unsigned char fqdn_flags = 0;
+  unsigned char *agent_id = NULL, *uuid = NULL;
+  unsigned char *emac = NULL;
+  int vendor_class_len = 0, emac_len = 0;
+  struct dhcp_netid known_id, iface_id, cpewan_id;
+  struct dhcp_opt *o;
+  unsigned char pxe_uuid[17];
+  unsigned char *oui = NULL, *serial = NULL;
+#ifdef HAVE_SCRIPT
+  unsigned char *class = NULL;
+#endif
+
+  subnet_addr.s_addr = override.s_addr = 0;
+
+  /* set tag with name == interface */
+  iface_id.net = iface_name;
+  iface_id.next = NULL;
+  netid = &iface_id; 
+  
+  if (mess->op != BOOTREQUEST || mess->hlen > DHCP_CHADDR_MAX)
+    return 0;
+   
+  if (mess->htype == 0 && mess->hlen != 0)
+    return 0;
+
+  /* check for DHCP rather than BOOTP */
+  if ((opt = option_find(mess, sz, OPTION_MESSAGE_TYPE, 1)))
+    {
+      u32 cookie = htonl(DHCP_COOKIE);
+      
+      /* only insist on a cookie for DHCP. */
+      if (memcmp(mess->options, &cookie, sizeof(u32)) != 0)
+	return 0;
+      
+      mess_type = option_uint(opt, 0, 1);
+      
+      /* two things to note here: expand_buf may move the packet,
+	 so reassign mess from daemon->packet. Also, the size
+	 sent includes the IP and UDP headers, hence the magic "-28" */
+      if ((opt = option_find(mess, sz, OPTION_MAXMESSAGE, 2)))
+	{
+	  size_t size = (size_t)option_uint(opt, 0, 2) - 28;
+	  
+	  if (size > DHCP_PACKET_MAX)
+	    size = DHCP_PACKET_MAX;
+	  else if (size < sizeof(struct dhcp_packet))
+	    size = sizeof(struct dhcp_packet);
+	  
+	  if (expand_buf(&daemon->dhcp_packet, size))
+	    {
+	      mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+	      real_end = end = ((unsigned char *)mess) + size;
+	    }
+	}
+
+      /* Some buggy clients set ciaddr when they shouldn't, so clear that here since
+	 it can affect the context-determination code. */
+      if ((option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ) || mess_type == DHCPDISCOVER))
+	mess->ciaddr.s_addr = 0;
+
+      /* search for device identity from CPEWAN devices, we pass this through to the script */
+      if ((opt = option_find(mess, sz, OPTION_VENDOR_IDENT_OPT, 5)))
+	{
+	  unsigned  int elen, offset, len = option_len(opt);
+	  
+	  for (offset = 0; offset < (len - 5); offset += elen + 5)
+	    {
+	      elen = option_uint(opt, offset + 4 , 1);
+	      if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA && offset + elen + 5 <= len)
+		{
+		  unsigned char *x = option_ptr(opt, offset + 5);
+		  unsigned char *y = option_ptr(opt, offset + elen + 5);
+		  oui = option_find1(x, y, 1, 1);
+		  serial = option_find1(x, y, 2, 1);
+#ifdef HAVE_SCRIPT
+		  class = option_find1(x, y, 3, 1);		  
+#endif
+		  /* If TR069-id is present set the tag "cpewan-id" to facilitate echoing 
+		     the gateway id back. Note that the device class is optional */
+		  if (oui && serial)
+		    {
+		      cpewan_id.net = "cpewan-id";
+		      cpewan_id.next = netid;
+		      netid = &cpewan_id;
+		    }
+		  break;
+		}
+	    }
+	}
+      
+      if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
+	{
+	  /* Any agent-id needs to be copied back out, verbatim, as the last option
+	     in the packet. Here, we shift it to the very end of the buffer, if it doesn't
+	     get overwritten, then it will be shuffled back at the end of processing.
+	     Note that the incoming options must not be overwritten here, so there has to 
+	     be enough free space at the end of the packet to copy the option. */
+	  unsigned char *sopt;
+	  unsigned int total = option_len(opt) + 2;
+	  unsigned char *last_opt = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + sz,
+						 OPTION_END, 0);
+	  if (last_opt && last_opt < end - total)
+	    {
+	      end -= total;
+	      agent_id = end;
+	      memcpy(agent_id, opt, total);
+	    }
+
+	  /* look for RFC3527 Link selection sub-option */
+	  if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBNET_SELECT, INADDRSZ)))
+	    subnet_addr = option_addr(sopt);
+
+	  /* look for RFC5107 server-identifier-override */
+	  if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SERVER_OR, INADDRSZ)))
+	    override = option_addr(sopt);
+	  
+	  /* if a circuit-id or remote-is option is provided, exact-match to options. */ 
+	  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+	    {
+	      int search;
+	      
+	      if (vendor->match_type == MATCH_CIRCUIT)
+		search = SUBOPT_CIRCUIT_ID;
+	      else if (vendor->match_type == MATCH_REMOTE)
+		search = SUBOPT_REMOTE_ID;
+	      else if (vendor->match_type == MATCH_SUBSCRIBER)
+		search = SUBOPT_SUBSCR_ID;
+	      else 
+		continue;
+
+	      if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), search, 1)) &&
+		  vendor->len == option_len(sopt) &&
+		  memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0)
+		{
+		  vendor->netid.next = netid;
+		  netid = &vendor->netid;
+		} 
+	    }
+	}
+
+      /* Check for RFC3011 subnet selector - only if RFC3527 one not present */
+      if (subnet_addr.s_addr == 0 && (opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
+	subnet_addr = option_addr(opt);
+      
+      /* If there is no client identifier option, use the hardware address */
+      if (!option_bool(OPT_IGNORE_CLID) && (opt = option_find(mess, sz, OPTION_CLIENT_ID, 1)))
+	{
+	  clid_len = option_len(opt);
+	  clid = option_ptr(opt, 0);
+	}
+
+      /* do we have a lease in store? */
+      lease = lease_find_by_client(mess->chaddr, mess->hlen, mess->htype, clid, clid_len);
+
+      /* If this request is missing a clid, but we've seen one before, 
+	 use it again for option matching etc. */
+      if (lease && !clid && lease->clid)
+	{
+	  clid_len = lease->clid_len;
+	  clid = lease->clid;
+	}
+
+      /* find mac to use for logging and hashing */
+      emac = extended_hwaddr(mess->htype, mess->hlen, mess->chaddr, clid_len, clid, &emac_len);
+    }
+  
+  for (mac = daemon->dhcp_macs; mac; mac = mac->next)
+    if (mac->hwaddr_len == mess->hlen &&
+	(mac->hwaddr_type == mess->htype || mac->hwaddr_type == 0) &&
+	memcmp_masked(mac->hwaddr, mess->chaddr, mess->hlen, mac->mask))
+      {
+	mac->netid.next = netid;
+	netid = &mac->netid;
+      }
+  
+  /* Determine network for this packet. Our caller will have already linked all the 
+     contexts which match the addresses of the receiving interface but if the 
+     machine has an address already, or came via a relay, or we have a subnet selector, 
+     we search again. If we don't have have a giaddr or explicit subnet selector, 
+     use the ciaddr. This is necessary because a  machine which got a lease via a 
+     relay won't use the relay to renew. If matching a ciaddr fails but we have a context 
+     from the physical network, continue using that to allow correct DHCPNAK generation later. */
+  if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr)
+    {
+      struct dhcp_context *context_tmp, *context_new = NULL;
+      struct shared_network *share = NULL;
+      struct in_addr addr;
+      int force = 0, via_relay = 0;
+      
+      if (subnet_addr.s_addr)
+	{
+	  addr = subnet_addr;
+	  force = 1;
+	}
+      else if (mess->giaddr.s_addr)
+	{
+	  addr = mess->giaddr;
+	  force = 1;
+	  via_relay = 1;
+	}
+      else
+	{
+	  /* If ciaddr is in the hardware derived set of contexts, leave that unchanged */
+	  addr = mess->ciaddr;
+	  for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
+	    if (context_tmp->netmask.s_addr && 
+		is_same_net(addr, context_tmp->start, context_tmp->netmask) &&
+		is_same_net(addr, context_tmp->end, context_tmp->netmask))
+	      {
+		context_new = context;
+		break;
+	      }
+	} 
+		
+      if (!context_new)
+	{
+	  for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next)
+	    {
+	      struct in_addr netmask = context_tmp->netmask;
+	      
+	      /* guess the netmask for relayed networks */
+	      if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0)
+		{
+		  if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr)))
+		    netmask.s_addr = htonl(0xff000000);
+		  else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr)))
+		    netmask.s_addr = htonl(0xffff0000);
+		  else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr)))
+		    netmask.s_addr = htonl(0xffffff00); 
+		}
+
+	      /* check to see is a context is OK because of a shared address on
+		 the relayed subnet. */
+	      if (via_relay)
+		for (share = daemon->shared_networks; share; share = share->next)
+		  {
+#ifdef HAVE_DHCP6
+		    if (share->shared_addr.s_addr == 0)
+		      continue;
+#endif
+		    if (share->if_index != 0 ||
+			share->match_addr.s_addr != mess->giaddr.s_addr)
+		      continue;
+		    
+		    if (netmask.s_addr != 0  && 
+			is_same_net(share->shared_addr, context_tmp->start, netmask) &&
+			is_same_net(share->shared_addr, context_tmp->end, netmask))
+		      break;
+		  }
+	      
+	      /* This section fills in context mainly when a client which is on a remote (relayed)
+		 network renews a lease without using the relay, after dnsmasq has restarted. */
+	      if (share ||
+		  (netmask.s_addr != 0  && 
+		   is_same_net(addr, context_tmp->start, netmask) &&
+		   is_same_net(addr, context_tmp->end, netmask)))
+		{
+		  context_tmp->netmask = netmask;
+		  if (context_tmp->local.s_addr == 0)
+		    context_tmp->local = fallback;
+		  if (context_tmp->router.s_addr == 0 && !share)
+		    context_tmp->router = mess->giaddr;
+		  
+		  /* fill in missing broadcast addresses for relayed ranges */
+		  if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 )
+		    context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr;
+		  
+		  context_tmp->current = context_new;
+		  context_new = context_tmp;
+		}
+	      
+	    }
+	}
+	  
+      if (context_new || force)
+	context = context_new; 
+    }
+  
+  if (!context)
+    {
+      const char *via;
+      if (subnet_addr.s_addr)
+	{
+	  via = _("with subnet selector");
+	  inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
+	}
+      else
+	{
+	  via = _("via");
+	  if (mess->giaddr.s_addr)
+	    inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
+	  else
+	    safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
+	}
+      my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
+		via, daemon->addrbuff);
+      return 0;
+    }
+
+  if (option_bool(OPT_LOG_OPTS))
+    {
+      struct dhcp_context *context_tmp;
+      for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
+	{
+	  inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
+	  if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+	    {
+	      inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
+	      my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
+			ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+	    }
+	  else
+	    {
+	      inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
+	      my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
+			ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+	    }
+	}
+    }
+  
+  /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match.
+     Otherwise assume the option is an array, and look for a matching element. 
+     If no data given, existence of the option is enough. This code handles 
+     rfc3925 V-I classes too. */
+  for (o = daemon->dhcp_match; o; o = o->next)
+    {
+      unsigned int len, elen, match = 0;
+      size_t offset, o2;
+
+      if (o->flags & DHOPT_RFC3925)
+	{
+	  if (!(opt = option_find(mess, sz, OPTION_VENDOR_IDENT, 5)))
+	    continue;
+	  
+	  for (offset = 0; offset < (option_len(opt) - 5u); offset += len + 5)
+	    {
+	      len = option_uint(opt, offset + 4 , 1);
+	      /* Need to take care that bad data can't run us off the end of the packet */
+	      if ((offset + len + 5 <= (unsigned)(option_len(opt))) &&
+		  (option_uint(opt, offset, 4) == (unsigned int)o->u.encap))
+		for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1)
+		  { 
+		    elen = option_uint(opt, o2, 1);
+		    if ((o2 + elen + 1 <= (unsigned)option_len(opt)) &&
+			(match = match_bytes(o, option_ptr(opt, o2 + 1), elen)))
+		      break;
+		  }
+	      if (match) 
+		break;
+	    }	  
+	}
+      else
+	{
+	  if (!(opt = option_find(mess, sz, o->opt, 1)))
+	    continue;
+	  
+	  match = match_bytes(o, option_ptr(opt, 0), option_len(opt));
+	} 
+
+      if (match)
+	{
+	  o->netid->next = netid;
+	  netid = o->netid;
+	}
+    }
+	
+  /* user-class options are, according to RFC3004, supposed to contain
+     a set of counted strings. Here we check that this is so (by seeing
+     if the counts are consistent with the overall option length) and if
+     so zero the counts so that we don't get spurious matches between 
+     the vendor string and the counts. If the lengths don't add up, we
+     assume that the option is a single string and non RFC3004 compliant 
+     and just do the substring match. dhclient provides these broken options.
+     The code, later, which sends user-class data to the lease-change script
+     relies on the transformation done here.
+  */
+
+  if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
+    {
+      unsigned char *ucp = option_ptr(opt, 0);
+      int tmp, j;
+      for (j = 0; j < option_len(opt); j += ucp[j] + 1);
+      if (j == option_len(opt))
+	for (j = 0; j < option_len(opt); j = tmp)
+	  {
+	    tmp = j + ucp[j] + 1;
+	    ucp[j] = 0;
+	  }
+    }
+    
+  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+    {
+      int mopt;
+      
+      if (vendor->match_type == MATCH_VENDOR)
+	mopt = OPTION_VENDOR_ID;
+      else if (vendor->match_type == MATCH_USER)
+	mopt = OPTION_USER_CLASS; 
+      else
+	continue;
+
+      if ((opt = option_find(mess, sz, mopt, 1)))
+	{
+	  int i;
+	  for (i = 0; i <= (option_len(opt) - vendor->len); i++)
+	    if (memcmp(vendor->data, option_ptr(opt, i), vendor->len) == 0)
+	      {
+		vendor->netid.next = netid;
+		netid = &vendor->netid;
+		break;
+	      }
+	}
+    }
+
+  /* mark vendor-encapsulated options which match the client-supplied vendor class,
+     save client-supplied vendor class */
+  if ((opt = option_find(mess, sz, OPTION_VENDOR_ID, 1)))
+    {
+      memcpy(daemon->dhcp_buff3, option_ptr(opt, 0), option_len(opt));
+      vendor_class_len = option_len(opt);
+    }
+  match_vendor_opts(opt, daemon->dhcp_opts);
+  
+  if (option_bool(OPT_LOG_OPTS))
+    {
+      if (sanitise(opt, daemon->namebuff))
+	my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %s"), ntohl(mess->xid), daemon->namebuff);
+      if (sanitise(option_find(mess, sz, OPTION_USER_CLASS, 1), daemon->namebuff))
+	my_syslog(MS_DHCP | LOG_INFO, _("%u user class: %s"), ntohl(mess->xid), daemon->namebuff);
+    }
+
+  mess->op = BOOTREPLY;
+  
+  config = find_config(daemon->dhcp_conf, context, clid, clid_len, 
+		       mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid));
+
+  /* set "known" tag for known hosts */
+  if (config)
+    {
+      known_id.net = "known";
+      known_id.next = netid;
+      netid = &known_id;
+    }
+  else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, 
+		       mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid)))
+    {
+      known_id.net = "known-othernet";
+      known_id.next = netid;
+      netid = &known_id;
+    }
+  
+  if (mess_type == 0 && !pxe)
+    {
+      /* BOOTP request */
+      struct dhcp_netid id, bootp_id;
+      struct in_addr *logaddr = NULL;
+
+      /* must have a MAC addr for bootp */
+      if (mess->htype == 0 || mess->hlen == 0 || (context->flags & CONTEXT_PROXY))
+	return 0;
+      
+      if (have_config(config, CONFIG_DISABLE))
+	message = _("disabled");
+
+      end = mess->options + 64; /* BOOTP vend area is only 64 bytes */
+            
+      if (have_config(config, CONFIG_NAME))
+	{
+	  hostname = config->hostname;
+	  domain = config->domain;
+	}
+
+      if (config)
+	{
+	  struct dhcp_netid_list *list;
+
+	  for (list = config->netid; list; list = list->next)
+	    {
+	      list->list->next = netid;
+	      netid = list->list;
+	    }
+	}
+
+      /* Match incoming filename field as a netid. */
+      if (mess->file[0])
+	{
+	  memcpy(daemon->dhcp_buff2, mess->file, sizeof(mess->file));
+	  daemon->dhcp_buff2[sizeof(mess->file) + 1] = 0; /* ensure zero term. */
+	  id.net = (char *)daemon->dhcp_buff2;
+	  id.next = netid;
+	  netid = &id;
+	}
+
+      /* Add "bootp" as a tag to allow different options, address ranges etc
+	 for BOOTP clients */
+      bootp_id.net = "bootp";
+      bootp_id.next = netid;
+      netid = &bootp_id;
+      
+      tagif_netid = run_tag_if(netid);
+
+      for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
+	if (match_netid(id_list->list, tagif_netid, 0))
+	  message = _("ignored");
+      
+      if (!message)
+	{
+	  int nailed = 0;
+
+	  if (have_config(config, CONFIG_ADDR))
+	    {
+	      nailed = 1;
+	      logaddr = &config->addr;
+	      mess->yiaddr = config->addr;
+	      if ((lease = lease_find_by_addr(config->addr)) &&
+		  (lease->hwaddr_len != mess->hlen ||
+		   lease->hwaddr_type != mess->htype ||
+		   memcmp(lease->hwaddr, mess->chaddr, lease->hwaddr_len) != 0))
+		message = _("address in use");
+	    }
+	  else
+	    {
+	      if (!(lease = lease_find_by_client(mess->chaddr, mess->hlen, mess->htype, NULL, 0)) ||
+		  !address_available(context, lease->addr, tagif_netid))
+		{
+		   if (lease)
+		     {
+		       /* lease exists, wrong network. */
+		       lease_prune(lease, now);
+		       lease = NULL;
+		     }
+		   if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now, loopback))
+		     message = _("no address available");
+		}
+	      else
+		mess->yiaddr = lease->addr;
+	    }
+	  
+	  if (!message && !(context = narrow_context(context, mess->yiaddr, netid)))
+	    message = _("wrong network");
+	  else if (context->netid.net)
+	    {
+	      context->netid.next = netid;
+	      tagif_netid = run_tag_if(&context->netid);
+	    }
+
+	  log_tags(tagif_netid, ntohl(mess->xid));
+	    
+	  if (!message && !nailed)
+	    {
+	      for (id_list = daemon->bootp_dynamic; id_list; id_list = id_list->next)
+		if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0))
+		  break;
+	      if (!id_list)
+		message = _("no address configured");
+	    }
+
+	  if (!message && 
+	      !lease && 
+	      (!(lease = lease4_allocate(mess->yiaddr))))
+	    message = _("no leases left");
+	  
+	  if (!message)
+	    {
+	      logaddr = &mess->yiaddr;
+		
+	      lease_set_hwaddr(lease, mess->chaddr, NULL, mess->hlen, mess->htype, 0, now, 1);
+	      if (hostname)
+		lease_set_hostname(lease, hostname, 1, get_domain(lease->addr), domain); 
+	      /* infinite lease unless nailed in dhcp-host line. */
+	      lease_set_expires(lease,  
+				have_config(config, CONFIG_TIME) ? config->lease_time : 0xffffffff, 
+				now); 
+	      lease_set_interface(lease, int_index, now);
+	      
+	      clear_packet(mess, end);
+	      do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), 
+			 netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
+	    }
+	}
+      
+      daemon->metrics[METRIC_BOOTP]++;
+      log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid);
+      
+      return message ? 0 : dhcp_packet_size(mess, agent_id, real_end);
+    }
+      
+  if ((opt = option_find(mess, sz, OPTION_CLIENT_FQDN, 3)))
+    {
+      /* http://tools.ietf.org/wg/dhc/draft-ietf-dhc-fqdn-option/draft-ietf-dhc-fqdn-option-10.txt */
+      int len = option_len(opt);
+      char *pq = daemon->dhcp_buff;
+      unsigned char *pp, *op = option_ptr(opt, 0);
+      
+      fqdn_flags = *op;
+      len -= 3;
+      op += 3;
+      pp = op;
+      
+      /* NB, the following always sets at least one bit */
+      if (option_bool(OPT_FQDN_UPDATE))
+	{
+	  if (fqdn_flags & 0x01)
+	    {
+	      fqdn_flags |= 0x02; /* set O */
+	      fqdn_flags &= ~0x01; /* clear S */
+	    }
+	  fqdn_flags |= 0x08; /* set N */
+	}
+      else 
+	{
+	  if (!(fqdn_flags & 0x01))
+	    fqdn_flags |= 0x03; /* set S and O */
+	  fqdn_flags &= ~0x08; /* clear N */
+	}
+      
+      if (fqdn_flags & 0x04)
+	while (*op != 0 && ((op + (*op)) - pp) < len)
+	  {
+	    memcpy(pq, op+1, *op);
+	    pq += *op;
+	    op += (*op)+1;
+	    *(pq++) = '.';
+	  }
+      else
+	{
+	  memcpy(pq, op, len);
+	  if (len > 0 && op[len-1] == 0)
+	    borken_opt = 1;
+	  pq += len + 1;
+	}
+      
+      if (pq != daemon->dhcp_buff)
+	pq--;
+      
+      *pq = 0;
+      
+      if (legal_hostname(daemon->dhcp_buff))
+	offer_hostname = client_hostname = daemon->dhcp_buff;
+    }
+  else if ((opt = option_find(mess, sz, OPTION_HOSTNAME, 1)))
+    {
+      int len = option_len(opt);
+      memcpy(daemon->dhcp_buff, option_ptr(opt, 0), len);
+      /* Microsoft clients are broken, and need zero-terminated strings
+	 in options. We detect this state here, and do the same in
+	 any options we send */
+      if (len > 0 && daemon->dhcp_buff[len-1] == 0)
+	borken_opt = 1;
+      else
+	daemon->dhcp_buff[len] = 0;
+      if (legal_hostname(daemon->dhcp_buff))
+	client_hostname = daemon->dhcp_buff;
+    }
+
+  if (client_hostname)
+    {
+      struct dhcp_match_name *m;
+      size_t nl = strlen(client_hostname);
+      
+      if (option_bool(OPT_LOG_OPTS))
+	my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname);
+      for (m = daemon->dhcp_name_match; m; m = m->next)
+	{
+	  size_t ml = strlen(m->name);
+	  char save = 0;
+	  
+	  if (nl < ml)
+	    continue;
+	  if (nl > ml)
+	    {
+	      save = client_hostname[ml];
+	      client_hostname[ml] = 0;
+	    }
+	  
+	  if (hostname_isequal(client_hostname, m->name) &&
+	      (save == 0 || m->wildcard))
+	    {
+	      m->netid->next = netid;
+	      netid = m->netid;
+	    }
+	  
+	  if (save != 0)
+	    client_hostname[ml] = save;
+	}
+    }
+  
+  if (have_config(config, CONFIG_NAME))
+    {
+      hostname = config->hostname;
+      domain = config->domain;
+      hostname_auth = 1;
+      /* be careful not to send an OFFER with a hostname not matching the DISCOVER. */
+      if (fqdn_flags != 0 || !client_hostname || hostname_isequal(hostname, client_hostname))
+        offer_hostname = hostname;
+    }
+  else if (client_hostname)
+    {
+      domain = strip_hostname(client_hostname);
+      
+      if (strlen(client_hostname) != 0)
+	{
+	  hostname = client_hostname;
+	  
+	  if (!config)
+	    {
+	      /* Search again now we have a hostname. 
+		 Only accept configs without CLID and HWADDR here, (they won't match)
+		 to avoid impersonation by name. */
+	      struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0,
+						    mess->chaddr, mess->hlen, 
+						    mess->htype, hostname, run_tag_if(netid));
+	      if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr)
+		{
+		  config = new;
+		  /* set "known" tag for known hosts */
+		  known_id.net = "known";
+		  known_id.next = netid;
+		  netid = &known_id;
+		}
+	    }
+	}
+    }
+  
+  if (config)
+    {
+      struct dhcp_netid_list *list;
+      
+      for (list = config->netid; list; list = list->next)
+	{
+	  list->list->next = netid;
+	  netid = list->list;
+	}
+    }
+  
+  tagif_netid = run_tag_if(netid);
+  
+  /* if all the netids in the ignore list are present, ignore this client */
+  for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
+    if (match_netid(id_list->list, tagif_netid, 0))
+      ignore = 1;
+
+  /* If configured, we can override the server-id to be the address of the relay, 
+     so that all traffic goes via the relay and can pick up agent-id info. This can be
+     configured for all relays, or by address. */
+  if (daemon->override && mess->giaddr.s_addr != 0 && override.s_addr == 0)
+    {
+      if (!daemon->override_relays)
+	override = mess->giaddr;
+      else
+	{
+	  struct addr_list *l;
+	  for (l = daemon->override_relays; l; l = l->next)
+	    if (l->addr.s_addr == mess->giaddr.s_addr)
+	      break;
+	  if (l)
+	    override = mess->giaddr;
+	}
+    }
+
+  /* Can have setting to ignore the client ID for a particular MAC address or hostname */
+  if (have_config(config, CONFIG_NOCLID))
+    clid = NULL;
+          
+  /* Check if client is PXE client. */
+  if (daemon->enable_pxe &&
+      is_pxe_client(mess, sz, &pxevendor))
+    {
+      if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
+	{
+	  memcpy(pxe_uuid, option_ptr(opt, 0), 17);
+	  uuid = pxe_uuid;
+	}
+
+      /* Check if this is really a PXE bootserver request, and handle specially if so. */
+      if ((mess_type == DHCPREQUEST || mess_type == DHCPINFORM) &&
+	  (opt = option_find(mess, sz, OPTION_VENDOR_CLASS_OPT, 1)) &&
+	  (opt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_PXE_BOOT_ITEM, 4)))
+	{
+	  struct pxe_service *service;
+	  int type = option_uint(opt, 0, 2);
+	  int layer = option_uint(opt, 2, 2);
+	  unsigned char save71[4];
+	  struct dhcp_opt opt71;
+
+	  if (ignore)
+	    return 0;
+
+	  if (layer & 0x8000)
+	    {
+	      my_syslog(MS_DHCP | LOG_ERR, _("PXE BIS not supported"));
+	      return 0;
+	    }
+
+	  memcpy(save71, option_ptr(opt, 0), 4);
+	  
+	  for (service = daemon->pxe_services; service; service = service->next)
+	    if (service->type == type)
+	      break;
+	  
+	  for (; context; context = context->current)
+	    if (match_netid(context->filter, tagif_netid, 1) &&
+		is_same_net(mess->ciaddr, context->start, context->netmask))
+	      break;
+	  
+	  if (!service || !service->basename || !context)
+	    return 0;
+	  	  
+	  clear_packet(mess, end);
+	  
+	  mess->yiaddr = mess->ciaddr;
+	  mess->ciaddr.s_addr = 0;
+	  if (service->sname)
+	    mess->siaddr = a_record_from_hosts(service->sname, now);
+	  else if (service->server.s_addr != 0)
+	    mess->siaddr = service->server; 
+	  else
+	    mess->siaddr = context->local; 
+	  
+	  if (strchr(service->basename, '.'))
+	    snprintf((char *)mess->file, sizeof(mess->file),
+		"%s", service->basename);
+	  else
+	    snprintf((char *)mess->file, sizeof(mess->file),
+		"%s.%d", service->basename, layer);
+	  
+	  option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
+	  option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
+	  pxe_misc(mess, end, uuid, pxevendor);
+	  
+	  prune_vendor_opts(tagif_netid);
+	  opt71.val = save71;
+	  opt71.opt = SUBOPT_PXE_BOOT_ITEM;
+	  opt71.len = 4;
+	  opt71.flags = DHOPT_VENDOR_MATCH;
+	  opt71.netid = NULL;
+	  opt71.next = daemon->dhcp_opts;
+	  do_encap_opts(&opt71, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+	  
+	  log_packet("PXE", &mess->yiaddr, emac, emac_len, iface_name, (char *)mess->file, NULL, mess->xid);
+	  log_tags(tagif_netid, ntohl(mess->xid));
+	  return dhcp_packet_size(mess, agent_id, real_end);	  
+	}
+      
+      if ((opt = option_find(mess, sz, OPTION_ARCH, 2)))
+	{
+	  pxearch = option_uint(opt, 0, 2);
+
+	  /* proxy DHCP here. */
+	  if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST)))
+	    {
+	      struct dhcp_context *tmp;
+	      int workaround = 0;
+	      
+	      for (tmp = context; tmp; tmp = tmp->current)
+		if ((tmp->flags & CONTEXT_PROXY) &&
+		    match_netid(tmp->filter, tagif_netid, 1))
+		  break;
+	      
+	      if (tmp)
+		{
+		  struct dhcp_boot *boot;
+		  int redirect4011 = 0;
+
+		  if (tmp->netid.net)
+		    {
+		      tmp->netid.next = netid;
+		      tagif_netid = run_tag_if(&tmp->netid);
+		    }
+		  
+		  boot = find_boot(tagif_netid);
+		  
+		  mess->yiaddr.s_addr = 0;
+		  if  (mess_type == DHCPDISCOVER || mess->ciaddr.s_addr == 0)
+		    {
+		      mess->ciaddr.s_addr = 0;
+		      mess->flags |= htons(0x8000); /* broadcast */
+		    }
+		  
+		  clear_packet(mess, end);
+		  
+		  /* Redirect EFI clients to port 4011 */
+		  if (pxearch >= 6)
+		    {
+		      redirect4011 = 1;
+		      mess->siaddr = tmp->local;
+		    }
+		  
+		  /* Returns true if only one matching service is available. On port 4011, 
+		     it also inserts the boot file and server name. */
+		  workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe);
+		  
+		  if (!workaround && boot)
+		    {
+		      /* Provide the bootfile here, for iPXE, and in case we have no menu items
+			 and set discovery_control = 8 */
+		      if (boot->next_server.s_addr) 
+			mess->siaddr = boot->next_server;
+		      else if (boot->tftp_sname) 
+			mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+		      
+		      if (boot->file)
+			safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+		    }
+		  
+		  option_put(mess, end, OPTION_MESSAGE_TYPE, 1, 
+			     mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
+		  option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
+		  pxe_misc(mess, end, uuid, pxevendor);
+		  prune_vendor_opts(tagif_netid);
+		  if ((pxe && !workaround) || !redirect4011)
+		    do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
+	    
+		  daemon->metrics[METRIC_PXE]++;
+		  log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid);
+		  log_tags(tagif_netid, ntohl(mess->xid));
+		  if (!ignore)
+		    apply_delay(mess->xid, recvtime, tagif_netid);
+		  return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end);	  
+		}
+	    }
+	}
+    }
+
+  /* if we're just a proxy server, go no further */
+  if ((context->flags & CONTEXT_PROXY) || pxe)
+    return 0;
+  
+  if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
+    {
+      req_options = (unsigned char *)daemon->dhcp_buff2;
+      memcpy(req_options, option_ptr(opt, 0), option_len(opt));
+      req_options[option_len(opt)] = OPTION_END;
+    }
+  
+  switch (mess_type)
+    {
+    case DHCPDECLINE:
+      if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
+	  option_addr(opt).s_addr != server_id(context, override, fallback).s_addr)
+	return 0;
+      
+      /* sanitise any message. Paranoid? Moi? */
+      sanitise(option_find(mess, sz, OPTION_MESSAGE, 1), daemon->dhcp_buff);
+      
+      if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
+	return 0;
+      
+      daemon->metrics[METRIC_DHCPDECLINE]++;
+      log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, daemon->dhcp_buff, mess->xid);
+      
+      if (lease && lease->addr.s_addr == option_addr(opt).s_addr)
+	lease_prune(lease, now);
+      
+      if (have_config(config, CONFIG_ADDR) && 
+	  config->addr.s_addr == option_addr(opt).s_addr)
+	{
+	  prettyprint_time(daemon->dhcp_buff, DECLINE_BACKOFF);
+	  inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN);
+	  my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), 
+		    daemon->addrbuff, daemon->dhcp_buff);
+	  config->flags |= CONFIG_DECLINED;
+	  config->decline_time = now;
+	}
+      else
+	/* make sure this host gets a different address next time. */
+	for (; context; context = context->current)
+	  context->addr_epoch++;
+      
+      return 0;
+
+    case DHCPRELEASE:
+      if (!(context = narrow_context(context, mess->ciaddr, tagif_netid)) ||
+	  !(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
+	  option_addr(opt).s_addr != server_id(context, override, fallback).s_addr)
+	return 0;
+      
+      if (lease && lease->addr.s_addr == mess->ciaddr.s_addr)
+	lease_prune(lease, now);
+      else
+	message = _("unknown lease");
+
+      daemon->metrics[METRIC_DHCPRELEASE]++;
+      log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
+	
+      return 0;
+      
+    case DHCPDISCOVER:
+      if (ignore || have_config(config, CONFIG_DISABLE))
+	{
+	  if (option_bool(OPT_QUIET_DHCP))
+	    return 0;
+	  message = _("ignored");
+	  opt = NULL;
+	}
+      else 
+	{
+	  struct in_addr addr, conf;
+	  
+	  addr.s_addr = conf.s_addr = 0;
+
+	  if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))	 
+	    addr = option_addr(opt);
+	  
+	  if (have_config(config, CONFIG_ADDR))
+	    {
+	      inet_ntop(AF_INET, &config->addr, daemon->addrbuff, ADDRSTRLEN);
+	      
+	      if ((ltmp = lease_find_by_addr(config->addr)) && 
+		  ltmp != lease &&
+		  !config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type))
+		{
+		  int len;
+		  unsigned char *mac = extended_hwaddr(ltmp->hwaddr_type, ltmp->hwaddr_len,
+						       ltmp->hwaddr, ltmp->clid_len, ltmp->clid, &len);
+		  my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is leased to %s"),
+			    daemon->addrbuff, print_mac(daemon->namebuff, mac, len));
+		}
+	      else
+		{
+		  struct dhcp_context *tmp;
+		  for (tmp = context; tmp; tmp = tmp->current)
+		    if (context->router.s_addr == config->addr.s_addr)
+		      break;
+		  if (tmp)
+		    my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is in use by the server or relay"), daemon->addrbuff);
+		  else if (have_config(config, CONFIG_DECLINED) &&
+			   difftime(now, config->decline_time) < (float)DECLINE_BACKOFF)
+		    my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), daemon->addrbuff);
+		  else
+		    conf = config->addr;
+		}
+	    }
+	  
+	  if (conf.s_addr)
+	    mess->yiaddr = conf;
+	  else if (lease && 
+		   address_available(context, lease->addr, tagif_netid) && 
+		   !config_find_by_address(daemon->dhcp_conf, lease->addr))
+	    mess->yiaddr = lease->addr;
+	  else if (opt && address_available(context, addr, tagif_netid) && !lease_find_by_addr(addr) && 
+		   !config_find_by_address(daemon->dhcp_conf, addr) && do_icmp_ping(now, addr, 0, loopback))
+	    mess->yiaddr = addr;
+	  else if (emac_len == 0)
+	    message = _("no unique-id");
+	  else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now, loopback))
+	    message = _("no address available");      
+	}
+      
+      daemon->metrics[METRIC_DHCPDISCOVER]++;
+      log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid); 
+
+      if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid)))
+	return 0;
+
+      if (context->netid.net)
+	{
+	  context->netid.next = netid;
+	  tagif_netid = run_tag_if(&context->netid);
+	}
+
+      log_tags(tagif_netid, ntohl(mess->xid));
+      apply_delay(mess->xid, recvtime, tagif_netid);
+
+      if (option_bool(OPT_RAPID_COMMIT) && option_find(mess, sz, OPTION_RAPID_COMMIT, 0))
+	{
+	  rapid_commit = 1;
+	  goto rapid_commit;
+	}
+      
+      daemon->metrics[METRIC_DHCPOFFER]++;
+      log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
+      
+      time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
+      clear_packet(mess, end);
+      option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
+      option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr));
+      option_put(mess, end, OPTION_LEASE_TIME, 4, time);
+      /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
+      do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), 
+		 netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+      
+      return dhcp_packet_size(mess, agent_id, real_end);
+	
+
+    case DHCPREQUEST:
+      if (ignore || have_config(config, CONFIG_DISABLE))
+	return 0;
+      if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ)))
+	{
+	  /* SELECTING  or INIT_REBOOT */
+	  mess->yiaddr = option_addr(opt);
+	  
+	  /* send vendor and user class info for new or recreated lease */
+	  do_classes = 1;
+	  
+	  if ((opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)))
+	    {
+	      /* SELECTING */
+	      selecting = 1;
+	      
+	      if (override.s_addr != 0)
+		{
+		  if (option_addr(opt).s_addr != override.s_addr)
+		    return 0;
+		}
+	      else 
+		{
+		  for (; context; context = context->current)
+		    if (context->local.s_addr == option_addr(opt).s_addr)
+		      break;
+		  
+		  if (!context)
+		    {
+		      /* Handle very strange configs where clients have more than one route to the server.
+			 If a clients idea of its server-id matches any of our DHCP interfaces, we let it pass.
+			 Have to set override to make sure we echo back the correct server-id */
+		      struct irec *intr;
+		      
+		      enumerate_interfaces(0);
+
+		      for (intr = daemon->interfaces; intr; intr = intr->next)
+			if (intr->addr.sa.sa_family == AF_INET &&
+			    intr->addr.in.sin_addr.s_addr == option_addr(opt).s_addr &&
+			    intr->tftp_ok)
+			  break;
+
+		      if (intr)
+			override = intr->addr.in.sin_addr;
+		      else
+			{
+			  /* In auth mode, a REQUEST sent to the wrong server
+			     should be faulted, so that the client establishes 
+			     communication with us, otherwise, silently ignore. */
+			  if (!option_bool(OPT_AUTHORITATIVE))
+			    return 0;
+			  message = _("wrong server-ID");
+			}
+		    }
+		}
+
+	      /* If a lease exists for this host and another address, squash it. */
+	      if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
+		{
+		  lease_prune(lease, now);
+		  lease = NULL;
+		}
+	    }
+	  else
+	    {
+	      /* INIT-REBOOT */
+	      if (!lease && !option_bool(OPT_AUTHORITATIVE))
+		return 0;
+	      
+	      if (lease && lease->addr.s_addr != mess->yiaddr.s_addr)
+		message = _("wrong address");
+	    }
+	}
+      else
+	{
+	  /* RENEWING or REBINDING */ 
+	  /* Check existing lease for this address.
+	     We allow it to be missing if dhcp-authoritative mode
+	     as long as we can allocate the lease now - checked below.
+	     This makes for a smooth recovery from a lost lease DB */
+	  if ((lease && mess->ciaddr.s_addr != lease->addr.s_addr) ||
+	      (!lease && !option_bool(OPT_AUTHORITATIVE)))
+	    {
+	      /* A client rebinding will broadcast the request, so we may see it even 
+		 if the lease is held by another server. Just ignore it in that case. 
+		 If the request is unicast to us, then somethings wrong, NAK */
+	      if (!unicast_dest)
+		return 0;
+	      message = _("lease not found");
+	      /* ensure we broadcast NAK */
+	      unicast_dest = 0;
+	    }
+
+	  /* desynchronise renewals */
+	  fuzz = rand16();
+	  mess->yiaddr = mess->ciaddr;
+	}
+
+      daemon->metrics[METRIC_DHCPREQUEST]++;
+      log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid);
+      
+    rapid_commit:
+      if (!message)
+	{
+	  struct dhcp_config *addr_config;
+	  struct dhcp_context *tmp = NULL;
+	  
+	  if (have_config(config, CONFIG_ADDR))
+	    for (tmp = context; tmp; tmp = tmp->current)
+	      if (context->router.s_addr == config->addr.s_addr)
+		break;
+	  
+	  if (!(context = narrow_context(context, mess->yiaddr, tagif_netid)))
+	    {
+	      /* If a machine moves networks whilst it has a lease, we catch that here. */
+	      message = _("wrong network");
+	      /* ensure we broadcast NAK */
+	      unicast_dest = 0;
+	    }
+	  
+	  /* Check for renewal of a lease which is outside the allowed range. */
+	  else if (!address_available(context, mess->yiaddr, tagif_netid) &&
+		   (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr))
+	    message = _("address not available");
+	  
+	  /* Check if a new static address has been configured. Be very sure that
+	     when the client does DISCOVER, it will get the static address, otherwise
+	     an endless protocol loop will ensue. */
+	  else if (!tmp && !selecting &&
+		   have_config(config, CONFIG_ADDR) && 
+		   (!have_config(config, CONFIG_DECLINED) ||
+		    difftime(now, config->decline_time) > (float)DECLINE_BACKOFF) &&
+		   config->addr.s_addr != mess->yiaddr.s_addr &&
+		   (!(ltmp = lease_find_by_addr(config->addr)) || ltmp == lease))
+	    message = _("static lease available");
+
+	  /* Check to see if the address is reserved as a static address for another host */
+	  else if ((addr_config = config_find_by_address(daemon->dhcp_conf, mess->yiaddr)) && addr_config != config)
+	    message = _("address reserved");
+
+	  else if (!lease && (ltmp = lease_find_by_addr(mess->yiaddr)))
+	    {
+	      /* If a host is configured with more than one MAC address, it's OK to 'nix 
+		 a lease from one of it's MACs to give the address to another. */
+	      if (config && config_has_mac(config, ltmp->hwaddr, ltmp->hwaddr_len, ltmp->hwaddr_type))
+		{
+		  inet_ntop(AF_INET, &ltmp->addr, daemon->addrbuff, ADDRSTRLEN);
+		  my_syslog(MS_DHCP | LOG_INFO, _("abandoning lease to %s of %s"),
+			    print_mac(daemon->namebuff, ltmp->hwaddr, ltmp->hwaddr_len), 
+			    daemon->addrbuff);
+		  lease = ltmp;
+		}
+	      else
+		message = _("address in use");
+	    }
+
+	  if (!message)
+	    {
+	      if (emac_len == 0)
+		message = _("no unique-id");
+	      
+	      else if (!lease)
+		{	     
+		  if ((lease = lease4_allocate(mess->yiaddr)))
+		    do_classes = 1;
+		  else
+		    message = _("no leases left");
+		}
+	    }
+	}
+
+      if (message)
+	{
+	  daemon->metrics[rapid_commit ? METRIC_NOANSWER : METRIC_DHCPNAK]++;
+	  log_packet(rapid_commit ? "NOANSWER" : "DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid);
+
+	  /* rapid commit case: lease allocate failed but don't send DHCPNAK */
+	  if (rapid_commit)
+	    return 0;
+	  
+	  mess->yiaddr.s_addr = 0;
+	  clear_packet(mess, end);
+	  option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);
+	  option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr));
+	  option_put_string(mess, end, OPTION_MESSAGE, message, borken_opt);
+	  /* This fixes a problem with the DHCP spec, broadcasting a NAK to a host on 
+	     a distant subnet which unicast a REQ to us won't work. */
+	  if (!unicast_dest || mess->giaddr.s_addr != 0 || 
+	      mess->ciaddr.s_addr == 0 || is_same_net(context->local, mess->ciaddr, context->netmask))
+	    {
+	      mess->flags |= htons(0x8000); /* broadcast */
+	      mess->ciaddr.s_addr = 0;
+	    }
+	}
+      else
+	{
+	  if (context->netid.net)
+	    {
+	      context->netid.next = netid;
+	      tagif_netid = run_tag_if( &context->netid);
+	    }
+
+	  log_tags(tagif_netid, ntohl(mess->xid));
+	  
+	  if (do_classes)
+	    {
+	      /* pick up INIT-REBOOT events. */
+	      lease->flags |= LEASE_CHANGED;
+
+#ifdef HAVE_SCRIPT
+	      if (daemon->lease_change_command)
+		{
+		  struct dhcp_netid *n;
+		  
+		  if (mess->giaddr.s_addr)
+		    lease->giaddr = mess->giaddr;
+		  
+		  free(lease->extradata);
+		  lease->extradata = NULL;
+		  lease->extradata_size = lease->extradata_len = 0;
+		  
+		  add_extradata_opt(lease, option_find(mess, sz, OPTION_VENDOR_ID, 1));
+		  add_extradata_opt(lease, option_find(mess, sz, OPTION_HOSTNAME, 1));
+		  add_extradata_opt(lease, oui);
+		  add_extradata_opt(lease, serial);
+		  add_extradata_opt(lease, class);
+
+		  if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
+		    {
+		      add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_CIRCUIT_ID, 1));
+		      add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SUBSCR_ID, 1));
+		      add_extradata_opt(lease, option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_REMOTE_ID, 1));
+		    }
+		  else
+		    {
+		      add_extradata_opt(lease, NULL);
+		      add_extradata_opt(lease, NULL);
+		      add_extradata_opt(lease, NULL);
+		    }
+
+		  /* DNSMASQ_REQUESTED_OPTIONS */
+		  if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 1)))
+		    {
+		      int len = option_len(opt);
+		      unsigned char *rop = option_ptr(opt, 0);
+		      char *q = daemon->namebuff;
+		      int i;
+		      for (i = 0; i < len; i++)
+		        {
+		          q += snprintf(q, MAXDNAME - (q - daemon->namebuff), "%d%s", rop[i], i + 1 == len ? "" : ",");
+		        }
+		      lease_add_extradata(lease, (unsigned char *)daemon->namebuff, (q - daemon->namebuff), 0); 
+		    }
+		  else
+		    {
+		      add_extradata_opt(lease, NULL);
+		    }
+
+		  /* space-concat tag set */
+		  if (!tagif_netid)
+		    add_extradata_opt(lease, NULL);
+		  else
+		    for (n = tagif_netid; n; n = n->next)
+		      {
+			struct dhcp_netid *n1;
+			/* kill dupes */
+			for (n1 = n->next; n1; n1 = n1->next)
+			  if (strcmp(n->net, n1->net) == 0)
+			    break;
+			if (!n1)
+			  lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); 
+		      }
+		  
+		  if ((opt = option_find(mess, sz, OPTION_USER_CLASS, 1)))
+		    {
+		      int len = option_len(opt);
+		      unsigned char *ucp = option_ptr(opt, 0);
+		      /* If the user-class option started as counted strings, the first byte will be zero. */
+		      if (len != 0 && ucp[0] == 0)
+			ucp++, len--;
+		      lease_add_extradata(lease, ucp, len, -1);
+		    }
+		}
+#endif
+	    }
+	  
+	  if (!hostname_auth && (client_hostname = host_from_dns(mess->yiaddr)))
+	    {
+	      domain = get_domain(mess->yiaddr);
+	      hostname = client_hostname;
+	      hostname_auth = 1;
+	    }
+	  
+	  time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4));
+	  lease_set_hwaddr(lease, mess->chaddr, clid, mess->hlen, mess->htype, clid_len, now, do_classes);
+	  
+	  /* if all the netids in the ignore_name list are present, ignore client-supplied name */
+	  if (!hostname_auth)
+	    {
+	      for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+		if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0))
+		  break;
+	      if (id_list)
+		hostname = NULL;
+	    }
+	  
+	  /* Last ditch, if configured, generate hostname from mac address */
+	  if (!hostname && emac_len != 0)
+	    {
+	      for (id_list = daemon->dhcp_gen_names; id_list; id_list = id_list->next)
+		if ((!id_list->list) || match_netid(id_list->list, tagif_netid, 0))
+		  break;
+	      if (id_list)
+		{
+		  int i;
+
+		  hostname = daemon->dhcp_buff;
+		  /* buffer is 256 bytes, 3 bytes per octet */
+		  for (i = 0; (i < emac_len) && (i < 80); i++)
+		    hostname += sprintf(hostname, "%.2x%s", emac[i], (i == emac_len - 1) ? "" : "-");
+		  hostname = daemon->dhcp_buff;
+		}
+	    }
+
+	  if (hostname)
+	    lease_set_hostname(lease, hostname, hostname_auth, get_domain(lease->addr), domain);
+	  
+	  lease_set_expires(lease, time, now);
+	  lease_set_interface(lease, int_index, now);
+
+	  if (override.s_addr != 0)
+	    lease->override = override;
+	  else
+	    override = lease->override;
+
+	  daemon->metrics[METRIC_DHCPACK]++;
+	  log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);  
+
+	  clear_packet(mess, end);
+	  option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
+	  option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr));
+	  option_put(mess, end, OPTION_LEASE_TIME, 4, time);
+	  if (rapid_commit)
+	     option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
+	   do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), 
+		     netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+	}
+
+      return dhcp_packet_size(mess, agent_id, real_end); 
+      
+    case DHCPINFORM:
+      if (ignore || have_config(config, CONFIG_DISABLE))
+	message = _("ignored");
+      
+      daemon->metrics[METRIC_DHCPINFORM]++;
+      log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, mess->xid);
+     
+      if (message || mess->ciaddr.s_addr == 0)
+	return 0;
+
+      /* For DHCPINFORM only, cope without a valid context */
+      context = narrow_context(context, mess->ciaddr, tagif_netid);
+      
+      /* Find a least based on IP address if we didn't
+	 get one from MAC address/client-d */
+      if (!lease &&
+	  (lease = lease_find_by_addr(mess->ciaddr)) && 
+	  lease->hostname)
+	hostname = lease->hostname;
+      
+      if (!hostname)
+	hostname = host_from_dns(mess->ciaddr);
+      
+      if (context && context->netid.net)
+	{
+	  context->netid.next = netid;
+	  tagif_netid = run_tag_if(&context->netid);
+	}
+
+      log_tags(tagif_netid, ntohl(mess->xid));
+      
+      daemon->metrics[METRIC_DHCPACK]++;
+      log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid);
+      
+      if (lease)
+	{
+	  lease_set_interface(lease, int_index, now);
+	  if (override.s_addr != 0)
+	    lease->override = override;
+	  else
+	    override = lease->override;
+	}
+
+      clear_packet(mess, end);
+      option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
+      option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr));
+     
+      /* RFC 2131 says that DHCPINFORM shouldn't include lease-time parameters, but 
+	 we supply a utility which makes DHCPINFORM requests to get this information.
+	 Only include lease time if OPTION_LEASE_TIME is in the parameter request list,
+	 which won't be true for ordinary clients, but will be true for the 
+	 dhcp_lease_time utility. */
+      if (lease && in_list(req_options, OPTION_LEASE_TIME))
+	{
+	  if (lease->expires == 0)
+	    time = 0xffffffff;
+	  else
+	    time = (unsigned int)difftime(lease->expires, now);
+	  option_put(mess, end, OPTION_LEASE_TIME, 4, time);
+	}
+
+      do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
+		 netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
+      
+      *is_inform = 1; /* handle reply differently */
+      return dhcp_packet_size(mess, agent_id, real_end); 
+    }
+  
+  return 0;
+}
+
+/* find a good value to use as MAC address for logging and address-allocation hashing.
+   This is normally just the chaddr field from the DHCP packet,
+   but eg Firewire will have hlen == 0 and use the client-id instead. 
+   This could be anything, but will normally be EUI64 for Firewire.
+   We assume that if the first byte of the client-id equals the htype byte
+   then the client-id is using the usual encoding and use the rest of the 
+   client-id: if not we can use the whole client-id. This should give
+   sane MAC address logs. */
+unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr, 
+				      int clid_len, unsigned char *clid, int *len_out)
+{
+  if (hwlen == 0 && clid && clid_len > 3)
+    {
+      if (clid[0]  == hwtype)
+	{
+	  *len_out = clid_len - 1 ;
+	  return clid + 1;
+	}
+
+#if defined(ARPHRD_EUI64) && defined(ARPHRD_IEEE1394)
+      if (clid[0] ==  ARPHRD_EUI64 && hwtype == ARPHRD_IEEE1394)
+	{
+	  *len_out = clid_len - 1 ;
+	  return clid + 1;
+	}
+#endif
+      
+      *len_out = clid_len;
+      return clid;
+    }
+  
+  *len_out = hwlen;
+  return hwaddr;
+}
+
+static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt)
+{
+  unsigned int time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
+  
+  if (opt)
+    { 
+      unsigned int req_time = option_uint(opt, 0, 4);
+      if (req_time < 120 )
+	req_time = 120; /* sanity */
+      if (time == 0xffffffff || (req_time != 0xffffffff && req_time < time))
+	time = req_time;
+    }
+
+  return time;
+}
+
+static struct in_addr server_id(struct dhcp_context *context, struct in_addr override, struct in_addr fallback)
+{
+  if (override.s_addr != 0)
+    return override;
+  else if (context && context->local.s_addr != 0)
+    return context->local;
+  else
+    return fallback;
+}
+
+static int sanitise(unsigned char *opt, char *buf)
+{
+  char *p;
+  int i;
+  
+  *buf = 0;
+  
+  if (!opt)
+    return 0;
+
+  p = option_ptr(opt, 0);
+
+  for (i = option_len(opt); i > 0; i--)
+    {
+      char c = *p++;
+      if (isprint((int)c))
+	*buf++ = c;
+    }
+  *buf = 0; /* add terminator */
+  
+  return 1;
+}
+
+#ifdef HAVE_SCRIPT
+static void add_extradata_opt(struct dhcp_lease *lease, unsigned char *opt)
+{
+  if (!opt)
+    lease_add_extradata(lease, NULL, 0, 0);
+  else
+    lease_add_extradata(lease, option_ptr(opt, 0), option_len(opt), 0); 
+}
+#endif
+
+static void log_packet(char *type, void *addr, unsigned char *ext_mac, 
+		       int mac_len, char *interface, char *string, char *err, u32 xid)
+{
+  if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
+    return;
+  
+  daemon->addrbuff[0] = 0;
+  if (addr)
+    inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
+  
+  print_mac(daemon->namebuff, ext_mac, mac_len);
+  
+  if (option_bool(OPT_LOG_OPTS))
+    my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s",
+	      ntohl(xid), 
+	      type,
+	      interface, 
+	      daemon->addrbuff,
+	      addr ? " " : "",
+	      daemon->namebuff,
+	      string ? string : "",
+	      err ? err : "");
+  else
+    my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s%s %s%s",
+	      type,
+	      interface, 
+	      daemon->addrbuff,
+	      addr ? " " : "",
+	      daemon->namebuff,
+	      string ? string : "",
+	      err ? err : "");
+  
+#ifdef HAVE_UBUS
+  if (!strcmp(type, "DHCPACK"))
+    ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
+  else if (!strcmp(type, "DHCPRELEASE"))
+    ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? daemon->addrbuff : NULL, string, interface);
+#endif
+}
+
+static void log_options(unsigned char *start, u32 xid)
+{
+  while (*start != OPTION_END)
+    {
+      char *optname = option_string(AF_INET, start[0], option_ptr(start, 0), option_len(start), daemon->namebuff, MAXDNAME);
+      
+      my_syslog(MS_DHCP | LOG_INFO, "%u sent size:%3d option:%3d %s  %s", 
+		ntohl(xid), option_len(start), start[0], optname, daemon->namebuff);
+      start += start[1] + 2;
+    }
+}
+
+static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt, int minsize)
+{
+  while (1) 
+    {
+      if (p >= end)
+	return NULL;
+      else if (*p == OPTION_END)
+	return opt == OPTION_END ? p : NULL;
+      else if (*p == OPTION_PAD)
+	p++;
+      else 
+	{ 
+	  int opt_len;
+	  if (p > end - 2)
+	    return NULL; /* malformed packet */
+	  opt_len = option_len(p);
+	  if (p > end - (2 + opt_len))
+	    return NULL; /* malformed packet */
+	  if (*p == opt && opt_len >= minsize)
+	    return p;
+	  p += opt_len + 2;
+	}
+    }
+}
+ 
+static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize)
+{
+  unsigned char *ret, *overload;
+  
+  /* skip over DHCP cookie; */
+  if ((ret = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, opt_type, minsize)))
+    return ret;
+
+  /* look for overload option. */
+  if (!(overload = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + size, OPTION_OVERLOAD, 1)))
+    return NULL;
+  
+  /* Can we look in filename area ? */
+  if ((overload[2] & 1) &&
+      (ret = option_find1(&mess->file[0], &mess->file[128], opt_type, minsize)))
+    return ret;
+
+  /* finally try sname area */
+  if ((overload[2] & 2) &&
+      (ret = option_find1(&mess->sname[0], &mess->sname[64], opt_type, minsize)))
+    return ret;
+
+  return NULL;
+}
+
+static struct in_addr option_addr(unsigned char *opt)
+{
+   /* this worries about unaligned data in the option. */
+  /* struct in_addr is network byte order */
+  struct in_addr ret;
+
+  memcpy(&ret, option_ptr(opt, 0), INADDRSZ);
+
+  return ret;
+}
+
+static unsigned int option_uint(unsigned char *opt, int offset, int size)
+{
+  /* this worries about unaligned data and byte order */
+  unsigned int ret = 0;
+  int i;
+  unsigned char *p = option_ptr(opt, offset);
+  
+  for (i = 0; i < size; i++)
+    ret = (ret << 8) | *p++;
+
+  return ret;
+}
+
+static unsigned char *dhcp_skip_opts(unsigned char *start)
+{
+  while (*start != 0)
+    start += start[1] + 2;
+  return start;
+}
+
+/* only for use when building packet: doesn't check for bad data. */ 
+static unsigned char *find_overload(struct dhcp_packet *mess)
+{
+  unsigned char *p = &mess->options[0] + sizeof(u32);
+  
+  while (*p != 0)
+    {
+      if (*p == OPTION_OVERLOAD)
+	return p;
+      p += p[1] + 2;
+    }
+  return NULL;
+}
+
+static size_t dhcp_packet_size(struct dhcp_packet *mess, unsigned char *agent_id, unsigned char *real_end)
+{
+  unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32));
+  unsigned char *overload;
+  size_t ret;
+  
+  /* move agent_id back down to the end of the packet */
+  if (agent_id)
+    {
+      memmove(p, agent_id, real_end - agent_id);
+      p += real_end - agent_id;
+      memset(p, 0, real_end - p); /* in case of overlap */
+    }
+  
+  /* add END options to the regions. */
+  overload = find_overload(mess);
+  
+  if (overload && (option_uint(overload, 0, 1) & 1))
+    {
+      *dhcp_skip_opts(mess->file) = OPTION_END;
+      if (option_bool(OPT_LOG_OPTS))
+	log_options(mess->file, mess->xid);
+    }
+  else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->file) != 0)
+    my_syslog(MS_DHCP | LOG_INFO, _("%u bootfile name: %s"), ntohl(mess->xid), (char *)mess->file);
+  
+  if (overload && (option_uint(overload, 0, 1) & 2))
+    {
+      *dhcp_skip_opts(mess->sname) = OPTION_END;
+      if (option_bool(OPT_LOG_OPTS))
+	log_options(mess->sname, mess->xid);
+    }
+  else if (option_bool(OPT_LOG_OPTS) && strlen((char *)mess->sname) != 0)
+    my_syslog(MS_DHCP | LOG_INFO, _("%u server name: %s"), ntohl(mess->xid), (char *)mess->sname);
+
+
+  *p++ = OPTION_END;
+  
+  if (option_bool(OPT_LOG_OPTS))
+    {
+      if (mess->siaddr.s_addr != 0)
+	{
+	  inet_ntop(AF_INET, &mess->siaddr, daemon->addrbuff, ADDRSTRLEN);
+	  my_syslog(MS_DHCP | LOG_INFO, _("%u next server: %s"), ntohl(mess->xid), daemon->addrbuff);
+	}
+      
+      if ((mess->flags & htons(0x8000)) && mess->ciaddr.s_addr == 0)
+	my_syslog(MS_DHCP | LOG_INFO, _("%u broadcast response"), ntohl(mess->xid));
+      
+      log_options(&mess->options[0] + sizeof(u32), mess->xid);
+    } 
+  
+  ret = (size_t)(p - (unsigned char *)mess);
+  
+  if (ret < MIN_PACKETSZ)
+    ret = MIN_PACKETSZ;
+  
+  return ret;
+}
+
+static unsigned char *free_space(struct dhcp_packet *mess, unsigned char *end, int opt, int len)
+{
+  unsigned char *p = dhcp_skip_opts(&mess->options[0] + sizeof(u32));
+  
+  if (p + len + 3 >= end)
+    /* not enough space in options area, try and use overload, if poss */
+    {
+      unsigned char *overload;
+      
+      if (!(overload = find_overload(mess)) &&
+	  (mess->file[0] == 0 || mess->sname[0] == 0))
+	{
+	  /* attempt to overload fname and sname areas, we've reserved space for the
+	     overflow option previuously. */
+	  overload = p;
+	  *(p++) = OPTION_OVERLOAD;
+	  *(p++) = 1;
+	}
+      
+      p = NULL;
+      
+      /* using filename field ? */
+      if (overload)
+	{
+	  if (mess->file[0] == 0)
+	    overload[2] |= 1;
+	  
+	  if (overload[2] & 1)
+	    {
+	      p = dhcp_skip_opts(mess->file);
+	      if (p + len + 3 >= mess->file + sizeof(mess->file))
+		p = NULL;
+	    }
+	  
+	  if (!p)
+	    {
+	      /* try to bring sname into play (it may be already) */
+	      if (mess->sname[0] == 0)
+		overload[2] |= 2;
+	      
+	      if (overload[2] & 2)
+		{
+		  p = dhcp_skip_opts(mess->sname);
+		  if (p + len + 3 >= mess->sname + sizeof(mess->sname))
+		    p = NULL;
+		}
+	    }
+	}
+      
+      if (!p)
+	my_syslog(MS_DHCP | LOG_WARNING, _("cannot send DHCP/BOOTP option %d: no space left in packet"), opt);
+    }
+ 
+  if (p)
+    {
+      *(p++) = opt;
+      *(p++) = len;
+    }
+
+  return p;
+}
+	      
+static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val)
+{
+  int i;
+  unsigned char *p = free_space(mess, end, opt, len);
+  
+  if (p) 
+    for (i = 0; i < len; i++)
+      *(p++) = val >> (8 * (len - (i + 1)));
+}
+
+static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, 
+			      const char *string, int null_term)
+{
+  unsigned char *p;
+  size_t len = strlen(string);
+
+  if (null_term && len != 255)
+    len++;
+
+  if ((p = free_space(mess, end, opt, len)))
+    memcpy(p, string, len);
+}
+
+/* return length, note this only does the data part */
+static int do_opt(struct dhcp_opt *opt, unsigned char *p, struct dhcp_context *context, int null_term)
+{
+  int len = opt->len;
+  
+  if ((opt->flags & DHOPT_STRING) && null_term && len != 255)
+    len++;
+
+  if (p && len != 0)
+    {
+      if (context && (opt->flags & DHOPT_ADDR))
+	{
+	  int j;
+	  struct in_addr *a = (struct in_addr *)opt->val;
+	  for (j = 0; j < opt->len; j+=INADDRSZ, a++)
+	    {
+	      /* zero means "self" (but not in vendorclass options.) */
+	      if (a->s_addr == 0)
+		memcpy(p, &context->local, INADDRSZ);
+	      else
+		memcpy(p, a, INADDRSZ);
+	      p += INADDRSZ;
+	    }
+	}
+      else
+	/* empty string may be extended to "\0" by null_term */
+	memcpy(p, opt->val ? opt->val : (unsigned char *)"", len);
+    }  
+  return len;
+}
+
+static int in_list(unsigned char *list, int opt)
+{
+  int i;
+
+   /* If no requested options, send everything, not nothing. */
+  if (!list)
+    return 1;
+  
+  for (i = 0; list[i] != OPTION_END; i++)
+    if (opt == list[i])
+      return 1;
+
+  return 0;
+}
+
+static struct dhcp_opt *option_find2(int opt)
+{
+  struct dhcp_opt *opts;
+  
+  for (opts = daemon->dhcp_opts; opts; opts = opts->next)
+    if (opts->opt == opt && (opts->flags & DHOPT_TAGOK))
+      return opts;
+  
+  return NULL;
+}
+
+/* mark vendor-encapsulated options which match the client-supplied  or
+   config-supplied vendor class */
+static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt)
+{
+  for (; dopt; dopt = dopt->next)
+    {
+      dopt->flags &= ~DHOPT_VENDOR_MATCH;
+      if (opt && (dopt->flags & DHOPT_VENDOR))
+	{
+	  const struct dhcp_pxe_vendor *pv;
+	  struct dhcp_pxe_vendor dummy_vendor = {
+	    .data = (char *)dopt->u.vendor_class,
+	    .next = NULL,
+	  };
+	  if (dopt->flags & DHOPT_VENDOR_PXE)
+	    pv = daemon->dhcp_pxe_vendors;
+	  else
+	    pv = &dummy_vendor;
+	  for (; pv; pv = pv->next)
+	    {
+	      int i, len = 0, matched = 0;
+	      if (pv->data)
+	        len = strlen(pv->data);
+	      for (i = 0; i <= (option_len(opt) - len); i++)
+	        if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0)
+	          {
+		    matched = 1;
+	            break;
+	          }
+	      if (matched)
+		{
+	          dopt->flags |= DHOPT_VENDOR_MATCH;
+		  break;
+		}
+	    }
+	}
+    }
+}
+
+static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag,  
+			 struct dhcp_packet *mess, unsigned char *end, int null_term)
+{
+  int len, enc_len, ret = 0;
+  struct dhcp_opt *start;
+  unsigned char *p;
+    
+  /* find size in advance */
+  for (enc_len = 0, start = opt; opt; opt = opt->next)
+    if (opt->flags & flag)
+      {
+	int new = do_opt(opt, NULL, NULL, null_term) + 2;
+	ret  = 1;
+	if (enc_len + new <= 255)
+	  enc_len += new;
+	else
+	  {
+	    p = free_space(mess, end, encap, enc_len);
+	    for (; start && start != opt; start = start->next)
+	      if (p && (start->flags & flag))
+		{
+		  len = do_opt(start, p + 2, NULL, null_term);
+		  *(p++) = start->opt;
+		  *(p++) = len;
+		  p += len;
+		}
+	    enc_len = new;
+	    start = opt;
+	  }
+      }
+  
+  if (enc_len != 0 &&
+      (p = free_space(mess, end, encap, enc_len + 1)))
+    {
+      for (; start; start = start->next)
+	if (start->flags & flag)
+	  {
+	    len = do_opt(start, p + 2, NULL, null_term);
+	    *(p++) = start->opt;
+	    *(p++) = len;
+	    p += len;
+	  }
+      *p = OPTION_END;
+    }
+
+  return ret;
+}
+
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor)
+{
+  unsigned char *p;
+
+  if (!pxevendor)
+    pxevendor="PXEClient";
+  option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 0);
+  if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17)))
+    memcpy(p, uuid, 17);
+}
+
+static int prune_vendor_opts(struct dhcp_netid *netid)
+{
+  int force = 0;
+  struct dhcp_opt *opt;
+
+  /* prune vendor-encapsulated options based on netid, and look if we're forcing them to be sent */
+  for (opt = daemon->dhcp_opts; opt; opt = opt->next)
+    if (opt->flags & DHOPT_VENDOR_MATCH)
+      {
+	if (!match_netid(opt->netid, netid, 1))
+	  opt->flags &= ~DHOPT_VENDOR_MATCH;
+	else if (opt->flags & DHOPT_FORCE)
+	  force = 1;
+      }
+  return force;
+}
+
+
+/* Many UEFI PXE implementations have badly broken menu code.
+   If there's exactly one relevant menu item, we abandon the menu system,
+   and jamb the data direct into the DHCP file, siaddr and sname fields.
+   Note that in this case, we have to assume that layer zero would be requested
+   by the client PXE stack. */
+static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe)
+{
+  struct pxe_service *service, *found;
+
+  /* Only workaround UEFI archs. */
+  if (pxe_arch < 6)
+    return 0;
+  
+  for (found = NULL, service = daemon->pxe_services; service; service = service->next)
+    if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1))
+      {
+	if (found)
+	  return 0; /* More than one relevant menu item */
+	  
+	found = service;
+      }
+
+  if (!found)
+    return 0; /* No relevant menu items. */
+  
+  if (!pxe)
+     return 1;
+  
+  if (found->sname)
+    {
+      mess->siaddr = a_record_from_hosts(found->sname, now);
+      snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname);
+    }
+  else 
+    {
+      if (found->server.s_addr != 0)
+	mess->siaddr = found->server; 
+      else
+	mess->siaddr = local;
+  
+      inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN);
+    }
+  
+  snprintf((char *)mess->file, sizeof(mess->file), 
+	   strchr(found->basename, '.') ? "%s" : "%s.0", found->basename);
+  
+  return 1;
+}
+
+static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now)
+{
+#define NUM_OPTS 4  
+
+  unsigned  char *p, *q;
+  struct pxe_service *service;
+  static struct dhcp_opt *o, *ret;
+  int i, j = NUM_OPTS - 1;
+  struct in_addr boot_server;
+  
+  /* We pass back references to these, hence they are declared static */
+  static unsigned char discovery_control;
+  static unsigned char fake_prompt[] = { 0, 'P', 'X', 'E' }; 
+  static struct dhcp_opt *fake_opts = NULL;
+  
+  /* Disable multicast, since we don't support it, and broadcast
+     unless we need it */
+  discovery_control = 3;
+  
+  ret = daemon->dhcp_opts;
+  
+  if (!fake_opts && !(fake_opts = whine_malloc(NUM_OPTS * sizeof(struct dhcp_opt))))
+    return ret;
+
+  for (i = 0; i < NUM_OPTS; i++)
+    {
+      fake_opts[i].flags = DHOPT_VENDOR_MATCH;
+      fake_opts[i].netid = NULL;
+      fake_opts[i].next = i == (NUM_OPTS - 1) ? ret : &fake_opts[i+1];
+    }
+  
+  /* create the data for the PXE_MENU and PXE_SERVERS options. */
+  p = (unsigned char *)daemon->dhcp_buff;
+  q = (unsigned char *)daemon->dhcp_buff3;
+
+  for (i = 0, service = daemon->pxe_services; service; service = service->next)
+    if (pxe_arch == service->CSA && match_netid(service->netid, netid, 1))
+      {
+	size_t len = strlen(service->menu);
+	/* opt 43 max size is 255. encapsulated option has type and length
+	   bytes, so its max size is 253. */
+	if (p - (unsigned char *)daemon->dhcp_buff + len + 3 < 253)
+	  {
+	    *(p++) = service->type >> 8;
+	    *(p++) = service->type;
+	    *(p++) = len;
+	    memcpy(p, service->menu, len);
+	    p += len;
+	    i++;
+	  }
+	else
+	  {
+	  toobig:
+	    my_syslog(MS_DHCP | LOG_ERR, _("PXE menu too large"));
+	    return daemon->dhcp_opts;
+	  }
+	
+	boot_server = service->basename ? local : 
+	  (service->sname ? a_record_from_hosts(service->sname, now) : service->server);
+	
+	if (boot_server.s_addr != 0)
+	  {
+	    if (q - (unsigned char *)daemon->dhcp_buff3 + 3 + INADDRSZ >= 253)
+	      goto toobig;
+	    
+	    /* Boot service with known address - give it */
+	    *(q++) = service->type >> 8;
+	    *(q++) = service->type;
+	    *(q++) = 1;
+	    /* dest misaligned */
+	    memcpy(q, &boot_server.s_addr, INADDRSZ);
+	    q += INADDRSZ;
+	  }
+	else if (service->type != 0)
+	  /* We don't know the server for a service type, so we'll
+	     allow the client to broadcast for it */
+	  discovery_control = 2;
+      }
+
+  /* if no prompt, wait forever if there's a choice */
+  fake_prompt[0] = (i > 1) ? 255 : 0;
+  
+  if (i == 0)
+    discovery_control = 8; /* no menu - just use use mess->filename */
+  else
+    {
+      ret = &fake_opts[j--];
+      ret->len = p - (unsigned char *)daemon->dhcp_buff;
+      ret->val = (unsigned char *)daemon->dhcp_buff;
+      ret->opt = SUBOPT_PXE_MENU;
+
+      if (q - (unsigned char *)daemon->dhcp_buff3 != 0)
+	{
+	  ret = &fake_opts[j--]; 
+	  ret->len = q - (unsigned char *)daemon->dhcp_buff3;
+	  ret->val = (unsigned char *)daemon->dhcp_buff3;
+	  ret->opt = SUBOPT_PXE_SERVERS;
+	}
+    }
+
+  for (o = daemon->dhcp_opts; o; o = o->next)
+    if ((o->flags & DHOPT_VENDOR_MATCH) && o->opt == SUBOPT_PXE_MENU_PROMPT)
+      break;
+  
+  if (!o)
+    {
+      ret = &fake_opts[j--]; 
+      ret->len = sizeof(fake_prompt);
+      ret->val = fake_prompt;
+      ret->opt = SUBOPT_PXE_MENU_PROMPT;
+    }
+  
+  ret = &fake_opts[j--]; 
+  ret->len = 1;
+  ret->opt = SUBOPT_PXE_DISCOVERY;
+  ret->val= &discovery_control;
+ 
+  return ret;
+}
+  
+static void clear_packet(struct dhcp_packet *mess, unsigned char *end)
+{
+  memset(mess->sname, 0, sizeof(mess->sname));
+  memset(mess->file, 0, sizeof(mess->file));
+  memset(&mess->options[0] + sizeof(u32), 0, end - (&mess->options[0] + sizeof(u32)));
+  mess->siaddr.s_addr = 0;
+}
+
+struct dhcp_boot *find_boot(struct dhcp_netid *netid)
+{
+  struct dhcp_boot *boot;
+
+  /* decide which dhcp-boot option we're using */
+  for (boot = daemon->boot_config; boot; boot = boot->next)
+    if (match_netid(boot->netid, netid, 0))
+      break;
+  if (!boot)
+    /* No match, look for one without a netid */
+    for (boot = daemon->boot_config; boot; boot = boot->next)
+      if (match_netid(boot->netid, netid, 1))
+	break;
+
+  return boot;
+}
+
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor)
+{
+  const unsigned char *opt = NULL;
+  ssize_t conf_len = 0;
+  const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors;
+  opt = option_find(mess, sz, OPTION_VENDOR_ID, 0);
+  if (!opt) 
+    return 0;
+  for (; conf; conf = conf->next)
+    {
+      conf_len = strlen(conf->data);
+      if (option_len(opt) < conf_len)
+        continue;
+      if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0)
+        {
+          if (pxe_vendor)
+            *pxe_vendor = conf->data;
+          return 1;
+        }
+    }
+  return 0;
+}
+
+static void do_options(struct dhcp_context *context,
+		       struct dhcp_packet *mess,
+		       unsigned char *end, 
+		       unsigned char *req_options,
+		       char *hostname, 
+		       char *domain,
+		       struct dhcp_netid *netid,
+		       struct in_addr subnet_addr,
+		       unsigned char fqdn_flags,
+		       int null_term, int pxe_arch,
+		       unsigned char *uuid,
+		       int vendor_class_len,
+		       time_t now,
+		       unsigned int lease_time,
+		       unsigned short fuzz,
+		       const char *pxevendor)
+{
+  struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
+  struct dhcp_boot *boot;
+  unsigned char *p;
+  int i, len, force_encap = 0;
+  unsigned char f0 = 0, s0 = 0;
+  int done_file = 0, done_server = 0;
+  int done_vendor_class = 0;
+  struct dhcp_netid *tagif;
+  struct dhcp_netid_list *id_list;
+
+  /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
+  if (context)
+    context->netid.next = NULL;
+  tagif = option_filter(netid, context && context->netid.net ? &context->netid : NULL, config_opts);
+	
+  /* logging */
+  if (option_bool(OPT_LOG_OPTS) && req_options)
+    {
+      char *q = daemon->namebuff;
+      for (i = 0; req_options[i] != OPTION_END; i++)
+	{
+	  char *s = option_string(AF_INET, req_options[i], NULL, 0, NULL, 0);
+	  q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
+			"%d%s%s%s", 
+			req_options[i],
+			strlen(s) != 0 ? ":" : "",
+			s, 
+			req_options[i+1] == OPTION_END ? "" : ", ");
+	  if (req_options[i+1] == OPTION_END || (q - daemon->namebuff) > 40)
+	    {
+	      q = daemon->namebuff;
+	      my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), ntohl(mess->xid), daemon->namebuff);
+	    }
+	}
+    }
+      
+  for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
+    if ((!id_list->list) || match_netid(id_list->list, netid, 0))
+      break;
+  if (id_list)
+    mess->flags |= htons(0x8000); /* force broadcast */
+  
+  if (context)
+    mess->siaddr = context->local;
+  
+  /* See if we can send the boot stuff as options.
+     To do this we need a requested option list, BOOTP
+     and very old DHCP clients won't have this, we also 
+     provide a manual option to disable it.
+     Some PXE ROMs have bugs (surprise!) and need zero-terminated 
+     names, so we always send those.  */
+  if ((boot = find_boot(tagif)))
+    {
+      if (boot->sname)
+	{	  
+	  if (!option_bool(OPT_NO_OVERRIDE) &&
+	      req_options && 
+	      in_list(req_options, OPTION_SNAME))
+	    option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
+	  else
+	    safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
+	}
+      
+      if (boot->file)
+	{
+	  if (!option_bool(OPT_NO_OVERRIDE) &&
+	      req_options && 
+	      in_list(req_options, OPTION_FILENAME))
+	    option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
+	  else
+	    safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+	}
+      
+      if (boot->next_server.s_addr) 
+	mess->siaddr = boot->next_server;
+      else if (boot->tftp_sname)
+	mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
+    }
+  else
+    /* Use the values of the relevant options if no dhcp-boot given and
+       they're not explicitly asked for as options. OPTION_END is used
+       as an internal way to specify siaddr without using dhcp-boot, for use in
+       dhcp-optsfile. */
+    {
+      if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
+	  (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
+	{
+	  safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
+	  done_file = 1;
+	}
+      
+      if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
+	  (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
+	{
+	  safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
+	  done_server = 1;
+	}
+      
+      if ((opt = option_find2(OPTION_END)))
+	mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;	
+    }
+        
+  /* We don't want to do option-overload for BOOTP, so make the file and sname
+     fields look like they are in use, even when they aren't. This gets restored
+     at the end of this function. */
+
+  if (!req_options || option_bool(OPT_NO_OVERRIDE))
+    {
+      f0 = mess->file[0];
+      mess->file[0] = 1;
+      s0 = mess->sname[0];
+      mess->sname[0] = 1;
+    }
+      
+  /* At this point, if mess->sname or mess->file are zeroed, they are available
+     for option overload, reserve space for the overload option. */
+  if (mess->file[0] == 0 || mess->sname[0] == 0)
+    end -= 3;
+
+  /* rfc3011 says this doesn't need to be in the requested options list. */
+  if (subnet_addr.s_addr)
+    option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
+   
+  if (lease_time != 0xffffffff)
+    { 
+      unsigned int t1val = lease_time/2; 
+      unsigned int t2val = (lease_time*7)/8;
+      unsigned int hval;
+      
+      /* If set by user, sanity check, so not longer than lease. */
+      if ((opt = option_find2(OPTION_T1)))
+	{
+	  hval = ntohl(*((unsigned int *)opt->val));
+	  if (hval < lease_time && hval > 2)
+	    t1val = hval;
+	}
+
+       if ((opt = option_find2(OPTION_T2)))
+	{
+	  hval = ntohl(*((unsigned int *)opt->val));
+	  if (hval < lease_time && hval > 2)
+	    t2val = hval;
+	}
+       	  
+       /* ensure T1 is still < T2 */
+       if (t2val <= t1val)
+	 t1val = t2val - 1; 
+
+       while (fuzz > (t1val/8))
+	 fuzz = fuzz/2;
+	 
+       t1val -= fuzz;
+       t2val -= fuzz;
+       
+       option_put(mess, end, OPTION_T1, 4, t1val);
+       option_put(mess, end, OPTION_T2, 4, t2val);
+    }
+
+  /* replies to DHCPINFORM may not have a valid context */
+  if (context)
+    {
+      if (!option_find2(OPTION_NETMASK))
+	option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
+  
+      /* May not have a "guessed" broadcast address if we got no packets via a relay
+	 from this net yet (ie just unicast renewals after a restart */
+      if (context->broadcast.s_addr &&
+	  !option_find2(OPTION_BROADCAST))
+	option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
+      
+      /* Same comments as broadcast apply, and also may not be able to get a sensible
+	 default when using subnet select.  User must configure by steam in that case. */
+      if (context->router.s_addr &&
+	  in_list(req_options, OPTION_ROUTER) &&
+	  !option_find2(OPTION_ROUTER))
+	option_put(mess, end, OPTION_ROUTER, INADDRSZ, ntohl(context->router.s_addr));
+      
+      if (daemon->port == NAMESERVER_PORT &&
+	  in_list(req_options, OPTION_DNSSERVER) &&
+	  !option_find2(OPTION_DNSSERVER))
+	option_put(mess, end, OPTION_DNSSERVER, INADDRSZ, ntohl(context->local.s_addr));
+    }
+
+  if (domain && in_list(req_options, OPTION_DOMAINNAME) && 
+      !option_find2(OPTION_DOMAINNAME))
+    option_put_string(mess, end, OPTION_DOMAINNAME, domain, null_term);
+ 
+  /* Note that we ignore attempts to set the fqdn using --dhc-option=81,<name> */
+  if (hostname)
+    {
+      if (in_list(req_options, OPTION_HOSTNAME) &&
+	  !option_find2(OPTION_HOSTNAME))
+	option_put_string(mess, end, OPTION_HOSTNAME, hostname, null_term);
+      
+      if (fqdn_flags != 0)
+	{
+	  len = strlen(hostname) + 3;
+	  
+	  if (fqdn_flags & 0x04)
+	    len += 2;
+	  else if (null_term)
+	    len++;
+
+	  if (domain)
+	    len += strlen(domain) + 1;
+	  else if (fqdn_flags & 0x04)
+	    len--;
+
+	  if ((p = free_space(mess, end, OPTION_CLIENT_FQDN, len)))
+	    {
+	      *(p++) = fqdn_flags & 0x0f; /* MBZ bits to zero */ 
+	      *(p++) = 255;
+	      *(p++) = 255;
+
+	      if (fqdn_flags & 0x04)
+		{
+		  p = do_rfc1035_name(p, hostname, NULL);
+		  if (domain)
+		    {
+		      p = do_rfc1035_name(p, domain, NULL);
+		      *p++ = 0;
+		    }
+		}
+	      else
+		{
+		  memcpy(p, hostname, strlen(hostname));
+		  p += strlen(hostname);
+		  if (domain)
+		    {
+		      *(p++) = '.';
+		      memcpy(p, domain, strlen(domain));
+		      p += strlen(domain);
+		    }
+		  if (null_term)
+		    *(p++) = 0;
+		}
+	    }
+	}
+    }      
+
+  for (opt = config_opts; opt; opt = opt->next)
+    {
+      int optno = opt->opt;
+
+      /* netids match and not encapsulated? */
+      if (!(opt->flags & DHOPT_TAGOK))
+	continue;
+      
+      /* was it asked for, or are we sending it anyway? */
+      if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno))
+	continue;
+      
+      /* prohibit some used-internally options. T1 and T2 already handled. */
+      if (optno == OPTION_CLIENT_FQDN ||
+	  optno == OPTION_MAXMESSAGE ||
+	  optno == OPTION_OVERLOAD ||
+	  optno == OPTION_PAD ||
+	  optno == OPTION_END ||
+	  optno == OPTION_T1 ||
+	  optno == OPTION_T2)
+	continue;
+
+      if (optno == OPTION_SNAME && done_server)
+	continue;
+
+      if (optno == OPTION_FILENAME && done_file)
+	continue;
+      
+      /* For the options we have default values on
+	 dhc-option=<optionno> means "don't include this option"
+	 not "include a zero-length option" */
+      if (opt->len == 0 && 
+	  (optno == OPTION_NETMASK ||
+	   optno == OPTION_BROADCAST ||
+	   optno == OPTION_ROUTER ||
+	   optno == OPTION_DNSSERVER || 
+	   optno == OPTION_DOMAINNAME ||
+	   optno == OPTION_HOSTNAME))
+	continue;
+
+      /* vendor-class comes from elsewhere for PXE */
+      if (pxe_arch != -1 && optno == OPTION_VENDOR_ID)
+	continue;
+      
+      /* always force null-term for filename and servername - buggy PXE again. */
+      len = do_opt(opt, NULL, context, 
+		   (optno == OPTION_SNAME || optno == OPTION_FILENAME) ? 1 : null_term);
+
+      if ((p = free_space(mess, end, optno, len)))
+	{
+	  do_opt(opt, p, context, 
+		 (optno == OPTION_SNAME || optno == OPTION_FILENAME) ? 1 : null_term);
+	  
+	  /* If we send a vendor-id, revisit which vendor-ops we consider 
+	     it appropriate to send. */
+	  if (optno == OPTION_VENDOR_ID)
+	    {
+	      match_vendor_opts(p - 2, config_opts);
+	      done_vendor_class = 1;
+	    }
+	}  
+    }
+
+  /* Now send options to be encapsulated in arbitrary options, 
+     eg dhcp-option=encap:172,17,.......
+     Also handle vendor-identifying vendor-encapsulated options,
+     dhcp-option = vi-encap:13,17,.......
+     The may be more that one "outer" to do, so group
+     all the options which match each outer in turn. */
+  for (opt = config_opts; opt; opt = opt->next)
+    opt->flags &= ~DHOPT_ENCAP_DONE;
+  
+  for (opt = config_opts; opt; opt = opt->next)
+    {
+      int flags;
+      
+      if ((flags = (opt->flags & (DHOPT_ENCAPSULATE | DHOPT_RFC3925))))
+	{
+	  int found = 0;
+	  struct dhcp_opt *o;
+
+	  if (opt->flags & DHOPT_ENCAP_DONE)
+	    continue;
+
+	  for (len = 0, o = config_opts; o; o = o->next)
+	    {
+	      int outer = flags & DHOPT_ENCAPSULATE ? o->u.encap : OPTION_VENDOR_IDENT_OPT;
+
+	      o->flags &= ~DHOPT_ENCAP_MATCH;
+	      
+	      if (!(o->flags & flags) || opt->u.encap != o->u.encap)
+		continue;
+	      
+	      o->flags |= DHOPT_ENCAP_DONE;
+	      if (match_netid(o->netid, tagif, 1) &&
+		  ((o->flags & DHOPT_FORCE) || in_list(req_options, outer)))
+		{
+		  o->flags |= DHOPT_ENCAP_MATCH;
+		  found = 1;
+		  len += do_opt(o, NULL, NULL, 0) + 2;
+		}
+	    } 
+	  
+	  if (found)
+	    { 
+	      if (flags & DHOPT_ENCAPSULATE)
+		do_encap_opts(config_opts, opt->u.encap, DHOPT_ENCAP_MATCH, mess, end, null_term);
+	      else if (len > 250)
+		my_syslog(MS_DHCP | LOG_WARNING, _("cannot send RFC3925 option: too many options for enterprise number %d"), opt->u.encap);
+	      else if ((p = free_space(mess, end,  OPTION_VENDOR_IDENT_OPT, len + 5)))
+		{
+		  int swap_ent = htonl(opt->u.encap);
+		  memcpy(p, &swap_ent, 4);
+		  p += 4;
+		  *(p++) = len;
+		  for (o = config_opts; o; o = o->next)
+		    if (o->flags & DHOPT_ENCAP_MATCH)
+		      {
+			len = do_opt(o, p + 2, NULL, 0);
+			*(p++) = o->opt;
+			*(p++) = len;
+			p += len;
+		      }     
+		}
+	    }
+	}
+    }      
+
+  force_encap = prune_vendor_opts(tagif);
+  
+  if (context && pxe_arch != -1)
+    {
+      pxe_misc(mess, end, uuid, pxevendor);
+      if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
+	config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
+    }
+
+  if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&
+      do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) && 
+      pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 &&
+      (p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len)))
+    /* If we send vendor encapsulated options, and haven't already sent option 60,
+       echo back the value we got from the client. */
+    memcpy(p, daemon->dhcp_buff3, vendor_class_len);	    
+   
+   /* restore BOOTP anti-overload hack */
+  if (!req_options || option_bool(OPT_NO_OVERRIDE))
+    {
+      mess->file[0] = f0;
+      mess->sname[0] = s0;
+    }
+}
+
+static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid)
+{
+  struct delay_config *delay_conf;
+  
+  /* Decide which delay_config option we're using */
+  for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next)
+    if (match_netid(delay_conf->netid, netid, 0))
+      break;
+  
+  if (!delay_conf)
+    /* No match, look for one without a netid */
+    for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next)
+      if (match_netid(delay_conf->netid, netid, 1))
+        break;
+
+  if (delay_conf)
+    {
+      if (!option_bool(OPT_QUIET_DHCP))
+	my_syslog(MS_DHCP | LOG_INFO, _("%u reply delay: %d"), ntohl(xid), delay_conf->delay);
+      delay_dhcp(recvtime, delay_conf->delay, -1, 0, 0);
+    }
+}
+
+#endif /* HAVE_DHCP */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c
new file mode 100755
index 0000000..5c2ff97
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rfc3315.c
@@ -0,0 +1,2230 @@
+/* dnsmasq is Copyright (c) 2000-2021 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
+
+struct state {
+  unsigned char *clid;
+  int clid_len, ia_type, interface, hostname_auth, lease_allocate;
+  char *client_hostname, *hostname, *domain, *send_domain;
+  struct dhcp_context *context;
+  struct in6_addr *link_address, *fallback, *ll_addr, *ula_addr;
+  unsigned int xid, fqdn_flags, iaid;
+  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;
+};
+
+static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, 
+			     struct in6_addr *client_addr, int is_unicast, time_t now);
+static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now);
+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);
+static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string);
+static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize);
+static void *opt6_next(void *opts, void *end);
+static unsigned int opt6_uint(unsigned char *opt, int offset, int size);
+static void get_context_tag(struct state *state, struct dhcp_context *context);
+static int check_ia(struct state *state, void *opt, void **endp, void **ia_option);
+static int build_ia(struct state *state, int *t1cntr);
+static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz);
+static void mark_context_used(struct state *state, struct in6_addr *addr);
+static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr);
+static int check_address(struct state *state, struct in6_addr *addr);
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now);
+static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr);
+static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, 
+			unsigned int *min_time, struct in6_addr *addr, time_t now);
+static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now);
+static int add_local_addrs(struct dhcp_context *context);
+static struct dhcp_netid *add_options(struct state *state, int do_refresh);
+static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep, 
+			    unsigned int *preferred_timep, unsigned int lease_time);
+
+#define opt6_len(opt) ((int)(opt6_uint(opt, -2, 2)))
+#define opt6_type(opt) (opt6_uint(opt, -4, 2))
+#define opt6_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[4+(i)]))
+
+#define opt6_user_vendor_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2+(i)]))
+#define opt6_user_vendor_len(opt) ((int)(opt6_uint(opt, -4, 2)))
+#define opt6_user_vendor_next(opt, end) (opt6_next(((void *) opt) - 2, end))
+ 
+
+unsigned short dhcp6_reply(struct dhcp_context *context, int interface, char *iface_name,
+			   struct in6_addr *fallback,  struct in6_addr *ll_addr, struct in6_addr *ula_addr,
+			   size_t sz, struct in6_addr *client_addr, time_t now)
+{
+  struct dhcp_vendor *vendor;
+  int msg_type;
+  struct state state;
+  
+  if (sz <= 4)
+    return 0;
+  
+  msg_type = *((unsigned char *)daemon->dhcp_packet.iov_base);
+  
+  /* Mark these so we only match each at most once, to avoid tangled linked lists */
+  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+    vendor->netid.next = &vendor->netid;
+  
+  reset_counter();
+  state.context = context;
+  state.interface = interface;
+  state.iface_name = iface_name;
+  state.fallback = fallback;
+  state.ll_addr = ll_addr;
+  state.ula_addr = ula_addr;
+  state.mac_len = 0;
+  state.tags = NULL;
+  state.link_address = NULL;
+
+  if (dhcp6_maybe_relay(&state, daemon->dhcp_packet.iov_base, sz, client_addr, 
+			IN6_IS_ADDR_MULTICAST(client_addr), now))
+    return msg_type == DHCP6RELAYFORW ? DHCPV6_SERVER_PORT : DHCPV6_CLIENT_PORT;
+
+  return 0;
+}
+
+/* This cost me blood to write, it will probably cost you blood to understand - srk. */
+static int dhcp6_maybe_relay(struct state *state, void *inbuff, size_t sz, 
+			     struct in6_addr *client_addr, int is_unicast, time_t now)
+{
+  void *end = inbuff + sz;
+  void *opts = inbuff + 34;
+  int msg_type = *((unsigned char *)inbuff);
+  unsigned char *outmsgtypep;
+  void *opt;
+  struct dhcp_vendor *vendor;
+
+  /* if not an encapsulated relayed message, just do the stuff */
+  if (msg_type != DHCP6RELAYFORW)
+    {
+      /* 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. 
+
+      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 (!state->link_address)
+	get_client_mac(client_addr, state->interface, state->mac, &state->mac_len, &state->mac_type, now);
+      else
+	{
+	  struct dhcp_context *c;
+	  struct shared_network *share = NULL;
+	  state->context = NULL;
+
+	  if (!IN6_IS_ADDR_LOOPBACK(state->link_address) &&
+	      !IN6_IS_ADDR_LINKLOCAL(state->link_address) &&
+	      !IN6_IS_ADDR_MULTICAST(state->link_address))
+	    for (c = daemon->dhcp6; c; c = c->next)
+	      {
+		for (share = daemon->shared_networks; share; share = share->next)
+		  {
+		    if (share->shared_addr.s_addr != 0)
+		      continue;
+		    
+		    if (share->if_index != 0 ||
+			!IN6_ARE_ADDR_EQUAL(state->link_address, &share->match_addr6))
+		      continue;
+		    
+		    if ((c->flags & CONTEXT_DHCP) &&
+			!(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+			is_same_net6(&share->shared_addr6, &c->start6, c->prefix) &&
+			is_same_net6(&share->shared_addr6, &c->end6, c->prefix))
+		      break;
+		  }
+		
+		if (share ||
+		    ((c->flags & CONTEXT_DHCP) &&
+		     !(c->flags & (CONTEXT_TEMPLATE | CONTEXT_OLD)) &&
+		     is_same_net6(state->link_address, &c->start6, c->prefix) &&
+		     is_same_net6(state->link_address, &c->end6, c->prefix)))
+		  {
+		    c->preferred = c->valid = 0xffffffff;
+		    c->current = state->context;
+		    state->context = c;
+		  }
+	      }
+	  
+	  if (!state->context)
+	    {
+	      inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN); 
+	      my_syslog(MS_DHCP | LOG_WARNING, 
+			_("no address range available for DHCPv6 request from relay at %s"),
+			daemon->addrbuff);
+	      return 0;
+	    }
+	}
+	  
+      if (!state->context)
+	{
+	  my_syslog(MS_DHCP | LOG_WARNING, 
+		    _("no address range available for DHCPv6 request via %s"), state->iface_name);
+	  return 0;
+	}
+
+      return dhcp6_no_relay(state, msg_type, inbuff, sz, is_unicast, now);
+    }
+
+  /* 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)
+    return 0;
+  
+  /* copy header stuff into reply message and set type to reply */
+  if (!(outmsgtypep = put_opt6(inbuff, 34)))
+    return 0;
+  *outmsgtypep = DHCP6RELAYREPL;
+
+  /* look for relay options and set tags if found. */
+  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+    {
+      int mopt;
+      
+      if (vendor->match_type == MATCH_SUBSCRIBER)
+	mopt = OPTION6_SUBSCRIBER_ID;
+      else if (vendor->match_type == MATCH_REMOTE)
+	mopt = OPTION6_REMOTE_ID; 
+      else
+	continue;
+
+      if ((opt = opt6_find(opts, end, mopt, 1)) &&
+	  vendor->len == opt6_len(opt) &&
+	  memcmp(vendor->data, opt6_ptr(opt, 0), vendor->len) == 0 &&
+	  vendor->netid.next != &vendor->netid)
+	{
+	  vendor->netid.next = state->tags;
+	  state->tags = &vendor->netid;
+	  break;
+	}
+    }
+  
+  /* RFC-6939 */
+  if ((opt = opt6_find(opts, end, OPTION6_CLIENT_MAC, 3)))
+    {
+      if (opt6_len(opt) - 2 > DHCP_CHADDR_MAX) {
+        return 0;
+      }
+      state->mac_type = opt6_uint(opt, 0, 2);
+      state->mac_len = opt6_len(opt) - 2;
+      memcpy(&state->mac[0], opt6_ptr(opt, 2), state->mac_len);
+    }
+  
+  for (opt = opts; opt; opt = opt6_next(opt, end))
+    {
+      if (opt6_ptr(opt, 0) + opt6_len(opt) > end) 
+        return 0;
+     
+      /* Don't copy MAC address into reply. */
+      if (opt6_type(opt) != OPTION6_CLIENT_MAC)
+	{
+	  int o = new_opt6(opt6_type(opt));
+	  if (opt6_type(opt) == OPTION6_RELAY_MSG)
+	    {
+	      struct in6_addr align;
+	      /* the packet data is unaligned, copy to aligned storage */
+	      memcpy(&align, inbuff + 2, IN6ADDRSZ); 
+	      state->link_address = &align;
+	      /* 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(state, opt6_ptr(opt, 0), opt6_len(opt), client_addr, 0, now))
+		return 0;
+	    }
+	  else
+	    put_opt6(opt6_ptr(opt, 0), opt6_len(opt));
+	  end_opt6(o);
+	}
+    }
+  
+  return 1;
+}
+
+static int dhcp6_no_relay(struct state *state, int msg_type, void *inbuff, size_t sz, int is_unicast, time_t now)
+{
+  void *opt;
+  int i, o, o1, start_opts;
+  struct dhcp_opt *opt_cfg;
+  struct dhcp_netid *tagif;
+  struct dhcp_config *config = NULL;
+  struct dhcp_netid known_id, iface_id, v6_id;
+  unsigned char *outmsgtypep;
+  struct dhcp_vendor *vendor;
+  struct dhcp_context *context_tmp;
+  struct dhcp_mac *mac_opt;
+  unsigned int ignore = 0;
+
+  state->packet_options = inbuff + 4;
+  state->end = inbuff + sz;
+  state->clid = NULL;
+  state->clid_len = 0;
+  state->lease_allocate = 0;
+  state->context_tags = NULL;
+  state->domain = NULL;
+  state->send_domain = NULL;
+  state->hostname_auth = 0;
+  state->hostname = NULL;
+  state->client_hostname = NULL;
+  state->fqdn_flags = 0x01; /* default to send if we receive no FQDN option */
+
+  /* set tag with name == interface */
+  iface_id.net = state->iface_name;
+  iface_id.next = state->tags;
+  state->tags = &iface_id; 
+
+  /* set tag "dhcpv6" */
+  v6_id.net = "dhcpv6";
+  v6_id.next = state->tags;
+  state->tags = &v6_id;
+
+  /* copy over transaction-id, and save pointer to message type */
+  if (!(outmsgtypep = put_opt6(inbuff, 4)))
+    return 0;
+  start_opts = save_counter(-1);
+  state->xid = outmsgtypep[3] | outmsgtypep[2] << 8 | outmsgtypep[1] << 16;
+   
+  /* We're going to be linking tags from all context we use. 
+     mark them as unused so we don't link one twice and break the list */
+  for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current)
+    {
+      context_tmp->netid.next = &context_tmp->netid;
+
+      if (option_bool(OPT_LOG_OPTS))
+	{
+	   inet_ntop(AF_INET6, &context_tmp->start6, daemon->dhcp_buff, ADDRSTRLEN); 
+	   inet_ntop(AF_INET6, &context_tmp->end6, daemon->dhcp_buff2, ADDRSTRLEN); 
+	   if (context_tmp->flags & (CONTEXT_STATIC))
+	     my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCPv6 subnet: %s/%d"),
+		       state->xid, daemon->dhcp_buff, context_tmp->prefix);
+	   else
+	     my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"), 
+		       state->xid, daemon->dhcp_buff, daemon->dhcp_buff2);
+	}
+    }
+
+  if ((opt = opt6_find(state->packet_options, state->end, OPTION6_CLIENT_ID, 1)))
+    {
+      state->clid = opt6_ptr(opt, 0);
+      state->clid_len = opt6_len(opt);
+      o = new_opt6(OPTION6_CLIENT_ID);
+      put_opt6(state->clid, state->clid_len);
+      end_opt6(o);
+    }
+  else if (msg_type != DHCP6IREQ)
+    return 0;
+
+  /* server-id must match except for SOLICIT, CONFIRM and REBIND messages */
+  if (msg_type != DHCP6SOLICIT && msg_type != DHCP6CONFIRM && msg_type != DHCP6IREQ && msg_type != DHCP6REBIND &&
+      (!(opt = opt6_find(state->packet_options, state->end, OPTION6_SERVER_ID, 1)) ||
+       opt6_len(opt) != daemon->duid_len ||
+       memcmp(opt6_ptr(opt, 0), daemon->duid, daemon->duid_len) != 0))
+    return 0;
+  
+  o = new_opt6(OPTION6_SERVER_ID);
+  put_opt6(daemon->duid, daemon->duid_len);
+  end_opt6(o);
+
+  if (is_unicast &&
+      (msg_type == DHCP6REQUEST || msg_type == DHCP6RENEW || msg_type == DHCP6RELEASE || msg_type == DHCP6DECLINE))
+    
+    {  
+      *outmsgtypep = DHCP6REPLY;
+      o1 = new_opt6(OPTION6_STATUS_CODE);
+      put_opt6_short(DHCP6USEMULTI);
+      put_opt6_string("Use multicast");
+      end_opt6(o1);
+      return 1;
+    }
+
+  /* match vendor and user class options */
+  for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+    {
+      int mopt;
+      
+      if (vendor->match_type == MATCH_VENDOR)
+	mopt = OPTION6_VENDOR_CLASS;
+      else if (vendor->match_type == MATCH_USER)
+	mopt = OPTION6_USER_CLASS; 
+      else
+	continue;
+
+      if ((opt = opt6_find(state->packet_options, state->end, mopt, 2)))
+	{
+	  void *enc_opt, *enc_end = opt6_ptr(opt, opt6_len(opt));
+	  int offset = 0;
+	  
+	  if (mopt == OPTION6_VENDOR_CLASS)
+	    {
+	      if (opt6_len(opt) < 4)
+		continue;
+	      
+	      if (vendor->enterprise != opt6_uint(opt, 0, 4))
+		continue;
+	    
+	      offset = 4;
+	    }
+ 
+	  /* Note that format if user/vendor classes is different to DHCP options - no option types. */
+	  for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end))
+	    for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++)
+	      if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)
+		{
+		  vendor->netid.next = state->tags;
+		  state->tags = &vendor->netid;
+		  break;
+		}
+	}
+    }
+
+  if (option_bool(OPT_LOG_OPTS) && (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4)))
+    my_syslog(MS_DHCP | LOG_INFO, _("%u vendor class: %u"), state->xid, opt6_uint(opt, 0, 4));
+  
+  /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match.
+     Otherwise assume the option is an array, and look for a matching element. 
+     If no data given, existence of the option is enough. This code handles 
+     V-I opts too. */
+  for (opt_cfg = daemon->dhcp_match6; opt_cfg; opt_cfg = opt_cfg->next)
+    {
+      int match = 0;
+      
+      if (opt_cfg->flags & DHOPT_RFC3925)
+	{
+	  for (opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_OPTS, 4);
+	       opt;
+	       opt = opt6_find(opt6_next(opt, state->end), state->end, OPTION6_VENDOR_OPTS, 4))
+	    {
+	      void *vopt;
+	      void *vend = opt6_ptr(opt, opt6_len(opt));
+	      
+	      for (vopt = opt6_find(opt6_ptr(opt, 4), vend, opt_cfg->opt, 0);
+		   vopt;
+		   vopt = opt6_find(opt6_next(vopt, vend), vend, opt_cfg->opt, 0))
+		if ((match = match_bytes(opt_cfg, opt6_ptr(vopt, 0), opt6_len(vopt))))
+		  break;
+	    }
+	  if (match)
+	    break;
+	}
+      else
+	{
+	  if (!(opt = opt6_find(state->packet_options, state->end, opt_cfg->opt, 1)))
+	    continue;
+	  
+	  match = match_bytes(opt_cfg, opt6_ptr(opt, 0), opt6_len(opt));
+	} 
+  
+      if (match)
+	{
+	  opt_cfg->netid->next = state->tags;
+	  state->tags = opt_cfg->netid;
+	}
+    }
+
+  if (state->mac_len != 0)
+    {
+      if (option_bool(OPT_LOG_OPTS))
+	{
+	  print_mac(daemon->dhcp_buff, state->mac, state->mac_len);
+	  my_syslog(MS_DHCP | LOG_INFO, _("%u client MAC address: %s"), state->xid, daemon->dhcp_buff);
+	}
+
+      for (mac_opt = daemon->dhcp_macs; mac_opt; mac_opt = mac_opt->next)
+	if ((unsigned)mac_opt->hwaddr_len == state->mac_len &&
+	    ((unsigned)mac_opt->hwaddr_type == state->mac_type || mac_opt->hwaddr_type == 0) &&
+	    memcmp_masked(mac_opt->hwaddr, state->mac, state->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 */
+       int len = opt6_len(opt) - 1;
+       
+       state->fqdn_flags = opt6_uint(opt, 0, 1);
+       
+       /* Always force update, since the client has no way to do it itself. */
+       if (!option_bool(OPT_FQDN_UPDATE) && !(state->fqdn_flags & 0x01))
+	 state->fqdn_flags |= 0x03;
+ 
+       state->fqdn_flags &= ~0x04;
+
+       if (len != 0 && len < 255)
+	 {
+	   unsigned char *pp, *op = opt6_ptr(opt, 1);
+	   char *pq = daemon->dhcp_buff;
+	   
+	   pp = op;
+	   while (*op != 0 && ((op + (*op)) - pp) < len)
+	     {
+	       memcpy(pq, op+1, *op);
+	       pq += *op;
+	       op += (*op)+1;
+	       *(pq++) = '.';
+	     }
+	   
+	   if (pq != daemon->dhcp_buff)
+	     pq--;
+	   *pq = 0;
+	   
+	   if (legal_hostname(daemon->dhcp_buff))
+	     {
+	       struct dhcp_match_name *m;
+	       size_t nl = strlen(daemon->dhcp_buff);
+	       
+	       state->client_hostname = daemon->dhcp_buff;
+	       
+	       if (option_bool(OPT_LOG_OPTS))
+		 my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), state->xid, state->client_hostname);
+	       
+	       for (m = daemon->dhcp_name_match; m; m = m->next)
+		 {
+		   size_t ml = strlen(m->name);
+		   char save = 0;
+		   
+		   if (nl < ml)
+		     continue;
+		   if (nl > ml)
+		     {
+		       save = state->client_hostname[ml];
+		       state->client_hostname[ml] = 0;
+		     }
+		   
+		   if (hostname_isequal(state->client_hostname, m->name) &&
+		       (save == 0 || m->wildcard))
+		     {
+		       m->netid->next = state->tags;
+		       state->tags = m->netid;
+		     }
+		   
+		   if (save != 0)
+		     state->client_hostname[ml] = save;
+		 }
+	     }
+	 }
+    }	 
+  
+  if (state->clid &&
+      (config = find_config(daemon->dhcp_conf, state->context, state->clid, state->clid_len,
+			    state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags))) &&
+      have_config(config, CONFIG_NAME))
+    {
+      state->hostname = config->hostname;
+      state->domain = config->domain;
+      state->hostname_auth = 1;
+    }
+  else if (state->client_hostname)
+    {
+      state->domain = strip_hostname(state->client_hostname);
+      
+      if (strlen(state->client_hostname) != 0)
+	{
+	  state->hostname = state->client_hostname;
+	  
+	  if (!config)
+	    {
+	      /* 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_config(daemon->dhcp_conf, state->context, NULL, 0, NULL, 0, 0, state->hostname, run_tag_if(state->tags));
+	      if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr)
+		config = new;
+	    }
+	}
+    }
+
+  if (config)
+    {
+      struct dhcp_netid_list *list;
+      
+      for (list = config->netid; list; list = list->next)
+        {
+          list->list->next = state->tags;
+          state->tags = list->list;
+        }
+
+      /* set "known" tag for known hosts */
+      known_id.net = "known";
+      known_id.next = state->tags;
+      state->tags = &known_id;
+
+      if (have_config(config, CONFIG_DISABLE))
+	ignore = 1;
+    }
+  else if (state->clid &&
+	   find_config(daemon->dhcp_conf, NULL, state->clid, state->clid_len,
+		       state->mac, state->mac_len, state->mac_type, NULL, run_tag_if(state->tags)))
+    {
+      known_id.net = "known-othernet";
+      known_id.next = state->tags;
+      state->tags = &known_id;
+    }
+  
+  tagif = run_tag_if(state->tags);
+  
+  /* if all the netids in the ignore list are present, ignore this client */
+  if (daemon->dhcp_ignore)
+    {
+      struct dhcp_netid_list *id_list;
+     
+      for (id_list = daemon->dhcp_ignore; id_list; id_list = id_list->next)
+	if (match_netid(id_list->list, tagif, 0))
+	  ignore = 1;
+    }
+  
+  /* if all the netids in the ignore_name list are present, ignore client-supplied name */
+  if (!state->hostname_auth)
+    {
+       struct dhcp_netid_list *id_list;
+       
+       for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+	 if ((!id_list->list) || match_netid(id_list->list, tagif, 0))
+	   break;
+       if (id_list)
+	 state->hostname = NULL;
+    }
+  
+
+  switch (msg_type)
+    {
+    default:
+      return 0;
+      
+      
+    case DHCP6SOLICIT:
+      {
+      	int address_assigned = 0;
+	/* tags without all prefix-class tags */
+	struct dhcp_netid *solicit_tags;
+	struct dhcp_context *c;
+	
+	*outmsgtypep = DHCP6ADVERTISE;
+	
+	if (opt6_find(state->packet_options, state->end, OPTION6_RAPID_COMMIT, 0))
+	  {
+	    *outmsgtypep = DHCP6REPLY;
+	    state->lease_allocate = 1;
+	    o = new_opt6(OPTION6_RAPID_COMMIT);
+	    end_opt6(o);
+	  }
+	
+  	log6_quiet(state, "DHCPSOLICIT", NULL, ignore ? _("ignored") : NULL);
+
+      request_no_address:
+	solicit_tags = tagif;
+	
+	if (ignore)
+	  return 0;
+	
+	/* reset USED bits in leases */
+	lease6_reset();
+
+	/* Can use configured address max once per prefix */
+	for (c = state->context; c; c = c->current)
+	  c->flags &= ~CONTEXT_CONF_USED;
+
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {   
+	    void *ia_option, *ia_end;
+	    unsigned int min_time = 0xffffffff;
+	    int t1cntr;
+	    int ia_counter;
+	    /* set unless we're sending a particular prefix-class, when we
+	       want only dhcp-ranges with the correct tags set and not those without any tags. */
+	    int plain_range = 1;
+	    u32 lease_time;
+	    struct dhcp_lease *ltmp;
+	    struct in6_addr req_addr, addr;
+	    
+	    if (!check_ia(state, opt, &ia_end, &ia_option))
+	      continue;
+	    
+	    /* reset USED bits in contexts - one address per prefix per IAID */
+	    for (c = state->context; c; c = c->current)
+	      c->flags &= ~CONTEXT_USED;
+
+	    o = build_ia(state, &t1cntr);
+	    if (address_assigned)
+		address_assigned = 2;
+
+	    for (ia_counter = 0; ia_option; ia_counter++, ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+	      {
+		/* worry about alignment here. */
+		memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+				
+		if ((c = address6_valid(state->context, &req_addr, solicit_tags, plain_range)))
+		  {
+		    lease_time = c->lease_time;
+		    /* If the client asks for an address on the same network as a configured address, 
+		       offer the configured address instead, to make moving to newly-configured
+		       addresses automatic. */
+		    if (!(c->flags & CONTEXT_CONF_USED) && config_valid(config, c, &addr, state, now))
+		      {
+			req_addr = addr;
+			mark_config_used(c, &addr);
+			if (have_config(config, CONFIG_TIME))
+			  lease_time = config->lease_time;
+		      }
+		    else if (!(c = address6_available(state->context, &req_addr, solicit_tags, plain_range)))
+		      continue; /* not an address we're allowed */
+		    else if (!check_address(state, &req_addr))
+		      continue; /* address leased elsewhere */
+		    
+		    /* add address to output packet */
+		    add_address(state, c, lease_time, ia_option, &min_time, &req_addr, now);
+		    mark_context_used(state, &req_addr);
+		    get_context_tag(state, c);
+		    address_assigned = 1;
+		  }
+	      }
+	    
+	    /* Suggest configured address(es) */
+	    for (c = state->context; c; c = c->current) 
+	      if (!(c->flags & CONTEXT_CONF_USED) &&
+		  match_netid(c->filter, solicit_tags, plain_range) &&
+		  config_valid(config, c, &addr, state, now))
+		{
+		  mark_config_used(state->context, &addr);
+		  if (have_config(config, CONFIG_TIME))
+		    lease_time = config->lease_time;
+		  else
+		    lease_time = c->lease_time;
+
+		  /* add address to output packet */
+		  add_address(state, c, lease_time, NULL, &min_time, &addr, now);
+		  mark_context_used(state, &addr);
+		  get_context_tag(state, c);
+		  address_assigned = 1;
+		}
+	    
+	    /* return addresses for existing leases */
+	    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 = ltmp->addr6;
+		if ((c = address6_available(state->context, &req_addr, solicit_tags, plain_range)))
+		  {
+		    add_address(state, c, c->lease_time, NULL, &min_time, &req_addr, now);
+		    mark_context_used(state, &req_addr);
+		    get_context_tag(state, c);
+		    address_assigned = 1;
+		  }
+	      }
+		 	   
+	    /* Return addresses for all valid contexts which don't yet have one */
+	    while ((c = address6_allocate(state->context, state->clid, state->clid_len, state->ia_type == OPTION6_IA_TA,
+					  state->iaid, ia_counter, solicit_tags, plain_range, &addr)))
+	      {
+		add_address(state, c, c->lease_time, NULL, &min_time, &addr, now);
+		mark_context_used(state, &addr);
+		get_context_tag(state, c);
+		address_assigned = 1;
+	      }
+	    
+	    if (address_assigned != 1)
+	      {
+		/* If the server will not assign any addresses to any IAs in a
+		   subsequent Request from the client, the server MUST send an Advertise
+		   message to the client that doesn't include any IA options. */
+		if (!state->lease_allocate)
+		  {
+		    save_counter(o);
+		    continue;
+		  }
+		
+		/* If the server cannot assign any addresses to an IA in the message
+		   from the client, the server MUST include the IA in the Reply message
+		   with no addresses in the IA and a Status Code option in the IA
+		   containing status code NoAddrsAvail. */
+		o1 = new_opt6(OPTION6_STATUS_CODE);
+		put_opt6_short(DHCP6NOADDRS);
+		put_opt6_string(_("address unavailable"));
+		end_opt6(o1);
+	      }
+	    
+	    end_ia(t1cntr, min_time, 0);
+	    end_opt6(o);	
+	  }
+
+	if (address_assigned) 
+	  {
+	    o1 = new_opt6(OPTION6_STATUS_CODE);
+	    put_opt6_short(DHCP6SUCCESS);
+	    put_opt6_string(_("success"));
+	    end_opt6(o1);
+	    
+	    /* If --dhcp-authoritative is set, we can tell client not to wait for
+	       other possible servers */
+	    o = new_opt6(OPTION6_PREFERENCE);
+	    put_opt6_char(option_bool(OPT_AUTHORITATIVE) ? 255 : 0);
+	    end_opt6(o);
+	    tagif = add_options(state, 0);
+	  }
+	else
+	  { 
+	    /* no address, return error */
+	    o1 = new_opt6(OPTION6_STATUS_CODE);
+	    put_opt6_short(DHCP6NOADDRS);
+	    put_opt6_string(_("no addresses available"));
+	    end_opt6(o1);
+
+	    /* Some clients will ask repeatedly when we're not giving
+	       out addresses because we're in stateless mode. Avoid spamming
+	       the log in that case. */
+	    for (c = state->context; c; c = c->current)
+	      if (!(c->flags & CONTEXT_RA_STATELESS))
+		{
+		  log6_packet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", NULL, _("no addresses available"));
+		  break;
+		}
+	  }
+
+	break;
+      }
+      
+    case DHCP6REQUEST:
+      {
+	int address_assigned = 0;
+	int start = save_counter(-1);
+
+	/* set reply message type */
+	*outmsgtypep = DHCP6REPLY;
+	state->lease_allocate = 1;
+
+	log6_quiet(state, "DHCPREQUEST", NULL, ignore ? _("ignored") : NULL);
+	
+	if (ignore)
+	  return 0;
+	
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {   
+	    void *ia_option, *ia_end;
+	    unsigned int min_time = 0xffffffff;
+	    int t1cntr;
+	    
+	     if (!check_ia(state, opt, &ia_end, &ia_option))
+	       continue;
+
+	     if (!ia_option)
+	       {
+		 /* If we get a request with an IA_*A without addresses, treat it exactly like
+		    a SOLICT with rapid commit set. */
+		 save_counter(start);
+		 goto request_no_address; 
+	       }
+
+	    o = build_ia(state, &t1cntr);
+	      
+	    for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+	      {
+		struct in6_addr req_addr;
+		struct dhcp_context *dynamic, *c;
+		unsigned int lease_time;
+		int config_ok = 0;
+
+		/* align. */
+		memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+		
+		if ((c = address6_valid(state->context, &req_addr, tagif, 1)))
+		  config_ok = (config_implies(config, c, &req_addr) != NULL);
+		
+		if ((dynamic = address6_available(state->context, &req_addr, tagif, 1)) || c)
+		  {
+		    if (!dynamic && !config_ok)
+		      {
+			/* Static range, not configured. */
+			o1 = new_opt6(OPTION6_STATUS_CODE);
+			put_opt6_short(DHCP6NOADDRS);
+			put_opt6_string(_("address unavailable"));
+			end_opt6(o1);
+		      }
+		    else if (!check_address(state, &req_addr))
+		      {
+			/* Address leased to another DUID/IAID */
+			o1 = new_opt6(OPTION6_STATUS_CODE);
+			put_opt6_short(DHCP6UNSPEC);
+			put_opt6_string(_("address in use"));
+			end_opt6(o1);
+		      } 
+		    else 
+		      {
+			if (!dynamic)
+			  dynamic = c;
+
+			lease_time = dynamic->lease_time;
+			
+			if (config_ok && have_config(config, CONFIG_TIME))
+			  lease_time = config->lease_time;
+
+			add_address(state, dynamic, lease_time, ia_option, &min_time, &req_addr, now);
+			get_context_tag(state, dynamic);
+			address_assigned = 1;
+		      }
+		  }
+		else 
+		  {
+		    /* requested address not on the correct link */
+		    o1 = new_opt6(OPTION6_STATUS_CODE);
+		    put_opt6_short(DHCP6NOTONLINK);
+		    put_opt6_string(_("not on link"));
+		    end_opt6(o1);
+		  }
+	      }
+	 
+	    end_ia(t1cntr, min_time, 0);
+	    end_opt6(o);	
+	  }
+
+	if (address_assigned) 
+	  {
+	    o1 = new_opt6(OPTION6_STATUS_CODE);
+	    put_opt6_short(DHCP6SUCCESS);
+	    put_opt6_string(_("success"));
+	    end_opt6(o1);
+	  }
+	else
+	  { 
+	    /* no address, return error */
+	    o1 = new_opt6(OPTION6_STATUS_CODE);
+	    put_opt6_short(DHCP6NOADDRS);
+	    put_opt6_string(_("no addresses available"));
+	    end_opt6(o1);
+	    log6_packet(state, "DHCPREPLY", NULL, _("no addresses available"));
+	  }
+
+	tagif = add_options(state, 0);
+	break;
+      }
+      
+  
+    case DHCP6RENEW:
+    case DHCP6REBIND:
+      {
+	int address_assigned = 0;
+
+	/* set reply message type */
+	*outmsgtypep = DHCP6REPLY;
+	
+	log6_quiet(state, msg_type == DHCP6RENEW ? "DHCPRENEW" : "DHCPREBIND", NULL, NULL);
+
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {
+	    void *ia_option, *ia_end;
+	    unsigned int min_time = 0xffffffff;
+	    int t1cntr, iacntr;
+	    
+	    if (!check_ia(state, opt, &ia_end, &ia_option))
+	      continue;
+	    
+	    o = build_ia(state, &t1cntr);
+	    iacntr = save_counter(-1); 
+	    
+	    for (; ia_option; ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+	      {
+		struct dhcp_lease *lease = NULL;
+		struct in6_addr req_addr;
+		unsigned int preferred_time =  opt6_uint(ia_option, 16, 4);
+		unsigned int valid_time =  opt6_uint(ia_option, 20, 4);
+		char *message = NULL;
+		struct dhcp_context *this_context;
+
+		memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ); 
+		
+		if (!(lease = lease6_find(state->clid, state->clid_len,
+					  state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA, 
+					  state->iaid, &req_addr)))
+		  {
+		    if (msg_type == DHCP6REBIND)
+		      {
+			/* When rebinding, we can create a lease if it doesn't exist. */
+			lease = lease6_allocate(&req_addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
+			if (lease)
+			  lease_set_iaid(lease, state->iaid);
+			else
+			  break;
+		      }
+		    else
+		      {
+			/* If the server cannot find a client entry for the IA the server
+			   returns the IA containing no addresses with a Status Code option set
+			   to NoBinding in the Reply message. */
+			save_counter(iacntr);
+			t1cntr = 0;
+			
+			log6_packet(state, "DHCPREPLY", &req_addr, _("lease not found"));
+			
+			o1 = new_opt6(OPTION6_STATUS_CODE);
+			put_opt6_short(DHCP6NOBINDING);
+			put_opt6_string(_("no binding found"));
+			end_opt6(o1);
+			
+			preferred_time = valid_time = 0;
+			break;
+		      }
+		  }
+		
+		if ((this_context = address6_available(state->context, &req_addr, tagif, 1)) ||
+		    (this_context = address6_valid(state->context, &req_addr, tagif, 1)))
+		  {
+		    unsigned int lease_time;
+
+		    get_context_tag(state, this_context);
+		    
+		    if (config_implies(config, this_context, &req_addr) && have_config(config, CONFIG_TIME))
+		      lease_time = config->lease_time;
+		    else 
+		      lease_time = this_context->lease_time;
+		    
+		    calculate_times(this_context, &min_time, &valid_time, &preferred_time, lease_time); 
+		    
+		    lease_set_expires(lease, valid_time, now);
+		    /* Update MAC record in case it's new information. */
+		    if (state->mac_len != 0)
+		      lease_set_hwaddr(lease, state->mac, state->clid, state->mac_len, state->mac_type, state->clid_len, now, 0);
+		    if (state->ia_type == OPTION6_IA_NA && state->hostname)
+		      {
+			char *addr_domain = get_domain6(&req_addr);
+			if (!state->send_domain)
+			  state->send_domain = addr_domain;
+			lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain); 
+			message = state->hostname;
+		      }
+		    
+		    
+		    if (preferred_time == 0)
+		      message = _("deprecated");
+
+		    address_assigned = 1;
+		  }
+		else
+		  {
+		    preferred_time = valid_time = 0;
+		    message = _("address invalid");
+		  } 
+
+		if (message && (message != state->hostname))
+		  log6_packet(state, "DHCPREPLY", &req_addr, message);	
+		else
+		  log6_quiet(state, "DHCPREPLY", &req_addr, message);
+	
+		o1 =  new_opt6(OPTION6_IAADDR);
+		put_opt6(&req_addr, sizeof(req_addr));
+		put_opt6_long(preferred_time);
+		put_opt6_long(valid_time);
+		end_opt6(o1);
+	      }
+	    
+	    end_ia(t1cntr, min_time, 1);
+	    end_opt6(o);
+	  }
+
+	if (!address_assigned && msg_type == DHCP6REBIND)
+	  { 
+	    /* can't create lease for any address, return error */
+	    o1 = new_opt6(OPTION6_STATUS_CODE);
+	    put_opt6_short(DHCP6NOADDRS);
+	    put_opt6_string(_("no addresses available"));
+	    end_opt6(o1);
+	  }
+	
+	tagif = add_options(state, 0);
+	break;
+      }
+      
+    case DHCP6CONFIRM:
+      {
+	int good_addr = 0;
+
+	/* set reply message type */
+	*outmsgtypep = DHCP6REPLY;
+	
+	log6_quiet(state, "DHCPCONFIRM", NULL, NULL);
+	
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {
+	    void *ia_option, *ia_end;
+	    
+	    for (check_ia(state, opt, &ia_end, &ia_option);
+		 ia_option;
+		 ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24))
+	      {
+		struct in6_addr req_addr;
+
+		/* alignment */
+		memcpy(&req_addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+		
+		if (!address6_valid(state->context, &req_addr, tagif, 1))
+		  {
+		    o1 = new_opt6(OPTION6_STATUS_CODE);
+		    put_opt6_short(DHCP6NOTONLINK);
+		    put_opt6_string(_("confirm failed"));
+		    end_opt6(o1);
+		    log6_quiet(state, "DHCPREPLY", &req_addr, _("confirm failed"));
+		    return 1;
+		  }
+
+		good_addr = 1;
+		log6_quiet(state, "DHCPREPLY", &req_addr, state->hostname);
+	      }
+	  }	 
+	
+	/* No addresses, no reply: RFC 3315 18.2.2 */
+	if (!good_addr)
+	  return 0;
+
+	o1 = new_opt6(OPTION6_STATUS_CODE);
+	put_opt6_short(DHCP6SUCCESS );
+	put_opt6_string(_("all addresses still on link"));
+	end_opt6(o1);
+	break;
+    }
+      
+    case DHCP6IREQ:
+      {
+	/* We can't discriminate contexts based on address, as we don't know it.
+	   If there is only one possible context, we can use its tags */
+	if (state->context && state->context->netid.net && !state->context->current)
+	  {
+	    state->context->netid.next = NULL;
+	    state->context_tags =  &state->context->netid;
+	  }
+
+	/* Similarly, we can't determine domain from address, but if the FQDN is
+	   given in --dhcp-host, we can use that, and failing that we can use the 
+	   unqualified configured domain, if any. */
+	if (state->hostname_auth)
+	  state->send_domain = state->domain;
+	else
+	  state->send_domain = get_domain6(NULL);
+
+	log6_quiet(state, "DHCPINFORMATION-REQUEST", NULL, ignore ? _("ignored") : state->hostname);
+	if (ignore)
+	  return 0;
+	*outmsgtypep = DHCP6REPLY;
+	tagif = add_options(state, 1);
+	break;
+      }
+      
+      
+    case DHCP6RELEASE:
+      {
+	/* set reply message type */
+	*outmsgtypep = DHCP6REPLY;
+
+	log6_quiet(state, "DHCPRELEASE", NULL, NULL);
+
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {
+	    void *ia_option, *ia_end;
+	    int made_ia = 0;
+	    	    
+	    for (check_ia(state, opt, &ia_end, &ia_option);
+		 ia_option;
+		 ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) 
+	      {
+		struct dhcp_lease *lease;
+		struct in6_addr addr;
+
+		/* align */
+		memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+		if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA,
+					 state->iaid, &addr)))
+		  lease_prune(lease, now);
+		else
+		  {
+		    if (!made_ia)
+		      {
+			o = new_opt6(state->ia_type);
+			put_opt6_long(state->iaid);
+			if (state->ia_type == OPTION6_IA_NA)
+			  {
+			    put_opt6_long(0);
+			    put_opt6_long(0); 
+			  }
+			made_ia = 1;
+		      }
+		    
+		    o1 = new_opt6(OPTION6_IAADDR);
+		    put_opt6(&addr, IN6ADDRSZ);
+		    put_opt6_long(0);
+		    put_opt6_long(0);
+		    end_opt6(o1);
+		  }
+	      }
+	    
+	    if (made_ia)
+	      {
+		o1 = new_opt6(OPTION6_STATUS_CODE);
+		put_opt6_short(DHCP6NOBINDING);
+		put_opt6_string(_("no binding found"));
+		end_opt6(o1);
+		
+		end_opt6(o);
+	      }
+	  }
+	
+	o1 = new_opt6(OPTION6_STATUS_CODE);
+	put_opt6_short(DHCP6SUCCESS);
+	put_opt6_string(_("release received"));
+	end_opt6(o1);
+	
+	break;
+      }
+
+    case DHCP6DECLINE:
+      {
+	/* set reply message type */
+	*outmsgtypep = DHCP6REPLY;
+	
+	log6_quiet(state, "DHCPDECLINE", NULL, NULL);
+
+	for (opt = state->packet_options; opt; opt = opt6_next(opt, state->end))
+	  {
+	    void *ia_option, *ia_end;
+	    int made_ia = 0;
+	    	    
+	    for (check_ia(state, opt, &ia_end, &ia_option);
+		 ia_option;
+		 ia_option = opt6_find(opt6_next(ia_option, ia_end), ia_end, OPTION6_IAADDR, 24)) 
+	      {
+		struct dhcp_lease *lease;
+		struct in6_addr addr;
+		struct addrlist *addr_list;
+		
+		/* align */
+		memcpy(&addr, opt6_ptr(ia_option, 0), IN6ADDRSZ);
+
+		if ((addr_list = config_implies(config, state->context, &addr)))
+		  {
+		    prettyprint_time(daemon->dhcp_buff3, DECLINE_BACKOFF);
+		    inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN);
+		    my_syslog(MS_DHCP | LOG_WARNING, _("disabling DHCP static address %s for %s"), 
+			      daemon->addrbuff, daemon->dhcp_buff3);
+		    addr_list->flags |= ADDRLIST_DECLINED;
+		    addr_list->decline_time = now;
+		  }
+		else
+		  /* make sure this host gets a different address next time. */
+		  for (context_tmp = state->context; context_tmp; context_tmp = context_tmp->current)
+		    context_tmp->addr_epoch++;
+		
+		if ((lease = lease6_find(state->clid, state->clid_len, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA,
+					 state->iaid, &addr)))
+		  lease_prune(lease, now);
+		else
+		  {
+		    if (!made_ia)
+		      {
+			o = new_opt6(state->ia_type);
+			put_opt6_long(state->iaid);
+			if (state->ia_type == OPTION6_IA_NA)
+			  {
+			    put_opt6_long(0);
+			    put_opt6_long(0); 
+			  }
+			made_ia = 1;
+		      }
+		    
+		    o1 = new_opt6(OPTION6_IAADDR);
+		    put_opt6(&addr, IN6ADDRSZ);
+		    put_opt6_long(0);
+		    put_opt6_long(0);
+		    end_opt6(o1);
+		  }
+	      }
+	    
+	    if (made_ia)
+	      {
+		o1 = new_opt6(OPTION6_STATUS_CODE);
+		put_opt6_short(DHCP6NOBINDING);
+		put_opt6_string(_("no binding found"));
+		end_opt6(o1);
+		
+		end_opt6(o);
+	      }
+	    
+	  }
+
+	/* We must answer with 'success' in global section anyway */
+	o1 = new_opt6(OPTION6_STATUS_CODE);
+	put_opt6_short(DHCP6SUCCESS);
+	put_opt6_string(_("success"));
+	end_opt6(o1);
+	break;
+      }
+
+    }
+  
+  log_tags(tagif, state->xid);
+  log6_opts(0, state->xid, daemon->outpacket.iov_base + start_opts, daemon->outpacket.iov_base + save_counter(-1));
+  
+  return 1;
+
+}
+
+static struct dhcp_netid *add_options(struct state *state, int do_refresh)  
+{
+  void *oro;
+  /* filter options based on tags, those we want get DHOPT_TAGOK bit set */
+  struct dhcp_netid *tagif = option_filter(state->tags, state->context_tags, daemon->dhcp_opts6);
+  struct dhcp_opt *opt_cfg;
+  int done_dns = 0, done_refresh = !do_refresh, do_encap = 0;
+  int i, o, o1;
+
+  oro = opt6_find(state->packet_options, state->end, OPTION6_ORO, 0);
+  
+  for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+    {
+      /* netids match and not encapsulated? */
+      if (!(opt_cfg->flags & DHOPT_TAGOK))
+	continue;
+      
+      if (!(opt_cfg->flags & DHOPT_FORCE) && oro)
+	{
+	  for (i = 0; i <  opt6_len(oro) - 1; i += 2)
+	    if (opt6_uint(oro, i, 2) == (unsigned)opt_cfg->opt)
+	      break;
+	  
+	  /* option not requested */
+	  if (i >=  opt6_len(oro) - 1)
+	    continue;
+	}
+      
+      if (opt_cfg->opt == OPTION6_REFRESH_TIME)
+	done_refresh = 1;
+       
+      if (opt_cfg->opt == OPTION6_DNS_SERVER)
+	done_dns = 1;
+      
+      if (opt_cfg->flags & DHOPT_ADDR6)
+	{
+	  int len, j;
+	  struct in6_addr *a;
+	  
+	  for (a = (struct in6_addr *)opt_cfg->val, len = opt_cfg->len, j = 0; 
+	       j < opt_cfg->len; j += IN6ADDRSZ, a++)
+	    if ((IN6_IS_ADDR_ULA_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ula_addr)) ||
+		(IN6_IS_ADDR_LINK_LOCAL_ZERO(a) && IN6_IS_ADDR_UNSPECIFIED(state->ll_addr)))
+	      len -= IN6ADDRSZ;
+	  
+	  if (len != 0)
+	    {
+	      
+	      o = new_opt6(opt_cfg->opt);
+	      	  
+	      for (a = (struct in6_addr *)opt_cfg->val, j = 0; j < opt_cfg->len; j+=IN6ADDRSZ, a++)
+		{
+		  struct in6_addr *p = NULL;
+
+		  if (IN6_IS_ADDR_UNSPECIFIED(a))
+		    {
+		      if (!add_local_addrs(state->context))
+			p = state->fallback;
+		    }
+		  else if (IN6_IS_ADDR_ULA_ZERO(a))
+		    {
+		      if (!IN6_IS_ADDR_UNSPECIFIED(state->ula_addr))
+			p = state->ula_addr;
+		    }
+		  else if (IN6_IS_ADDR_LINK_LOCAL_ZERO(a))
+		    {
+		      if (!IN6_IS_ADDR_UNSPECIFIED(state->ll_addr))
+			p = state->ll_addr;
+		    }
+		  else
+		    p = a;
+
+		  if (!p)
+		    continue;
+		  else if (opt_cfg->opt == OPTION6_NTP_SERVER)
+		    {
+		      if (IN6_IS_ADDR_MULTICAST(p))
+			o1 = new_opt6(NTP_SUBOPTION_MC_ADDR);
+		      else
+			o1 = new_opt6(NTP_SUBOPTION_SRV_ADDR);
+		      put_opt6(p, IN6ADDRSZ);
+		      end_opt6(o1);
+		    }
+		  else
+		    put_opt6(p, IN6ADDRSZ);
+		}
+
+	      end_opt6(o);
+	    }
+	}
+      else
+	{
+	  o = new_opt6(opt_cfg->opt);
+	  if (opt_cfg->val)
+	    put_opt6(opt_cfg->val, opt_cfg->len);
+	  end_opt6(o);
+	}
+    }
+  
+  if (daemon->port == NAMESERVER_PORT && !done_dns)
+    {
+      o = new_opt6(OPTION6_DNS_SERVER);
+      if (!add_local_addrs(state->context))
+	put_opt6(state->fallback, IN6ADDRSZ);
+      end_opt6(o); 
+    }
+
+  if (state->context && !done_refresh)
+    {
+      struct dhcp_context *c;
+      unsigned int lease_time = 0xffffffff;
+      
+      /* Find the smallest lease tie of all contexts,
+	 subject to the RFC-4242 stipulation that this must not 
+	 be less than 600. */
+      for (c = state->context; c; c = c->next)
+	if (c->lease_time < lease_time)
+	  {
+	    if (c->lease_time < 600)
+	      lease_time = 600;
+	    else
+	      lease_time = c->lease_time;
+	  }
+
+      o = new_opt6(OPTION6_REFRESH_TIME);
+      put_opt6_long(lease_time);
+      end_opt6(o); 
+    }
+   
+    /* handle vendor-identifying vendor-encapsulated options,
+       dhcp-option = vi-encap:13,17,....... */
+  for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+    opt_cfg->flags &= ~DHOPT_ENCAP_DONE;
+    
+  if (oro)
+    for (i = 0; i <  opt6_len(oro) - 1; i += 2)
+      if (opt6_uint(oro, i, 2) == OPTION6_VENDOR_OPTS)
+	do_encap = 1;
+  
+  for (opt_cfg = daemon->dhcp_opts6; opt_cfg; opt_cfg = opt_cfg->next)
+    { 
+      if (opt_cfg->flags & DHOPT_RFC3925)
+	{
+	  int found = 0;
+	  struct dhcp_opt *oc;
+	  
+	  if (opt_cfg->flags & DHOPT_ENCAP_DONE)
+	    continue;
+	  
+	  for (oc = daemon->dhcp_opts6; oc; oc = oc->next)
+	    {
+	      oc->flags &= ~DHOPT_ENCAP_MATCH;
+	      
+	      if (!(oc->flags & DHOPT_RFC3925) || opt_cfg->u.encap != oc->u.encap)
+		continue;
+	      
+	      oc->flags |= DHOPT_ENCAP_DONE;
+	      if (match_netid(oc->netid, tagif, 1))
+		{
+		  /* option requested/forced? */
+		  if (!oro || do_encap || (oc->flags & DHOPT_FORCE))
+		    {
+		      oc->flags |= DHOPT_ENCAP_MATCH;
+		      found = 1;
+		    }
+		} 
+	    }
+	  
+	  if (found)
+	    { 
+	      o = new_opt6(OPTION6_VENDOR_OPTS);	      
+	      put_opt6_long(opt_cfg->u.encap);	
+	     
+	      for (oc = daemon->dhcp_opts6; oc; oc = oc->next)
+		if (oc->flags & DHOPT_ENCAP_MATCH)
+		  {
+		    o1 = new_opt6(oc->opt);
+		    put_opt6(oc->val, oc->len);
+		    end_opt6(o1);
+		  }
+	      end_opt6(o);
+	    }
+	}
+    }      
+
+
+  if (state->hostname)
+    {
+      unsigned char *p;
+      size_t len = strlen(state->hostname);
+      
+      if (state->send_domain)
+	len += strlen(state->send_domain) + 2;
+
+      o = new_opt6(OPTION6_FQDN);
+      if ((p = expand(len + 2)))
+	{
+	  *(p++) = state->fqdn_flags;
+	  p = do_rfc1035_name(p, state->hostname, NULL);
+	  if (state->send_domain)
+	    {
+	      p = do_rfc1035_name(p, state->send_domain, NULL);
+	      *p = 0;
+	    }
+	}
+      end_opt6(o);
+    }
+
+
+  /* logging */
+  if (option_bool(OPT_LOG_OPTS) && oro)
+    {
+      char *q = daemon->namebuff;
+      for (i = 0; i <  opt6_len(oro) - 1; i += 2)
+	{
+	  char *s = option_string(AF_INET6, opt6_uint(oro, i, 2), NULL, 0, NULL, 0);
+	  q += snprintf(q, MAXDNAME - (q - daemon->namebuff),
+			"%d%s%s%s", 
+			opt6_uint(oro, i, 2),
+			strlen(s) != 0 ? ":" : "",
+			s, 
+			(i > opt6_len(oro) - 3) ? "" : ", ");
+	  if ( i >  opt6_len(oro) - 3 || (q - daemon->namebuff) > 40)
+	    {
+	      q = daemon->namebuff;
+	      my_syslog(MS_DHCP | LOG_INFO, _("%u requested options: %s"), state->xid, daemon->namebuff);
+	    }
+	}
+    } 
+
+  return tagif;
+}
+ 
+static int add_local_addrs(struct dhcp_context *context)
+{
+  int done = 0;
+  
+  for (; context; context = context->current)
+    if ((context->flags & CONTEXT_USED) && !IN6_IS_ADDR_UNSPECIFIED(&context->local6))
+      {
+	/* squash duplicates */
+	struct dhcp_context *c;
+	for (c = context->current; c; c = c->current)
+	  if ((c->flags & CONTEXT_USED) &&
+	      IN6_ARE_ADDR_EQUAL(&context->local6, &c->local6))
+	    break;
+	
+	if (!c)
+	  { 
+	    done = 1;
+	    put_opt6(&context->local6, IN6ADDRSZ);
+	  }
+      }
+
+  return done;
+}
+
+
+static void get_context_tag(struct state *state, struct dhcp_context *context)
+{
+  /* get tags from context if we've not used it before */
+  if (context->netid.next == &context->netid && context->netid.net)
+    {
+      context->netid.next = state->context_tags;
+      state->context_tags = &context->netid;
+      if (!state->hostname_auth)
+	{
+	  struct dhcp_netid_list *id_list;
+	  
+	  for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+	    if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0))
+	      break;
+	  if (id_list)
+	    state->hostname = NULL;
+	}
+    }
+} 
+
+static int check_ia(struct state *state, void *opt, void **endp, void **ia_option)
+{
+  state->ia_type = opt6_type(opt);
+  *ia_option = NULL;
+
+  if (state->ia_type != OPTION6_IA_NA && state->ia_type != OPTION6_IA_TA)
+    return 0;
+  
+  if (state->ia_type == OPTION6_IA_NA && opt6_len(opt) < 12)
+    return 0;
+	    
+  if (state->ia_type == OPTION6_IA_TA && opt6_len(opt) < 4)
+    return 0;
+  
+  *endp = opt6_ptr(opt, opt6_len(opt));
+  state->iaid = opt6_uint(opt, 0, 4);
+  *ia_option = opt6_find(opt6_ptr(opt, state->ia_type == OPTION6_IA_NA ? 12 : 4), *endp, OPTION6_IAADDR, 24);
+
+  return 1;
+}
+
+
+static int build_ia(struct state *state, int *t1cntr)
+{
+  int  o = new_opt6(state->ia_type);
+ 
+  put_opt6_long(state->iaid);
+  *t1cntr = 0;
+	    
+  if (state->ia_type == OPTION6_IA_NA)
+    {
+      /* save pointer */
+      *t1cntr = save_counter(-1);
+      /* so we can fill these in later */
+      put_opt6_long(0);
+      put_opt6_long(0); 
+    }
+
+  return o;
+}
+
+static void end_ia(int t1cntr, unsigned int min_time, int do_fuzz)
+{
+  if (t1cntr != 0)
+    {
+      /* go back and fill in fields in IA_NA option */
+      int sav = save_counter(t1cntr);
+      unsigned int t1, t2, fuzz = 0;
+
+      if (do_fuzz)
+	{
+	  fuzz = rand16();
+      
+	  while (fuzz > (min_time/16))
+	    fuzz = fuzz/2;
+	}
+      
+      t1 = (min_time == 0xffffffff) ? 0xffffffff : min_time/2 - fuzz;
+      t2 = (min_time == 0xffffffff) ? 0xffffffff : ((min_time/8)*7) - fuzz;
+      put_opt6_long(t1);
+      put_opt6_long(t2);
+      save_counter(sav);
+    }	
+}
+
+static void add_address(struct state *state, struct dhcp_context *context, unsigned int lease_time, void *ia_option, 
+			unsigned int *min_time, struct in6_addr *addr, time_t now)
+{
+  unsigned int valid_time = 0, preferred_time = 0;
+  int o = new_opt6(OPTION6_IAADDR);
+  struct dhcp_lease *lease;
+
+  /* get client requested times */
+  if (ia_option)
+    {
+      preferred_time =  opt6_uint(ia_option, 16, 4);
+      valid_time =  opt6_uint(ia_option, 20, 4);
+    }
+
+  calculate_times(context, min_time, &valid_time, &preferred_time, lease_time); 
+  
+  put_opt6(addr, sizeof(*addr));
+  put_opt6_long(preferred_time);
+  put_opt6_long(valid_time); 		    
+  end_opt6(o);
+  
+  if (state->lease_allocate)
+    update_leases(state, context, addr, valid_time, now);
+
+  if ((lease = lease6_find_by_addr(addr, 128, 0)))
+    lease->flags |= LEASE_USED;
+
+  /* get tags from context if we've not used it before */
+  if (context->netid.next == &context->netid && context->netid.net)
+    {
+      context->netid.next = state->context_tags;
+      state->context_tags = &context->netid;
+      
+      if (!state->hostname_auth)
+	{
+	  struct dhcp_netid_list *id_list;
+	  
+	  for (id_list = daemon->dhcp_ignore_names; id_list; id_list = id_list->next)
+	    if ((!id_list->list) || match_netid(id_list->list, &context->netid, 0))
+	      break;
+	  if (id_list)
+	    state->hostname = NULL;
+	}
+    }
+
+  log6_quiet(state, state->lease_allocate ? "DHCPREPLY" : "DHCPADVERTISE", addr, state->hostname);
+
+}
+
+static void mark_context_used(struct state *state, struct in6_addr *addr)
+{
+  struct dhcp_context *context;
+
+  /* Mark that we have an address for this prefix. */
+  for (context = state->context; context; context = context->current)
+    if (is_same_net6(addr, &context->start6, context->prefix))
+      context->flags |= CONTEXT_USED;
+}
+
+static void mark_config_used(struct dhcp_context *context, struct in6_addr *addr)
+{
+  for (; context; context = context->current)
+    if (is_same_net6(addr, &context->start6, context->prefix))
+      context->flags |= CONTEXT_CONF_USED;
+}
+
+/* make sure address not leased to another CLID/IAID */
+static int check_address(struct state *state, struct in6_addr *addr)
+{ 
+  struct dhcp_lease *lease;
+
+  if (!(lease = lease6_find_by_addr(addr, 128, 0)))
+    return 1;
+
+  if (lease->clid_len != state->clid_len || 
+      memcmp(lease->clid, state->clid, state->clid_len) != 0 ||
+      lease->iaid != state->iaid)
+    return 0;
+
+  return 1;
+}
+
+
+/* return true of *addr could have been generated from config. */
+static struct addrlist *config_implies(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr)
+{
+  int prefix;
+  struct in6_addr wild_addr;
+  struct addrlist *addr_list;
+  
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return NULL;
+  
+  for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+    {
+      prefix = (addr_list->flags & ADDRLIST_PREFIX) ? addr_list->prefixlen : 128;
+      wild_addr = addr_list->addr.addr6;
+      
+      if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64)
+	{
+	  wild_addr = context->start6;
+	  setaddr6part(&wild_addr, addr6part(&addr_list->addr.addr6));
+	}
+      else if (!is_same_net6(&context->start6, addr, context->prefix))
+	continue;
+      
+      if (is_same_net6(&wild_addr, addr, prefix))
+	return addr_list;
+    }
+  
+  return NULL;
+}
+
+static int config_valid(struct dhcp_config *config, struct dhcp_context *context, struct in6_addr *addr, struct state *state, time_t now)
+{
+  u64 addrpart, i, addresses;
+  struct addrlist *addr_list;
+  
+  if (!config || !(config->flags & CONFIG_ADDR6))
+    return 0;
+
+  for (addr_list = config->addr6; addr_list; addr_list = addr_list->next)
+    if (!(addr_list->flags & ADDRLIST_DECLINED) ||
+	difftime(now, addr_list->decline_time) >= (float)DECLINE_BACKOFF)
+      {
+	addrpart = addr6part(&addr_list->addr.addr6);
+	addresses = 1;
+	
+	if (addr_list->flags & ADDRLIST_PREFIX)
+	  addresses = (u64)1<<(128-addr_list->prefixlen);
+	
+	if ((addr_list->flags & ADDRLIST_WILDCARD))
+	  {
+	    if (context->prefix != 64)
+	      continue;
+	    
+	    *addr = context->start6;
+	  }
+	else if (is_same_net6(&context->start6, &addr_list->addr.addr6, context->prefix))
+	  *addr = addr_list->addr.addr6;
+	else
+	  continue;
+	
+	for (i = 0 ; i < addresses; i++)
+	  {
+	    setaddr6part(addr, addrpart+i);
+	    
+	    if (check_address(state, addr))
+	      return 1;
+	  }
+      }
+  
+  return 0;
+}
+
+/* Calculate valid and preferred times to send in leases/renewals. 
+
+   Inputs are:
+
+   *valid_timep, *preferred_timep - requested times from IAADDR options.
+   context->valid, context->preferred - times associated with subnet address on local interface.
+   context->flags | CONTEXT_DEPRECATE - "deprecated" flag in dhcp-range.
+   lease_time - configured time for context for individual client.
+   *min_time - smallest valid time sent so far.
+
+   Outputs are :
+   
+   *valid_timep, *preferred_timep - times to be send in IAADDR option.
+   *min_time - smallest valid time sent so far, to calculate T1 and T2.
+   
+   */
+static void calculate_times(struct dhcp_context *context, unsigned int *min_time, unsigned int *valid_timep, 
+			    unsigned int *preferred_timep, unsigned int lease_time)
+{
+  unsigned int req_preferred = *preferred_timep, req_valid = *valid_timep;
+  unsigned int valid_time = lease_time, preferred_time = lease_time;
+  
+  /* RFC 3315: "A server ignores the lifetimes set
+     by the client if the preferred lifetime is greater than the valid
+     lifetime. */
+  if (req_preferred <= req_valid)
+    {
+      if (req_preferred != 0)
+	{
+	  /* 0 == "no preference from client" */
+	  if (req_preferred < 120u)
+	    req_preferred = 120u; /* sanity */
+	  
+	  if (req_preferred < preferred_time)
+	    preferred_time = req_preferred;
+	}
+      
+      if (req_valid != 0)
+	/* 0 == "no preference from client" */
+	{
+	  if (req_valid < 120u)
+	    req_valid = 120u; /* sanity */
+	  
+	  if (req_valid < valid_time)
+	    valid_time = req_valid;
+	}
+    }
+
+  /* deprecate (preferred == 0) which configured, or when local address 
+     is deprecated */
+  if ((context->flags & CONTEXT_DEPRECATE) || context->preferred == 0)
+    preferred_time = 0;
+  
+  if (preferred_time != 0 && preferred_time < *min_time)
+    *min_time = preferred_time;
+  
+  if (valid_time != 0 && valid_time < *min_time)
+    *min_time = valid_time;
+  
+  *valid_timep = valid_time;
+  *preferred_timep = preferred_time;
+}
+
+static void update_leases(struct state *state, struct dhcp_context *context, struct in6_addr *addr, unsigned int lease_time, time_t now)
+{
+  struct dhcp_lease *lease = lease6_find_by_addr(addr, 128, 0);
+#ifdef HAVE_SCRIPT
+  struct dhcp_netid *tagif = run_tag_if(state->tags);
+#endif
+
+  (void)context;
+
+  if (!lease)
+    lease = lease6_allocate(addr, state->ia_type == OPTION6_IA_NA ? LEASE_NA : LEASE_TA);
+  
+  if (lease)
+    {
+      lease_set_expires(lease, lease_time, now);
+      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)
+	{
+	  char *addr_domain = get_domain6(addr);
+	  if (!state->send_domain)
+	    state->send_domain = addr_domain;
+	  lease_set_hostname(lease, state->hostname, state->hostname_auth, addr_domain, state->domain);
+	}
+      
+#ifdef HAVE_SCRIPT
+      if (daemon->lease_change_command)
+	{
+	  void *class_opt;
+	  lease->flags |= LEASE_CHANGED;
+	  free(lease->extradata);
+	  lease->extradata = NULL;
+	  lease->extradata_size = lease->extradata_len = 0;
+	  lease->vendorclass_count = 0; 
+	  
+	  if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_VENDOR_CLASS, 4)))
+	    {
+	      void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt));
+	      lease->vendorclass_count++;
+	      /* send enterprise number first  */
+	      sprintf(daemon->dhcp_buff2, "%u", opt6_uint(class_opt, 0, 4));
+	      lease_add_extradata(lease, (unsigned char *)daemon->dhcp_buff2, strlen(daemon->dhcp_buff2), 0);
+	      
+	      if (opt6_len(class_opt) >= 6) 
+		for (enc_opt = opt6_ptr(class_opt, 4); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
+		  {
+		    lease->vendorclass_count++;
+		    lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
+		  }
+	    }
+	  
+	  lease_add_extradata(lease, (unsigned char *)state->client_hostname, 
+			      state->client_hostname ? strlen(state->client_hostname) : 0, 0);				
+	  
+	  /* space-concat tag set */
+	  if (!tagif && !context->netid.net)
+	    lease_add_extradata(lease, NULL, 0, 0);
+	  else
+	    {
+	      if (context->netid.net)
+		lease_add_extradata(lease, (unsigned char *)context->netid.net, strlen(context->netid.net), tagif ? ' ' : 0);
+	      
+	      if (tagif)
+		{
+		  struct dhcp_netid *n;
+		  for (n = tagif; n; n = n->next)
+		    {
+		      struct dhcp_netid *n1;
+		      /* kill dupes */
+		      for (n1 = n->next; n1; n1 = n1->next)
+			if (strcmp(n->net, n1->net) == 0)
+			  break;
+		      if (!n1)
+			lease_add_extradata(lease, (unsigned char *)n->net, strlen(n->net), n->next ? ' ' : 0); 
+		    }
+		}
+	    }
+	  
+	  if (state->link_address)
+	    inet_ntop(AF_INET6, state->link_address, daemon->addrbuff, ADDRSTRLEN);
+	  
+	  lease_add_extradata(lease, (unsigned char *)daemon->addrbuff, state->link_address ? strlen(daemon->addrbuff) : 0, 0);
+	  
+	  if ((class_opt = opt6_find(state->packet_options, state->end, OPTION6_USER_CLASS, 2)))
+	    {
+	      void *enc_opt, *enc_end = opt6_ptr(class_opt, opt6_len(class_opt));
+	      for (enc_opt = opt6_ptr(class_opt, 0); enc_opt; enc_opt = opt6_next(enc_opt, enc_end))
+		lease_add_extradata(lease, opt6_ptr(enc_opt, 0), opt6_len(enc_opt), 0);
+	    }
+	}
+#endif	
+      
+    }
+}
+			  
+			
+	
+static void log6_opts(int nest, unsigned int xid, void *start_opts, void *end_opts)
+{
+  void *opt;
+  char *desc = nest ? "nest" : "sent";
+  
+  if (!option_bool(OPT_LOG_OPTS) || start_opts == end_opts)
+    return;
+  
+  for (opt = start_opts; opt; opt = opt6_next(opt, end_opts))
+    {
+      int type = opt6_type(opt);
+      void *ia_options = NULL;
+      char *optname;
+      
+      if (type == OPTION6_IA_NA)
+	{
+	  sprintf(daemon->namebuff, "IAID=%u T1=%u T2=%u",
+		  opt6_uint(opt, 0, 4), opt6_uint(opt, 4, 4), opt6_uint(opt, 8, 4));
+	  optname = "ia-na";
+	  ia_options = opt6_ptr(opt, 12);
+	}
+      else if (type == OPTION6_IA_TA)
+	{
+	  sprintf(daemon->namebuff, "IAID=%u", opt6_uint(opt, 0, 4));
+	  optname = "ia-ta";
+	  ia_options = opt6_ptr(opt, 4);
+	}
+      else if (type == OPTION6_IAADDR)
+	{
+	  struct in6_addr addr;
+
+	  /* align */
+	  memcpy(&addr, opt6_ptr(opt, 0), IN6ADDRSZ);
+	  inet_ntop(AF_INET6, &addr, daemon->addrbuff, ADDRSTRLEN);
+	  sprintf(daemon->namebuff, "%s PL=%u VL=%u", 
+		  daemon->addrbuff, opt6_uint(opt, 16, 4), opt6_uint(opt, 20, 4));
+	  optname = "iaaddr";
+	  ia_options = opt6_ptr(opt, 24);
+	}
+      else if (type == OPTION6_STATUS_CODE)
+	{
+	  int len = sprintf(daemon->namebuff, "%u ", opt6_uint(opt, 0, 2));
+	  memcpy(daemon->namebuff + len, opt6_ptr(opt, 2), opt6_len(opt)-2);
+	  daemon->namebuff[len + opt6_len(opt) - 2] = 0;
+	  optname = "status";
+	}
+      else
+	{
+	  /* account for flag byte on FQDN */
+	  int offset = type == OPTION6_FQDN ? 1 : 0;
+	  optname = option_string(AF_INET6, type, opt6_ptr(opt, offset), opt6_len(opt) - offset, daemon->namebuff, MAXDNAME);
+	}
+      
+      my_syslog(MS_DHCP | LOG_INFO, "%u %s size:%3d option:%3d %s  %s", 
+		xid, desc, opt6_len(opt), type, optname, daemon->namebuff);
+      
+      if (ia_options)
+	log6_opts(1, xid, ia_options, opt6_ptr(opt, opt6_len(opt)));
+    }
+}		 
+ 
+static void log6_quiet(struct state *state, char *type, struct in6_addr *addr, char *string)
+{
+  if (option_bool(OPT_LOG_OPTS) || !option_bool(OPT_QUIET_DHCP6))
+    log6_packet(state, type, addr, string);
+}
+
+static void log6_packet(struct state *state, char *type, struct in6_addr *addr, char *string)
+{
+  int clid_len = state->clid_len;
+
+  /* avoid buffer overflow */
+  if (clid_len > 100)
+    clid_len = 100;
+  
+  print_mac(daemon->namebuff, state->clid, clid_len);
+
+  if (addr)
+    {
+      inet_ntop(AF_INET6, addr, daemon->dhcp_buff2, DHCP_BUFF_SZ - 1);
+      strcat(daemon->dhcp_buff2, " ");
+    }
+  else
+    daemon->dhcp_buff2[0] = 0;
+
+  if(option_bool(OPT_LOG_OPTS))
+    my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s %s",
+	      state->xid, 
+	      type,
+	      state->iface_name, 
+	      daemon->dhcp_buff2,
+	      daemon->namebuff,
+	      string ? string : "");
+  else
+    my_syslog(MS_DHCP | LOG_INFO, "%s(%s) %s%s %s",
+	      type,
+	      state->iface_name, 
+	      daemon->dhcp_buff2,
+	      daemon->namebuff,
+	      string ? string : "");
+}
+
+static void *opt6_find (void *opts, void *end, unsigned int search, unsigned int minsize)
+{
+  u16 opt, opt_len;
+  void *start;
+  
+  if (!opts)
+    return NULL;
+    
+  while (1)
+    {
+      if (end - opts < 4) 
+	return NULL;
+      
+      start = opts;
+      GETSHORT(opt, opts);
+      GETSHORT(opt_len, opts);
+      
+      if (opt_len > (end - opts))
+	return NULL;
+      
+      if (opt == search && (opt_len >= minsize))
+	return start;
+      
+      opts += opt_len;
+    }
+}
+
+static void *opt6_next(void *opts, void *end)
+{
+  u16 opt_len;
+  
+  if (end - opts < 4) 
+    return NULL;
+  
+  opts += 2;
+  GETSHORT(opt_len, opts);
+  
+  if (opt_len >= (end - opts))
+    return NULL;
+  
+  return opts + opt_len;
+}
+
+static unsigned int opt6_uint(unsigned char *opt, int offset, int size)
+{
+  /* this worries about unaligned data and byte order */
+  unsigned int ret = 0;
+  int i;
+  unsigned char *p = opt6_ptr(opt, offset);
+  
+  for (i = 0; i < size; i++)
+    ret = (ret << 8) | *p++;
+  
+  return ret;
+} 
+
+void relay_upstream6(struct dhcp_relay *relay, ssize_t sz, 
+		     struct in6_addr *peer_address, u32 scope_id, time_t now)
+{
+  /* ->local is same value for all relays on ->current chain */
+  
+  union all_addr from;
+  unsigned char *header;
+  unsigned char *inbuff = daemon->dhcp_packet.iov_base;
+  int msg_type = *inbuff;
+  int hopcount;
+  struct in6_addr multicast;
+  unsigned int maclen, mactype;
+  unsigned char mac[DHCP_CHADDR_MAX];
+
+  inet_pton(AF_INET6, ALL_SERVERS, &multicast);
+  get_client_mac(peer_address, scope_id, mac, &maclen, &mactype, now);
+
+  /* source address == relay address */
+  from.addr6 = relay->local.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;
+
+  reset_counter();
+
+  if ((header = put_opt6(NULL, 34)))
+    {
+      int o;
+
+      header[0] = DHCP6RELAYFORW;
+      header[1] = hopcount;
+      memcpy(&header[2],  &relay->local.addr6, IN6ADDRSZ);
+      memcpy(&header[18], peer_address, IN6ADDRSZ);
+ 
+      /* RFC-6939 */
+      if (maclen != 0)
+	{
+	  o = new_opt6(OPTION6_CLIENT_MAC);
+	  put_opt6_short(mactype);
+	  put_opt6(mac, maclen);
+	  end_opt6(o);
+	}
+      
+      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.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.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(-1), &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.addr6) &&
+	(!relay->interface || wildcard_match(relay->interface, arrival_interface)))
+      break;
+      
+  reset_counter();
+
+  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
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c b/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c
new file mode 100755
index 0000000..58c6d8f
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/rrfilter.c
@@ -0,0 +1,339 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* Code to safely remove RRs from a DNS answer */ 
+
+#include "dnsmasq.h"
+
+/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+   we've chopped out of the packet, or check they don't point into an elided part.  */
+static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  unsigned char *ansp = *namep;
+
+  while(1)
+    {
+      unsigned int label_type;
+      
+      if (!CHECK_LEN(header, ansp, plen, 1))
+	return 0;
+      
+      label_type = (*ansp) & 0xc0;
+
+      if (label_type == 0xc0)
+	{
+	  /* pointer for compression. */
+	  unsigned int offset;
+	  int i;
+	  unsigned char *p;
+	  
+	  if (!CHECK_LEN(header, ansp, plen, 2))
+	    return 0;
+
+	  offset = ((*ansp++) & 0x3f) << 8;
+	  offset |= *ansp++;
+
+	  p = offset + (unsigned char *)header;
+	  
+	  for (i = 0; i < rr_count; i++)
+	    if (p < rrs[i])
+	      break;
+	    else
+	      if (i & 1)
+		offset -= rrs[i] - rrs[i-1];
+
+	  /* does the pointer end up in an elided RR? */
+	  if (i & 1)
+	    return 0;
+
+	  /* No, scale the pointer */
+	  if (fixup)
+	    {
+	      ansp -= 2;
+	      *ansp++ = (offset >> 8) | 0xc0;
+	      *ansp++ = offset & 0xff;
+	    }
+	  break;
+	}
+      else if (label_type == 0x80)
+	return 0; /* reserved */
+      else if (label_type == 0x40)
+	{
+	  /* Extended label type */
+	  unsigned int count;
+	  
+	  if (!CHECK_LEN(header, ansp, plen, 2))
+	    return 0;
+	  
+	  if (((*ansp++) & 0x3f) != 1)
+	    return 0; /* we only understand bitstrings */
+	  
+	  count = *(ansp++); /* Bits in bitstring */
+	  
+	  if (count == 0) /* count == 0 means 256 bits */
+	    ansp += 32;
+	  else
+	    ansp += ((count-1)>>3)+1;
+	}
+      else
+	{ /* label type == 0 Bottom six bits is length */
+	  unsigned int len = (*ansp++) & 0x3f;
+	  
+	  if (!ADD_RDLEN(header, ansp, plen, len))
+	    return 0;
+
+	  if (len == 0)
+	    break; /* zero length label marks the end. */
+	}
+    }
+
+  *namep = ansp;
+
+  return 1;
+}
+
+/* Go through RRs and check or fixup the domain names contained within */
+static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+  int i, j, type, class, rdlen;
+  unsigned char *pp;
+  
+  for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
+    {
+      pp = p;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+	return 0;
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+
+      /* If this RR is to be elided, don't fix up its contents */
+      for (j = 0; j < rr_count; j += 2)
+	if (rrs[j] == pp)
+	  break;
+
+      if (j >= rr_count)
+	{
+	  /* fixup name of RR */
+	  if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+	    return 0;
+	  
+	  if (class == C_IN)
+	    {
+	      u16 *d;
+ 
+	      for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
+		{
+		  if (*d != 0)
+		    pp += *d;
+		  else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+		    return 0;
+		}
+	    }
+	}
+      
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	return 0;
+    }
+  
+  return 1;
+}
+	
+
+/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */
+size_t rrfilter(struct dns_header *header, size_t plen, int mode)
+{
+  static unsigned char **rrs;
+  static int rr_sz = 0;
+
+  unsigned char *p = (unsigned char *)(header+1);
+  int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
+
+  if (ntohs(header->qdcount) != 1 ||
+      !(p = skip_name(p, header, plen, 4)))
+    return plen;
+  
+  GETSHORT(qtype, p);
+  GETSHORT(qclass, p);
+
+  /* First pass, find pointers to start and end of all the records we wish to elide:
+     records added for DNSSEC, unless explicitly queried for */
+  for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0; 
+       i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
+       i++)
+    {
+      unsigned char *pstart = p;
+      int type, class;
+
+      if (!(p = skip_name(p, header, plen, 10)))
+	return plen;
+      
+      GETSHORT(type, p); 
+      GETSHORT(class, p);
+      p += 4; /* TTL */
+      GETSHORT(rdlen, p);
+        
+      if (!ADD_RDLEN(header, p, plen, rdlen))
+	return plen;
+
+      /* Don't remove the answer. */
+      if (i < ntohs(header->ancount) && type == qtype && class == qclass)
+	continue;
+      
+      if (mode == 0) /* EDNS */
+	{
+	  /* EDNS mode, remove T_OPT from additional section only */
+	  if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
+	    continue;
+	}
+      else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+	/* DNSSEC mode, remove SIGs and NSECs from all three sections. */
+	continue;
+      
+      
+      if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+	return plen; 
+      
+      rrs[rr_found++] = pstart;
+      rrs[rr_found++] = p;
+      
+      if (i < ntohs(header->ancount))
+	chop_an++;
+      else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
+	chop_ns++;
+      else
+	chop_ar++;
+    }
+  
+  /* Nothing to do. */
+  if (rr_found == 0)
+    return plen;
+
+  /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+     point to records we're going to elide. This is theoretically possible, but unlikely. If
+     it happens, we give up and leave the answer unchanged. */
+  p = (unsigned char *)(header+1);
+  
+  /* question first */
+  if (!check_name(&p, header, plen, 0, rrs, rr_found))
+    return plen;
+  p += 4; /* qclass, qtype */
+  
+  /* Now answers and NS */
+  if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+    return plen;
+  
+  /* Third pass, actually fix up pointers in the records */
+  p = (unsigned char *)(header+1);
+  
+  check_name(&p, header, plen, 1, rrs, rr_found);
+  p += 4; /* qclass, qtype */
+  
+  check_rrs(p, header, plen, 1, rrs, rr_found);
+
+  /* Fourth pass, elide records */
+  for (p = rrs[0], i = 1; i < rr_found; i += 2)
+    {
+      unsigned char *start = rrs[i];
+      unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)header) + plen;
+      
+      memmove(p, start, end-start);
+      p += end-start;
+    }
+     
+  plen = p - (unsigned char *)header;
+  header->ancount = htons(ntohs(header->ancount) - chop_an);
+  header->nscount = htons(ntohs(header->nscount) - chop_ns);
+  header->arcount = htons(ntohs(header->arcount) - chop_ar);
+
+  return plen;
+}
+
+/* This is used in the DNSSEC code too, hence it's exported */
+u16 *rrfilter_desc(int type)
+{
+  /* List of RRtypes which include domains in the data.
+     0 -> domain
+     integer -> no. of plain bytes
+     -1 -> end
+
+     zero is not a valid RRtype, so the final entry is returned for
+     anything which needs no mangling.
+  */
+  
+  static u16 rr_desc[] = 
+    { 
+      T_NS, 0, -1, 
+      T_MD, 0, -1,
+      T_MF, 0, -1,
+      T_CNAME, 0, -1,
+      T_SOA, 0, 0, -1,
+      T_MB, 0, -1,
+      T_MG, 0, -1,
+      T_MR, 0, -1,
+      T_PTR, 0, -1,
+      T_MINFO, 0, 0, -1,
+      T_MX, 2, 0, -1,
+      T_RP, 0, 0, -1,
+      T_AFSDB, 2, 0, -1,
+      T_RT, 2, 0, -1,
+      T_SIG, 18, 0, -1,
+      T_PX, 2, 0, 0, -1,
+      T_NXT, 0, -1,
+      T_KX, 2, 0, -1,
+      T_SRV, 6, 0, -1,
+      T_DNAME, 0, -1,
+      0, -1 /* wildcard/catchall */
+    }; 
+  
+  u16 *p = rr_desc;
+  
+  while (*p != type && *p != 0)
+    while (*p++ != (u16)-1);
+
+  return p+1;
+}
+
+int expand_workspace(unsigned char ***wkspc, int *szp, int new)
+{
+  unsigned char **p;
+  int old = *szp;
+
+  if (old >= new+1)
+    return 1;
+
+  if (new >= 100)
+    return 0;
+
+  new += 5;
+  
+  if (!(p = whine_malloc(new * sizeof(unsigned char *))))
+    return 0;  
+  
+  if (old != 0 && *wkspc)
+    {
+      memcpy(p, *wkspc, old * sizeof(unsigned char *));
+      free(*wkspc);
+    }
+  
+  *wkspc = p;
+  *szp = new;
+
+  return 1;
+}
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c b/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c
new file mode 100755
index 0000000..9b10063
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/slaac.c
@@ -0,0 +1,213 @@
+/* dnsmasq is Copyright (c) 2000-2021 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>
+
+static int ping_id = 0;
+
+void slaac_add_addrs(struct dhcp_lease *lease, time_t now, int force)
+{
+  struct slaac_address *slaac, *old, **up;
+  struct dhcp_context *context;
+  int dns_dirty = 0;
+  
+  if (!(lease->flags & LEASE_HAVE_HWADDR) || 
+      (lease->flags & (LEASE_TA | LEASE_NA)) ||
+      lease->last_interface == 0 ||
+      !lease->hostname)
+    return ;
+  
+  old = lease->slaac_address;
+  lease->slaac_address = NULL;
+
+  for (context = daemon->dhcp6; context; context = context->next) 
+    if ((context->flags & CONTEXT_RA_NAME) && 
+	!(context->flags & CONTEXT_OLD) &&
+	lease->last_interface == context->if_index)
+      {
+	struct in6_addr addr = context->start6;
+	if (lease->hwaddr_len == 6 &&
+	    (lease->hwaddr_type == ARPHRD_ETHER || lease->hwaddr_type == ARPHRD_IEEE802))
+	  {
+	    /* convert MAC address to EUI-64 */
+	    memcpy(&addr.s6_addr[8], lease->hwaddr, 3);
+	    memcpy(&addr.s6_addr[13], &lease->hwaddr[3], 3);
+	    addr.s6_addr[11] = 0xff;
+	    addr.s6_addr[12] = 0xfe;
+	  }
+#if defined(ARPHRD_EUI64)
+	else if (lease->hwaddr_len == 8 &&
+		 lease->hwaddr_type == ARPHRD_EUI64)
+	  memcpy(&addr.s6_addr[8], lease->hwaddr, 8);
+#endif
+#if defined(ARPHRD_IEEE1394) && defined(ARPHRD_EUI64)
+	else if (lease->clid_len == 9 && 
+		 lease->clid[0] ==  ARPHRD_EUI64 &&
+		 lease->hwaddr_type == ARPHRD_IEEE1394)
+	  /* firewire has EUI-64 identifier as clid */
+	  memcpy(&addr.s6_addr[8], &lease->clid[1], 8);
+#endif
+	else
+	  continue;
+	
+	addr.s6_addr[8] ^= 0x02;
+	
+	/* check if we already have this one */
+	for (up = &old, slaac = old; slaac; slaac = slaac->next)
+	  {
+	    if (IN6_ARE_ADDR_EQUAL(&addr, &slaac->addr))
+	      {
+		*up = slaac->next;
+		/* recheck when DHCPv4 goes through init-reboot */
+		if (force)
+		  {
+		    slaac->ping_time = now;
+		    slaac->backoff = 1;
+		    dns_dirty = 1;
+		  }
+		break;
+	      }
+	    up = &slaac->next;
+	  }
+	    
+	/* No, make new one */
+	if (!slaac && (slaac = whine_malloc(sizeof(struct slaac_address))))
+	  {
+	    slaac->ping_time = now;
+	    slaac->backoff = 1;
+	    slaac->addr = addr;
+	    /* Do RA's to prod it */
+	    ra_start_unsolicited(now, context);
+	  }
+	
+	if (slaac)
+	  {
+	    slaac->next = lease->slaac_address;
+	    lease->slaac_address = slaac;
+	  }
+      }
+  
+  if (old || dns_dirty)
+    lease_update_dns(1);
+  
+  /* Free any no reused */
+  for (; old; old = slaac)
+    {
+      slaac = old->next;
+      free(old);
+    }
+}
+
+
+time_t periodic_slaac(time_t now, struct dhcp_lease *leases)
+{
+  struct dhcp_context *context;
+  struct dhcp_lease *lease;
+  struct slaac_address *slaac;
+  time_t next_event = 0;
+  
+  for (context = daemon->dhcp6; context; context = context->next)
+    if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD))
+      break;
+
+  /* nothing configured */
+  if (!context)
+    return 0;
+
+  while (ping_id == 0)
+    ping_id = rand16();
+
+  for (lease = leases; lease; lease = lease->next)
+    for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+      {
+	/* confirmed or given up? */
+	if (slaac->backoff == 0 || slaac->ping_time == 0)
+	  continue;
+	
+	if (difftime(slaac->ping_time, now) <= 0.0)
+	  {
+	    struct ping_packet *ping;
+	    struct sockaddr_in6 addr;
+ 
+	    reset_counter();
+
+	    if (!(ping = expand(sizeof(struct ping_packet))))
+	      continue;
+
+	    ping->type = ICMP6_ECHO_REQUEST;
+	    ping->code = 0;
+	    ping->identifier = ping_id;
+	    ping->sequence_no = slaac->backoff;
+	    
+	    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 = slaac->addr;
+	    
+	    if (sendto(daemon->icmp6fd, daemon->outpacket.iov_base, save_counter(-1), 0,
+		       (struct sockaddr *)&addr,  sizeof(addr)) == -1 &&
+		errno == EHOSTUNREACH &&
+		slaac->backoff == 12)
+	      slaac->ping_time = 0; /* Give up */ 
+	    else
+	      {
+		slaac->ping_time += (1 << (slaac->backoff - 1)) + (rand16()/21785); /* 0 - 3 */
+		if (slaac->backoff > 4)
+		  slaac->ping_time += rand16()/4000; /* 0 - 15 */
+		if (slaac->backoff < 12)
+		  slaac->backoff++;
+	      }
+	  }
+	
+	if (slaac->ping_time != 0 &&
+	    (next_event == 0 || difftime(next_event, slaac->ping_time) >= 0.0))
+	  next_event = slaac->ping_time;
+      }
+
+  return next_event;
+}
+
+
+void slaac_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface, struct dhcp_lease *leases)
+{
+  struct dhcp_lease *lease;
+  struct slaac_address *slaac;
+  struct ping_packet *ping = (struct ping_packet *)packet;
+  int gotone = 0;
+  
+  if (ping->identifier == ping_id)
+    for (lease = leases; lease; lease = lease->next)
+      for (slaac = lease->slaac_address; slaac; slaac = slaac->next)
+	if (slaac->backoff != 0 && IN6_ARE_ADDR_EQUAL(sender, &slaac->addr))
+	  {
+	    slaac->backoff = 0;
+	    gotone = 1;
+	    inet_ntop(AF_INET6, sender, daemon->addrbuff, ADDRSTRLEN);
+	    if (!option_bool(OPT_QUIET_DHCP6))
+	      my_syslog(MS_DHCP | LOG_INFO, "SLAAC-CONFIRM(%s) %s %s", interface, daemon->addrbuff, lease->hostname); 
+	  }
+  
+  lease_update_dns(gotone);
+}
+	
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c b/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c
new file mode 100755
index 0000000..ccfe9ed
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/tables.c
@@ -0,0 +1,144 @@
+/* tables.c is Copyright (c) 2014 Sven Falempin  All Rights Reserved.
+
+   Author's email: sfalempin@citypassenger.com 
+
+   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"
+
+#if defined(HAVE_IPSET) && defined(HAVE_BSD_NETWORK)
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#define UNUSED(x) (void)(x)
+
+static char *pf_device = "/dev/pf";
+static int dev = -1;
+
+static char *pfr_strerror(int errnum)
+{
+  switch (errnum) 
+    {
+    case ESRCH:
+      return "Table does not exist";
+    case ENOENT:
+      return "Anchor or Ruleset does not exist";
+    default:
+      return strerror(errnum);
+    }
+}
+
+
+void ipset_init(void) 
+{
+  dev = open( pf_device, O_RDWR);
+  if (dev == -1)
+    {
+      err(1, "%s", pf_device);
+      die (_("failed to access pf devices: %s"), NULL, EC_MISC);
+    }
+}
+
+int add_to_ipset(const char *setname, const union all_addr *ipaddr,
+		 int flags, int remove)
+{
+  struct pfr_addr addr;
+  struct pfioc_table io;
+  struct pfr_table table;
+
+  if (dev == -1) 
+    {
+      my_syslog(LOG_ERR, _("warning: no opened pf devices %s"), pf_device);
+      return -1;
+    }
+
+  bzero(&table, sizeof(struct pfr_table));
+  table.pfrt_flags |= PFR_TFLAG_PERSIST;
+  if (strlen(setname) >= PF_TABLE_NAME_SIZE)
+    {
+      my_syslog(LOG_ERR, _("error: cannot use table name %s"), setname);
+      errno = ENAMETOOLONG;
+      return -1;
+    }
+  
+  if (strlcpy(table.pfrt_name, setname,
+	      sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name)) 
+    {
+      my_syslog(LOG_ERR, _("error: cannot strlcpy table name %s"), setname);
+      return -1;
+    }
+  
+  bzero(&io, sizeof io);
+  io.pfrio_flags = 0;
+  io.pfrio_buffer = &table;
+  io.pfrio_esize = sizeof(table);
+  io.pfrio_size = 1;
+  if (ioctl(dev, DIOCRADDTABLES, &io))
+    {
+      my_syslog(LOG_WARNING, _("IPset: error: %s"), pfr_strerror(errno));
+      
+      return -1;
+    }
+  
+  table.pfrt_flags &= ~PFR_TFLAG_PERSIST;
+  if (io.pfrio_nadd)
+    my_syslog(LOG_INFO, _("info: table created"));
+ 
+  bzero(&addr, sizeof(addr));
+
+  if (flags & F_IPV6) 
+    {
+      addr.pfra_af = AF_INET6;
+      addr.pfra_net = 0x80;
+      memcpy(&(addr.pfra_ip6addr), ipaddr, sizeof(struct in6_addr));
+    } 
+  else 
+    {
+      addr.pfra_af = AF_INET;
+      addr.pfra_net = 0x20;
+      addr.pfra_ip4addr.s_addr = ipaddr->addr4.s_addr;
+    }
+
+  bzero(&io, sizeof(io));
+  io.pfrio_flags = 0;
+  io.pfrio_table = table;
+  io.pfrio_buffer = &addr;
+  io.pfrio_esize = sizeof(addr);
+  io.pfrio_size = 1;
+  if (ioctl(dev, ( remove ? DIOCRDELADDRS : DIOCRADDADDRS ), &io)) 
+    {
+      my_syslog(LOG_WARNING, _("warning: DIOCR%sADDRS: %s"), ( remove ? "DEL" : "ADD" ), pfr_strerror(errno));
+      return -1;
+    }
+  
+  my_syslog(LOG_INFO, _("%d addresses %s"),
+            io.pfrio_nadd, ( remove ? "removed" : "added" ));
+  
+  return io.pfrio_nadd;
+}
+
+
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c b/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c
new file mode 100755
index 0000000..37bdff2
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/tftp.c
@@ -0,0 +1,879 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_TFTP
+
+static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len);
+static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix);
+static void free_transfer(struct tftp_transfer *transfer);
+static ssize_t tftp_err(int err, char *packet, char *message, char *file);
+static ssize_t tftp_err_oops(char *packet, const char *file);
+static ssize_t get_block(char *packet, struct tftp_transfer *transfer);
+static char *next(char **p, char *end);
+static void sanitise(char *buf);
+
+#define OP_RRQ  1
+#define OP_WRQ  2
+#define OP_DATA 3
+#define OP_ACK  4
+#define OP_ERR  5
+#define OP_OACK 6
+
+#define ERR_NOTDEF 0
+#define ERR_FNF    1
+#define ERR_PERM   2
+#define ERR_FULL   3
+#define ERR_ILL    4
+#define ERR_TID    5
+
+void tftp_request(struct listener *listen, time_t now)
+{
+  ssize_t len;
+  char *packet = daemon->packet;
+  char *filename, *mode, *p, *end, *opt;
+  union mysockaddr addr, peer;
+  struct msghdr msg;
+  struct iovec iov;
+  struct ifreq ifr;
+  int is_err = 1, if_index = 0, mtu = 0;
+  struct iname *tmp;
+  struct tftp_transfer *transfer = NULL, **up;
+  int port = daemon->start_tftp_port; /* may be zero to use ephemeral port */
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+  int mtuflag = IP_PMTUDISC_DONT;
+#endif
+  char namebuff[IF_NAMESIZE];
+  char *name = NULL;
+  char *prefix = daemon->tftp_prefix;
+  struct tftp_prefix *pref;
+  union all_addr addra;
+  int family = listen->addr.sa.sa_family;
+  /* Can always get recvd interface for IPv6 */
+  int check_dest = !option_bool(OPT_NOWILD) || family == AF_INET6;
+  union {
+    struct cmsghdr align; /* this ensures alignment */
+    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+#if defined(HAVE_LINUX_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_pktinfo))];
+#elif defined(HAVE_SOLARIS_NETWORK)
+    char control[CMSG_SPACE(sizeof(struct in_addr)) +
+		 CMSG_SPACE(sizeof(unsigned int))];
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+    char control[CMSG_SPACE(sizeof(struct in_addr)) +
+		 CMSG_SPACE(sizeof(struct sockaddr_dl))];
+#endif
+  } control_u; 
+
+  msg.msg_controllen = sizeof(control_u);
+  msg.msg_control = control_u.control;
+  msg.msg_flags = 0;
+  msg.msg_name = &peer;
+  msg.msg_namelen = sizeof(peer);
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
+  iov.iov_base = packet;
+  iov.iov_len = daemon->packet_buff_sz;
+
+  /* we overwrote the buffer... */
+  daemon->srv_save = NULL;
+
+  if ((len = recvmsg(listen->tftpfd, &msg, 0)) < 2)
+    return;
+  
+  /* Can always get recvd interface for IPv6 */
+  if (!check_dest)
+    {
+      if (listen->iface)
+	{
+	  addr = listen->iface->addr;
+	  name = listen->iface->name;
+	  mtu = listen->iface->mtu;
+	  if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
+	    mtu = daemon->tftp_mtu;
+	}
+      else
+	{
+	  /* we're listening on an address that doesn't appear on an interface,
+	     ask the kernel what the socket is bound to */
+	  socklen_t tcp_len = sizeof(union mysockaddr);
+	  if (getsockname(listen->tftpfd, (struct sockaddr *)&addr, &tcp_len) == -1)
+	    return;
+	}
+    }
+  else
+    {
+      struct cmsghdr *cmptr;
+
+      if (msg.msg_controllen < sizeof(struct cmsghdr))
+        return;
+      
+      addr.sa.sa_family = family;
+      
+#if defined(HAVE_LINUX_NETWORK)
+      if (family == AF_INET)
+	for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	  if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
+	    {
+	      union {
+		unsigned char *c;
+		struct in_pktinfo *p;
+	      } p;
+	      p.c = CMSG_DATA(cmptr);
+	      addr.in.sin_addr = p.p->ipi_spec_dst;
+	      if_index = p.p->ipi_ifindex;
+	    }
+      
+#elif defined(HAVE_SOLARIS_NETWORK)
+      if (family == AF_INET)
+	for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	  {
+	    union {
+	      unsigned char *c;
+	      struct in_addr *a;
+	      unsigned int *i;
+	    } p;
+	    p.c = CMSG_DATA(cmptr);
+	    if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
+	    addr.in.sin_addr = *(p.a);
+	    else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
+	    if_index = *(p.i);
+	  }
+      
+#elif defined(IP_RECVDSTADDR) && defined(IP_RECVIF)
+      if (family == AF_INET)
+	for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+	  {
+	    union {
+	      unsigned char *c;
+	      struct in_addr *a;
+	      struct sockaddr_dl *s;
+	    } p;
+	    p.c = CMSG_DATA(cmptr);
+	    if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVDSTADDR)
+	      addr.in.sin_addr = *(p.a);
+	    else if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF)
+	      if_index = p.s->sdl_index;
+	  }
+	  
+#endif
+
+      if (family == AF_INET6)
+        {
+          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);
+                  
+                addr.in6.sin6_addr = p.p->ipi6_addr;
+                if_index = p.p->ipi6_ifindex;
+              }
+        }
+      
+      if (!indextoname(listen->tftpfd, if_index, namebuff))
+	return;
+
+      name = namebuff;
+      
+      addra.addr4 = addr.in.sin_addr;
+
+      if (family == AF_INET6)
+	addra.addr6 = addr.in6.sin6_addr;
+
+      if (daemon->tftp_interfaces)
+	{
+	  /* dedicated tftp interface list */
+	  for (tmp = daemon->tftp_interfaces; tmp; tmp = tmp->next)
+	    if (tmp->name && wildcard_match(tmp->name, name))
+	      break;
+
+	  if (!tmp)
+	    return;
+	}
+      else
+	{
+	  /* Do the same as DHCP */
+	  if (!iface_check(family, &addra, name, NULL))
+	    {
+	      if (!option_bool(OPT_CLEVERBIND))
+		enumerate_interfaces(0); 
+	      if (!loopback_exception(listen->tftpfd, family, &addra, name) &&
+		  !label_exception(if_index, family, &addra))
+		return;
+	    }
+	  
+#ifdef HAVE_DHCP      
+	  /* allowed interfaces are the same as for DHCP */
+	  for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next)
+	    if (tmp->name && wildcard_match(tmp->name, name))
+	      return;
+#endif
+	}
+
+      safe_strncpy(ifr.ifr_name, name, IF_NAMESIZE);
+      if (ioctl(listen->tftpfd, SIOCGIFMTU, &ifr) != -1)
+	{
+	  mtu = ifr.ifr_mtu;  
+	  if (daemon->tftp_mtu != 0 && daemon->tftp_mtu < mtu)
+	    mtu = daemon->tftp_mtu;    
+	}
+    }
+
+  /* Failed to get interface mtu - can use configured value. */
+  if (mtu == 0)
+    mtu = daemon->tftp_mtu;
+
+  /* data transfer via server listening socket */
+  if (option_bool(OPT_SINGLE_PORT))
+    {
+      int tftp_cnt;
+
+      for (tftp_cnt = 0, transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; up = &transfer->next, transfer = transfer->next)
+	{
+	  tftp_cnt++;
+
+	  if (sockaddr_isequal(&peer, &transfer->peer))
+	    {
+	      if (ntohs(*((unsigned short *)packet)) == OP_RRQ)
+		{
+		  /* Handle repeated RRQ or abandoned transfer from same host and port 
+		     by unlinking and reusing the struct transfer. */
+		  *up = transfer->next;
+		  break;
+		}
+	      else
+		{
+		  handle_tftp(now, transfer, len);
+		  return;
+		}
+	    }
+	}
+      
+      /* Enforce simultaneous transfer limit. In non-single-port mode
+	 this is doene by not listening on the server socket when
+	 too many transfers are in progress. */
+      if (!transfer && tftp_cnt >= daemon->tftp_max)
+	return;
+    }
+  
+  if (name)
+    {
+      /* check for per-interface prefix */ 
+      for (pref = daemon->if_prefix; pref; pref = pref->next)
+	if (strcmp(pref->interface, name) == 0)
+	  prefix = pref->prefix;  
+    }
+
+  if (family == AF_INET)
+    {
+      addr.in.sin_port = htons(port);
+#ifdef HAVE_SOCKADDR_SA_LEN
+      addr.in.sin_len = sizeof(addr.in);
+#endif
+    }
+  else
+    {
+      addr.in6.sin6_port = htons(port);
+      addr.in6.sin6_flowinfo = 0;
+      addr.in6.sin6_scope_id = 0;
+#ifdef HAVE_SOCKADDR_SA_LEN
+      addr.in6.sin6_len = sizeof(addr.in6);
+#endif
+    }
+
+  /* May reuse struct transfer from abandoned transfer in single port mode. */
+  if (!transfer && !(transfer = whine_malloc(sizeof(struct tftp_transfer))))
+    return;
+  
+  if (option_bool(OPT_SINGLE_PORT))
+    transfer->sockfd = listen->tftpfd;
+  else if ((transfer->sockfd = socket(family, SOCK_DGRAM, 0)) == -1)
+    {
+      free(transfer);
+      return;
+    }
+  
+  transfer->peer = peer;
+  transfer->source = addra;
+  transfer->if_index = if_index;
+  transfer->timeout = now + 2;
+  transfer->backoff = 1;
+  transfer->block = 1;
+  transfer->blocksize = 512;
+  transfer->offset = 0;
+  transfer->file = NULL;
+  transfer->opt_blocksize = transfer->opt_transize = 0;
+  transfer->netascii = transfer->carrylf = 0;
+ 
+  (void)prettyprint_addr(&peer, daemon->addrbuff);
+  
+  /* if we have a nailed-down range, iterate until we find a free one. */
+  while (!option_bool(OPT_SINGLE_PORT))
+    {
+      if (bind(transfer->sockfd, &addr.sa, sa_len(&addr)) == -1 ||
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+	  setsockopt(transfer->sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &mtuflag, sizeof(mtuflag)) == -1 ||
+#endif
+	  !fix_fd(transfer->sockfd))
+	{
+	  if (errno == EADDRINUSE && daemon->start_tftp_port != 0)
+	    {
+	      if (++port <= daemon->end_tftp_port)
+		{ 
+		  if (family == AF_INET)
+		    addr.in.sin_port = htons(port);
+		  else
+		    addr.in6.sin6_port = htons(port);
+		  
+		  continue;
+		}
+	      my_syslog(MS_TFTP | LOG_ERR, _("unable to get free port for TFTP"));
+	    }
+	  free_transfer(transfer);
+	  return;
+	}
+      break;
+    }
+  
+  p = packet + 2;
+  end = packet + len;
+  
+  if (ntohs(*((unsigned short *)packet)) != OP_RRQ ||
+      !(filename = next(&p, end)) ||
+      !(mode = next(&p, end)) ||
+      (strcasecmp(mode, "octet") != 0 && strcasecmp(mode, "netascii") != 0))
+    {
+      len = tftp_err(ERR_ILL, packet, _("unsupported request from %s"), daemon->addrbuff);
+      is_err = 1;
+    }
+  else
+    {
+      if (strcasecmp(mode, "netascii") == 0)
+	transfer->netascii = 1;
+      
+      while ((opt = next(&p, end)))
+	{
+	  if (strcasecmp(opt, "blksize") == 0)
+	    {
+	      if ((opt = next(&p, end)) && !option_bool(OPT_TFTP_NOBLOCK))
+		{
+		  /* 32 bytes for IP, UDP and TFTP headers, 52 bytes for IPv6 */
+		  int overhead = (family == AF_INET) ? 32 : 52;
+		  transfer->blocksize = atoi(opt);
+		  if (transfer->blocksize < 1)
+		    transfer->blocksize = 1;
+		  if (transfer->blocksize > (unsigned)daemon->packet_buff_sz - 4)
+		    transfer->blocksize = (unsigned)daemon->packet_buff_sz - 4;
+		  if (mtu != 0 && transfer->blocksize > (unsigned)mtu - overhead)
+		    transfer->blocksize = (unsigned)mtu - overhead;
+		  transfer->opt_blocksize = 1;
+		  transfer->block = 0;
+		}
+	    }
+	  else if (strcasecmp(opt, "tsize") == 0 && next(&p, end) && !transfer->netascii)
+	    {
+	      transfer->opt_transize = 1;
+	      transfer->block = 0;
+	    }
+	}
+
+      /* cope with backslashes from windows boxen. */
+      for (p = filename; *p; p++)
+	if (*p == '\\')
+	  *p = '/';
+	else if (option_bool(OPT_TFTP_LC))
+	  *p = tolower(*p);
+		
+      strcpy(daemon->namebuff, "/");
+      if (prefix)
+	{
+	  if (prefix[0] == '/')
+	    daemon->namebuff[0] = 0;
+	  strncat(daemon->namebuff, prefix, (MAXDNAME-1) - strlen(daemon->namebuff));
+	  if (prefix[strlen(prefix)-1] != '/')
+	    strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
+
+	  if (option_bool(OPT_TFTP_APREF_IP))
+	    {
+	      size_t oldlen = strlen(daemon->namebuff);
+	      struct stat statbuf;
+	      
+	      strncat(daemon->namebuff, daemon->addrbuff, (MAXDNAME-1) - strlen(daemon->namebuff));
+	      strncat(daemon->namebuff, "/", (MAXDNAME-1) - strlen(daemon->namebuff));
+	      
+	      /* remove unique-directory if it doesn't exist */
+	      if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
+		daemon->namebuff[oldlen] = 0;
+	    }
+	  
+	  if (option_bool(OPT_TFTP_APREF_MAC))
+	    {
+	      unsigned char *macaddr = NULL;
+	      unsigned char macbuf[DHCP_CHADDR_MAX];
+	      
+#ifdef HAVE_DHCP
+	      if (daemon->dhcp && peer.sa.sa_family == AF_INET)
+	        {
+		  /* Check if the client IP is in our lease database */
+		  struct dhcp_lease *lease = lease_find_by_addr(peer.in.sin_addr);
+		  if (lease && lease->hwaddr_type == ARPHRD_ETHER && lease->hwaddr_len == ETHER_ADDR_LEN)
+		    macaddr = lease->hwaddr;
+		}
+#endif
+	      
+	      /* If no luck, try to find in ARP table. This only works if client is in same (V)LAN */
+	      if (!macaddr && find_mac(&peer, macbuf, 1, now) > 0)
+		macaddr = macbuf;
+	      
+	      if (macaddr)
+	        {
+		  size_t oldlen = strlen(daemon->namebuff);
+		  struct stat statbuf;
+
+		  snprintf(daemon->namebuff + oldlen, (MAXDNAME-1) - oldlen, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x/",
+			   macaddr[0], macaddr[1], macaddr[2], macaddr[3], macaddr[4], macaddr[5]);
+		  
+		  /* remove unique-directory if it doesn't exist */
+		  if (stat(daemon->namebuff, &statbuf) == -1 || !S_ISDIR(statbuf.st_mode))
+		    daemon->namebuff[oldlen] = 0;
+		}
+	    }
+	  
+	  /* Absolute pathnames OK if they match prefix */
+	  if (filename[0] == '/')
+	    {
+	      if (strstr(filename, daemon->namebuff) == filename)
+		daemon->namebuff[0] = 0;
+	      else
+		filename++;
+	    }
+	}
+      else if (filename[0] == '/')
+	daemon->namebuff[0] = 0;
+      strncat(daemon->namebuff, filename, (MAXDNAME-1) - strlen(daemon->namebuff));
+      
+      /* check permissions and open file */
+      if ((transfer->file = check_tftp_fileperm(&len, prefix)))
+	{
+	  if ((len = get_block(packet, transfer)) == -1)
+	    len = tftp_err_oops(packet, daemon->namebuff);
+	  else
+	    is_err = 0;
+	}
+    }
+
+  send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), packet, len, &peer, &addra, if_index);
+  
+  if (is_err)
+    free_transfer(transfer);
+  else
+    {
+      transfer->next = daemon->tftp_trans;
+      daemon->tftp_trans = transfer;
+    }
+}
+ 
+static struct tftp_file *check_tftp_fileperm(ssize_t *len, char *prefix)
+{
+  char *packet = daemon->packet, *namebuff = daemon->namebuff;
+  struct tftp_file *file;
+  struct tftp_transfer *t;
+  uid_t uid = geteuid();
+  struct stat statbuf;
+  int fd = -1;
+
+  /* trick to ban moving out of the subtree */
+  if (prefix && strstr(namebuff, "/../"))
+    goto perm;
+  
+  if ((fd = open(namebuff, O_RDONLY)) == -1)
+    {
+      if (errno == ENOENT)
+	{
+	  *len = tftp_err(ERR_FNF, packet, _("file %s not found"), namebuff);
+	  return NULL;
+	}
+      else if (errno == EACCES)
+	goto perm;
+      else
+	goto oops;
+    }
+  
+  /* stat the file descriptor to avoid stat->open races */
+  if (fstat(fd, &statbuf) == -1)
+    goto oops;
+  
+  /* running as root, must be world-readable */
+  if (uid == 0)
+    {
+      if (!(statbuf.st_mode & S_IROTH))
+	goto perm;
+    }
+  /* in secure mode, must be owned by user running dnsmasq */
+  else if (option_bool(OPT_TFTP_SECURE) && uid != statbuf.st_uid)
+    goto perm;
+      
+  /* If we're doing many transfers from the same file, only 
+     open it once this saves lots of file descriptors 
+     when mass-booting a big cluster, for instance. 
+     Be conservative and only share when inode and name match
+     this keeps error messages sane. */
+  for (t = daemon->tftp_trans; t; t = t->next)
+    if (t->file->dev == statbuf.st_dev && 
+	t->file->inode == statbuf.st_ino &&
+	strcmp(t->file->filename, namebuff) == 0)
+      {
+	close(fd);
+	t->file->refcount++;
+	return t->file;
+      }
+  
+  if (!(file = whine_malloc(sizeof(struct tftp_file) + strlen(namebuff) + 1)))
+    {
+      errno = ENOMEM;
+      goto oops;
+    }
+
+  file->fd = fd;
+  file->size = statbuf.st_size;
+  file->dev = statbuf.st_dev;
+  file->inode = statbuf.st_ino;
+  file->refcount = 1;
+  strcpy(file->filename, namebuff);
+  return file;
+  
+ perm:
+  errno = EACCES;
+  *len =  tftp_err(ERR_PERM, packet, _("cannot access %s: %s"), namebuff);
+  if (fd != -1)
+    close(fd);
+  return NULL;
+
+ oops:
+  *len =  tftp_err_oops(packet, namebuff);
+  if (fd != -1)
+    close(fd);
+  return NULL;
+}
+
+void check_tftp_listeners(time_t now)
+{
+  struct tftp_transfer *transfer, *tmp, **up;
+  
+  /* In single port mode, all packets come via port 69 and tftp_request() */
+  if (!option_bool(OPT_SINGLE_PORT))
+    for (transfer = daemon->tftp_trans; transfer; transfer = transfer->next)
+      if (poll_check(transfer->sockfd, POLLIN))
+	{
+	  union mysockaddr peer;
+	  socklen_t addr_len = sizeof(union mysockaddr);
+	  ssize_t len;
+	  
+	  /* we overwrote the buffer... */
+	  daemon->srv_save = NULL;
+
+	  if ((len = recvfrom(transfer->sockfd, daemon->packet, daemon->packet_buff_sz, 0, &peer.sa, &addr_len)) > 0)
+	    {
+	      if (sockaddr_isequal(&peer, &transfer->peer)) 
+		handle_tftp(now, transfer, len);
+	      else
+		{
+		  /* Wrong source address. See rfc1350 para 4. */
+		  prettyprint_addr(&peer, daemon->addrbuff);
+		  len = tftp_err(ERR_TID, daemon->packet, _("ignoring packet from %s (TID mismatch)"), daemon->addrbuff);
+		  sendto(transfer->sockfd, daemon->packet, len, 0, &peer.sa, sa_len(&peer));
+		}
+	    }
+	}
+	  
+  for (transfer = daemon->tftp_trans, up = &daemon->tftp_trans; transfer; transfer = tmp)
+    {
+      tmp = transfer->next;
+      
+      if (difftime(now, transfer->timeout) >= 0.0)
+	{
+	  int endcon = 0;
+	  ssize_t len;
+
+	  /* timeout, retransmit */
+	  transfer->timeout += 1 + (1<<(transfer->backoff/2));
+	  	  
+	  /* we overwrote the buffer... */
+	  daemon->srv_save = NULL;
+
+	  if ((len = get_block(daemon->packet, transfer)) == -1)
+	    {
+	      len = tftp_err_oops(daemon->packet, transfer->file->filename);
+	      endcon = 1;
+	    }
+	  else if (++transfer->backoff > 7)
+	    {
+	      /* don't complain about timeout when we're awaiting the last
+		 ACK, some clients never send it */
+	      if ((unsigned)len == transfer->blocksize + 4)
+		endcon = 1;
+	      len = 0;
+	    }
+
+	  if (len != 0)
+	    send_from(transfer->sockfd, !option_bool(OPT_SINGLE_PORT), daemon->packet, len,
+		      &transfer->peer, &transfer->source, transfer->if_index);
+	  	  
+	  if (endcon || len == 0)
+	    {
+	      strcpy(daemon->namebuff, transfer->file->filename);
+	      sanitise(daemon->namebuff);
+	      (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
+	      my_syslog(MS_TFTP | LOG_INFO, endcon ? _("failed sending %s to %s") : _("sent %s to %s"), daemon->namebuff, daemon->addrbuff);
+	      /* unlink */
+	      *up = tmp;
+	      if (endcon)
+		free_transfer(transfer);
+	      else
+		{
+		  /* put on queue to be sent to script and deleted */
+		  transfer->next = daemon->tftp_done_trans;
+		  daemon->tftp_done_trans = transfer;
+		}
+	      continue;
+	    }
+	}
+
+      up = &transfer->next;
+    }    
+}
+	  
+/* packet in daemon->packet as this is called. */
+static void handle_tftp(time_t now, struct tftp_transfer *transfer, ssize_t len)
+{
+  struct ack {
+    unsigned short op, block;
+  } *mess = (struct ack *)daemon->packet;
+  
+  if (len >= (ssize_t)sizeof(struct ack))
+    {
+      if (ntohs(mess->op) == OP_ACK && ntohs(mess->block) == (unsigned short)transfer->block) 
+	{
+	  /* Got ack, ensure we take the (re)transmit path */
+	  transfer->timeout = now;
+	  transfer->backoff = 0;
+	  if (transfer->block++ != 0)
+	    transfer->offset += transfer->blocksize - transfer->expansion;
+	}
+      else if (ntohs(mess->op) == OP_ERR)
+	{
+	  char *p = daemon->packet + sizeof(struct ack);
+	  char *end = daemon->packet + len;
+	  char *err = next(&p, end);
+	  
+	  (void)prettyprint_addr(&transfer->peer, daemon->addrbuff);
+	  
+	  /* Sanitise error message */
+	  if (!err)
+	    err = "";
+	  else
+	    sanitise(err);
+	  
+	  my_syslog(MS_TFTP | LOG_ERR, _("error %d %s received from %s"),
+		    (int)ntohs(mess->block), err, 
+		    daemon->addrbuff);	
+	  
+	  /* Got err, ensure we take abort */
+	  transfer->timeout = now;
+	  transfer->backoff = 100;
+	}
+    }
+}
+
+static void free_transfer(struct tftp_transfer *transfer)
+{
+  if (!option_bool(OPT_SINGLE_PORT))
+    close(transfer->sockfd);
+
+  if (transfer->file && (--transfer->file->refcount) == 0)
+    {
+      close(transfer->file->fd);
+      free(transfer->file);
+    }
+  
+  free(transfer);
+}
+
+static char *next(char **p, char *end)
+{
+  char *ret = *p;
+  size_t len;
+
+  if (*(end-1) != 0 || 
+      *p == end ||
+      (len = strlen(ret)) == 0)
+    return NULL;
+
+  *p += len + 1;
+  return ret;
+}
+
+static void sanitise(char *buf)
+{
+  unsigned char *q, *r;
+  for (q = r = (unsigned char *)buf; *r; r++)
+    if (isprint((int)*r))
+      *(q++) = *r;
+  *q = 0;
+
+}
+
+#define MAXMESSAGE 500 /* limit to make packet < 512 bytes and definitely smaller than buffer */ 
+static ssize_t tftp_err(int err, char *packet, char *message, char *file)
+{
+  struct errmess {
+    unsigned short op, err;
+    char message[];
+  } *mess = (struct errmess *)packet;
+  ssize_t len, ret = 4;
+  char *errstr = strerror(errno);
+  
+  memset(packet, 0, daemon->packet_buff_sz);
+  if (file)
+    sanitise(file);
+  
+  mess->op = htons(OP_ERR);
+  mess->err = htons(err);
+  len = snprintf(mess->message, MAXMESSAGE,  message, file, errstr);
+  ret += (len < MAXMESSAGE) ? len + 1 : MAXMESSAGE; /* include terminating zero */
+  
+  if (err != ERR_FNF || !option_bool(OPT_QUIET_TFTP))
+    my_syslog(MS_TFTP | LOG_ERR, "%s", mess->message);
+  
+  return  ret;
+}
+
+static ssize_t tftp_err_oops(char *packet, const char *file)
+{
+  /* May have >1 refs to file, so potentially mangle a copy of the name */
+  if (file != daemon->namebuff)
+    strcpy(daemon->namebuff, file);
+  return tftp_err(ERR_NOTDEF, packet, _("cannot read %s: %s"), daemon->namebuff);
+}
+
+/* return -1 for error, zero for done. */
+static ssize_t get_block(char *packet, struct tftp_transfer *transfer)
+{
+  memset(packet, 0, daemon->packet_buff_sz);
+  
+  if (transfer->block == 0)
+    {
+      /* send OACK */
+      char *p;
+      struct oackmess {
+	unsigned short op;
+	char data[];
+      } *mess = (struct oackmess *)packet;
+      
+      p = mess->data;
+      mess->op = htons(OP_OACK);
+      if (transfer->opt_blocksize)
+	{
+	  p += (sprintf(p, "blksize") + 1);
+	  p += (sprintf(p, "%u", transfer->blocksize) + 1);
+	}
+      if (transfer->opt_transize)
+	{
+	  p += (sprintf(p,"tsize") + 1);
+	  p += (sprintf(p, "%u", (unsigned int)transfer->file->size) + 1);
+	}
+
+      return p - packet;
+    }
+  else
+    {
+      /* send data packet */
+      struct datamess {
+	unsigned short op, block;
+	unsigned char data[];
+      } *mess = (struct datamess *)packet;
+      
+      size_t size = transfer->file->size - transfer->offset; 
+      
+      if (transfer->offset > transfer->file->size)
+	return 0; /* finished */
+      
+      if (size > transfer->blocksize)
+	size = transfer->blocksize;
+      
+      mess->op = htons(OP_DATA);
+      mess->block = htons((unsigned short)(transfer->block));
+      
+      if (lseek(transfer->file->fd, transfer->offset, SEEK_SET) == (off_t)-1 ||
+	  !read_write(transfer->file->fd, mess->data, size, 1))
+	return -1;
+      
+      transfer->expansion = 0;
+      
+      /* Map '\n' to CR-LF in netascii mode */
+      if (transfer->netascii)
+	{
+	  size_t i;
+	  int newcarrylf;
+
+	  for (i = 0, newcarrylf = 0; i < size; i++)
+	    if (mess->data[i] == '\n' && ( i != 0 || !transfer->carrylf))
+	      {
+		transfer->expansion++;
+
+		if (size != transfer->blocksize)
+		  size++; /* room in this block */
+		else  if (i == size - 1)
+		  newcarrylf = 1; /* don't expand LF again if it moves to the next block */
+		  
+		/* make space and insert CR */
+		memmove(&mess->data[i+1], &mess->data[i], size - (i + 1));
+		mess->data[i] = '\r';
+		
+		i++;
+	      }
+	  transfer->carrylf = newcarrylf;
+	  
+	}
+
+      return size + 4;
+    }
+}
+
+
+int do_tftp_script_run(void)
+{
+  struct tftp_transfer *transfer;
+
+  if ((transfer = daemon->tftp_done_trans))
+    {
+      daemon->tftp_done_trans = transfer->next;
+#ifdef HAVE_SCRIPT
+      queue_tftp(transfer->file->size, transfer->file->filename, &transfer->peer);
+#endif
+      free_transfer(transfer);
+      return 1;
+    }
+
+  return 0;
+}
+#endif
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c b/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c
new file mode 100755
index 0000000..0c502ad
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/ubus.c
@@ -0,0 +1,386 @@
+/* dnsmasq is Copyright (c) 2000-2021 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_UBUS
+
+#include <libubus.h>
+
+static struct blob_buf b;
+static int error_logged = 0;
+
+static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj,
+			       struct ubus_request_data *req, const char *method,
+			       struct blob_attr *msg);
+
+#ifdef HAVE_CONNTRACK
+enum {
+  SET_CONNMARK_ALLOWLIST_MARK,
+  SET_CONNMARK_ALLOWLIST_MASK,
+  SET_CONNMARK_ALLOWLIST_PATTERNS
+};
+static const struct blobmsg_policy set_connmark_allowlist_policy[] = {
+  [SET_CONNMARK_ALLOWLIST_MARK] = {
+    .name = "mark",
+    .type = BLOBMSG_TYPE_INT32
+  },
+  [SET_CONNMARK_ALLOWLIST_MASK] = {
+    .name = "mask",
+    .type = BLOBMSG_TYPE_INT32
+  },
+  [SET_CONNMARK_ALLOWLIST_PATTERNS] = {
+    .name = "patterns",
+    .type = BLOBMSG_TYPE_ARRAY
+  }
+};
+static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
+					      struct ubus_request_data *req, const char *method,
+					      struct blob_attr *msg);
+#endif
+
+static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj);
+
+static const struct ubus_method ubus_object_methods[] = {
+  UBUS_METHOD_NOARG("metrics", ubus_handle_metrics),
+#ifdef HAVE_CONNTRACK
+  UBUS_METHOD("set_connmark_allowlist", ubus_handle_set_connmark_allowlist, set_connmark_allowlist_policy),
+#endif
+};
+
+static struct ubus_object_type ubus_object_type =
+  UBUS_OBJECT_TYPE("dnsmasq", ubus_object_methods);
+
+static struct ubus_object ubus_object = {
+  .name = NULL,
+  .type = &ubus_object_type,
+  .methods = ubus_object_methods,
+  .n_methods = ARRAY_SIZE(ubus_object_methods),
+  .subscribe_cb = ubus_subscribe_cb,
+};
+
+static void ubus_subscribe_cb(struct ubus_context *ctx, struct ubus_object *obj)
+{
+  (void)ctx;
+
+  my_syslog(LOG_DEBUG, _("UBus subscription callback: %s subscriber(s)"), obj->has_subscribers ? "1" : "0");
+}
+
+static void ubus_destroy(struct ubus_context *ubus)
+{
+  ubus_free(ubus);
+  daemon->ubus = NULL;
+  
+  /* Forces re-initialization when we're reusing the same definitions later on. */
+  ubus_object.id = 0;
+  ubus_object_type.id = 0;
+}
+
+static void ubus_disconnect_cb(struct ubus_context *ubus)
+{
+  int ret;
+
+  ret = ubus_reconnect(ubus, NULL);
+  if (ret)
+    {
+      my_syslog(LOG_ERR, _("Cannot reconnect to UBus: %s"), ubus_strerror(ret));
+
+      ubus_destroy(ubus);
+    }
+}
+
+char *ubus_init()
+{
+  struct ubus_context *ubus = NULL;
+  int ret = 0;
+
+  if (!(ubus = ubus_connect(NULL)))
+    return NULL;
+  
+  ubus_object.name = daemon->ubus_name;
+  ret = ubus_add_object(ubus, &ubus_object);
+  if (ret)
+    {
+      ubus_destroy(ubus);
+      return (char *)ubus_strerror(ret);
+    }    
+  
+  ubus->connection_lost = ubus_disconnect_cb;
+  daemon->ubus = ubus;
+  error_logged = 0;
+
+  return NULL;
+}
+
+void set_ubus_listeners()
+{
+  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+  if (!ubus)
+    {
+      if (!error_logged)
+        {
+          my_syslog(LOG_ERR, _("Cannot set UBus listeners: no connection"));
+          error_logged = 1;
+        }
+      return;
+    }
+
+  error_logged = 0;
+
+  poll_listen(ubus->sock.fd, POLLIN);
+  poll_listen(ubus->sock.fd, POLLERR);
+  poll_listen(ubus->sock.fd, POLLHUP);
+}
+
+void check_ubus_listeners()
+{
+  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+  if (!ubus)
+    {
+      if (!error_logged)
+        {
+          my_syslog(LOG_ERR, _("Cannot poll UBus listeners: no connection"));
+          error_logged = 1;
+        }
+      return;
+    }
+  
+  error_logged = 0;
+
+  if (poll_check(ubus->sock.fd, POLLIN))
+    ubus_handle_event(ubus);
+  
+  if (poll_check(ubus->sock.fd, POLLHUP | POLLERR))
+    {
+      my_syslog(LOG_INFO, _("Disconnecting from UBus"));
+
+      ubus_destroy(ubus);
+    }
+}
+
+#define CHECK(stmt) \
+  do { \
+    int e = (stmt); \
+    if (e) \
+      { \
+	my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \
+	return (UBUS_STATUS_UNKNOWN_ERROR); \
+      } \
+  } while (0)
+
+static int ubus_handle_metrics(struct ubus_context *ctx, struct ubus_object *obj,
+			       struct ubus_request_data *req, const char *method,
+			       struct blob_attr *msg)
+{
+  int i;
+
+  (void)obj;
+  (void)method;
+  (void)msg;
+
+  CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE));
+
+  for (i=0; i < __METRIC_MAX; i++)
+    CHECK(blobmsg_add_u32(&b, get_metric_name(i), daemon->metrics[i]));
+  
+  CHECK(ubus_send_reply(ctx, req, b.head));
+  return UBUS_STATUS_OK;
+}
+
+#ifdef HAVE_CONNTRACK
+static int ubus_handle_set_connmark_allowlist(struct ubus_context *ctx, struct ubus_object *obj,
+					      struct ubus_request_data *req, const char *method,
+					      struct blob_attr *msg)
+{
+  const struct blobmsg_policy *policy = set_connmark_allowlist_policy;
+  size_t policy_len = countof(set_connmark_allowlist_policy);
+  struct allowlist *allowlists = NULL, **allowlists_pos;
+  char **patterns = NULL, **patterns_pos;
+  u32 mark, mask = UINT32_MAX;
+  size_t num_patterns = 0;
+  struct blob_attr *tb[policy_len];
+  struct blob_attr *attr;
+  
+  if (blobmsg_parse(policy, policy_len, tb, blob_data(msg), blob_len(msg)))
+    return UBUS_STATUS_INVALID_ARGUMENT;
+  
+  if (!tb[SET_CONNMARK_ALLOWLIST_MARK])
+    return UBUS_STATUS_INVALID_ARGUMENT;
+  mark = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MARK]);
+  if (!mark)
+    return UBUS_STATUS_INVALID_ARGUMENT;
+  
+  if (tb[SET_CONNMARK_ALLOWLIST_MASK])
+    {
+      mask = blobmsg_get_u32(tb[SET_CONNMARK_ALLOWLIST_MASK]);
+      if (!mask || (mark & ~mask))
+	return UBUS_STATUS_INVALID_ARGUMENT;
+    }
+  
+  if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
+    {
+      struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+      size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+      __blob_for_each_attr(attr, head, len)
+	{
+	  char *pattern;
+	  if (blob_id(attr) != BLOBMSG_TYPE_STRING)
+	    return UBUS_STATUS_INVALID_ARGUMENT;
+	  if (!(pattern = blobmsg_get_string(attr)))
+	    return UBUS_STATUS_INVALID_ARGUMENT;
+	  if (strcmp(pattern, "*") && !is_valid_dns_name_pattern(pattern))
+	    return UBUS_STATUS_INVALID_ARGUMENT;
+	  num_patterns++;
+	}
+    }
+  
+  for (allowlists_pos = &daemon->allowlists; *allowlists_pos; allowlists_pos = &(*allowlists_pos)->next)
+    if ((*allowlists_pos)->mark == mark && (*allowlists_pos)->mask == mask)
+      {
+	struct allowlist *allowlists_next = (*allowlists_pos)->next;
+	for (patterns_pos = (*allowlists_pos)->patterns; *patterns_pos; patterns_pos++)
+	  {
+	    free(*patterns_pos);
+	    *patterns_pos = NULL;
+	  }
+	free((*allowlists_pos)->patterns);
+	(*allowlists_pos)->patterns = NULL;
+	free(*allowlists_pos);
+	*allowlists_pos = allowlists_next;
+	break;
+      }
+  
+  if (!num_patterns)
+    return UBUS_STATUS_OK;
+  
+  patterns = whine_malloc((num_patterns + 1) * sizeof(char *));
+  if (!patterns)
+    goto fail;
+  patterns_pos = patterns;
+  if (tb[SET_CONNMARK_ALLOWLIST_PATTERNS])
+    {
+      struct blob_attr *head = blobmsg_data(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+      size_t len = blobmsg_data_len(tb[SET_CONNMARK_ALLOWLIST_PATTERNS]);
+      __blob_for_each_attr(attr, head, len)
+	{
+	  char *pattern;
+	  if (!(pattern = blobmsg_get_string(attr)))
+	    goto fail;
+	  if (!(*patterns_pos = whine_malloc(strlen(pattern) + 1)))
+	    goto fail;
+	  strcpy(*patterns_pos++, pattern);
+	}
+    }
+  
+  allowlists = whine_malloc(sizeof(struct allowlist));
+  if (!allowlists)
+    goto fail;
+  memset(allowlists, 0, sizeof(struct allowlist));
+  allowlists->mark = mark;
+  allowlists->mask = mask;
+  allowlists->patterns = patterns;
+  allowlists->next = daemon->allowlists;
+  daemon->allowlists = allowlists;
+  return UBUS_STATUS_OK;
+  
+fail:
+  if (patterns)
+    {
+      for (patterns_pos = patterns; *patterns_pos; patterns_pos++)
+	{
+	  free(*patterns_pos);
+	  *patterns_pos = NULL;
+	}
+      free(patterns);
+      patterns = NULL;
+    }
+  if (allowlists)
+    {
+      free(allowlists);
+      allowlists = NULL;
+    }
+  return UBUS_STATUS_UNKNOWN_ERROR;
+}
+#endif
+
+#undef CHECK
+
+#define CHECK(stmt) \
+  do { \
+    int e = (stmt); \
+    if (e) \
+      { \
+	my_syslog(LOG_ERR, _("UBus command failed: %d (%s)"), e, #stmt); \
+	return; \
+      } \
+  } while (0)
+
+void ubus_event_bcast(const char *type, const char *mac, const char *ip, const char *name, const char *interface)
+{
+  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+  if (!ubus || !ubus_object.has_subscribers)
+    return;
+
+  CHECK(blob_buf_init(&b, BLOBMSG_TYPE_TABLE));
+  if (mac)
+    CHECK(blobmsg_add_string(&b, "mac", mac));
+  if (ip)
+    CHECK(blobmsg_add_string(&b, "ip", ip));
+  if (name)
+    CHECK(blobmsg_add_string(&b, "name", name));
+  if (interface)
+    CHECK(blobmsg_add_string(&b, "interface", interface));
+  
+  CHECK(ubus_notify(ubus, &ubus_object, type, b.head, -1));
+}
+
+#ifdef HAVE_CONNTRACK
+void ubus_event_bcast_connmark_allowlist_refused(u32 mark, const char *name)
+{
+  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+  if (!ubus || !ubus_object.has_subscribers)
+    return;
+
+  CHECK(blob_buf_init(&b, 0));
+  CHECK(blobmsg_add_u32(&b, "mark", mark));
+  CHECK(blobmsg_add_string(&b, "name", name));
+  
+  CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.refused", b.head, -1));
+}
+
+void ubus_event_bcast_connmark_allowlist_resolved(u32 mark, const char *name, const char *value, u32 ttl)
+{
+  struct ubus_context *ubus = (struct ubus_context *)daemon->ubus;
+
+  if (!ubus || !ubus_object.has_subscribers)
+    return;
+
+  CHECK(blob_buf_init(&b, 0));
+  CHECK(blobmsg_add_u32(&b, "mark", mark));
+  CHECK(blobmsg_add_string(&b, "name", name));
+  CHECK(blobmsg_add_string(&b, "value", value));
+  CHECK(blobmsg_add_u32(&b, "ttl", ttl));
+  
+  /* Set timeout to allow UBus subscriber to configure firewall rules before returning. */
+  CHECK(ubus_notify(ubus, &ubus_object, "connmark-allowlist.resolved", b.head, /* timeout: */ 1000));
+}
+#endif
+
+#undef CHECK
+
+#endif /* HAVE_UBUS */
diff --git a/ap/app/dnsmasq/dnsmasq-2.86/src/util.c b/ap/app/dnsmasq/dnsmasq-2.86/src/util.c
new file mode 100755
index 0000000..1425764
--- /dev/null
+++ b/ap/app/dnsmasq/dnsmasq-2.86/src/util.c
@@ -0,0 +1,817 @@
+/* dnsmasq is Copyright (c) 2000-2021 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/>.
+*/
+
+/* The SURF random number generator was taken from djbdns-1.05, by 
+   Daniel J Bernstein, which is public domain. */
+
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_BROKEN_RTC
+#include <sys/times.h>
+#endif
+
+#if defined(HAVE_LIBIDN2)
+#include <idn2.h>
+#elif defined(HAVE_IDN)
+#include <idna.h>
+#endif
+
+#ifdef HAVE_LINUX_NETWORK
+#include <sys/utsname.h>
+#endif
+
+/* SURF random number generator */
+
+static u32 seed[32];
+static u32 in[12];
+static u32 out[8];
+static int outleft = 0;
+
+void rand_init()
+{
+  int fd = open(RANDFILE, O_RDONLY);
+  
+  if (fd == -1 ||
+      !read_write(fd, (unsigned char *)&seed, sizeof(seed), 1) ||
+      !read_write(fd, (unsigned char *)&in, sizeof(in), 1))
+    die(_("failed to seed the random number generator: %s"), NULL, EC_MISC);
+  
+  close(fd);
+}
+
+#define ROTATE(x,b) (((x) << (b)) | ((x) >> (32 - (b))))
+#define MUSH(i,b) x = t[i] += (((x ^ seed[i]) + sum) ^ ROTATE(x,b));
+
+static void surf(void)
+{
+  u32 t[12]; u32 x; u32 sum = 0;
+  int r; int i; int loop;
+
+  for (i = 0;i < 12;++i) t[i] = in[i] ^ seed[12 + i];
+  for (i = 0;i < 8;++i) out[i] = seed[24 + i];
+  x = t[11];
+  for (loop = 0;loop < 2;++loop) {
+    for (r = 0;r < 16;++r) {
+      sum += 0x9e3779b9;
+      MUSH(0,5) MUSH(1,7) MUSH(2,9) MUSH(3,13)
+      MUSH(4,5) MUSH(5,7) MUSH(6,9) MUSH(7,13)
+      MUSH(8,5) MUSH(9,7) MUSH(10,9) MUSH(11,13)
+    }
+    for (i = 0;i < 8;++i) out[i] ^= t[i + 4];
+  }
+}
+
+unsigned short rand16(void)
+{
+  if (!outleft) 
+    {
+      if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+      surf();
+      outleft = 8;
+    }
+  
+  return (unsigned short) out[--outleft];
+}
+
+u32 rand32(void)
+{
+ if (!outleft) 
+    {
+      if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+      surf();
+      outleft = 8;
+    }
+  
+  return out[--outleft]; 
+}
+
+u64 rand64(void)
+{
+  static int outleft = 0;
+
+  if (outleft < 2)
+    {
+      if (!++in[0]) if (!++in[1]) if (!++in[2]) ++in[3];
+      surf();
+      outleft = 8;
+    }
+  
+  outleft -= 2;
+
+  return (u64)out[outleft+1] + (((u64)out[outleft]) << 32);
+}
+
+/* returns 2 if names is OK but contains one or more underscores */
+static int check_name(char *in)
+{
+  /* remove trailing . 
+     also fail empty string and label > 63 chars */
+  size_t dotgap = 0, l = strlen(in);
+  char c;
+  int nowhite = 0;
+  int hasuscore = 0;
+  
+  if (l == 0 || l > MAXDNAME) return 0;
+  
+  if (in[l-1] == '.')
+    {
+      in[l-1] = 0;
+      nowhite = 1;
+    }
+
+  for (; (c = *in); in++)
+    {
+      if (c == '.')
+	dotgap = 0;
+      else if (++dotgap > MAXLABEL)
+	return 0;
+      else if (isascii((unsigned char)c) && iscntrl((unsigned char)c)) 
+	/* iscntrl only gives expected results for ascii */
+	return 0;
+#if !defined(HAVE_IDN) && !defined(HAVE_LIBIDN2)
+      else if (!isascii((unsigned char)c))
+	return 0;
+#endif
+      else if (c != ' ')
+	{
+	  nowhite = 1;
+	  if (c == '_')
+	    hasuscore = 1;
+	}
+    }
+
+  if (!nowhite)
+    return 0;
+
+  return hasuscore ? 2 : 1;
+}
+
+/* Hostnames have a more limited valid charset than domain names
+   so check for legal char a-z A-Z 0-9 - _ 
+   Note that this may receive a FQDN, so only check the first label 
+   for the tighter criteria. */
+int legal_hostname(char *name)
+{
+  char c;
+  int first;
+
+  if (!check_name(name))
+    return 0;
+
+  for (first = 1; (c = *name); name++, first = 0)
+    /* check for legal char a-z A-Z 0-9 - _ . */
+    {
+      if ((c >= 'A' && c <= 'Z') ||
+	  (c >= 'a' && c <= 'z') ||
+	  (c >= '0' && c <= '9'))
+	continue;
+
+      if (!first && (c == '-' || c == '_'))
+	continue;
+      
+      /* end of hostname part */
+      if (c == '.')
+	return 1;
+      
+      return 0;
+    }
+  
+  return 1;
+}
+  
+char *canonicalise(char *in, int *nomem)
+{
+  char *ret = NULL;
+  int rc;
+  
+  if (nomem)
+    *nomem = 0;
+  
+  if (!(rc = check_name(in)))
+    return NULL;
+  
+#if defined(HAVE_LIBIDN2) && (!defined(IDN2_VERSION_NUMBER) || IDN2_VERSION_NUMBER < 0x02000003)
+  /* older libidn2 strips underscores, so don't do IDN processing
+     if the name has an underscore (check_name() returned 2) */
+  if (rc != 2)
+#endif
+#if defined(HAVE_IDN) || defined(HAVE_LIBIDN2)
+    {
+#  ifdef HAVE_LIBIDN2
+      rc = idn2_to_ascii_lz(in, &ret, IDN2_NONTRANSITIONAL);
+      if (rc == IDN2_DISALLOWED)
+	rc = idn2_to_ascii_lz(in, &ret, IDN2_TRANSITIONAL);
+#  else
+      rc = idna_to_ascii_lz(in, &ret, 0);
+#  endif
+      if (rc != IDNA_SUCCESS)
+	{
+	  if (ret)
+	    free(ret);
+	  
+	  if (nomem && (rc == IDNA_MALLOC_ERROR || rc == IDNA_DLOPEN_ERROR))
+	    {
+	      my_syslog(LOG_ERR, _("failed to allocate memory"));
+	      *nomem = 1;
+	    }
+	  
+	  return NULL;
+	}
+      
+      return ret;
+    }
+#endif
+  
+  if ((ret = whine_malloc(strlen(in)+1)))
+    strcpy(ret, in);
+  else if (nomem)
+    *nomem = 1;    
+
+  return ret;
+}
+
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
+{
+  int j;
+  
+  while (sval && *sval)
+    {
+      unsigned char *cp = p++;
+
+      if (limit && p > (unsigned char*)limit)
+        return NULL;
+
+      for (j = 0; *sval && (*sval != '.'); sval++, j++)
+	{
+          if (limit && p + 1 > (unsigned char*)limit)
+            return NULL;
+
+#ifdef HAVE_DNSSEC
+	  if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
+	    *p++ = (*(++sval))-1;
+	  else
+#endif		
+	    *p++ = *sval;
+	}
+      
+      *cp  = j;
+      if (*sval)
+	sval++;
+    }
+  
+  return p;
+}
+
+/* for use during startup */
+void *safe_malloc(size_t size)
+{
+  void *ret = calloc(1, size);
+  
+  if (!ret)
+    die(_("could not get memory"), NULL, EC_NOMEM);
+      
+  return ret;
+}
+
+/* Ensure limited size string is always terminated.
+ * Can be replaced by (void)strlcpy() on some platforms */
+void safe_strncpy(char *dest, const char *src, size_t size)
+{
+  if (size != 0)
+    {
+      dest[size-1] = '\0';
+      strncpy(dest, src, size-1);
+    }
+}
+
+void safe_pipe(int *fd, int read_noblock)
+{
+  if (pipe(fd) == -1 || 
+      !fix_fd(fd[1]) ||
+      (read_noblock && !fix_fd(fd[0])))
+    die(_("cannot create pipe: %s"), NULL, EC_MISC);
+}
+
+void *whine_malloc(size_t size)
+{
+  void *ret = calloc(1, size);
+
+  if (!ret)
+    my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int) size);
+  
+  return ret;
+}
+
+int sockaddr_isequal(const union mysockaddr *s1, const union mysockaddr *s2)
+{
+  if (s1->sa.sa_family == s2->sa.sa_family)
+    { 
+      if (s1->sa.sa_family == AF_INET &&
+	  s1->in.sin_port == s2->in.sin_port &&
+	  s1->in.sin_addr.s_addr == s2->in.sin_addr.s_addr)
+	return 1;
+      
+      if (s1->sa.sa_family == AF_INET6 &&
+	  s1->in6.sin6_port == s2->in6.sin6_port &&
+	  s1->in6.sin6_scope_id == s2->in6.sin6_scope_id &&
+	  IN6_ARE_ADDR_EQUAL(&s1->in6.sin6_addr, &s2->in6.sin6_addr))
+	return 1;
+    }
+  return 0;
+}
+
+int sa_len(union mysockaddr *addr)
+{
+#ifdef HAVE_SOCKADDR_SA_LEN
+  return addr->sa.sa_len;
+#else
+  if (addr->sa.sa_family == AF_INET6)
+    return sizeof(addr->in6);
+  else
+    return sizeof(addr->in); 
+#endif
+}
+
+/* don't use strcasecmp and friends here - they may be messed up by LOCALE */
+int hostname_isequal(const char *a, const char *b)
+{
+  unsigned int c1, c2;
+  
+  do {
+    c1 = (unsigned char) *a++;
+    c2 = (unsigned char) *b++;
+    
+    if (c1 >= 'A' && c1 <= 'Z')
+      c1 += 'a' - 'A';
+    if (c2 >= 'A' && c2 <= 'Z')
+      c2 += 'a' - 'A';
+    
+    if (c1 != c2)
+      return 0;
+  } while (c1);
+  
+  return 1;
+}
+
+/* is b equal to or a subdomain of a return 2 for equal, 1 for subdomain */
+int hostname_issubdomain(char *a, char *b)
+{
+  char *ap, *bp;
+  unsigned int c1, c2;
+  
+  /* move to the end */
+  for (ap = a; *ap; ap++); 
+  for (bp = b; *bp; bp++);
+
+  /* a shorter than b or a empty. */
+  if ((bp - b) < (ap - a) || ap == a)
+    return 0;
+
+  do
+    {
+      c1 = (unsigned char) *(--ap);
+      c2 = (unsigned char) *(--bp);
+  
+       if (c1 >= 'A' && c1 <= 'Z')
+	 c1 += 'a' - 'A';
+       if (c2 >= 'A' && c2 <= 'Z')
+	 c2 += 'a' - 'A';
+
+       if (c1 != c2)
+	 return 0;
+    } while (ap != a);
+
+  if (bp == b)
+    return 2;
+
+  if (*(--bp) == '.')
+    return 1;
+
+  return 0;
+}
+ 
+  
+time_t dnsmasq_time(void)
+{
+#ifdef HAVE_BROKEN_RTC
+  struct tms dummy;
+  static long tps = 0;
+
+  if (tps == 0)
+    tps = sysconf(_SC_CLK_TCK);
+
+  return (time_t)(times(&dummy)/tps);
+#else
+  return time(NULL);
+#endif
+}
+
+int netmask_length(struct in_addr mask)
+{
+  int zero_count = 0;
+
+  while (0x0 == (mask.s_addr & 0x1) && zero_count < 32) 
+    {
+      mask.s_addr >>= 1;
+      zero_count++;
+    }
+  
+  return 32 - zero_count;
+}
+
+int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask)
+{
+  return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr);
+}
+
+int is_same_net_prefix(struct in_addr a, struct in_addr b, int prefix)
+{
+  struct in_addr mask;
+
+  mask.s_addr = htonl(~((1 << (32 - prefix)) - 1));
+
+  return is_same_net(a, b, mask);
+}
+
+
+int is_same_net6(struct in6_addr *a, struct in6_addr *b, int prefixlen)
+{
+  int pfbytes = prefixlen >> 3;
+  int pfbits = prefixlen & 7;
+
+  if (memcmp(&a->s6_addr, &b->s6_addr, pfbytes) != 0)
+    return 0;
+
+  if (pfbits == 0 ||
+      (a->s6_addr[pfbytes] >> (8 - pfbits) == b->s6_addr[pfbytes] >> (8 - pfbits)))
+    return 1;
+
+  return 0;
+}
+
+/* return least significant 64 bits if IPv6 address */
+u64 addr6part(struct in6_addr *addr)
+{
+  int i;
+  u64 ret = 0;
+
+  for (i = 8; i < 16; i++)
+    ret = (ret << 8) + addr->s6_addr[i];
+
+  return ret;
+}
+
+void setaddr6part(struct in6_addr *addr, u64 host)
+{
+  int i;
+
+  for (i = 15; i >= 8; i--)
+    {
+      addr->s6_addr[i] = host;
+      host = host >> 8;
+    }
+}
+
+
+/* returns port number from address */
+int prettyprint_addr(union mysockaddr *addr, char *buf)
+{
+  int port = 0;
+  
+  if (addr->sa.sa_family == AF_INET)
+    {
+      inet_ntop(AF_INET, &addr->in.sin_addr, buf, ADDRSTRLEN);
+      port = ntohs(addr->in.sin_port);
+    }
+  else if (addr->sa.sa_family == AF_INET6)
+    {
+      char name[IF_NAMESIZE];
+      inet_ntop(AF_INET6, &addr->in6.sin6_addr, buf, ADDRSTRLEN);
+      if (addr->in6.sin6_scope_id != 0 &&
+	  if_indextoname(addr->in6.sin6_scope_id, name) &&
+	  strlen(buf) + strlen(name) + 2 <= ADDRSTRLEN)
+	{
+	  strcat(buf, "%");
+	  strcat(buf, name);
+	}
+      port = ntohs(addr->in6.sin6_port);
+    }
+  
+  return port;
+}
+
+void prettyprint_time(char *buf, unsigned int t)
+{
+  if (t == 0xffffffff)
+    sprintf(buf, _("infinite"));
+  else
+    {
+      unsigned int x, p = 0;
+       if ((x = t/86400))
+	p += sprintf(&buf[p], "%ud", x);
+       if ((x = (t/3600)%24))
+	p += sprintf(&buf[p], "%uh", x);
+      if ((x = (t/60)%60))
+	p += sprintf(&buf[p], "%um", x);
+      if ((x = t%60))
+	p += sprintf(&buf[p], "%us", x);
+    }
+}
+
+
+/* in may equal out, when maxlen may be -1 (No max len). 
+   Return -1 for extraneous no-hex chars found. */
+int parse_hex(char *in, unsigned char *out, int maxlen, 
+	      unsigned int *wildcard_mask, int *mac_type)
+{
+  int done = 0, mask = 0, i = 0;
+  char *r;
+    
+  if (mac_type)
+    *mac_type = 0;
+  
+  while (!done && (maxlen == -1 || i < maxlen))
+    {
+      for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++)
+	if (*r != '*' && !isxdigit((unsigned char)*r))
+	  return -1;
+      
+      if (*r == 0)
+	done = 1;
+      
+      if (r != in )
+	{
+	  if (*r == '-' && i == 0 && mac_type)
+	   {
+	      *r = 0;
+	      *mac_type = strtol(in, NULL, 16);
+	      mac_type = NULL;
+	   }
+	  else
+	    {
+	      *r = 0;
+	      if (strcmp(in, "*") == 0)
+		{
+		  mask = (mask << 1) | 1;
+		  i++;
+		}
+	      else
+		{
+		  int j, bytes = (1 + (r - in))/2;
+		  for (j = 0; j < bytes; j++)
+		    { 
+		      char sav = sav;
+		      if (j < bytes - 1)
+			{
+			  sav = in[(j+1)*2];
+			  in[(j+1)*2] = 0;
+			}
+		      /* checks above allow mix of hexdigit and *, which
+			 is illegal. */
+		      if (strchr(&in[j*2], '*'))
+			return -1;
+		      out[i] = strtol(&in[j*2], NULL, 16);
+		      mask = mask << 1;
+		      if (++i == maxlen)
+			break; 
+		      if (j < bytes - 1)
+			in[(j+1)*2] = sav;
+		    }
+		}
+	    }
+	}
+      in = r+1;
+    }
+  
+  if (wildcard_mask)
+    *wildcard_mask = mask;
+
+  return i;
+}
+
+/* return 0 for no match, or (no matched octets) + 1 */
+int memcmp_masked(unsigned char *a, unsigned char *b, int len, unsigned int mask)
+{
+  int i, count;
+  for (count = 1, i = len - 1; i >= 0; i--, mask = mask >> 1)
+    if (!(mask & 1))
+      {
+	if (a[i] == b[i])
+	  count++;
+	else
+	  return 0;
+      }
+  return count;
+}
+
+/* _note_ may copy buffer */
+int expand_buf(struct iovec *iov, size_t size)
+{
+  void *new;
+
+  if (size <= (size_t)iov->iov_len)
+    return 1;
+
+  if (!(new = whine_malloc(size)))
+    {
+      errno = ENOMEM;
+      return 0;
+    }
+
+  if (iov->iov_base)
+    {
+      memcpy(new, iov->iov_base, iov->iov_len);
+      free(iov->iov_base);
+    }
+
+  iov->iov_base = new;
+  iov->iov_len = size;
+
+  return 1;
+}
+
+char *print_mac(char *buff, unsigned char *mac, int len)
+{
+  char *p = buff;
+  int i;
+   
+  if (len == 0)
+    sprintf(p, "<null>");
+  else
+    for (i = 0; i < len; i++)
+      p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
+  
+  return buff;
+}
+
+/* rc is return from sendto and friends.
+   Return 1 if we should retry.
+   Set errno to zero if we succeeded. */
+int retry_send(ssize_t rc)
+{
+  static int retries = 0;
+  struct timespec waiter;
+  
+  if (rc != -1)
+    {
+      retries = 0;
+      errno = 0;
+      return 0;
+    }
+  
+  /* Linux kernels can return EAGAIN in perpetuity when calling
+     sendmsg() and the relevant interface has gone. Here we loop
+     retrying in EAGAIN for 1 second max, to avoid this hanging 
+     dnsmasq. */
+
+  if (errno == EAGAIN || errno == EWOULDBLOCK)
+     {
+       waiter.tv_sec = 0;
+       waiter.tv_nsec = 10000;
+       nanosleep(&waiter, NULL);
+       if (retries++ < 1000)
+	 return 1;
+     }
+  
+  retries = 0;
+  
+  if (errno == EINTR)
+    return 1;
+  
+  return 0;
+}
+
+int read_write(int fd, unsigned char *packet, int size, int rw)
+{
+  ssize_t n, done;
+  
+  for (done = 0; done < size; done += n)
+    {
+      do { 
+	if (rw)
+	  n = read(fd, &packet[done], (size_t)(size - done));
+	else
+	  n = write(fd, &packet[done], (size_t)(size - done));
+	
+	if (n == 0)
+	  return 0;
+	
+      } while (retry_send(n) || errno == ENOMEM || errno == ENOBUFS);
+
+      if (errno != 0)
+	return 0;
+    }
+     
+  return 1;
+}
+
+/* close all fds except STDIN, STDOUT and STDERR, spare1, spare2 and spare3 */
+void close_fds(long max_fd, int spare1, int spare2, int spare3) 
+{
+  /* On Linux, use the /proc/ filesystem to find which files
+     are actually open, rather than iterate over the whole space,
+     for efficiency reasons. If this fails we drop back to the dumb code. */
+#ifdef HAVE_LINUX_NETWORK 
+  DIR *d;
+  
+  if ((d = opendir("/proc/self/fd")))
+    {
+      struct dirent *de;
+
+      while ((de = readdir(d)))
+	{
+	  long fd;
+	  char *e = NULL;
+	  
+	  errno = 0;
+	  fd = strtol(de->d_name, &e, 10);
+	  	  
+      	  if (errno != 0 || !e || *e || fd == dirfd(d) ||
+	      fd == STDOUT_FILENO || fd == STDERR_FILENO || fd == STDIN_FILENO ||
+	      fd == spare1 || fd == spare2 || fd == spare3)
+	    continue;
+	  
+	  close(fd);
+	}
+      
+      closedir(d);
+      return;
+  }
+#endif
+
+  /* fallback, dumb code. */
+  for (max_fd--; max_fd >= 0; max_fd--)
+    if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO && max_fd != STDIN_FILENO &&
+	max_fd != spare1 && max_fd != spare2 && max_fd != spare3)
+      close(max_fd);
+}
+
+/* Basically match a string value against a wildcard pattern.  */
+int wildcard_match(const char* wildcard, const char* match)
+{
+  while (*wildcard && *match)
+    {
+      if (*wildcard == '*')
+        return 1;
+
+      if (*wildcard != *match)
+        return 0; 
+
+      ++wildcard;
+      ++match;
+    }
+
+  return *wildcard == *match;
+}
+
+/* The same but comparing a maximum of NUM characters, like strncmp.  */
+int wildcard_matchn(const char* wildcard, const char* match, int num)
+{
+  while (*wildcard && *match && num)
+    {
+      if (*wildcard == '*')
+        return 1;
+
+      if (*wildcard != *match)
+        return 0; 
+
+      ++wildcard;
+      ++match;
+      --num;
+    }
+
+  return (!num) || (*wildcard == *match);
+}
+
+#ifdef HAVE_LINUX_NETWORK
+int kernel_version(void)
+{
+  struct utsname utsname;
+  int version;
+  char *split;
+  
+  if (uname(&utsname) < 0)
+    die(_("failed to find kernel version: %s"), NULL, EC_MISC);
+  
+  split = strtok(utsname.release, ".");
+  version = (split ? atoi(split) : 0);
+  split = strtok(NULL, ".");
+  version = version * 256 + (split ? atoi(split) : 0);
+  split = strtok(NULL, ".");
+  return version * 256 + (split ? atoi(split) : 0);
+}
+#endif