]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - resolv/res_send.c
time/tst-strftime2.c: Make the file easier to maintain
[thirdparty/glibc.git] / resolv / res_send.c
index c35fb66bda166ca02d92bd60ed224eb62782f1f9..ca441c4ce1de95a0eb52b701718d0da49bb34c36 100644 (file)
@@ -1,3 +1,20 @@
+/* Copyright (C) 2016-2019 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
 /*
  * Copyright (c) 1985, 1989, 1993
  *    The Regents of the University of California.  All rights reserved.
  * SOFTWARE.
  */
 
-#if defined(LIBC_SCCS) && !defined(lint)
-static const char sccsid[] = "@(#)res_send.c   8.1 (Berkeley) 6/4/93";
-static const char rcsid[] = "$BINDId: res_send.c,v 8.38 2000/03/30 20:16:51 vixie Exp $";
-#endif /* LIBC_SCCS and not lint */
-
 /*
  * Send query to name server and wait for reply.
  */
@@ -89,14 +101,15 @@ static const char rcsid[] = "$BINDId: res_send.c,v 8.38 2000/03/30 20:16:51 vixi
 #include <errno.h>
 #include <fcntl.h>
 #include <netdb.h>
-#include <resolv.h>
+#include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 #include <signal.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 #include <kernel-features.h>
-#include <libc-internal.h>
+#include <libc-diag.h>
+#include <random-bits.h>
 
 #if PACKETSZ > 65536
 #define MAXPACKET       PACKETSZ
@@ -104,14 +117,6 @@ static const char rcsid[] = "$BINDId: res_send.c,v 8.38 2000/03/30 20:16:51 vixi
 #define MAXPACKET       65536
 #endif
 
-
-#ifndef __ASSUME_SOCK_CLOEXEC
-static int __have_o_nonblock;
-#else
-# define __have_o_nonblock 0
-#endif
-
-
 /* From ev_streams.c.  */
 
 static inline void
@@ -176,14 +181,11 @@ evNowTime(struct timespec *res) {
 }
 
 
-/* Options.  Leave them on. */
-/* #undef DEBUG */
-#include "res_debug.h"
-
 #define EXT(res) ((res)->_u._ext)
 
 /* Forward. */
 
