Rebase Cradlepoint diff over v2.90

Run these commands only if remote upstream is not added:
  1. git remote add upstream http://thekelleys.org.uk/git/dnsmasq.git
  2. git remote -v show
  3. git fetch upstream

Following are the commands used for generating the diff
between upstream version 2.85 and origin/cp-main:
  1. git diff v2.85 origin/cp-main > ../dnsmasq_v2.85-to-cp_main.gitdiff
  2. patch -p1 < ../dnsmasq_v2.85-to-cp_main.gitdiff
  3. git add . && git commit -m "Rebase Cradlepoint diff over v2.90"
And then fix all the rejects generated while applying ‘patch -p1 < ../dnsmasq_v2.85-to-cp_main.gitdiff’

EDNS_PKTSZ and SAFE_PKTSZ were already present due to backport of CVE
patch:
    Fix up ./man/dnsmasq.8
    Fix up ./src/config.h
    Fix up ./CHANGELOG

As domain is no longer used in upstream, we removed the check for OPT_EDNS_RESTRICT:
    Fix up ./src/forward.c

Fix up ./src/dhcp.c
Fix up ./src/dhcp6.c
Fix up ./src/dns-protocol.h
Fix up ./src/dnsmasq.c
Fix up ./src/dnsmasq.h
Fix up ./src/edns0.c
Fix up ./src/option.c
Fix up ./src/radv.c
Fix up ./src/rfc1035.c
Fix up ./src/util.c
Fix up ./Makefile
Compilation fixes for cache.c

Change-Id: Ibb8d162526168e5963aedb98b6333ded5f054a82
diff --git a/src/cache.c b/src/cache.c
index 0eacec9..06ead08 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -1972,6 +1972,198 @@
     }
 }
 
