]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/dns_internal.cc
Maintenance: rework SASL detection (#1694)
[thirdparty/squid.git] / src / dns_internal.cc
index 4122a1dbb0c603c65786a5b39f418af4b7d50a98..fb09f8cc277404a7201298e8042ba9bb721d145f 100644 (file)
@@ -1,53 +1,37 @@
 /*
- * DEBUG: section 78    DNS lookups; interacts with lib/rfc1035.c
- * AUTHOR: Duane Wessels
- *
- * SQUID Web Proxy Cache          http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- *  Squid is the result of efforts by numerous individuals from
- *  the Internet community; see the CONTRIBUTORS file for full
- *  details.   Many organizations have provided support for Squid's
- *  development; see the SPONSORS file for full details.  Squid is
- *  Copyrighted (C) 2001 by the Regents of the University of
- *  California; see the COPYRIGHT file for full details.  Squid
- *  incorporates software developed and/or copyrighted by other
- *  sources; see the CREDITS file for full details.
- *
- *  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; 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.
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
  *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
+/* DEBUG: section 78    DNS lookups; interacts with dns/rfc1035.cc */
+
 #include "squid.h"
+#include "base/CodeContext.h"
 #include "base/InstanceId.h"
+#include "base/IoManip.h"
+#include "base/Random.h"
+#include "base/RunnersRegistry.h"
+#include "comm.h"
 #include "comm/Connection.h"
 #include "comm/ConnOpener.h"
-#include "comm.h"
 #include "comm/Loops.h"
+#include "comm/Read.h"
 #include "comm/Write.h"
+#include "debug/Messages.h"
 #include "dlink.h"
+#include "dns/forward.h"
+#include "dns/rfc3596.h"
 #include "event.h"
 #include "fd.h"
 #include "fde.h"
 #include "ip/tools.h"
-#include "Mem.h"
 #include "MemBuf.h"
 #include "mgr/Registration.h"
-#include "rfc3596.h"
+#include "snmp_agent.h"
 #include "SquidConfig.h"
-#include "SquidTime.h"
 #include "Store.h"
 #include "tools.h"
 #include "util.h"
 #if HAVE_ARPA_NAMESER_H
 #include <arpa/nameser.h>
 #endif
+#include <cerrno>
 #if HAVE_RESOLV_H
 #include <resolv.h>
 #endif
-#if HAVE_ERRNO_H
-#include <errno.h>
-#endif
 
 #if _SQUID_WINDOWS_
 #define REG_TCPIP_PARA_INTERFACES "SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces"
@@ -117,88 +99,136 @@ static const char *Rcodes[] = {
     "Bad OPT Version or TSIG Signature Failure"
 };
 
-typedef struct _idns_query idns_query;
-
-typedef struct _ns ns;
-
 typedef struct _sp sp;
 
-typedef struct _nsvc nsvc;
+class idns_query
+{
+    CBDATA_CLASS(idns_query);
+
+public:
+    idns_query():
+        codeContext(CodeContext::Current())
+    {
+        callback = nullptr;
+        memset(&query, 0, sizeof(query));
+        *buf = 0;
+        *name = 0;
+        *orig = 0;
+        memset(&start_t, 0, sizeof(start_t));
+        memset(&sent_t, 0, sizeof(sent_t));
+        memset(&queue_t, 0, sizeof(queue_t));
+    }
+
+    ~idns_query() {
+        if (message)
+            rfc1035MessageDestroy(&message);
+        delete queue;
+        delete slave;
+        // master is just a back-reference
+        cbdataReferenceDone(callback_data);
+    }
 
-struct _idns_query {
     hash_link hash;
     rfc1035_query query;
     char buf[RESOLV_BUFSZ];
     char name[NS_MAXDNAME + 1];
     char orig[NS_MAXDNAME + 1];
-    ssize_t sz;
-    unsigned short query_id; /// random query ID sent to server; changes with every query sent
-    InstanceId<idns_query> xact_id; /// identifies our "transaction", stays constant when query is retried
+    ssize_t sz = 0;
+    unsigned short query_id = 0; ///< random query ID sent to server; changes with every query sent
+    InstanceId<idns_query> xact_id; ///< identifies our "transaction", stays constant when query is retried
 
-    int nsends;
-    int need_vc;
-    bool permit_mdns;
-    int pending;
+    int nsends = 0;
+    int need_vc = 0;
+    bool permit_mdns = false;
+    int pending = 0;
 
     struct timeval start_t;
     struct timeval sent_t;
     struct timeval queue_t;
     dlink_node lru;
+
     IDNSCB *callback;
-    void *callback_data;
-    int attempt;
-    int rcode;
-    idns_query *queue;
-    idns_query *slave;  // single linked list
-    idns_query *master; // single pointer to a shared master
-    unsigned short domain;
-    unsigned short do_searchpath;
-    rfc1035_message *message;
-    int ancount;
-    const char *error;
+    void *callback_data = nullptr;
+    CodeContext::Pointer codeContext; ///< requestor's context
+
+    int attempt = 0;
+    int rcode = 0;
+    idns_query *queue = nullptr;
+    idns_query *slave = nullptr;  // single linked list
+    idns_query *master = nullptr; // single pointer to a shared master
+    unsigned short domain = 0;
+    unsigned short do_searchpath = 0;
+    rfc1035_message *message = nullptr;
+    int ancount = 0;
+    const char *error = nullptr;
 };
+
 InstanceIdDefinitions(idns_query,  "dns");
 
-struct _nsvc {
-    int ns;
+CBDATA_CLASS_INIT(idns_query);
+
+class nsvc
+{
+    CBDATA_CLASS(nsvc);
+
+public:
+    explicit nsvc(size_t nsv) : ns(nsv), msg(new MemBuf()), queue(new MemBuf()) {}
+    ~nsvc();
+
+    size_t ns = 0;
     Comm::ConnectionPointer conn;
-    unsigned short msglen;
-    int read_msglen;
-    MemBuf *msg;
-    MemBuf *queue;
-    bool busy;
+    unsigned short msglen = 0;
+    int read_msglen = 0;
+    MemBuf *msg = nullptr;
+    MemBuf *queue = nullptr;
+    bool busy = true;
 };
 
-struct _ns {
+CBDATA_CLASS_INIT(nsvc);
+
+class ns
+{
+public:
     Ip::Address S;
-    int nqueries;
-    int nreplies;
+    int nqueries = 0;
+    int nreplies = 0;
 #if WHEN_EDNS_RESPONSES_ARE_PARSED
-    int last_seen_edns;
+    int last_seen_edns = 0;
 #endif
-    bool mDNSResolver;
-    nsvc *vc;
+    bool mDNSResolver = false;
+    nsvc *vc = nullptr;
+};
+
+namespace Dns
+{
+
+/// manage DNS internal component
+class ConfigRr : public RegisteredRunner
+{
+public:
+    /* RegisteredRunner API */
+    void startReconfigure() override;
+    void endingShutdown() override;
 };
 
+} // namespace Dns
+
+DefineRunnerRegistratorIn(Dns, ConfigRr);
+
 struct _sp {
     char domain[NS_MAXDNAME];
     int queries;
 };
 
-CBDATA_TYPE(nsvc);
-CBDATA_TYPE(idns_query);
-
-static ns *nameservers = NULL;
-static sp *searchpath = NULL;
-static int nns = 0;
-static int nns_alloc = 0;
+static std::vector<ns> nameservers;
+static sp *searchpath = nullptr;
 static int nns_mdns_count = 0;
 static int npc = 0;
 static int npc_alloc = 0;
 static int ndots = 1;
 static dlink_list lru_list;
 static int event_queued = 0;
-static hash_table *idns_lookup_hash = NULL;
+static hash_table *idns_lookup_hash = nullptr;
 
 /*
  * Notes on EDNS:
@@ -232,12 +262,9 @@ static OBJH idnsStats;
 static void idnsAddNameserver(const char *buf);
 static void idnsAddMDNSNameservers();
 static void idnsAddPathComponent(const char *buf);
-static void idnsFreeNameservers(void);
 static void idnsFreeSearchpath(void);
 static bool idnsParseNameservers(void);
-#if _SQUID_WINDOWS_
 static bool idnsParseResolvConf(void);
-#endif
 #if _SQUID_WINDOWS_
 static bool idnsParseWIN32Registry(void);
 static void idnsParseWIN32SearchList(const char *);
@@ -260,6 +287,7 @@ static void idnsRcodeCount(int, int);
 static CLCB idnsVCClosed;
 static unsigned short idnsQueryID(void);
 static void idnsSendSlaveAAAAQuery(idns_query *q);
+static void idnsCallbackOnEarlyError(IDNSCB *callback, void *cbdata, const char *error);
 
 static void
 idnsCheckMDNS(idns_query *q)
@@ -285,14 +313,14 @@ idnsAddMDNSNameservers()
     // mDNS resolver addresses are explicit multicast group IPs
     if (Ip::EnableIpv6) {
         idnsAddNameserver("FF02::FB");
-        nameservers[nns-1].S.port(5353);
-        nameservers[nns-1].mDNSResolver = true;
+        nameservers.back().S.port(5353);
+        nameservers.back().mDNSResolver = true;
         ++nns_mdns_count;
     }
 
     idnsAddNameserver("224.0.0.251");
-    nameservers[nns-1].S.port(5353);
-    nameservers[nns-1].mDNSResolver = true;
+    nameservers.back().S.port(5353);
+    nameservers.back().mDNSResolver = true;
 
     ++nns_mdns_count;
 }
@@ -318,33 +346,14 @@ idnsAddNameserver(const char *buf)
         return;
     }
 
-    if (nns == nns_alloc) {
-        int oldalloc = nns_alloc;
-        ns *oldptr = nameservers;
-
-        if (nns_alloc == 0)
-            nns_alloc = 2;
-        else
-            nns_alloc <<= 1;
-
-        nameservers = (ns *)xcalloc(nns_alloc, sizeof(*nameservers));
-
-        if (oldptr && oldalloc)
-            memcpy(nameservers, oldptr, oldalloc * sizeof(*nameservers));
-
-        if (oldptr)
-            safe_free(oldptr);
-    }
-
-    assert(nns < nns_alloc);
+    auto &nameserver = nameservers.emplace_back(ns());
     A.port(NS_DEFAULTPORT);
-    nameservers[nns].S = A;
+    nameserver.S = A;
 #if WHEN_EDNS_RESPONSES_ARE_PARSED
-    nameservers[nns].last_seen_edns = RFC1035_DEFAULT_PACKET_SZ;
+    nameserver.last_seen_edns = RFC1035_DEFAULT_PACKET_SZ;
     // TODO generate a test packet to probe this NS from EDNS size and ability.
 #endif
-    debugs(78, 3, "idnsAddNameserver: Added nameserver #" << nns << " (" << A << ")");
-    ++nns;
+    debugs(78, 3, "Added nameserver #" << nameservers.size()-1 << " (" << A << ")");
 }
 
 static void
@@ -376,13 +385,6 @@ idnsAddPathComponent(const char *buf)
     ++npc;
 }
 
-static void
-idnsFreeNameservers(void)
-{
-    safe_free(nameservers);
-    nns = nns_alloc = 0;
-}
-
 static void
 idnsFreeSearchpath(void)
 {
@@ -394,42 +396,38 @@ static bool
 idnsParseNameservers(void)
 {
     bool result = false;
-    for (wordlist *w = Config.dns_nameservers; w; w = w->next) {
-        debugs(78, DBG_IMPORTANT, "Adding nameserver " << w->key << " from squid.conf");
-        idnsAddNameserver(w->key);
+    for (auto &i : Config.dns.nameservers) {
+        debugs(78, Important(15), "Adding nameserver " << i << " from squid.conf");
+        idnsAddNameserver(i.c_str());
         result = true;
     }
     return result;
 }
 
-#if !_SQUID_WINDOWS_
 static bool
 idnsParseResolvConf(void)
 {
-    FILE *fp;
-    char buf[RESOLV_BUFSZ];
-    const char *t;
     bool result = false;
-    fp = fopen(_PATH_RESCONF, "r");
+#if !_SQUID_WINDOWS_
+    FILE *fp = fopen(_PATH_RESCONF, "r");
 
-    if (fp == NULL) {
-        debugs(78, DBG_IMPORTANT, "" << _PATH_RESCONF << ": " << xstrerror());
+    if (!fp) {
+        int xerrno = errno;
+        debugs(78, DBG_IMPORTANT, "" << _PATH_RESCONF << ": " << xstrerr(xerrno));
         return false;
     }
 
-#if _SQUID_CYGWIN_
-    setmode(fileno(fp), O_TEXT);
-#endif
-
+    char buf[RESOLV_BUFSZ];
+    const char *t = nullptr;
     while (fgets(buf, RESOLV_BUFSZ, fp)) {
         t = strtok(buf, w_space);
 
-        if (NULL == t) {
+        if (nullptr == t) {
             continue;
         } else if (strcmp(t, "nameserver") == 0) {
-            t = strtok(NULL, w_space);
+            t = strtok(nullptr, w_space);
 
-            if (NULL == t)
+            if (nullptr == t)
                 continue;
 
             debugs(78, DBG_IMPORTANT, "Adding nameserver " << t << " from " << _PATH_RESCONF);
@@ -438,9 +436,9 @@ idnsParseResolvConf(void)
             result = true;
         } else if (strcmp(t, "domain") == 0) {
             idnsFreeSearchpath();
-            t = strtok(NULL, w_space);
+            t = strtok(nullptr, w_space);
 
-            if (NULL == t)
+            if (nullptr == t)
                 continue;
 
             debugs(78, DBG_IMPORTANT, "Adding domain " << t << " from " << _PATH_RESCONF);
@@ -448,10 +446,10 @@ idnsParseResolvConf(void)
             idnsAddPathComponent(t);
         } else if (strcmp(t, "search") == 0) {
             idnsFreeSearchpath();
-            while (NULL != t) {
-                t = strtok(NULL, w_space);
+            while (nullptr != t) {
+                t = strtok(nullptr, w_space);
 
-                if (NULL == t)
+                if (nullptr == t)
                     continue;
 
                 debugs(78, DBG_IMPORTANT, "Adding domain " << t << " from " << _PATH_RESCONF);
@@ -459,10 +457,10 @@ idnsParseResolvConf(void)
                 idnsAddPathComponent(t);
             }
         } else if (strcmp(t, "options") == 0) {
-            while (NULL != t) {
-                t = strtok(NULL, w_space);
+            while (nullptr != t) {
+                t = strtok(nullptr, w_space);
 
-                if (NULL == t)
+                if (nullptr == t)
                     continue;
 
                 if (strncmp(t, "ndots:", 6) == 0) {
@@ -483,11 +481,10 @@ idnsParseResolvConf(void)
     }
 
     fclose(fp);
+#endif
     return result;
 }
 
-#endif
-
 #if _SQUID_WINDOWS_
 static void
 idnsParseWIN32SearchList(const char * Separator)
@@ -500,26 +497,26 @@ idnsParseWIN32SearchList(const char * Separator)
         DWORD Type = 0;
         DWORD Size = 0;
         LONG Result;
-        Result = RegQueryValueEx(hndKey, "Domain", NULL, &Type, NULL, &Size);
+        Result = RegQueryValueEx(hndKey, "Domain", nullptr, &Type, nullptr, &Size);
 
         if (Result == ERROR_SUCCESS && Size) {
             t = (char *) xmalloc(Size);
-            RegQueryValueEx(hndKey, "Domain", NULL, &Type, (LPBYTE) t, &Size);
+            RegQueryValueEx(hndKey, "Domain", nullptr, &Type, (LPBYTE) t, &Size);
             debugs(78, DBG_IMPORTANT, "Adding domain " << t << " from Registry");
             idnsAddPathComponent(t);
             xfree(t);
         }
-        Result = RegQueryValueEx(hndKey, "SearchList", NULL, &Type, NULL, &Size);
+        Result = RegQueryValueEx(hndKey, "SearchList", nullptr, &Type, nullptr, &Size);
 
         if (Result == ERROR_SUCCESS && Size) {
             t = (char *) xmalloc(Size);
-            RegQueryValueEx(hndKey, "SearchList", NULL, &Type, (LPBYTE) t, &Size);
+            RegQueryValueEx(hndKey, "SearchList", nullptr, &Type, (LPBYTE) t, &Size);
             token = strtok(t, Separator);
 
             while (token) {
                 idnsAddPathComponent(token);
                 debugs(78, DBG_IMPORTANT, "Adding domain " << token << " from Registry");
-                token = strtok(NULL, Separator);
+                token = strtok(nullptr, Separator);
             }
             xfree(t);
         }
@@ -550,34 +547,34 @@ idnsParseWIN32Registry(void)
             DWORD Type = 0;
             DWORD Size = 0;
             LONG Result;
-            Result = RegQueryValueEx(hndKey, "DhcpNameServer", NULL, &Type, NULL, &Size);
+            Result = RegQueryValueEx(hndKey, "DhcpNameServer", nullptr, &Type, nullptr, &Size);
 
             if (Result == ERROR_SUCCESS && Size) {
                 t = (char *) xmalloc(Size);
-                RegQueryValueEx(hndKey, "DhcpNameServer", NULL, &Type, (LPBYTE) t, &Size);
+                RegQueryValueEx(hndKey, "DhcpNameServer", nullptr, &Type, (LPBYTE) t, &Size);
                 token = strtok(t, ", ");
 
                 while (token) {
                     idnsAddNameserver(token);
                     result = true;
                     debugs(78, DBG_IMPORTANT, "Adding DHCP nameserver " << token << " from Registry");
-                    token = strtok(NULL, ",");
+                    token = strtok(nullptr, ",");
                 }
                 xfree(t);
             }
 
-            Result = RegQueryValueEx(hndKey, "NameServer", NULL, &Type, NULL, &Size);
+            Result = RegQueryValueEx(hndKey, "NameServer", nullptr, &Type, nullptr, &Size);
 
             if (Result == ERROR_SUCCESS && Size) {
                 t = (char *) xmalloc(Size);
-                RegQueryValueEx(hndKey, "NameServer", NULL, &Type, (LPBYTE) t, &Size);
+                RegQueryValueEx(hndKey, "NameServer", nullptr, &Type, (LPBYTE) t, &Size);
                 token = strtok(t, ", ");
 
                 while (token) {
                     debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
                     idnsAddNameserver(token);
                     result = true;
-                    token = strtok(NULL, ", ");
+                    token = strtok(nullptr, ", ");
                 }
                 xfree(t);
             }
@@ -607,12 +604,12 @@ idnsParseWIN32Registry(void)
             char *keyname;
             FILETIME ftLastWriteTime;
 
-            if (RegQueryInfoKey(hndKey, NULL, NULL, NULL, &InterfacesCount, &MaxSubkeyLen, NULL, NULL, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
+            if (RegQueryInfoKey(hndKey, nullptr, nullptr, nullptr, &InterfacesCount, &MaxSubkeyLen, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr) == ERROR_SUCCESS) {
                 keyname = (char *) xmalloc(++MaxSubkeyLen);
                 for (i = 0; i < (int) InterfacesCount; ++i) {
                     DWORD j;
                     j = MaxSubkeyLen;
-                    if (RegEnumKeyEx(hndKey, i, keyname, &j, NULL, NULL, NULL, &ftLastWriteTime) == ERROR_SUCCESS) {
+                    if (RegEnumKeyEx(hndKey, i, keyname, &j, nullptr, nullptr, nullptr, &ftLastWriteTime) == ERROR_SUCCESS) {
                         char *newkeyname;
                         newkeyname = (char *) xmalloc(sizeof(REG_TCPIP_PARA_INTERFACES) + j + 2);
                         strcpy(newkeyname, REG_TCPIP_PARA_INTERFACES);
@@ -622,30 +619,30 @@ idnsParseWIN32Registry(void)
                             DWORD Type = 0;
                             DWORD Size = 0;
                             LONG Result;
-                            Result = RegQueryValueEx(hndKey2, "DhcpNameServer", NULL, &Type, NULL, &Size);
+                            Result = RegQueryValueEx(hndKey2, "DhcpNameServer", nullptr, &Type, nullptr, &Size);
                             if (Result == ERROR_SUCCESS && Size) {
                                 t = (char *) xmalloc(Size);
-                                RegQueryValueEx(hndKey2, "DhcpNameServer", NULL, &Type, (LPBYTE)t, &Size);
+                                RegQueryValueEx(hndKey2, "DhcpNameServer", nullptr, &Type, (LPBYTE)t, &Size);
                                 token = strtok(t, ", ");
                                 while (token) {
                                     debugs(78, DBG_IMPORTANT, "Adding DHCP nameserver " << token << " from Registry");
                                     idnsAddNameserver(token);
                                     result = true;
-                                    token = strtok(NULL, ", ");
+                                    token = strtok(nullptr, ", ");
                                 }
                                 xfree(t);
                             }
 
-                            Result = RegQueryValueEx(hndKey2, "NameServer", NULL, &Type, NULL, &Size);
+                            Result = RegQueryValueEx(hndKey2, "NameServer", nullptr, &Type, nullptr, &Size);
                             if (Result == ERROR_SUCCESS && Size) {
                                 t = (char *) xmalloc(Size);
-                                RegQueryValueEx(hndKey2, "NameServer", NULL, &Type, (LPBYTE)t, &Size);
+                                RegQueryValueEx(hndKey2, "NameServer", nullptr, &Type, (LPBYTE)t, &Size);
                                 token = strtok(t, ", ");
                                 while (token) {
                                     debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
                                     idnsAddNameserver(token);
                                     result = true;
-                                    token = strtok(NULL, ", ");
+                                    token = strtok(nullptr, ", ");
                                 }
 
                                 xfree(t);
@@ -679,18 +676,18 @@ idnsParseWIN32Registry(void)
             DWORD Type = 0;
             DWORD Size = 0;
             LONG Result;
-            Result = RegQueryValueEx(hndKey, "NameServer", NULL, &Type, NULL, &Size);
+            Result = RegQueryValueEx(hndKey, "NameServer", nullptr, &Type, nullptr, &Size);
 
             if (Result == ERROR_SUCCESS && Size) {
                 t = (char *) xmalloc(Size);
-                RegQueryValueEx(hndKey, "NameServer", NULL, &Type, (LPBYTE) t, &Size);
+                RegQueryValueEx(hndKey, "NameServer", nullptr, &Type, (LPBYTE) t, &Size);
                 token = strtok(t, ", ");
 
                 while (token) {
                     debugs(78, DBG_IMPORTANT, "Adding nameserver " << token << " from Registry");
                     idnsAddNameserver(token);
                     result = true;
-                    token = strtok(NULL, ", ");
+                    token = strtok(nullptr, ", ");
                 }
                 xfree(t);
             }
@@ -701,7 +698,7 @@ idnsParseWIN32Registry(void)
         break;
 
     default:
-        debugs(78, DBG_IMPORTANT, "Failed to read nameserver from Registry: Unknown System Type.");
+        debugs(78, DBG_IMPORTANT, "ERROR: Failed to read nameserver from Registry: Unknown System Type.");
     }
 
     return result;
@@ -742,12 +739,12 @@ idnsStats(StoreEntry * sentry)
     storeAppendPrintf(sentry, "IP ADDRESS                                     # QUERIES # REPLIES Type\n");
     storeAppendPrintf(sentry, "---------------------------------------------- --------- --------- --------\n");
 
-    for (i = 0; i < nns; ++i) {
+    for (const auto &server : nameservers) {
         storeAppendPrintf(sentry, "%-45s %9d %9d %s\n",  /* Let's take the maximum: (15 IPv4/45 IPv6) */
-                          nameservers[i].S.toStr(buf,MAX_IPSTRLEN),
-                          nameservers[i].nqueries,
-                          nameservers[i].nreplies,
-                          nameservers[i].mDNSResolver?"multicast":"recurse");
+                          server.S.toStr(buf,MAX_IPSTRLEN),
+                          server.nqueries,
+                          server.nreplies,
+                          server.mDNSResolver?"multicast":"recurse");
     }
 
     storeAppendPrintf(sentry, "\nRcode Matrix:\n");
@@ -786,29 +783,29 @@ idnsTickleQueue(void)
     if (event_queued)
         return;
 
-    if (NULL == lru_list.tail)
+    if (nullptr == lru_list.tail)
         return;
 
     const double when = min(Config.Timeout.idns_query, Config.Timeout.idns_retransmit)/1000.0;
 
-    eventAdd("idnsCheckQueue", idnsCheckQueue, NULL, when, 1);
+    eventAdd("idnsCheckQueue", idnsCheckQueue, nullptr, when, 1);
 
     event_queued = 1;
 }
 
 static void
