]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ipcache.cc
SourceFormat: enforcement
[thirdparty/squid.git] / src / ipcache.cc
index 5a1a0078d523a35e544570ed28572bd6f27fa525..5d1a336eb74fa8419ddaaab87d266b655750a942 100644 (file)
@@ -1,7 +1,4 @@
-
 /*
- * $Id: ipcache.cc,v 1.236 2001/11/13 22:16:24 hno Exp $
- *
  * DEBUG: section 14    IP Cache
  * AUTHOR: Harvest Derived
  *
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
- *  
+ *
  *  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, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  */
 
 #include "squid.h"
+#include "cbdata.h"
+#include "event.h"
+#include "CacheManager.h"
+#include "SquidTime.h"
+#include "Store.h"
+#include "wordlist.h"
+#include "ip/IpAddress.h"
 
-typedef struct _ipcache_entry ipcache_entry;
+/**
+ \defgroup IPCacheAPI IP Cache API
+ \ingroup Components
+ \section Introduction Introduction
+ \par
+ *  The IP cache is a built-in component of squid providing
+ *  Hostname to IP-Number translation functionality and managing
+ *  the involved data-structures. Efficiency concerns require
+ *  mechanisms that allow non-blocking access to these mappings.
+ *  The IP cache usually doesn't block on a request except for
+ *  special cases where this is desired (see below).
+ *
+ \todo IP Cache should have its own API *.h header file.
+ */
 
