summaryrefslogtreecommitdiff
path: root/ares_process.c
diff options
context:
space:
mode:
Diffstat (limited to 'ares_process.c')
-rw-r--r--ares_process.c625
1 files changed, 625 insertions, 0 deletions
diff --git a/ares_process.c b/ares_process.c
new file mode 100644
index 0000000..ee9623a
--- /dev/null
+++ b/ares_process.c
@@ -0,0 +1,625 @@
+/* Copyright 1998 by the Massachusetts Institute of Technology.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is"
+ * without express or implied warranty.
+ */
+
+static const char rcsid[] = "$Id$";
+
+#include <sys/types.h>
+
+#ifdef WIN32
+#include "nameser.h"
+#else
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/nameser.h>
+#include <unistd.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <time.h>
+#include <errno.h>
+#include "ares.h"
+#include "ares_dns.h"
+#include "ares_private.h"
+
+static void write_tcp_data(ares_channel channel, fd_set *write_fds,
+ time_t now);
+static void read_tcp_data(ares_channel channel, fd_set *read_fds, time_t now);
+static void read_udp_packets(ares_channel channel, fd_set *read_fds,
+ time_t now);
+static void process_timeouts(ares_channel channel, time_t now);
+static void process_answer(ares_channel channel, unsigned char *abuf,
+ int alen, int whichserver, int tcp, int now);
+static void handle_error(ares_channel channel, int whichserver, time_t now);
+static void next_server(ares_channel channel, struct query *query, time_t now);
+static int open_tcp_socket(ares_channel channel, struct server_state *server);
+static int open_udp_socket(ares_channel channel, struct server_state *server);
+static int same_questions(const unsigned char *qbuf, int qlen,
+ const unsigned char *abuf, int alen);
+static void end_query(ares_channel channel, struct query *query, int status,
+ unsigned char *abuf, int alen);
+
+/* Something interesting happened on the wire, or there was a timeout.
+ * See what's up and respond accordingly.
+ */
+void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds)
+{
+ time_t now;
+
+ time(&now);
+ write_tcp_data(channel, write_fds, now);
+ read_tcp_data(channel, read_fds, now);
+ read_udp_packets(channel, read_fds, now);
+ process_timeouts(channel, now);
+}
+
+/* If any TCP sockets select true for writing, write out queued data
+ * we have for them.
+ */
+static void write_tcp_data(ares_channel channel, fd_set *write_fds, time_t now)
+{
+ struct server_state *server;
+ struct send_request *sendreq;
+ struct iovec *vec;
+ int i, n, count;
+
+ for (i = 0; i < channel->nservers; i++)
+ {
+ /* Make sure server has data to send and is selected in write_fds. */
+ server = &channel->servers[i];
+ if (!server->qhead || server->tcp_socket == -1
+ || !FD_ISSET(server->tcp_socket, write_fds))
+ continue;
+
+ /* Count the number of send queue items. */
+ n = 0;
+ for (sendreq = server->qhead; sendreq; sendreq = sendreq->next)
+ n++;
+
+#ifdef WIN32
+ vec = NULL;
+#else
+ /* Allocate iovecs so we can send all our data at once. */
+ vec = malloc(n * sizeof(struct iovec));
+#endif
+ if (vec)
+ {
+#ifdef WIN32
+#else
+ /* Fill in the iovecs and send. */
+ n = 0;
+ for (sendreq = server->qhead; sendreq; sendreq = sendreq->next)
+ {
+ vec[n].iov_base = (char *) sendreq->data;
+ vec[n].iov_len = sendreq->len;
+ n++;
+ }
+ count = writev(server->tcp_socket, vec, n);
+ free(vec);
+ if (count < 0)
+ {
+ handle_error(channel, i, now);
+ continue;
+ }
+
+ /* Advance the send queue by as many bytes as we sent. */
+ while (count)
+ {
+ sendreq = server->qhead;
+ if (count >= sendreq->len)
+ {
+ count -= sendreq->len;
+ server->qhead = sendreq->next;
+ if (server->qhead == NULL)
+ server->qtail = NULL;
+ free(sendreq);
+ }
+ else
+ {
+ sendreq->data += count;
+ sendreq->len -= count;
+ break;
+ }
+ }
+#endif
+ }
+ else
+ {
+ /* Can't allocate iovecs; just send the first request. */
+ sendreq = server->qhead;
+
+ count = send(server->tcp_socket, sendreq->data, sendreq->len, 0);
+
+ if (count < 0)
+ {
+ handle_error(channel, i, now);
+ continue;
+ }
+
+ /* Advance the send queue by as many bytes as we sent. */
+ if (count == sendreq->len)
+ {
+ server->qhead = sendreq->next;
+ if (server->qhead == NULL)
+ server->qtail = NULL;
+ free(sendreq);
+ }
+ else
+ {
+ sendreq->data += count;
+ sendreq->len -= count;
+ }
+ }
+ }
+}
+
+/* If any TCP socket selects true for reading, read some data,
+ * allocate a buffer if we finish reading the length word, and process
+ * a packet if we finish reading one.
+ */
+static void read_tcp_data(ares_channel channel, fd_set *read_fds, time_t now)
+{
+ struct server_state *server;
+ int i, count;
+
+ for (i = 0; i < channel->nservers; i++)
+ {
+ /* Make sure the server has a socket and is selected in read_fds. */
+ server = &channel->servers[i];
+ if (server->tcp_socket == -1 || !FD_ISSET(server->tcp_socket, read_fds))
+ continue;
+
+ if (server->tcp_lenbuf_pos != 2)
+ {
+ /* We haven't yet read a length word, so read that (or
+ * what's left to read of it).
+ */
+ count = recv(server->tcp_socket,
+ server->tcp_lenbuf + server->tcp_buffer_pos,
+ 2 - server->tcp_buffer_pos, 0);
+ if (count <= 0)
+ {
+ handle_error(channel, i, now);
+ continue;
+ }
+
+ server->tcp_lenbuf_pos += count;
+ if (server->tcp_lenbuf_pos == 2)
+ {
+ /* We finished reading the length word. Decode the
+ * length and allocate a buffer for the data.
+ */
+ server->tcp_length = server->tcp_lenbuf[0] << 8
+ | server->tcp_lenbuf[1];
+ server->tcp_buffer = malloc(server->tcp_length);
+ if (!server->tcp_buffer)
+ handle_error(channel, i, now);
+ server->tcp_buffer_pos = 0;
+ }
+ }
+ else
+ {
+ /* Read data into the allocated buffer. */
+ count = recv(server->tcp_socket,
+ server->tcp_buffer + server->tcp_buffer_pos,
+ server->tcp_length - server->tcp_buffer_pos, 0);
+ if (count <= 0)
+ {
+ handle_error(channel, i, now);
+ continue;
+ }
+
+ server->tcp_buffer_pos += count;
+ if (server->tcp_buffer_pos == server->tcp_length)
+ {
+ /* We finished reading this answer; process it and
+ * prepare to read another length word.
+ */
+ process_answer(channel, server->tcp_buffer, server->tcp_length,
+ i, 1, now);
+ free(server->tcp_buffer);
+ server->tcp_buffer = NULL;
+ server->tcp_lenbuf_pos = 0;
+ }
+ }
+ }
+}
+
+/* If any UDP sockets select true for reading, process them. */
+static void read_udp_packets(ares_channel channel, fd_set *read_fds,
+ time_t now)
+{
+ struct server_state *server;
+ int i, count;
+ unsigned char buf[PACKETSZ + 1];
+
+ for (i = 0; i < channel->nservers; i++)
+ {
+ /* Make sure the server has a socket and is selected in read_fds. */
+ server = &channel->servers[i];
+
+ if (server->udp_socket == -1 || !FD_ISSET(server->udp_socket, read_fds))
+ continue;
+
+ count = recv(server->udp_socket, buf, sizeof(buf), 0);
+ if (count <= 0)
+ handle_error(channel, i, now);
+
+ process_answer(channel, buf, count, i, 0, now);
+ }
+}
+
+/* If any queries have timed out, note the timeout and move them on. */
+static void process_timeouts(ares_channel channel, time_t now)
+{
+ struct query *query, *next;
+
+ for (query = channel->queries; query; query = next)
+ {
+ next = query->next;
+ if (query->timeout != 0 && now >= query->timeout)
+ {
+ query->error_status = ARES_ETIMEOUT;
+ next_server(channel, query, now);
+ }
+ }
+}
+
+/* Handle an answer from a server. */
+static void process_answer(ares_channel channel, unsigned char *abuf,
+ int alen, int whichserver, int tcp, int now)
+{
+ int id, tc, rcode;
+ struct query *query;
+
+ /* If there's no room in the answer for a header, we can't do much
+ * with it. */
+ if (alen < HFIXEDSZ)
+ return;
+
+ /* Grab the query ID, truncate bit, and response code from the packet. */
+ id = DNS_HEADER_QID(abuf);
+ tc = DNS_HEADER_TC(abuf);
+ rcode = DNS_HEADER_RCODE(abuf);
+
+ /* Find the query corresponding to this packet. */
+ for (query = channel->queries; query; query = query->next)
+ {
+ if (query->qid == id)
+ break;
+ }
+ if (!query)
+ return;
+
+ /* If we got a truncated UDP packet and are not ignoring truncation,
+ * don't accept the packet, and switch the query to TCP if we hadn't
+ * done so already.
+ */
+ if ((tc || alen > PACKETSZ) && !tcp && !(channel->flags & ARES_FLAG_IGNTC))
+ {
+ if (!query->using_tcp)
+ {
+ query->using_tcp = 1;
+ ares__send_query(channel, query, now);
+ }
+ return;
+ }
+
+ /* Limit alen to PACKETSZ if we aren't using TCP (only relevant if we
+ * are ignoring truncation.
+ */
+ if (alen > PACKETSZ && !tcp)
+ alen = PACKETSZ;
+
+ /* If we aren't passing through all error packets, discard packets
+ * with SERVFAIL, NOTIMP, or REFUSED response codes.
+ */
+ if (!(channel->flags & ARES_FLAG_NOCHECKRESP))
+ {
+ if (rcode == SERVFAIL || rcode == NOTIMP || rcode == REFUSED)
+ {
+ query->skip_server[whichserver] = 1;
+ if (query->server == whichserver)
+ next_server(channel, query, now);
+ return;
+ }
+ if (!same_questions(query->qbuf, query->qlen, abuf, alen))
+ {
+ if (query->server == whichserver)
+ next_server(channel, query, now);
+ return;
+ }
+ }
+
+ end_query(channel, query, ARES_SUCCESS, abuf, alen);
+}
+
+static void handle_error(ares_channel channel, int whichserver, time_t now)
+{
+ struct query *query;
+
+ /* Reset communications with this server. */
+ ares__close_sockets(&channel->servers[whichserver]);
+
+ /* Tell all queries talking to this server to move on and not try
+ * this server again.
+ */
+ for (query = channel->queries; query; query = query->next)
+ {
+ if (query->server == whichserver)
+ {
+ query->skip_server[whichserver] = 1;
+ next_server(channel, query, now);
+ }
+ }
+}
+
+static void next_server(ares_channel channel, struct query *query, time_t now)
+{
+ /* Advance to the next server or try. */
+ query->server++;
+ for (; query->try < channel->tries; query->try++)
+ {
+ for (; query->server < channel->nservers; query->server++)
+ {
+ if (!query->skip_server[query->server])
+ {
+ ares__send_query(channel, query, now);
+ return;
+ }
+ }
+ query->server = 0;
+
+ /* Only one try if we're using TCP. */
+ if (query->using_tcp)
+ break;
+ }
+ end_query(channel, query, query->error_status, NULL, 0);
+}
+
+void ares__send_query(ares_channel channel, struct query *query, time_t now)
+{
+ struct send_request *sendreq;
+ struct server_state *server;
+
+ server = &channel->servers[query->server];
+ if (query->using_tcp)
+ {
+ /* Make sure the TCP socket for this server is set up and queue
+ * a send request.
+ */
+ if (server->tcp_socket == -1)
+ {
+ if (open_tcp_socket(channel, server) == -1)
+ {
+ query->skip_server[query->server] = 1;
+ next_server(channel, query, now);
+ return;
+ }
+ }
+ sendreq = malloc(sizeof(struct send_request));
+ if (!sendreq)
+ end_query(channel, query, ARES_ENOMEM, NULL, 0);
+ sendreq->data = query->tcpbuf;
+ sendreq->len = query->tcplen;
+ sendreq->next = NULL;
+ if (server->qtail)
+ server->qtail->next = sendreq;
+ else
+ server->qhead = sendreq;
+ server->qtail = sendreq;
+ query->timeout = 0;
+ }
+ else
+ {
+ if (server->udp_socket == -1)
+ {
+ if (open_udp_socket(channel, server) == -1)
+ {
+ query->skip_server[query->server] = 1;
+ next_server(channel, query, now);
+ return;
+ }
+ }
+ if (send(server->udp_socket, query->qbuf, query->qlen, 0) == -1)
+ {
+ query->skip_server[query->server] = 1;
+ next_server(channel, query, now);
+ return;
+ }
+ query->timeout = now
+ + ((query->try == 0) ? channel->timeout
+ : channel->timeout << query->try / channel->nservers);
+ }
+}
+
+static int open_tcp_socket(ares_channel channel, struct server_state *server)
+{
+ int s, flags;
+ struct sockaddr_in sin;
+
+ /* Acquire a socket. */
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s == -1)
+ return -1;
+
+ /* Set the socket non-blocking. */
+
+#ifdef WIN32
+ flags = 1;
+ ioctlsocket(s, FIONBIO, &flags);
+#else
+ if (fcntl(s, F_GETFL, &flags) == -1)
+ {
+ close(s);
+ return -1;
+ }
+ flags &= O_NONBLOCK;
+ if (fcntl(s, F_SETFL, flags) == -1)
+ {
+ close(s);
+ return -1;
+ }
+#endif
+
+ /* Connect to the server. */
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr = server->addr;
+ sin.sin_port = channel->tcp_port;
+ if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) == -1
+ && errno != EINPROGRESS)
+ {
+ closesocket(s);
+ return -1;
+ }
+
+ server->tcp_socket = s;
+ return 0;
+}
+
+static int open_udp_socket(ares_channel channel, struct server_state *server)
+{
+ int s;
+ struct sockaddr_in sin;
+
+ /* Acquire a socket. */
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ if (s == -1)
+ return -1;
+
+ /* Connect to the server. */
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr = server->addr;
+ sin.sin_port = channel->udp_port;
+ if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) == -1)
+ {
+ closesocket(s);
+ return -1;
+ }
+
+ server->udp_socket = s;
+ return 0;
+}
+
+static int same_questions(const unsigned char *qbuf, int qlen,
+ const unsigned char *abuf, int alen)
+{
+ struct {
+ const unsigned char *p;
+ int qdcount;
+ char *name;
+ int namelen;
+ int type;
+ int dnsclass;
+ } q, a;
+ int i, j;
+
+ if (qlen < HFIXEDSZ || alen < HFIXEDSZ)
+ return 0;
+
+ /* Extract qdcount from the request and reply buffers and compare them. */
+ q.qdcount = DNS_HEADER_QDCOUNT(qbuf);
+ a.qdcount = DNS_HEADER_QDCOUNT(abuf);
+ if (q.qdcount != a.qdcount)
+ return 0;
+
+ /* For each question in qbuf, find it in abuf. */
+ q.p = qbuf + HFIXEDSZ;
+ for (i = 0; i < q.qdcount; i++)
+ {
+ /* Decode the question in the query. */
+ if (ares_expand_name(q.p, qbuf, qlen, &q.name, &q.namelen)
+ != ARES_SUCCESS)
+ return 0;
+ q.p += q.namelen;
+ if (q.p + QFIXEDSZ > qbuf + qlen)
+ {
+ free(q.name);
+ return 0;
+ }
+ q.type = DNS_QUESTION_TYPE(q.p);
+ q.dnsclass = DNS_QUESTION_CLASS(q.p);
+ q.p += QFIXEDSZ;
+
+ /* Search for this question in the answer. */
+ a.p = abuf + HFIXEDSZ;
+ for (j = 0; j < a.qdcount; j++)
+ {
+ /* Decode the question in the answer. */
+ if (ares_expand_name(a.p, abuf, alen, &a.name, &a.namelen)
+ != ARES_SUCCESS)
+ {
+ free(q.name);
+ return 0;
+ }
+ a.p += a.namelen;
+ if (a.p + QFIXEDSZ > abuf + alen)
+ {
+ free(q.name);
+ free(a.name);
+ return 0;
+ }
+ a.type = DNS_QUESTION_TYPE(a.p);
+ a.dnsclass = DNS_QUESTION_CLASS(a.p);
+ a.p += QFIXEDSZ;
+
+ /* Compare the decoded questions. */
+ if (strcasecmp(q.name, a.name) == 0 && q.type == a.type
+ && q.dnsclass == a.dnsclass)
+ {
+ free(a.name);
+ break;
+ }
+ free(a.name);
+ }
+
+ free(q.name);
+ if (j == a.qdcount)
+ return 0;
+ }
+ return 1;
+}
+
+static void end_query(ares_channel channel, struct query *query, int status,
+ unsigned char *abuf, int alen)
+{
+ struct query **q;
+ int i;
+
+ query->callback(query->arg, status, abuf, alen);
+ for (q = &channel->queries; *q; q = &(*q)->next)
+ {
+ if (*q == query)
+ break;
+ }
+ *q = query->next;
+ free(query->tcpbuf);
+ free(query->skip_server);
+ free(query);
+
+ /* Simple cleanup policy: if no queries are remaining, close all
+ * network sockets unless STAYOPEN is set.
+ */
+ if (!channel->queries && !(channel->flags & ARES_FLAG_STAYOPEN))
+ {
+ for (i = 0; i < channel->nservers; i++)
+ ares__close_sockets(&channel->servers[i]);
+ }
+}