]> 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 887d048e1958a59595b40fa35ab3469214fe71cf..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,12 +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-diag.h>
+#include <random-bits.h>
 
 #if PACKETSZ > 65536
 #define MAXPACKET       PACKETSZ
@@ -102,7 +117,6 @@ static const char rcsid[] = "$BINDId: res_send.c,v 8.38 2000/03/30 20:16:51 vixi
 #define MAXPACKET       65536
 #endif
 
-
 /* From ev_streams.c.  */
 
 static inline void
@@ -147,7 +161,7 @@ evSubTime(struct timespec *res, const struct timespec *minuend,
        }
 }
 
-static inline int
+static int
 evCmpTime(struct timespec a, struct timespec b) {
        long x = a.tv_sec - b.tv_sec;
 
@@ -156,7 +170,7 @@ evCmpTime(struct timespec a, struct timespec b) {
        return (x < 0L ? (-1) : x > 0L ? (1) : (0));
 }
 
-static inline void
+static void
 evNowTime(struct timespec *res) {
        struct timeval now;
 
@@ -167,31 +181,22 @@ 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,
-                               u_char **, int *, int *, int, u_char **);
+                               const u_char *, int,
+                               u_char **, int *, int *, int, u_char **,
+                               u_char **, int *, int *, int *);
 static int             send_dg(res_state, const u_char *, int,
+                               const u_char *, int,
                                u_char **, int *, int *, int,
-                               int *, int *, u_char **);
-#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
+                               int *, int *, u_char **,
+                               u_char **, int *, int *, int *);
 static int             sock_eq(struct sockaddr_in6 *, struct sockaddr_in6 *);
 