-struct _ipcache_entry {
+/**
+ \defgroup IPCacheInternal IP Cache Internals
+ \ingroup IPCacheAPI
+ \todo  when IP cache is provided as a class. These sub-groups will be obsolete
+ *     for now they are used to seperate the public and private functions.
+ *     with the private ones all being in IPCachInternal and public in IPCacheAPI
+ *
+ \section InternalOperation Internal Operation
+ *
+ * Internally, the execution flow is as follows: On a miss,
+ * ipcache_getnbhostbyname checks whether a request for
+ * this name is already pending, and if positive, it creates
+ * a new entry using ipcacheAddNew with the IP_PENDING
+ * flag set . Then it calls ipcacheAddPending to add a
+ * request to the queue together with data and handler.  Else,
+ * ipcache_dnsDispatch() is called to directly create a
+ * DNS query or to ipcacheEnqueue() if all no DNS port
+ * is free.  ipcache_call_pending() is called regularly
+ * to walk down the pending list and call handlers. LRU clean-up
+ * is performed through ipcache_purgelru() according to
+ * the ipcache_high threshold.
+ */
+
+/**
+ \ingroup IPCacheAPI
+ *
+ * The data structure used for storing name-address mappings
+ * is a small hashtable (static hash_table *ip_table),
+ * where structures of type ipcache_entry whose most
+ * interesting members are:
+ */
+class ipcache_entry
+{
+public:
     hash_link hash;            /* must be first */
     time_t lastref;
     time_t expires;
@@ -45,26 +95,38 @@ struct _ipcache_entry {
     IPH *handler;
     void *handlerData;
     char *error_message;
+
     struct timeval request_time;
     dlink_node lru;
     unsigned short locks;
+#if DNS_CNAME
+    unsigned short cname_wait;
+#endif
+
     struct {
-       unsigned int negcached:1;
-       unsigned int fromhosts:1;
+        unsigned int negcached:1;
+        unsigned int fromhosts:1;
     } flags;
+
+    int age() const; ///< time passed since request_time or -1 if unknown
 };
 
-static struct {
+/// \ingroup IPCacheInternal
+static struct _ipcache_stats {
     int requests;
     int replies;
     int hits;
     int misses;
     int negative_hits;
-    int errors;
-    int ghbn_calls;            /* # calls to blocking gethostbyname() */
-    int release_locked;
+    int numeric_hits;
+    int rr_a;
+    int rr_aaaa;
+    int rr_cname;
+    int cname_only;
+    int invalid;
 } IpcacheStats;
 
+/// \ingroup IPCacheInternal
 static dlink_list lru_list;
 
 static FREE ipcacheFreeEntry;
@@ -73,77 +135,97 @@ static HLPCB ipcacheHandleReply;
 #else
 static IDNSCB ipcacheHandleReply;
 #endif
-static IPH dummy_handler;
+static IPH ipcacheHandleCnameRecurse;
 static int ipcacheExpiredEntry(ipcache_entry *);
-static int ipcache_testname(void);
 #if USE_DNSSERVERS
-static ipcache_entry *ipcacheParse(const char *buf);
+static int ipcacheParse(ipcache_entry *, const char *buf);
 #else
-static ipcache_entry *ipcacheParse(rfc1035_rr *, int);
+static int ipcacheParse(ipcache_entry *, rfc1035_rr *, int, const char *error);
 #endif
 static ipcache_entry *ipcache_get(const char *);
 static void ipcacheLockEntry(ipcache_entry *);
 static void ipcacheStatPrint(ipcache_entry *, StoreEntry *);
 static void ipcacheUnlockEntry(ipcache_entry *);
-static void ipcacheRelease(ipcache_entry *);
+static void ipcacheRelease(ipcache_entry *, bool dofree = true);
 
+/// \ingroup IPCacheInternal
 static ipcache_addrs static_addrs;
+/// \ingroup IPCacheInternal
 static hash_table *ip_table = NULL;
 
+/// \ingroup IPCacheInternal
 static long ipcache_low = 180;
+/// \ingroup IPCacheInternal
 static long ipcache_high = 200;
 
 #if LIBRESOLV_DNS_TTL_HACK
 extern int _dns_ttl_;
 #endif
 
-static int
-ipcache_testname(void)
+int
+ipcache_entry::age() const
 {
-    wordlist *w = NULL;
-    debug(14, 1) ("Performing DNS Tests...\n");
-    if ((w = Config.dns_testname_list) == NULL)
-       return 1;
-    for (; w; w = w->next) {
-       IpcacheStats.ghbn_calls++;
-       if (gethostbyname(w->key) != NULL)
-           return 1;
-    }
-    return 0;
+    return request_time.tv_sec ? tvSubMsec(request_time, current_time) : -1;
 }
 
-/* removes the given ipcache entry */
+
+
+/**
+ \ingroup IPCacheInternal
+ *
+ * removes the given ipcache entry
+ */
 static void
-ipcacheRelease(ipcache_entry * i)
+ipcacheRelease(ipcache_entry * i, bool dofree)
 {
+    if (!i) {
+        debugs(14, 0, "ipcacheRelease: Releasing entry with i=<NULL>");
+        return;
+    }
+
+    if (!i || !i->hash.key) {
+        debugs(14, 0, "ipcacheRelease: Releasing entry without hash link!");
+        return;
+    }
+
+    debugs(14, 3, "ipcacheRelease: Releasing entry for '" << (const char *) i->hash.key << "'");
+
     hash_remove_link(ip_table, (hash_link *) i);
     dlinkDelete(&i->lru, &lru_list);
-    ipcacheFreeEntry(i);
+    if (dofree)
+        ipcacheFreeEntry(i);
 }
 
+/// \ingroup IPCacheInternal
 static ipcache_entry *
 ipcache_get(const char *name)
 {
     if (ip_table != NULL)
-       return (ipcache_entry *) hash_lookup(ip_table, name);
+        return (ipcache_entry *) hash_lookup(ip_table, name);
     else
-       return NULL;
+        return NULL;
 }
 
+/// \ingroup IPCacheInternal
 static int
 ipcacheExpiredEntry(ipcache_entry * i)
 {
     /* all static entries are locked, so this takes care of them too */
+
     if (i->locks != 0)
-       return 0;
+        return 0;
+
     if (i->addrs.count == 0)
-       if (0 == i->flags.negcached)
-           return 1;
+        if (0 == i->flags.negcached)
+            return 1;
+
     if (i->expires > squid_curtime)
-       return 0;
+        return 0;
+
     return 1;
 }
 
+/// \ingroup IPCacheAPI
 void
 ipcache_purgelru(void *voidnotused)
 {
@@ -152,240 +234,463 @@ ipcache_purgelru(void *voidnotused)
     ipcache_entry *i;
     int removed = 0;
     eventAdd("ipcache_purgelru", ipcache_purgelru, NULL, 10.0, 1);
+
     for (m = lru_list.tail; m; m = prev) {
-       if (memInUse(MEM_IPCACHE_ENTRY) < ipcache_low)
-           break;
-       prev = m->prev;
-       i = m->data;
-       if (i->locks != 0)
-           continue;
-       ipcacheRelease(i);
-       removed++;
+        if (memInUse(MEM_IPCACHE_ENTRY) < ipcache_low)
+            break;
+
+        prev = m->prev;
+
+        i = (ipcache_entry *)m->data;
+
+        if (i->locks != 0)
+            continue;
+
+        ipcacheRelease(i);
+
+        removed++;
     }
-    debug(14, 9) ("ipcache_purgelru: removed %d entries\n", removed);
+
+    debugs(14, 9, "ipcache_purgelru: removed " << removed << " entries");
 }
 
-/* purges entries added from /etc/hosts (or whatever). */
+/**
+ \ingroup IPCacheInternal
+ *
+ * purges entries added from /etc/hosts (or whatever).
+ */
 static void
 purge_entries_fromhosts(void)
 {
     dlink_node *m = lru_list.head;
     ipcache_entry *i = NULL, *t;
+
     while (m) {
-       if (i != NULL) {        /* need to delay deletion */
-           ipcacheRelease(i);  /* we just override locks */
-           i = NULL;
-       }
-       t = m->data;
-       if (t->flags.fromhosts)
-           i = t;
-       m = m->next;
+        if (i != NULL) {       /* need to delay deletion */
+            ipcacheRelease(i); /* we just override locks */
+            i = NULL;
+        }
+
+        t = (ipcache_entry*)m->data;
+
+        if (t->flags.fromhosts)
+            i = t;
+
+        m = m->next;
     }
+
     if (i != NULL)
-       ipcacheRelease(i);
+        ipcacheRelease(i);
 }
 
-/* create blank ipcache_entry */
+/**
+ \ingroup IPCacheInternal
+ *
+ * create blank ipcache_entry
+ */
 static ipcache_entry *
 ipcacheCreateEntry(const char *name)
 {
     static ipcache_entry *i;
-    i = memAllocate(MEM_IPCACHE_ENTRY);
+    i = (ipcache_entry *)memAllocate(MEM_IPCACHE_ENTRY);
     i->hash.key = xstrdup(name);
     i->expires = squid_curtime + Config.negativeDnsTtl;
     return i;
 }
 
+/// \ingroup IPCacheInternal
 static void
 ipcacheAddEntry(ipcache_entry * i)
 {
-    hash_link *e = hash_lookup(ip_table, i->hash.key);
+    hash_link *e = (hash_link *)hash_lookup(ip_table, i->hash.key);
+
+#if DNS_CNAME
+    /* INET6 : should NOT be adding this entry until all CNAME have been received. */
+    assert(i->cname_wait == 0);
+#endif
+
     if (NULL != e) {
-       /* avoid colission */
-       ipcache_entry *q = (ipcache_entry *) e;
-       ipcacheRelease(q);
+        /* avoid colission */
+        ipcache_entry *q = (ipcache_entry *) e;
+#if DNS_CNAME
+        if (q == i)  {
+            /* can occur with Multiple-depth CNAME Recursion if parent returned early with additional */
+            /* just need to drop from the hash without releasing actual memory */
+            ipcacheRelease(q, false);
+        } else
+#endif
+            ipcacheRelease(q);
     }
+
     hash_join(ip_table, &i->hash);
     dlinkAdd(i, &i->lru, &lru_list);
     i->lastref = squid_curtime;
 }
 
-/* walks down the pending list, calling handlers */
+/**
+ \ingroup IPCacheInternal
+ *
+ * walks down the pending list, calling handlers
+ */
 static void
-ipcacheCallback(ipcache_entry * i)
+ipcacheCallback(ipcache_entry *i, int wait)
 {
-    IPH *handler = i->handler;
-    void *handlerData = i->handlerData;
+    IPH *callback = i->handler;
+    void *cbdata = NULL;
     i->lastref = squid_curtime;
+
+    if (!i->handler)
+        return;
+
     ipcacheLockEntry(i);
-    if (NULL == handler)
-       return;
+
+    callback = i->handler;
+
     i->handler = NULL;
-    i->handlerData = NULL;
-    if (cbdataValid(handlerData)) {
-       dns_error_message = i->error_message;
-       handler(i->flags.negcached ? NULL : &i->addrs, handlerData);
+
+    if (cbdataReferenceValidDone(i->handlerData, &cbdata)) {
+        const DnsLookupDetails details(i->error_message, wait);
+        callback((i->addrs.count ? &i->addrs : NULL), details, cbdata);
     }
-    cbdataUnlock(handlerData);
+
     ipcacheUnlockEntry(i);
 }
 
-static ipcache_entry *
+/// \ingroup IPCacheAPI
 #if USE_DNSSERVERS
-ipcacheParse(const char *inbuf)
+static int
+ipcacheParse(ipcache_entry *i, const char *inbuf)
 {
     LOCAL_ARRAY(char, buf, DNS_INBUF_SZ);
     char *token;
-    static ipcache_entry i;
-    int j;
-    int k;
     int ipcount = 0;
     int ttl;
-    char A[32][16];
-    memset(&i, '\0', sizeof(i));
-    i.expires = squid_curtime;
-    i.flags.negcached = 1;
+    char *A[32];
+    const char *name = (const char *)i->hash.key;
+    i->expires = squid_curtime + Config.negativeDnsTtl;
+    i->flags.negcached = 1;
+    safe_free(i->addrs.in_addrs);
+    safe_free(i->addrs.bad_mask);
+    safe_free(i->error_message);
+    i->addrs.count = 0;
+
     if (inbuf == NULL) {
-       debug(14, 1) ("ipcacheParse: Got <NULL> reply\n");
-       i.error_message = xstrdup("Internal Squid Error");
-       return &i;
+        debugs(14, 1, "ipcacheParse: Got <NULL> reply");
+        i->error_message = xstrdup("Internal Error");
+        return -1;
     }
+
     xstrncpy(buf, inbuf, DNS_INBUF_SZ);
-    debug(14, 5) ("ipcacheParse: parsing: {%s}\n", buf);
+    debugs(14, 5, "ipcacheParse: parsing: {" << buf << "}");
     token = strtok(buf, w_space);
+
     if (NULL == token) {
-       debug(14, 1) ("ipcacheParse: Got <NULL>, expecting '$addr'\n");
-       return &i;
+        debugs(14, 1, "ipcacheParse: expecting result, got '" << inbuf << "'");
+
+        i->error_message = xstrdup("Internal Error");
+        return -1;
     }
+
     if (0 == strcmp(token, "$fail")) {
-       i.expires = squid_curtime + Config.negativeDnsTtl;
-       token = strtok(NULL, "\n");
-       assert(NULL != token);
-       i.error_message = xstrdup(token);
-       return &i;
+        token = strtok(NULL, "\n");
+        assert(NULL != token);
+        i->error_message = xstrdup(token);
+        return 0;
     }
+
     if (0 != strcmp(token, "$addr")) {
-       debug(14, 1) ("ipcacheParse: Got '%s', expecting '$addr'\n", token);
-       return &i;
+        debugs(14, 1, "ipcacheParse: expecting '$addr', got '" << inbuf << "' in response to '" << name << "'");
+
+        i->error_message = xstrdup("Internal Error");
+        return -1;
     }
+
     token = strtok(NULL, w_space);
+
     if (NULL == token) {
-       debug(14, 1) ("ipcacheParse: Got <NULL>, expecting TTL\n");
-       return &i;
+        debugs(14, 1, "ipcacheParse: expecting TTL, got '" << inbuf << "' in response to '" << name << "'");
+
+        i->error_message = xstrdup("Internal Error");
+        return -1;
     }
-    i.flags.negcached = 0;
+
     ttl = atoi(token);
-    if (ttl > 0)
-       i.expires = squid_curtime + ttl;
-    else
-       i.expires = squid_curtime + Config.positiveDnsTtl;
+
     while (NULL != (token = strtok(NULL, w_space))) {
-       xstrncpy(A[ipcount], token, 16);
-       if (++ipcount == 32)
-           break;
+        A[ipcount] = token;
+
+        if (++ipcount == 32)
+            break;
     }
-    if (0 == ipcount) {
-       i.addrs.in_addrs = NULL;
-       i.addrs.bad_mask = NULL;
-    } else {
-       i.addrs.in_addrs = xcalloc(ipcount, sizeof(struct in_addr));
-       i.addrs.bad_mask = xcalloc(ipcount, sizeof(unsigned char));
+
+    if (ipcount > 0) {
+        int j, k;
+
+        i->addrs.in_addrs = (IpAddress *)xcalloc(ipcount, sizeof(IpAddress));
+        for (int l = 0; l < ipcount; l++)
+            i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
+        i->addrs.bad_mask = (unsigned char *)xcalloc(ipcount, sizeof(unsigned char));
+        memset(i->addrs.bad_mask, 0, sizeof(unsigned char) * ipcount);
+
+        for (j = 0, k = 0; k < ipcount; k++) {
+            if ( i->addrs.in_addrs[j] = A[k] )
+                j++;
+            else
+                debugs(14, 1, "ipcacheParse: Invalid IP address '" << A[k] << "' in response to '" << name << "'");
+        }
+
+        i->addrs.count = (unsigned char) j;
     }
-    for (j = 0, k = 0; k < ipcount; k++) {
-       if (safe_inet_addr(A[k], &i.addrs.in_addrs[j]))
-           j++;
-       else
-           debug(14, 1) ("ipcacheParse: Invalid IP address '%s'\n", A[k]);
+
+    if (i->addrs.count <= 0) {
+        debugs(14, 1, "ipcacheParse: No addresses in response to '" << name << "'");
+        return -1;
     }
-    i.addrs.count = (unsigned char) j;
-    return &i;
+
+    if (ttl == 0 || ttl > Config.positiveDnsTtl)
+        ttl = Config.positiveDnsTtl;
+
+    if (ttl < Config.negativeDnsTtl)
+        ttl = Config.negativeDnsTtl;
+
+    i->expires = squid_curtime + ttl;
+
+    i->flags.negcached = 0;
+
+    return i->addrs.count;
 }
+
 #else
-ipcacheParse(rfc1035_rr * answers, int nr)
+static int
+ipcacheParse(ipcache_entry *i, rfc1035_rr * answers, int nr, const char *error_message)
 {
-    static ipcache_entry i;
     int k;
-    int j;
+    int j = 0;
     int na = 0;
-    memset(&i, '\0', sizeof(i));
-    i.expires = squid_curtime + Config.negativeDnsTtl;
-    i.flags.negcached = 1;
+    int ttl = 0;
+    const char *name = (const char *)i->hash.key;
+    int cname_found = 0;
+
+    i->expires = squid_curtime + Config.negativeDnsTtl;
+    i->flags.negcached = 1;
+    safe_free(i->addrs.in_addrs);
+    assert(i->addrs.in_addrs == NULL);
+    safe_free(i->addrs.bad_mask);
+    assert(i->addrs.bad_mask == NULL);
+    safe_free(i->error_message);
+    assert(i->error_message == NULL);
+    i->addrs.count = 0;
+
     if (nr < 0) {
-       debug(14, 3) ("ipcacheParse: Lookup failed (error %d)\n",
-           rfc1035_errno);
-       assert(rfc1035_error_message);
-       i.error_message = xstrdup(rfc1035_error_message);
-       return &i;
+        debugs(14, 3, "ipcacheParse: Lookup failed '" << error_message << "' for '" << (const char *)i->hash.key << "'");
+        i->error_message = xstrdup(error_message);
+        return -1;
     }
+
     if (nr == 0) {
-       debug(14, 3) ("ipcacheParse: No DNS records\n");
-       i.error_message = xstrdup("No DNS records");
-       return &i;
+        debugs(14, 3, "ipcacheParse: No DNS records in response to '" << name << "'");
+        i->error_message = xstrdup("No DNS records");
+        return -1;
     }
+
     assert(answers);
-    for (j = 0, k = 0; k < nr; k++) {
-       if (answers[k].type != RFC1035_TYPE_A)
-           continue;
-       if (answers[k].class != RFC1035_CLASS_IN)
-           continue;
-       na++;
+
+    for (k = 0; k < nr; k++) {
+
+#if USE_IPV6
+        if (answers[k].type == RFC1035_TYPE_AAAA) {
+            if (answers[k].rdlength != sizeof(struct in6_addr)) {
+                debugs(14, 1, "ipcacheParse: Invalid IPv6 address in response to '" << name << "'");
+                continue;
+            }
+            na++;
+            IpcacheStats.rr_aaaa++;
+            continue;
+        }
+#endif
+
+        if (answers[k].type == RFC1035_TYPE_A) {
+            if (answers[k].rdlength != sizeof(struct in_addr)) {
+                debugs(14, 1, "ipcacheParse: Invalid IPv4 address in response to '" << name << "'");
+                continue;
+            }
+            na++;
+            IpcacheStats.rr_a++;
+            continue;
+        }
+
+        /* With A and AAAA, the CNAME does not necessarily come with additional records to use. */
+        if (answers[k].type == RFC1035_TYPE_CNAME) {
+            cname_found=1;
+            IpcacheStats.rr_cname++;
+
+#if DNS_CNAME
+            debugs(14, 5, "ipcacheParse: " << name << " CNAME " << answers[k].rdata << " (checking destination: " << i << ").");
+            const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
+            if (res) {
+                na += res->count;
+                debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " already has " << res->count << " IPs cached.");
+            } else {
+                /* keep going on this, but flag the fact that we need to wait for a CNAME lookup to finish */
+                debugs(14, 5, "ipcacheParse: CNAME " << answers[k].rdata << " has no IPs! Recursing.");
+                ipcache_nbgethostbyname(answers[k].rdata, ipcacheHandleCnameRecurse, new generic_cbdata(i) );
+                i->cname_wait++;
+            }
+#endif /* DNS_CNAME */
+
+            continue;
+        }
+
+        // otherwise its an unknown RR. debug at level 9 since we usually want to ignore these and they are common.
+        debugs(14, 9, HERE << "Unknown RR type received: type=" << answers[k].type << " starting at " << &(answers[k]) );
+    }
+
+#if DNS_CNAME
+    if (na == 0 && i->cname_wait >0 ) {
+        /* don't set any error message (yet). Allow recursion to do its work first. */
+        IpcacheStats.cname_only++;
+        return 0;
     }
+#endif /* DNS_CNAME */
+
     if (na == 0) {
-       debug(14, 1) ("ipcacheParse: No Address records\n");
-       i.error_message = xstrdup("No Address records");
-       return &i;
-    }
-    i.flags.negcached = 0;
-    i.addrs.in_addrs = xcalloc(na, sizeof(struct in_addr));
-    i.addrs.bad_mask = xcalloc(na, sizeof(unsigned char));
-    i.addrs.count = (unsigned char) na;
+        debugs(14, 1, "ipcacheParse: No Address records in response to '" << name << "'");
+        i->error_message = xstrdup("No Address records");
+        if (cname_found)
+            IpcacheStats.cname_only++;
+        return 0;
+    }
+
+    i->addrs.in_addrs = (IpAddress *)xcalloc(na, sizeof(IpAddress));
+    for (int l = 0; l < na; l++)
+        i->addrs.in_addrs[l].SetEmpty(); // perform same init actions as constructor would.
+    i->addrs.bad_mask = (unsigned char *)xcalloc(na, sizeof(unsigned char));
+
     for (j = 0, k = 0; k < nr; k++) {
-       if (answers[k].type != RFC1035_TYPE_A)
-           continue;
-       if (answers[k].class != RFC1035_CLASS_IN)
-           continue;
-       if (j == 0)
-           i.expires = squid_curtime + answers[k].ttl;
-       assert(answers[k].rdlength == 4);
-       xmemcpy(&i.addrs.in_addrs[j++], answers[k].rdata, 4);
-       debug(14, 3) ("ipcacheParse: #%d %s\n",
-           j - 1,
-           inet_ntoa(i.addrs.in_addrs[j - 1]));
+
+        if (answers[k].type == RFC1035_TYPE_A) {
+            if (answers[k].rdlength != sizeof(struct in_addr))
+                continue;
+
+            struct in_addr temp;
+            xmemcpy(&temp, answers[k].rdata, sizeof(struct in_addr));
+            i->addrs.in_addrs[j] = temp;
+
+            debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j]);
+            j++;
+
+#if USE_IPV6
+        } else if (answers[k].type == RFC1035_TYPE_AAAA) {
+            if (answers[k].rdlength != sizeof(struct in6_addr))
+                continue;
+
+            struct in6_addr temp;
+            xmemcpy(&temp, answers[k].rdata, sizeof(struct in6_addr));
+            i->addrs.in_addrs[j] = temp;
+
+            debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
+            j++;
+#endif
+        }
+#if DNS_CNAME
+        else if (answers[k].type == RFC1035_TYPE_CNAME) {
+            debugs(14, 3, "ipcacheParse: " << name << " #x CNAME " << answers[k].rdata);
+            const ipcache_addrs *res = ipcache_gethostbyname(answers[k].rdata, 0);
+            if (res) {
+                /* NP: the results of *that* query need to be integrated in place of the CNAME */
+                /* Ideally we should also integrate the min TTL of the above IPA's into ttl.   */
+                for (int l = 0; l < res->count; l++, j++) {
+                    i->addrs.in_addrs[j] = res->in_addrs[l];
+                    debugs(14, 3, "ipcacheParse: " << name << " #" << j << " " << i->addrs.in_addrs[j] );
+                }
+            } else {
+                debugs(14, 9, "ipcacheParse: " << answers[k].rdata << " (CNAME) waiting on A/AAAA records.");
+            }
+        }
+#endif /* DNS_CNAME */
+
+        if (ttl == 0 || (int) answers[k].ttl < ttl)
+            ttl = answers[k].ttl;
     }
+
     assert(j == na);
-    return &i;
+
+    if (na < 256)
+        i->addrs.count = (unsigned char) na;
+    else
+        i->addrs.count = 255;
+
+    if (ttl > Config.positiveDnsTtl)
+        ttl = Config.positiveDnsTtl;
+
+    if (ttl < Config.negativeDnsTtl)
+        ttl = Config.negativeDnsTtl;
+
+    i->expires = squid_curtime + ttl;
+
+    i->flags.negcached = 0;
+
+#if DNS_CNAME
+    /* SPECIAL CASE: may get here IFF CNAME received with Additional records */
+    /*               reurn  0/'wait for further details' value.              */
+    /*               NP: 'No DNS Results' is a return -1 +msg                */
+    if (i->cname_wait)
+        return 0;
+    else
+#endif /* DNS_CNAME */
+        return i->addrs.count;
 }
+
 #endif
 
+/// \ingroup IPCacheInternal
 static void
 #if USE_DNSSERVERS
 ipcacheHandleReply(void *data, char *reply)
 #else
-ipcacheHandleReply(void *data, rfc1035_rr * answers, int na)
+ipcacheHandleReply(void *data, rfc1035_rr * answers, int na, const char *error_message)
 #endif
 {
-    generic_cbdata *c = data;
-    ipcache_entry *i = c->data;
-    ipcache_entry *x = NULL;
-    cbdataFree(c);
-    c = NULL;
+    int done;
+    ipcache_entry *i;
+    static_cast<generic_cbdata *>(data)->unwrap(&i);
     IpcacheStats.replies++;
-    statHistCount(&statCounter.dns.svc_time,
-       tvSubMsec(i->request_time, current_time));
+    const int age = i->age();
+    statHistCount(&statCounter.dns.svc_time, age);
+
 #if USE_DNSSERVERS
-    x = ipcacheParse(reply);
+
+    done = ipcacheParse(i, reply);
 #else
-    x = ipcacheParse(answers, na);
+
+    done = ipcacheParse(i, answers, na, error_message);
+
+    /* If we have not produced either IPs or Error immediately, wait for recursion to finish. */
+    if (done != 0 || error_message != NULL)
 #endif
-    assert(x);
-    i->addrs = x->addrs;
-    i->error_message = x->error_message;
-    i->expires = x->expires;
-    i->flags = x->flags;
-    ipcacheAddEntry(i);
-    ipcacheCallback(i);
+
+    {
+        ipcacheAddEntry(i);
+        ipcacheCallback(i, age);
+    }
 }
 
+/**
+ \ingroup IPCacheAPI
+ *
+ \param name           Host to resolve.
+ \param handler                Pointer to the function to be called when the reply
+ *                     from the IP cache (or the DNS if the IP cache misses)
+ \param handlerData    Information that is passed to the handler and does not affect the IP cache.
+ *
+ * XXX: on hits and some errors, the handler is called immediately instead
+ * of scheduling an async call. This reentrant behavior means that the
+ * user job must be extra careful after calling ipcache_nbgethostbyname,
+ * especially if the handler destroys the job. Moreover, the job has
+ * no way of knowing whether the reentrant call happened. commConnectStart
+ * protects the job by scheduling an async call, but some user code calls
+ * ipcache_nbgethostbyname directly.
+ */
 void
 ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
 {
@@ -393,305 +698,695 @@ ipcache_nbgethostbyname(const char *name, IPH * handler, void *handlerData)
     const ipcache_addrs *addrs = NULL;
     generic_cbdata *c;
     assert(handler != NULL);
-    debug(14, 4) ("ipcache_nbgethostbyname: Name '%s'.\n", name);
+    debugs(14, 4, "ipcache_nbgethostbyname: Name '" << name << "'.");
     IpcacheStats.requests++;
+
     if (name == NULL || name[0] == '\0') {
-       debug(14, 4) ("ipcache_nbgethostbyname: Invalid name!\n");
-       handler(NULL, handlerData);
-       return;
+        debugs(14, 4, "ipcache_nbgethostbyname: Invalid name!");
+        IpcacheStats.invalid++;
+        const DnsLookupDetails details("Invalid hostname", -1); // error, no lookup
+        handler(NULL, details, handlerData);
+        return;
     }
+
     if ((addrs = ipcacheCheckNumeric(name))) {
-       handler(addrs, handlerData);
-       return;
+        debugs(14, 4, "ipcache_nbgethostbyname: BYPASS for '" << name << "' (already numeric)");
+        IpcacheStats.numeric_hits++;
+        const DnsLookupDetails details(NULL, -1); // no error, no lookup
+        handler(addrs, details, handlerData);
+        return;
     }
+
     i = ipcache_get(name);
+
     if (NULL == i) {
-       /* miss */
-       (void) 0;
+        /* miss */
+        (void) 0;
     } else if (ipcacheExpiredEntry(i)) {
-       /* hit, but expired -- bummer */
-       ipcacheRelease(i);
-       i = NULL;
+        /* hit, but expired -- bummer */
+        ipcacheRelease(i);
+        i = NULL;
     } else {
-       /* hit */
-       debug(14, 4) ("ipcache_nbgethostbyname: HIT for '%s'\n", name);
-       if (i->flags.negcached)
-           IpcacheStats.negative_hits++;
-       else
-           IpcacheStats.hits++;
-       i->handler = handler;
-       i->handlerData = handlerData;
-       cbdataLock(handlerData);
-       ipcacheCallback(i);
-       return;
-    }
-    debug(14, 5) ("ipcache_nbgethostbyname: MISS for '%s'\n", name);
+        /* hit */
+        debugs(14, 4, "ipcache_nbgethostbyname: HIT for '" << name << "'");
+
+        if (i->flags.negcached)
+            IpcacheStats.negative_hits++;
+        else
+            IpcacheStats.hits++;
+
+        i->handler = handler;
+
+        i->handlerData = cbdataReference(handlerData);
+
+        ipcacheCallback(i, -1); // no lookup
+
+        return;
+    }
+
+    debugs(14, 5, "ipcache_nbgethostbyname: MISS for '" << name << "'");
     IpcacheStats.misses++;
     i = ipcacheCreateEntry(name);
     i->handler = handler;
-    i->handlerData = handlerData;
-    cbdataLock(handlerData);
+    i->handlerData = cbdataReference(handlerData);
     i->request_time = current_time;
-    c = cbdataAlloc(generic_cbdata);
-    c->data = i;
+    c = new generic_cbdata(i);
 #if USE_DNSSERVERS
+
     dnsSubmit(hashKeyStr(&i->hash), ipcacheHandleReply, c);
 #else
+
     idnsALookup(hashKeyStr(&i->hash), ipcacheHandleReply, c);
 #endif
 }
 
-/* initialize the ipcache */
+/// \ingroup IPCacheInternal
+static void
+ipcacheRegisterWithCacheManager(void)
+{
+    CacheManager::GetInstance()->
+    registerAction("ipcache",
+                   "IP Cache Stats and Contents",
+                   stat_ipcache_get, 0, 1);
+}
+
+
+/**
+ \ingroup IPCacheAPI
+ *
+ * Initialize the ipcache.
+ * Is called from mainInitialize() after disk initialization
+ * and prior to the reverse FQDNCache initialization
+ */
 void
 ipcache_init(void)
 {
     int n;
-    debug(14, 3) ("Initializing IP Cache...\n");
+    debugs(14, DBG_IMPORTANT, "Initializing IP Cache...");
     memset(&IpcacheStats, '\0', sizeof(IpcacheStats));
     memset(&lru_list, '\0', sizeof(lru_list));
-    /* test naming lookup */
-    if (!opt_dns_tests) {
-       debug(14, 4) ("ipcache_init: Skipping DNS name lookup tests.\n");
-    } else if (!ipcache_testname()) {
-       fatal("ipcache_init: DNS name lookup tests failed.");
-    } else {
-       debug(14, 1) ("Successful DNS name lookup tests...\n");
-    }
     memset(&static_addrs, '\0', sizeof(ipcache_addrs));
-    static_addrs.in_addrs = xcalloc(1, sizeof(struct in_addr));
-    static_addrs.bad_mask = xcalloc(1, sizeof(unsigned char));
+
+    static_addrs.in_addrs = (IpAddress *)xcalloc(1, sizeof(IpAddress));
+    static_addrs.in_addrs->SetEmpty(); // properly setup the IpAddress!
+    static_addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
     ipcache_high = (long) (((float) Config.ipcache.size *
-           (float) Config.ipcache.high) / (float) 100);
+                            (float) Config.ipcache.high) / (float) 100);
     ipcache_low = (long) (((float) Config.ipcache.size *
-           (float) Config.ipcache.low) / (float) 100);
+                           (float) Config.ipcache.low) / (float) 100);
     n = hashPrime(ipcache_high / 4);
     ip_table = hash_create((HASHCMP *) strcmp, n, hash4);