+/* This function will return the app_ids, cat_ids along with their respective
+counts in the ipset for the host enquired */
+static void extract_appid_catid_fm_ipset(struct ipsets *ipset, char *host,
+                  long *app_ids, long *cat_ids, int *app_id_count,
+                  int *cat_id_count)
+{
+  char *setptr = NULL;
+  char *tempptr = NULL, *endptr = NULL;
+  int index = 0;
+  while (ipset)
+  {
+    if (strcmp(ipset->domain, host) == 0) {
+      index = 0;
+      setptr = ipset->sets[index];
+      while (setptr)
+      {
+        if (strncmp(setptr, "ip4-a", 5) == 0) {
+          tempptr = &setptr[5];
+          if (tempptr!= NULL && *app_id_count < MAX_APPID_PER_HOST) {
+            app_ids[(*app_id_count)] = strtoul(tempptr, &endptr, 16);
+            *app_id_count = *app_id_count + 1;
+          }
+        }
+        if (strncmp(setptr, "ip4-c", 5) == 0) {
+          tempptr = &setptr[5];
+          if (tempptr!= NULL && *cat_id_count < MAX_CATID_PER_HOST) {
+            cat_ids[(*cat_id_count)] = strtoul(tempptr, &endptr, 16);
+            *cat_id_count = *cat_id_count + 1;
+          }
+        }
+        index++;
+        setptr = ipset->sets[index];
+      }
+    }
+    ipset = ipset->next;
+  }
+}
+
+/* CRADLEPOINT */
+void dump_cache_json(time_t now)
+{
+  struct server *serv, *serv1;
+  int add_comma;
+  char *t = "";
+
+  // assume signaler has created this named pipe already with mkfifo
+  // If not it will end up being a simple file
+  FILE *f = fopen("/tmp/dns_cache.pipe", "w");
+
+  if (!f)
+    {
+      my_syslog(LOG_ERR, _("failed to open dns cache dump pipe"));
+      return;
+    }
+
+  // rather than pull in complex json libs, brute force it
+  fprintf(f, "{");
+  fprintf(f, "\"time\":  %lu,", (unsigned long)now);
+  fprintf(f, "\"size\": %d,", daemon->cachesize);
+  fprintf(f, "\"freed\": %d,", daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]);
+  fprintf(f, "\"inserted\": %d,", daemon->metrics[METRIC_DNS_CACHE_INSERTED]);
+  fprintf(f, "\"forwarded\": %d,", daemon->metrics[METRIC_DNS_QUERIES_FORWARDED]);
+  fprintf(f, "\"local\": %d,", daemon->metrics[METRIC_DNS_LOCAL_ANSWERED]);
+#ifdef HAVE_AUTH
+  fprintf(f, "\"authoritative\": %u,", daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+
+  /* sum counts from different records for same server */
+  for (serv = daemon->servers; serv; serv = serv->next)
+    serv->flags &= ~SERV_MARK;
+
+  fprintf(f, "\"servers\": [");
+  add_comma = 0;
+  for (serv = daemon->servers; serv; serv = serv->next)
+   if (!(serv->flags & (SERV_LITERAL_ADDRESS | SERV_MARK |
+                        SERV_USE_RESOLV)))
+      {
+        int port;
+        unsigned int queries = 0, failed_queries = 0;
+        for (serv1 = serv; serv1; serv1 = serv1->next)
+            if (!(serv1->flags &
+            (SERV_LITERAL_ADDRESS | SERV_MARK |
+             SERV_USE_RESOLV)) &&
+                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);
+        if (add_comma)
+          fprintf(f, ",");
+        else
+          add_comma = 1;
+        fprintf(f, "{\"addr\": \"%s\", \"port\": %d, \"queries\": %u, \
+                   \"failed\": %u}", daemon->addrbuff, port, queries, failed_queries);
+      }
+  fprintf(f, "], \"entries\": [");
+
+  struct crec *cache ;
+  int i;
+
+  add_comma = 0;
+  for (i=0; i<hash_size; i++)
+  {
+    for (cache = hash_table[i]; cache; cache = cache->hash_next)
+    {
+      char *a = daemon->addrbuff, *n = cache_get_name(cache);
+      *a = 0;
+
+      if (add_comma)
+        fprintf(f, ",");
+      else
+        add_comma = 1;
+
+      if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
+        n = "<Root>";
+      fprintf(f, "{\"host\": \"%s\",", sanitise(n));
+
+      if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
+        a = sanitise(cache_get_cname_target(cache));
+#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.addr4, a, ADDRSTRLEN);
+        else if (cache->flags & F_IPV6)
+          inet_ntop(AF_INET6, &cache->addr.addr6, a, ADDRSTRLEN);
+      }
+      fprintf(f, "\"addr\": \"%s\",", a);
+
+      if (cache->flags & F_IPV4)
+        t = "4";
+      else if (cache->flags & F_IPV6)
+        t = "6";
+      else if (cache->flags & F_CNAME)
+        t = "C";
+#ifdef HAVE_DNSSEC
+      else if (cache->flags & F_DS)
+        t = "S";
+      else if (cache->flags & F_DNSKEY)
+        t = "K";
+#endif
+      fprintf(f, "\"type\": \"%s\",", t);
+      fprintf(f, "\"flags\": \"%s%s%s%s%s%s%s%s\",",
+        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" : "");
+      fprintf(f, "\"expires\": %lu", (unsigned long)(cache->ttd - now));
+      long app_id[MAX_APPID_PER_HOST], cat_id[MAX_CATID_PER_HOST];
+      int app_id_count = 0, cat_id_count = 0;
+      int index = 0;
+      extract_appid_catid_fm_ipset(daemon->ipsets, n, app_id, cat_id,
+                                    &app_id_count, &cat_id_count);
+      if (app_id_count) {
+        fprintf(f, ",\"appid\": [");
+        for (index = 0; index < app_id_count - 1; index++) {
+          fprintf(f, "%ld,", app_id[index]);
+        }
+        fprintf(f, "%ld]", app_id[index]);
+      }
+      if (cat_id_count) {
+        fprintf(f, ",\"catid\": [");
+        for (index = 0; index < cat_id_count - 1; index++) {
+          fprintf(f, "%ld,", cat_id[index]);
+        }
+        fprintf(f, "%ld]", cat_id[index]);
+      }
+      fprintf(f, "}");
+    }
+  }
+  fprintf(f, "]}\n");
+  fclose(f);
+}
+/* CRADLEPOINT */
+
 char *record_source(unsigned int index)
 {
   struct hostsfile *ah;
diff --git a/src/cache.c.orig b/src/cache.c.orig
new file mode 100644
index 0000000..0eacec9
--- /dev/null
+++ b/src/cache.c.orig
@@ -0,0 +1,2254 @@
+/* dnsmasq is Copyright (c) 2000-2024 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);
+static void dump_cache_entry(struct crec *cache, time_t now);
+static char *querystr(char *desc, unsigned short type);
+
+/* type->string mapping: this is also used by the name-hash function as a mixing table. */
+/* taken from https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml */
+static const struct {
+  unsigned int type;
+  const char * const name;
+} typestr[] = {
+  { 1,   "A" }, /* a host address [RFC1035] */
+  { 2,   "NS" }, /* an authoritative name server [RFC1035] */
+  { 3,   "MD" }, /* a mail destination (OBSOLETE - use MX) [RFC1035] */
+  { 4,   "MF" }, /* a mail forwarder (OBSOLETE - use MX) [RFC1035] */
+  { 5,   "CNAME" }, /* the canonical name for an alias [RFC1035] */
+  { 6,   "SOA" }, /* marks the start of a zone of authority [RFC1035] */
+  { 7,   "MB" }, /* a mailbox domain name (EXPERIMENTAL) [RFC1035] */
+  { 8,   "MG" }, /* a mail group member (EXPERIMENTAL) [RFC1035] */
+  { 9,   "MR" }, /* a mail rename domain name (EXPERIMENTAL) [RFC1035] */
+  { 10,  "NULL" }, /* a null RR (EXPERIMENTAL) [RFC1035] */
+  { 11,  "WKS" }, /* a well known service description [RFC1035] */
+  { 12,  "PTR" }, /* a domain name pointer [RFC1035] */
+  { 13,  "HINFO" }, /* host information [RFC1035] */
+  { 14,  "MINFO" }, /* mailbox or mail list information [RFC1035] */
+  { 15,  "MX" }, /* mail exchange [RFC1035] */
+  { 16,  "TXT" }, /* text strings [RFC1035] */
+  { 17,  "RP" }, /* for Responsible Person [RFC1183] */
+  { 18,  "AFSDB" }, /* for AFS Data Base location [RFC1183][RFC5864] */
+  { 19,  "X25" }, /* for X.25 PSDN address [RFC1183] */
+  { 20,  "ISDN" }, /* for ISDN address [RFC1183] */
+  { 21,  "RT" }, /* for Route Through [RFC1183] */
+  { 22,  "NSAP" }, /* for NSAP address, NSAP style A record [RFC1706] */
+  { 23,  "NSAP_PTR" }, /* for domain name pointer, NSAP style [RFC1348][RFC1637][RFC1706] */
+  { 24,  "SIG" }, /* for security signature [RFC2535][RFC2536][RFC2537][RFC2931][RFC3008][RFC3110][RFC3755][RFC4034] */
+  { 25,  "KEY" }, /* for security key [RFC2535][RFC2536][RFC2537][RFC2539][RFC3008][RFC3110][RFC3755][RFC4034] */
+  { 26,  "PX" }, /* X.400 mail mapping information [RFC2163] */
+  { 27,  "GPOS" }, /* Geographical Position [RFC1712] */
+  { 28,  "AAAA" }, /* IP6 Address [RFC3596] */
+  { 29,  "LOC" }, /* Location Information [RFC1876] */
+  { 30,  "NXT" }, /* Next Domain (OBSOLETE) [RFC2535][RFC3755] */
+  { 31,  "EID" }, /* Endpoint Identifier [Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt] 1995-06*/
+  { 32,  "NIMLOC" }, /* Nimrod Locator [1][Michael_Patton][http://ana-3.lcs.mit.edu/~jnc/nimrod/dns.txt] 1995-06*/
+  { 33,  "SRV" }, /* Server Selection [1][RFC2782] */
+  { 34,  "ATMA" }, /* ATM Address [ ATM Forum Technical Committee, "ATM Name System, V2.0", Doc ID: AF-DANS-0152.000, July 2000. Available from and held in escrow by IANA.] */
+  { 35,  "NAPTR" }, /* Naming Authority Pointer [RFC2168][RFC2915][RFC3403] */
+  { 36,  "KX" }, /* Key Exchanger [RFC2230] */
+  { 37,  "CERT" }, /* CERT [RFC4398] */
+  { 38,  "A6" }, /* A6 (OBSOLETE - use AAAA) [RFC2874][RFC3226][RFC6563] */
+  { 39,  "DNAME" }, /* DNAME [RFC6672] */
+  { 40,  "SINK" }, /* SINK [Donald_E_Eastlake][http://tools.ietf.org/html/draft-eastlake-kitchen-sink] 1997-11*/
+  { 41,  "OPT" }, /* OPT [RFC3225][RFC6891] */
+  { 42,  "APL" }, /* APL [RFC3123] */
+  { 43,  "DS" }, /* Delegation Signer [RFC3658][RFC4034] */
+  { 44,  "SSHFP" }, /* SSH Key Fingerprint [RFC4255] */
+  { 45,  "IPSECKEY" }, /* IPSECKEY [RFC4025] */
+  { 46,  "RRSIG" }, /* RRSIG [RFC3755][RFC4034] */
+  { 47,  "NSEC" }, /* NSEC [RFC3755][RFC4034][RFC9077] */
+  { 48,  "DNSKEY" }, /* DNSKEY [RFC3755][RFC4034] */
+  { 49,  "DHCID" }, /* DHCID [RFC4701] */
+  { 50,  "NSEC3" }, /* NSEC3 [RFC5155][RFC9077] */
+  { 51,  "NSEC3PARAM" }, /* NSEC3PARAM [RFC5155] */
+  { 52,  "TLSA" }, /* TLSA [RFC6698] */
+  { 53,  "SMIMEA" }, /* S/MIME cert association [RFC8162] SMIMEA/smimea-completed-template 2015-12-01*/
+  { 55,  "HIP" }, /* Host Identity Protocol [RFC8005] */
+  { 56,  "NINFO" }, /* NINFO [Jim_Reid] NINFO/ninfo-completed-template 2008-01-21*/
+  { 57,  "RKEY" }, /* RKEY [Jim_Reid] RKEY/rkey-completed-template 2008-01-21*/
+  { 58,  "TALINK" }, /* Trust Anchor LINK [Wouter_Wijngaards] TALINK/talink-completed-template 2010-02-17*/
+  { 59,  "CDS" }, /* Child DS [RFC7344] CDS/cds-completed-template 2011-06-06*/
+  { 60,  "CDNSKEY" }, /* DNSKEY(s) the Child wants reflected in DS [RFC7344] 2014-06-16*/
+  { 61,  "OPENPGPKEY" }, /* OpenPGP Key [RFC7929] OPENPGPKEY/openpgpkey-completed-template 2014-08-12*/
+  { 62,  "CSYNC" }, /* Child-To-Parent Synchronization [RFC7477] 2015-01-27*/
+  { 63,  "ZONEMD" }, /* Message Digest Over Zone Data [RFC8976] ZONEMD/zonemd-completed-template 2018-12-12*/
+  { 64,  "SVCB" }, /* Service Binding [draft-ietf-dnsop-svcb-https-00] SVCB/svcb-completed-template 2020-06-30*/
+  { 65,  "HTTPS" }, /* HTTPS Binding [draft-ietf-dnsop-svcb-https-00] HTTPS/https-completed-template 2020-06-30*/
+  { 99,  "SPF" }, /* [RFC7208] */
+  { 100, "UINFO" }, /* [IANA-Reserved] */
+  { 101, "UID" }, /* [IANA-Reserved] */
+  { 102, "GID" }, /* [IANA-Reserved] */
+  { 103, "UNSPEC" }, /* [IANA-Reserved] */
+  { 104, "NID" }, /* [RFC6742] ILNP/nid-completed-template */
+  { 105, "L32" }, /* [RFC6742] ILNP/l32-completed-template */
+  { 106, "L64" }, /* [RFC6742] ILNP/l64-completed-template */
+  { 107, "LP" }, /* [RFC6742] ILNP/lp-completed-template */
+  { 108, "EUI48" }, /* an EUI-48 address [RFC7043] EUI48/eui48-completed-template 2013-03-27*/
+  { 109, "EUI64" }, /* an EUI-64 address [RFC7043] EUI64/eui64-completed-template 2013-03-27*/
+  { 249, "TKEY" }, /* Transaction Key [RFC2930] */
+  { 250, "TSIG" }, /* Transaction Signature [RFC8945] */
+  { 251, "IXFR" }, /* incremental transfer [RFC1995] */
+  { 252, "AXFR" }, /* transfer of an entire zone [RFC1035][RFC5936] */
+  { 253, "MAILB" }, /* mailbox-related RRs (MB, MG or MR) [RFC1035] */
+  { 254, "MAILA" }, /* mail agent RRs (OBSOLETE - see MX) [RFC1035] */
+  { 255, "ANY" }, /* A request for some or all records the server has available [RFC1035][RFC6895][RFC8482] */
+  { 256, "URI" }, /* URI [RFC7553] URI/uri-completed-template 2011-02-22*/
+  { 257, "CAA" }, /* Certification Authority Restriction [RFC8659] CAA/caa-completed-template 2011-04-07*/
+  { 258, "AVC" }, /* Application Visibility and Control [Wolfgang_Riedel] AVC/avc-completed-template 2016-02-26*/
+  { 259, "DOA" }, /* Digital Object Architecture [draft-durand-doa-over-dns] DOA/doa-completed-template 2017-08-30*/
+  { 260, "AMTRELAY" }, /* Automatic Multicast Tunneling Relay [RFC8777] AMTRELAY/amtrelay-completed-template 2019-02-06*/
+  { 261, "RESINFO" }, /* Resolver Information as Key/Value Pairs https://datatracker.ietf.org/doc/draft-ietf-add-resolver-info/06/ */
+  { 32768,  "TA" }, /* DNSSEC Trust Authorities [Sam_Weiler][http://cameo.library.cmu.edu/][ Deploying DNSSEC Without a Signed Root. Technical Report 1999-19, Information Networking Institute, Carnegie Mellon University, April 2004.] 2005-12-13*/
+  { 32769,  "DLV" }, /* DNSSEC Lookaside Validation (OBSOLETE) [RFC8749][RFC4431] */
+};
+
+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);
+
+unsigned short rrtype(char *in)
+{
+  unsigned int i;
+  
+  for (i = 0; i < (sizeof(typestr)/sizeof(typestr[0])); i++)
+    if (strcasecmp(in, typestr[i].name) == 0)
+      return typestr[i].type;
+
+  return 0;
+}
+
+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 */
+
+  char *name = cache_get_name(crecp);
+  struct crec **up = hash_bucket(name);
+  unsigned int flags = crecp->flags & (F_IMMORTAL | F_REVERSE);
+  
+  if (!(flags & F_REVERSE))
+    {
+      while (*up && ((*up)->flags & F_REVERSE))
+	up = &((*up)->hash_next); 
+      
+      if (flags & F_IMMORTAL)
+	while (*up && !((*up)->flags & F_IMMORTAL))
+	  up = &((*up)->hash_next);
+    }
+
+  /* Preserve order when inserting the same name multiple times.
+     Do not mess up the flag invariants. */
+  while (*up &&
+	 hostname_isequal(cache_get_name(*up), name) &&
+	 flags == ((*up)->flags & (F_IMMORTAL | F_REVERSE)))
+    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_RR) && (crecp->flags & F_KEYTAG))
+	blockdata_free(crecp->addr.rrblock.rrdata);
+#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)
+{
+  /* Don't dump expired entries if they are within the accepted timeout range.
+     The cache becomes approx. LRU. Never use expired DS or DNSKEY entries.
+     Possible values for daemon->cache_max_expiry:
+      -1  == serve cached content regardless how long ago it expired
+       0  == the option is disabled, expired content isn't served
+      <n> == serve cached content only if it expire less than <n> seconds
+             ago (where n is a positive integer) */
+  if (daemon->cache_max_expiry != 0 &&
+      (daemon->cache_max_expiry == -1 ||
+       difftime(now, crecp->ttd) < daemon->cache_max_expiry) &&
+      !(crecp->flags & (F_DS | F_DNSKEY)))
+    return 0;
+
+  if (crecp->flags & F_IMMORTAL)
+    return 0;
+
+  if (difftime(now, crecp->ttd) < 0)
+    return 0;
+  
+  return 1;
+}
+
+/* Remove entries with a given UID from the cache */
+unsigned int cache_remove_uid(const unsigned int uid)
+{
+  int i;
+  unsigned int removed = 0;
+  struct crec *crecp, *tmp, **up;
+
+  for (i = 0; i < hash_size; i++)
+    for (crecp = hash_table[i], up = &hash_table[i]; crecp; crecp = tmp)
+      {
+	tmp = crecp->hash_next;
+	if ((crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG)) && crecp->uid == uid)
+	  {
+	    *up = tmp;
+	    free(crecp);
+	    removed++;
+	  }
+	else
+	  up = &crecp->hash_next;
+      }
+  
+  return removed;
+}
+
+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))
+	    {
+	      int rrmatch = 0;
+	      if (crecp->flags & flags & F_RR)
+		{
+		  unsigned short rrc = (crecp->flags & F_KEYTAG) ? crecp->addr.rrblock.rrtype : crecp->addr.rrdata.rrtype;
+		  unsigned short rra = (flags & F_KEYTAG) ? addr->rrblock.rrtype : addr->rrdata.rrtype;
+
+		  if (rrc == rra)
+		    rrmatch = 1;
+		}
+
+	      /* Don't delete DNSSEC in favour of a CNAME, they can co-exist */
+	      if ((flags & crecp->flags & (F_IPV4 | F_IPV6 | F_NXDOMAIN)) || 
+		  (((crecp->flags | flags) & F_CNAME) && !(crecp->flags & (F_DNSKEY | F_DS))) ||
+		  rrmatch)
+		{
+		  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)) &&
+		   addr && 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);
+  struct crec *free_avail = NULL;
+  unsigned int target_uid;
+  
+  /* if previous insertion failed give up now. */
+  if (insert_error)
+    return NULL;
+
+  /* we don't cache zero-TTL records unless we're doing stale-caching. */
+  if (daemon->cache_max_expiry == 0 && 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)
+	{
+	  my_syslog(LOG_ERR, _("Internal error in cache."));
+	  /* Log the entry we tried to delete. */
+	  dump_cache_entry(free_avail, now);
+	  insert_error = 1;
+	  return NULL;
+	}
+      
+      if (freed_all)
+	{
+	  /* For DNSSEC records, uid holds class. */
+	  free_avail = new; /* Must be free space now. */
+	  
+	  /* condition valid when stale-caching */
+	  if (difftime(now, new->ttd) < 0)
+	    daemon->metrics[METRIC_DNS_CACHE_LIVE_FREED]++;
+	  
+	  cache_scan_free(cache_get_name(new), &new->addr, new->uid, now, new->flags, NULL, NULL); 
+	}
+      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);
+	      read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
+	      
+	      if (flags & F_RR)
+		{
+		  /* A negative RR entry is possible and has no data, obviously. */
+		  if (!(flags & F_NEG) && (flags & F_KEYTAG))
+		    blockdata_write(new_chain->addr.rrblock.rrdata, new_chain->addr.rrblock.datalen, 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);
+
+#ifdef HAVE_DNSSEC
+      /* Sneak out possibly updated crypto HWM values. */
+      m = daemon->metrics[METRIC_CRYPTO_HWM];
+      read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+      m = daemon->metrics[METRIC_SIG_FAIL_HWM];
+      read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+      m = daemon->metrics[METRIC_WORK_HWM];
+      read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
+#endif
+    }
+      
+  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)
+	{
+#ifdef HAVE_DNSSEC
+	  /* Sneak in possibly updated crypto HWM. */
+	  if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+	    return 0;
+	  if (m > daemon->metrics[METRIC_CRYPTO_HWM])
+	    daemon->metrics[METRIC_CRYPTO_HWM] = m;
+	  if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+	    return 0;
+	  if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
+	    daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
+	  if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
+	    return 0;
+	  if (m > daemon->metrics[METRIC_WORK_HWM])
+	    daemon->metrics[METRIC_WORK_HWM] = m;
+#endif
+	  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) ||
+	  !read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
+	return 0;
+
+      daemon->namebuff[m] = 0;
+
+      ttl = difftime(ttd, now);
+      
+      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;
+		}
+	    }
+	}
+      else
+	{
+	  unsigned short class = C_IN;
+
+	  if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
+	      && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
+	    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);
+	}
+    }
+}
+	
+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) || option_bool(OPT_NORR);
+
+  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)
+{
+  int i;
+  unsigned int j; 
+  struct crec *lookup = NULL;
+
+  /* Remove duplicates in hosts files. */
+  while ((lookup = cache_find_by_name(lookup, cache_get_name(cache), 0, cache->flags & (F_IPV4 | F_IPV6))))
+    if ((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 names_done = 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;
+	}
+      
+      /* 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++;
+		  names_done++;
+		}
+	      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++;
+		  names_done++;
+		}
+	      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 names"), filename, names_done);
+  
+  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;
+  
+  /* If no DNS service, cache not initialised. */
+  if (daemon->port != 0)
+    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_DNSKEY | F_DS | F_RR)) && 
+	  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_RR | F_DNSKEY | F_DS | F_REVERSE);
+	  if (!(crecp->flags & F_IMMORTAL))
+	    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_realloc(buff, newlen)))
+		  return 0;
+		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;
+}
+
+static void dump_cache_entry(struct crec *cache, time_t now)
+{
+  (void)now;
+  static char *buff = NULL;
+  
+  char *p, *t = " ";
+  char *a = daemon->addrbuff, *n = cache_get_name(cache);
+
+  /* String length is limited below */
+  if (!buff && !(buff = whine_malloc(150)))
+    return;
+  
+  p = buff;
+  
+  *a = 0;
+
+  if (cache->flags & F_REVERSE)
+    {
+      if ((cache->flags & F_NEG))
+	n = "";
+    }
+  else
+    {
+      if (strlen(n) == 0)
+	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_RR)
+    {
+      if (cache->flags & F_KEYTAG)
+	sprintf(a, "%s", querystr(NULL, cache->addr.rrblock.rrtype));
+      else
+	sprintf(a, "%s", querystr(NULL, cache->addr.rrdata.rrtype));
+    }
+#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_RR)
+    t = "T";
+#ifdef HAVE_DNSSEC
+  else if (cache->flags & F_DS)
+    t = "S";
+  else if (cache->flags & F_DNSKEY)
+    t = "K";
+#endif
+  else if (!(cache->flags & F_NXDOMAIN)) /* non-terminal */
+    t = "!";
+  
+  p += sprintf(p, "%-40.40s %s%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_CONFIG ? "C" : " ",
+	       cache->flags & F_DNSSECOK ? "V" : " ");
+#ifdef HAVE_BROKEN_RTC
+  p += sprintf(p, "%-24lu", cache->flags & F_IMMORTAL ? 0: (unsigned long)(cache->ttd - now));
+#else
+  p += sprintf(p, "%-24.24s", cache->flags & F_IMMORTAL ? "" : ctime(&(cache->ttd)));
+#endif
+  if(cache->flags & (F_HOSTS | F_CONFIG) && cache->uid > 0)
+    p += sprintf(p, " %-40.40s", record_source(cache->uid));
+  
+  my_syslog(LOG_INFO, "%s", buff);
+}
+
+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]);
+  if (daemon->cache_max_expiry != 0)
+    my_syslog(LOG_INFO, _("queries answered from stale cache %u"), daemon->metrics[METRIC_DNS_STALE_ANSWERED]);
+#ifdef HAVE_AUTH
+  my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
+#endif
+#ifdef HAVE_DNSSEC
+  my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]);
+  my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]);
+  my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]);
+#endif
+
+  blockdata_report();
+  my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
+	    daemon->metrics[METRIC_TCP_CONNECTIONS],
+	    daemon->max_procs_used,
+	    daemon->max_procs);
+  daemon->max_procs_used = daemon->metrics[METRIC_TCP_CONNECTIONS];
+  
+  /* 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, nxdomain_replies = 0, retrys = 0;
+	unsigned int sigma_latency = 0, count_latency = 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;
+	      nxdomain_replies += serv1->nxdomain_replies;
+	      retrys += serv1->retrys;
+	      sigma_latency += serv1->query_latency;
+	      count_latency++;
+	    }
+	port = prettyprint_addr(&serv->addr, daemon->addrbuff);
+	my_syslog(LOG_INFO, _("server %s#%d: queries sent %u, retried %u, failed %u, nxdomain replies %u, avg. latency %ums"),
+		  daemon->addrbuff, port, queries, retrys, failed_queries, nxdomain_replies, sigma_latency/count_latency);
+      }
+
+  if (option_bool(OPT_DEBUG) || option_bool(OPT_LOG))
+    {
+      struct crec *cache;
+      int i;
+      my_syslog(LOG_INFO, "Host                           Address                                  Flags      Expires                  Source");
+      my_syslog(LOG_INFO, "------------------------------ ---------------------------------------- ---------- ------------------------ ------------");
+    
+      for (i=0; i<hash_size; i++)
+	for (cache = hash_table[i]; cache; cache = cache->hash_next)
+	  dump_cache_entry(cache, now);
+    }
+}
+
+char *record_source(unsigned int index)
+{
+  struct hostsfile *ah;
+#ifdef HAVE_INOTIFY
+  struct dyndir *dd;
+#endif
+  
+  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
+  /* Dynamic directories contain multiple files */
+  for (dd = daemon->dynamic_dirs; dd; dd = dd->next)
+    for (ah = dd->files; ah; ah = ah->next)
+      if (ah->index == index)
+	return ah->fname;
+#endif
+
+  return "<unknown>";
+}
+
+static 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";
+    case EDE_SIG_E_B_V:                   return "signature expired before valid";
+    case EDE_TOO_EARLY:                   return "too early";
+    case EDE_UNS_NS3_ITER:                return "unsupported NSEC3 iterations value";
+    case EDE_UNABLE_POLICY:               return "uanble to conform to policy";
+    case EDE_SYNTHESIZED:                 return "synthesized";
+    default:                              return "unknown";
+    }
+}
+
+void log_query(unsigned int flags, char *name, union all_addr *addr, char *arg, unsigned short type)
+{
+  char *source, *dest;
+  char *verb = "is";
+  char *extra = "";
+  char *gap = " ";
+  char portstring[7]; /* space for #<portnum> */
+  
+  if (!option_bool(OPT_LOG))
+    return;
+
+  /* build query type string if requested */
+  if (!(flags & (F_SERVER | F_IPSET)) && type > 0)
+    arg = querystr(arg, type);
+
+  dest = arg;
+
+#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_RR)
+	 {
+	   if (flags & F_KEYTAG)
+	     dest = querystr(NULL, addr->rrblock.rrtype);
+	   else
+	     dest = querystr(NULL, addr->rrdata.rrtype);
+	 }
+       else 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);
+	  if ((flags & F_SERVER) && type != NAMESERVER_PORT)
+	    {
+	      extra = portstring;
+	      sprintf(portstring, "#%u", type);
+	    }
+	}
+      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_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_DNSSEC)
+    {
+      source = arg;
+      verb = "to";
+    }
+   else if (flags & F_SERVER)
+    {
+      source = "forwarded";
+      verb = "to";
+    }
+  else if (flags & F_QUERY)
+    {
+      source = arg;
+      verb = "from";
+    }
+  else if (flags & F_IPSET)
+    {
+      source = type ? "ipset add" : "nftset add";
+      dest = name;
+      name = arg;
+      verb = daemon->addrbuff;
+    }
+  else if (flags & F_STALE)
+    source = "cached-stale";
+  else
+    source = "cached";
+  
+  if (!name)
+    gap = name = "";
+  else if (!name[0])
+    name = ".";
+  
+  if (option_bool(OPT_EXTRALOG))
+    {
+      if (flags & F_NOEXTRA)
+	my_syslog(LOG_INFO, "%u %s %s%s%s %s%s", daemon->log_display_id, source, name, gap, 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%s", daemon->log_display_id, daemon->addrbuff2, port, source, name, gap, verb, dest, extra);
+	}
+    }
+  else
+    my_syslog(LOG_INFO, "%s %s%s%s %s%s", source, name, gap, verb, dest, extra);
+}
diff --git a/src/config.h b/src/config.h
index e722e98..41d9f66 100644
--- a/src/config.h
+++ b/src/config.h
@@ -179,14 +179,18 @@
 /* The default set of options to build. Built with these options, dnsmasq
    has no library dependencies other than libc */
 