+static struct sockaddr *get_nsaddr (res_state, unsigned int);
 static int             send_vc(res_state, const u_char *, int,
                                const u_char *, int,
                                u_char **, int *, int *, int, u_char **,
@@ -193,11 +195,6 @@ static int         send_dg(res_state, const u_char *, int,
                                u_char **, int *, int *, int,
                                int *, int *, u_char **,
                                u_char **, int *, int *, int *);
-#ifdef DEBUG
-static void            Aerror(const res_state, FILE *, const char *, int,
-                              const struct sockaddr *);
-static void            Perror(const res_state, FILE *, const char *, int);
-#endif
 static int             sock_eq(struct sockaddr_in6 *, struct sockaddr_in6 *);
 
 /* Public. */
@@ -221,20 +218,21 @@ res_ourserver_p(const res_state statp, const struct sockaddr_in6 *inp)
            in_port_t port = in4p->sin_port;
            in_addr_t addr = in4p->sin_addr.s_addr;
 
-           for (ns = 0;  ns < MAXNS;  ns++) {
+           for (ns = 0;  ns < statp->nscount;  ns++) {
                const struct sockaddr_in *srv =
-                   (struct sockaddr_in *)EXT(statp).nsaddrs[ns];
+                   (struct sockaddr_in *) get_nsaddr (statp, ns);
 
-               if ((srv != NULL) && (srv->sin_family == AF_INET) &&
+               if ((srv->sin_family == AF_INET) &&
                    (srv->sin_port == port) &&
                    (srv->sin_addr.s_addr == INADDR_ANY ||
                     srv->sin_addr.s_addr == addr))
                    return (1);
            }
        } else if (inp->sin6_family == AF_INET6) {
-           for (ns = 0;  ns < MAXNS;  ns++) {
-               const struct sockaddr_in6 *srv = EXT(statp).nsaddrs[ns];
-               if ((srv != NULL) && (srv->sin6_family == AF_INET6) &&
+           for (ns = 0;  ns < statp->nscount;  ns++) {
+               const struct sockaddr_in6 *srv
+                 = (struct sockaddr_in6 *) get_nsaddr (statp, ns);
+               if ((srv->sin6_family == AF_INET6) &&
                    (srv->sin6_port == inp->sin6_port) &&
                    !(memcmp(&srv->sin6_addr, &in6addr_any,
                             sizeof (struct in6_addr)) &&
@@ -246,6 +244,12 @@ res_ourserver_p(const res_state statp, const struct sockaddr_in6 *inp)
        return (0);
 }
 
+int
+res_isourserver (const struct sockaddr_in *inp)
+{
+  return res_ourserver_p (&_res, (const struct sockaddr_in6 *) inp);
+}
+
 /* int
  * res_nameinquery(name, type, class, buf, eom)
  *     look for (name,type,class) in the query section of packet (buf,eom)
@@ -285,6 +289,54 @@ res_nameinquery(const char *name, int type, int class,
 }
 libresolv_hidden_def (res_nameinquery)
 
+/* Returns a shift value for the name server index.  Used to implement
+   RES_ROTATE.  */
+static unsigned int
+nameserver_offset (struct __res_state *statp)
+{
+  /* If we only have one name server or rotation is disabled, return
+     offset 0 (no rotation).  */
+  unsigned int nscount = statp->nscount;
+  if (nscount <= 1 || !(statp->options & RES_ROTATE))
+    return 0;
+
+  /* Global offset.  The lowest bit indicates whether the offset has
+     been initialized with a random value.  Use relaxed MO to access
+     global_offset because all we need is a sequence of roughly
+     sequential value.  */
+  static unsigned int global_offset;
+  unsigned int offset = atomic_fetch_add_relaxed (&global_offset, 2);
+  if ((offset & 1) == 0)
+    {
+      /* Initialization is required.  */
+      offset = random_bits ();
+      /* The lowest bit is the most random.  Preserve it.  */
+      offset <<= 1;
+
+      /* Store the new starting value.  atomic_fetch_add_relaxed
+        returns the old value, so emulate that by storing the new
+        (incremented) value.  Concurrent initialization with
+        different random values is harmless.  */
+      atomic_store_relaxed (&global_offset, (offset | 1) + 2);
+    }
+
+  /* Remove the initialization bit.  */
+  offset >>= 1;
+
+  /* Avoid the division in the most common cases.  */
+  switch (nscount)
+    {
+    case 2:
+      return offset & 1;
+    case 3:
+      return offset % 3;
+    case 4:
+      return offset & 3;
+    default:
+      return offset % nscount;
+    }
+}
+
 /* int
  * res_queriesmatch(buf1, eom1, buf2, eom2)
  *     is there a 1:1 mapping of (name,type,class)
@@ -341,12 +393,15 @@ res_queriesmatch(const u_char *buf1, const u_char *eom1,
 libresolv_hidden_def (res_queriesmatch)
 
 int
-__libc_res_nsend(res_state statp, const u_char *buf, int buflen,
-                const u_char *buf2, int buflen2,
-                u_char *ans, int anssiz, u_char **ansp, u_char **ansp2,
-                int *nansp2, int *resplen2, int *ansp2_malloced)
+__res_context_send (struct resolv_context *ctx,
+                   const unsigned char *buf, int buflen,
+                   const unsigned char *buf2, int buflen2,
+                   unsigned char *ans, int anssiz,
+                   unsigned char **ansp, unsigned char **ansp2,
+                   int *nansp2, int *resplen2, int *ansp2_malloced)
 {
-  int gotsomewhere, terrno, try, v_circuit, resplen, ns, n;
+       struct __res_state *statp = ctx->resp;
+       int gotsomewhere, terrno, try, v_circuit, resplen, n;
 
        if (statp->nscount == 0) {
                __set_errno (ESRCH);
@@ -358,22 +413,6 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                return (-1);
        }
 
-#ifdef USE_HOOKS
-       if (__glibc_unlikely (statp->qhook || statp->rhook))       {
-               if (anssiz < MAXPACKET && ansp) {
-                       u_char *buf = malloc (MAXPACKET);
-                       if (buf == NULL)
-                               return (-1);
-                       memcpy (buf, ans, HFIXEDSZ);
-                       *ansp = buf;
-                       ans = buf;
-                       anssiz = MAXPACKET;
-               }
-       }
-#endif
-
-       DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_QUERY),
-               (stdout, ";; res_send()\n"), buf, buflen);
        v_circuit = ((statp->options & RES_USEVC)
                     || buflen > PACKETSZ
                     || buflen2 > PACKETSZ);
@@ -384,170 +423,69 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
         * If the ns_addr_list in the resolver context has changed, then
         * invalidate our cached copy and the associated timing data.
         */
-       if (EXT(statp).nsinit) {
+       if (EXT(statp).nscount != 0) {
                int needclose = 0;
 
                if (EXT(statp).nscount != statp->nscount)
                        needclose++;
                else
-                       for (ns = 0; ns < MAXNS; ns++) {
-                               unsigned int map = EXT(statp).nsmap[ns];
-                               if (map < MAXNS
+                       for (unsigned int ns = 0; ns < statp->nscount; ns++) {
+                               if (statp->nsaddr_list[ns].sin_family != 0
                                    && !sock_eq((struct sockaddr_in6 *)
-                                               &statp->nsaddr_list[map],
+                                               &statp->nsaddr_list[ns],
                                                EXT(statp).nsaddrs[ns]))
                                {
                                        needclose++;
                                        break;
                                }
                        }
-               if (needclose)
+               if (needclose) {
                        __res_iclose(statp, false);
+                       EXT(statp).nscount = 0;
+               }
        }
 
        /*
         * Maybe initialize our private copy of the ns_addr_list.
         */
-       if (EXT(statp).nsinit == 0) {
-               unsigned char map[MAXNS];
-
-               memset (map, MAXNS, sizeof (map));
-               for (n = 0; n < MAXNS; n++) {
-                       ns = EXT(statp).nsmap[n];
-                       if (ns < statp->nscount)
-                               map[ns] = n;
-                       else if (ns < MAXNS) {
-                               free(EXT(statp).nsaddrs[n]);
-                               EXT(statp).nsaddrs[n] = NULL;
-                               EXT(statp).nsmap[n] = MAXNS;
-                       }
-               }
-               n = statp->nscount;
-               if (statp->nscount > EXT(statp).nscount)
-                       for (n = EXT(statp).nscount, ns = 0;
-                            n < statp->nscount; n++) {
-                               while (ns < MAXNS
-                                      && EXT(statp).nsmap[ns] != MAXNS)
-                                       ns++;
-                               if (ns == MAXNS)
-                                       break;
-                               /* NS never exceeds MAXNS, but gcc 4.9 somehow
-                                  does not see this.  */
-                               DIAG_PUSH_NEEDS_COMMENT;
-                               DIAG_IGNORE_NEEDS_COMMENT (4.9,
-                                                          "-Warray-bounds");
-                               EXT(statp).nsmap[ns] = n;
-                               DIAG_POP_NEEDS_COMMENT;
-                               map[n] = ns++;
-                       }
-               EXT(statp).nscount = n;
-               for (ns = 0; ns < EXT(statp).nscount; ns++) {
-                       n = map[ns];
-                       if (EXT(statp).nsaddrs[n] == NULL)
-                               EXT(statp).nsaddrs[n] =
+       if (EXT(statp).nscount == 0) {
+               for (unsigned int ns = 0; ns < statp->nscount; ns++) {
+                       EXT(statp).nssocks[ns] = -1;
+                       if (statp->nsaddr_list[ns].sin_family == 0)
+                               continue;
+                       if (EXT(statp).nsaddrs[ns] == NULL)
+                               EXT(statp).nsaddrs[ns] =
                                    malloc(sizeof (struct sockaddr_in6));
-                       if (EXT(statp).nsaddrs[n] != NULL) {
-                               memset (mempcpy(EXT(statp).nsaddrs[n],
+                       if (EXT(statp).nsaddrs[ns] != NULL)
+                               memset (mempcpy(EXT(statp).nsaddrs[ns],
                                                &statp->nsaddr_list[ns],
                                                sizeof (struct sockaddr_in)),
                                        '\0',
                                        sizeof (struct sockaddr_in6)
                                        - sizeof (struct sockaddr_in));
-                               EXT(statp).nssocks[n] = -1;
-                               n++;
-                       }
+                       else
+                               return -1;
                }
-               EXT(statp).nsinit = 1;
+               EXT(statp).nscount = statp->nscount;
        }
 
-       /*
-        * Some resolvers want to even out the load on their nameservers.
-        * Note that RES_BLAST overrides RES_ROTATE.
-        */
-       if (__builtin_expect ((statp->options & RES_ROTATE) != 0, 0) &&
-           (statp->options & RES_BLAST) == 0) {
-               struct sockaddr_in6 *ina;
-               unsigned int map;
-
-               n = 0;
-               while (n < MAXNS && EXT(statp).nsmap[n] == MAXNS)
-                       n++;
-               if (n < MAXNS) {
-                       ina = EXT(statp).nsaddrs[n];
-                       map = EXT(statp).nsmap[n];
-                       for (;;) {
-                               ns = n + 1;
-                               while (ns < MAXNS
-                                      && EXT(statp).nsmap[ns] == MAXNS)
-                                       ns++;
-                               if (ns == MAXNS)
-                                       break;
-                               EXT(statp).nsaddrs[n] = EXT(statp).nsaddrs[ns];
-                               EXT(statp).nsmap[n] = EXT(statp).nsmap[ns];
-                               n = ns;
-                       }
-                       EXT(statp).nsaddrs[n] = ina;
-                       EXT(statp).nsmap[n] = map;
-               }
-       }
+       /* Name server index offset.  Used to implement
+          RES_ROTATE.  */
+       unsigned int ns_offset = nameserver_offset (statp);
 
        /*
         * Send request, RETRY times, or until successful.
         */
        for (try = 0; try < statp->retry; try++) {
-           for (ns = 0; ns < MAXNS; ns++)
+           for (unsigned ns_shift = 0; ns_shift < statp->nscount; ns_shift++)
            {
-#ifdef DEBUG
-               char tmpbuf[40];
-#endif
-               struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
+               /* The actual name server index.  This implements
+                  RES_ROTATE.  */
+               unsigned int ns = ns_shift + ns_offset;
+               if (ns >= statp->nscount)
+                       ns -= statp->nscount;
 
-               if (nsap == NULL)
-                       goto next_ns;
            same_ns:
-#ifdef USE_HOOKS
-               if (__glibc_unlikely (statp->qhook != NULL))       {
-                       int done = 0, loops = 0;
-
-                       do {
-                               res_sendhookact act;
-
-                               struct sockaddr_in *nsap4;
-                               nsap4 = (struct sockaddr_in *) nsap;
-                               act = (*statp->qhook)(&nsap4, &buf, &buflen,
-                                                     ans, anssiz, &resplen);
-                               nsap = (struct sockaddr_in6 *) nsap4;
-                               switch (act) {
-                               case res_goahead:
-                                       done = 1;
-                                       break;
-                               case res_nextns:
-                                       __res_iclose(statp, false);
-                                       goto next_ns;
-                               case res_done:
-                                       return (resplen);
-                               case res_modified:
-                                       /* give the hook another try */
-                                       if (++loops < 42) /*doug adams*/
-                                               break;
-                                       /*FALLTHROUGH*/
-                               case res_error:
-                                       /*FALLTHROUGH*/
-                               default:
-                                       return (-1);
-                               }
-                       } while (!done);
-               }
-#endif
-
-               Dprint(statp->options & RES_DEBUG,
-                      (stdout, ";; Querying server (# %d) address = %s\n",
-                       ns + 1, inet_ntop(nsap->sin6_family,
-                                         (nsap->sin6_family == AF_INET6
-                                          ? &nsap->sin6_addr
-                                          : &((struct sockaddr_in *) nsap)->sin_addr),
-                                         tmpbuf, sizeof (tmpbuf))));
-
                if (__glibc_unlikely (v_circuit))       {
                        /* Use VC; at most one attempt per server. */
                        try = statp->retry;
@@ -577,22 +515,6 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
 
                resplen = n;
 
-               Dprint((statp->options & RES_DEBUG) ||
-                      ((statp->pfcode & RES_PRF_REPLY) &&
-                       (statp->pfcode & RES_PRF_HEAD1)),
-                      (stdout, ";; got answer:\n"));
-
-               DprintQ((statp->options & RES_DEBUG) ||
-                       (statp->pfcode & RES_PRF_REPLY),
-                       (stdout, "%s", ""),
-                       ans, (resplen > anssiz) ? anssiz : resplen);
-               if (buf2 != NULL) {
-                 DprintQ((statp->options & RES_DEBUG) ||
-                         (statp->pfcode & RES_PRF_REPLY),
-                         (stdout, "%s", ""),
-                         *ansp2, (*resplen2 > *nansp2) ? *nansp2 : *resplen2);
-               }
-
                /*
                 * If we have temporarily opened a virtual circuit,
                 * or if we haven't been asked to keep a socket open,
@@ -602,38 +524,6 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                    (statp->options & RES_STAYOPEN) == 0) {
                        __res_iclose(statp, false);
                }
-#ifdef USE_HOOKS
-               if (__glibc_unlikely (statp->rhook))       {
-                       int done = 0, loops = 0;
-
-                       do {
-                               res_sendhookact act;
-
-                               act = (*statp->rhook)((struct sockaddr_in *)
-                                                     nsap, buf, buflen,
-                                                     ans, anssiz, &resplen);
-                               switch (act) {
-                               case res_goahead:
-                               case res_done:
-                                       done = 1;
-                                       break;
-                               case res_nextns:
-                                       __res_iclose(statp, false);
-                                       goto next_ns;
-                               case res_modified:
-                                       /* give the hook another try */
-                                       if (++loops < 42) /*doug adams*/
-                                               break;
-                                       /*FALLTHROUGH*/
-                               case res_error:
-                                       /*FALLTHROUGH*/
-                               default:
-                                       return (-1);
-                               }
-                       } while (!done);
-
-               }
-#endif
                return (resplen);
  next_ns: ;
           } /*foreach ns*/
@@ -649,17 +539,139 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
        return (-1);
 }
 
+/* Common part of res_nsend and res_send.  */
+static int
+context_send_common (struct resolv_context *ctx,
+                    const unsigned char *buf, int buflen,
+                    unsigned char *ans, int anssiz)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return -1;
+    }
+  int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
+                                  NULL, NULL, NULL, NULL, NULL);
+  __resolv_context_put (ctx);
+  return result;
+}
+
 int
-res_nsend(res_state statp,
-         const u_char *buf, int buflen, u_char *ans, int anssiz)
+res_nsend (res_state statp, const unsigned char *buf, int buflen,
+          unsigned char *ans, int anssiz)
 {
-  return __libc_res_nsend(statp, buf, buflen, NULL, 0, ans, anssiz,
-                         NULL, NULL, NULL, NULL, NULL);
+  return context_send_common
+    (__resolv_context_get_override (statp), buf, buflen, ans, anssiz);
+}
+
+int
+res_send (const unsigned char *buf, int buflen, unsigned char *ans, int anssiz)
+{
+  return context_send_common
+    (__resolv_context_get (), buf, buflen, ans, anssiz);
 }
-libresolv_hidden_def (res_nsend)
 
 /* Private */
 
+static struct sockaddr *
+get_nsaddr (res_state statp, unsigned int n)
+{
+  assert (n < statp->nscount);
+
+  if (statp->nsaddr_list[n].sin_family == 0 && EXT(statp).nsaddrs[n] != NULL)
+    /* EXT(statp).nsaddrs[n] holds an address that is larger than
+       struct sockaddr, and user code did not update
+       statp->nsaddr_list[n].  */
+    return (struct sockaddr *) EXT(statp).nsaddrs[n];
+  else
+    /* User code updated statp->nsaddr_list[n], or statp->nsaddr_list[n]
+       has the same content as EXT(statp).nsaddrs[n].  */
+    return (struct sockaddr *) (void *) &statp->nsaddr_list[n];
+}
+
+/* Close the resolver structure, assign zero to *RESPLEN2 if RESPLEN2
+   is not NULL, and return zero.  */
+static int
+__attribute__ ((warn_unused_result))
+close_and_return_error (res_state statp, int *resplen2)
+{
+  __res_iclose(statp, false);
+  if (resplen2 != NULL)
+    *resplen2 = 0;
+  return 0;
+}
+
+/* The send_vc function is responsible for sending a DNS query over TCP
+   to the nameserver numbered NS from the res_state STATP i.e.
+   EXT(statp).nssocks[ns].  The function supports sending both IPv4 and
+   IPv6 queries at the same serially on the same socket.
+
+   Please note that for TCP there is no way to disable sending both
+   queries, unlike UDP, which honours RES_SNGLKUP and RES_SNGLKUPREOP
+   and sends the queries serially and waits for the result after each
+   sent query.  This implementation should be corrected to honour these
+   options.
+
+   Please also note that for TCP we send both queries over the same
+   socket one after another.  This technically violates best practice
+   since the server is allowed to read the first query, respond, and
+   then close the socket (to service another client).  If the server
+   does this, then the remaining second query in the socket data buffer
+   will cause the server to send the client an RST which will arrive
+   asynchronously and the client's OS will likely tear down the socket
+   receive buffer resulting in a potentially short read and lost
+   response data.  This will force the client to retry the query again,
+   and this process may repeat until all servers and connection resets
+   are exhausted and then the query will fail.  It's not known if this
+   happens with any frequency in real DNS server implementations.  This
+   implementation should be corrected to use two sockets by default for
+   parallel queries.
+
+   The query stored in BUF of BUFLEN length is sent first followed by
+   the query stored in BUF2 of BUFLEN2 length.  Queries are sent
+   serially on the same socket.
+
+   Answers to the query are stored firstly in *ANSP up to a max of
+   *ANSSIZP bytes.  If more than *ANSSIZP bytes are needed and ANSCP
+   is non-NULL (to indicate that modifying the answer buffer is allowed)
+   then malloc is used to allocate a new response buffer and ANSCP and
+   ANSP will both point to the new buffer.  If more than *ANSSIZP bytes
+   are needed but ANSCP is NULL, then as much of the response as
+   possible is read into the buffer, but the results will be truncated.
+   When truncation happens because of a small answer buffer the DNS
+   packets header field TC will bet set to 1, indicating a truncated
+   message and the rest of the socket data will be read and discarded.
+
+   Answers to the query are stored secondly in *ANSP2 up to a max of
+   *ANSSIZP2 bytes, with the actual response length stored in
+   *RESPLEN2.  If more than *ANSSIZP bytes are needed and ANSP2
+   is non-NULL (required for a second query) then malloc is used to
+   allocate a new response buffer, *ANSSIZP2 is set to the new buffer
+   size and *ANSP2_MALLOCED is set to 1.
+
+   The ANSP2_MALLOCED argument will eventually be removed as the
+   change in buffer pointer can be used to detect the buffer has
+   changed and that the caller should use free on the new buffer.
+
+   Note that the answers may arrive in any order from the server and
+   therefore the first and second answer buffers may not correspond to
+   the first and second queries.
+
+   It is not supported to call this function with a non-NULL ANSP2
+   but a NULL ANSCP.  Put another way, you can call send_vc with a
+   single unmodifiable buffer or two modifiable buffers, but no other
+   combination is supported.
+
+   It is the caller's responsibility to free the malloc allocated
+   buffers by detecting that the pointers have changed from their
+   original values i.e. *ANSCP or *ANSP2 has changed.
+
+   If errors are encountered then *TERRNO is set to an appropriate
+   errno value and a zero result is returned for a recoverable error,
+   and a less-than zero result is returned for a non-recoverable error.
+
+   If no errors are encountered then *TERRNO is left unmodified and
+   a the length of the first response in bytes is returned.  */
 static int
 send_vc(res_state statp,
        const u_char *buf, int buflen, const u_char *buf2, int buflen2,
@@ -669,12 +681,8 @@ send_vc(res_state statp,
 {
        const HEADER *hp = (HEADER *) buf;
        const HEADER *hp2 = (HEADER *) buf2;
-       u_char *ans = *ansp;
-       int orig_anssizp = *anssizp;
-       // XXX REMOVE
-       // int anssiz = *anssizp;
-       HEADER *anhp = (HEADER *) ans;
-       struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
+       HEADER *anhp = (HEADER *) *ansp;
+       struct sockaddr *nsap = get_nsaddr (statp, ns);
        int truncating, connreset, n;
        /* On some architectures compiler might emit a warning indicating
           'resplen' may be used uninitialized.  However if buf2 == NULL
@@ -685,21 +693,15 @@ send_vc(res_state statp,
           times round the loop resplen has been initialized.  So this is
           a false-positive.
         */
-#if __GNUC_PREREQ (4, 7)
        DIAG_PUSH_NEEDS_COMMENT;
        DIAG_IGNORE_NEEDS_COMMENT (5, "-Wmaybe-uninitialized");
-#endif
        int resplen;
-#if __GNUC_PREREQ (4, 7)
        DIAG_POP_NEEDS_COMMENT;
-#endif
        struct iovec iov[4];
        u_short len;
        u_short len2;
        u_char *cp;
 
-       if (resplen2 != NULL)
-         *resplen2 = 0;
        connreset = 0;
  same_ns:
        truncating = 0;
@@ -711,8 +713,8 @@ send_vc(res_state statp,
 
                if (getpeername(statp->_vcsock,
                                (struct sockaddr *)&peer, &size) < 0 ||
-                   !sock_eq(&peer, nsap)) {
-                 __res_iclose(statp, false);
+                   !sock_eq(&peer, (struct sockaddr_in6 *) nsap)) {
+                       __res_iclose(statp, false);
                        statp->_flags &= ~RES_F_VC;
                }
        }
@@ -721,22 +723,21 @@ send_vc(res_state statp,
                if (statp->_vcsock >= 0)
                  __res_iclose(statp, false);
 
-               statp->_vcsock = socket(nsap->sin6_family, SOCK_STREAM, 0);
+               statp->_vcsock = socket
+                 (nsap->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0);
                if (statp->_vcsock < 0) {
                        *terrno = errno;
-                       Perror(statp, stderr, "socket(vc)", errno);
+                       if (resplen2 != NULL)
+                         *resplen2 = 0;
                        return (-1);
                }
                __set_errno (0);
-               if (connect(statp->_vcsock, (struct sockaddr *)nsap,
-                           nsap->sin6_family == AF_INET
+               if (connect(statp->_vcsock, nsap,
+                           nsap->sa_family == AF_INET
                            ? sizeof (struct sockaddr_in)
                            : sizeof (struct sockaddr_in6)) < 0) {
                        *terrno = errno;
-                       Aerror(statp, stderr, "connect/vc", errno,
-                              (struct sockaddr *) nsap);
-                       __res_iclose(statp, false);
-                       return (0);
+                       return close_and_return_error (statp, resplen2);
                }
                statp->_flags |= RES_F_VC;
        }
@@ -758,14 +759,14 @@ send_vc(res_state statp,
        }
        if (TEMP_FAILURE_RETRY (writev(statp->_vcsock, iov, niov)) != explen) {
                *terrno = errno;
-               Perror(statp, stderr, "write failed", errno);
-               __res_iclose(statp, false);
-               return (0);
+               return close_and_return_error (statp, resplen2);
        }
        /*
         * Receive length & response
         */
        int recvresp1 = 0;
+       /* Skip the second response if there is no second query.
+          To do that we mark the second response as received.  */
        int recvresp2 = buf2 == NULL;
        uint16_t rlen16;
  read_len:
@@ -779,8 +780,6 @@ send_vc(res_state statp,
        }
        if (n <= 0) {
                *terrno = errno;
-               Perror(statp, stderr, "read failed", errno);
-               __res_iclose(statp, false);
                /*
                 * A long running process might get its TCP
                 * connection reset if the remote server was
@@ -790,11 +789,13 @@ send_vc(res_state statp,
                 * instead of failing.  We only allow one reset
                 * per query to prevent looping.
                 */
-               if (*terrno == ECONNRESET && !connreset) {
-                       connreset = 1;
-                       goto same_ns;
-               }
-               return (0);
+               if (*terrno == ECONNRESET && !connreset)
+                 {
+                   __res_iclose (statp, false);
+                   connreset = 1;
+                   goto same_ns;
+                 }
+               return close_and_return_error (statp, resplen2);
        }
        int rlen = ntohs (rlen16);
 
@@ -802,40 +803,14 @@ send_vc(res_state statp,
        u_char **thisansp;
        int *thisresplenp;
        if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {
+               /* We have not received any responses
+                  yet or we only have one response to
+                  receive.  */
                thisanssizp = anssizp;
                thisansp = anscp ?: ansp;
                assert (anscp != NULL || ansp2 == NULL);
                thisresplenp = &resplen;
        } else {
-               if (*anssizp != MAXPACKET) {
-                       /* No buffer allocated for the first
-                          reply.  We can try to use the rest
-                          of the user-provided buffer.  */
-#if __GNUC_PREREQ (4, 7)
-                       DIAG_PUSH_NEEDS_COMMENT;
-                       DIAG_IGNORE_NEEDS_COMMENT (5, "-Wmaybe-uninitialized");
-#endif
-#if _STRING_ARCH_unaligned
-                       *anssizp2 = orig_anssizp - resplen;
-                       *ansp2 = *ansp + resplen;
-#else
-                       int aligned_resplen
-                         = ((resplen + __alignof__ (HEADER) - 1)
-                            & ~(__alignof__ (HEADER) - 1));
-                       *anssizp2 = orig_anssizp - aligned_resplen;
-                       *ansp2 = *ansp + aligned_resplen;
-#endif
-#if __GNUC_PREREQ (4, 7)
-                       DIAG_POP_NEEDS_COMMENT;
-#endif
-               } else {
-                       /* The first reply did not fit into the
-                          user-provided buffer.  Maybe the second
-                          answer will.  */
-                       *anssizp2 = orig_anssizp;
-                       *ansp2 = *ansp;
-               }
-
                thisanssizp = anssizp2;
                thisansp = ansp2;
                thisresplenp = resplen2;
@@ -843,26 +818,30 @@ send_vc(res_state statp,
        anhp = (HEADER *) *thisansp;
 
        *thisresplenp = rlen;
-       if (rlen > *thisanssizp) {
-               /* Yes, we test ANSCP here.  If we have two buffers
-                  both will be allocatable.  */
-               if (__glibc_likely (anscp != NULL))       {
+       /* Is the answer buffer too small?  */
+       if (*thisanssizp < rlen) {
+               /* If the current buffer is not the the static
+                  user-supplied buffer then we can reallocate
+                  it.  */
+               if (thisansp != NULL && thisansp != ansp) {
+                       /* Always allocate MAXPACKET, callers expect
+                          this specific size.  */
                        u_char *newp = malloc (MAXPACKET);
-                       if (newp == NULL) {
-                               *terrno = ENOMEM;
-                               __res_iclose(statp, false);
-                               return (0);
-                       }
+                       if (newp == NULL)
+                         {
+                           *terrno = ENOMEM;
+                           return close_and_return_error (statp, resplen2);
+                         }
                        *thisanssizp = MAXPACKET;
                        *thisansp = newp;
                        if (thisansp == ansp2)
                          *ansp2_malloced = 1;
                        anhp = (HEADER *) newp;
+                       /* A uint16_t can't be larger than MAXPACKET
+                          thus it's safe to allocate MAXPACKET but
+                          read RLEN bytes instead.  */
                        len = rlen;
                } else {
-                       Dprint(statp->options & RES_DEBUG,
-                               (stdout, ";; response truncated\n")
-                       );
                        truncating = 1;
                        len = *thisanssizp;
                }
@@ -873,11 +852,8 @@ send_vc(res_state statp,
                /*
                 * Undersized message.
                 */
-               Dprint(statp->options & RES_DEBUG,
-                      (stdout, ";; undersized: %d\n", len));
                *terrno = EMSGSIZE;
-               __res_iclose(statp, false);
-               return (0);
+               return close_and_return_error (statp, resplen2);
        }
 
        cp = *thisansp;
@@ -887,9 +863,7 @@ send_vc(res_state statp,
        }
        if (__glibc_unlikely (n <= 0))       {
                *terrno = errno;
-               Perror(statp, stderr, "read(vc)", errno);
-               __res_iclose(statp, false);
-               return (0);
+               return close_and_return_error (statp, resplen2);
        }
        if (__glibc_unlikely (truncating))       {
                /*
@@ -916,14 +890,8 @@ send_vc(res_state statp,
         * wait for the correct one.
         */
        if ((recvresp1 || hp->id != anhp->id)
-           && (recvresp2 || hp2->id != anhp->id)) {
-               DprintQ((statp->options & RES_DEBUG) ||
-                       (statp->pfcode & RES_PRF_REPLY),
-                       (stdout, ";; old answer (unexpected):\n"),
-                       *thisansp,
-                       (rlen > *thisanssizp) ? *thisanssizp: rlen);
+           && (recvresp2 || hp2->id != anhp->id))
                goto read_len;
-       }
 
        /* Mark which reply we received.  */
        if (recvresp1 == 0 && hp->id == anhp->id)
@@ -945,52 +913,40 @@ static int
 reopen (res_state statp, int *terrno, int ns)
 {
        if (EXT(statp).nssocks[ns] == -1) {
-               struct sockaddr *nsap
-                 = (struct sockaddr *) EXT(statp).nsaddrs[ns];
+               struct sockaddr *nsap = get_nsaddr (statp, ns);
                socklen_t slen;
 
                /* only try IPv6 if IPv6 NS and if not failed before */
                if (nsap->sa_family == AF_INET6 && !statp->ipv6_unavail) {
-                       if (__glibc_likely (__have_o_nonblock >= 0))       {
-                               EXT(statp).nssocks[ns] =
-                                 socket(PF_INET6, SOCK_DGRAM|SOCK_NONBLOCK,
-                                        0);
-#ifndef __ASSUME_SOCK_CLOEXEC
-                               if (__have_o_nonblock == 0)
-                                       __have_o_nonblock
-                                         = (EXT(statp).nssocks[ns] == -1
-                                            && errno == EINVAL ? -1 : 1);
-#endif
-                       }
-                       if (__glibc_unlikely (__have_o_nonblock < 0))
-                               EXT(statp).nssocks[ns] =
-                                 socket(PF_INET6, SOCK_DGRAM, 0);
+                       EXT(statp).nssocks[ns] = socket
+                         (PF_INET6,
+                          SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
                        if (EXT(statp).nssocks[ns] < 0)
                            statp->ipv6_unavail = errno == EAFNOSUPPORT;
                        slen = sizeof (struct sockaddr_in6);
                } else if (nsap->sa_family == AF_INET) {
-                       if (__glibc_likely (__have_o_nonblock >= 0))       {
-                               EXT(statp).nssocks[ns]
-                                 = socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK,
-                                          0);
-#ifndef __ASSUME_SOCK_CLOEXEC
-                               if (__have_o_nonblock == 0)
-                                       __have_o_nonblock
-                                         = (EXT(statp).nssocks[ns] == -1
-                                            && errno == EINVAL ? -1 : 1);
-#endif
-                       }
-                       if (__glibc_unlikely (__have_o_nonblock < 0))
-                               EXT(statp).nssocks[ns]
-                                 = socket(PF_INET, SOCK_DGRAM, 0);
+                       EXT(statp).nssocks[ns] = socket
+                         (PF_INET,
+                          SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
                        slen = sizeof (struct sockaddr_in);
                }
                if (EXT(statp).nssocks[ns] < 0) {
                        *terrno = errno;
-                       Perror(statp, stderr, "socket(dg)", errno);
                        return (-1);
                }
 
+               /* Enable full ICMP error reporting for this
+                  socket.  */
+               if (__res_enable_icmp (nsap->sa_family,
+                                      EXT (statp).nssocks[ns]) < 0)
+                 {
+                   int saved_errno = errno;
+                   __res_iclose (statp, false);
+                   __set_errno (saved_errno);
+                   *terrno = saved_errno;
+                   return -1;
+                 }
+
                /*
                 * On a 4.3BSD+ machine (client and server,
                 * actually), sending to a nameserver datagram
@@ -1002,25 +958,84 @@ reopen (res_state statp, int *terrno, int ns)
                 * error message is received.  We can thus detect
                 * the absence of a nameserver without timing out.
                 */
+               /* With GCC 5.3 when compiling with -Os the compiler
+                  emits a warning that slen may be used uninitialized,
+                  but that is never true.  Both slen and
+                  EXT(statp).nssocks[ns] are initialized together or
+                  the function return -1 before control flow reaches
+                  the call to connect with slen.  */
+               DIAG_PUSH_NEEDS_COMMENT;
+               DIAG_IGNORE_Os_NEEDS_COMMENT (5, "-Wmaybe-uninitialized");
                if (connect(EXT(statp).nssocks[ns], nsap, slen) < 0) {
-                       Aerror(statp, stderr, "connect(dg)", errno, nsap);
+               DIAG_POP_NEEDS_COMMENT;
                        __res_iclose(statp, false);
                        return (0);
                }
-               if (__glibc_unlikely (__have_o_nonblock < 0))       {
-                       /* Make socket non-blocking.  */
-                       int fl = __fcntl (EXT(statp).nssocks[ns], F_GETFL);
-                       if  (fl != -1)
-                               __fcntl (EXT(statp).nssocks[ns], F_SETFL,
-                                        fl | O_NONBLOCK);
-                       Dprint(statp->options & RES_DEBUG,
-                              (stdout, ";; new DG socket\n"))
-               }
        }
 
        return 1;
 }
 
+/* The send_dg function is responsible for sending a DNS query over UDP
+   to the nameserver numbered NS from the res_state STATP i.e.
+   EXT(statp).nssocks[ns].  The function supports IPv4 and IPv6 queries
+   along with the ability to send the query in parallel for both stacks
+   (default) or serially (RES_SINGLKUP).  It also supports serial lookup
+   with a close and reopen of the socket used to talk to the server
+   (RES_SNGLKUPREOP) to work around broken name servers.
+
+   The query stored in BUF of BUFLEN length is sent first followed by
+   the query stored in BUF2 of BUFLEN2 length.  Queries are sent
+   in parallel (default) or serially (RES_SINGLKUP or RES_SNGLKUPREOP).
+
+   Answers to the query are stored firstly in *ANSP up to a max of
+   *ANSSIZP bytes.  If more than *ANSSIZP bytes are needed and ANSCP
+   is non-NULL (to indicate that modifying the answer buffer is allowed)
+   then malloc is used to allocate a new response buffer and ANSCP and
+   ANSP will both point to the new buffer.  If more than *ANSSIZP bytes
+   are needed but ANSCP is NULL, then as much of the response as
+   possible is read into the buffer, but the results will be truncated.
+   When truncation happens because of a small answer buffer the DNS
+   packets header field TC will bet set to 1, indicating a truncated
+   message, while the rest of the UDP packet is discarded.
+
+   Answers to the query are stored secondly in *ANSP2 up to a max of
+   *ANSSIZP2 bytes, with the actual response length stored in
+   *RESPLEN2.  If more than *ANSSIZP bytes are needed and ANSP2
+   is non-NULL (required for a second query) then malloc is used to
+   allocate a new response buffer, *ANSSIZP2 is set to the new buffer
+   size and *ANSP2_MALLOCED is set to 1.
+
+   The ANSP2_MALLOCED argument will eventually be removed as the
+   change in buffer pointer can be used to detect the buffer has
+   changed and that the caller should use free on the new buffer.
+
+   Note that the answers may arrive in any order from the server and
+   therefore the first and second answer buffers may not correspond to
+   the first and second queries.
+
+   It is not supported to call this function with a non-NULL ANSP2
+   but a NULL ANSCP.  Put another way, you can call send_vc with a
+   single unmodifiable buffer or two modifiable buffers, but no other
+   combination is supported.
+
+   It is the caller's responsibility to free the malloc allocated
+   buffers by detecting that the pointers have changed from their
+   original values i.e. *ANSCP or *ANSP2 has changed.
+
+   If an answer is truncated because of UDP datagram DNS limits then
+   *V_CIRCUIT is set to 1 and the return value non-zero to indicate to
+   the caller to retry with TCP.  The value *GOTSOMEWHERE is set to 1
+   if any progress was made reading a response from the nameserver and
+   is used by the caller to distinguish between ECONNREFUSED and
+   ETIMEDOUT (the latter if *GOTSOMEWHERE is 1).
+
+   If errors are encountered then *TERRNO is set to an appropriate
+   errno value and a zero result is returned for a recoverable error,
+   and a less-than zero result is returned for a non-recoverable error.
+
+   If no errors are encountered then *TERRNO is left unmodified and
+   a the length of the first response in bytes is returned.  */
 static int
 send_dg(res_state statp,
        const u_char *buf, int buflen, const u_char *buf2, int buflen2,
@@ -1030,8 +1045,6 @@ send_dg(res_state statp,
 {
        const HEADER *hp = (HEADER *) buf;
        const HEADER *hp2 = (HEADER *) buf2;
-       u_char *ans = *ansp;
-       int orig_anssizp = *anssizp;
        struct timespec now, timeout, finish;
        struct pollfd pfd[1];
        int ptimeout;
@@ -1056,7 +1069,11 @@ send_dg(res_state statp,
  retry_reopen:
        retval = reopen (statp, terrno, ns);
        if (retval <= 0)
-               return retval;
+         {
+           if (resplen2 != NULL)
+             *resplen2 = 0;
+           return retval;
+         }
  retry:
        evNowTime(&now);
        evConsTime(&timeout, seconds, 0);
@@ -1064,21 +1081,18 @@ send_dg(res_state statp,
        int need_recompute = 0;
        int nwritten = 0;
        int recvresp1 = 0;
+       /* Skip the second response if there is no second query.
+          To do that we mark the second response as received.  */
        int recvresp2 = buf2 == NULL;
        pfd[0].fd = EXT(statp).nssocks[ns];
        pfd[0].events = POLLOUT;
-       if (resplen2 != NULL)
-         *resplen2 = 0;
  wait:
        if (need_recompute) {
        recompute_resend:
                evNowTime(&now);
                if (evCmpTime(finish, now) <= 0) {
                poll_err_out:
-                       Perror(statp, stderr, "poll", errno);
-               err_out:
-                       __res_iclose(statp, false);
-                       return (0);
+                       return close_and_return_error (statp, resplen2);
                }
                evSubTime(&timeout, &finish, &now);
                need_recompute = 0;
@@ -1094,7 +1108,6 @@ send_dg(res_state statp,
                need_recompute = 1;
        }
        if (n == 0) {
-               Dprint(statp->options & RES_DEBUG, (stdout, ";; timeout\n"));
                if (resplen > 1 && (recvresp1 || (buf2 != NULL && recvresp2)))
                  {
                    /* There are quite a few broken name servers out
@@ -1125,7 +1138,9 @@ send_dg(res_state statp,
                  }
 
                *gotsomewhere = 1;
-               return (0);
+               if (resplen2 != NULL)
+                 *resplen2 = 0;
+               return 0;
        }
        if (n < 0) {
                if (errno == EINTR)
@@ -1143,25 +1158,27 @@ send_dg(res_state statp,
                if (have_sendmmsg >= 0 && nwritten == 0 && buf2 != NULL
                    && !single_request)
                  {
-                   struct iovec iov[2];
-                   struct mmsghdr reqs[2];
-                   reqs[0].msg_hdr.msg_name = NULL;
-                   reqs[0].msg_hdr.msg_namelen = 0;
-                   reqs[0].msg_hdr.msg_iov = &iov[0];
-                   reqs[0].msg_hdr.msg_iovlen = 1;
-                   iov[0].iov_base = (void *) buf;
-                   iov[0].iov_len = buflen;
-                   reqs[0].msg_hdr.msg_control = NULL;
-                   reqs[0].msg_hdr.msg_controllen = 0;
-
-                   reqs[1].msg_hdr.msg_name = NULL;
-                   reqs[1].msg_hdr.msg_namelen = 0;
-                   reqs[1].msg_hdr.msg_iov = &iov[1];
-                   reqs[1].msg_hdr.msg_iovlen = 1;
-                   iov[1].iov_base = (void *) buf2;
-                   iov[1].iov_len = buflen2;
-                   reqs[1].msg_hdr.msg_control = NULL;
-                   reqs[1].msg_hdr.msg_controllen = 0;
+                   struct iovec iov =
+                     { .iov_base = (void *) buf, .iov_len = buflen };
+                   struct iovec iov2 =
+                     { .iov_base = (void *) buf2, .iov_len = buflen2 };
+                   struct mmsghdr reqs[2] =
+                     {
+                       {
+                         .msg_hdr =
+                           {
+                             .msg_iov = &iov,
+                             .msg_iovlen = 1,
+                           },
+                       },
+                       {
+                         .msg_hdr =
+                           {
+                             .msg_iov = &iov2,
+                             .msg_iovlen = 1,
+                           }
+                       },
+                     };
 
                    int ndg = __sendmmsg (pfd[0].fd, reqs, 2, MSG_NOSIGNAL);
                    if (__glibc_likely (ndg == 2))
@@ -1192,8 +1209,7 @@ send_dg(res_state statp,
 #endif
 
                      fail_sendmmsg:
-                       Perror(statp, stderr, "sendmmsg", errno);
-                       goto err_out;
+                       return close_and_return_error (statp, resplen2);
                      }
                  }
                else
@@ -1210,8 +1226,7 @@ send_dg(res_state statp,
                    if (sr != (nwritten != 0 ? buflen2 : buflen)) {
                      if (errno == EINTR || errno == EAGAIN)
                        goto recompute_resend;
-                     Perror(statp, stderr, "send", errno);
-                     goto err_out;
+                     return close_and_return_error (statp, resplen2);
                    }
                  just_one:
                    if (nwritten != 0 || buf2 == NULL || single_request)
@@ -1227,55 +1242,50 @@ send_dg(res_state statp,
                int *thisresplenp;
 
                if ((recvresp1 | recvresp2) == 0 || buf2 == NULL) {
+                       /* We have not received any responses
+                          yet or we only have one response to
+                          receive.  */
                        thisanssizp = anssizp;
                        thisansp = anscp ?: ansp;
                        assert (anscp != NULL || ansp2 == NULL);
                        thisresplenp = &resplen;
                } else {
-                       if (*anssizp != MAXPACKET) {
-                               /* No buffer allocated for the first
-                                  reply.  We can try to use the rest
-                                  of the user-provided buffer.  */
-#if _STRING_ARCH_unaligned
-                               *anssizp2 = orig_anssizp - resplen;
-                               *ansp2 = *ansp + resplen;
-#else
-                               int aligned_resplen
-                                 = ((resplen + __alignof__ (HEADER) - 1)
-                                    & ~(__alignof__ (HEADER) - 1));
-                               *anssizp2 = orig_anssizp - aligned_resplen;
-                               *ansp2 = *ansp + aligned_resplen;
-#endif
-                       } else {
-                               /* The first reply did not fit into the
-                                  user-provided buffer.  Maybe the second
-                                  answer will.  */
-                               *anssizp2 = orig_anssizp;
-                               *ansp2 = *ansp;
-                       }
-
                        thisanssizp = anssizp2;
                        thisansp = ansp2;
                        thisresplenp = resplen2;
                }
 
                if (*thisanssizp < MAXPACKET
-                   /* Yes, we test ANSCP here.  If we have two buffers
-                      both will be allocatable.  */
-                   && anscp
+                   /* If the current buffer is not the the static
+                      user-supplied buffer then we can reallocate
+                      it.  */
+                   && (thisansp != NULL && thisansp != ansp)
 #ifdef FIONREAD
+                   /* Is the size too small?  */
                    && (ioctl (pfd[0].fd, FIONREAD, thisresplenp) < 0
                        || *thisanssizp < *thisresplenp)
 #endif
                     ) {
+                       /* Always allocate MAXPACKET, callers expect
+                          this specific size.  */
                        u_char *newp = malloc (MAXPACKET);
                        if (newp != NULL) {
-                               *anssizp = MAXPACKET;
-                               *thisansp = ans = newp;
+                               *thisanssizp = MAXPACKET;
+                               *thisansp = newp;
                                if (thisansp == ansp2)
                                  *ansp2_malloced = 1;
                        }
                }
+               /* We could end up with truncation if anscp was NULL
+                  (not allowed to change caller's buffer) and the
+                  response buffer size is too small.  This isn't a
+                  reliable way to detect truncation because the ioctl
+                  may be an inaccurate report of the UDP message size.
+                  Therefore we use this only to issue debug output.
+                  To do truncation accurately with UDP we need
+                  MSG_TRUNC which is only available on Linux.  We
+                  can abstract out the Linux-specific feature in the
+                  future to detect truncation.  */
                HEADER *anhp = (HEADER *) *thisansp;
                socklen_t fromlen = sizeof(struct sockaddr_in6);
                assert (sizeof(from) <= fromlen);
@@ -1287,19 +1297,15 @@ send_dg(res_state statp,
                                need_recompute = 1;
                                goto wait;
                        }
-                       Perror(statp, stderr, "recvfrom", errno);
-                       goto err_out;
+                       return close_and_return_error (statp, resplen2);
                }
                *gotsomewhere = 1;
                if (__glibc_unlikely (*thisresplenp < HFIXEDSZ))       {
                        /*
                         * Undersized message.
                         */
-                       Dprint(statp->options & RES_DEBUG,
-                              (stdout, ";; undersized: %d\n",
-                               *thisresplenp));
                        *terrno = EMSGSIZE;
-                       goto err_out;
+                       return close_and_return_error (statp, resplen2);
                }
                if ((recvresp1 || hp->id != anhp->id)
                    && (recvresp2 || hp2->id != anhp->id)) {
@@ -1308,12 +1314,6 @@ send_dg(res_state statp,
                         * XXX - potential security hazard could
                         *       be detected here.
                         */
-                       DprintQ((statp->options & RES_DEBUG) ||
-                               (statp->pfcode & RES_PRF_REPLY),
-                               (stdout, ";; old answer:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
                        goto wait;
                }
                if (!(statp->options & RES_INSECURE1) &&
@@ -1323,34 +1323,8 @@ send_dg(res_state statp,
                         * XXX - potential security hazard could
                         *       be detected here.
                         */
-                       DprintQ((statp->options & RES_DEBUG) ||
-                               (statp->pfcode & RES_PRF_REPLY),
-                               (stdout, ";; not our server:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
                        goto wait;
                }
-#ifdef RES_USE_EDNS0
-               if (anhp->rcode == FORMERR
-                   && (statp->options & RES_USE_EDNS0) != 0U) {
-                       /*
-                        * Do not retry if the server does not understand
-                        * EDNS0.  The case has to be captured here, as
-                        * FORMERR packet do not carry query section, hence
-                        * res_queriesmatch() returns 0.
-                        */
-                       DprintQ(statp->options & RES_DEBUG,
-                               (stdout,
-                                "server rejected query with EDNS0:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
-                       /* record the error */
-                       statp->_flags |= RES_F_EDNS0ERR;
-                       goto err_out;
-       }
-#endif
                if (!(statp->options & RES_INSECURE2)
                    && (recvresp1 || !res_queriesmatch(buf, buf + buflen,
                                                       *thisansp,
@@ -1365,23 +1339,11 @@ send_dg(res_state statp,
                         * XXX - potential security hazard could
                         *       be detected here.
                         */
-                       DprintQ((statp->options & RES_DEBUG) ||
-                               (statp->pfcode & RES_PRF_REPLY),
-                               (stdout, ";; wrong query name:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
                        goto wait;
                }
                if (anhp->rcode == SERVFAIL ||
                    anhp->rcode == NOTIMP ||
                    anhp->rcode == REFUSED) {
-                       DprintQ(statp->options & RES_DEBUG,
-                               (stdout, "server rejected query:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
-
                next_ns:
                        if (recvresp1 || (buf2 != NULL && recvresp2)) {
                          *resplen2 = 0;
@@ -1400,18 +1362,13 @@ send_dg(res_state statp,
                            goto wait;
                          }
 
-                       __res_iclose(statp, false);
                        /* don't retry if called from dig */
                        if (!statp->pfcode)
-                               return (0);
+                         return close_and_return_error (statp, resplen2);
+                       __res_iclose(statp, false);
                }
                if (anhp->rcode == NOERROR && anhp->ancount == 0
                    && anhp->aa == 0 && anhp->ra == 0 && anhp->arcount == 0) {
-                       DprintQ(statp->options & RES_DEBUG,
-                               (stdout, "referred query:\n"),
-                               *thisansp,
-                               (*thisresplenp > *thisanssizp)
-                               ? *thisanssizp : *thisresplenp);
                        goto next_ns;
                }
                if (!(statp->options & RES_IGNTC) && anhp->tc) {
@@ -1419,12 +1376,12 @@ send_dg(res_state statp,
                         * To get the rest of answer,
                         * use TCP with same server.
                         */
-                       Dprint(statp->options & RES_DEBUG,
-                              (stdout, ";; truncated answer\n"));
                        *v_circuit = 1;
                        __res_iclose(statp, false);
                        // XXX if we have received one reply we could
                        // XXX use it and not repeat it over TCP...
+                       if (resplen2 != NULL)
+                         *resplen2 = 0;
                        return (1);
                }
                /* Mark which reply we received.  */
@@ -1440,67 +1397,28 @@ send_dg(res_state statp,
                                        __res_iclose (statp, false);
                                        retval = reopen (statp, terrno, ns);
                                        if (retval <= 0)
-                                               return retval;
+                                         {
+                                           if (resplen2 != NULL)
+                                             *resplen2 = 0;
+                                           return retval;
+                                         }
                                        pfd[0].fd = EXT(statp).nssocks[ns];
                                }
                        }
                        goto wait;
                }
-               /*
-                * All is well, or the error is fatal.  Signal that the
-                * next nameserver ought not be tried.
-                */
+               /* All is well.  We have received both responses (if
+                  two responses were requested).  */
                return (resplen);
-       } else if (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
-               /* Something went wrong.  We can stop trying.  */
-               goto err_out;
-       }
+       } else if (pfd[0].revents & (POLLERR | POLLHUP | POLLNVAL))
+         /* Something went wrong.  We can stop trying.  */
+         return close_and_return_error (statp, resplen2);
        else {
                /* poll should not have returned > 0 in this case.  */
                abort ();
        }
 }
 
-#ifdef DEBUG
-static void
-Aerror(const res_state statp, FILE *file, const char *string, int error,
-       const struct sockaddr *address)
-{
-       int save = errno;
-
-       if ((statp->options & RES_DEBUG) != 0) {
-               char tmp[sizeof "xxxx.xxxx.xxxx.255.255.255.255"];
-
-               fprintf(file, "res_send: %s ([%s].%u): %s\n",
-                       string,
-                       (address->sa_family == AF_INET
-                        ? inet_ntop(address->sa_family,
-                                    &((const struct sockaddr_in *) address)->sin_addr,
-                                    tmp, sizeof tmp)
-                        : inet_ntop(address->sa_family,
-                                    &((const struct sockaddr_in6 *) address)->sin6_addr,
-                                    tmp, sizeof tmp)),
-                       (address->sa_family == AF_INET
-                        ? ntohs(((struct sockaddr_in *) address)->sin_port)
-                        : address->sa_family == AF_INET6
-                        ? ntohs(((struct sockaddr_in6 *) address)->sin6_port)
-                        : 0),
-                       strerror(error));
-       }
-       __set_errno (save);
-}
-
-static void
-Perror(const res_state statp, FILE *file, const char *string, int error) {
-       int save = errno;
-
-       if ((statp->options & RES_DEBUG) != 0)
-               fprintf(file, "res_send: %s: %s\n",
-                       string, strerror(error));
-       __set_errno (save);
-}
-#endif
-
 static int
 sock_eq(struct sockaddr_in6 *a1, struct sockaddr_in6 *a2) {
        if (a1->sin6_family == a2->sin6_family) {