-    cachemgrRegister("ipcache",
-       "IP Cache Stats and Contents",
-       stat_ipcache_get, 0, 1);
     memDataInit(MEM_IPCACHE_ENTRY, "ipcache_entry", sizeof(ipcache_entry), 0);
+
+    ipcacheRegisterWithCacheManager();
 }
 
+/**
+ \ingroup IPCacheAPI
+ *
+ * Is different from ipcache_nbgethostbyname in that it only checks
+ * if an entry exists in the cache and does not by default contact the DNS,
+ * unless this is requested, by setting the flags.
+ *
+ \param name           Host name to resolve.
+ \param flags          Default is NULL, set to IP_LOOKUP_IF_MISS
+ *                     to explicitly perform DNS lookups.
+ *
+ \retval NULL  An error occured during lookup
+ \retval NULL  No results available in cache and no lookup specified
+ \retval *     Pointer to the ipcahce_addrs structure containing the lookup results
+ */
 const ipcache_addrs *
 ipcache_gethostbyname(const char *name, int flags)
 {
     ipcache_entry *i = NULL;
     ipcache_addrs *addrs;
     assert(name);
-    debug(14, 3) ("ipcache_gethostbyname: '%s', flags=%x\n", name, flags);
+    debugs(14, 3, "ipcache_gethostbyname: '" << name  << "', flags=" << std::hex << flags);
     IpcacheStats.requests++;
     i = ipcache_get(name);
+
     if (NULL == i) {
-       (void) 0;
+        (void) 0;
     } else if (ipcacheExpiredEntry(i)) {
-       ipcacheRelease(i);
-       i = NULL;
+        ipcacheRelease(i);
+        i = NULL;
     } else if (i->flags.negcached) {
-       IpcacheStats.negative_hits++;
-       dns_error_message = i->error_message;
-       return NULL;
+        IpcacheStats.negative_hits++;
+        // ignore i->error_message: the caller just checks IP cache presence
+        return NULL;
     } else {
-       IpcacheStats.hits++;
-       i->lastref = squid_curtime;
-       return &i->addrs;
+        IpcacheStats.hits++;
+        i->lastref = squid_curtime;
+        // ignore i->error_message: the caller just checks IP cache presence
+        return &i->addrs;
     }
-    if ((addrs = ipcacheCheckNumeric(name)))
-       return addrs;
+
+    /* no entry [any more] */
+
+    if ((addrs = ipcacheCheckNumeric(name))) {
+        IpcacheStats.numeric_hits++;
+        return addrs;
+    }
+
     IpcacheStats.misses++;
+
     if (flags & IP_LOOKUP_IF_MISS)
-       ipcache_nbgethostbyname(name, dummy_handler, NULL);
+        ipcache_nbgethostbyname(name, ipcacheHandleCnameRecurse, NULL);
+
     return NULL;
 }
 
