Configuration option to limit number of UDP queries per ephemeral port (#549)

Add a new ARES_OPT_UDP_MAX_QUERIES option with udp_max_queries parameter that can be passed to ares_init_options(). This value defaults to 0 (unlimited) to maintain existing compatibility, any positive number will cause new UDP ephemeral ports to be created once the threshold is reached, we'll call these 'connections' even though its technically wrong for UDP.

Implementation Details:
* Each server entry in a channel now has a linked-list of connections/ports for udp and tcp. The first connection in the list is the one most likely to be eligible to accept new queries.
* Queries are now tracked by connection rather than by server.
* Every time a query is detached from a connection, the connection that it was attached to will be checked to see if it needs to be cleaned up.
* Insertion, lookup, and searching for connections has been implemented as O(1) complexity so the number of connections will not impact performance.
* Remove is_broken from the server, it appears it would be set and immediately unset, so must have been invalidated via a prior patch. A future patch should probably track consecutive server errors and de-prioritize such servers. The code right now will always try servers in the order of configuration, so a bad server in the list will always be tried and may rely on timeout logic to try the next.
* Various other cleanups to remove code duplication and for clarification.

Fixes Bug: #444
Fix By: Brad House (@bradh352)
pull/553/head
Brad House 2 years ago committed by GitHub
parent 7f3262312f
commit dd93f30082
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      ci/test.sh
  2. 9
      docs/ares_init_options.3
  3. 40
      include/ares.h
  4. 2
      src/lib/Makefile.inc
  5. 107
      src/lib/ares__close_sockets.c
  6. 53
      src/lib/ares__htable.c
  7. 7
      src/lib/ares__htable.h
  8. 186
      src/lib/ares__htable_asvp.c
  9. 111
      src/lib/ares__htable_asvp.h
  10. 7
      src/lib/ares__htable_stvp.c
  11. 5
      src/lib/ares__slist.c
  12. 20
      src/lib/ares_cancel.c
  13. 14
      src/lib/ares_destroy.c
  14. 42
      src/lib/ares_fds.c
  15. 53
      src/lib/ares_getsock.c
  16. 57
      src/lib/ares_init.c
  17. 45
      src/lib/ares_private.h
  18. 1178
      src/lib/ares_process.c
  19. 2
      src/lib/ares_send.c
  20. 88
      test/ares-test-mock.cc

@ -3,9 +3,11 @@
# SPDX-License-Identifier: MIT
set -e
# Travis on MacOS uses CloudFlare's DNS (1.1.1.1/1.0.0.1) which rejects ANY requests
# Travis on MacOS uses CloudFlare's DNS (1.1.1.1/1.0.0.1) which rejects ANY requests.
# Also, LiveSearchTXT is known to fail on Cirrus-CI on some MacOS hosts, we don't get
# a truncated UDP response so we never follow up with TCP.
# Note res_ninit() and /etc/resolv.conf actually have different configs, bad Travis
[ -z "$TEST_FILTER" ] && export TEST_FILTER="--gtest_filter=-*LiveSearchANY*"
[ -z "$TEST_FILTER" ] && export TEST_FILTER="--gtest_filter=-*LiveSearchTXT*:*LiveSearchANY*"
# No tests for ios as it is a cross-compile
if [ "$BUILD_TYPE" = "ios" -o "$BUILD_TYPE" = "ios-cmake" -o "$DIST" = "iOS" ] ; then

@ -44,6 +44,7 @@ struct ares_options {
int ednspsz;
char *resolvconf_path;
char *hosts_path;
int udp_max_queries;
};
int ares_init_options(ares_channel *\fIchannelptr\fP,
@ -206,6 +207,14 @@ should be set to a path string, and will be honoured on *nix like systems. The
default is
.B /etc/hosts
.br
.TP 18
.B ARES_OPT_UDP_MAX_QUERIES
.B int \fIudp_max_queries\fP;
.br
The maximum number of udp queries that can be sent on a single ephemeral port
to a given DNS server before a new ephemeral port is assigned. Any value of 0
or less will be considered unlimited, and is the default.
.br
.PP
The \fIoptmask\fP parameter also includes options without a corresponding
field in the

@ -158,25 +158,26 @@ extern "C" {
#define ARES_FLAG_EDNS (1 << 8)
/* Option mask values */
#define ARES_OPT_FLAGS (1 << 0)
#define ARES_OPT_TIMEOUT (1 << 1)
#define ARES_OPT_TRIES (1 << 2)
#define ARES_OPT_NDOTS (1 << 3)
#define ARES_OPT_UDP_PORT (1 << 4)
#define ARES_OPT_TCP_PORT (1 << 5)
#define ARES_OPT_SERVERS (1 << 6)
#define ARES_OPT_DOMAINS (1 << 7)
#define ARES_OPT_LOOKUPS (1 << 8)
#define ARES_OPT_SOCK_STATE_CB (1 << 9)
#define ARES_OPT_SORTLIST (1 << 10)
#define ARES_OPT_SOCK_SNDBUF (1 << 11)
#define ARES_OPT_SOCK_RCVBUF (1 << 12)
#define ARES_OPT_TIMEOUTMS (1 << 13)
#define ARES_OPT_ROTATE (1 << 14)
#define ARES_OPT_EDNSPSZ (1 << 15)
#define ARES_OPT_NOROTATE (1 << 16)
#define ARES_OPT_RESOLVCONF (1 << 17)
#define ARES_OPT_HOSTS_FILE (1 << 18)
#define ARES_OPT_FLAGS (1 << 0)
#define ARES_OPT_TIMEOUT (1 << 1)
#define ARES_OPT_TRIES (1 << 2)
#define ARES_OPT_NDOTS (1 << 3)
#define ARES_OPT_UDP_PORT (1 << 4)
#define ARES_OPT_TCP_PORT (1 << 5)
#define ARES_OPT_SERVERS (1 << 6)
#define ARES_OPT_DOMAINS (1 << 7)
#define ARES_OPT_LOOKUPS (1 << 8)
#define ARES_OPT_SOCK_STATE_CB (1 << 9)
#define ARES_OPT_SORTLIST (1 << 10)
#define ARES_OPT_SOCK_SNDBUF (1 << 11)
#define ARES_OPT_SOCK_RCVBUF (1 << 12)
#define ARES_OPT_TIMEOUTMS (1 << 13)
#define ARES_OPT_ROTATE (1 << 14)
#define ARES_OPT_EDNSPSZ (1 << 15)
#define ARES_OPT_NOROTATE (1 << 16)
#define ARES_OPT_RESOLVCONF (1 << 17)
#define ARES_OPT_HOSTS_FILE (1 << 18)
#define ARES_OPT_UDP_MAX_QUERIES (1 << 19)
/* Nameinfo flag values */
#define ARES_NI_NOFQDN (1 << 0)
@ -287,6 +288,7 @@ struct ares_options {
int ednspsz;
char *resolvconf_path;
char *hosts_path;
int udp_max_queries;
};
struct hostent;

@ -6,6 +6,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__close_sockets.c \
ares__get_hostent.c \
ares__htable.c \
ares__htable_asvp.c \
ares__htable_stvp.c \
ares__llist.c \
ares__parse_into_addrinfo.c \
@ -66,6 +67,7 @@ CSOURCES = ares__addrinfo2hostent.c \
windows_port.c
HHEADERS = ares__htable.h \
ares__htable_asvp.h \
ares__htable_stvp.h \
ares__llist.h \
ares__slist.h \

@ -20,14 +20,19 @@
#include "ares.h"
#include "ares_private.h"
#include <assert.h>
void ares__close_sockets(ares_channel channel, struct server_state *server)
void ares__close_connection(struct server_connection *conn)
{
struct send_request *sendreq;
struct server_state *server = conn->server;
ares_channel channel = server->channel;
if (conn->is_tcp) {
struct send_request *sendreq;
/* Free all pending output buffers. */
while (server->qhead)
{
/* Free all pending output buffers. */
while (server->qhead) {
/* Advance server->qhead; pull out query as we go. */
sendreq = server->qhead;
server->qhead = sendreq->next;
@ -35,29 +40,71 @@ void ares__close_sockets(ares_channel channel, struct server_state *server)
ares_free(sendreq->data_storage);
ares_free(sendreq);
}
server->qtail = NULL;
/* Reset any existing input buffer. */
if (server->tcp_buffer)
ares_free(server->tcp_buffer);
server->tcp_buffer = NULL;
server->tcp_lenbuf_pos = 0;
/* Reset brokenness */
server->is_broken = 0;
/* Close the TCP and UDP sockets. */
if (server->tcp_socket != ARES_SOCKET_BAD)
{
SOCK_STATE_CALLBACK(channel, server->tcp_socket, 0, 0);
ares__close_socket(channel, server->tcp_socket);
server->tcp_socket = ARES_SOCKET_BAD;
server->tcp_connection_generation = ++channel->tcp_connection_generation;
}
if (server->udp_socket != ARES_SOCKET_BAD)
{
SOCK_STATE_CALLBACK(channel, server->udp_socket, 0, 0);
ares__close_socket(channel, server->udp_socket);
server->udp_socket = ARES_SOCKET_BAD;
}
server->qtail = NULL;
/* Reset any existing input buffer. */
if (server->tcp_buffer)
ares_free(server->tcp_buffer);
server->tcp_buffer = NULL;
server->tcp_lenbuf_pos = 0;
server->tcp_connection_generation = ++channel->tcp_connection_generation;
server->tcp_conn = NULL;
}
SOCK_STATE_CALLBACK(channel, conn->fd, 0, 0);
ares__close_socket(channel, conn->fd);
ares__llist_node_claim(
ares__htable_asvp_get_direct(channel->connnode_by_socket, conn->fd)
);
ares__htable_asvp_remove(channel->connnode_by_socket, conn->fd);
#ifndef NDEBUG
assert(ares__llist_len(conn->queries_to_conn) == 0);
#endif
ares__llist_destroy(conn->queries_to_conn);
ares_free(conn);
}
void ares__close_sockets(struct server_state *server)
{
ares__llist_node_t *node;
while ((node = ares__llist_node_first(server->connections)) != NULL) {
struct server_connection *conn = ares__llist_node_val(node);
ares__close_connection(conn);
}
}
void ares__check_cleanup_conn(ares_channel channel, ares_socket_t fd)
{
ares__llist_node_t *node;
struct server_connection *conn;
int do_cleanup = 0;
node = ares__htable_asvp_get_direct(channel->connnode_by_socket, fd);
if (node == NULL) {
return;
}
conn = ares__llist_node_val(node);
if (ares__llist_len(conn->queries_to_conn)) {
return;
}
/* If we are configured not to stay open, close it out */
if (!(channel->flags & ARES_FLAG_STAYOPEN)) {
do_cleanup = 1;
}
/* If the udp connection hit its max queries, always close it */
if (!conn->is_tcp && channel->udp_max_queries > 0 &&
conn->total_queries >= channel->udp_max_queries) {
do_cleanup = 1;
}
if (do_cleanup) {
ares__close_connection(conn);
}
}

@ -293,16 +293,15 @@ size_t ares__htable_num_keys(ares__htable_t *htable)
return htable->num_keys;
}
unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len,
unsigned int ares__htable_hash_FNV1a(const unsigned char *key, size_t key_len,
unsigned int seed)
{
const unsigned char *data = key;
/* recommended seed is 2166136261U, but we don't want collisions */
unsigned int hv = seed;
size_t i;
for (i = 0; i < key_len; i++) {
hv ^= (unsigned int)data[i];
hv ^= (unsigned int)key[i];
/* hv *= 0x01000193 */
hv += (hv<<1) + (hv<<4) + (hv<<7) + (hv<<8) + (hv<<24);
}
@ -310,17 +309,55 @@ unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len,
return hv;
}
/* Case insensitive version, meant for strings */
unsigned int ares__htable_hash_FNV1a_casecmp(const void *key, size_t key_len,
/* tolower() is locale-specific. Use a lookup table fast conversion that only
* operates on ASCII */
static const char ares__tolower_lookup[] = {
0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F,
0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
};
/* Case insensitive version, meant for ASCII strings */
unsigned int ares__htable_hash_FNV1a_casecmp(const unsigned char *key, size_t key_len,
unsigned int seed)
{
const unsigned char *data = key;
/* recommended seed is 2166136261U, but we don't want collisions */
unsigned int hv = seed;
size_t i;
for (i = 0; i < key_len; i++) {
hv ^= (unsigned int)tolower((char)data[i]);
/* hv *= 16777619 */
hv ^= (unsigned int)ares__tolower_lookup[key[i]];
/* hv *= 0x01000193 */
hv += (hv<<1) + (hv<<4) + (hv<<7) + (hv<<8) + (hv<<24);
}

@ -134,7 +134,7 @@ unsigned int ares__htable_remove(ares__htable_t *htable, const void *key);
* \param[in] seed Seed for generating hash
* \return hash value
*/
unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len,
unsigned int ares__htable_hash_FNV1a(const unsigned char *key, size_t key_len,
unsigned int seed);
/*! FNV1a hash algorithm, but converts all characters to lowercase before
@ -146,8 +146,9 @@ unsigned int ares__htable_hash_FNV1a(const void *key, size_t key_len,
* \param[in] seed Seed for generating hash
* \return hash value
*/
unsigned int ares__htable_hash_FNV1a_casecmp(const void *key, size_t key_len,
unsigned int);
unsigned int ares__htable_hash_FNV1a_casecmp(const unsigned char *key,
size_t key_len,
unsigned int seed);
/*! @} */

@ -0,0 +1,186 @@
/* Copyright (C) 2023 by Brad House
*
* 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.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
#include "ares__htable.h"
#include "ares__htable_asvp.h"
struct ares__htable_asvp {
ares__htable_asvp_val_free_t free_val;
ares__htable_t *hash;
};
typedef struct {
ares_socket_t key;
void *val;
ares__htable_asvp_t *parent;
} ares__htable_asvp_bucket_t;
void ares__htable_asvp_destroy(ares__htable_asvp_t *htable)
{
if (htable == NULL)
return;
ares__htable_destroy(htable->hash);
ares_free(htable);
}
static unsigned int hash_func(const void *key, unsigned int seed)
{
const ares_socket_t *arg = key;
return ares__htable_hash_FNV1a((const unsigned char *)arg, sizeof(*arg),
seed);
}
static const void *bucket_key(const void *bucket)
{
const ares__htable_asvp_bucket_t *arg = bucket;
return &arg->key;
}
static void bucket_free(void *bucket)
{
ares__htable_asvp_bucket_t *arg = bucket;
if (arg->parent->free_val)
arg->parent->free_val(arg->val);
ares_free(arg);
}
static unsigned int key_eq(const void *key1, const void *key2)
{
const ares_socket_t *k1 = key1;
const ares_socket_t *k2 = key2;
if (*k1 == *k2)
return 1;
return 0;
}
ares__htable_asvp_t *ares__htable_asvp_create(
ares__htable_asvp_val_free_t val_free)
{
ares__htable_asvp_t *htable = ares_malloc(sizeof(*htable));
if (htable == NULL)
goto fail;
htable->hash = ares__htable_create(hash_func,
bucket_key,
bucket_free,
key_eq);
if (htable->hash == NULL)
goto fail;
htable->free_val = val_free;
return htable;
fail:
if (htable) {
ares__htable_destroy(htable->hash);
ares_free(htable);
}
return NULL;
}
unsigned int ares__htable_asvp_insert(ares__htable_asvp_t *htable,
ares_socket_t key, void *val)
{
ares__htable_asvp_bucket_t *bucket = NULL;
if (htable == NULL)
goto fail;
bucket = ares_malloc(sizeof(*bucket));
if (bucket == NULL)
goto fail;
bucket->parent = htable;
bucket->key = key;
bucket->val = val;
if (!ares__htable_insert(htable->hash, bucket))
goto fail;
return 1;
fail:
if (bucket) {
ares_free(bucket);
}
return 0;
}
unsigned int ares__htable_asvp_get(ares__htable_asvp_t *htable,
ares_socket_t key, void **val)
{
ares__htable_asvp_bucket_t *bucket = NULL;
if (val)
*val = NULL;
if (htable == NULL)
return 0;
bucket = ares__htable_get(htable->hash, &key);
if (bucket == NULL)
return 0;
if (val)
*val = bucket->val;
return 1;
}
void *ares__htable_asvp_get_direct(ares__htable_asvp_t *htable,
ares_socket_t key)
{
void *val = NULL;
ares__htable_asvp_get(htable, key, &val);
return val;
}
unsigned int ares__htable_asvp_remove(ares__htable_asvp_t *htable,
ares_socket_t key)
{
if (htable == NULL)
return 0;
return ares__htable_remove(htable->hash, &key);
}
size_t ares__htable_asvp_num_keys(ares__htable_asvp_t *htable)
{
if (htable == NULL)
return 0;
return ares__htable_num_keys(htable->hash);
}

@ -0,0 +1,111 @@
/* Copyright (C) 2023 by Brad House
*
* 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.
*
* SPDX-License-Identifier: MIT
*/
#ifndef __ARES__HTABLE_ASVP_H
#define __ARES__HTABLE_ASVP_H
/*! \addtogroup ares__htable_asvp HashTable with ares_socket_t Key and
* void pointer Value
*
* This data structure wraps the base ares__htable data structure in order to
* split the key and value data types as ares_socket_t and void pointer,
* respectively.
*
* Average time complexity:
* - Insert: O(1)
* - Search: O(1)
* - Delete: O(1)
*
* @{
*/
struct ares__htable_asvp;
/*! Opaque data type for ares_socket_t key, void pointer hash table
* implementation */
typedef struct ares__htable_asvp ares__htable_asvp_t;
/*! Callback to free value stored in hashtable
*
* \param[in] val user-supplied value
*/
typedef void (*ares__htable_asvp_val_free_t)(void *val);
/*! Destroy hashtable
*
* \param[in] htable Initialized hashtable
*/
void ares__htable_asvp_destroy(ares__htable_asvp_t *htable);
/*! Create size_t key, void pointer value hash table
*
* \param[in] val_free Optional. Call back to free user-supplied value. If
* NULL it is expected the caller will clean up any user
* supplied values.
*/
ares__htable_asvp_t *ares__htable_asvp_create(
ares__htable_asvp_val_free_t val_free);
/*! Insert key/value into hash table
*
* \param[in] htable Initialized hash table
* \param[in] key key to associate with value
* \param[in] val value to store (takes ownership). May be NULL.
* \return 1 on success, 0 on out of memory or misuse
*/
unsigned int ares__htable_asvp_insert(ares__htable_asvp_t *htable,
ares_socket_t key, void *val);
/*! Retrieve value from hashtable based on key
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \param[out] val Optional. Pointer to store value.
* \return 1 on success, 0 on failure
*/
unsigned int ares__htable_asvp_get(ares__htable_asvp_t *htable,
ares_socket_t key, void **val);
/*! Retrieve value from hashtable directly as return value. Caveat to this
* function over ares__htable_asvp_get() is that if a NULL value is stored
* you cannot determine if the key is not found or the value is NULL.
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \return value associated with key in hashtable or NULL
*/
void *ares__htable_asvp_get_direct(ares__htable_asvp_t *htable,
ares_socket_t key);
/*! Remove a value from the hashtable by key
*
* \param[in] htable Initialized hash table
* \param[in] key key to use to search
* \return 1 if found, 0 if not
*/
unsigned int ares__htable_asvp_remove(ares__htable_asvp_t *htable,
ares_socket_t key);
/*! Retrieve the number of keys stored in the hash table
*
* \param[in] htable Initialized hash table
* \return count
*/
size_t ares__htable_asvp_num_keys(ares__htable_asvp_t *htable);
/*! @} */
#endif /* __ARES__HTABLE_ASVP_H */

@ -44,10 +44,11 @@ void ares__htable_stvp_destroy(ares__htable_stvp_t *htable)
}
static unsigned int hash_func(const void *bucket, unsigned int seed)
static unsigned int hash_func(const void *key, unsigned int seed)
{
const ares__htable_stvp_bucket_t *arg = bucket;
return ares__htable_hash_FNV1a(&arg->key, sizeof(arg->key), seed);
const size_t *arg = key;
return ares__htable_hash_FNV1a((const unsigned char *)arg, sizeof(*arg),
seed);
}

@ -223,7 +223,7 @@ ares__slist_node_t *ares__slist_insert(ares__slist_t *list, void *val)
if (list->levels < node->levels) {
size_t zero_len = sizeof(*list->head) * (node->levels - list->levels);
size_t offset = sizeof(*list->head) * list->levels;
void *ptr = ares_realloc(list->head, sizeof(*list->head) * list->levels);
void *ptr = ares_realloc(list->head, sizeof(*list->head) * node->levels);
if (ptr == NULL)
goto fail;
@ -236,7 +236,6 @@ ares__slist_node_t *ares__slist_insert(ares__slist_t *list, void *val)
/* Scan from highest level in the slist, even if we're not using that number
* of levels for this entry as this is what makes it O(log n) */
for (i=list->levels; i-- > 0; ) {
/* set left if left is NULL and the current node value is greater than the
* head at this level */
if (left == NULL &&
@ -307,7 +306,7 @@ ares__slist_node_t *ares__slist_node_find(ares__slist_t *list, const void *val)
/* Scan nodes starting at the highest level. For each level scan forward
* until the value is between the prior and next node, or if equal quit
* as we found a match */
for (i=list->levels; i-- > 0; ) {
for (i=list->levels-1; i-- > 0; ) {
if (node == NULL)
node = list->head[i];

@ -49,6 +49,7 @@ void ares_cancel(ares_channel channel)
node = ares__llist_node_first(list_copy);
while (node != NULL) {
struct query *query;
ares_socket_t fd = ARES_SOCKET_BAD;
/* Cache next since this node is being deleted */
next = ares__llist_node_next(node);
@ -56,24 +57,21 @@ void ares_cancel(ares_channel channel)
query = ares__llist_node_claim(node);
query->node_all_queries = NULL;
/* Cache file descriptor for connection so we can clean it up possibly */
if (query->conn)
fd = query->conn->fd;
/* NOTE: its possible this may enqueue new queries */
query->callback(query->arg, ARES_ECANCELLED, 0, NULL, 0);
ares__free_query(query);
/* See if the connection should be cleaned up */
if (fd != ARES_SOCKET_BAD)
ares__check_cleanup_conn(channel, fd);
node = next;
}
ares__llist_destroy(list_copy);
}
if (!(channel->flags & ARES_FLAG_STAYOPEN) && ares__llist_len(channel->all_queries) == 0)
{
if (channel->servers)
{
int i;
for (i = 0; i < channel->nservers; i++)
ares__close_sockets(channel, &channel->servers[i]);
}
}
}

@ -52,6 +52,7 @@ void ares_destroy(ares_channel channel)
if (!channel)
return;
/* Destroy all queries */
node = ares__llist_node_first(channel->all_queries);
while (node != NULL) {
ares__llist_node_t *next = ares__llist_node_next(node);
@ -63,7 +64,8 @@ void ares_destroy(ares_channel channel)
node = next;
}
#ifndef NDEBUG
/* Freeing the query should remove it from all the lists in which it sits,
* so all query lists should be empty now.
@ -75,6 +77,10 @@ void ares_destroy(ares_channel channel)
ares__destroy_servers_state(channel);
#ifndef NDEBUG
assert(ares__htable_asvp_num_keys(channel->connnode_by_socket) == 0);
#endif
if (channel->domains) {
for (i = 0; i < channel->ndomains; i++)
ares_free(channel->domains[i]);
@ -84,6 +90,7 @@ void ares_destroy(ares_channel channel)
ares__llist_destroy(channel->all_queries);
ares__slist_destroy(channel->queries_by_timeout);
ares__htable_stvp_destroy(channel->queries_by_qid);
ares__htable_asvp_destroy(channel->connnode_by_socket);
if(channel->sortlist)
ares_free(channel->sortlist);
@ -113,9 +120,8 @@ void ares__destroy_servers_state(ares_channel channel)
for (i = 0; i < channel->nservers; i++)
{
server = &channel->servers[i];
ares__close_sockets(channel, server);
assert(ares__llist_len(server->queries_to_server) == 0);
ares__llist_destroy(server->queries_to_server);
ares__close_sockets(server);
ares__llist_destroy(server->connections);
}
ares_free(channel->servers);
channel->servers = NULL;

@ -32,30 +32,30 @@ int ares_fds(ares_channel channel, fd_set *read_fds, fd_set *write_fds)
size_t active_queries = ares__llist_len(channel->all_queries);
nfds = 0;
for (i = 0; i < channel->nservers; i++)
{
server = &channel->servers[i];
for (i = 0; i < channel->nservers; i++) {
server = &channel->servers[i];
ares__llist_node_t *node;
for (node = ares__llist_node_first(server->connections);
node != NULL;
node = ares__llist_node_next(node)) {
struct server_connection *conn = ares__llist_node_val(node);
/* We only need to register interest in UDP sockets if we have
* outstanding queries.
*/
if (active_queries && server->udp_socket != ARES_SOCKET_BAD)
{
FD_SET(server->udp_socket, read_fds);
if (server->udp_socket >= nfds)
nfds = server->udp_socket + 1;
}
/* We always register for TCP events, because we want to know
* when the other side closes the connection, so we don't waste
* time trying to use a broken connection.
*/
if (server->tcp_socket != ARES_SOCKET_BAD)
{
FD_SET(server->tcp_socket, read_fds);
if (server->qhead)
FD_SET(server->tcp_socket, write_fds);
if (server->tcp_socket >= nfds)
nfds = server->tcp_socket + 1;
}
if (active_queries || conn->is_tcp) {
FD_SET(conn->fd, read_fds);
if (conn->fd >= nfds)
nfds = conn->fd + 1;
}
if (conn->is_tcp && server->qhead) {
FD_SET(conn->fd, write_fds);
}
}
}
return (int)nfds;
}

@ -32,37 +32,38 @@ int ares_getsock(ares_channel channel,
/* Are there any active queries? */
size_t active_queries = ares__llist_len(channel->all_queries);
for (i = 0; i < channel->nservers; i++)
{
server = &channel->servers[i];
for (i = 0; i < channel->nservers; i++) {
server = &channel->servers[i];
ares__llist_node_t *node;
for (node = ares__llist_node_first(server->connections);
node != NULL;
node = ares__llist_node_next(node)) {
struct server_connection *conn = ares__llist_node_val(node);
if (sockindex >= numsocks || sockindex >= ARES_GETSOCK_MAXNUM)
break;
/* We only need to register interest in UDP sockets if we have
* outstanding queries.
*/
if (active_queries && server->udp_socket != ARES_SOCKET_BAD)
{
if(sockindex >= numsocks || sockindex >= ARES_GETSOCK_MAXNUM)
break;
socks[sockindex] = server->udp_socket;
bitmap |= ARES_GETSOCK_READABLE(setbits, sockindex);
sockindex++;
}
/* We always register for TCP events, because we want to know
* when the other side closes the connection, so we don't waste
* time trying to use a broken connection.
*/
if (server->tcp_socket != ARES_SOCKET_BAD)
{
if(sockindex >= numsocks || sockindex >= ARES_GETSOCK_MAXNUM)
break;
socks[sockindex] = server->tcp_socket;
bitmap |= ARES_GETSOCK_READABLE(setbits, sockindex);
if (!active_queries && !conn->is_tcp)
continue;
socks[sockindex] = conn->fd;
if (active_queries || conn->is_tcp) {
bitmap |= ARES_GETSOCK_READABLE(setbits, sockindex);
}
if (server->qhead && active_queries)
/* then the tcp socket is also writable! */
bitmap |= ARES_GETSOCK_WRITABLE(setbits, sockindex);
if (conn->is_tcp && server->qhead) {
/* then the tcp socket is also writable! */
bitmap |= ARES_GETSOCK_WRITABLE(setbits, sockindex);
}
sockindex++;
}
sockindex++;
}
}
return bitmap;
}

@ -127,7 +127,6 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options,
int optmask)
{
ares_channel channel;
int i;
int status = ARES_SUCCESS;
if (ares_library_initialized() != ARES_SUCCESS)
@ -189,6 +188,12 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options,
goto done;
}
channel->connnode_by_socket = ares__htable_asvp_create(NULL);
if (channel->connnode_by_socket == NULL) {
status = ARES_ENOMEM;
goto done;
}
/* Initialize configuration by each of the four sources, from highest
* precedence to lowest.
*/
@ -234,9 +239,6 @@ done:
{
/* Something failed; clean up memory we may have allocated. */
if (channel->servers) {
for (i = 0; i < channel->nservers; i++) {
ares__llist_destroy(channel->servers[i].queries_to_server);
}
ares_free(channel->servers);
}
if (channel->ndomains != -1)
@ -255,6 +257,7 @@ done:
ares__htable_stvp_destroy(channel->queries_by_qid);
ares__llist_destroy(channel->all_queries);
ares__slist_destroy(channel->queries_by_timeout);
ares__htable_asvp_destroy(channel->connnode_by_socket);
ares_free(channel);
return status;
}
@ -450,6 +453,11 @@ int ares_save_options(ares_channel channel, struct ares_options *options,
return ARES_ENOMEM;
}
if (channel->udp_max_queries > 0) {
(*optmask) |= ARES_OPT_UDP_MAX_QUERIES;
options->udp_max_queries = channel->udp_max_queries;
}
return ARES_SUCCESS;
}
@ -575,6 +583,9 @@ static int init_by_options(ares_channel channel,
return ARES_ENOMEM;
}
if (optmask & ARES_OPT_UDP_MAX_QUERIES)
channel->udp_max_queries = options->udp_max_queries;
channel->optmask = optmask;
return ARES_SUCCESS;
@ -2379,23 +2390,25 @@ int ares__init_servers_state(ares_channel channel)
struct server_state *server;
int i;
for (i = 0; i < channel->nservers; i++)
{
server = &channel->servers[i];
server->udp_socket = ARES_SOCKET_BAD;
server->tcp_socket = ARES_SOCKET_BAD;
server->tcp_connection_generation = ++channel->tcp_connection_generation;
server->tcp_lenbuf_pos = 0;
server->tcp_buffer_pos = 0;
server->tcp_buffer = NULL;
server->tcp_length = 0;
server->qhead = NULL;
server->qtail = NULL;
server->channel = channel;
server->is_broken = 0;
server->queries_to_server = ares__llist_create(NULL);
if (server->queries_to_server == NULL)
return ARES_ENOMEM;
}
for (i = 0; i < channel->nservers; i++) {
server = &channel->servers[i];
/* NOTE: Can't use memset() here because the server addresses have been
* filled in already */
server->tcp_lenbuf_pos = 0;
server->tcp_buffer_pos = 0;
server->tcp_buffer = NULL;
server->tcp_length = 0;
server->qhead = NULL;
server->qtail = NULL;
server->idx = i;
server->connections = ares__llist_create(NULL);
if (server->connections == NULL)
return ARES_ENOMEM;
server->tcp_connection_generation = ++channel->tcp_connection_generation;
server->channel = channel;
}
return ARES_SUCCESS;
}

@ -111,6 +111,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__llist.h"
#include "ares__slist.h"
#include "ares__htable_stvp.h"
#include "ares__htable_asvp.h"
#ifndef HAVE_GETENV
# include "ares_getenv.h"
@ -170,10 +171,24 @@ struct send_request {
struct send_request *next;
};
struct server_state;
struct server_connection {
struct server_state *server;
ares_socket_t fd;
int is_tcp;
/* total number of queries run on this connection since it was established */
size_t total_queries;
/* list of outstanding queries to this connection */
ares__llist_t *queries_to_conn;
};
struct server_state {
size_t idx; /* index for server in ares_channel */
struct ares_addr addr;
ares_socket_t udp_socket;
ares_socket_t tcp_socket;
ares__llist_t *connections;
struct server_connection *tcp_conn;
/* Mini-buffer for reading the length word */
unsigned char tcp_lenbuf[2];
@ -194,16 +209,8 @@ struct server_state {
* re-send. */
int tcp_connection_generation;
/* list of outstanding queries to this server */
ares__llist_t *queries_to_server;
/* Link back to owning channel */
ares_channel channel;
/* Is this server broken? We mark connections as broken when a
* request that is queued for sending times out.
*/
int is_broken;
};
/* State to represent a DNS query */
@ -218,9 +225,12 @@ struct query {
* make removal operations O(1).
*/
ares__slist_node_t *node_queries_by_timeout;
ares__llist_node_t *node_queries_to_server;
ares__llist_node_t *node_queries_to_conn;
ares__llist_node_t *node_all_queries;
/* connection handle for validation purposes */
const struct server_connection *conn;
/* Query buf with length at beginning, for TCP transmission */
unsigned char *tcpbuf;
int tcplen;
@ -314,6 +324,12 @@ struct ares_channeldata {
/* Queries bucketed by timeout, for quickly handling timeouts: */
ares__slist_t *queries_by_timeout;
/* Map linked list node member for connection to file descriptor. We use
* the node instead of the connection object itself so we can quickly look
* up a connection and remove it if necessary (as otherwise we'd have to
* scan all connections) */
ares__htable_asvp_t *connnode_by_socket;
ares_sock_state_cb sock_state_cb;
void *sock_state_cb_data;
@ -331,6 +347,9 @@ struct ares_channeldata {
/* Path for hosts file, configurable via ares_options */
char *hosts_path;
/* Maximum UDP queries per connection allowed */
int udp_max_queries;
};
/* Does the domain end in ".onion" or ".onion."? Case-insensitive. */
@ -347,7 +366,9 @@ int ares__timedout(struct timeval *now,
void ares__send_query(ares_channel channel, struct query *query,
struct timeval *now);
void ares__close_sockets(ares_channel channel, struct server_state *server);
void ares__close_connection(struct server_connection *conn);
void ares__close_sockets(struct server_state *server);
void ares__check_cleanup_conn(ares_channel channel, ares_socket_t fd);
int ares__get_hostent(FILE *fp, int family, struct hostent **host);
int ares__read_line(FILE *fp, char **buf, size_t *bufsize);
void ares__free_query(struct query *query);

File diff suppressed because it is too large Load Diff

@ -113,7 +113,7 @@ void ares_send(ares_channel channel, const unsigned char *qbuf, int qlen,
/* Initialize our list nodes. */
query->node_queries_by_timeout = NULL;
query->node_queries_to_server = NULL;
query->node_queries_to_conn = NULL;
/* Chain the query into the list of all queries. */
query->node_all_queries = ares__llist_insert_last(channel->all_queries, query);

@ -219,7 +219,91 @@ TEST_P(MockChannelTest, SockConfigureFailCallback) {
EXPECT_EQ(ARES_ECONNREFUSED, result.status_);
}
// TCP only to prevent retries
#define MAXUDPQUERIES_TOTAL 256
#define MAXUDPQUERIES_LIMIT 16
class MockUDPMaxQueriesTest
: public MockChannelOptsTest,
public ::testing::WithParamInterface<int> {
public:
MockUDPMaxQueriesTest()
: MockChannelOptsTest(1, GetParam(), false,
FillOptions(&opts_),
ARES_OPT_UDP_MAX_QUERIES) {}
static struct ares_options* FillOptions(struct ares_options * opts) {
memset(opts, 0, sizeof(struct ares_options));
opts->udp_max_queries = MAXUDPQUERIES_LIMIT;
return opts;
}
private:
struct ares_options opts_;
};
TEST_P(MockUDPMaxQueriesTest, GetHostByNameParallelLookups) {
DNSPacket rsp;
rsp.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 100, {2, 3, 4, 5}));
ON_CALL(server_, OnRequest("www.google.com", T_A))
.WillByDefault(SetReply(&server_, &rsp));
// Get notified of new sockets so we can validate how many are created
int rc = ARES_SUCCESS;
ares_set_socket_callback(channel_, SocketConnectCallback, &rc);
sock_cb_count = 0;
HostResult result[MAXUDPQUERIES_TOTAL];
for (size_t i=0; i<MAXUDPQUERIES_TOTAL; i++) {
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result[i]);
}
Process();
EXPECT_EQ(MAXUDPQUERIES_TOTAL / MAXUDPQUERIES_LIMIT, sock_cb_count);
for (size_t i=0; i<MAXUDPQUERIES_TOTAL; i++) {
std::stringstream ss;
EXPECT_TRUE(result[i].done_);
ss << result[i].host_;
EXPECT_EQ("{'www.google.com' aliases=[] addrs=[2.3.4.5]}", ss.str());
}
}
/* There may be an issue with TCP parallel queries, this test fails, as does
* issue #266 indicate */
#if 0
#define TCPPARALLELLOOKUPS 256
TEST_P(MockTCPChannelTest, GetHostByNameParallelLookups) {
DNSPacket rsp;
rsp.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 100, {2, 3, 4, 5}));
ON_CALL(server_, OnRequest("www.google.com", T_A))
.WillByDefault(SetReply(&server_, &rsp));
// Get notified of new sockets so we can validate how many are created
int rc = ARES_SUCCESS;
ares_set_socket_callback(channel_, SocketConnectCallback, &rc);
sock_cb_count = 0;
HostResult result[TCPPARALLELLOOKUPS];
for (size_t i=0; i<TCPPARALLELLOOKUPS; i++) {
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result[i]);
}
Process();
EXPECT_EQ(1, sock_cb_count);
for (size_t i=0; i<TCPPARALLELLOOKUPS; i++) {
std::stringstream ss;
EXPECT_TRUE(result[i].done_);
ss << result[i].host_;
EXPECT_EQ("{'www.google.com' aliases=[] addrs=[2.3.4.5]}", ss.str());
}
}
#endif
TEST_P(MockTCPChannelTest, MalformedResponse) {
std::vector<byte> one = {0x01};
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
@ -1197,6 +1281,8 @@ INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockChannelTest, ::testing::ValuesIn(a
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPChannelTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockUDPMaxQueriesTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockTCPChannelTest, ::testing::ValuesIn(ares::test::families));
INSTANTIATE_TEST_SUITE_P(AddressFamilies, MockExtraOptsTest, ::testing::ValuesIn(ares::test::families_modes));

Loading…
Cancel
Save