+/* CRADLEPOINT */
+#define NO_LARGEFILE
+#define NO_CONNTRACK
+#define NO_TFTP
+#define NO_AUTH
+#define NO_INOTIFY
 #define HAVE_DHCP
 #define HAVE_DHCP6 
-#define HAVE_TFTP
 #define HAVE_SCRIPT
-#define HAVE_AUTH
 #define HAVE_IPSET 
 #define HAVE_LOOP
-#define HAVE_DUMPFILE
+/* CRADLEPOINT */
 
 /* Build options which require external libraries.
    
diff --git a/src/dhcp.c b/src/dhcp.c
index b65facd..3bbc7e8 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -283,6 +283,9 @@
 #ifdef HAVE_LINUX_NETWORK
       safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
 #endif 
+/* CRADLEPOINT */
+      parm.current = NULL;
+/* CRADLEPOINT */
     }
   else
     {
@@ -395,17 +398,21 @@
   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;
+/* CRADLEPOINT */
+      if (!parm.current || parm.current->forcedaddress.s_addr == 0)
+      {
+	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 = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+	cmptr->cmsg_level = IPPROTO_IP;
+	cmptr->cmsg_type = IP_PKTINFO;
+      }
+/* CRADLEPOINT */
 
       if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 ||
          mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0)
@@ -476,6 +483,30 @@
   setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index));
 #endif
 
+/* CRADLEPOINT */
+#ifdef HAVE_LINUX_NETWORK
+  /* if we're forcing the address, use IP_PKTINFO and IP_TRANSPARENT to lie about
+   * our source address and force it out the same iface we got it on. */
+  if (parm.current && parm.current->forcedaddress.s_addr != 0)
+    {
+      struct in_pktinfo *pkt;
+      int transparent = 1;
+
+      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 = parm.current->forcedaddress;
+      msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+      cmptr->cmsg_level = IPPROTO_IP;
+      cmptr->cmsg_type = IP_PKTINFO;
+      #define IP_TRANSPARENT 19 /* XXX: This isn't in uclibc headers, hardcode for now */
+      setsockopt(fd, SOL_IP, IP_TRANSPARENT, &transparent, sizeof(transparent));
+    }
+#endif
+/* CRADLEPOINT */
+
 #ifdef HAVE_DUMPFILE
   dump_packet_udp(DUMP_DHCP, (void *)iov.iov_base, iov.iov_len, NULL,
 		  (union mysockaddr *)&dest, fd);
@@ -558,7 +589,10 @@
   struct dhcp_relay *relay;
   struct iface_param *param = vparam;
   struct shared_network *share;
-  
+/* CRADLEPOINT */
+  char namebuff[IF_NAMESIZE];
+/* CRADLEPOINT */
+
   (void)label;
 
   for (share = daemon->shared_networks; share; share = share->next)