+/// \ingroup IPCacheInternal
 static void
 ipcacheStatPrint(ipcache_entry * i, StoreEntry * sentry)
 {
     int k;
+    char buf[MAX_IPSTRLEN];
+
+    if (!sentry) {
+        debugs(14, 0, HERE << "CRITICAL: sentry is NULL!");
+        return;
+    }
+
+    if (!i) {
+        debugs(14, 0, HERE << "CRITICAL: ipcache_entry is NULL!");
+        storeAppendPrintf(sentry, "CRITICAL ERROR\n");
+        return;
+    }
+
+    int count = i->addrs.count;
+
     storeAppendPrintf(sentry, " %-32.32s %c%c %6d %6d %2d(%2d)",
-       hashKeyStr(&i->hash),
-       i->flags.fromhosts ? 'H' : ' ',
-       i->flags.negcached ? 'N' : ' ',
-       (int) (squid_curtime - i->lastref),
-       (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
-       (int) i->addrs.count,
-       (int) i->addrs.badcount);
-    for (k = 0; k < (int) i->addrs.count; k++) {
-       storeAppendPrintf(sentry, " %15s-%3s", inet_ntoa(i->addrs.in_addrs[k]),
-           i->addrs.bad_mask[k] ? "BAD" : "OK ");
+                      hashKeyStr(&i->hash),
+                      i->flags.fromhosts ? 'H' : ' ',
+                      i->flags.negcached ? 'N' : ' ',
+                      (int) (squid_curtime - i->lastref),
+                      (int) ((i->flags.fromhosts ? -1 : i->expires - squid_curtime)),
+                      (int) i->addrs.count,
+                      (int) i->addrs.badcount);
+
+    /** \par
+     * Negative-cached entries have no IPs listed. */
+    if (i->flags.negcached) {
+        storeAppendPrintf(sentry, "\n");
+        return;
+    }
+
+    /** \par
+     * Cached entries have IPs listed with a BNF of:   ip-address '-' ('OK'|'BAD') */
+    for (k = 0; k < count; k++) {
+        /* Display tidy-up: IPv6 are so big make the list vertical */
+        if (k == 0)
+            storeAppendPrintf(sentry, " %45.45s-%3s\n",
+                              i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
+                              i->addrs.bad_mask[k] ? "BAD" : "OK ");
+        else
+            storeAppendPrintf(sentry, "%s %45.45s-%3s\n",
+                              "                                                         ", /* blank-space indenting IP list */
+                              i->addrs.in_addrs[k].NtoA(buf,MAX_IPSTRLEN),
+                              i->addrs.bad_mask[k] ? "BAD" : "OK ");
     }
-    storeAppendPrintf(sentry, "\n");
 }
 
-/* process objects list */
+/**
+ \ingroup IPCacheInternal
+ *
+ * process objects list
+ */
 void
 stat_ipcache_get(StoreEntry * sentry)
 {
     dlink_node *m;
     assert(ip_table != NULL);
     storeAppendPrintf(sentry, "IP Cache Statistics:\n");
-    storeAppendPrintf(sentry, "IPcache Entries: %d\n",
-       memInUse(MEM_IPCACHE_ENTRY));
+    storeAppendPrintf(sentry, "IPcache Entries:  %d\n",
+                      memInUse(MEM_IPCACHE_ENTRY));
     storeAppendPrintf(sentry, "IPcache Requests: %d\n",
-       IpcacheStats.requests);
-    storeAppendPrintf(sentry, "IPcache Hits: %d\n",
-       IpcacheStats.hits);
-    storeAppendPrintf(sentry, "IPcache Negative Hits: %d\n",
-       IpcacheStats.negative_hits);
-    storeAppendPrintf(sentry, "IPcache Misses: %d\n",
-       IpcacheStats.misses);
-    storeAppendPrintf(sentry, "Blocking calls to gethostbyname(): %d\n",
-       IpcacheStats.ghbn_calls);
-    storeAppendPrintf(sentry, "Attempts to release locked entries: %d\n",
-       IpcacheStats.release_locked);
+                      IpcacheStats.requests);
+    storeAppendPrintf(sentry, "IPcache Hits:            %d\n",
+                      IpcacheStats.hits);
+    storeAppendPrintf(sentry, "IPcache Negative Hits:       %d\n",
+                      IpcacheStats.negative_hits);
+    storeAppendPrintf(sentry, "IPcache Numeric Hits:        %d\n",
+                      IpcacheStats.numeric_hits);
+    storeAppendPrintf(sentry, "IPcache Misses:          %d\n",
+                      IpcacheStats.misses);
+    storeAppendPrintf(sentry, "IPcache Retrieved A:     %d\n",
+                      IpcacheStats.rr_a);
+    storeAppendPrintf(sentry, "IPcache Retrieved AAAA:  %d\n",
+                      IpcacheStats.rr_aaaa);
+    storeAppendPrintf(sentry, "IPcache Retrieved CNAME: %d\n",
+                      IpcacheStats.rr_cname);
+    storeAppendPrintf(sentry, "IPcache CNAME-Only Response: %d\n",
+                      IpcacheStats.cname_only);
+    storeAppendPrintf(sentry, "IPcache Invalid Request: %d\n",
+                      IpcacheStats.invalid);
     storeAppendPrintf(sentry, "\n\n");
     storeAppendPrintf(sentry, "IP Cache Contents:\n\n");
-    storeAppendPrintf(sentry, " %-29.29s %3s %6s %6s %1s\n",
-       "Hostname",
-       "Flg",
-       "lstref",
-       "TTL",
-       "N");
-    for (m = lru_list.head; m; m = m->next)
-       ipcacheStatPrint(m->data, sentry);
+    storeAppendPrintf(sentry, " %-31.31s %3s %6s %6s  %4s\n",
+                      "Hostname",
+                      "Flg",
+                      "lstref",
+                      "TTL",
+                      "N(b)");
+
+    for (m = lru_list.head; m; m = m->next) {
+        assert( m->next != m );
+        ipcacheStatPrint((ipcache_entry *)m->data, sentry);
+    }
+}
+
+#if DNS_CNAME
+/**
+ * Takes two IpAddress arrays and merges them into a single array
+ * which is allocated dynamically to fit the number of unique addresses
+ *
+ \param aaddrs One list to merge
+ \param alen   Size of list aaddrs
+ \param baddrs Other list to merge
+ \param alen   Size of list baddrs
+ \param out    Combined list of unique addresses (sorted with IPv6 first in IPv6-mode)
+ \param outlen Size of list out
+ */
+void
+ipcacheMergeIPLists(const IpAddress *aaddrs, const int alen,
+                    const IpAddress *baddrs, const int blen,
+                    IpAddress **out, int &outlen )
+{
+    int fc=0, t=0, c=0;
+
+    IpAddress const *ip4ptrs[255];
+#if USE_IPV6
+    IpAddress const *ip6ptrs[255];
+#endif
+    int num_ip4 = 0;
+    int num_ip6 = 0;
+
+    memset(ip4ptrs, 0, sizeof(IpAddress*)*255);
+#if USE_IPV6
+    memset(ip6ptrs, 0, sizeof(IpAddress*)*255);
+#endif
+
+    // for each unique address in list A - grab ptr
+    for (t = 0; t < alen; t++) {
+        if (aaddrs[t].IsIPv4()) {
+            // check against IPv4 pruned list
+            for (c = 0; c <= num_ip4; c++) {
+                if (ip4ptrs[c] && aaddrs[t] == *(ip4ptrs[c]) ) break; // duplicate.
+            }
+            if (c > num_ip4) {
+                ip4ptrs[num_ip4] = &aaddrs[t];
+                num_ip4++;
+            }
+        }
+#if USE_IPV6
+        else if (aaddrs[t].IsIPv6()) {
+            debugs(14,8, HERE << "A[" << t << "]=IPv6 " << aaddrs[t]);
+            // check against IPv6 pruned list
+            for (c = 0; c <= num_ip6; c++) {
+                if (ip6ptrs[c] && aaddrs[t] == *ip6ptrs[c]) break; // duplicate.
+            }
+            if (c > num_ip6) {
+                ip6ptrs[num_ip6] = &aaddrs[t];
+                num_ip6++;
+            }
+        }
+#endif
+    }
+
+    // for each unique address in list B - grab ptr
+    for (t = 0; t < blen; t++) {
+        if (baddrs[t].IsIPv4()) {
+            // check against IPv4 pruned list
+            for (c = 0; c <= num_ip4; c++) {
+                if (ip4ptrs[c] && baddrs[t] == *ip4ptrs[c]) break; // duplicate.
+            }
+            if (c > num_ip4) {
+                ip4ptrs[num_ip4] = &baddrs[t];
+                num_ip4++;
+            }
+        }
+#if USE_IPV6
+        else if (baddrs[t].IsIPv6()) {
+            // check against IPv6 pruned list
+            for (c = 0; c <= num_ip6; c++) {
+                if (ip6ptrs[c] && baddrs[t] == *ip6ptrs[c]) break; // duplicate.
+            }
+            if (c > num_ip6) {
+                ip6ptrs[num_ip6] = &baddrs[t];
+                num_ip6++;
+            }
+        }
+#endif
+    }
+
+    fc = num_ip6 + num_ip4;
+
+    assert(fc > 0);
+
+    debugs(14, 5, "ipcacheMergeIPLists: Merge " << alen << "+" << blen << " into " << fc << " unique IPs.");
+
+    // copy the old IPs into the new list buffer.
+    (*out) = (IpAddress*)xcalloc(fc, sizeof(IpAddress));
+    outlen=0;
+
+    assert(out != NULL);
+
+#if USE_IPV6
+    /* IPv6 are preferred (tried first) over IPv4 */
+
+    for (int l = 0; outlen < num_ip6; l++, outlen++) {
+        (*out)[outlen] = *ip6ptrs[l];
+        debugs(14, 5, "ipcacheMergeIPLists:  #" << outlen << " " << (*out)[outlen] );
+    }
+#endif /* USE_IPV6 */
+
+    for (int l = 0; outlen < num_ip4; l++, outlen++) {
+        (*out)[outlen] = *ip4ptrs[l];
+        debugs(14, 5, "ipcacheMergeIPLists:  #" << outlen << " " << (*out)[outlen] );
+    }
+
+    assert(outlen == fc); // otherwise something broke badly!
 }