-idnsSentQueryVC(const Comm::ConnectionPointer &conn, char *buf, size_t size, comm_err_t flag, int xerrno, void *data)
+idnsSentQueryVC(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag flag, int, void *data)
 {
     nsvc * vc = (nsvc *)data;
 
-    if (flag == COMM_ERR_CLOSING)
+    if (flag == Comm::ERR_CLOSING)
         return;
 
     // XXX: irrelevant now that we have conn pointer?
     if (!Comm::IsConnOpen(conn) || fd_table[conn->fd].closing())
         return;
 
-    if (flag != COMM_OK || size <= 0) {
+    if (flag != Comm::OK || size <= 0) {
         conn->close();
         return;
     }
@@ -837,8 +834,8 @@ idnsDoSendQueryVC(nsvc *vc)
     vc->busy = 1;
 
     // Comm needs seconds but idnsCheckQueue() will check the exact timeout
-    const int timeout = (Config.Timeout.idns_query % 1000 ?
-                         Config.Timeout.idns_query + 1000 : Config.Timeout.idns_query) / 1000;
+    const auto timeout = (Config.Timeout.idns_query % 1000 ?
+                          Config.Timeout.idns_query + 1000 : Config.Timeout.idns_query) / 1000;
     AsyncCall::Pointer nil;
 
     commSetConnTimeout(vc->conn, timeout, nil);
@@ -851,15 +848,15 @@ idnsDoSendQueryVC(nsvc *vc)
 }
 
 static void
-idnsInitVCConnected(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
+idnsInitVCConnected(const Comm::ConnectionPointer &conn, Comm::Flag status, int, void *data)
 {
     nsvc * vc = (nsvc *)data;
 
-    if (status != COMM_OK || !conn) {
+    if (status != Comm::OK || !conn) {
         char buf[MAX_IPSTRLEN] = "";
-        if (vc->ns < nns)
+        if (vc->ns < nameservers.size())
             nameservers[vc->ns].S.toStr(buf,MAX_IPSTRLEN);
-        debugs(78, DBG_IMPORTANT, HERE << "Failed to connect to nameserver " << buf << " using TCP.");
+        debugs(78, DBG_IMPORTANT, "ERROR: Failed to connect to nameserver " << buf << " using TCP.");
         return;
     }
 
@@ -877,38 +874,38 @@ static void
 idnsVCClosed(const CommCloseCbParams &params)
 {
     nsvc * vc = (nsvc *)params.data;
-    delete vc->queue;
-    delete vc->msg;
-    vc->conn = NULL;
-    if (vc->ns < nns) // XXX: dnsShutdown may have freed nameservers[]
-        nameservers[vc->ns].vc = NULL;
-    cbdataFree(vc);
+    if (vc->conn) {
+        vc->conn->noteClosure();
+        vc->conn = nullptr;
+    }
+    delete vc;
+}
+
+nsvc::~nsvc()
+{
+    delete queue;
+    delete msg;
+    if (ns < nameservers.size()) // XXX: idnsShutdownAndFreeState may have freed nameservers[]
+        nameservers[ns].vc = nullptr;
 }
 
 static void
-idnsInitVC(int nsv)
+idnsInitVC(size_t nsv)
 {
-    nsvc *vc = cbdataAlloc(nsvc);
-    assert(nsv < nns);
-    assert(vc->conn == NULL); // MUST be NULL from the construction process!
+    assert(nsv < nameservers.size());
+    nsvc *vc = new nsvc(nsv);
+    assert(vc->conn == nullptr); // MUST be NULL from the construction process!
     nameservers[nsv].vc = vc;
-    vc->ns = nsv;
-    vc->queue = new MemBuf;
-    vc->msg = new MemBuf;
-    vc->busy = 1;
 
     Comm::ConnectionPointer conn = new Comm::Connection();
 
     if (!Config.Addrs.udp_outgoing.isNoAddr())
-        conn->local = Config.Addrs.udp_outgoing;
+        conn->setAddrs(Config.Addrs.udp_outgoing, nameservers[nsv].S);
     else
-        conn->local = Config.Addrs.udp_incoming;
-
-    conn->remote = nameservers[nsv].S;
+        conn->setAddrs(Config.Addrs.udp_incoming, nameservers[nsv].S);
 
-    if (conn->remote.isIPv4()) {
+    if (conn->remote.isIPv4())
         conn->local.setIPv4();
-    }
 
     AsyncCall::Pointer call = commCbCall(78,3, "idnsInitVCConnected", CommConnectCbPtrFun(idnsInitVCConnected, vc));
 
@@ -918,17 +915,17 @@ idnsInitVC(int nsv)
 }
 
 static void
-idnsSendQueryVC(idns_query * q, int nsn)
+idnsSendQueryVC(idns_query * q, size_t nsn)
 {
-    assert(nsn < nns);
-    if (nameservers[nsn].vc == NULL)
+    assert(nsn < nameservers.size());
+    if (nameservers[nsn].vc == nullptr)
         idnsInitVC(nsn);
 
     nsvc *vc = nameservers[nsn].vc;
 
     if (!vc) {
         char buf[MAX_IPSTRLEN];
-        debugs(78, DBG_IMPORTANT, "idnsSendQuery: Failed to initiate TCP connection to nameserver " << nameservers[nsn].S.toStr(buf,MAX_IPSTRLEN) << "!");
+        debugs(78, DBG_IMPORTANT, "ERROR: idnsSendQuery: Failed to initiate TCP connection to nameserver " << nameservers[nsn].S.toStr(buf,MAX_IPSTRLEN) << "!");
 
         return;
     }
@@ -947,29 +944,37 @@ idnsSendQueryVC(idns_query * q, int nsn)
 static void
 idnsSendQuery(idns_query * q)
 {
+    // XXX: DNS sockets get closed during reconfigure produces a race between
+    // any already active connections (or ones received between closing DNS
+    // sockets and server listening sockets) and the reconfigure completing
+    // (Runner syncConfig() being run). Transactions which loose this race will
+    // produce DNS timeouts (or whatever the caller set) as their queries never
+    // get queued to be re-tried after the DNS socekts are re-opened.
+
     if (DnsSocketA < 0 && DnsSocketB < 0) {
         debugs(78, DBG_IMPORTANT, "WARNING: idnsSendQuery: Can't send query, no DNS socket!");
         return;
     }
 
-    if (nns <= 0) {
+    if (nameservers.empty()) {
         debugs(78, DBG_IMPORTANT, "WARNING: idnsSendQuery: Can't send query, no DNS nameservers known!");
         return;
     }
 
-    assert(q->lru.next == NULL);
+    assert(q->lru.next == nullptr);
 
-    assert(q->lru.prev == NULL);
+    assert(q->lru.prev == nullptr);
 
     int x = -1, y = -1;
-    int nsn;
+    size_t nsn;
+    const auto nsCount = nameservers.size();
 
     do {
         // only use mDNS resolvers for mDNS compatible queries
         if (!q->permit_mdns)
-            nsn = nns_mdns_count + q->nsends % (nns-nns_mdns_count);
+            nsn = nns_mdns_count + q->nsends % (nsCount - nns_mdns_count);
         else
-            nsn = q->nsends % nns;
+            nsn = q->nsends % nsCount;
 
         if (q->need_vc) {
             idnsSendQueryVC(q, nsn);
@@ -980,23 +985,24 @@ idnsSendQuery(idns_query * q)
             else if (DnsSocketA >= 0)
                 x = comm_udp_sendto(DnsSocketA, nameservers[nsn].S, q->buf, q->sz);
         }
+        int xerrno = errno;
 
         ++ q->nsends;
 
         q->sent_t = current_time;
 
         if (y < 0 && nameservers[nsn].S.isIPv6())
-            debugs(50, DBG_IMPORTANT, "idnsSendQuery: FD " << DnsSocketB << ": sendto: " << xstrerror());
+            debugs(50, DBG_IMPORTANT, MYNAME << "FD " << DnsSocketB << ": sendto: " << xstrerr(xerrno));
         if (x < 0 && nameservers[nsn].S.isIPv4())
-            debugs(50, DBG_IMPORTANT, "idnsSendQuery: FD " << DnsSocketA << ": sendto: " << xstrerror());
+            debugs(50, DBG_IMPORTANT, MYNAME << "FD " << DnsSocketA << ": sendto: " << xstrerr(xerrno));
 
-    } while ( (x<0 && y<0) && q->nsends % nns != 0);
+    } while ( (x<0 && y<0) && q->nsends % nsCount != 0);
 
     if (y > 0) {
-        fd_bytes(DnsSocketB, y, FD_WRITE);
+        fd_bytes(DnsSocketB, y, IoDirection::Write);
     }
     if (x > 0) {
-        fd_bytes(DnsSocketA, x, FD_WRITE);
+        fd_bytes(DnsSocketA, x, IoDirection::Write);
     }
 
     ++ nameservers[nsn].nqueries;
@@ -1009,9 +1015,7 @@ idnsSendQuery(idns_query * q)
 static int
 idnsFromKnownNameserver(Ip::Address const &from)
 {
-    int i;
-
-    for (i = 0; i < nns; ++i) {
+    for (int i = 0; static_cast<size_t>(i) < nameservers.size(); ++i) {
         if (nameservers[i].S != from)
             continue;
 
@@ -1037,20 +1041,23 @@ idnsFindQuery(unsigned short id)
             return q;
     }
 
-    return NULL;
+    return nullptr;
 }
 
 static unsigned short
-idnsQueryID(void)
+idnsQueryID()
 {
-    unsigned short id = squid_random() & 0xFFFF;
+    // NP: apparently ranlux are faster, but not quite as "proven"
+    static std::mt19937 mt(RandomSeed32());
+    unsigned short id = mt() & 0xFFFF;
     unsigned short first_id = id;
 
+    // ensure temporal uniqueness by looking for an existing use
     while (idnsFindQuery(id)) {
         ++id;
 
         if (id == first_id) {
-            debugs(78, DBG_IMPORTANT, "idnsQueryID: Warning, too many pending DNS requests");
+            debugs(78, DBG_IMPORTANT, "WARNING: idnsQueryID: too many pending DNS requests");
             break;
         }
     }
@@ -1058,114 +1065,116 @@ idnsQueryID(void)
     return id;
 }
 
-static void
-idnsCallback(idns_query *q, const char *error)
+/// \returns whether master or associated queries are still waiting for replies
+static bool
+idnsStillPending(const idns_query *master)
 {
-    IDNSCB *callback;
-    void *cbdata;
+    assert(!master->master); // we were given the master transaction
+    for (const idns_query *qi = master; qi; qi = qi->slave) {
+        if (qi->pending)
+            return true;
+    }
+    return false;
+}
 
-    if (error)
-        q->error = error;
+static std::ostream &
+operator <<(std::ostream &os, const idns_query &answered)
+{
+    if (answered.error)
+        os << "error \"" << answered.error << "\"";
+    else
+        os << answered.ancount << " records";
+    return os;
+}
 
-    if (q->master)
-        q = q->master;
+static void
+idnsCallbackOnEarlyError(IDNSCB *callback, void *cbdata, const char *error)
+{
+    // A cbdataReferenceValid() check asserts on unlocked cbdata: Early errors,
+    // by definition, happen before we store/cbdataReference() cbdata.
+    debugs(78, 6, "\"" << error << "\" for " << cbdata);
+    callback(cbdata, nullptr, 0, "Internal error", true); // hide error details
+}
 
-    // If any of our subqueries are still pending then wait for them to complete before continuing
-    for (idns_query *q2 = q; q2; q2 = q2->slave) {
-        if (q2->pending) {
-            return;
-        }
-    }
+/// safely sends one set of DNS records (or an error) to the caller
+static bool
+idnsCallbackOneWithAnswer(IDNSCB *callback, void *cbdata, const idns_query &answered, const bool lastAnswer)
+{
+    if (!cbdataReferenceValid(cbdata))
+        return false;
+    const rfc1035_rr *records = answered.message ? answered.message->answer : nullptr;
+    debugs(78, 6, (lastAnswer ? "last " : "") << answered << " for " << cbdata);
+    callback(cbdata, records, answered.ancount, answered.error, lastAnswer);
+    return true;
+}
 
-    /* Merge results */
-    rfc1035_message *message = q->message;
-    q->message = NULL;
-    int n = q->ancount;
-    error = q->error;
-
-    while ( idns_query *q2 = q->slave ) {
-        debugs(78, 6, HERE << "Merging DNS results " << q->name << " A has " << n << " RR, AAAA has " << q2->ancount << " RR");
-        q->slave = q2->slave;
-        if ( !q2->error ) {
-            if (n > 0) {
-                // two sets of RR need merging
-                rfc1035_rr *result = (rfc1035_rr*) xmalloc( sizeof(rfc1035_rr)*(n + q2->ancount) );
-                if (Config.dns.v4_first) {
-                    memcpy(result, message->answer, (sizeof(rfc1035_rr)*n) );
-                    memcpy(result+n, q2->message->answer, (sizeof(rfc1035_rr)*q2->ancount) );
-                } else {
-                    memcpy(result, q2->message->answer, (sizeof(rfc1035_rr)*q2->ancount) );
-                    memcpy(result+q2->ancount, message->answer, (sizeof(rfc1035_rr)*n) );
-                }
-                n += q2->ancount;
-                // HACK WARNING, the answer rr:s have been copied in-place to
-                // result, do not free them here
-                safe_free(message->answer);
-                safe_free(q2->message->answer);
-                message->answer = result;
-                message->ancount += q2->message->ancount;
-            } else {
-                // first response empty or failed, just use the second
-                rfc1035MessageDestroy(&message);
-                message = q2->message;
-                q2->message = NULL;
-                n = q2->ancount;
-                error = NULL;
-            }
-        }
-        rfc1035MessageDestroy(&q2->message);
-        cbdataFree(q2);
+static void
+idnsCallbackNewCallerWithOldAnswers(IDNSCB *callback, void *cbdata, const idns_query * const master)
+{
+    const bool lastAnswer = false;
+    // iterate all queries to act on answered ones
+    for (auto query = master; query; query = query->slave) {
+        if (query->pending)
+            continue; // no answer yet
+        // no CallBack(CodeContext...) -- we always run in requestor's context
+        if (!idnsCallbackOneWithAnswer(callback, cbdata, *query, lastAnswer))
+            break; // the caller disappeared
     }
+}
 
-    debugs(78, 6, HERE << "Sending " << n << " (" << (error ? error : "OK") << ") DNS results to caller.");
-
-    callback = q->callback;
-    q->callback = NULL;
-    const rfc1035_rr *answers = message ? message->answer : NULL;
+static void
+idnsCallbackAllCallersWithNewAnswer(const idns_query * const answered, const bool lastAnswer)
+{
+    debugs(78, 8, (lastAnswer ? "last " : "") << *answered);
+    const auto master = answered->master ? answered->master : answered;
+    // iterate all queued lookup callers
+    for (auto looker = master; looker; looker = looker->queue) {
+        CallBack(looker->codeContext, [&] {
+            (void)idnsCallbackOneWithAnswer(looker->callback, looker->callback_data,
+                                            *answered, lastAnswer);
+        });
+    }
+}
 
-    if (cbdataReferenceValidDone(q->callback_data, &cbdata))
-        callback(cbdata, answers, n, error);
+static void
+idnsCallback(idns_query *q, const char *error)
+{
+    if (error)
+        q->error = error;
 
-    while (q->queue) {
-        idns_query *q2 = q->queue;
-        q->queue = q2->queue;
-        callback = q2->callback;
-        q2->callback = NULL;
+    auto master = q->master ? q->master : q;
 
-        if (cbdataReferenceValidDone(q2->callback_data, &cbdata))
-            callback(cbdata, answers, n, error);
+    const bool lastAnswer = !idnsStillPending(master);
+    idnsCallbackAllCallersWithNewAnswer(q, lastAnswer);
 
-        cbdataFree(q2);
-    }
+    if (!lastAnswer)
+        return; // wait for more answers
 
-    if (q->hash.key) {
-        hash_remove_link(idns_lookup_hash, &q->hash);
-        q->hash.key = NULL;
+    if (master->hash.key) {
+        hash_remove_link(idns_lookup_hash, &master->hash);
+        master->hash.key = nullptr;
     }
 
-    rfc1035MessageDestroy(&message);
-    cbdataFree(q);
+    delete master;
 }
 
 static void
-idnsGrokReply(const char *buf, size_t sz, int from_ns)
+idnsGrokReply(const char *buf, size_t sz, int /*from_ns*/)
 {
-    int n;
-    rfc1035_message *message = NULL;
-    idns_query *q;
+    rfc1035_message *message = nullptr;
 
-    n = rfc1035MessageUnpack(buf, sz, &message);
+    int n = rfc1035MessageUnpack(buf, sz, &message);
 
-    if (message == NULL) {
-        debugs(78, DBG_IMPORTANT, "idnsGrokReply: Malformed DNS response");
+    if (message == nullptr) {
+        debugs(78, DBG_IMPORTANT, "ERROR: idnsGrokReply: Malformed DNS response");
         return;
     }
 
-    debugs(78, 3, "idnsGrokReply: QID 0x" << std::hex <<   message->id << ", " << std::dec << n << " answers");
+    debugs(78, 3, "idnsGrokReply: QID 0x" << asHex(message->id) << ", " << n << " answers");
 
-    q = idnsFindQuery(message->id);
+    idns_query *q = idnsFindQuery(message->id);
 
-    if (q == NULL) {
+    if (q == nullptr) {
         debugs(78, 3, "idnsGrokReply: Late response");
         rfc1035MessageDestroy(&message);
         return;
@@ -1179,9 +1188,9 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
 
 #if WHEN_EDNS_RESPONSES_ARE_PARSED
 // TODO: actually gr the message right here.
-//     pull out the DNS meta data we need (A records, AAAA records and EDNS OPT) and store in q
-//     this is overall better than force-feeding A response with AAAA an section later anyway.
-//     AND allows us to merge AN+AR sections from both responses (one day)
+//  pull out the DNS meta data we need (A records, AAAA records and EDNS OPT) and store in q
+//  this is overall better than force-feeding A response with AAAA an section later anyway.
+//  AND allows us to merge AN+AR sections from both responses (one day)
 
     if (q->edns_seen >= 0) {
         if (max_shared_edns == nameservers[from_ns].last_seen_edns && max_shared_edns < q->edns_seen) {
@@ -1189,11 +1198,11 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
             // the altered NS was limiting the whole group.
             max_shared_edns = q->edns_seen;
             // may be limited by one of the others still
-            for (int i = 0; i < nns; ++i)
-                max_shared_edns = min(max_shared_edns, nameservers[i].last_seen_edns);
+            for (const auto &server : nameservers)
+                max_shared_edns = min(max_shared_edns, server.last_seen_edns);
         } else {
             nameservers[from_ns].last_seen_edns = q->edns_seen;
-            // maybe reduce the global limit downwards to accomodate this NS
+            // maybe reduce the global limit downwards to accommodate this NS
             max_shared_edns = min(max_shared_edns, q->edns_seen);
         }
         if (max_shared_edns < RFC1035_DEFAULT_PACKET_SZ)
@@ -1205,7 +1214,7 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
     q->pending = 0;
 
     if (message->tc) {
-        debugs(78, 3, HERE << "Resolver requested TC (" << q->query.name << ")");
+        debugs(78, 3, "Resolver requested TC (" << q->query.name << ")");
         rfc1035MessageDestroy(&message);
 
         if (!q->need_vc) {
@@ -1215,7 +1224,7 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
         } else {
             // Strange: A TCP DNS response with the truncation bit (TC) set.
             // Return an error and cleanup; no point in trying TCP again.
-            debugs(78, 3, HERE << "TCP DNS response");
+            debugs(78, 3, "TCP DNS response");
             idnsCallback(q, "Truncated TCP DNS response");
         }
 
@@ -1241,10 +1250,10 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
         }
 
         // Do searchpath processing on the master A query only to keep
-        // things simple. NXDOMAIN is authorative for the label, not
+        // things simple. NXDOMAIN is authoritative for the label, not
         // the record type.
         if (q->rcode == 3 && !q->master && q->do_searchpath && q->attempt < MAX_ATTEMPT) {
-            assert(NULL == message->answer);
+            assert(nullptr == message->answer);
             strcpy(q->name, q->orig);
 
             debugs(78, 3, "idnsGrokReply: Query result: NXDOMAIN - " << q->name );
@@ -1264,8 +1273,8 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
             while (idns_query *slave = q->slave) {
                 dlinkDelete(&slave->lru, &lru_list);
                 q->slave = slave->slave;
-                rfc1035MessageDestroy(&slave->message);
-                cbdataFree(slave);
+                slave->slave = nullptr;
+                delete slave;
             }
 
             // Build new query
@@ -1293,14 +1302,14 @@ idnsGrokReply(const char *buf, size_t sz, int from_ns)
     q->ancount = n;
 
     if (n >= 0)
-        idnsCallback(q, NULL);
+        idnsCallback(q, nullptr);
     else
         idnsCallback(q, rfc1035ErrorMessage(q->rcode));
 
 }
 
 static void
-idnsRead(int fd, void *data)
+idnsRead(int fd, void *)
 {
     int *N = &incoming_sockets_accepted;
     int len;
@@ -1312,7 +1321,7 @@ idnsRead(int fd, void *data)
 
     // Always keep reading. This stops (or at least makes harder) several
     // attacks on the DNS client.
-    Comm::SetSelect(fd, COMM_SELECT_READ, idnsRead, NULL, 0);
+    Comm::SetSelect(fd, COMM_SELECT_READ, idnsRead, nullptr, 0);
 
     /* BUG (UNRESOLVED)
      *  two code lines after returning from comm_udprecvfrom()
@@ -1333,7 +1342,8 @@ idnsRead(int fd, void *data)
             break;
 
         if (len < 0) {
-            if (ignoreErrno(errno))
+            int xerrno = errno;
+            if (ignoreErrno(xerrno))
                 break;
 
 #if _SQUID_LINUX_
@@ -1341,15 +1351,14 @@ idnsRead(int fd, void *data)
              * return ECONNREFUSED when sendto() fails and generates an ICMP
              * port unreachable message. */
             /* or maybe an EHOSTUNREACH "No route to host" message */
-            if (errno != ECONNREFUSED && errno != EHOSTUNREACH)
+            if (xerrno != ECONNREFUSED && xerrno != EHOSTUNREACH)
 #endif
-
-                debugs(50, DBG_IMPORTANT, "idnsRead: FD " << fd << " recvfrom: " << xstrerror());
+                debugs(50, DBG_IMPORTANT, MYNAME << "FD " << fd << " recvfrom: " << xstrerr(xerrno));
 
             break;
         }
 
-        fd_bytes(fd, len, FD_READ);
+        fd_bytes(fd, len, IoDirection::Read);
 
         assert(N);
         ++(*N);
@@ -1385,17 +1394,18 @@ idnsRead(int fd, void *data)
 }
 
 static void
-idnsCheckQueue(void *unused)
+idnsCheckQueue(void *)
 {
     dlink_node *n;
-    dlink_node *p = NULL;
+    dlink_node *p = nullptr;
     idns_query *q;
     event_queued = 0;
 
-    if (0 == nns)
+    if (nameservers.empty())
         /* name servers went away; reconfiguring or shutting down */
         return;
 
+    const auto nsCount = nameservers.size();
     for (n = lru_list.tail; n; n = p) {
 
         p = n->prev;
@@ -1406,7 +1416,7 @@ idnsCheckQueue(void *unused)
             break;
 
         /* Query timer still running? */
-        if ((time_msec_t)tvSubMsec(q->sent_t, current_time) < (Config.Timeout.idns_retransmit * 1 << ((q->nsends - 1) / nns))) {
+        if ((time_msec_t)tvSubMsec(q->sent_t, current_time) < (Config.Timeout.idns_retransmit * 1 << ((q->nsends - 1) / nsCount))) {
             dlinkDelete(&q->lru, &lru_list);
             q->queue_t = current_time;
             dlinkAdd(q, &q->lru, &lru_list);
@@ -1414,8 +1424,7 @@ idnsCheckQueue(void *unused)
         }
 
         debugs(78, 3, "idnsCheckQueue: ID " << q->xact_id <<
-               " QID 0x"  << std::hex << std::setfill('0')  <<
-               std::setw(4) << q->query_id << ": timeout" );
+               " QID 0x" << asHex(q->query_id).minDigits(4) << ": timeout");
 
         dlinkDelete(&q->lru, &lru_list);
         q->pending = 0;
@@ -1424,8 +1433,8 @@ idnsCheckQueue(void *unused)
             idnsSendQuery(q);
         } else {
             debugs(78, 2, "idnsCheckQueue: ID " << q->xact_id <<
-                   " QID 0x" << std::hex << q->query_id <<
-                   " : giving up after " << std::dec << q->nsends << " tries and " <<
+                   " QID 0x" << asHex(q->query_id) <<
+                   ": giving up after " << q->nsends << " tries and " <<
                    std::setw(5)<< std::setprecision(2) << tvSubDsec(q->start_t, current_time) << " seconds");
 
             if (q->rcode != 0)
@@ -1439,14 +1448,14 @@ idnsCheckQueue(void *unused)
 }
 
 static void
-idnsReadVC(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
+idnsReadVC(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int, void *data)
 {
     nsvc * vc = (nsvc *)data;
 
-    if (flag == COMM_ERR_CLOSING)
+    if (flag == Comm::ERR_CLOSING)
         return;
 
-    if (flag != COMM_OK || len <= 0) {
+    if (flag != Comm::OK || len <= 0) {
         if (Comm::IsConnOpen(conn))
             conn->close();
         return;
@@ -1461,8 +1470,8 @@ idnsReadVC(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_
         return;
     }
 
-    assert(vc->ns < nns);
-    debugs(78, 3, HERE << conn << ": received " << vc->msg->contentSize() << " bytes via TCP from " << nameservers[vc->ns].S << ".");
+    assert(vc->ns < nameservers.size());
+    debugs(78, 3, conn << ": received " << vc->msg->contentSize() << " bytes via TCP from " << nameservers[vc->ns].S << ".");
 
     idnsGrokReply(vc->msg->buf, vc->msg->contentSize(), vc->ns);
     vc->msg->clean();
@@ -1472,14 +1481,14 @@ idnsReadVC(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_
 }
 
 static void
-idnsReadVCHeader(const Comm::ConnectionPointer &conn, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
+idnsReadVCHeader(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm::Flag flag, int, void *data)
 {
     nsvc * vc = (nsvc *)data;
 
-    if (flag == COMM_ERR_CLOSING)
+    if (flag == Comm::ERR_CLOSING)
         return;
 
-    if (flag != COMM_OK || len <= 0) {
+    if (flag != Comm::OK || len <= 0) {
         if (Comm::IsConnOpen(conn))
             conn->close();
         return;
@@ -1500,6 +1509,12 @@ idnsReadVCHeader(const Comm::ConnectionPointer &conn, char *buf, size_t len, com
 
     vc->msglen = ntohs(vc->msglen);
 
+    if (!vc->msglen) {
+        if (Comm::IsConnOpen(conn))
+            conn->close();
+        return;
+    }
+
     vc->msg->init(vc->msglen, vc->msglen);
     AsyncCall::Pointer call = commCbCall(5,4, "idnsReadVC",
                                          CommIoCbPtrFun(idnsReadVC, vc));
@@ -1522,24 +1537,13 @@ idnsRcodeCount(int rcode, int attempt)
             ++ RcodeMatrix[rcode][attempt];
 }
 
-/* ====================================================================== */
-
-static void
-idnsRegisterWithCacheManager(void)
-{
-    Mgr::RegisterAction("idns", "Internal DNS Statistics", idnsStats, 0, 1);
-}
-
 void
-dnsInit(void)
+Dns::Init(void)
 {
     static int init = 0;
 
-    CBDATA_INIT_TYPE(nsvc);
-    CBDATA_INIT_TYPE(idns_query);
-
     if (DnsSocketA < 0 && DnsSocketB < 0) {
-        Ip::Address addrV6; // since we don't want to alter Config.Addrs.udp_* and dont have one of our own.
+        Ip::Address addrV6; // since we do not want to alter Config.Addrs.udp_* and do not have one of our own.
 
         if (!Config.Addrs.udp_outgoing.isNoAddr())
             addrV6 = Config.Addrs.udp_outgoing;
@@ -1575,32 +1579,30 @@ dnsInit(void)
          */
         if (DnsSocketB >= 0) {
             comm_local_port(DnsSocketB);
-            debugs(78, DBG_IMPORTANT, "DNS Socket created at " << addrV6 << ", FD " << DnsSocketB);
-            Comm::SetSelect(DnsSocketB, COMM_SELECT_READ, idnsRead, NULL, 0);
+            debugs(78, Important(16), "DNS IPv6 socket created at " << addrV6 << ", FD " << DnsSocketB);
+            Comm::SetSelect(DnsSocketB, COMM_SELECT_READ, idnsRead, nullptr, 0);
         }
         if (DnsSocketA >= 0) {
             comm_local_port(DnsSocketA);
-            debugs(78, DBG_IMPORTANT, "DNS Socket created at " << addrV4 << ", FD " << DnsSocketA);
-            Comm::SetSelect(DnsSocketA, COMM_SELECT_READ, idnsRead, NULL, 0);
+            debugs(78, Important(64), "DNS IPv4 socket created at " << addrV4 << ", FD " << DnsSocketA);
+            Comm::SetSelect(DnsSocketA, COMM_SELECT_READ, idnsRead, nullptr, 0);
         }
     }
 
-    assert(0 == nns);
+    assert(nameservers.empty());
     idnsAddMDNSNameservers();
     bool nsFound = idnsParseNameservers();
-#if !_SQUID_WINDOWS_
 
     if (!nsFound)
         nsFound = idnsParseResolvConf();
 
-#endif
 #if _SQUID_WINDOWS_
     if (!nsFound)
         nsFound = idnsParseWIN32Registry();
 #endif
 
     if (!nsFound) {
-        debugs(78, DBG_IMPORTANT, "Warning: Could not find any nameservers. Trying to use localhost");
+        debugs(78, DBG_IMPORTANT, "WARNING: Could not find any nameservers. Trying to use localhost");
 #if _SQUID_WINDOWS_
         debugs(78, DBG_IMPORTANT, "Please check your TCP-IP settings or /etc/resolv.conf file");
 #else
@@ -1614,7 +1616,6 @@ dnsInit(void)
     }
 
     if (!init) {
-        memDataInit(MEM_IDNS_QUERY, "idns_query", sizeof(idns_query), 0);
         memset(RcodeMatrix, '\0', sizeof(RcodeMatrix));
         idns_lookup_hash = hash_create((HASHCMP *) strcmp, 103, hash_string);
         ++init;
@@ -1627,15 +1628,17 @@ dnsInit(void)
     }
 #endif
 
-    idnsRegisterWithCacheManager();
+    Mgr::RegisterAction("idns", "Internal DNS Statistics", idnsStats, 0, 1);
 }
 
-void
-dnsShutdown(void)
+static void
+idnsShutdownAndFreeState(const char *reason)
 {
     if (DnsSocketA < 0 && DnsSocketB < 0)
         return;
 
+    debugs(78, 2, reason << ": Closing DNS sockets");
+
     if (DnsSocketA >= 0 ) {
         comm_close(DnsSocketA);
         DnsSocketA = -1;
@@ -1646,41 +1649,57 @@ dnsShutdown(void)
         DnsSocketB = -1;
     }
 
-    for (int i = 0; i < nns; ++i) {
-        if (nsvc *vc = nameservers[i].vc) {
+    for (const auto &server : nameservers) {
+        if (const auto vc = server.vc) {
             if (Comm::IsConnOpen(vc->conn))
                 vc->conn->close();
         }
     }
 
     // XXX: vcs are not closed/freed yet and may try to access nameservers[]
-    idnsFreeNameservers();
+    nameservers.clear();
     idnsFreeSearchpath();
 }
 
+void
+Dns::ConfigRr::endingShutdown()
+{
+    idnsShutdownAndFreeState("Shutdown");
+}
+
+void
+Dns::ConfigRr::startReconfigure()
+{
+    idnsShutdownAndFreeState("Reconfigure");
+}
+
 static int
 idnsCachedLookup(const char *key, IDNSCB * callback, void *data)
 {
-    idns_query *q;
-
     idns_query *old = (idns_query *) hash_lookup(idns_lookup_hash, key);
 
     if (!old)
         return 0;
 
-    q = cbdataAlloc(idns_query);
-    // idns_query is POD so no constructors are called after allocation
-    q->xact_id.change();
+    // XXX: We are collapsing this DNS query (B) onto another one (A), but there
+    // is no code to later send B if the A answer has unshareable 0 TTL records.
+
+    idns_query *q = new idns_query;
     // no query_id on this instance.
 
     q->callback = callback;
-
     q->callback_data = cbdataReference(data);
 
     q->queue = old->queue;
-
     old->queue = q;
 
+    // This check must follow cbdataReference() above because our callback code
+    // needs a locked cbdata to call cbdataReferenceValid().
+    if (idnsStillPending(old))
+        idnsCallbackNewCallerWithOldAnswers(callback, data, old);
+    // else: idns_lookup_hash is not a cache so no pending lookups means we are
+    // in a reentrant lookup and will be called back when dequeued.
+
     return 1;
 }
 
@@ -1700,21 +1719,23 @@ idnsStartQuery(idns_query *q, IDNSCB * callback, void *data)
 static void
 idnsSendSlaveAAAAQuery(idns_query *master)
 {
-    idns_query *q = cbdataAlloc(idns_query);
+    idns_query *q = new idns_query;
     memcpy(q->name, master->name, sizeof(q->name));
     memcpy(q->orig, master->orig, sizeof(q->orig));
     q->master = master;
     q->query_id = idnsQueryID();
     q->sz = rfc3596BuildAAAAQuery(q->name, q->buf, sizeof(q->buf), q->query_id, &q->query, Config.dns.packet_max);
-    q->start_t = master->start_t;
-    q->slave = master->slave;
 
-    debugs(78, 3, HERE << "buf is " << q->sz << " bytes for " << q->name <<
-           ", id = 0x" << std::hex << q->query_id);
+    debugs(78, 3, "buf is " << q->sz << " bytes for " << q->name <<
+           ", id = 0x" << asHex(q->query_id));
     if (!q->sz) {
-        cbdataFree(q);
+        delete q;
         return;
     }
+
+    q->start_t = master->start_t;
+    q->slave = master->slave;
+
     idnsCheckMDNS(q);
     master->slave = q;
     idnsSendQuery(q);
@@ -1728,20 +1749,18 @@ idnsALookup(const char *name, IDNSCB * callback, void *data)
     // Prevent buffer overflow on q->name
     if (nameLength > NS_MAXDNAME) {
         debugs(23, DBG_IMPORTANT, "SECURITY ALERT: DNS name too long to perform lookup: '" << name << "'. see access.log for details.");
-        callback(data, NULL, 0, "Internal error");
+        idnsCallbackOnEarlyError(callback, data, "huge name");
         return;
     }
 
     if (idnsCachedLookup(name, callback, data))
         return;
 
-    idns_query *q = cbdataAlloc(idns_query);
-    // idns_query is POD so no constructors are called after allocation
-    q->xact_id.change();
+    idns_query *q = new idns_query;
     q->query_id = idnsQueryID();
 
     int nd = 0;
-    for (unsigned int i = 0; i < nameLength; ++i)
+    for (size_t i = 0; i < nameLength; ++i)
         if (name[i] == '.')
             ++nd;
 
@@ -1766,35 +1785,29 @@ idnsALookup(const char *name, IDNSCB * callback, void *data)
 
     if (q->sz < 0) {
         /* problem with query data -- query not sent */
-        callback(data, NULL, 0, "Internal error");
-        cbdataFree(q);
+        idnsCallbackOnEarlyError(callback, data, "rfc3596BuildAQuery error");
+        delete q;
         return;
     }
 
     debugs(78, 3, "idnsALookup: buf is " << q->sz << " bytes for " << q->name <<
-           ", id = 0x" << std::hex << q->query_id);
+           ", id = 0x" << asHex(q->query_id));
 
     idnsCheckMDNS(q);
     idnsStartQuery(q, callback, data);
 
     if (Ip::EnableIpv6)
         idnsSendSlaveAAAAQuery(q);
-
 }
 
 void
 idnsPTRLookup(const Ip::Address &addr, IDNSCB * callback, void *data)
 {
-    idns_query *q;
-
     char ip[MAX_IPSTRLEN];
 
     addr.toStr(ip,MAX_IPSTRLEN);
 
-    q = cbdataAlloc(idns_query);
-
-    // idns_query is POD so no constructors are called after allocation
-    q->xact_id.change();
+    idns_query *q = new idns_query;
     q->query_id = idnsQueryID();
 
     if (addr.isIPv6()) {
@@ -1810,18 +1823,18 @@ idnsPTRLookup(const Ip::Address &addr, IDNSCB * callback, void *data)
 
     if (q->sz < 0) {
         /* problem with query data -- query not sent */
-        callback(data, NULL, 0, "Internal error");
-        cbdataFree(q);
+        idnsCallbackOnEarlyError(callback, data, "rfc3596BuildPTRQuery error");
+        delete q;
         return;
     }
 
     if (idnsCachedLookup(q->query.name, callback, data)) {
-        cbdataFree(q);
+        delete q;
         return;
     }
 
     debugs(78, 3, "idnsPTRLookup: buf is " << q->sz << " bytes for " << ip <<
-           ", id = 0x" << std::hex << q->query_id);
+           ", id = 0x" << asHex(q->query_id));
 
     q->permit_mdns = Config.onoff.dns_mdns;
     idnsStartQuery(q, callback, data);
@@ -1834,8 +1847,8 @@ idnsPTRLookup(const Ip::Address &addr, IDNSCB * callback, void *data)
 variable_list *
 snmp_netDnsFn(variable_list * Var, snint * ErrP)
 {
-    int i, n = 0;
-    variable_list *Answer = NULL;
+    int n = 0;
+    variable_list *Answer = nullptr;
     MemBuf tmp;
     debugs(49, 5, "snmp_netDnsFn: Processing request: " << snmpDebugOid(Var->name, Var->name_length, tmp));
     *ErrP = SNMP_ERR_NOERROR;
@@ -1844,8 +1857,8 @@ snmp_netDnsFn(variable_list * Var, snint * ErrP)
 
     case DNS_REQ:
 
-        for (i = 0; i < nns; ++i)
-            n += nameservers[i].nqueries;
+        for (const auto &server : nameservers)
+            n += server.nqueries;
 
         Answer = snmp_var_new_integer(Var->name, Var->name_length,
                                       n,
@@ -1854,8 +1867,8 @@ snmp_netDnsFn(variable_list * Var, snint * ErrP)
         break;
 
     case DNS_REP:
-        for (i = 0; i < nns; ++i)
-            n += nameservers[i].nreplies;
+        for (const auto &server : nameservers)
+            n += server.nreplies;
 
         Answer = snmp_var_new_integer(Var->name, Var->name_length,
                                       n,
@@ -1865,7 +1878,7 @@ snmp_netDnsFn(variable_list * Var, snint * ErrP)
 
     case DNS_SERVERS:
         Answer = snmp_var_new_integer(Var->name, Var->name_length,
-                                      nns,
+                                      nameservers.size(),
                                       SMI_COUNTER32);
 
         break;
@@ -1880,3 +1893,4 @@ snmp_netDnsFn(variable_list * Var, snint * ErrP)
 }
 
 #endif /*SQUID_SNMP */
+