@@ -606,8 +640,40 @@
   
   for (context = daemon->dhcp; context; context = context->next)
     {
-      if (context->netmask.s_addr != 0 &&
-	  is_same_net(local, context->start, context->netmask) &&
+/* CRADLEPOINT */
+      if (context->forcedinterface &&
+	  indextoname(daemon->dhcpfd, if_index, namebuff) &&
+	  strcmp(context->forcedinterface, namebuff) == 0)
+	{
+	  /* link it onto the current chain if we've not seen it before */
+	  if (if_index == param->ind && context->current == context)
+	    {
+	      if (context->forcedaddress.s_addr != 0)
+	        {
+		  context->router = context->forcedaddress;
+	          context->local = context->forcedaddress;
+	        }
+	      else
+	        {
+		  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;
+	    }
+	}
+      else if (context->netmask.s_addr != 0 &&
+/* CRADLEPOINT */
+  	    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 */
diff --git a/src/dhcp6.c b/src/dhcp6.c
index c9d54dc..2d65313 100644
--- a/src/dhcp6.c
+++ b/src/dhcp6.c
@@ -348,6 +348,8 @@
   struct iname *tmp;
   int match = !daemon->if_addrs;
  
+  char ifr_namebuff[IF_NAMESIZE];
+  int force_ra = 0;
   (void)scope; /* warning */
   
   if (if_index != param->ind)
@@ -359,7 +361,7 @@
     param->ula_addr = *local;
       
   if (IN6_IS_ADDR_LOOPBACK(local) ||
-      IN6_IS_ADDR_LINKLOCAL(local) ||
+      (IN6_IS_ADDR_LINKLOCAL(local) && !option_bool(OPT_RA)) ||
       IN6_IS_ADDR_MULTICAST(local))
     return 1;
   
@@ -382,8 +384,39 @@
 	prefix <= context->prefix &&
 	context->current == context)
       {
-	if (is_same_net6(local, &context->start6, context->prefix) &&
-	    is_same_net6(local, &context->end6, context->prefix))
+	memset(ifr_namebuff, 0, IF_NAMESIZE);
+	force_ra = context->forcedinterface6 &&
+	  indextoname(daemon->dhcpfd, if_index, ifr_namebuff) &&
+	  (strncmp(context->forcedinterface6, ifr_namebuff, IF_NAMESIZE) == 0) &&
+	  option_bool(OPT_FORCE_RA);
+	if (force_ra)
+	  {
+	    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 = context->forcedaddress6;
+	    context->preferred = preferred;
+	    context->valid = valid;
+	  }
+	else if (is_same_net6(local, &context->start6, context->prefix) &&
+	     is_same_net6(local, &context->end6, context->prefix))
 	  {
 	    struct dhcp_context *tmp, **up;
 	    
diff --git a/src/dns-protocol.h b/src/dns-protocol.h
index 2777be9..f1e7602 100644
--- a/src/dns-protocol.h
+++ b/src/dns-protocol.h
@@ -83,6 +83,10 @@
 #define EDNS0_OPTION_EDE            15    /* IANA - RFC 8914 */
 #define EDNS0_OPTION_NOMDEVICEID    65073 /* Nominum temporary assignment */
 #define EDNS0_OPTION_NOMCPEID       65074 /* Nominum temporary assignment */
+/* CRADLEPOINT */
+#define EDNS0_OPTION_OWNER          4     /* Owner Option (Used by OpenDNS) */
+#define EDNS0_OPTION_APPID		    65001
+/* CRADLEPOINT */
 #define EDNS0_OPTION_UMBRELLA       20292 /* Cisco Umbrella temporary assignment */
 
 /* RFC-8914 extended errors, negative values are our definitions */
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index 30fb419..1fc8e41 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -30,7 +30,9 @@
 static volatile int pipewrite;
 
 static void set_dns_listeners(void);
+#ifdef HAVE_TFTP
 static void set_tftp_listeners(void);
+#endif
 static void check_dns_listeners(time_t now);
 static void sig_handler(int sig);
 static void async_event(int pipe, time_t now);
@@ -336,13 +338,10 @@
 #endif
 
 #ifdef HAVE_IPSET
-  if (daemon->ipsets)
-    {
       ipset_init();
 #  ifdef HAVE_LINUX_NETWORK
       need_cap_net_admin = 1;
 #  endif
-    }
 #endif
 
 #ifdef HAVE_NFTSET
@@ -1506,7 +1505,10 @@
 	
       case EVENT_DUMP:
 	if (daemon->port != 0)
-	  dump_cache(now);
+/* CRADLEPOINT */
+          dump_cache_json(now);
+/* CRADLEPOINT */
+
 	break;
 	
       case EVENT_ALARM:
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index e455c3f..f6a03a3 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -65,6 +65,8 @@
 #include "ip6addr.h"
 #include "metrics.h"
 
+#include <stdbool.h>
+
 typedef unsigned char u8;
 typedef unsigned short u16;
 typedef unsigned int u32;
@@ -282,7 +284,9 @@
 #define OPT_NO_IDENT       70
 #define OPT_CACHE_RR       71
 #define OPT_LOCALHOST_SERVICE  72
-#define OPT_LAST           73
+#define OPT_EDNS_RESTRICT  73
+#define OPT_FORCE_RA       74
+#define OPT_LAST           75
 
 #define OPTION_BITS (sizeof(unsigned int)*8)
 #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -667,6 +671,10 @@
   char *name;
   union mysockaddr addr;
   int flags;
+  bool is_ra_adv_dis;
+  bool first_ra_delayed;
+  unsigned int final_ra_sent_count;
+  time_t last_ra_for_rtr_sol;
   struct iname *next;
 };
 
@@ -945,6 +953,13 @@
   struct dhcp_opt *next;
 };
 
+/* CRADLEPOINT */
+struct ssid_device_id_map {
+  unsigned char *map;
+  size_t map_size;
+};
+/* CRADLEPOINT */
+
 #define DHOPT_ADDR               1
 #define DHOPT_STRING             2
 #define DHOPT_ENCAPSULATE        4
@@ -1032,6 +1047,8 @@
   char *name;
   char *mtu_name;
   int interval, lifetime, prio, mtu;
+  unsigned int reachable_time, retrans_time, min_ra_adv_interval, max_ra_adv_interval, ra_sent_count;
+  unsigned char hop_limit;
   struct ra_interface *next;
 };
 
@@ -1044,11 +1061,17 @@
   struct in6_addr start6, end6; /* range of available addresses */
   struct in6_addr local6;
   int prefix, if_index;
-  unsigned int valid, preferred, saved_valid;
+  unsigned int valid, preferred, saved_valid, valid_lifetime;
   time_t ra_time, ra_short_period_start, address_lost_time;
   char *template_interface;
+  char *forcedinterface6;
+  struct in6_addr forcedaddress6;
 #endif
   int flags;
+  /* CRADLEPOINT */
+  char *forcedinterface;
+  struct in_addr forcedaddress;
+  /* CRADLEPOINT */  
   struct dhcp_netid netid, *filter;
   struct dhcp_context *next, *current;
 };
@@ -1083,6 +1106,8 @@
 #define CONTEXT_V6             (1u<<17)
 #define CONTEXT_RA_OFF_LINK    (1u<<18)
 #define CONTEXT_SETLEASE       (1u<<19)
+#define CONTEXT_SETVALID       (1u<<20)
+#define CONTEXT_RA_AUTO_OFF    (1u<<21)
 
 struct ping_result {
   struct in_addr addr;
@@ -1172,7 +1197,7 @@
   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 iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers, *tftp_interfaces, *ra_adv_if_names;
   struct bogus_addr *bogus_addr, *ignore_addr;
   struct server *servers, *servers_tail, *local_domains, **serverarray;
   struct rebind_domain *no_rebind;
@@ -1317,6 +1342,14 @@
   char *addrbuff;
   char *addrbuff2; /* only allocated when OPT_EXTRALOG */
 
+/* CRADLEPOINT */
+  /* EDNS options */
+  unsigned char *edns_options;
+  int edns_options_len;
+
+  /* ssid => network id map */
+  struct ssid_device_id_map ssid_device_id_map;
+/* CRADLEPOINT */
 #ifdef HAVE_DUMPFILE
   /* file for packet dumps. */
   int dumpfd;
@@ -1356,6 +1389,9 @@
 struct in_addr a_record_from_hosts(char *name, time_t now);
 void cache_unhash_dhcp(void);
 void dump_cache(time_t now);
+/*CRADLEPOINT*/
+void dump_cache_json(time_t now);
+/*CRADLEPOINT*/
 #ifndef NO_ID
 int cache_make_stat(struct txt_record *t);
 #endif
@@ -1686,7 +1722,7 @@
 /* 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);
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove, unsigned long attl);
 #endif
 
 /* nftset.c */
@@ -1817,6 +1853,22 @@
 
 /* radv.c */
 #ifdef HAVE_DHCP6
+// All RTR_ADV_INTERVALs in seconds
+#define DEFAULT_RTR_ADV_INTERVAL 600
+#define MAX_RTR_ADV_INTERVAL_UPPER_BOUND 1800
+#define MAX_RTR_ADV_INTERVAL_LOWER_BOUND 4
+#define MIN_RTR_ADV_INTERVAL 3
+// MAX_RA_DELAY_TIME in milliseconds
+#define MAX_RA_DELAY_TIME               500
+#define random_num_in_range(range) (rand() % (range))
+// MAX_INITIAL_RTR_ADVERT_INTERVAL in seconds
+#define MAX_INITIAL_RTR_ADVERT_INTERVAL 16
+// MAX_INITIAL_RTR_ADVERTISEMENTS in number of transmissions
+#define MAX_INITIAL_RTR_ADVERTISEMENTS  3
+// MAX_FINAL_RTR_ADVERTISEMENTS in number of transmissions
+#define MAX_FINAL_RTR_ADVERTISEMENTS    3
+// MIN_DELAY_BETWEEN_RAS in seconds
+#define MIN_DELAY_BETWEEN_RAS           3
 void ra_init(time_t now);
 void icmp6_packet(time_t now);
 time_t periodic_ra(time_t now);
@@ -1869,6 +1921,9 @@
 size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
 			union mysockaddr *source, time_t now, int *cacheable);
 int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+int process_appid_catid(struct dns_header *dns_buffer, size_t dns_buffer_length);
+#define MAX_APPID_PER_HOST 1
+#define MAX_CATID_PER_HOST 8
 
 /* arp.c */
 int find_mac(union mysockaddr *addr, unsigned char *mac, int lazy, time_t now);
diff --git a/src/edns0.c b/src/edns0.c
index 598478f..ce10a8d 100644
--- a/src/edns0.c
+++ b/src/edns0.c
@@ -15,6 +15,16 @@
 */
 
 #include "dnsmasq.h"
+/* CRADLEPOINT */
+#include "opendns.h"
+/* CRADLEPOINT */
+
+unsigned char *process_ar(unsigned char *ansp, int arcount, struct dns_header *header, size_t plen);
+static void insert_appid_catid_to_ipset(unsigned char *ansp, size_t plen);
+static void add_appid_catid_to_ipset(struct ipsets **ipset, char *domain, char *set);
+static struct ipsets *search_domain_in_ipset(struct ipsets *ipset, char *domain);
+static struct ipsets *add_domain_to_ipset(struct ipsets *ipset, char *domain);
+static void add_set_to_ipset(struct ipsets *ipset, char *set);
 
 unsigned char *find_pseudoheader(struct dns_header *header, size_t plen, size_t  *len, unsigned char **p, int *is_sign, int *is_last)
 {
@@ -524,6 +534,12 @@
 size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
 			union mysockaddr *source, time_t now, int *cacheable)    
 {
+/* CRADLEPOINT */
+  unsigned char *edns_options = daemon->edns_options;
+  size_t edns_options_len = daemon->edns_options_len;
+  int i, len, code;
+/* CRADLEPOINT */
+
   *cacheable = 1;
   
   plen  = add_mac(header, plen, limit, source, now, cacheable);
@@ -538,5 +554,218 @@
   
   plen = add_source_addr(header, plen, limit, source, cacheable);
   	  
+
+/* CRADLEPOINT */
+  if (daemon->ssid_device_id_map.map) {
+    opendns_pop_tag_from_query((unsigned char *) header, &plen,
+	&edns_options, &edns_options_len);
+  }
+
+  if (edns_options) {
+    for (i = edns_options_len; i > 0; i -= len + 4)
+    {
+      GETSHORT(code, edns_options);
+      GETSHORT(len, edns_options);
+      plen = add_pseudoheader(header, plen, limit, PACKETSZ, code,
+            (unsigned char *)edns_options, len, 0, 1);
+      edns_options += len;
+      
+      /* my_syslog(LOG_DEBUG, _("EDNS options added")); */
+    }
+  }
+/* CRADLEPOINT */
+
   return plen;
 }
+
+int process_appid_catid(struct dns_header *dns_buffer, size_t dns_buffer_length)
+{
+  struct dns_header *header = dns_buffer;
+  unsigned char *ansp;
+
+  /* Skip DNS header and all questions records
+  if packet is malformed, return as-is. */
+  if (!(ansp = skip_questions(dns_buffer, dns_buffer_length)))
+    return dns_buffer_length;
+
+  /* Skip all answers records and auth records
+  if packet is malformed, return as-is. */
+  if (!(ansp = skip_section(ansp, ntohs(dns_buffer->ancount) + ntohs(dns_buffer->nscount),
+                            dns_buffer, dns_buffer_length)))
+    return dns_buffer_length;
+
+  /* Now processing Additional records */
+  if (!(ansp = process_ar(ansp, ntohs(header->arcount), dns_buffer, dns_buffer_length)))
+    return dns_buffer_length;
+  return dns_buffer_length;
+}
+
+unsigned char *process_ar(unsigned char *ansp, int arcount, struct dns_header *header, size_t plen)
+{
+  unsigned short rdlen, type, index, opcode, opdlen, oplen;
+
+  for (index = 0; index < arcount; index++)
+  {
+    if (!(ansp = skip_name(ansp, header, plen, 10)))
+      return NULL;
+    GETSHORT(type, ansp);
+    ansp += 6; /* To skip type, UDP payload size, extended RCODE and flags. Ref: RFC6891 */
+    GETSHORT(rdlen, ansp);
+    if (type == T_OPT)
+    {
+      while (rdlen > 0)
+      {
+        GETSHORT(opcode, ansp);
+        GETSHORT(opdlen, ansp);
+        /* 2 byte option code + 2 byte option length + option data */
+        oplen = 2 + 2 + opdlen;
+        if (opcode == EDNS0_OPTION_APPID)
+        {
+          insert_appid_catid_to_ipset(ansp, opdlen);
+          memmove(ansp, ansp + oplen, plen - oplen);
+          header->arcount = htons(arcount-1);
+        }
+        else
+        {
+          ansp += oplen;
+        }
+        rdlen -= oplen;
+      }
+    }
+    else
+    {
+      ansp += rdlen - 8;
+    }
+  }
+  return ansp;
+}
+
+static void insert_appid_catid_to_ipset(unsigned char *ansp, size_t plen)
+{
+  unsigned char *end = ansp + plen;
+  char *set_ipv4, *set_ipv6;
+  ansp++;
+  while (ansp < end)
+  {
+    int entry_len = strnlen((char *)ansp, plen) + 1;
+    if (ansp + entry_len > end)
+    {
+      // Malformed data
+      return;
+    }
+
+    if ((set_ipv4 = whine_malloc(5 + entry_len)))
+    {
+      memcpy(set_ipv4, "ip4-", 4);
+      strncpy(set_ipv4 + 4, (char *)ansp, entry_len);
+      add_appid_catid_to_ipset(&daemon->ipsets, daemon->namebuff, set_ipv4);
+      free(set_ipv4);
+    }
+
+    if ((set_ipv6 = whine_malloc(5 + entry_len)))
+    {
+      memcpy(set_ipv6, "ip6-", 4);
+      strncpy(set_ipv6 + 4, (char *)ansp, entry_len);
+      add_appid_catid_to_ipset(&daemon->ipsets, daemon->namebuff, set_ipv6);
+      free(set_ipv6);
+    }
+
+    ansp += entry_len;
+  }
+}
+
+static void add_appid_catid_to_ipset(struct ipsets **ipset, char *domain, char *set)
+{
+  struct ipsets *ipsetptr;
+  if (!(*ipset))
+  {
+    if ((*ipset = whine_malloc(sizeof(struct ipsets))))
+    {
+      memset(*ipset, 0, sizeof(struct ipsets));
+      ipsetptr = *ipset;
+      if ((ipsetptr->domain = whine_malloc(strlen(domain) + 1)))
+      {
+        strcpy(ipsetptr->domain, domain);
+      }
+    }
+  }
+
+  ipsetptr = search_domain_in_ipset(*ipset, domain);
+
+  if (!(ipsetptr))
+    ipsetptr = add_domain_to_ipset(*ipset, domain);
+  if (ipsetptr)
+    add_set_to_ipset(ipsetptr, set);
+}
+
+static struct ipsets *search_domain_in_ipset(struct ipsets *ipset, char *domain)
+{
+  while (ipset)
+  {
+    if (strcmp(ipset->domain, domain) == 0)
+    {
+      return ipset;
+    }
+    ipset = ipset->next;
+  }
+  return NULL;
+}
+
+static struct ipsets *add_domain_to_ipset(struct ipsets *ipset, char *domain)
+{
+  while (ipset)
+  {
+    if (ipset->next == NULL)
+      break;
+    ipset = ipset->next;
+  }
+
+  if ((ipset->next = whine_malloc(sizeof(struct ipsets))))
+  {
+    memset(ipset->next, 0, sizeof(struct ipsets));
+    if ((ipset->next->domain = whine_malloc(strlen(domain) + 1)))
+    {
+      strcpy(ipset->next->domain, domain);
+      return ipset->next;
+    }
+  }
+  return NULL;
+}
+
+static void add_set_to_ipset(struct ipsets *ipset, char *set)
+{
+  int count = 0;
+  if (!(ipset->sets))
+  {
+    if ((ipset->sets = realloc(ipset->sets, 2 * sizeof(char *))) == NULL)
+    {
+      my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int)(2 * sizeof(char *)));
+      return;
+    }
+    if ((ipset->sets[0] = whine_malloc(strlen(set) + 1)))
+    {
+      strcpy(ipset->sets[0], set);
+      ipset->sets[1] = NULL;
+      return;
+    }
+  }
+
+  while (ipset->sets[count])
+  {
+    if (strcmp(ipset->sets[count], set) == 0)
+      return;
+    else
+      count++;
+  }
+  if ((ipset->sets = realloc(ipset->sets, sizeof(char *) * (count + 2))) == NULL)
+  {
+    my_syslog(LOG_ERR, _("failed to allocate %d bytes"), (int)((count + 2) * sizeof(char *)));
+    return;
+  }
+  if ((ipset->sets[count] = whine_malloc(strlen(set) + 1)))
+  {
+    strcpy(ipset->sets[count], set);
+    ipset->sets[count + 1] = NULL;
+  }
+  return;
+}
diff --git a/src/forward.c b/src/forward.c
index 32f37e4..44501bf 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -521,9 +521,11 @@
 	    }
 #endif
 	  