+#endif /* DNS_CNAME */
 
+/// \ingroup IPCacheInternal
+/// Callback.
 static void
-dummy_handler(const ipcache_addrs * addrsnotused, void *datanotused)
+ipcacheHandleCnameRecurse(const ipcache_addrs *addrs, const DnsLookupDetails &, void *cbdata)
 {
-    return;
+#if DNS_CNAME
+    ipcache_entry *i = NULL;
+    char *pname = NULL;
+    IpAddress *tmpbuf = NULL;
+    int fc = 0;
+    int ttl = 0;
+    generic_cbdata* gcb = (generic_cbdata*)cbdata;
+    // count of addrs at parent and child (REQ as .count is a char type!)
+    int ccount = 0, pcount = 0;
+
+    debugs(14, 5, "ipcacheHandleCnameRecurse: Handling basic A/AAAA response.");
+
+    /* IFF no CNAME recursion being processed. do nothing. */
+    if (cbdata == NULL)
+        return;
+
+    gcb->unwrap(&i);
+    assert(i != NULL);
+
+    // make sure we are actualy waiting for a CNAME callback to be run.
+    assert(i->cname_wait > 0);
+    // count this event. its being handled.
+    i->cname_wait--;
+
+    pname = (char*)i->hash.key;
+    assert(pname != NULL);
+
+    debugs(14, 5, "ipcacheHandleCnameRecurse: Handling CNAME recursion. CBDATA('" << gcb->data << "')='" << pname << "' -> " << std::hex << i);
+
+    if (i == NULL) {
+        return; // Parent has expired. Don't merge, just leave for future Ref:
+    }
+
+    /* IFF addrs is NULL (Usually an Error or Timeout occured on lookup.) */
+    /* Ignore it and HOPE that we got some Additional records to use.     */
+    if (addrs == NULL)
+        return;
+
+    ccount = (0+ addrs->count);
+    pcount = (0+ i->addrs.count);
+    ttl = i->expires;
+
+    /* IFF no CNAME results. do none of the processing BUT finish anyway. */
+    if (addrs) {
+
+        debugs(14, 5, "ipcacheHandleCnameRecurse: Merge IP Lists for " << pname << " (" << pcount << "+" << ccount << ")");
+
+        /* add new IP records to entry */
+        tmpbuf = i->addrs.in_addrs;
+        i->addrs.in_addrs = NULL;
+        ipcacheMergeIPLists(tmpbuf, pcount, addrs->in_addrs, ccount, &(i->addrs.in_addrs), fc);
+        debugs(14,8, HERE << "in=" << tmpbuf << ", out=" << i->addrs.in_addrs );
+        assert( (pcount>0 ? tmpbuf!=NULL : tmpbuf==NULL) );
+        safe_free(tmpbuf);
+
+        if ( pcount > 0) {
+            /* IFF the parent initial lookup was given Additional records with A */
+            // clear the 'bad IP mask'
+            safe_free(i->addrs.bad_mask);
+        }
+        // create a new bad IP mask to fit the new size needed.
+        if (fc > 0) {
+            i->addrs.bad_mask = (unsigned char*)xcalloc(fc, sizeof(unsigned char));
+            memset(i->addrs.bad_mask, 0, sizeof(unsigned char)*fc);
+        }
+
+        if (fc < 256)
+            i->addrs.count = (unsigned char) fc;
+        else
+            i->addrs.count = 255;
+
+        if (ttl == 0 || ttl > Config.positiveDnsTtl)
+            ttl = Config.positiveDnsTtl;
+
+        if (ttl < Config.negativeDnsTtl)
+            ttl = Config.negativeDnsTtl;
+
+        i->expires = squid_curtime + ttl;
+
+        i->flags.negcached = 0;
+
+        i->addrs.cur = 0;
+
+        i->addrs.badcount = 0;
+    }
+
+    if (fc == 0) {
+        i->error_message = xstrdup("No DNS Records");
+    }
+
+    /* finish the lookup we were doing on parent when we got side-tracked for CNAME loop */
+    if (i->cname_wait == 0) {
+        ipcacheAddEntry(i);
+        ipcacheCallback(i, i->age()); // age since i creation, includes CNAMEs
+    }
+    // else still more CNAME to be found.
+#endif /* DNS_CNAME */
 }
 
