aboutsummaryrefslogtreecommitdiffstats
path: root/openbsd-compat/libasr/res_send_async.c
diff options
context:
space:
mode:
Diffstat (limited to 'openbsd-compat/libasr/res_send_async.c')
-rw-r--r--openbsd-compat/libasr/res_send_async.c806
1 files changed, 806 insertions, 0 deletions
diff --git a/openbsd-compat/libasr/res_send_async.c b/openbsd-compat/libasr/res_send_async.c
new file mode 100644
index 00000000..7eeeef48
--- /dev/null
+++ b/openbsd-compat/libasr/res_send_async.c
@@ -0,0 +1,806 @@
+/* $OpenBSD: res_send_async.c,v 1.39 2019/09/28 11:21:07 eric Exp $ */
+/*
+ * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
+#include <arpa/nameser_compat.h>
+#endif
+#include <netdb.h>
+
+#include <asr.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <resolv.h> /* for res_random */
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "asr_private.h"
+
+#define OP_QUERY (0)
+
+static int res_send_async_run(struct asr_query *, struct asr_result *);
+static int sockaddr_connect(const struct sockaddr *, int);
+static int udp_send(struct asr_query *);
+static int udp_recv(struct asr_query *);
+static int tcp_write(struct asr_query *);
+static int tcp_read(struct asr_query *);
+static int validate_packet(struct asr_query *);
+static int setup_query(struct asr_query *, const char *, const char *, int, int);
+static int ensure_ibuf(struct asr_query *, size_t);
+static int iter_ns(struct asr_query *);
+
+#define AS_NS_SA(p) ((p)->as_ctx->ac_ns[(p)->as.dns.nsidx - 1])
+
+
+struct asr_query *
+res_send_async(const unsigned char *buf, int buflen, void *asr)
+{
+ struct asr_ctx *ac;
+ struct asr_query *as;
+ struct asr_unpack p;
+ struct asr_dns_header h;
+ struct asr_dns_query q;
+
+ DPRINT_PACKET("asr: res_send_async()", buf, buflen);
+
+ ac = _asr_use_resolver(asr);
+ if ((as = _asr_async_new(ac, ASR_SEND)) == NULL) {
+ _asr_ctx_unref(ac);
+ return (NULL); /* errno set */
+ }
+ as->as_run = res_send_async_run;
+
+ as->as_flags |= ASYNC_EXTOBUF;
+ as->as.dns.obuf = (unsigned char *)buf;
+ as->as.dns.obuflen = buflen;
+ as->as.dns.obufsize = buflen;
+
+ _asr_unpack_init(&p, buf, buflen);
+ _asr_unpack_header(&p, &h);
+ _asr_unpack_query(&p, &q);
+ if (p.err) {
+ errno = EINVAL;
+ goto err;
+ }
+ as->as.dns.reqid = h.id;
+ as->as.dns.type = q.q_type;
+ as->as.dns.class = q.q_class;
+ as->as.dns.dname = strdup(q.q_dname);
+ if (as->as.dns.dname == NULL)
+ goto err; /* errno set */
+
+ _asr_ctx_unref(ac);
+ return (as);
+ err:
+ if (as)
+ _asr_async_free(as);
+ _asr_ctx_unref(ac);
+ return (NULL);
+}
+DEF_WEAK(res_send_async);
+
+/*
+ * Unlike res_query(), this version will actually return the packet
+ * if it has received a valid one (errno == 0) even if h_errno is
+ * not NETDB_SUCCESS. So the packet *must* be freed if necessary.
+ */
+struct asr_query *
+res_query_async(const char *name, int class, int type, void *asr)
+{
+ struct asr_ctx *ac;
+ struct asr_query *as;
+
+ DPRINT("asr: res_query_async(\"%s\", %i, %i)\n", name, class, type);
+
+ ac = _asr_use_resolver(asr);
+ as = _res_query_async_ctx(name, class, type, ac);
+ _asr_ctx_unref(ac);
+
+ return (as);
+}
+DEF_WEAK(res_query_async);
+
+struct asr_query *
+_res_query_async_ctx(const char *name, int class, int type, struct asr_ctx *a_ctx)
+{
+ struct asr_query *as;
+
+ DPRINT("asr: res_query_async_ctx(\"%s\", %i, %i)\n", name, class, type);
+
+ if ((as = _asr_async_new(a_ctx, ASR_SEND)) == NULL)
+ return (NULL); /* errno set */
+ as->as_run = res_send_async_run;
+
+ /* This adds a "." to name if it doesn't already has one.
+ * That's how res_query() behaves (through res_mkquery").
+ */
+ if (setup_query(as, name, NULL, class, type) == -1)
+ goto err; /* errno set */
+
+ return (as);
+
+ err:
+ if (as)
+ _asr_async_free(as);
+
+ return (NULL);
+}
+
+static int
+res_send_async_run(struct asr_query *as, struct asr_result *ar)
+{
+ next:
+ switch (as->as_state) {
+
+ case ASR_STATE_INIT:
+
+ if (as->as_ctx->ac_nscount == 0) {
+ ar->ar_errno = ECONNREFUSED;
+ async_set_state(as, ASR_STATE_HALT);
+ break;
+ }
+
+ async_set_state(as, ASR_STATE_NEXT_NS);
+ break;
+
+ case ASR_STATE_NEXT_NS:
+
+ if (iter_ns(as) == -1) {
+ ar->ar_errno = ETIMEDOUT;
+ async_set_state(as, ASR_STATE_HALT);
+ break;
+ }
+
+ if (as->as_ctx->ac_options & RES_USEVC ||
+ as->as.dns.obuflen > PACKETSZ)
+ async_set_state(as, ASR_STATE_TCP_WRITE);
+ else
+ async_set_state(as, ASR_STATE_UDP_SEND);
+ break;
+
+ case ASR_STATE_UDP_SEND:
+
+ if (udp_send(as) == -1) {
+ async_set_state(as, ASR_STATE_NEXT_NS);
+ break;
+ }
+ async_set_state(as, ASR_STATE_UDP_RECV);
+ ar->ar_cond = ASR_WANT_READ;
+ ar->ar_fd = as->as_fd;
+ ar->ar_timeout = as->as_timeout;
+ return (ASYNC_COND);
+ break;
+
+ case ASR_STATE_UDP_RECV:
+
+ if (udp_recv(as) == -1) {
+ if (errno == ENOMEM) {
+ ar->ar_errno = errno;
+ async_set_state(as, ASR_STATE_HALT);
+ break;
+ }
+ if (errno != EOVERFLOW) {
+ /* Fail or timeout */
+ async_set_state(as, ASR_STATE_NEXT_NS);
+ break;
+ }
+ if (as->as_ctx->ac_options & RES_IGNTC)
+ async_set_state(as, ASR_STATE_PACKET);
+ else
+ async_set_state(as, ASR_STATE_TCP_WRITE);
+ } else
+ async_set_state(as, ASR_STATE_PACKET);
+ break;
+
+ case ASR_STATE_TCP_WRITE:
+
+ switch (tcp_write(as)) {
+ case -1: /* fail or timeout */
+ async_set_state(as, ASR_STATE_NEXT_NS);
+ break;
+ case 0:
+ async_set_state(as, ASR_STATE_TCP_READ);
+ ar->ar_cond = ASR_WANT_READ;
+ ar->ar_fd = as->as_fd;
+ ar->ar_timeout = as->as_timeout;
+ return (ASYNC_COND);
+ case 1:
+ ar->ar_cond = ASR_WANT_WRITE;
+ ar->ar_fd = as->as_fd;
+ ar->ar_timeout = as->as_timeout;
+ return (ASYNC_COND);
+ }
+ break;
+
+ case ASR_STATE_TCP_READ:
+
+ switch (tcp_read(as)) {
+ case -1: /* Fail or timeout */
+ if (errno == ENOMEM) {
+ ar->ar_errno = errno;
+ async_set_state(as, ASR_STATE_HALT);
+ } else
+ async_set_state(as, ASR_STATE_NEXT_NS);
+ break;
+ case 0:
+ async_set_state(as, ASR_STATE_PACKET);
+ break;
+ case 1:
+ ar->ar_cond = ASR_WANT_READ;
+ ar->ar_fd = as->as_fd;
+ ar->ar_timeout = as->as_timeout;
+ return (ASYNC_COND);
+ }
+ break;
+
+ case ASR_STATE_PACKET:
+
+ memmove(&ar->ar_ns, AS_NS_SA(as), SA_LEN(AS_NS_SA(as)));
+ ar->ar_datalen = as->as.dns.ibuflen;
+ ar->ar_data = as->as.dns.ibuf;
+ as->as.dns.ibuf = NULL;
+ ar->ar_errno = 0;
+ ar->ar_rcode = as->as.dns.rcode;
+ async_set_state(as, ASR_STATE_HALT);
+ break;
+
+ case ASR_STATE_HALT:
+
+ if (ar->ar_errno) {
+ ar->ar_h_errno = TRY_AGAIN;
+ ar->ar_count = 0;
+ ar->ar_datalen = -1;
+ ar->ar_data = NULL;
+ } else if (as->as.dns.ancount) {
+ ar->ar_h_errno = NETDB_SUCCESS;
+ ar->ar_count = as->as.dns.ancount;
+ } else {
+ ar->ar_count = 0;
+ switch (as->as.dns.rcode) {
+ case NXDOMAIN:
+ ar->ar_h_errno = HOST_NOT_FOUND;
+ break;
+ case SERVFAIL:
+ ar->ar_h_errno = TRY_AGAIN;
+ break;
+ case NOERROR:
+ ar->ar_h_errno = NO_DATA;
+ break;
+ default:
+ ar->ar_h_errno = NO_RECOVERY;
+ }
+ }
+ return (ASYNC_DONE);
+
+ default:
+
+ ar->ar_errno = EOPNOTSUPP;
+ ar->ar_h_errno = NETDB_INTERNAL;
+ async_set_state(as, ASR_STATE_HALT);
+ break;
+ }
+ goto next;
+}
+
+static int
+sockaddr_connect(const struct sockaddr *sa, int socktype)
+{
+ int errno_save, sock, flags;
+
+ if ((sock = socket(sa->sa_family, socktype, 0)) == -1)
+ goto fail;
+
+ if ((flags = fcntl(sock, F_GETFL, 0)) == -1)
+ goto fail;
+
+ flags |= O_NONBLOCK;
+
+ if ((flags = fcntl(sock, F_SETFL, flags)) == -1)
+ goto fail;
+
+ if (connect(sock, sa, SA_LEN(sa)) == -1) {
+ /*
+ * In the TCP case, the caller will be asked to poll for
+ * POLLOUT so that we start writing the packet in tcp_write()
+ * when the connection is established, or fail there on error.
+ */
+ if (errno == EINPROGRESS)
+ return (sock);
+ goto fail;
+ }
+
+ return (sock);
+
+ fail:
+
+ if (sock != -1) {
+ errno_save = errno;
+ close(sock);
+ errno = errno_save;
+ }
+
+ return (-1);
+}
+
+/*
+ * Prepare the DNS packet for the query type "type", class "class" and domain
+ * name created by the concatenation on "name" and "dom".
+ * Return 0 on success, set errno and return -1 on error.
+ */
+static int
+setup_query(struct asr_query *as, const char *name, const char *dom,
+ int class, int type)
+{
+ struct asr_pack p;
+ struct asr_dns_header h;
+ char fqdn[MAXDNAME];
+ char dname[MAXDNAME];
+
+ if (as->as_flags & ASYNC_EXTOBUF) {
+ errno = EINVAL;
+ DPRINT("attempting to write in user packet");
+ return (-1);
+ }
+
+ if (_asr_make_fqdn(name, dom, fqdn, sizeof(fqdn)) > sizeof(fqdn)) {
+ errno = EINVAL;
+ DPRINT("asr_make_fqdn: name too long\n");
+ return (-1);
+ }
+
+ if (_asr_dname_from_fqdn(fqdn, dname, sizeof(dname)) == -1) {
+ errno = EINVAL;
+ DPRINT("asr_dname_from_fqdn: invalid\n");
+ return (-1);
+ }
+
+ if (as->as.dns.obuf == NULL) {
+ as->as.dns.obufsize = PACKETSZ;
+ as->as.dns.obuf = malloc(as->as.dns.obufsize);
+ if (as->as.dns.obuf == NULL)
+ return (-1); /* errno set */
+ }
+ as->as.dns.obuflen = 0;
+
+ memset(&h, 0, sizeof h);
+ h.id = res_randomid();
+ if (as->as_ctx->ac_options & RES_RECURSE)
+ h.flags |= RD_MASK;
+#ifdef RES_USE_CD
+ if (as->as_ctx->ac_options & RES_USE_CD)
+ h.flags |= CD_MASK;
+#endif
+ h.qdcount = 1;
+ if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+ h.arcount = 1;
+
+ _asr_pack_init(&p, as->as.dns.obuf, as->as.dns.obufsize);
+ _asr_pack_header(&p, &h);
+ _asr_pack_query(&p, type, class, dname);
+ if (as->as_ctx->ac_options & (RES_USE_EDNS0 | RES_USE_DNSSEC))
+ _asr_pack_edns0(&p, MAXPACKETSZ,
+ as->as_ctx->ac_options & RES_USE_DNSSEC);
+ if (p.err) {
+ DPRINT("error packing query");
+ errno = EINVAL;
+ return (-1);
+ }
+
+ /* Remember the parameters. */
+ as->as.dns.reqid = h.id;
+ as->as.dns.type = type;
+ as->as.dns.class = class;
+ if (as->as.dns.dname)
+ free(as->as.dns.dname);
+ as->as.dns.dname = strdup(dname);
+ if (as->as.dns.dname == NULL) {
+ DPRINT("strdup");
+ return (-1); /* errno set */
+ }
+ as->as.dns.obuflen = p.offset;
+
+ DPRINT_PACKET("asr_setup_query", as->as.dns.obuf, as->as.dns.obuflen);
+
+ return (0);
+}
+
+/*
+ * Create a connect UDP socket and send the output packet.
+ *
+ * Return 0 on success, or -1 on error (errno set).
+ */
+static int
+udp_send(struct asr_query *as)
+{
+ ssize_t n;
+ int save_errno;
+#ifdef DEBUG
+ char buf[256];
+#endif
+
+ DPRINT("asr: [%p] connecting to %s UDP\n", as,
+ _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf));
+
+ as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_DGRAM);
+ if (as->as_fd == -1)
+ return (-1); /* errno set */
+
+ n = send(as->as_fd, as->as.dns.obuf, as->as.dns.obuflen, 0);
+ if (n == -1) {
+ save_errno = errno;
+ close(as->as_fd);
+ errno = save_errno;
+ as->as_fd = -1;
+ return (-1);
+ }
+
+ return (0);
+}
+
+/*
+ * Try to receive a valid packet from the current UDP socket.
+ *
+ * Return 0 if a full packet could be read, or -1 on error (errno set).
+ */
+static int
+udp_recv(struct asr_query *as)
+{
+ ssize_t n;
+ int save_errno;
+
+ if (ensure_ibuf(as, MAXPACKETSZ) == -1) {
+ save_errno = errno;
+ close(as->as_fd);
+ errno = save_errno;
+ as->as_fd = -1;
+ return (-1);
+ }
+
+ n = recv(as->as_fd, as->as.dns.ibuf, as->as.dns.ibufsize, 0);
+ save_errno = errno;
+ close(as->as_fd);
+ errno = save_errno;
+ as->as_fd = -1;
+ if (n == -1)
+ return (-1);
+
+ as->as.dns.ibuflen = n;
+
+ DPRINT_PACKET("asr_udp_recv()", as->as.dns.ibuf, as->as.dns.ibuflen);
+
+ if (validate_packet(as) == -1)
+ return (-1); /* errno set */
+
+ return (0);
+}
+
+/*
+ * Write the output packet to the TCP socket.
+ *
+ * Return 0 when all bytes have been sent, 1 there is no buffer space on the
+ * socket or it is not connected yet, or -1 on error (errno set).
+ */
+static int
+tcp_write(struct asr_query *as)
+{
+ struct msghdr msg;
+ struct iovec iov[2];
+ uint16_t len;
+ ssize_t n;
+ size_t offset;
+ int i;
+#ifdef DEBUG
+ char buf[256];
+#endif
+
+ /* First try to connect if not already */
+ if (as->as_fd == -1) {
+ DPRINT("asr: [%p] connecting to %s TCP\n", as,
+ _asr_print_sockaddr(AS_NS_SA(as), buf, sizeof buf));
+ as->as_fd = sockaddr_connect(AS_NS_SA(as), SOCK_STREAM);
+ if (as->as_fd == -1)
+ return (-1); /* errno set */
+/*
+ * Some systems (MacOS X) have SO_NOSIGPIPE instead of MSG_NOSIGNAL.
+ * If neither is available the system is probably broken. We might
+ * want to detect this at configure time.
+ */
+#ifdef SO_NOSIGPIPE
+ i = 1;
+ if (setsockopt(as->as_fd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&i,
+ sizeof(i)) == -1)
+ return (-1); /* errno set */
+#endif
+ as->as.dns.datalen = 0; /* bytes sent */
+ return (1);
+ }
+
+ i = 0;
+
+ /* Prepend de packet length if not sent already. */
+ if (as->as.dns.datalen < sizeof(len)) {
+ offset = 0;
+ len = htons(as->as.dns.obuflen);
+ iov[i].iov_base = (char *)(&len) + as->as.dns.datalen;
+ iov[i].iov_len = sizeof(len) - as->as.dns.datalen;
+ i++;
+ } else
+ offset = as->as.dns.datalen - sizeof(len);
+
+ iov[i].iov_base = as->as.dns.obuf + offset;
+ iov[i].iov_len = as->as.dns.obuflen - offset;
+ i++;
+
+ memset(&msg, 0, sizeof msg);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = i;
+
+ send_again:
+/* See above. */
+#ifndef MSG_NOSIGNAL
+#define MSG_NOSIGNAL 0
+#endif
+ n = sendmsg(as->as_fd, &msg, MSG_NOSIGNAL);
+ if (n == -1) {
+ if (errno == EINTR)
+ goto send_again;
+ goto close; /* errno set */
+ }
+
+ as->as.dns.datalen += n;
+
+ if (as->as.dns.datalen == as->as.dns.obuflen + sizeof(len)) {
+ /* All sent. Prepare for TCP read */
+ as->as.dns.datalen = 0;
+ return (0);
+ }
+
+ /* More data to write */
+ return (1);
+
+close:
+ close(as->as_fd);
+ as->as_fd = -1;
+ return (-1);
+}
+
+/*
+ * Try to read a valid packet from the current TCP socket.
+ *
+ * Return 0 if a full packet could be read, 1 if more data is needed and the
+ * socket must be read again, or -1 on error (errno set).
+ */
+static int
+tcp_read(struct asr_query *as)
+{
+ ssize_t n;
+ size_t offset, len;
+ char *pos;
+ int save_errno, nfds;
+ struct pollfd pfd;
+
+ /* We must read the packet len first */
+ if (as->as.dns.datalen < sizeof(as->as.dns.pktlen)) {
+
+ pos = (char *)(&as->as.dns.pktlen) + as->as.dns.datalen;
+ len = sizeof(as->as.dns.pktlen) - as->as.dns.datalen;
+
+ read_again0:
+ n = read(as->as_fd, pos, len);
+ if (n == -1) {
+ if (errno == EINTR)
+ goto read_again0;
+ goto close; /* errno set */
+ }
+ if (n == 0) {
+ errno = ECONNRESET;
+ goto close;
+ }
+ as->as.dns.datalen += n;
+ if (as->as.dns.datalen < sizeof(as->as.dns.pktlen))
+ return (1); /* need more data */
+
+ as->as.dns.ibuflen = ntohs(as->as.dns.pktlen);
+ if (ensure_ibuf(as, as->as.dns.ibuflen) == -1)
+ goto close; /* errno set */
+
+ pfd.fd = as->as_fd;
+ pfd.events = POLLIN;
+ poll_again:
+ nfds = poll(&pfd, 1, 0);
+ if (nfds == -1) {
+ if (errno == EINTR)
+ goto poll_again;
+ goto close; /* errno set */
+ }
+ if (nfds == 0)
+ return (1); /* no more data available */
+ }
+
+ offset = as->as.dns.datalen - sizeof(as->as.dns.pktlen);
+ pos = as->as.dns.ibuf + offset;
+ len = as->as.dns.ibuflen - offset;
+
+ read_again:
+ n = read(as->as_fd, pos, len);
+ if (n == -1) {
+ if (errno == EINTR)
+ goto read_again;
+ goto close; /* errno set */
+ }
+ if (n == 0) {
+ errno = ECONNRESET;
+ goto close;
+ }
+ as->as.dns.datalen += n;
+
+ /* See if we got all the advertised bytes. */
+ if (as->as.dns.datalen != as->as.dns.ibuflen + sizeof(as->as.dns.pktlen))
+ return (1);
+
+ DPRINT_PACKET("asr_tcp_read()", as->as.dns.ibuf, as->as.dns.ibuflen);
+
+ if (validate_packet(as) == -1)
+ goto close; /* errno set */
+
+ errno = 0;
+close:
+ save_errno = errno;
+ close(as->as_fd);
+ errno = save_errno;
+ as->as_fd = -1;
+ return (errno ? -1 : 0);
+}
+
+/*
+ * Make sure the input buffer is at least "n" bytes long, and allocate or
+ * extend it if necessary. Return 0 on success, or set errno and return -1.
+ */
+static int
+ensure_ibuf(struct asr_query *as, size_t n)
+{
+ char *t;
+
+ if (as->as.dns.ibufsize >= n)
+ return (0);
+
+ t = recallocarray(as->as.dns.ibuf, as->as.dns.ibufsize, n, 1);
+ if (t == NULL)
+ return (-1); /* errno set */
+ as->as.dns.ibuf = t;
+ as->as.dns.ibufsize = n;
+
+ return (0);
+}
+
+/*
+ * Check if the received packet is valid.
+ * Return 0 on success, or set errno and return -1.
+ */
+static int
+validate_packet(struct asr_query *as)
+{
+ struct asr_unpack p;
+ struct asr_dns_header h;
+ struct asr_dns_query q;
+ struct asr_dns_rr rr;
+ int r;
+
+ _asr_unpack_init(&p, as->as.dns.ibuf, as->as.dns.ibuflen);
+
+ _asr_unpack_header(&p, &h);
+ if (p.err)
+ goto inval;
+
+ if (h.id != as->as.dns.reqid) {
+ DPRINT("incorrect reqid\n");
+ goto inval;
+ }
+ if (h.qdcount != 1)
+ goto inval;
+ /* Should be zero, we could allow this */
+ if ((h.flags & Z_MASK) != 0)
+ goto inval;
+ /* Actually, it depends on the request but we only use OP_QUERY */
+ if (OPCODE(h.flags) != OP_QUERY)
+ goto inval;
+ /* Must be a response */
+ if ((h.flags & QR_MASK) == 0)
+ goto inval;
+
+ as->as.dns.rcode = RCODE(h.flags);
+ as->as.dns.ancount = h.ancount;
+
+ _asr_unpack_query(&p, &q);
+ if (p.err)
+ goto inval;
+
+ if (q.q_type != as->as.dns.type ||
+ q.q_class != as->as.dns.class ||
+ strcasecmp(q.q_dname, as->as.dns.dname)) {
+ DPRINT("incorrect type/class/dname '%s' != '%s'\n",
+ q.q_dname, as->as.dns.dname);
+ goto inval;
+ }
+
+ /* Check for truncation */
+ if (h.flags & TC_MASK && !(as->as_ctx->ac_options & RES_IGNTC)) {
+ DPRINT("truncated\n");
+ errno = EOVERFLOW;
+ return (-1);
+ }
+
+ /* Validate the rest of the packet */
+ for (r = h.ancount + h.nscount + h.arcount; r; r--)
+ _asr_unpack_rr(&p, &rr);
+
+ /* Report any error found when unpacking the RRs. */
+ if (p.err) {
+ DPRINT("unpack: %s\n", strerror(p.err));
+ errno = p.err;
+ return (-1);
+ }
+
+ if (p.offset != as->as.dns.ibuflen) {
+ DPRINT("trailing garbage\n");
+ errno = EMSGSIZE;
+ return (-1);
+ }
+
+ return (0);
+
+ inval:
+ errno = EINVAL;
+ return (-1);
+}
+
+/*
+ * Set the async context nameserver index to the next nameserver, cycling
+ * over the list until the maximum retry counter is reached. Return 0 on
+ * success, or -1 if all nameservers were used.
+ */
+static int
+iter_ns(struct asr_query *as)
+{
+ for (;;) {
+ if (as->as.dns.nsloop >= as->as_ctx->ac_nsretries)
+ return (-1);
+
+ as->as.dns.nsidx += 1;
+ if (as->as.dns.nsidx <= as->as_ctx->ac_nscount)
+ break;
+ as->as.dns.nsidx = 0;
+ as->as.dns.nsloop++;
+ DPRINT("asr: iter_ns(): cycle %i\n", as->as.dns.nsloop);
+ }
+
+ as->as_timeout = 1000 * (as->as_ctx->ac_nstimeout << as->as.dns.nsloop);
+ if (as->as.dns.nsloop > 0)
+ as->as_timeout /= as->as_ctx->ac_nscount;
+ if (as->as_timeout < 1000)
+ as->as_timeout = 1000;
+
+ return (0);
+}