+/* 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.
*/
#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
#define MAXPACKET 65536
#endif
-
/* From ev_streams.c. */
static inline void
}
}
-static inline int
+static int
evCmpTime(struct timespec a, struct timespec b) {
long x = a.tv_sec - b.tv_sec;
return (x < 0L ? (-1) : x > 0L ? (1) : (0));
}
-static inline void
+static void
evNowTime(struct timespec *res) {
struct timeval now;
}
-/* 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
{
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)
}
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)
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;
* 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,
(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*/
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;
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;
}
}
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;
}
/*
* 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)
}
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
* 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];
}
}
/*
- * 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
* 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:
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)
}
__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) &&
* 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) {
* 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) {
(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;
-}