+/// \ingroup IPCacheAPI
 void
 ipcacheInvalidate(const char *name)
 {
     ipcache_entry *i;
+
     if ((i = ipcache_get(name)) == NULL)
-       return;
+        return;
+
     i->expires = squid_curtime;
+
     /*
-     * NOTE, don't call ipcacheRelease here becuase we might be here due
+     * NOTE, don't call ipcacheRelease here because we might be here due
      * to a thread started from a callback.
      */
 }
 
+/// \ingroup IPCacheAPI
+void
+ipcacheInvalidateNegative(const char *name)
+{
+    ipcache_entry *i;
+
+    if ((i = ipcache_get(name)) == NULL)
+        return;
+
+    if (i->flags.negcached)
+        i->expires = squid_curtime;
+
+    /*
+     * NOTE, don't call ipcacheRelease here because we might be here due
+     * to a thread started from a callback.
+     */
+}
+
+/// \ingroup IPCacheAPI
 ipcache_addrs *
 ipcacheCheckNumeric(const char *name)
 {
-    struct in_addr ip;
+
+    IpAddress ip;
     /* check if it's already a IP address in text form. */
-    if (!safe_inet_addr(name, &ip))
-       return NULL;
+
+    /* it may be IPv6-wrapped */
+    if (name[0] == '[') {
+        char *tmp = xstrdup(&name[1]);
+        tmp[strlen(tmp)-1] = '\0';
+        if (!(ip = tmp)) {
+            delete tmp;
+            return NULL;
+        }
+        delete tmp;
+    } else if (!(ip = name))
+        return NULL;
+
+    debugs(14, 4, "ipcacheCheckNumeric: HIT_BYPASS for '" << name << "' == " << ip );
+
     static_addrs.count = 1;
+
     static_addrs.cur = 0;
-    static_addrs.in_addrs[0].s_addr = ip.s_addr;
+
+    static_addrs.in_addrs[0] = ip;
+
     static_addrs.bad_mask[0] = FALSE;
+
     static_addrs.badcount = 0;
+
     return &static_addrs;
 }
 