-	  if (retry_send(sendto(fd, (char *)header, plen, 0,
-				&srv->addr.sa,
-				sa_len(&srv->addr))))
+	      if (udpaddr &&
+		  (srv->addr.in.sin_addr.s_addr != udpaddr->in.sin_addr.s_addr) &&
+		  (retry_send(sendto(fd, (char *)header, plen, 0,
+				    &srv->addr.sa,
+				    sa_len(&srv->addr)))))
 	    continue;
 	  
 	  if (errno == 0)
@@ -666,7 +668,8 @@
   struct ipsets *ipset_pos, *ret = NULL;
   unsigned int namelen = strlen(domain);
   unsigned int matchlen = 0;
-  for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next) 
+  if (daemon->ipsets)
+    for (ipset_pos = setlist; ipset_pos; ipset_pos = ipset_pos->next) 
     {
       unsigned int domainlen = strlen(ipset_pos->domain);
       const char *matchstart = domain + namelen - domainlen;
@@ -696,8 +699,12 @@
   (void)do_bit;
  
 #ifdef HAVE_IPSET
-  if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
+  if (extract_request(header, n, daemon->namebuff, NULL))
+  {
+	  if (header && (ntohs(header->arcount)))
+	    n = process_appid_catid(header, n);
     ipsets = domain_find_sets(daemon->ipsets, daemon->namebuff);
+  }
 #endif
 
 #ifdef HAVE_NFTSET
diff --git a/src/ipset.c b/src/ipset.c
index 0c014cb..76972ea 100644
--- a/src/ipset.c
+++ b/src/ipset.c
@@ -24,63 +24,27 @@
 #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 */
-};
-
+#include <linux/netfilter/ipset/ip_set.h>
+#include <linux/netfilter/nfnetlink.h>
 
 /* 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)
+static inline struct nlattr *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;
+  struct nlattr *attr = (void *)nlh + NLA_ALIGN(nlh->nlmsg_len);
+  uint16_t payload_len = NLA_ALIGN(sizeof(struct 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);
+  if (len > 0) {
+    memcpy((void *)attr + NLA_ALIGN(sizeof(struct nlattr)), data, len);
+  }
+  nlh->nlmsg_len += NLA_ALIGN(payload_len);
+  return attr;
 }
 
 void ipset_init(void)
@@ -99,13 +63,39 @@
   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)
+/*
+ *  <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ * |        Header       | Pad |     Payload       | Pad |
+ * |   (struct nlattr)   | ing |                   | ing |
+ * +---------------------+- - -+- - - - - - - - - -+- - -+
+ *  <-------------- nlattr->nla_len -------------->
+ *
+ * For nested attributes, nla_len of IPSET_ATTR_DATA nlattr
+ * need to be updated appropriately after adding each attribute
+ *
+ * nla_type (16 bits)
+ * +---+---+-------------------------------+
+ * | N | O | Attribute Type                |
+ * +---+---+-------------------------------+
+ * N := Carries nested attributes
+ * O := Payload stored in network byte order
+ *
+ * Note: The N and O flag are mutually exclusive.
+ */
+
+static int new_add_to_ipset(const char *setname, const union all_addr *ipaddr,
+                            int af, int remove, unsigned long attl)
 {
   struct nlmsghdr *nlh;
-  struct my_nfgenmsg *nfg;
-  struct my_nlattr *nested[2];
+  struct nfgenmsg *nfg;
   uint8_t proto;
   int addrsz = (af == AF_INET6) ? IN6ADDRSZ : INADDRSZ;
+  struct nlattr *ipset_attr_data_ptr = NULL;
+  struct nlattr *ipset_attr_ip_ptr = NULL;
+  struct nlattr *ipset_attr_ip_addr_ptr = NULL;
+  struct nlattr *ipset_attr_timeout_ptr = NULL;
+  unsigned int attl_val = htonl(attl);
 
   if (strlen(setname) >= IPSET_MAXNAMELEN) 
     {
@@ -116,12 +106,12 @@
   memset(buffer, 0, BUFF_SZ);
 
   nlh = (struct nlmsghdr *)buffer;
-  nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr));
+  nlh->nlmsg_len = NLA_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 = (struct nfgenmsg *)(buffer + nlh->nlmsg_len);
+  nlh->nlmsg_len += NLA_ALIGN(sizeof(struct nfgenmsg));
   nfg->nfgen_family = af;
   nfg->version = NFNETLINK_V0;
   nfg->res_id = htons(0);
@@ -129,17 +119,17 @@
   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, 
+  ipset_attr_data_ptr = add_attr(nlh, NLA_F_NESTED | IPSET_ATTR_DATA,  0, NULL);
+  ipset_attr_ip_ptr = add_attr(nlh, NLA_F_NESTED | IPSET_ATTR_IP,  0, NULL);
+  ipset_attr_data_ptr->nla_len += ipset_attr_ip_ptr->nla_len;
+  ipset_attr_ip_addr_ptr = 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];
+  ipset_attr_ip_ptr->nla_len += ipset_attr_ip_addr_ptr->nla_len;
+  ipset_attr_data_ptr->nla_len += ipset_attr_ip_addr_ptr->nla_len;
+  ipset_attr_timeout_ptr = add_attr(nlh, IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER,
+                                    sizeof(attl_val), &attl_val);
+  ipset_attr_data_ptr->nla_len += ipset_attr_timeout_ptr->nla_len;
 	
   while (retry_send(sendto(ipset_sock, buffer, nlh->nlmsg_len, 0,
 			   (struct sockaddr *)&snl, sizeof(snl))));
@@ -189,7 +179,8 @@
 
 
 
-int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags, int remove)
+int add_to_ipset(const char *setname, const union all_addr *ipaddr, int flags,
+                 int remove, unsigned long attl)
 {
   int ret = 0, af = AF_INET;
 
@@ -205,7 +196,7 @@
     }
   
   if (ret != -1) 
-    ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove);
+    ret = old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove, attl);
 
   if (ret == -1)
      my_syslog(LOG_ERR, _("failed to update ipset %s: %s"), setname, strerror(errno));
diff --git a/src/opendns.c b/src/opendns.c
new file mode 100644
index 0000000..d0b4348
--- /dev/null
+++ b/src/opendns.c
@@ -0,0 +1,231 @@
+
+#include "dnsmasq.h"
+#include "opendns.h"
+
+const unsigned char *
+opendns_ssid_to_device_id(size_t * const device_id_len_p,
+                          const unsigned char * const ssid,
+                          const size_t ssid_len)
+{
+    unsigned char *map = daemon->ssid_device_id_map.map;
+    unsigned char *map_tmp;
+    unsigned char *ssid_tmp;
+    unsigned char *device_id_tmp;
+    size_t         ssid_tmp_len;
+    size_t         device_id_len;
+
+    if (map == NULL || ssid == NULL) {
+        return NULL;
+    }
+    map_tmp = map;
+    while ((ssid_tmp_len = (size_t) *map_tmp) > 0U) {
+        ssid_tmp = ++map_tmp;
+        map_tmp += ssid_tmp_len;
+        device_id_len = (size_t) *map_tmp;
+        device_id_tmp = ++map_tmp;
+        if (ssid_tmp_len == ssid_len &&
+            memcmp(ssid_tmp, ssid, ssid_len) == 0) {
+            *device_id_len_p = device_id_len;
+            return device_id_tmp;
+        }
+        map_tmp += device_id_len;
+    }
+    return NULL;
+}
+
+static int
+opendns_parse_hex_char(unsigned char * const device_id,
+                       size_t * const device_id_pos_p, int * const state_p,
+                       unsigned char * const val_p, size_t device_id_len_max,
+                       const int c)
+{
+    unsigned char c_val;
+
+    switch (*state_p) {
+    case 0:
+    case 1:
+        if (isspace(c) || (c == ':' && *state_p == 0)) {
+            break;
+        }
+        if (c == '#') {
+            *state_p = 2;
+            break;
+        }
+        if (!isxdigit(c)) {
+            return -1;
+        }
+        c_val = (c >= '0' && c <= '9') ? c - '0' : c - 'a' + 10;
+        if (*state_p == 0) {
+            *val_p = c_val * 16U;
+            *state_p = 1;
+        } else {
+            *val_p |= c_val;
+            device_id[(*device_id_pos_p)++] = *val_p;
+            if (*device_id_pos_p >= device_id_len_max) {
+                return 0;
+            }
+            *state_p = 0;
+        }
+        break;
+    case 2:
+        if (c == '\n') {
+            *state_p = 0;
+        }
+    }
+    return 1;
+}
+
+size_t
+opendns_parse_hex(const char * const device_id_hex,
+                  unsigned char * const device_id,
+                  const size_t device_id_len_max)
+{
+    const char   *p = device_id_hex;
+    size_t        device_id_pos = (size_t) 0U;
+    int           c;
+    int           ret;
+    int           state = 0;
+    unsigned char val = 0U;
+
+    if (device_id_hex == NULL) {
+        return (size_t) 0U;
+    }
+    while ((c = tolower((int) (unsigned char) *p)) != 0) {
+        ret = opendns_parse_hex_char(device_id, &device_id_pos,
+                                     &state, &val, device_id_len_max, c);
+        if (ret < 0) {
+            return (size_t) 0U;
+        }
+        if (ret == 0) {
+            break;
+        }
+        p++;
+    }
+    return device_id_pos;
+}
+
+char *
+opendns_parse_device_id_opt(char * const arg)
+{
+    struct ssid_device_id_map * const ssid_device_id_map =
+        &daemon->ssid_device_id_map;
+    unsigned char *device_id;
+    unsigned char *tmp_map;
+    char          *device_id_hex;
+    char          *sep;
+    char          *ssid;
+    size_t         device_id_hex_len;
+    size_t         device_id_len;
+    size_t         device_id_len_max;
+    size_t         pos;
+    size_t         ssid_len;
+    size_t         tmp_map_size;
+
+    if ((sep = strrchr(arg, ',')) == NULL ||
+        *(device_id_hex = sep + 1) == 0) {
+        return _("missing device id");
+    }
+    *sep = 0;
+    if (*(ssid = arg) == 0) {
+        return _("missing SSID");
+    }
+    ssid_len = (size_t) (sep - ssid);
+    if (ssid_len > 255U) {
+        return _("SSID too long");
+    }
+    device_id_hex_len = strlen(device_id_hex);
+    device_id_len_max = (device_id_hex_len + 1U) / 2U;
+    device_id = safe_malloc(device_id_len_max);
+    if (device_id_len_max > 255U) {
+        free(device_id);
+        return _("device id too long");
+    }
+    device_id_len = opendns_parse_hex(device_id_hex, device_id,
+                                      device_id_len_max);
+    if (device_id_len <= 0U) {
+        free(device_id);
+        return _("unable to parse a hex device id");
+    }
+    if (device_id_len > device_id_len_max) {
+        free(device_id);
+        return _("parsed device id too long");
+    }
+    tmp_map_size = ssid_device_id_map->map_size + 1U +
+        ssid_len + 1U + device_id_len;
+    if (ssid_device_id_map->map == NULL) {
+        tmp_map_size++;
+    }
+    if ((tmp_map = realloc(ssid_device_id_map->map,
+                           tmp_map_size)) == NULL) {
+        die(_("could not get memory"), NULL, EC_NOMEM);
+    }
+    if (ssid_device_id_map->map_size <= 0U) {
+        pos = 0U;
+    } else {
+        pos = ssid_device_id_map->map_size - 1U;
+    }
+    tmp_map[pos++] = (unsigned char) ssid_len;
+    memcpy(&tmp_map[pos], ssid, ssid_len);
+    pos += ssid_len;
+    tmp_map[pos++] = (unsigned char) device_id_len;
+    memcpy(&tmp_map[pos], device_id, device_id_len);
+    pos += device_id_len;
+    free(device_id);
+    tmp_map[pos] = 0U;
+    ssid_device_id_map->map = tmp_map;
+    ssid_device_id_map->map_size = tmp_map_size;
+
+    return NULL;
+}
+
+int
+opendns_pop_tag_from_query(unsigned char * const packet,
+                           size_t * const packet_size_p,
+                           unsigned char * * const edns_options,
+                           size_t *edns_options_len_p)
+{
+    static unsigned char edns_options_tpl[2U + 2U + 7U + 255U] = {
+        0U, 4U, 0U, 0U, 'O', 'p', 'e', 'n', 'D', 'N', 'S'
+    };
+    const unsigned char *device_id;
+    const unsigned char *tag;
+    unsigned char       *tmp;
+    size_t               edns_options_len;
+    size_t               device_id_len;
+    size_t               packet_size = *packet_size_p;
+    size_t               tag_len;
+
+    if (packet_size <= 0U) {
+        *edns_options = NULL;
+        *edns_options_len_p = 0U;
+        return -1;
+    }
+    tag_len = (size_t) packet[packet_size - 1U];
+    if (tag_len >= packet_size) {
+        *edns_options = NULL;
+        *edns_options_len_p = 0U;
+        return -1;
+    }
+    tag = &packet[packet_size - tag_len - 1U];
+    if ((device_id = opendns_ssid_to_device_id(&device_id_len,
+                                                 tag, tag_len)) == NULL) {
+        return -1;
+    }
+    edns_options_len = 2U + 2U + sizeof "OpenDNS" - 1U + device_id_len;
+    if (edns_options_len > sizeof edns_options_tpl) {
+        return -1;
+    }
+    memcpy(edns_options_tpl + 2U + 2U + sizeof "OpenDNS" - 1U,
+           device_id, device_id_len);
+    if (packet_size <= 1U + device_id_len) {
+        return -1;
+    }
+    tmp = edns_options_tpl + 2U;
+    PUTSHORT(sizeof "OpenDNS" - 1U + device_id_len, tmp);
+    *edns_options = edns_options_tpl;
+    *edns_options_len_p = edns_options_len;
+    packet_size -= 1U + tag_len;
+    *packet_size_p = packet_size;
+
+    return 0;
+}
diff --git a/src/opendns.h b/src/opendns.h
new file mode 100644
index 0000000..cfcae29
--- /dev/null
+++ b/src/opendns.h
@@ -0,0 +1,15 @@
+#ifndef __OPENDNS_H__
+#define __OPENDNS_H__ 1
+
+char *opendns_parse_device_id_opt(char * const arg);
+
+const unsigned char *
+opendns_ssid_to_device_id(size_t * const device_id_len_p,
+                          const unsigned char * const ssid,
+                          const size_t ssid_len);
+
+int opendns_pop_tag_from_query(unsigned char * const packet,
+                               size_t * const packet_size_p,
+                               unsigned char * * const edns_options,
+                               size_t *edns_options_len_p);
+#endif
diff --git a/src/option.c b/src/option.c
index f4ff7c0..27353b9 100644
--- a/src/option.c
+++ b/src/option.c
@@ -18,6 +18,10 @@
 #define SYSLOG_NAMES
 #include "dnsmasq.h"
 #include <setjmp.h>