-/* Reachover. */
-
-static void convaddr4to6(struct sockaddr_in6 *sa);
-void res_pquery(const res_state, const u_char *, int, FILE *);
-
 /* Public. */
 
 /* int
@@ -208,36 +213,43 @@ res_ourserver_p(const res_state statp, const struct sockaddr_in6 *inp)
 {
        int ns;
 
-        if (inp->sin6_family == AF_INET) {
-            struct sockaddr_in *in4p = (struct sockaddr_in *) inp;
+       if (inp->sin6_family == AF_INET) {
+           struct sockaddr_in *in4p = (struct sockaddr_in *) inp;
            in_port_t port = in4p->sin_port;
            in_addr_t addr = in4p->sin_addr.s_addr;
 
-            for (ns = 0;  ns < MAXNS;  ns++) {
-                const struct sockaddr_in *srv =
-                   (struct sockaddr_in *)EXT(statp).nsaddrs[ns];
-
-                if ((srv != NULL) && (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) &&
-                    (srv->sin6_port == inp->sin6_port) &&
-                    !(memcmp(&srv->sin6_addr, &in6addr_any,
-                             sizeof (struct in6_addr)) &&
-                      memcmp(&srv->sin6_addr, &inp->sin6_addr,
-                             sizeof (struct in6_addr))))
-                    return (1);
-            }
-        }
+           for (ns = 0;  ns < statp->nscount;  ns++) {
+               const struct sockaddr_in *srv =
+                   (struct sockaddr_in *) get_nsaddr (statp, ns);
+
+               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 < 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)) &&
+                     memcmp(&srv->sin6_addr, &inp->sin6_addr,
+                            sizeof (struct in6_addr))))
+                   return (1);
+           }
+       }
        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)
@@ -277,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)
@@ -333,34 +393,29 @@ 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,
-                u_char *ans, int anssiz, u_char **ansp)
+__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);
                return (-1);
        }
 
-       if (anssiz < HFIXEDSZ) {
+       if (anssiz < (buf2 == NULL ? 1 : 2) * HFIXEDSZ) {
                __set_errno (EINVAL);
                return (-1);
        }
 
-       if ((statp->qhook || statp->rhook) && anssiz < MAXPACKET && ansp) {
-               u_char *buf = malloc (MAXPACKET);
-               if (buf == NULL)
-                       return (-1);
-               memcpy (buf, ans, HFIXEDSZ);
-               *ansp = buf;
-               ans = buf;
-               anssiz = MAXPACKET;
-       }
-
-       DprintQ((statp->options & RES_DEBUG) || (statp->pfcode & RES_PRF_QUERY),
-               (stdout, ";; res_send()\n"), buf, buflen);
-       v_circuit = (statp->options & RES_USEVC) || buflen > PACKETSZ;
+       v_circuit = ((statp->options & RES_USEVC)
+                    || buflen > PACKETSZ
+                    || buflen2 > PACKETSZ);
        gotsomewhere = 0;
        terrno = ETIMEDOUT;
 
@@ -368,188 +423,97 @@ __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;
-                               EXT(statp).nsmap[ns] = n;
-                               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) {
-                               memcpy(EXT(statp).nsaddrs[n],
-                                      &statp->nsaddr_list[ns],
-                                      sizeof (struct sockaddr_in));
-                               EXT(statp).nssocks[n] = -1;
-                               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));
+                       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 ((statp->options & RES_ROTATE) != 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++)
            {
-               struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
-
-               if (nsap == NULL)
-                       goto next_ns;
- same_ns:
-               if (statp->qhook) {
-                       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);
-               }
-
-#ifdef DEBUG
-               char tmpbuf[40];
-#endif
-               Dprint(statp->options & RES_DEBUG,
-                      (stdout, ";; Querying server (# %d) address = %s\n",
-                       ns + 1, inet_ntop(AF_INET6, &nsap->sin6_addr,
-                                         tmpbuf, sizeof (tmpbuf))));
-
-               if (v_circuit) {
+               /* The actual name server index.  This implements
+                  RES_ROTATE.  */
+               unsigned int ns = ns_shift + ns_offset;
+               if (ns >= statp->nscount)
+                       ns -= statp->nscount;
+
+           same_ns:
+               if (__glibc_unlikely (v_circuit))       {
                        /* Use VC; at most one attempt per server. */
                        try = statp->retry;
-                       n = send_vc(statp, buf, buflen, &ans, &anssiz, &terrno,
-                                   ns, ansp);
+                       n = send_vc(statp, buf, buflen, buf2, buflen2,
+                                   &ans, &anssiz, &terrno,
+                                   ns, ansp, ansp2, nansp2, resplen2,
+                                   ansp2_malloced);
                        if (n < 0)
                                return (-1);
-                       if (n == 0)
+                       if (n == 0 && (buf2 == NULL || *resplen2 == 0))
                                goto next_ns;
-                       resplen = n;
                } else {
                        /* Use datagrams. */
-                       n = send_dg(statp, buf, buflen, &ans, &anssiz, &terrno,
-                                   ns, &v_circuit, &gotsomewhere, ansp);
+                       n = send_dg(statp, buf, buflen, buf2, buflen2,
+                                   &ans, &anssiz, &terrno,
+                                   ns, &v_circuit, &gotsomewhere, ansp,
+                                   ansp2, nansp2, resplen2, ansp2_malloced);
                        if (n < 0)
                                return (-1);
-                       if (n == 0)
+                       if (n == 0 && (buf2 == NULL || *resplen2 == 0))
                                goto next_ns;
                        if (v_circuit)
+                         // XXX Check whether both requests failed or
+                         // XXX whether one has been answered successfully
                                goto same_ns;
-                       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);
+               resplen = n;
 
                /*
                 * If we have temporarily opened a virtual circuit,
@@ -560,36 +524,6 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
                    (statp->options & RES_STAYOPEN) == 0) {
                        __res_iclose(statp, false);
                }
-               if (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);
-
-               }
                return (resplen);
  next_ns: ;
           } /*foreach ns*/
