diff options
Diffstat (limited to 'ares/ares_process.c')
-rw-r--r-- | ares/ares_process.c | 113 |
1 files changed, 105 insertions, 8 deletions
diff --git a/ares/ares_process.c b/ares/ares_process.c index 1b5802994..15aa06e1c 100644 --- a/ares/ares_process.c +++ b/ares/ares_process.c @@ -62,6 +62,7 @@ static void read_tcp_data(ares_channel channel, fd_set *read_fds, static void read_udp_packets(ares_channel channel, fd_set *read_fds, ares_socket_t read_fd, time_t now); static void process_timeouts(ares_channel channel, time_t now); +static void process_broken_connections(ares_channel channel, time_t now); static void process_answer(ares_channel channel, unsigned char *abuf, int alen, int whichserver, int tcp, time_t now); static void handle_error(ares_channel channel, int whichserver, time_t now); @@ -87,6 +88,7 @@ void ares_process(ares_channel channel, fd_set *read_fds, fd_set *write_fds) read_tcp_data(channel, read_fds, ARES_SOCKET_BAD, now); read_udp_packets(channel, read_fds, ARES_SOCKET_BAD, now); process_timeouts(channel, now); + process_broken_connections(channel, now); } /* Something interesting happened on the wire, or there was a timeout. @@ -157,7 +159,7 @@ static void write_tcp_data(ares_channel channel, /* Make sure server has data to send and is selected in write_fds or write_fd. */ server = &channel->servers[i]; - if (!server->qhead || server->tcp_socket == ARES_SOCKET_BAD) + if (!server->qhead || server->tcp_socket == ARES_SOCKET_BAD || server->is_broken) continue; if(write_fds) { @@ -216,6 +218,8 @@ static void write_tcp_data(ares_channel channel, SOCK_STATE_CALLBACK(channel, server->tcp_socket, 1, 0); server->qtail = NULL; } + if (sendreq->data_storage != NULL) + free(sendreq->data_storage); free(sendreq); } else @@ -248,6 +252,8 @@ static void write_tcp_data(ares_channel channel, SOCK_STATE_CALLBACK(channel, server->tcp_socket, 1, 0); server->qtail = NULL; } + if (sendreq->data_storage != NULL) + free(sendreq->data_storage); free(sendreq); } else @@ -278,7 +284,7 @@ static void read_tcp_data(ares_channel channel, fd_set *read_fds, { /* Make sure the server has a socket and is selected in read_fds. */ server = &channel->servers[i]; - if (server->tcp_socket == ARES_SOCKET_BAD) + if (server->tcp_socket == ARES_SOCKET_BAD || server->is_broken) continue; if(read_fds) { @@ -376,7 +382,7 @@ static void read_udp_packets(ares_channel channel, fd_set *read_fds, /* Make sure the server has a socket and is selected in read_fds. */ server = &channel->servers[i]; - if (server->udp_socket == ARES_SOCKET_BAD) + if (server->udp_socket == ARES_SOCKET_BAD || server->is_broken) continue; if(read_fds) { @@ -492,6 +498,20 @@ static void process_answer(ares_channel channel, unsigned char *abuf, end_query(channel, query, ARES_SUCCESS, abuf, alen); } +/* Close all the connections that are no longer usable. */ +static void process_broken_connections(ares_channel channel, time_t now) +{ + int i; + for (i = 0; i < channel->nservers; i++) + { + struct server_state *server = &channel->servers[i]; + if (server->is_broken) + { + handle_error(channel, i, now); + } + } +} + static void handle_error(ares_channel channel, int whichserver, time_t now) { struct query *query, *next; @@ -526,7 +546,7 @@ static void skip_server(ares_channel channel, struct query *query, */ if (channel->nservers > 1) { - query->skip_server[whichserver] = 1; + query->server_info[whichserver].skip_server = 1; } } @@ -538,10 +558,21 @@ static struct query *next_server(ares_channel channel, struct query *query, time { for (; query->server < channel->nservers; query->server++) { - if (!query->skip_server[query->server]) + struct server_state *server = &channel->servers[query->server]; + /* We don't want to use this server if (1) we decided this + * connection is broken, and thus about to be closed, (2) + * we've decided to skip this server because of earlier + * errors we encountered, or (3) we already sent this query + * over this exact connection. + */ + if (!server->is_broken && + !query->server_info[query->server].skip_server && + !(query->using_tcp && + (query->server_info[query->server].tcp_connection_generation == + server->tcp_connection_generation))) { - ares__send_query(channel, query, now); - return (query->next); + ares__send_query(channel, query, now); + return (query->next); } } query->server = 0; @@ -582,8 +613,16 @@ void ares__send_query(ares_channel channel, struct query *query, time_t now) end_query(channel, query, ARES_ENOMEM, NULL, 0); return; } + /* To make the common case fast, we avoid copies by using the + * query's tcpbuf for as long as the query is alive. In the rare + * case where the query ends while it's queued for transmission, + * then we give the sendreq its own copy of the request packet + * and put it in sendreq->data_storage. + */ + sendreq->data_storage = NULL; sendreq->data = query->tcpbuf; sendreq->len = query->tcplen; + sendreq->owner_query = query; sendreq->next = NULL; if (server->qtail) server->qtail->next = sendreq; @@ -594,6 +633,8 @@ void ares__send_query(ares_channel channel, struct query *query, time_t now) } server->qtail = sendreq; query->timeout = 0; + query->server_info[query->server].tcp_connection_generation = + server->tcp_connection_generation; } else { @@ -721,6 +762,7 @@ static int open_tcp_socket(ares_channel channel, struct server_state *server) SOCK_STATE_CALLBACK(channel, s, 1, 0); server->tcp_buffer_pos = 0; server->tcp_socket = s; + server->tcp_connection_generation = ++channel->tcp_connection_generation; return 0; } @@ -839,6 +881,61 @@ static struct query *end_query (ares_channel channel, struct query *query, int s struct query **q, *next; int i; + /* First we check to see if this query ended while one of our send + * queues still has pointers to it. + */ + for (i = 0; i < channel->nservers; i++) + { + struct server_state *server = &channel->servers[i]; + struct send_request *sendreq; + for (sendreq = server->qhead; sendreq; sendreq = sendreq->next) + if (sendreq->owner_query == query) + { + sendreq->owner_query = NULL; + assert(sendreq->data_storage == NULL); + if (status == ARES_SUCCESS) + { + /* We got a reply for this query, but this queued + * sendreq points into this soon-to-be-gone query's + * tcpbuf. Probably this means we timed out and queued + * the query for retransmission, then received a + * response before actually retransmitting. This is + * perfectly fine, so we want to keep the connection + * running smoothly if we can. But in the worst case + * we may have sent only some prefix of the query, + * with some suffix of the query left to send. Also, + * the buffer may be queued on multiple queues. To + * prevent dangling pointers to the query's tcpbuf and + * handle these cases, we just give such sendreqs + * their own copy of the query packet. + */ + sendreq->data_storage = malloc(sendreq->len); + if (sendreq->data_storage != NULL) + { + memcpy(sendreq->data_storage, sendreq->data, sendreq->len); + sendreq->data = sendreq->data_storage; + } + } + if ((status != ARES_SUCCESS) || (sendreq->data_storage == NULL)) + { + /* We encountered an error (probably a timeout, + * suggesting the DNS server we're talking to is + * probably unreachable, wedged, or severely + * overloaded) or we couldn't copy the request, so + * mark the connection as broken. When we get to + * process_broken_connections() we'll close the + * connection and try to re-send requests to another + * server. + */ + server->is_broken = 1; + /* Just to be paranoid, zero out this sendreq... */ + sendreq->data = NULL; + sendreq->len = 0; + } + } + } + + /* Invoke the callback */ query->callback(query->arg, status, abuf, alen); for (q = &channel->queries; *q; q = &(*q)->next) { @@ -851,7 +948,7 @@ static struct query *end_query (ares_channel channel, struct query *query, int s else next = NULL; free(query->tcpbuf); - free(query->skip_server); + free(query->server_info); free(query); /* Simple cleanup policy: if no queries are remaining, close all |