+/// \ingroup IPCacheInternal
 static void
 ipcacheLockEntry(ipcache_entry * i)
 {
     if (i->locks++ == 0) {
-       dlinkDelete(&i->lru, &lru_list);
-       dlinkAdd(i, &i->lru, &lru_list);
+        dlinkDelete(&i->lru, &lru_list);
+        dlinkAdd(i, &i->lru, &lru_list);
     }
 }
 
+/// \ingroup IPCacheInternal
 static void
 ipcacheUnlockEntry(ipcache_entry * i)
 {
-    assert(i->locks > 0);
+    if (i->locks < 1) {
+        debugs(14, 1, "WARNING: ipcacheEntry unlocked with no lock! locks=" << i->locks);
+        return;
+    }
+
     i->locks--;
+
     if (ipcacheExpiredEntry(i))
-       ipcacheRelease(i);
+        ipcacheRelease(i);
 }
 
+/// \ingroup IPCacheAPI
 void
 ipcacheCycleAddr(const char *name, ipcache_addrs * ia)
 {
     ipcache_entry *i;
     unsigned char k;
     assert(name || ia);
+
     if (NULL == ia) {
-       if ((i = ipcache_get(name)) == NULL)
-           return;
-       if (i->flags.negcached)
-           return;
-       ia = &i->addrs;
+        if ((i = ipcache_get(name)) == NULL)
+            return;
+
+        if (i->flags.negcached)
+            return;
+
+        ia = &i->addrs;
     }
+
     for (k = 0; k < ia->count; k++) {
-       if (++ia->cur == ia->count)
-           ia->cur = 0;
-       if (!ia->bad_mask[ia->cur])
-           break;
+        if (++ia->cur == ia->count)
+            ia->cur = 0;
+
+        if (!ia->bad_mask[ia->cur])
+            break;
     }
+
     if (k == ia->count) {
-       /* All bad, reset to All good */
-       debug(14, 3) ("ipcacheCycleAddr: Changing ALL %s addrs from BAD to OK\n",
-           name);
-       for (k = 0; k < ia->count; k++)
-           ia->bad_mask[k] = 0;
-       ia->badcount = 0;
-       ia->cur = 0;
+        /* All bad, reset to All good */
+        debugs(14, 3, "ipcacheCycleAddr: Changing ALL " << name << " addrs from BAD to OK");
+
+        for (k = 0; k < ia->count; k++)
+            ia->bad_mask[k] = 0;
+
+        ia->badcount = 0;
+
+        ia->cur = 0;
     }
-    debug(14, 3) ("ipcacheCycleAddr: %s now at %s\n", name,
-       inet_ntoa(ia->in_addrs[ia->cur]));
+
+    debugs(14, 3, "ipcacheCycleAddr: " << name << " now at " << ia->in_addrs[ia->cur] << " (" << ia->cur << " of " << ia->count << ")");
 }
 
-/*
- * Marks the given address as BAD and calls ipcacheCycleAddr to
- * advance the current pointer to the next OK address.
+/**
+ \ingroup IPCacheAPI
+ *
+ \param name   domain name to have an IP marked bad
+ \param addr   specific addres to be marked bad
  */
 void
-ipcacheMarkBadAddr(const char *name, struct in_addr addr)
+ipcacheMarkBadAddr(const char *name, IpAddress &addr)
 {
     ipcache_entry *i;
     ipcache_addrs *ia;
     int k;
+
+    /** Does nothing if the domain name does not exist. */
     if ((i = ipcache_get(name)) == NULL)
-       return;
+        return;
+
     ia = &i->addrs;
+
     for (k = 0; k < (int) ia->count; k++) {
-       if (ia->in_addrs[k].s_addr == addr.s_addr)
-           break;
+        if (addr == ia->in_addrs[k] )
+            break;
     }
-    if (k == (int) ia->count)  /* not found */
-       return;
+
+    /** Does nothing if the IP does not exist for the doamin. */
+    if (k == (int) ia->count)
+        return;
+
+    /** Marks the given address as BAD */
     if (!ia->bad_mask[k]) {
-       ia->bad_mask[k] = TRUE;
-       ia->badcount++;
-       debug(14, 2) ("ipcacheMarkBadAddr: %s [%s]\n", name, inet_ntoa(addr));
+        ia->bad_mask[k] = TRUE;
+        ia->badcount++;
+        i->expires = min(squid_curtime + max((time_t)60, Config.negativeDnsTtl), i->expires);
+        debugs(14, 2, "ipcacheMarkBadAddr: " << name << " " << addr );
     }
+
+    /** then calls ipcacheCycleAddr() to advance the current pointer to the next OK address. */
     ipcacheCycleAddr(name, ia);
 }
 