@@ -605,29 +539,167 @@ __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, ans, anssiz, 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, u_char **ansp, int *anssizp,
-       int *terrno, int ns, u_char **anscp)
+       const u_char *buf, int buflen, const u_char *buf2, int buflen2,
+       u_char **ansp, int *anssizp,
+       int *terrno, int ns, u_char **anscp, u_char **ansp2, int *anssizp2,
+       int *resplen2, int *ansp2_malloced)
 {
        const HEADER *hp = (HEADER *) buf;
-       u_char *ans = *ansp;
-       int anssiz = *anssizp;
-       HEADER *anhp = (HEADER *) ans;
-       struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
-       int truncating, connreset, resplen, n;
-       struct iovec iov[2];
+       const HEADER *hp2 = (HEADER *) buf2;
+       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
+          then this code won't be executed; if buf2 != NULL, then first
+          time round the loop recvresp1 and recvresp2 will be 0 so this
+          code won't be executed but "thisresplenp = &resplen;" followed
+          by "*thisresplenp = rlen;" will be executed so that subsequent
+          times round the loop resplen has been initialized.  So this is
+          a false-positive.
+        */
+       DIAG_PUSH_NEEDS_COMMENT;
+       DIAG_IGNORE_NEEDS_COMMENT (5, "-Wmaybe-uninitialized");
+       int resplen;
+       DIAG_POP_NEEDS_COMMENT;
+       struct iovec iov[4];
        u_short len;
+       u_short len2;
        u_char *cp;
 
        connreset = 0;
@@ -641,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;
                }
        }
@@ -651,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;
        }