+/* CRADLEPOINT */
+#include "unescape_c_string.h"
+#include "opendns.h"
+/* CRADLEPOINT */
 
 static volatile int mem_recover = 0;
 static jmp_buf mem_jmp;
@@ -192,6 +196,13 @@
 #define LOPT_NO_DHCP4      383
 #define LOPT_MAX_PROCS     384
 #define LOPT_DNSSEC_LIMITS 385
+/* CRADLEPOINT */
+#define LOPT_EDNS_OPTION   386
+#define LOPT_SSID_MAP      387
+#define LOPT_EDNS_RESTRICT 388
+/* CRADLEPOINT */ 
+#define LOPT_RA_ADV_DISABLE 389
+#define LOPT_FORCE_RA      390
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -388,6 +399,13 @@
     { "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
     { "no-ident", 0, 0, LOPT_NO_IDENT },
     { "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
+/* CRADLEPOINT */
+    { "edns-option", 1, 0, LOPT_EDNS_OPTION },
+    { "ssid-map", 1, 0, LOPT_SSID_MAP },
+    { "edns-restrict", 0, 0, LOPT_EDNS_RESTRICT },
+    { "ra-adv-disable", 1, 0, LOPT_RA_ADV_DISABLE },
+    { "force-ra", 0, 0, LOPT_FORCE_RA},
+/* CRADLEPOINT */
     { NULL, 0, 0, 0 }
   };
 
@@ -547,6 +565,7 @@
   { 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_FORCE_RA, OPT_FORCE_RA, NULL, gettext_noop("Force Router Advertisements on the provided forced interface"), 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 },
@@ -571,7 +590,7 @@
   { 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_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), 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_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>][,<reachable_time>][,<hop_limit>][,<retrans_time>][,<min_ra_adv_interval>][,<max_ra_adv_interval>]", gettext_noop("Set MTU, priority, resend-interval, router-lifetime, reachable_time, hop_limit, retrans_time, min_ra_adv_interval and max_ra_adv_interval"), 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 },
@@ -591,6 +610,7 @@
   { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL },
   { LOPT_CACHE_RR, ARG_DUP, "<RR-type>", gettext_noop("Cache this DNS resource record type."), NULL },
   { LOPT_MAX_PROCS, ARG_ONE, "<integer>", gettext_noop("Maximum number of concurrent tcp connections."), NULL },
+  { LOPT_RA_ADV_DISABLE, ARG_DUP, "<interface>", gettext_noop("Specify interface(s) to disable RA. This parameter should be subset of interfaces and should be provided after interfaces option"), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
 
@@ -1294,6 +1314,10 @@
      "interface=" to disable all interfaces except loop. */
   new->name = opt_string_alloc(ifname);
   new->flags = 0;
+  new->is_ra_adv_dis = false;
+  new->first_ra_delayed = false;
+  new->final_ra_sent_count = 0;
+  time(&new->last_ra_for_rtr_sol);
 }
 
 #ifdef HAVE_DHCP
@@ -2854,6 +2878,21 @@
       } while (arg);
       break;
       
+    case LOPT_RA_ADV_DISABLE: ; /* --ra-adv-disable */
+      struct iname *tmp;
+      do {
+        comma = split(arg);
+	for (tmp = daemon->if_names; tmp; tmp = tmp->next)
+	{
+	  if (strcmp(tmp->name, arg) == 0) {
+	    tmp->is_ra_adv_dis = true;
+		tmp->final_ra_sent_count = 0;
+	  }
+	}
+	arg = comma;
+      } while (arg);
+      break;
+
     case LOPT_TFTP: /* --enable-tftp */
       set_option_bool(OPT_TFTP);
       if (!arg)
@@ -3682,7 +3721,7 @@
     case 'F':  /* --dhcp-range */
       {
 	int k, leasepos = 2;
-	char *cp, *a[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
+	char *cp, *a[9] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL };
 	struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context));
 	
 	memset (new, 0, sizeof(*new));
@@ -3697,7 +3736,17 @@
 	    
 	    if (*cp != ',' && (comma = split(arg)))
 	      {
-		if (is_tag_prefix(arg))
+/* CRADLEPOINT */
+		if (strstr(arg, "forcedinterface:") == arg)
+		  new->forcedinterface = opt_string_alloc(arg+16);
+		else if (strstr(arg, "forcedaddress:") == arg)
+		  new->forcedaddress.s_addr = inet_addr(arg+14);
+		else if (strstr(arg, "forcedinterface6:") == arg)
+		  new->forcedinterface6 = opt_string_alloc(arg+17);
+		else if (strstr(arg, "forcedaddress6:") == arg)
+		  inet_pton(AF_INET6, arg+15, &(new->forcedaddress6));
+		else if (is_tag_prefix(arg))
+/* CRADLEPOINT */		      
 		  {
 		    /* ignore empty tag */
 		    if (arg[4])
@@ -3722,7 +3771,7 @@
 	      }
 	  }
 	
-	for (k = 1; k < 8; k++)
+	for (k = 1; k < 9; k++)
 	  if (!(a[k] = split(a[k-1])))
 	    break;
 	
@@ -3784,6 +3833,7 @@
 	    new->prefix = 64; /* default */
 	    new->end6 = new->start6;
 	    new->lease_time = DEFLEASE6;
+	    new->valid_lifetime = DEFLEASE6;
 	    new->next = daemon->dhcp6;
 	    daemon->dhcp6 = new;
 
@@ -3801,6 +3851,8 @@
 		  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 (strcmp(a[leasepos], "auto-off") == 0)
+		  new->flags |= CONTEXT_RA_AUTO_OFF;
 		else if (leasepos == 1 && inet_pton(AF_INET6, a[leasepos], &new->end6))
 		  new->flags |= CONTEXT_DHCP; 
 		else if (strstr(a[leasepos], "constructor:") == a[leasepos])
@@ -3876,12 +3928,14 @@
 	
 	if (leasepos < k)
 	  {
+	    
+#ifndef HAVE_DHCP6
 	    if (leasepos != k-1)
 	      {
 		dhcp_context_free(new);
 		ret_err(_("bad dhcp-range"));
 	      }
-	    
+#endif
 	    if (strcmp(a[leasepos], "infinite") == 0)
 	      {
 		new->lease_time = 0xffffffff;
@@ -3921,7 +3975,7 @@
 		      if (!(*cp >= '0' && *cp <= '9'))
 			break;
 
-		    if (*cp || (leasepos+1 < k))
+		    if (*cp)
 		      ret_err_free(_("bad dhcp-range"), new);
 		    
 		    new->lease_time = atoi(a[leasepos]) * fac;
@@ -3932,8 +3986,63 @@
 		      new->lease_time = 120;
 		  }
 	      }
+		leasepos++;
 	  }
+#ifdef HAVE_DHCP6
+	if (leasepos < k)
+	  {
+	    if (leasepos != k-1)
+	      {
+		dhcp_context_free(new);
+		ret_err(_("bad dhcp-range"));
+	      }
+	    if (strcmp(a[leasepos], "infinite") == 0)
+	      {
+		new->valid_lifetime = 0xffffffff;
+		new->flags |= CONTEXT_SETVALID;
+	      }
+	    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->valid_lifetime = atoi(a[leasepos]) * fac;
+		    new->flags |= CONTEXT_SETVALID;
+		  }
+	      }
+		leasepos++;
+	  }
+#endif
 	break;
       }
 
@@ -4726,45 +4835,71 @@
 #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)))
+        {
+          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);
+          new->reachable_time = 0;
+          new->hop_limit = 0;
+          new->retrans_time = 0;
+          new->ra_sent_count = 0;
+          new->max_ra_adv_interval = DEFAULT_RTR_ADV_INTERVAL;
+          new->min_ra_adv_interval = new->max_ra_adv_interval/3;
+          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) && (new->mtu != 0))
+                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 (comma && !atoi_check(comma, &new->interval))
+             goto err;
+           comma = split(arg);
+           if (arg && !atoi_check(arg, &new->lifetime))
+             goto err;
+           arg = split(comma);
+           if (comma && !atoi_check(comma, (int *)&new->reachable_time))
+             goto err;
+           comma = split(arg);
+           if (arg && (!atoi_check(arg, (int *)&new->hop_limit)))
+             goto err;
+           arg = split(comma);
+           if (comma && !atoi_check(comma, (int *)&new->retrans_time))
+             goto err;
+           comma = split(arg);
+           if ((arg && !atoi_check(arg, (int *)&new->min_ra_adv_interval)) ||
+               (comma && (!atoi_check(comma, (int *)&new->max_ra_adv_interval))))
              {
 err:
-	       free(new->name);
-	       ret_err_free(_("bad RA-params"), new);
+               free(new->name);
+               ret_err_free(_("bad RA-params"), new);
              }
-	  
-	  new->next = daemon->ra_interfaces;
-	  daemon->ra_interfaces = new;
-	}
+           if (new->hop_limit != 0)
+             {
+               setsockopt(daemon->icmp6fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS,
+                          &new->hop_limit, sizeof(new->hop_limit));
+             }
+
+          new->next = daemon->ra_interfaces;
+          daemon->ra_interfaces = new;
+        }
       break;
       
     case LOPT_DUID: /* --dhcp-duid */
@@ -5361,6 +5496,75 @@
 	break;
       }
 