+/// \ingroup IPCacheAPI
 void
-ipcacheMarkGoodAddr(const char *name, struct in_addr addr)
+ipcacheMarkGoodAddr(const char *name, IpAddress &addr)
 {
     ipcache_entry *i;
     ipcache_addrs *ia;
     int k;
+
     if ((i = ipcache_get(name)) == NULL)
-       return;
+        return;
+
     ia = &i->addrs;
+
     for (k = 0; k < (int) ia->count; k++) {
-       if (ia->in_addrs[k].s_addr == addr.s_addr)
-           break;
+        if (addr == ia->in_addrs[k])
+            break;
     }
+
     if (k == (int) ia->count)  /* not found */
-       return;
+        return;
+
     if (!ia->bad_mask[k])      /* already OK */
-       return;
+        return;
+
     ia->bad_mask[k] = FALSE;
+
     ia->badcount--;
-    debug(14, 2) ("ipcacheMarkGoodAddr: %s [%s]\n", name, inet_ntoa(addr));
+
+    debugs(14, 2, "ipcacheMarkGoodAddr: " << name << " " << addr );
 }
 
+/// \ingroup IPCacheInternal
 static void
 ipcacheFreeEntry(void *data)
 {
-    ipcache_entry *i = data;
+    ipcache_entry *i = (ipcache_entry *)data;
     safe_free(i->addrs.in_addrs);
     safe_free(i->addrs.bad_mask);
     safe_free(i->hash.key);
@@ -699,6 +1394,7 @@ ipcacheFreeEntry(void *data)
     memFree(i, MEM_IPCACHE_ENTRY);
 }
 
+/// \ingroup IPCacheAPI
 void
 ipcacheFreeMemory(void)
 {
@@ -707,53 +1403,74 @@ ipcacheFreeMemory(void)
     ip_table = NULL;
 }
 
-/* Recalculate IP cache size upon reconfigure */
+/**
+ \ingroup IPCacheAPI
+ *
+ * Recalculate IP cache size upon reconfigure.
+ * Is called to clear the IPCache's data structures,
+ * cancel all pending requests.
+ */
 void
 ipcache_restart(void)
 {
     ipcache_high = (long) (((float) Config.ipcache.size *
-           (float) Config.ipcache.high) / (float) 100);
+                            (float) Config.ipcache.high) / (float) 100);
     ipcache_low = (long) (((float) Config.ipcache.size *
-           (float) Config.ipcache.low) / (float) 100);
+                           (float) Config.ipcache.low) / (float) 100);
     purge_entries_fromhosts();
 }
 
-/*
- *  adds a "static" entry from /etc/hosts.  
- *  returns 0 upon success, 1 if the ip address is invalid
+/**
+ \ingroup IPCacheAPI
+ *
+ * Adds a "static" entry from /etc/hosts
+ *
+ \param name   Hostname to be linked with IP
+ \param ipaddr IP Address to be cached.
+ *
+ \retval 0     Success.
+ \retval 1     IP address is invalid or other error.
  */
 int
 ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
 {
     ipcache_entry *i;
-    struct in_addr ip;
-    if (!safe_inet_addr(ipaddr, &ip)) {
-       if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
-           debug(14, 3) ("ipcacheAddEntryFromHosts: Skipping IPv6 address '%s'\n", ipaddr);
-       } else {
-           debug(14, 1) ("ipcacheAddEntryFromHosts: Bad IP address '%s'\n",
-               ipaddr);
-       }
-       return 1;
+
+    IpAddress ip;
+
+    if (!(ip = ipaddr)) {
+#if USE_IPV6
+        if (strchr(ipaddr, ':') && strspn(ipaddr, "0123456789abcdefABCDEF:") == strlen(ipaddr)) {
+            debugs(14, 3, "ipcacheAddEntryFromHosts: Skipping IPv6 address '" << ipaddr << "'");
+        } else {
+            debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
+        }
+#else
+        debugs(14, 1, "ipcacheAddEntryFromHosts: Bad IP address '" << ipaddr << "'");
+#endif
+
+        return 1;
     }
+
     if ((i = ipcache_get(name))) {
-       if (1 == i->flags.fromhosts) {
-           ipcacheUnlockEntry(i);
-       } else if (i->locks > 0) {
-           debug(14, 1) ("ipcacheAddEntryFromHosts: can't add static entry"
-               " for locked name '%s'\n", name);
-           return 1;
-       } else {
-           ipcacheRelease(i);
-       }
+        if (1 == i->flags.fromhosts) {
+            ipcacheUnlockEntry(i);
+        } else if (i->locks > 0) {
+            debugs(14, 1, "ipcacheAddEntryFromHosts: can't add static entry for locked name '" << name << "'");
+            return 1;
+        } else {
+            ipcacheRelease(i);
+        }
     }
+
     i = ipcacheCreateEntry(name);
     i->addrs.count = 1;
     i->addrs.cur = 0;
     i->addrs.badcount = 0;
-    i->addrs.in_addrs = xcalloc(1, sizeof(struct in_addr));
-    i->addrs.bad_mask = xcalloc(1, sizeof(unsigned char));
-    i->addrs.in_addrs[0].s_addr = ip.s_addr;
+
+    i->addrs.in_addrs = (IpAddress *)xcalloc(1, sizeof(IpAddress));
+    i->addrs.bad_mask = (unsigned char *)xcalloc(1, sizeof(unsigned char));
+    i->addrs.in_addrs[0] = ip;
     i->addrs.bad_mask[0] = FALSE;
     i->flags.fromhosts = 1;
     ipcacheAddEntry(i);
@@ -762,63 +1479,75 @@ ipcacheAddEntryFromHosts(const char *name, const char *ipaddr)
 }
 
 #ifdef SQUID_SNMP
-/*
+/**
+ \ingroup IPCacheAPI
+ *
  * The function to return the ip cache statistics to via SNMP
  */
-
 variable_list *
 snmp_netIpFn(variable_list * Var, snint * ErrP)
 {
     variable_list *Answer = NULL;
-    debug(49, 5) ("snmp_netIpFn: Processing request:\n");
+    debugs(49, 5, "snmp_netIpFn: Processing request:");
     snmpDebugOid(5, Var->name, Var->name_length);
     *ErrP = SNMP_ERR_NOERROR;
+
     switch (Var->name[LEN_SQ_NET + 1]) {
+
     case IP_ENT:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           memInUse(MEM_IPCACHE_ENTRY),
-           SMI_GAUGE32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      memInUse(MEM_IPCACHE_ENTRY),
+                                      SMI_GAUGE32);
+        break;
+
     case IP_REQ:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.requests,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      IpcacheStats.requests,
+                                      SMI_COUNTER32);
+        break;
+
     case IP_HITS:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.hits,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      IpcacheStats.hits,
+                                      SMI_COUNTER32);
+        break;
+
     case IP_PENDHIT:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           0,
-           SMI_GAUGE32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      0,
+                                      SMI_GAUGE32);
+        break;
+
     case IP_NEGHIT:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.negative_hits,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      IpcacheStats.negative_hits,
+                                      SMI_COUNTER32);
+        break;
+
     case IP_MISS:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.misses,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      IpcacheStats.misses,
+                                      SMI_COUNTER32);
+        break;
+
     case IP_GHBN:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.ghbn_calls,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      0, /* deprecated */
+                                      SMI_COUNTER32);
+        break;
+
     case IP_LOC:
-       Answer = snmp_var_new_integer(Var->name, Var->name_length,
-           IpcacheStats.release_locked,
-           SMI_COUNTER32);
-       break;
+        Answer = snmp_var_new_integer(Var->name, Var->name_length,
+                                      0, /* deprecated */
+                                      SMI_COUNTER32);
+        break;
+
     default:
-       *ErrP = SNMP_ERR_NOSUCHNAME;
-       snmp_var_free(Answer);
-       return (NULL);
+        *ErrP = SNMP_ERR_NOSUCHNAME;
+        snmp_var_free(Answer);
+        return (NULL);
     }
+
     return Answer;
 }