@@ -674,23 +745,34 @@ send_vc(res_state statp,
        /*
         * Send length & message
         */
-       ns_put16((u_short)buflen, (u_char*)&len);
+       len = htons ((u_short) buflen);
        evConsIovec(&len, INT16SZ, &iov[0]);
        evConsIovec((void*)buf, buflen, &iov[1]);
-       if (TEMP_FAILURE_RETRY (writev(statp->_vcsock, iov, 2))
-           != (INT16SZ + buflen)) {
+       int niov = 2;
+       ssize_t explen = INT16SZ + buflen;
+       if (buf2 != NULL) {
+               len2 = htons ((u_short) buflen2);
+               evConsIovec(&len2, INT16SZ, &iov[2]);
+               evConsIovec((void*)buf2, buflen2, &iov[3]);
+               niov = 4;
+               explen += INT16SZ + buflen2;
+       }
+       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:
-       cp = ans;
-       len = INT16SZ;
-       while ((n = TEMP_FAILURE_RETRY (read(statp->_vcsock, (char *)cp,
+       cp = (u_char *)&rlen16;
+       len = sizeof(rlen16);
+       while ((n = TEMP_FAILURE_RETRY (read(statp->_vcsock, cp,
                                             (int)len))) > 0) {
                cp += n;
                if ((len -= n) <= 0)
@@ -698,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
@@ -709,63 +789,88 @@ 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);
        }
-       resplen = ns_get16(ans);
-       if (resplen > anssiz) {
-               if (anscp) {
-                       ans = malloc (MAXPACKET);
-                       if (ans == NULL) {
-                               *terrno = ENOMEM;
-                               __res_iclose(statp, false);
-                               return (0);
-                       }
-                       anssiz = MAXPACKET;
-                       *anssizp = MAXPACKET;
-                       *ansp = ans;
-                       *anscp = ans;
-                       anhp = (HEADER *) ans;
-                       len = resplen;
+       int rlen = ntohs (rlen16);
+
+       int *thisanssizp;
+       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 {
+               thisanssizp = anssizp2;
+               thisansp = ansp2;
+               thisresplenp = resplen2;
+       }
+       anhp = (HEADER *) *thisansp;
+
+       *thisresplenp = rlen;
+       /* 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;
+                           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 = anssiz;
+                       len = *thisanssizp;
                }
        } else
-               len = resplen;
-       if (len < HFIXEDSZ) {
+               len = rlen;
+
+       if (__glibc_unlikely (len < HFIXEDSZ))       {
                /*
                 * 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 = ans;
+
+       cp = *thisansp;
        while (len != 0 && (n = read(statp->_vcsock, (char *)cp, (int)len)) > 0){
                cp += n;
                len -= n;
        }
-       if (n <= 0) {
+       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 (truncating) {
+       if (__glibc_unlikely (truncating))       {
                /*
                 * Flush rest of answer so connection stays in synch.
                 */
                anhp->tc = 1;
-               len = resplen - anssiz;
+               len = rlen - *thisanssizp;
                while (len != 0) {
                        char junk[PACKETSZ];
 
@@ -778,63 +883,70 @@ send_vc(res_state statp,
                }
        }
        /*
-        * If the calling applicating has bailed out of
+        * If the calling application has bailed out of
         * a previous call and failed to arrange to have
         * the circuit closed or the server has got
         * itself confused, then drop the packet and
         * wait for the correct one.
         */
-       if (hp->id != anhp->id) {
-               DprintQ((statp->options & RES_DEBUG) ||
-                       (statp->pfcode & RES_PRF_REPLY),
-                       (stdout, ";; old answer (unexpected):\n"),
-                       ans, (resplen > anssiz) ? anssiz: resplen);
+       if ((recvresp1 || hp->id != anhp->id)
+           && (recvresp2 || hp2->id != anhp->id))
+               goto read_len;
+
+       /* Mark which reply we received.  */
+       if (recvresp1 == 0 && hp->id == anhp->id)
+         recvresp1 = 1;
+       else
+         recvresp2 = 1;
+       /* Repeat waiting if we have a second answer to arrive.  */
+       if ((recvresp1 & recvresp2) == 0)
                goto read_len;
-       }
 
        /*
         * All is well, or the error is fatal.  Signal that the
         * next nameserver ought not be tried.
         */
-       return (resplen);
+       return resplen;
 }
 
 static int
-send_dg(res_state statp,
-       const u_char *buf, int buflen, u_char **ansp, int *anssizp,
-       int *terrno, int ns, int *v_circuit, int *gotsomewhere, u_char **anscp)
+reopen (res_state statp, int *terrno, int ns)
 {
-       const HEADER *hp = (HEADER *) buf;
-       u_char *ans = *ansp;
-       int anssiz = *anssizp;
-       HEADER *anhp = (HEADER *) ans;
-       struct sockaddr_in6 *nsap = EXT(statp).nsaddrs[ns];
-       struct timespec now, timeout, finish;
-       struct pollfd pfd[1];
-        int ptimeout;
-       struct sockaddr_in6 from;
-       static int socket_pf = 0;
-       socklen_t fromlen;
-       int resplen, seconds, n;
-
        if (EXT(statp).nssocks[ns] == -1) {
+               struct sockaddr *nsap = get_nsaddr (statp, ns);
+               socklen_t slen;
+
                /* only try IPv6 if IPv6 NS and if not failed before */
-               if ((EXT(statp).nscount6 > 0) && (socket_pf != PF_INET)) {
-                       EXT(statp).nssocks[ns] =
-                           socket(PF_INET6, SOCK_DGRAM, 0);
-                       socket_pf = EXT(statp).nssocks[ns] < 0 ? PF_INET
-                                                              : PF_INET6;
+               if (nsap->sa_family == AF_INET6 && !statp->ipv6_unavail) {
+                       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) {
+                       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)
-                       EXT(statp).nssocks[ns] = socket(PF_INET, SOCK_DGRAM, 0);
                if (EXT(statp).nssocks[ns] < 0) {
                        *terrno = errno;
-                       Perror(statp, stderr, "socket(dg)", errno);
                        return (-1);
                }
-               /* If IPv6 socket and nsap is IPv4, make it IPv4-mapped */
-               if ((socket_pf == PF_INET6) && (nsap->sin6_family == AF_INET))
-                       convaddr4to6(nsap);
+
+               /* 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
@@ -846,35 +958,132 @@ send_dg(res_state statp,
                 * error message is received.  We can thus detect
                 * the absence of a nameserver without timing out.
                 */
-               if (connect(EXT(statp).nssocks[ns], (struct sockaddr *)nsap,
-                           sizeof *nsap) < 0) {
-                       Aerror(statp, stderr, "connect(dg)", errno,
-                              (struct sockaddr *) nsap);
+               /* 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) {
+               DIAG_POP_NEEDS_COMMENT;
                        __res_iclose(statp, false);
                        return (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,
+       u_char **ansp, int *anssizp,
+       int *terrno, int ns, int *v_circuit, int *gotsomewhere, u_char **anscp,
+       u_char **ansp2, int *anssizp2, int *resplen2, int *ansp2_malloced)
+{
+       const HEADER *hp = (HEADER *) buf;
+       const HEADER *hp2 = (HEADER *) buf2;
+       struct timespec now, timeout, finish;
+       struct pollfd pfd[1];
+       int ptimeout;
+       struct sockaddr_in6 from;
+       int resplen = 0;
+       int n;
+
        /*
         * Compute time for the total operation.
         */
-       seconds = (statp->retrans << ns);
+       int seconds = (statp->retrans << ns);
        if (ns > 0)
                seconds /= statp->nscount;
        if (seconds <= 0)
                seconds = 1;
+       bool single_request_reopen = (statp->options & RES_SNGLKUPREOP) != 0;
+       bool single_request = (((statp->options & RES_SNGLKUP) != 0)
+                              | single_request_reopen);
+       int save_gotsomewhere = *gotsomewhere;
+
+       int retval;
+ retry_reopen:
+       retval = reopen (statp, terrno, ns);
+       if (retval <= 0)
+         {
+           if (resplen2 != NULL)
+             *resplen2 = 0;
+           return retval;
+         }
+ retry:
        evNowTime(&now);
        evConsTime(&timeout, seconds, 0);
        evAddTime(&finish, &now, &timeout);
        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;
  wait:
@@ -883,28 +1092,55 @@ send_dg(res_state statp,
                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;
        }
-        /* Convert struct timespec in milliseconds.  */
+       /* Convert struct timespec in milliseconds.  */
        ptimeout = timeout.tv_sec * 1000 + timeout.tv_nsec / 1000000;
 
        n = 0;
        if (nwritten == 0)
          n = __poll (pfd, 1, 0);
-       if (__builtin_expect (n == 0, 0)) {
+       if (__glibc_unlikely (n == 0))       {
                n = __poll (pfd, 1, ptimeout);
                need_recompute = 1;
        }
        if (n == 0) {
-               Dprint(statp->options & RES_DEBUG, (stdout,
-                                                   ";; timeout sending\n"));
+               if (resplen > 1 && (recvresp1 || (buf2 != NULL && recvresp2)))
+                 {
+                   /* There are quite a few broken name servers out
+                      there which don't handle two outstanding
+                      requests from the same source.  There are also
+                      broken firewall settings.  If we time out after
+                      having received one answer switch to the mode
+                      where we send the second request only once we
+                      have received the first answer.  */
+                   if (!single_request)
+                     {
+                       statp->options |= RES_SNGLKUP;
+                       single_request = true;
+                       *gotsomewhere = save_gotsomewhere;
+                       goto retry;
+                     }
+                   else if (!single_request_reopen)
+                     {
+                       statp->options |= RES_SNGLKUPREOP;
+                       single_request_reopen = true;
+                       *gotsomewhere = save_gotsomewhere;
+                       __res_iclose (statp, false);
+                       goto retry_reopen;
+                     }
+
+                   *resplen2 = 1;
+                   return resplen;
+                 }
+
                *gotsomewhere = 1;
-               return (0);
+               if (resplen2 != NULL)
+                 *resplen2 = 0;
+               return 0;
        }
        if (n < 0) {
                if (errno == EINTR)
@@ -914,63 +1150,170 @@ send_dg(res_state statp,
        }
        __set_errno (0);
        if (pfd[0].revents & POLLOUT) {
-               if (send (pfd[0].fd, buf, buflen, MSG_NOSIGNAL) != buflen) {
-                       if (errno == EINTR || errno == EAGAIN)
-                               goto recompute_resend;
-                       Perror(statp, stderr, "send", errno);
-                       goto err_out;
-               }
-               pfd[0].events = POLLIN;
-               ++nwritten;
+#ifndef __ASSUME_SENDMMSG
+               static int have_sendmmsg;
+#else
+# define have_sendmmsg 1
+#endif
+               if (have_sendmmsg >= 0 && nwritten == 0 && buf2 != NULL
+                   && !single_request)
+                 {
+                   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))
+                     {
+                       if (reqs[0].msg_len != buflen
+                           || reqs[1].msg_len != buflen2)
+                         goto fail_sendmmsg;
+
+                       pfd[0].events = POLLIN;
+                       nwritten += 2;
+                     }
+                   else if (ndg == 1 && reqs[0].msg_len == buflen)
+                     goto just_one;
+                   else if (ndg < 0 && (errno == EINTR || errno == EAGAIN))
+                     goto recompute_resend;
+                   else
+                     {
+#ifndef __ASSUME_SENDMMSG
+                       if (__glibc_unlikely (have_sendmmsg == 0))
+                         {
+                           if (ndg < 0 && errno == ENOSYS)
+                             {
+                               have_sendmmsg = -1;
+                               goto try_send;
+                             }
+                           have_sendmmsg = 1;
+                         }
+#endif
+
+                     fail_sendmmsg:
+                       return close_and_return_error (statp, resplen2);
+                     }
+                 }
+               else
+                 {
+                   ssize_t sr;
+#ifndef __ASSUME_SENDMMSG
+                 try_send:
+#endif
+                   if (nwritten != 0)
+                     sr = send (pfd[0].fd, buf2, buflen2, MSG_NOSIGNAL);
+                   else
+                     sr = send (pfd[0].fd, buf, buflen, MSG_NOSIGNAL);
+
+                   if (sr != (nwritten != 0 ? buflen2 : buflen)) {
+                     if (errno == EINTR || errno == EAGAIN)
+                       goto recompute_resend;
+                     return close_and_return_error (statp, resplen2);
+                   }
+                 just_one:
+                   if (nwritten != 0 || buf2 == NULL || single_request)
+                     pfd[0].events = POLLIN;
+                   else
+                     pfd[0].events = POLLIN | POLLOUT;
+                   ++nwritten;
+                 }
                goto wait;
        } else if (pfd[0].revents & POLLIN) {
-               fromlen = sizeof(struct sockaddr_in6);
-               if (anssiz < MAXPACKET
-                   && anscp
-                   && (ioctl (pfd[0].fd, FIONREAD, &resplen) < 0
-               || anssiz < resplen)) {
-                       ans = malloc (MAXPACKET);
-                       if (ans == NULL)
-                               ans = *ansp;
-                       else {
-                               anssiz = MAXPACKET;
-                               *anssizp = MAXPACKET;
-                               *ansp = ans;
-                               *anscp = ans;
-                               anhp = (HEADER *) ans;
+               int *thisanssizp;
+               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 {
+                       thisanssizp = anssizp2;
+                       thisansp = ansp2;
+                       thisresplenp = resplen2;
+               }
+
+               if (*thisanssizp < MAXPACKET
+                   /* 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) {
+                               *thisanssizp = MAXPACKET;
+                               *thisansp = newp;
+                               if (thisansp == ansp2)
+                                 *ansp2_malloced = 1;
                        }
                }
-               resplen = recvfrom(pfd[0].fd, (char*)ans, anssiz,0,
-                                  (struct sockaddr *)&from, &fromlen);
-               if (resplen <= 0) {
+               /* 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);
+               *thisresplenp = recvfrom(pfd[0].fd, (char*)*thisansp,
+                                        *thisanssizp, 0,
+                                       (struct sockaddr *)&from, &fromlen);
+               if (__glibc_unlikely (*thisresplenp <= 0))       {
                        if (errno == EINTR || errno == EAGAIN) {
                                need_recompute = 1;
                                goto wait;
                        }
-                       Perror(statp, stderr, "recvfrom", errno);
-                       goto err_out;
+                       return close_and_return_error (statp, resplen2);
                }
                *gotsomewhere = 1;
-               if (resplen < HFIXEDSZ) {
+               if (__glibc_unlikely (*thisresplenp < HFIXEDSZ))       {
                        /*
                         * Undersized message.
                         */
-                       Dprint(statp->options & RES_DEBUG,
-                              (stdout, ";; undersized: %d\n",
-                               resplen));
                        *terrno = EMSGSIZE;
-                       goto err_out;
+                       return close_and_return_error (statp, resplen2);
                }
-               if (hp->id != anhp->id) {
+               if ((recvresp1 || hp->id != anhp->id)
+                   && (recvresp2 || hp2->id != anhp->id)) {
                        /*
                         * response from old query, ignore it.
                         * XXX - potential security hazard could
                         *       be detected here.
                         */
-                       DprintQ((statp->options & RES_DEBUG) ||
-                               (statp->pfcode & RES_PRF_REPLY),
-                               (stdout, ";; old answer:\n"),
-                               ans, (resplen > anssiz) ? anssiz : resplen);
                        goto wait;
                }
                if (!(statp->options & RES_INSECURE1) &&
@@ -980,43 +1323,52 @@ 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"),
-                               ans, (resplen > anssiz) ? anssiz : resplen);
                        goto wait;
                }
-               if (!(statp->options & RES_INSECURE2) &&
-                   !res_queriesmatch(buf, buf + buflen,
-                                     ans, ans + anssiz)) {
+               if (!(statp->options & RES_INSECURE2)
+                   && (recvresp1 || !res_queriesmatch(buf, buf + buflen,
+                                                      *thisansp,
+                                                      *thisansp
+                                                      + *thisanssizp))
+                   && (recvresp2 || !res_queriesmatch(buf2, buf2 + buflen2,
+                                                      *thisansp,
+                                                      *thisansp
+                                                      + *thisanssizp))) {
                        /*
                         * response contains wrong query? ignore it.
                         * XXX - potential security hazard could
                         *       be detected here.
                         */
-                       DprintQ((statp->options & RES_DEBUG) ||
-                               (statp->pfcode & RES_PRF_REPLY),
-                               (stdout, ";; wrong query name:\n"),
-                               ans, (resplen > anssiz) ? anssiz : resplen);
                        goto wait;
                }
                if (anhp->rcode == SERVFAIL ||
                    anhp->rcode == NOTIMP ||
                    anhp->rcode == REFUSED) {
-                       DprintQ(statp->options & RES_DEBUG,
-                               (stdout, "server rejected query:\n"),
-                               ans, (resplen > anssiz) ? anssiz : resplen);
                next_ns:
-                       __res_iclose(statp, false);
+                       if (recvresp1 || (buf2 != NULL && recvresp2)) {
+                         *resplen2 = 0;
+                         return resplen;
+                       }
+                       if (buf2 != NULL)
+                         {
+                           /* No data from the first reply.  */
+                           resplen = 0;
+                           /* We are waiting for a possible second reply.  */
+                           if (hp->id == anhp->id)
+                             recvresp1 = 1;
+                           else
+                             recvresp2 = 1;
+
+                           goto wait;
+                         }
+
                        /* 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"),
-                               ans, (resplen > anssiz) ? anssiz : resplen);
                        goto next_ns;
                }
                if (!(statp->options & RES_IGNTC) && anhp->tc) {
@@ -1024,67 +1376,49 @@ 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);
                }
-               /*
-                * All is well, or the error is fatal.  Signal that the
-                * next nameserver ought not be tried.
-                */
+               /* Mark which reply we received.  */
+               if (recvresp1 == 0 && hp->id == anhp->id)
+                       recvresp1 = 1;
+               else
+                       recvresp2 = 1;
+               /* Repeat waiting if we have a second answer to arrive.  */
+               if ((recvresp1 & recvresp2) == 0) {
+                       if (single_request) {
+                               pfd[0].events = POLLOUT;
+                               if (single_request_reopen) {
+                                       __res_iclose (statp, false);
+                                       retval = reopen (statp, terrno, ns);
+                                       if (retval <= 0)
+                                         {
+                                           if (resplen2 != NULL)
+                                             *resplen2 = 0;
+                                           return retval;
+                                         }
+                                       pfd[0].fd = EXT(statp).nssocks[ns];
+                               }
+                       }
+                       goto wait;
+               }
+               /* 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.  */
+               /* 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) {
@@ -1108,22 +1442,3 @@ sock_eq(struct sockaddr_in6 *a1, struct sockaddr_in6 *a2) {
                (a1->sin6_addr.s6_addr32[3] ==
                 ((struct sockaddr_in *)a2)->sin_addr.s_addr));
 }
-
-/*
- * Converts IPv4 family, address and port to
- * IPv6 family, IPv4-mapped IPv6 address and port.
- */
-static void
-convaddr4to6(struct sockaddr_in6 *sa)
-{
-    struct sockaddr_in *sa4p = (struct sockaddr_in *) sa;
-    in_port_t port = sa4p->sin_port;
-    in_addr_t addr = sa4p->sin_addr.s_addr;
-
-    sa->sin6_family = AF_INET6;
-    sa->sin6_port = port;
-    sa->sin6_addr.s6_addr32[0] = 0;
-    sa->sin6_addr.s6_addr32[1] = 0;
-    sa->sin6_addr.s6_addr32[2] = htonl(0xFFFF);
-    sa->sin6_addr.s6_addr32[3] = addr;
-}