+/* CRADLEPOINT */
+/* This option (OPT_EDNS_OPTION) is required to tack a(n) owner record onto a DNS request:
+ * Option Code: Owner (reserved) 4 (0x00 0x04)
+ * Option Length: 19  ("Cradlepoint" + device_id (obtained from OpenDNS)) (0x00 0x13)
+ * Option Data: "Cradlepoint" + device_id (an 4 byte identifier)
+ *
+ * The EDNS option is passed in like this:
+ * --edns-option=4,CradlepointAABBCCDD
+ */
+    case LOPT_EDNS_OPTION:
+      {
+	unsigned char *tmp;
+	int option_code, option_len = 0;
+
+	comma = split(arg);
+
+	if (*arg == 0 || !atoi_check16(arg, &option_code))
+	{
+	  option = '?';
+	  ret_err(_("invalid EDNS option code"));
+	  break;
+	}
+
+	if (comma &&
+	    (option_len = str_unescape_c_string(comma, comma)) <= 0)
+	{
+	  option = '?';
+	  ret_err(_("invalid EDNS option data"));
+	  break;
+	}
+	tmp = opt_malloc(daemon->edns_options_len + 4 + option_len);
+
+	if (daemon->edns_options_len > 0)
+	{
+	  memcpy(tmp, daemon->edns_options, daemon->edns_options_len);
+	  free(daemon->edns_options);
+	}
+	daemon->edns_options = tmp;
+	tmp += daemon->edns_options_len;
+	PUTSHORT(option_code, tmp);
+	PUTSHORT(option_len, tmp);
+	memcpy(tmp, comma, option_len);
+	daemon->edns_options_len += 4 + option_len;
+	my_syslog(LOG_DEBUG, _("EDNS Option %d added length %d"), option_code,
+	    daemon->edns_options_len);
+
+	break;
+      }
+
+    case LOPT_SSID_MAP:
+      {
+	char *err;
+
+	unhide_metas(arg);
+
+	if ((err = opendns_parse_device_id_opt(arg)) != NULL) {
+	  /* int vs pointer warning return err; */
+	  ret_err(_("opendns parse device id failed"));
+	}
+
+	break;
+      }
+    case LOPT_EDNS_RESTRICT:
+      {
+		set_option_bool(OPT_EDNS_RESTRICT);
+	break;
+	}
+/* CRADLEPOINT */
+      
     default:
       ret_err(_("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DNSSEC/DBus support)"));
       
diff --git a/src/radv.c b/src/radv.c
index d2d3390..0457496 100644
--- a/src/radv.c
+++ b/src/radv.c
@@ -64,7 +64,11 @@
 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 unsigned char calc_hop_limit(struct ra_interface *ra);
+static unsigned int calc_reachable_time(struct ra_interface *ra);
+static unsigned int calc_retrans_time(struct ra_interface *ra);
 static struct ra_interface *find_iface_param(char *iface);
+int64_t timespecdiff(struct timespec const *a, struct timespec const *b);
 
 static int hop_limit;
 
@@ -76,7 +80,10 @@
   int class = IPTOS_CLASS_CS6;
 #endif
   int val = 255; /* radvd uses this value */
-  socklen_t len = sizeof(int);
+#ifdef IPV6_RECVHOPLIMIT
+  int on_ipv6_recvhoplimit = 1;
+#endif
+  socklen_t len = sizeof(hop_limit);
   struct dhcp_context *context;
   
   /* ensure this is around even if we're not doing DHCPv6 */
@@ -106,6 +113,9 @@
       !set_ipv6pktinfo(fd) ||
       setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &val, sizeof(val)) ||
       setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof(val)) ||
+#ifdef IPV6_RECVHOPLIMIT
+      setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on_ipv6_recvhoplimit, sizeof(on_ipv6_recvhoplimit)) ||
+#endif
       setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == -1)
     die (_("cannot create ICMPv6 socket: %s"), NULL, EC_BADNET);
   
@@ -113,6 +123,9 @@
    
    if (daemon->doing_ra)
      ra_start_unsolicited(now, NULL);
+
+   /* Intializes random number generator */
+   srand(time(0));
 }
 
 void ra_start_unsolicited(time_t now, struct dhcp_context *context)
@@ -143,11 +156,14 @@
   char interface[IF_NAMESIZE+1];
   ssize_t sz; 
   int if_index = 0;
+  int hoplimit = 255;
   struct cmsghdr *cmptr;
   struct msghdr msg;
   union {
-    struct cmsghdr align; /* this ensures alignment */
-    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo))];
+    struct cmsghdr align;
+    /* Extending control6 array to include hoplimit information
+    in addition to in6_pktinfo information */
+    char control6[CMSG_SPACE(sizeof(struct in6_pktinfo)) + CMSG_SPACE(sizeof(int))];
   } control_u;
   struct sockaddr_in6 from;
   unsigned char *packet;
@@ -167,7 +183,15 @@
    
   packet = (unsigned char *)daemon->outpacket.iov_base;
 
-  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
+  for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) {
+#ifdef IPV6_HOPLIMIT
+    if (cmptr->cmsg_level == IPPROTO_IPV6 && (cmptr->cmsg_type == IPV6_HOPLIMIT)) {
+      if ((cmptr->cmsg_len == CMSG_LEN(sizeof(int))) &&
+	        (*(int *)CMSG_DATA(cmptr) >= 0) && (*(int *)CMSG_DATA(cmptr) < 256)) {
+	      hoplimit = *(int *)CMSG_DATA(cmptr);
+      }
+    }
+#endif /* IPV6_HOPLIMIT */
     if (cmptr->cmsg_level == IPPROTO_IPV6 && cmptr->cmsg_type == daemon->v6pktinfo)
       {
 	union {
@@ -178,6 +202,7 @@
         
 	if_index = p.p->ipi6_ifindex;
       }
+  }
   
   if (!indextoname(daemon->icmp6fd, if_index, interface))
     return;
@@ -195,13 +220,19 @@
 
   if (packet[0] == ICMP6_ECHO_REPLY)
     lease_ping_reply(&from.sin6_addr, packet, interface); 
-  else if (packet[0] == ND_ROUTER_SOLICIT)
+  else if ((packet[0] == ND_ROUTER_SOLICIT) && (hoplimit == 255))
+  {
+    if (!((sz > 8) && (packet[9] == 0))) {
+      inet_ntop(AF_INET6, &from.sin6_addr, daemon->addrbuff, ADDRSTRLEN);
+      if (!((strncmp(daemon->addrbuff, "::",2)==0) && (packet[8] == ICMP6_OPT_SOURCE_MAC)))
     {
       char *mac = "";
       struct dhcp_bridge *bridge, *alias;
       ssize_t rem;
       unsigned char *p;
       int opt_sz;
+      struct timespec ts;
+      int tms = 0;
       
 #ifdef HAVE_DUMPFILE
       dump_packet_icmp(DUMP_RA, (void *)packet, sz, (union mysockaddr *)&from, NULL);
@@ -251,7 +282,13 @@
       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);
+  tms = random_num_in_range(MAX_RA_DELAY_TIME);
+  ts.tv_sec = 0;
+  ts.tv_nsec = tms * 1000000;
+  nanosleep(&ts, NULL);
     }
+   }
+  }
 }
 
 static void send_ra_alias(time_t now, int iface, char *iface_name, struct in6_addr *dest, int send_iface)
@@ -268,7 +305,10 @@
 #ifdef HAVE_LINUX_NETWORK
   FILE *f;
 #endif
-  
+
+  struct iname *if_tmp;
+  time_t ts;
+  struct timespec ts_delay;
   parm.ind = iface;
   parm.managed = 0;
   parm.other = 0;
@@ -288,17 +328,31 @@
   
   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;
+  ra->reachable_time = htonl(calc_reachable_time(ra_param));
+  ra->hop_limit = calc_hop_limit(ra_param);
+  ra->retrans_time = htonl(calc_retrans_time(ra_param));
 
   /* set tag with name == interface */
   iface_id.net = iface_name;
   iface_id.next = NULL;
   parm.tags = &iface_id; 
   
+  for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+  {
+    if ((strcmp(if_tmp->name, iface_name) == 0) && (if_tmp->is_ra_adv_dis == true)) {
+      if (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS) {
+        ra->lifetime = htons(0);
+        if_tmp->final_ra_sent_count++;
+        break;
+      }
+      else {
+        return;
+      }
+    }
+  }
+
   for (context = daemon->dhcp6; context; context = context->next)
     {
       context->flags &= ~CONTEXT_RA_DONE;
@@ -385,7 +439,8 @@
 		  /* 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);
+                    ((context->flags & CONTEXT_RA_OFF_LINK) ? 0 : 0x80) |
+                    ((context->flags & CONTEXT_RA_AUTO_OFF) ? 0 : 0x40);
 		  opt->valid_lifetime = htonl(context->saved_valid - old);
 		  opt->preferred_lifetime = htonl(0);
 		  opt->reserved = 0; 
@@ -424,13 +479,9 @@
 
   /* 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")))
@@ -439,8 +490,9 @@
             mtu = atoi(daemon->namebuff);
           fclose(f);
         }
-    }
 #endif
+if (ra_param)
+    mtu = ra_param->mtu;
   if (mtu > 0)
     {
       put_opt6_char(ICMP6_OPT_MTU);
@@ -570,10 +622,26 @@
   }
 #endif
 
+  for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+  {
+    if ((strcmp(if_tmp->name, iface_name) == 0)) {
+      time(&ts);
+      if (difftime(ts, if_tmp->last_ra_for_rtr_sol) < MIN_DELAY_BETWEEN_RAS) {
+        /* last RA for RTR Solicit was sent only a few moments ago,
+        don't send another immediately. */
+        ts_delay.tv_sec = MIN_DELAY_BETWEEN_RAS;
+        ts_delay.tv_nsec = 0;
+        nanosleep(&ts_delay, NULL);
+      }
+      time(&if_tmp->last_ra_for_rtr_sol);
+    }
+  }
   while (retry_send(sendto(daemon->icmp6fd, daemon->outpacket.iov_base, 
 			   save_counter(-1), 0, (struct sockaddr *)&addr, 
 			   sizeof(addr))));
   
+  if(ra_param)
+	ra_param->ra_sent_count++;
 }
 
 static void send_ra(time_t now, int iface, char *iface_name, struct in6_addr *dest)
@@ -588,8 +656,24 @@
 			unsigned int preferred, unsigned int valid, void *vparam)
 {
   struct ra_param *param = vparam;
+  char ifr_namebuff[IF_NAMESIZE];
+  int force_ra = 0;
+  struct in6_addr forcedaddress6;
 
   (void)scope; /* warning */
+
+  struct iname *if_tmp;
+  for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+  {
+    if ((strcmp(if_tmp->name, param->if_name) == 0) && (if_tmp->is_ra_adv_dis == true)) {
+      if (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS) {
+        break;
+      }
+      else {
+        return 1;
+      }
+    }
+  }
   
   if (if_index == param->ind)
     {
@@ -604,8 +688,9 @@
 	      param->link_local = *local;
 	    }
 	}
-      else if (!IN6_IS_ADDR_LOOPBACK(local) &&
-	       !IN6_IS_ADDR_MULTICAST(local))
+  /* Force RA's for the link local address */
+  if ((!IN6_IS_ADDR_LOOPBACK(local) && !IN6_IS_ADDR_MULTICAST(local)) ||
+      (option_bool(OPT_FORCE_RA) && IN6_IS_ADDR_LINKLOCAL(local)))
 	{
 	  int real_prefix = 0;
 	  int do_slaac = 0;
@@ -613,14 +698,24 @@
 	  int constructed = 0;
 	  int adv_router = 0;
 	  int off_link = 0;
+	  int auto_off = 0;
+	  unsigned int valid_lifetime = 0xffffffff;
 	  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))
+
+	 for (context = daemon->dhcp6; context; context = context->next) {
+	   memset(ifr_namebuff, 0, IF_NAMESIZE);
+	   force_ra = context->forcedinterface6 &&
+	     indextoname(daemon->dhcpfd, if_index, ifr_namebuff) &&
+	     (strncmp(context->forcedinterface6, ifr_namebuff, IF_NAMESIZE) == 0) &&
+	     option_bool(OPT_FORCE_RA);
+	   if (force_ra)
+	     forcedaddress6 = context->forcedaddress6;
+	   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)) ||
+		   force_ra))
 	      {
 		context->saved_valid = valid;
 
@@ -659,6 +754,12 @@
 		if ((context->flags & CONTEXT_SETLEASE) &&
 		    time > context->lease_time)
 		  {
+	 
+		    if ((context->flags & CONTEXT_SETVALID))
+		        valid_lifetime = context->valid_lifetime;
+		    else
+		        valid_lifetime = context->lease_time;
+
 		    time = context->lease_time;
 		    if (time < ((unsigned int)(3 * param->adv_interval)))
 		      time = 3 * param->adv_interval;
@@ -689,6 +790,7 @@
 		    context->flags |= CONTEXT_RA_DONE;
 		    real_prefix = context->prefix;
                     off_link = (context->flags & CONTEXT_RA_OFF_LINK);
+                    auto_off = (context->flags & CONTEXT_RA_AUTO_OFF);
 		  }
 
 		param->first = 0;
@@ -696,10 +798,10 @@
 		   more than one, it's not the first. */
 		param->found_context = context;
 	      }
-
+   }
 	  /* configured time is ceiling */
 	  if (!constructed || valid > time)
-	    valid = time;
+	    valid = valid_lifetime;
 	  
 	  if (flags & IFACE_DEPRECATED)
 	    preferred = 0;
@@ -744,14 +846,17 @@
 		  /* 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)
+		  if (do_slaac && !auto_off)
 		    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;
+		  if (force_ra)
+		    opt->prefix = forcedaddress6;
+		  else
+		    opt->prefix = *local;
 		  
 		  inet_ntop(AF_INET6, local, daemon->addrbuff, ADDRSTRLEN);
 		  if (!option_bool(OPT_QUIET_RA))
@@ -841,14 +946,21 @@
 	      break;
 	  if (!tmp)
             {
-              send_ra(now, param.iface, param.name, NULL); 
+              struct iname *if_tmp;
+              for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next)
+              {
+                if ((strcmp(if_tmp->name, param.name) == 0) &&
+                    ((if_tmp->is_ra_adv_dis == false) ||
+                     (if_tmp->final_ra_sent_count < MAX_FINAL_RTR_ADVERTISEMENTS)) &&
+                     (if_tmp->first_ra_delayed)) {
+                  send_ra(now, param.iface, param.name, NULL);
 
-              /* Also send on all interfaces that are aliases of this
+                /* Also send on all interfaces that are aliases of this
                  one. */
-              for (aparam.bridge = daemon->bridges;
+                for (aparam.bridge = daemon->bridges;
                    aparam.bridge;
                    aparam.bridge = aparam.bridge->next)
-                if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
+                  if ((int)if_nametoindex(aparam.bridge->iface) == param.iface)
                   {
                     /* Count the number of alias interfaces for this
                        'bridge', by calling iface_enumerate with
@@ -890,6 +1002,10 @@
                        one --bridge-interface. */
                     break;
                   }
+                } else {
+                  if_tmp->first_ra_delayed = true;
+                }
+              }
             }
 	}
     }      
@@ -972,14 +1088,13 @@
  
 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);
+  struct ra_interface *ra = find_iface_param(iface_name);
+  if (ra && ra->ra_sent_count < (MAX_INITIAL_RTR_ADVERTISEMENTS-1))
+    context->ra_time = now + MAX_INITIAL_RTR_ADVERT_INTERVAL;
   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);
+      unsigned int adv_interval = calc_interval(ra);
+      context->ra_time = now + adv_interval;
     }
 }
 
@@ -996,17 +1111,25 @@
 
 static unsigned int calc_interval(struct ra_interface *ra)
 {
-  int interval = 600;
+  int interval = DEFAULT_RTR_ADV_INTERVAL;
+  if (ra) {
+  unsigned int min_rtr_interval = ra->min_ra_adv_interval;
+  unsigned int max_rtr_interval = ra->max_ra_adv_interval;
+
+  if (max_rtr_interval > MAX_RTR_ADV_INTERVAL_UPPER_BOUND)
+    max_rtr_interval = MAX_RTR_ADV_INTERVAL_UPPER_BOUND;
+  else if (max_rtr_interval < MAX_RTR_ADV_INTERVAL_LOWER_BOUND)
+    max_rtr_interval = MAX_RTR_ADV_INTERVAL_LOWER_BOUND;
+
+  if (min_rtr_interval < MIN_RTR_ADV_INTERVAL)
+    min_rtr_interval = MIN_RTR_ADV_INTERVAL;
+
+  interval = (rand() % (max_rtr_interval - min_rtr_interval + 1)) + min_rtr_interval;
   
-  if (ra && ra->interval != 0)
-    {
-      interval = ra->interval;
-      if (interval > 1800)
-	interval = 1800;
-      else if (interval < 4)
-	interval = 4;
-    }
-  
+  if (((ra->ra_sent_count + 1) < MAX_INITIAL_RTR_ADVERTISEMENTS) &&
+       interval > MAX_INITIAL_RTR_ADVERT_INTERVAL)
+    interval = MAX_INITIAL_RTR_ADVERT_INTERVAL;
+  }
   return (unsigned int)interval;
 }
 
@@ -1036,4 +1159,35 @@
   return 0;
 }
 
+static unsigned char calc_hop_limit(struct ra_interface *ra)
+{
+  if (ra) {
+    return ra->hop_limit;
+  }
+  return (unsigned char)hop_limit;
+}
+
+static unsigned int calc_retrans_time(struct ra_interface *ra)
+{
+  if (ra) {
+    return ra->retrans_time;
+  }
+  return 0;
+}
+
+static unsigned int calc_reachable_time(struct ra_interface *ra)
+{
+  if (ra) {
+    return ra->reachable_time;
+  }
+  return 0;
+}
+
+int64_t timespecdiff(struct timespec const *a, struct timespec const *b)
+{
+	int64_t msec;
+	msec = ((int64_t)a->tv_sec - b->tv_sec) * 1000;
+	msec += ((int64_t)a->tv_nsec - b->tv_nsec) / (1000 * 1000);
+	return msec;
+}
 #endif
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 387d894..70910e1 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -994,7 +994,7 @@
 #ifdef HAVE_IPSET
 		  if (ipsets && (flags & (F_IPV4 | F_IPV6)))
 		    for (ipsets_cur = ipsets->sets; *ipsets_cur; ipsets_cur++)
-		      if (add_to_ipset(*ipsets_cur, &addr, flags, 0) == 0)
+		      if (add_to_ipset(*ipsets_cur, &addr, flags, 0, attl) == 0)
 			log_query((flags & (F_IPV4 | F_IPV6)) | F_IPSET, ipsets->domain, &addr, *ipsets_cur, 1);
 #endif
 #ifdef HAVE_NFTSET
diff --git a/src/unescape_c_string.c b/src/unescape_c_string.c
new file mode 100644
index 0000000..bf4f9d3
--- /dev/null
+++ b/src/unescape_c_string.c
@@ -0,0 +1,230 @@
+
+#include <sys/types.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "unescape_c_string.h"
+
+#ifndef isoctal
+# define isoctal(c)     ((c) >= '0' && (c) <= '7')
+#endif
+#ifndef digittoint
+# define digittoint(c) (((c) >= '0' && (c) <= '9') ? (c) - '0' : tolower(c) - 'a' + 10)
+#endif
+
+typedef enum C_STRING_STATE {
+        C_STRING_STATE_GROUND,
+        C_STRING_STATE_START,
+        C_STRING_STATE_OCTAL2,
+        C_STRING_STATE_OCTAL3,
+        C_STRING_STATE_HEX1,
+        C_STRING_STATE_HEX2
+} C_STRING_STATE;
+
+int
+unescape_c_string(char *cp, char c, int *astate, int flag)
+{
+    if (flag & C_STRING_FLAG_END) {
+        switch (*astate) {
+        case C_STRING_STATE_OCTAL2:
+        case C_STRING_STATE_OCTAL3:
+        case C_STRING_STATE_HEX1:
+        case C_STRING_STATE_HEX2:
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case C_STRING_STATE_GROUND:
+            return C_STRING_RESULT_NOCHAR;
+        default:
+            return C_STRING_RESULT_SYNBAD;
+        }
+    }
+
+    switch (*astate) {
+    case C_STRING_STATE_GROUND:
+        *cp = 0;
+        if (c == '\\') {
+            *astate = C_STRING_STATE_START;
+            return C_STRING_RESULT_PENDING;
+        }
+        *cp = c;
+        return C_STRING_RESULT_VALID;
+
+    case C_STRING_STATE_START:
+        *cp = 0;
+        switch (c) {
+        case '\\':
+            *cp = c;
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+
+        case '0': case '1': case '2': case '3':
+        case '4': case '5': case '6': case '7':
+            *cp = (c - '0');
+            *astate = C_STRING_STATE_OCTAL2;
+            return C_STRING_RESULT_PENDING;
+
+        case 'x':
+            *astate = C_STRING_STATE_HEX1;
+            return C_STRING_RESULT_PENDING;
+
+        case 'n':
+            *cp = '\n';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'r':
+            *cp = '\r';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'b':
+            *cp = '\b';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'a':
+            *cp = '\a';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'v':
+            *cp = '\v';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 't':
+            *cp = '\t';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'f':
+            *cp = '\f';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 's':
+            *cp = ' ';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+        case 'E':
+            *cp = '\033';
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_VALID;
+
+        case '\n':
+        case '$':
+            *astate = C_STRING_STATE_GROUND;
+            return C_STRING_RESULT_NOCHAR;
+        }
+        *astate = C_STRING_STATE_GROUND;
+        return C_STRING_RESULT_SYNBAD;
+
+    case C_STRING_STATE_OCTAL2:
+        if (isoctal((int) (unsigned char) c)) {
+            *cp = (*cp << 3) + (c - '0');
+            *astate = C_STRING_STATE_OCTAL3;
+            return C_STRING_RESULT_PENDING;
+        }
+        *astate = C_STRING_STATE_GROUND;
+        return C_STRING_RESULT_VALIDPUSH;
+
+    case C_STRING_STATE_OCTAL3:
+        *astate = C_STRING_STATE_GROUND;
+        if (isoctal((int) (unsigned char) c)) {
+            *cp = (*cp << 3) + (c - '0');
+            return C_STRING_RESULT_VALID;
+        }
+        return C_STRING_RESULT_VALIDPUSH;
+
+    case C_STRING_STATE_HEX1:
+        if (isxdigit((int) (unsigned char) c)) {
+            *cp = digittoint((int) (unsigned char) c);
+            *astate = C_STRING_STATE_HEX2;
+            return C_STRING_RESULT_PENDING;
+        }
+        *astate = C_STRING_STATE_GROUND;
+        return C_STRING_RESULT_VALIDPUSH;
+
+    case C_STRING_STATE_HEX2:
+        *astate = C_STRING_STATE_GROUND;
+        if (isxdigit((int) (unsigned char) c)) {
+            *cp = (*cp << 4) + digittoint((int) (unsigned char) c);
+            return C_STRING_RESULT_VALID;
+        }
+        return C_STRING_RESULT_VALIDPUSH;
+
+    default:
+        *astate = C_STRING_STATE_GROUND;
+        return C_STRING_RESULT_SYNBAD;
+    }
+}
+
+int
+str_unescape_c_string(char *dst, const char *src)
+{
+    char c;
+    char *start = dst;
+    int state = 0;
+
+    while ((c = *src++)) {
+again:
+        switch (unescape_c_string(dst, c, &state, 0)) {
+        case C_STRING_RESULT_VALID:
+            dst++;
+            break;
+        case C_STRING_RESULT_VALIDPUSH:
+            dst++;
+            goto again;
+        case C_STRING_RESULT_PENDING:
+        case C_STRING_RESULT_NOCHAR:
+            break;
+        default:
+            *dst = 0;
+            return -1;
+        }
+    }
+    if (unescape_c_string(dst, c, &state, C_STRING_FLAG_END) == C_STRING_RESULT_VALID) {
+        dst++;
+    }
+    *dst = 0;
+    return dst - start;
+}
+
+ssize_t
+strn_unescape_c_string(char *dst, const char *src, size_t sz)
+{
+    char c, p;
+    char *start = dst, *end = dst + sz - 1;
+    int state = 0;
+
+    if (sz > 0) {
+        *end = 0;
+    }
+    while ((c = *src++)) {
+again:
+        switch (unescape_c_string(&p, c, &state, 0)) {
+        case C_STRING_RESULT_VALID:
+            if (dst < end) {
+                *dst = p;
+            }
+            dst++;
+            break;
+        case C_STRING_RESULT_VALIDPUSH:
+            if (dst < end) {
+                *dst = p;
+            }
+            dst++;
+            goto again;
+        case 0:
+        case C_STRING_RESULT_NOCHAR:
+            break;
+        default:
+            if (dst <= end) {
+                *dst = 0;
+            }
+            return -1;
+        }
+    }
+    if (unescape_c_string(&p, c, &state, C_STRING_FLAG_END) == C_STRING_RESULT_VALID) {
+        if (dst < end) {
+            *dst = p;
+        }
+        dst++;
+    }
+    if (dst <= end) {
+        *dst = 0;
+    }
+    return dst - start;
+}
diff --git a/src/unescape_c_string.h b/src/unescape_c_string.h
new file mode 100644
index 0000000..46ac055
--- /dev/null
+++ b/src/unescape_c_string.h
@@ -0,0 +1,23 @@
+
+#ifndef UNESCAPE_C_STRING_H
+# define UNESCAPE_C_STRING_H
+
+typedef enum C_STRING_RESULT {
+        C_STRING_RESULT_END,
+        C_STRING_RESULT_NOCHAR,
+        C_STRING_RESULT_PENDING,
+        C_STRING_RESULT_SYNBAD,
+        C_STRING_RESULT_VALID,
+        C_STRING_RESULT_VALIDPUSH
+} C_STRING_RESULT;
+
+typedef enum C_STRING_FLAG {
+        C_STRING_FLAG_NONE,
+        C_STRING_FLAG_END
+} C_STRING_FLAG;
+
+int unescape_c_string(char *cp, char c, int *astate, int flag);
+int str_unescape_c_string(char *dst, const char *src);
+ssize_t strn_unescape_c_string(char *dst, const char *src, size_t sz);
+
+#endif
diff --git a/src/util.c b/src/util.c
index 0c7de44..fd2a935 100644
--- a/src/util.c
+++ b/src/util.c
@@ -476,6 +476,17 @@
     die(_("cannot read monotonic clock: %s"), NULL, EC_MISC);
 
   return ts.tv_sec;
+/* CRADLEPOINT */
+#elif defined(CPLINUX)
+  struct timespec tv;
+
+  if (clock_gettime(CLOCK_MONOTONIC, &tv) == -1) {
+    my_syslog(LOG_ERR, _("Failed to get micro uptime: %s"), strerror(errno));
+    return -1;
+  }
+
+  return tv.tv_sec;
+/* CRADLEPOINT */
 #else
   return time(NULL);
 #endif