diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index 36de54af..cad7fc95 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -8,12 +8,14 @@ CSOURCES = ares__addrinfo2hostent.c \ ares__get_hostent.c \ ares__htable.c \ ares__htable_asvp.c \ - ares__htable_stvp.c \ + ares__htable_strvp.c \ + ares__htable_szvp.c \ ares__llist.c \ ares__parse_into_addrinfo.c \ ares__read_line.c \ ares__readaddrinfo.c \ ares__slist.c \ + ares__socket.c \ ares__sortaddrinfo.c \ ares__timeval.c \ ares_android.c \ @@ -72,7 +74,8 @@ CSOURCES = ares__addrinfo2hostent.c \ HHEADERS = ares__buf.h \ ares__htable.h \ ares__htable_asvp.h \ - ares__htable_stvp.h \ + ares__htable_strvp.h \ + ares__htable_szvp.h \ ares__llist.h \ ares__slist.h \ ares_android.h \ diff --git a/src/lib/ares__buf.c b/src/lib/ares__buf.c index 3e064efa..b1092b12 100644 --- a/src/lib/ares__buf.c +++ b/src/lib/ares__buf.c @@ -45,6 +45,69 @@ struct ares__buf { * SIZE_MAX if not set. */ }; + +/* Reserved characters for names that need to be escaped */ +static ares_bool_t is_reservedch(int ch) +{ + switch (ch) { + case '"': + case '.': + case ';': + case '\\': + case '(': + case ')': + case '@': + case '$': + return ARES_TRUE; + default: + break; + } + + return ARES_FALSE; +} + +static ares_bool_t ares__isprint(int ch) +{ + if (ch >= 0x20 && ch <= 0x7E) { + return ARES_TRUE; + } + return ARES_FALSE; +} + +/* Character set allowed by hostnames. This is to include the normal + * domain name character set plus: + * - underscores which are used in SRV records. + * - Forward slashes such as are used for classless in-addr.arpa + * delegation (CNAMEs) + * - Asterisks may be used for wildcard domains in CNAMEs as seen in the + * real world. + * While RFC 2181 section 11 does state not to do validation, + * that applies to servers, not clients. Vulnerabilities have been + * reported when this validation is not performed. Security is more + * important than edge-case compatibility (which is probably invalid + * anyhow). */ +static ares_bool_t is_hostnamech(int ch) +{ + /* [A-Za-z0-9-*._/] + * Don't use isalnum() as it is locale-specific + */ + if (ch >= 'A' && ch <= 'Z') { + return ARES_TRUE; + } + if (ch >= 'a' && ch <= 'z') { + return ARES_TRUE; + } + if (ch >= '0' && ch <= '9') { + return ARES_TRUE; + } + if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') { + return ARES_TRUE; + } + + return ARES_FALSE; +} + + ares__buf_t *ares__buf_create(void) { ares__buf_t *buf = ares_malloc_zero(sizeof(*buf)); @@ -324,6 +387,62 @@ const unsigned char *ares__buf_tag_fetch(const ares__buf_t *buf, size_t *len) return buf->data + buf->tag_offset; } +size_t ares__buf_tag_length(const ares__buf_t *buf) +{ + if (buf == NULL || buf->tag_offset == SIZE_MAX) + return 0; + return buf->offset - buf->tag_offset; +} + +ares_status_t ares__buf_tag_fetch_bytes(const ares__buf_t *buf, + unsigned char *bytes, size_t *len) +{ + size_t ptr_len = 0; + const unsigned char *ptr = ares__buf_tag_fetch(buf, &ptr_len); + + if (ptr == NULL || bytes == NULL || len == NULL) + return ARES_EFORMERR; + + if (*len < ptr_len) + return ARES_EFORMERR; + + *len = ptr_len; + + if (ptr_len > 0) { + memcpy(bytes, ptr, ptr_len); + } + return ARES_SUCCESS; +} + +ares_status_t ares__buf_tag_fetch_string(const ares__buf_t *buf, char *str, + size_t len) +{ + size_t out_len; + ares_status_t status; + size_t i; + + if (str == NULL || len == 0) + return ARES_EFORMERR; + + /* Space for NULL terminator */ + out_len = len - 1; + + status = ares__buf_tag_fetch_bytes(buf, (unsigned char *)str, &out_len); + if (status != ARES_SUCCESS) + return status; + + /* NULL terminate */ + str[out_len] = 0; + + /* Validate string is printable */ + for (i=0; i= 0x20 && ch <= 0x7E) { - return ARES_TRUE; - } - return ARES_FALSE; -} - -/* Character set allowed by hostnames. This is to include the normal - * domain name character set plus: - * - underscores which are used in SRV records. - * - Forward slashes such as are used for classless in-addr.arpa - * delegation (CNAMEs) - * - Asterisks may be used for wildcard domains in CNAMEs as seen in the - * real world. - * While RFC 2181 section 11 does state not to do validation, - * that applies to servers, not clients. Vulnerabilities have been - * reported when this validation is not performed. Security is more - * important than edge-case compatibility (which is probably invalid - * anyhow). */ -static ares_bool_t is_hostnamech(int ch) -{ - /* [A-Za-z0-9-*._/] - * Don't use isalnum() as it is locale-specific - */ - if (ch >= 'A' && ch <= 'Z') { - return ARES_TRUE; - } - if (ch >= 'a' && ch <= 'z') { - return ARES_TRUE; - } - if (ch >= '0' && ch <= '9') { - return ARES_TRUE; - } - if (ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '*') { - return ARES_TRUE; - } - - return ARES_FALSE; -} static ares_status_t ares__buf_fetch_dnsname_into_buf(ares__buf_t *buf, ares__buf_t *dest, @@ -582,7 +641,8 @@ fail: return status; } -size_t ares__buf_consume_whitespace(ares__buf_t *buf, int include_linefeed) +size_t ares__buf_consume_whitespace(ares__buf_t *buf, + ares_bool_t include_linefeed) { size_t remaining_len = 0; const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len); @@ -648,7 +708,7 @@ done: return i; } -size_t ares__buf_consume_line(ares__buf_t *buf, int include_linefeed) +size_t ares__buf_consume_line(ares__buf_t *buf, ares_bool_t include_linefeed) { size_t remaining_len = 0; const unsigned char *ptr = ares__buf_fetch(buf, &remaining_len); diff --git a/src/lib/ares__buf.h b/src/lib/ares__buf.h index 6fc9c900..f2680914 100644 --- a/src/lib/ares__buf.h +++ b/src/lib/ares__buf.h @@ -176,6 +176,40 @@ ares_status_t ares__buf_tag_clear(ares__buf_t *buf); */ const unsigned char *ares__buf_tag_fetch(const ares__buf_t *buf, size_t *len); +/*! Get the length of the current tag offset to the current position. + * + * \param[in] buf Initialized buffer object + * \return length + */ +size_t ares__buf_tag_length(const ares__buf_t *buf); + +/*! Fetch the bytes starting from the tagged position up to the _current_ + * position using the provided buffer. It will not unset the tagged position. + * + * \param[in] buf Initialized buffer object + * \param[in,out] bytes Buffer to hold data + * \param[in,out] len On input, buffer size, on output, bytes place in + * buffer. + * \return ARES_SUCCESS if fetched, ARES_EFORMERR if insufficient buffer size + */ +ares_status_t ares__buf_tag_fetch_bytes(const ares__buf_t *buf, + unsigned char *bytes, size_t *len); + +/*! Fetch the bytes starting from the tagged position up to the _current_ + * position as a NULL-terminated string using the provided buffer. The data + * is validated to be ASCII-printable data. It will not unset the tagged + * poition. + * + * \param[in] buf Initialized buffer object + * \param[in,out] str Buffer to hold data + * \param[in] len On input, buffer size, on output, bytes place in + * buffer. + * \return ARES_SUCCESS if fetched, ARES_EFORMERR if insufficient buffer size, + * ARES_EBADSTR if not printable ASCII + */ +ares_status_t ares__buf_tag_fetch_string(const ares__buf_t *buf, char *str, + size_t len); + /*! Consume the given number of bytes without reading them. * * \param[in] buf Initialized buffer object @@ -249,10 +283,12 @@ ares_status_t ares__buf_fetch_str_dup(ares__buf_t *buf, size_t len, char **str); * 0x0A). * * \param[in] buf Initialized buffer object - * \param[in] include_linefeed 1 to include consuming 0x0A, 0 otherwise. + * \param[in] include_linefeed ARES_TRUE to include consuming 0x0A, + * ARES_FALSE otherwise. * \return number of whitespace characters consumed */ -size_t ares__buf_consume_whitespace(ares__buf_t *buf, int include_linefeed); +size_t ares__buf_consume_whitespace(ares__buf_t *buf, + ares_bool_t include_linefeed); /*! Consume any non-whitespace character (anything other than 0x09, 0x0B, 0x0C, @@ -267,10 +303,11 @@ size_t ares__buf_consume_nonwhitespace(ares__buf_t *buf); * the end of line character (0x0A) itself. * * \param[in] buf Initialized buffer object - * \param[in] include_linefeed 1 to include consuming 0x0A, 0 otherwise. + * \param[in] include_linefeed ARES_TRUE to include consuming 0x0A, + * ARES_FALSE otherwise. * \return number of characters consumed */ -size_t ares__buf_consume_line(ares__buf_t *buf, int include_linefeed); +size_t ares__buf_consume_line(ares__buf_t *buf, ares_bool_t include_linefeed); /*! Check the unprocessed buffer to see if it begins with the sequence of diff --git a/src/lib/ares__htable_strvp.c b/src/lib/ares__htable_strvp.c new file mode 100644 index 00000000..db84ae7b --- /dev/null +++ b/src/lib/ares__htable_strvp.c @@ -0,0 +1,195 @@ +/* MIT License + * + * Copyright (c) 2023 Brad House + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ +#include "ares_setup.h" +#include "ares.h" +#include "ares_private.h" +#include "ares__htable.h" +#include "ares__htable_strvp.h" + +struct ares__htable_strvp { + ares__htable_strvp_val_free_t free_val; + ares__htable_t *hash; +}; + +typedef struct { + char *key; + void *val; + ares__htable_strvp_t *parent; +} ares__htable_strvp_bucket_t; + +void ares__htable_strvp_destroy(ares__htable_strvp_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 char *arg = key; + return ares__htable_hash_FNV1a_casecmp((const unsigned char *)arg, + ares_strlen(arg), seed); +} + +static const void *bucket_key(const void *bucket) +{ + const ares__htable_strvp_bucket_t *arg = bucket; + return arg->key; +} + +static void bucket_free(void *bucket) +{ + ares__htable_strvp_bucket_t *arg = bucket; + + if (arg->parent->free_val) { + arg->parent->free_val(arg->val); + } + ares_free(arg->key); + ares_free(arg); +} + +static ares_bool_t key_eq(const void *key1, const void *key2) +{ + const char *k1 = key1; + const char *k2 = key2; + + if (strcmp(k1, k2) == 0) { + return ARES_TRUE; + } + + return ARES_FALSE; +} + +ares__htable_strvp_t * + ares__htable_strvp_create(ares__htable_strvp_val_free_t val_free) +{ + ares__htable_strvp_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; +} + +ares_bool_t ares__htable_strvp_insert(ares__htable_strvp_t *htable, + const char *key, + void *val) +{ + ares__htable_strvp_bucket_t *bucket = NULL; + + if (htable == NULL || key == NULL) { + goto fail; + } + + bucket = ares_malloc(sizeof(*bucket)); + if (bucket == NULL) { + goto fail; + } + + bucket->parent = htable; + bucket->key = ares_strdup(key); + bucket->val = val; + + if (!ares__htable_insert(htable->hash, bucket)) { + goto fail; + } + + return ARES_TRUE; + +fail: + if (bucket) { + ares_free(bucket); + } + return ARES_FALSE; +} + +ares_bool_t ares__htable_strvp_get(const ares__htable_strvp_t *htable, + const char *key, void **val) +{ + ares__htable_strvp_bucket_t *bucket = NULL; + + if (val) { + *val = NULL; + } + + if (htable == NULL || key == NULL) { + return ARES_FALSE; + } + + bucket = ares__htable_get(htable->hash, &key); + if (bucket == NULL) { + return ARES_FALSE; + } + + if (val) { + *val = bucket->val; + } + return ARES_TRUE; +} + +void *ares__htable_strvp_get_direct(const ares__htable_strvp_t *htable, + const char *key) +{ + void *val = NULL; + ares__htable_strvp_get(htable, key, &val); + return val; +} + +ares_bool_t ares__htable_strvp_remove(ares__htable_strvp_t *htable, + const char *key) +{ + if (htable == NULL) { + return ARES_FALSE; + } + + return ares__htable_remove(htable->hash, key); +} + +size_t ares__htable_strvp_num_keys(const ares__htable_strvp_t *htable) +{ + if (htable == NULL) { + return 0; + } + return ares__htable_num_keys(htable->hash); +} diff --git a/src/lib/ares__htable_strvp.h b/src/lib/ares__htable_strvp.h new file mode 100644 index 00000000..52ad489a --- /dev/null +++ b/src/lib/ares__htable_strvp.h @@ -0,0 +1,118 @@ +/* MIT License + * + * Copyright (c) 2023 Brad House + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ +#ifndef __ARES__HTABLE_STRVP_H +#define __ARES__HTABLE_STRVP_H + +/*! \addtogroup ares__htable_strvp HashTable with string 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 string and void pointer, respectively. + * + * Average time complexity: + * - Insert: O(1) + * - Search: O(1) + * - Delete: O(1) + * + * @{ + */ + +struct ares__htable_strvp; + +/*! Opaque data type for size_t key, void pointer hash table implementation */ +typedef struct ares__htable_strvp ares__htable_strvp_t; + +/*! Callback to free value stored in hashtable + * + * \param[in] val user-supplied value + */ +typedef void (*ares__htable_strvp_val_free_t)(void *val); + +/*! Destroy hashtable + * + * \param[in] htable Initialized hashtable + */ +void ares__htable_strvp_destroy(ares__htable_strvp_t *htable); + +/*! Create string, 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_strvp_t * + ares__htable_strvp_create(ares__htable_strvp_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 ARES_TRUE on success, ARES_FALSE on failure or out of memory + */ +ares_bool_t ares__htable_strvp_insert(ares__htable_strvp_t *htable, + const char *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 ARES_TRUE on success, ARES_FALSE on failure + */ +ares_bool_t ares__htable_strvp_get(const ares__htable_strvp_t *htable, + const char *key, void **val); + +/*! Retrieve value from hashtable directly as return value. Caveat to this + * function over ares__htable_strvp_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_strvp_get_direct(const ares__htable_strvp_t *htable, + const char *key); + +/*! Remove a value from the hashtable by key + * + * \param[in] htable Initialized hash table + * \param[in] key key to use to search + * \return ARES_TRUE if found, ARES_FALSE if not + */ +ares_bool_t ares__htable_strvp_remove(ares__htable_strvp_t *htable, + const char *key); + +/*! Retrieve the number of keys stored in the hash table + * + * \param[in] htable Initialized hash table + * \return count + */ +size_t ares__htable_strvp_num_keys(const ares__htable_strvp_t *htable); + +/*! @} */ + +#endif /* __ARES__HTABLE_STVP_H */ diff --git a/src/lib/ares__htable_stvp.c b/src/lib/ares__htable_szvp.c similarity index 79% rename from src/lib/ares__htable_stvp.c rename to src/lib/ares__htable_szvp.c index a79bdc63..2ff64784 100644 --- a/src/lib/ares__htable_stvp.c +++ b/src/lib/ares__htable_szvp.c @@ -27,20 +27,20 @@ #include "ares.h" #include "ares_private.h" #include "ares__htable.h" -#include "ares__htable_stvp.h" +#include "ares__htable_szvp.h" -struct ares__htable_stvp { - ares__htable_stvp_val_free_t free_val; +struct ares__htable_szvp { + ares__htable_szvp_val_free_t free_val; ares__htable_t *hash; }; typedef struct { size_t key; void *val; - ares__htable_stvp_t *parent; -} ares__htable_stvp_bucket_t; + ares__htable_szvp_t *parent; +} ares__htable_szvp_bucket_t; -void ares__htable_stvp_destroy(ares__htable_stvp_t *htable) +void ares__htable_szvp_destroy(ares__htable_szvp_t *htable) { if (htable == NULL) { return; @@ -59,13 +59,13 @@ static unsigned int hash_func(const void *key, unsigned int seed) static const void *bucket_key(const void *bucket) { - const ares__htable_stvp_bucket_t *arg = bucket; + const ares__htable_szvp_bucket_t *arg = bucket; return &arg->key; } static void bucket_free(void *bucket) { - ares__htable_stvp_bucket_t *arg = bucket; + ares__htable_szvp_bucket_t *arg = bucket; if (arg->parent->free_val) { arg->parent->free_val(arg->val); @@ -86,10 +86,10 @@ static ares_bool_t key_eq(const void *key1, const void *key2) return ARES_FALSE; } -ares__htable_stvp_t * - ares__htable_stvp_create(ares__htable_stvp_val_free_t val_free) +ares__htable_szvp_t * + ares__htable_szvp_create(ares__htable_szvp_val_free_t val_free) { - ares__htable_stvp_t *htable = ares_malloc(sizeof(*htable)); + ares__htable_szvp_t *htable = ares_malloc(sizeof(*htable)); if (htable == NULL) { goto fail; } @@ -112,10 +112,10 @@ fail: return NULL; } -ares_bool_t ares__htable_stvp_insert(ares__htable_stvp_t *htable, size_t key, +ares_bool_t ares__htable_szvp_insert(ares__htable_szvp_t *htable, size_t key, void *val) { - ares__htable_stvp_bucket_t *bucket = NULL; + ares__htable_szvp_bucket_t *bucket = NULL; if (htable == NULL) { goto fail; @@ -143,10 +143,10 @@ fail: return ARES_FALSE; } -ares_bool_t ares__htable_stvp_get(const ares__htable_stvp_t *htable, size_t key, +ares_bool_t ares__htable_szvp_get(const ares__htable_szvp_t *htable, size_t key, void **val) { - ares__htable_stvp_bucket_t *bucket = NULL; + ares__htable_szvp_bucket_t *bucket = NULL; if (val) { *val = NULL; @@ -167,15 +167,15 @@ ares_bool_t ares__htable_stvp_get(const ares__htable_stvp_t *htable, size_t key, return ARES_TRUE; } -void *ares__htable_stvp_get_direct(const ares__htable_stvp_t *htable, +void *ares__htable_szvp_get_direct(const ares__htable_szvp_t *htable, size_t key) { void *val = NULL; - ares__htable_stvp_get(htable, key, &val); + ares__htable_szvp_get(htable, key, &val); return val; } -ares_bool_t ares__htable_stvp_remove(ares__htable_stvp_t *htable, size_t key) +ares_bool_t ares__htable_szvp_remove(ares__htable_szvp_t *htable, size_t key) { if (htable == NULL) { return ARES_FALSE; @@ -184,7 +184,7 @@ ares_bool_t ares__htable_stvp_remove(ares__htable_stvp_t *htable, size_t key) return ares__htable_remove(htable->hash, &key); } -size_t ares__htable_stvp_num_keys(const ares__htable_stvp_t *htable) +size_t ares__htable_szvp_num_keys(const ares__htable_szvp_t *htable) { if (htable == NULL) { return 0; diff --git a/src/lib/ares__htable_stvp.h b/src/lib/ares__htable_szvp.h similarity index 82% rename from src/lib/ares__htable_stvp.h rename to src/lib/ares__htable_szvp.h index 003b1843..9857afe7 100644 --- a/src/lib/ares__htable_stvp.h +++ b/src/lib/ares__htable_szvp.h @@ -26,7 +26,7 @@ #ifndef __ARES__HTABLE_STVP_H #define __ARES__HTABLE_STVP_H -/*! \addtogroup ares__htable_stvp HashTable with size_t Key and void pointer +/*! \addtogroup ares__htable_szvp HashTable with size_t Key and void pointer * Value * * This data structure wraps the base ares__htable data structure in order to @@ -40,22 +40,22 @@ * @{ */ -struct ares__htable_stvp; +struct ares__htable_szvp; /*! Opaque data type for size_t key, void pointer hash table implementation */ -typedef struct ares__htable_stvp ares__htable_stvp_t; +typedef struct ares__htable_szvp ares__htable_szvp_t; /*! Callback to free value stored in hashtable * * \param[in] val user-supplied value */ -typedef void (*ares__htable_stvp_val_free_t)(void *val); +typedef void (*ares__htable_szvp_val_free_t)(void *val); /*! Destroy hashtable * * \param[in] htable Initialized hashtable */ -void ares__htable_stvp_destroy(ares__htable_stvp_t *htable); +void ares__htable_szvp_destroy(ares__htable_szvp_t *htable); /*! Create size_t key, void pointer value hash table * @@ -63,8 +63,8 @@ void ares__htable_stvp_destroy(ares__htable_stvp_t *htable); * NULL it is expected the caller will clean up any user * supplied values. */ -ares__htable_stvp_t * - ares__htable_stvp_create(ares__htable_stvp_val_free_t val_free); +ares__htable_szvp_t * + ares__htable_szvp_create(ares__htable_szvp_val_free_t val_free); /*! Insert key/value into hash table * @@ -73,7 +73,7 @@ ares__htable_stvp_t * * \param[in] val value to store (takes ownership). May be NULL. * \return ARES_TRUE on success, ARES_FALSE on failure or out of memory */ -ares_bool_t ares__htable_stvp_insert(ares__htable_stvp_t *htable, size_t key, +ares_bool_t ares__htable_szvp_insert(ares__htable_szvp_t *htable, size_t key, void *val); /*! Retrieve value from hashtable based on key @@ -83,18 +83,18 @@ ares_bool_t ares__htable_stvp_insert(ares__htable_stvp_t *htable, size_t key, * \param[out] val Optional. Pointer to store value. * \return ARES_TRUE on success, ARES_FALSE on failure */ -ares_bool_t ares__htable_stvp_get(const ares__htable_stvp_t *htable, size_t key, +ares_bool_t ares__htable_szvp_get(const ares__htable_szvp_t *htable, size_t key, void **val); /*! Retrieve value from hashtable directly as return value. Caveat to this - * function over ares__htable_stvp_get() is that if a NULL value is stored + * function over ares__htable_szvp_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_stvp_get_direct(const ares__htable_stvp_t *htable, +void *ares__htable_szvp_get_direct(const ares__htable_szvp_t *htable, size_t key); /*! Remove a value from the hashtable by key @@ -103,14 +103,14 @@ void *ares__htable_stvp_get_direct(const ares__htable_stvp_t *htable, * \param[in] key key to use to search * \return ARES_TRUE if found, ARES_FALSE if not */ -ares_bool_t ares__htable_stvp_remove(ares__htable_stvp_t *htable, size_t key); +ares_bool_t ares__htable_szvp_remove(ares__htable_szvp_t *htable, size_t key); /*! Retrieve the number of keys stored in the hash table * * \param[in] htable Initialized hash table * \return count */ -size_t ares__htable_stvp_num_keys(const ares__htable_stvp_t *htable); +size_t ares__htable_szvp_num_keys(const ares__htable_szvp_t *htable); /*! @} */ diff --git a/src/lib/ares__socket.c b/src/lib/ares__socket.c new file mode 100644 index 00000000..a986c523 --- /dev/null +++ b/src/lib/ares__socket.c @@ -0,0 +1,463 @@ +/* MIT License + * + * Copyright (c) Massachusetts Institute of Technology + * Copyright (c) The c-ares project and its contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ + #include "ares_setup.h" + +#ifdef HAVE_SYS_UIO_H +# include +#endif +#ifdef HAVE_NETINET_IN_H +# include +#endif +#ifdef HAVE_NETINET_TCP_H +# include +#endif +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif + +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_SYS_IOCTL_H +# include +#endif +#ifdef NETWARE +# include +#endif + +#include +#include +#include + +#include "ares.h" +#include "ares_private.h" + +ares_ssize_t ares__socket_recvfrom(ares_channel channel, ares_socket_t s, + void *data, size_t data_len, int flags, + struct sockaddr *from, + ares_socklen_t *from_len) +{ + if (channel->sock_funcs && channel->sock_funcs->arecvfrom) { + return channel->sock_funcs->arecvfrom(s, data, data_len, flags, from, + from_len, channel->sock_func_cb_data); + } + +#ifdef HAVE_RECVFROM + return recvfrom(s, data, data_len, flags, from, from_len); +#else + return sread(s, data, data_len); +#endif +} + +ares_ssize_t ares__socket_recv(ares_channel channel, ares_socket_t s, + void *data, size_t data_len) +{ + if (channel->sock_funcs && channel->sock_funcs->arecvfrom) { + return channel->sock_funcs->arecvfrom(s, data, data_len, 0, 0, 0, + channel->sock_func_cb_data); + } + + return sread(s, data, data_len); +} + +/* + * setsocknonblock sets the given socket to either blocking or non-blocking + * mode based on the 'nonblock' boolean argument. This function is highly + * portable. + */ +static int setsocknonblock(ares_socket_t sockfd, /* operate on this */ + int nonblock /* TRUE or FALSE */) +{ +#if defined(USE_BLOCKING_SOCKETS) + + return 0; /* returns success */ + +#elif defined(HAVE_FCNTL_O_NONBLOCK) + + /* most recent unix versions */ + int flags; + flags = fcntl(sockfd, F_GETFL, 0); + if (FALSE != nonblock) { + return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); + } else { + return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); /* LCOV_EXCL_LINE */ + } + +#elif defined(HAVE_IOCTL_FIONBIO) + + /* older unix versions */ + int flags = nonblock ? 1 : 0; + return ioctl(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_FIONBIO) + +# ifdef WATT32 + char flags = nonblock ? 1 : 0; +# else + /* Windows */ + unsigned long flags = nonblock ? 1UL : 0UL; +# endif + return ioctlsocket(sockfd, FIONBIO, &flags); + +#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) + + /* Amiga */ + long flags = nonblock ? 1L : 0L; + return IoctlSocket(sockfd, FIONBIO, flags); + +#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) + + /* BeOS */ + long b = nonblock ? 1L : 0L; + return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); + +#else +# error "no non-blocking method was found/used/set" +#endif +} + +#if defined(IPV6_V6ONLY) && defined(WIN32) +/* It makes support for IPv4-mapped IPv6 addresses. + * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; + * Windows Vista and later: default is on; + * DragonFly BSD: acts like off, and dummy setting; + * OpenBSD and earlier Windows: unsupported. + * Linux: controlled by /proc/sys/net/ipv6/bindv6only. + */ +static void set_ipv6_v6only(ares_socket_t sockfd, int on) +{ + (void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); +} +#else +# define set_ipv6_v6only(s, v) +#endif + +static int configure_socket(ares_socket_t s, int family, ares_channel channel) +{ + union { + struct sockaddr sa; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + } local; + + /* do not set options for user-managed sockets */ + if (channel->sock_funcs && channel->sock_funcs->asocket) { + return 0; + } + + (void)setsocknonblock(s, TRUE); + +#if defined(FD_CLOEXEC) && !defined(MSDOS) + /* Configure the socket fd as close-on-exec. */ + if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { + return -1; /* LCOV_EXCL_LINE */ + } +#endif + + /* Set the socket's send and receive buffer sizes. */ + if ((channel->socket_send_buffer_size > 0) && + setsockopt(s, SOL_SOCKET, SO_SNDBUF, + (void *)&channel->socket_send_buffer_size, + sizeof(channel->socket_send_buffer_size)) == -1) { + return -1; + } + + if ((channel->socket_receive_buffer_size > 0) && + setsockopt(s, SOL_SOCKET, SO_RCVBUF, + (void *)&channel->socket_receive_buffer_size, + sizeof(channel->socket_receive_buffer_size)) == -1) { + return -1; + } + +#ifdef SO_BINDTODEVICE + if (channel->local_dev_name[0]) { + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name, + sizeof(channel->local_dev_name))) { + /* Only root can do this, and usually not fatal if it doesn't work, so */ + /* just continue on. */ + } + } +#endif + + if (family == AF_INET) { + if (channel->local_ip4) { + memset(&local.sa4, 0, sizeof(local.sa4)); + local.sa4.sin_family = AF_INET; + local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); + if (bind(s, &local.sa, sizeof(local.sa4)) < 0) { + return -1; + } + } + } else if (family == AF_INET6) { + if (memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8, + sizeof(channel->local_ip6)) != 0) { + memset(&local.sa6, 0, sizeof(local.sa6)); + local.sa6.sin6_family = AF_INET6; + memcpy(&local.sa6.sin6_addr, channel->local_ip6, + sizeof(channel->local_ip6)); + if (bind(s, &local.sa, sizeof(local.sa6)) < 0) { + return -1; + } + } + set_ipv6_v6only(s, 0); + } + + return 0; +} + +ares_status_t ares__open_connection(ares_channel channel, + struct server_state *server, + ares_bool_t is_tcp) +{ + ares_socket_t s; + int opt; + ares_socklen_t salen; + + union { + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + } saddr; + struct sockaddr *sa; + unsigned short port; + struct server_connection *conn; + ares__llist_node_t *node; + int type = is_tcp ? SOCK_STREAM : SOCK_DGRAM; + + if (is_tcp) { + port = server->addr.tcp_port ? server->addr.tcp_port : channel->tcp_port; + } else { + port = server->addr.udp_port ? server->addr.udp_port : channel->udp_port; + } + + switch (server->addr.family) { + case AF_INET: + sa = (void *)&saddr.sa4; + salen = sizeof(saddr.sa4); + memset(sa, 0, salen); + saddr.sa4.sin_family = AF_INET; + saddr.sa4.sin_port = port; + memcpy(&saddr.sa4.sin_addr, &server->addr.addrV4, + sizeof(server->addr.addrV4)); + break; + case AF_INET6: + sa = (void *)&saddr.sa6; + salen = sizeof(saddr.sa6); + memset(sa, 0, salen); + saddr.sa6.sin6_family = AF_INET6; + saddr.sa6.sin6_port = port; + memcpy(&saddr.sa6.sin6_addr, &server->addr.addrV6, + sizeof(server->addr.addrV6)); + break; + default: + return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */ + } + + /* Acquire a socket. */ + s = ares__open_socket(channel, server->addr.family, type, 0); + if (s == ARES_SOCKET_BAD) { + return ARES_ECONNREFUSED; + } + + /* Configure it. */ + if (configure_socket(s, server->addr.family, channel) < 0) { + ares__close_socket(channel, s); + return ARES_ECONNREFUSED; + } + +#ifdef TCP_NODELAY + if (is_tcp) { + /* + * Disable the Nagle algorithm (only relevant for TCP sockets, and thus not + * in configure_socket). In general, in DNS lookups we're pretty much + * interested in firing off a single request and then waiting for a reply, + * so batching isn't very interesting. + */ + opt = 1; + if (!channel->sock_funcs || !channel->sock_funcs->asocket) { + if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) == + -1) { + ares__close_socket(channel, s); + return ARES_ECONNREFUSED; + } + } + } +#endif + + if (channel->sock_config_cb) { + int err = channel->sock_config_cb(s, type, channel->sock_config_cb_data); + if (err < 0) { + ares__close_socket(channel, s); + return ARES_ECONNREFUSED; + } + } + + /* Connect to the server. */ + if (ares__connect_socket(channel, s, sa, salen) == -1) { + int err = SOCKERRNO; + + if (err != EINPROGRESS && err != EWOULDBLOCK) { + ares__close_socket(channel, s); + return ARES_ECONNREFUSED; + } + } + + if (channel->sock_create_cb) { + int err = channel->sock_create_cb(s, type, channel->sock_create_cb_data); + if (err < 0) { + ares__close_socket(channel, s); + return ARES_ECONNREFUSED; + } + } + + conn = ares_malloc(sizeof(*conn)); + if (conn == NULL) { + ares__close_socket(channel, s); + return ARES_ENOMEM; + } + memset(conn, 0, sizeof(*conn)); + conn->fd = s; + conn->server = server; + conn->queries_to_conn = ares__llist_create(NULL); + conn->is_tcp = is_tcp; + if (conn->queries_to_conn == NULL) { + ares__close_socket(channel, s); + ares_free(conn); + return ARES_ENOMEM; + } + + /* TCP connections are thrown to the end as we don't spawn multiple TCP + * connections. UDP connections are put on front where the newest connection + * can be quickly pulled */ + if (is_tcp) { + node = ares__llist_insert_last(server->connections, conn); + } else { + node = ares__llist_insert_first(server->connections, conn); + } + if (node == NULL) { + ares__close_socket(channel, s); + ares__llist_destroy(conn->queries_to_conn); + ares_free(conn); + return ARES_ENOMEM; + } + + /* Register globally to quickly map event on file descriptor to connection + * node object */ + if (!ares__htable_asvp_insert(channel->connnode_by_socket, s, node)) { + ares__close_socket(channel, s); + ares__llist_destroy(conn->queries_to_conn); + ares__llist_node_claim(node); + ares_free(conn); + return ARES_ENOMEM; + } + + SOCK_STATE_CALLBACK(channel, s, 1, 0); + + if (is_tcp) { + server->tcp_connection_generation = ++channel->tcp_connection_generation; + server->tcp_conn = conn; + } + + return ARES_SUCCESS; +} + +ares_socket_t ares__open_socket(ares_channel channel, int af, int type, + int protocol) +{ + if (channel->sock_funcs && channel->sock_funcs->asocket) { + return channel->sock_funcs->asocket(af, type, protocol, + channel->sock_func_cb_data); + } + + return socket(af, type, protocol); +} + +int ares__connect_socket(ares_channel channel, ares_socket_t sockfd, + const struct sockaddr *addr, ares_socklen_t addrlen) +{ + if (channel->sock_funcs && channel->sock_funcs->aconnect) { + return channel->sock_funcs->aconnect(sockfd, addr, addrlen, + channel->sock_func_cb_data); + } + + return connect(sockfd, addr, addrlen); +} + +void ares__close_socket(ares_channel channel, ares_socket_t s) +{ + if (channel->sock_funcs && channel->sock_funcs->aclose) { + channel->sock_funcs->aclose(s, channel->sock_func_cb_data); + } else { + sclose(s); + } +} + +#ifndef HAVE_WRITEV +/* Structure for scatter/gather I/O. */ +struct iovec { + void *iov_base; /* Pointer to data. */ + size_t iov_len; /* Length of data. */ +}; +#endif + +ares_ssize_t ares__socket_write(ares_channel channel, ares_socket_t s, + const void *data, size_t len) +{ + if (channel->sock_funcs && channel->sock_funcs->asendv) { + struct iovec vec; + vec.iov_base = (void *)data; + vec.iov_len = len; + return channel->sock_funcs->asendv(s, &vec, 1, channel->sock_func_cb_data); + } + return swrite(s, data, len); +} + +void ares_set_socket_callback(ares_channel channel, + ares_sock_create_callback cb, void *data) +{ + channel->sock_create_cb = cb; + channel->sock_create_cb_data = data; +} + +void ares_set_socket_configure_callback(ares_channel channel, + ares_sock_config_callback cb, + void *data) +{ + channel->sock_config_cb = cb; + channel->sock_config_cb_data = data; +} + +void ares_set_socket_functions(ares_channel channel, + const struct ares_socket_functions *funcs, + void *data) +{ + channel->sock_funcs = funcs; + channel->sock_func_cb_data = data; +} + diff --git a/src/lib/ares_destroy.c b/src/lib/ares_destroy.c index e05ea05e..be38ad12 100644 --- a/src/lib/ares_destroy.c +++ b/src/lib/ares_destroy.c @@ -87,7 +87,7 @@ void ares_destroy(ares_channel channel) * so all query lists should be empty now. */ assert(ares__llist_len(channel->all_queries) == 0); - assert(ares__htable_stvp_num_keys(channel->queries_by_qid) == 0); + assert(ares__htable_szvp_num_keys(channel->queries_by_qid) == 0); assert(ares__slist_len(channel->queries_by_timeout) == 0); #endif @@ -106,7 +106,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_szvp_destroy(channel->queries_by_qid); ares__htable_asvp_destroy(channel->connnode_by_socket); if (channel->sortlist) { diff --git a/src/lib/ares_getaddrinfo.c b/src/lib/ares_getaddrinfo.c index 631b7380..225d3736 100644 --- a/src/lib/ares_getaddrinfo.c +++ b/src/lib/ares_getaddrinfo.c @@ -570,7 +570,7 @@ static void terminate_retries(struct host_query *hquery, unsigned short qid) return; } - query = ares__htable_stvp_get_direct(channel->queries_by_qid, term_qid); + query = ares__htable_szvp_get_direct(channel->queries_by_qid, term_qid); if (query == NULL) { return; } diff --git a/src/lib/ares_init.c b/src/lib/ares_init.c index 17656ce8..6e055b0a 100644 --- a/src/lib/ares_init.c +++ b/src/lib/ares_init.c @@ -71,9 +71,6 @@ #endif -static ares_status_t init_by_options(ares_channel channel, - const struct ares_options *options, - int optmask); static ares_status_t init_by_environment(ares_channel channel); static ares_status_t init_by_resolv_conf(ares_channel channel); static ares_status_t init_by_defaults(ares_channel channel); @@ -101,9 +98,7 @@ static ares_status_t config_lookup(ares_channel channel, const char *str, static char *try_config(char *s, const char *opt, char scc); #endif -#define ARES_CONFIG_CHECK(x) \ - (x->lookups && x->nservers > 0 && x->ndots > 0 && x->timeout > 0 && \ - x->tries > 0) + int ares_init(ares_channel *channelptr) { @@ -167,7 +162,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, goto done; } - channel->queries_by_qid = ares__htable_stvp_create(NULL); + channel->queries_by_qid = ares__htable_szvp_create(NULL); if (channel->queries_by_qid == NULL) { status = ARES_ENOMEM; goto done; @@ -190,7 +185,7 @@ int ares_init_options(ares_channel *channelptr, struct ares_options *options, * precedence to lowest. */ - status = init_by_options(channel, options, optmask); + status = ares__init_by_options(channel, options, optmask); if (status != ARES_SUCCESS) { DEBUGF(fprintf(stderr, "Error: init_by_options failed: %s\n", ares_strerror(status))); @@ -256,7 +251,7 @@ done: ares__destroy_rand_state(channel->rand_state); } - ares__htable_stvp_destroy(channel->queries_by_qid); + ares__htable_szvp_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); @@ -341,286 +336,6 @@ int ares_dup(ares_channel *dest, ares_channel src) return ARES_SUCCESS; /* everything went fine */ } -/* Save options from initialized channel */ -int ares_save_options(ares_channel channel, struct ares_options *options, - int *optmask) -{ - size_t i; - size_t j; - size_t ipv4_nservers = 0; - - /* Zero everything out */ - memset(options, 0, sizeof(struct ares_options)); - - if (!ARES_CONFIG_CHECK(channel)) { - return ARES_ENODATA; - } - - /* Traditionally the optmask wasn't saved in the channel struct so it was - recreated here. ROTATE is the first option that has no struct field of - its own in the public config struct */ - (*optmask) = (ARES_OPT_FLAGS | ARES_OPT_TRIES | ARES_OPT_NDOTS | - ARES_OPT_UDP_PORT | ARES_OPT_TCP_PORT | ARES_OPT_SOCK_STATE_CB | - ARES_OPT_SERVERS | ARES_OPT_DOMAINS | ARES_OPT_LOOKUPS | - ARES_OPT_SORTLIST | ARES_OPT_TIMEOUTMS); - (*optmask) |= (channel->rotate ? ARES_OPT_ROTATE : ARES_OPT_NOROTATE); - - if (channel->resolvconf_path) { - (*optmask) |= ARES_OPT_RESOLVCONF; - } - - if (channel->hosts_path) { - (*optmask) |= ARES_OPT_HOSTS_FILE; - } - - /* Copy easy stuff */ - options->flags = (int)channel->flags; - - /* We return full millisecond resolution but that's only because we don't - set the ARES_OPT_TIMEOUT anymore, only the new ARES_OPT_TIMEOUTMS */ - options->timeout = (int)channel->timeout; - options->tries = (int)channel->tries; - options->ndots = (int)channel->ndots; - options->udp_port = ntohs(channel->udp_port); - options->tcp_port = ntohs(channel->tcp_port); - options->sock_state_cb = channel->sock_state_cb; - options->sock_state_cb_data = channel->sock_state_cb_data; - - /* Copy IPv4 servers that use the default port */ - if (channel->nservers) { - for (i = 0; i < channel->nservers; i++) { - if ((channel->servers[i].addr.family == AF_INET) && - (channel->servers[i].addr.udp_port == 0) && - (channel->servers[i].addr.tcp_port == 0)) { - ipv4_nservers++; - } - } - if (ipv4_nservers) { - options->servers = ares_malloc(ipv4_nservers * sizeof(struct in_addr)); - if (!options->servers) { - return ARES_ENOMEM; - } - - for (i = j = 0; i < channel->nservers; i++) { - if ((channel->servers[i].addr.family == AF_INET) && - (channel->servers[i].addr.udp_port == 0) && - (channel->servers[i].addr.tcp_port == 0)) { - memcpy(&options->servers[j++], &channel->servers[i].addr.addrV4, - sizeof(channel->servers[i].addr.addrV4)); - } - } - } - } - options->nservers = (int)ipv4_nservers; - - /* copy domains */ - if (channel->ndomains) { - options->domains = ares_malloc(channel->ndomains * sizeof(char *)); - if (!options->domains) { - return ARES_ENOMEM; - } - - for (i = 0; i < channel->ndomains; i++) { - options->domains[i] = ares_strdup(channel->domains[i]); - if (!options->domains[i]) { - options->ndomains = (int)i; - return ARES_ENOMEM; - } - } - } - options->ndomains = (int)channel->ndomains; - - /* copy lookups */ - if (channel->lookups) { - options->lookups = ares_strdup(channel->lookups); - if (!options->lookups && channel->lookups) { - return ARES_ENOMEM; - } - } - - /* copy sortlist */ - if (channel->nsort) { - options->sortlist = ares_malloc(channel->nsort * sizeof(struct apattern)); - if (!options->sortlist) { - return ARES_ENOMEM; - } - for (i = 0; i < channel->nsort; i++) { - options->sortlist[i] = channel->sortlist[i]; - } - } - options->nsort = (int)channel->nsort; - - /* copy path for resolv.conf file */ - if (channel->resolvconf_path) { - options->resolvconf_path = ares_strdup(channel->resolvconf_path); - if (!options->resolvconf_path) { - return ARES_ENOMEM; - } - } - - /* copy path for hosts file */ - if (channel->hosts_path) { - options->hosts_path = ares_strdup(channel->hosts_path); - if (!options->hosts_path) { - return ARES_ENOMEM; - } - } - - if (channel->udp_max_queries > 0) { - (*optmask) |= ARES_OPT_UDP_MAX_QUERIES; - options->udp_max_queries = (int)channel->udp_max_queries; - } - - return ARES_SUCCESS; -} - -static ares_status_t init_by_options(ares_channel channel, - const struct ares_options *options, - int optmask) -{ - size_t i; - - /* Easy stuff. */ - if (optmask & ARES_OPT_FLAGS) { - channel->flags = (unsigned int)options->flags; - } - - if (optmask & ARES_OPT_TIMEOUTMS) { - channel->timeout = (unsigned int)options->timeout; - } else if (optmask & ARES_OPT_TIMEOUT) { - channel->timeout = (unsigned int)options->timeout * 1000; - } - - if (optmask & ARES_OPT_TRIES) { - channel->tries = (size_t)options->tries; - } - - if (optmask & ARES_OPT_NDOTS) { - channel->ndots = (size_t)options->ndots; - } - - if (optmask & ARES_OPT_ROTATE) { - channel->rotate = ARES_TRUE; - } - - if (optmask & ARES_OPT_NOROTATE) { - channel->rotate = ARES_FALSE; - } - - if ((optmask & ARES_OPT_UDP_PORT) && channel->udp_port == 0) { - channel->udp_port = htons(options->udp_port); - } - - if ((optmask & ARES_OPT_TCP_PORT) && channel->tcp_port == 0) { - channel->tcp_port = htons(options->tcp_port); - } - - if ((optmask & ARES_OPT_SOCK_STATE_CB) && channel->sock_state_cb == NULL) { - channel->sock_state_cb = options->sock_state_cb; - channel->sock_state_cb_data = options->sock_state_cb_data; - } - - if (optmask & ARES_OPT_SOCK_SNDBUF && options->socket_send_buffer_size > 0) { - channel->socket_send_buffer_size = options->socket_send_buffer_size; - } - - if (optmask & ARES_OPT_SOCK_RCVBUF && - channel->socket_receive_buffer_size > 0) { - channel->socket_receive_buffer_size = options->socket_receive_buffer_size; - } - - if (optmask & ARES_OPT_EDNSPSZ) { - channel->ednspsz = (size_t)options->ednspsz; - } - - /* Copy the IPv4 servers, if given. */ - if (optmask & ARES_OPT_SERVERS) { - /* Avoid zero size allocations at any cost */ - if (options->nservers > 0) { - channel->servers = - ares_malloc((size_t)options->nservers * sizeof(*channel->servers)); - if (!channel->servers) { - return ARES_ENOMEM; - } - memset(channel->servers, 0, - (size_t)options->nservers * sizeof(*channel->servers)); - for (i = 0; i < (size_t)options->nservers; i++) { - channel->servers[i].addr.family = AF_INET; - channel->servers[i].addr.udp_port = 0; - channel->servers[i].addr.tcp_port = 0; - memcpy(&channel->servers[i].addr.addrV4, &options->servers[i], - sizeof(channel->servers[i].addr.addrV4)); - } - } - channel->nservers = (size_t)options->nservers; - } - - /* Copy the domains, if given. Keep channel->ndomains consistent so - * we can clean up in case of error. - */ - if (optmask & ARES_OPT_DOMAINS) { - /* Avoid zero size allocations at any cost */ - if (options->ndomains > 0) { - channel->domains = - ares_malloc((size_t)options->ndomains * sizeof(char *)); - if (!channel->domains) { - return ARES_ENOMEM; - } - for (i = 0; i < (size_t)options->ndomains; i++) { - channel->domains[i] = ares_strdup(options->domains[i]); - if (!channel->domains[i]) { - return ARES_ENOMEM; - } - } - } - channel->ndomains = (size_t)options->ndomains; - } - - /* Set lookups, if given. */ - if ((optmask & ARES_OPT_LOOKUPS) && !channel->lookups) { - channel->lookups = ares_strdup(options->lookups); - if (!channel->lookups) { - return ARES_ENOMEM; - } - } - - /* copy sortlist */ - if (optmask & ARES_OPT_SORTLIST && options->nsort > 0) { - channel->nsort = (size_t)options->nsort; - channel->sortlist = - ares_malloc((size_t)options->nsort * sizeof(struct apattern)); - if (!channel->sortlist) { - return ARES_ENOMEM; - } - for (i = 0; i < (size_t)options->nsort; i++) { - channel->sortlist[i] = options->sortlist[i]; - } - } - - /* Set path for resolv.conf file, if given. */ - if ((optmask & ARES_OPT_RESOLVCONF) && !channel->resolvconf_path) { - channel->resolvconf_path = ares_strdup(options->resolvconf_path); - if (!channel->resolvconf_path && options->resolvconf_path) { - return ARES_ENOMEM; - } - } - - /* Set path for hosts file, if given. */ - if ((optmask & ARES_OPT_HOSTS_FILE) && !channel->hosts_path) { - channel->hosts_path = ares_strdup(options->hosts_path); - if (!channel->hosts_path && options->hosts_path) { - return ARES_ENOMEM; - } - } - - if (optmask & ARES_OPT_UDP_MAX_QUERIES) { - channel->udp_max_queries = (size_t)options->udp_max_queries; - } - - channel->optmask = (unsigned int)optmask; - - return ARES_SUCCESS; -} static ares_status_t init_by_environment(ares_channel channel) { @@ -2456,28 +2171,6 @@ void ares_set_local_dev(ares_channel channel, const char *local_dev_name) channel->local_dev_name[sizeof(channel->local_dev_name) - 1] = 0; } -void ares_set_socket_callback(ares_channel channel, - ares_sock_create_callback cb, void *data) -{ - channel->sock_create_cb = cb; - channel->sock_create_cb_data = data; -} - -void ares_set_socket_configure_callback(ares_channel channel, - ares_sock_config_callback cb, - void *data) -{ - channel->sock_config_cb = cb; - channel->sock_config_cb_data = data; -} - -void ares_set_socket_functions(ares_channel channel, - const struct ares_socket_functions *funcs, - void *data) -{ - channel->sock_funcs = funcs; - channel->sock_func_cb_data = data; -} int ares_set_sortlist(ares_channel channel, const char *sortstr) { diff --git a/src/lib/ares_options.c b/src/lib/ares_options.c index c2a36434..3666ef48 100644 --- a/src/lib/ares_options.c +++ b/src/lib/ares_options.c @@ -403,3 +403,284 @@ int ares_set_servers_ports_csv(ares_channel channel, const char *_csv) { return (int)set_servers_csv(channel, _csv, TRUE); } + +/* Save options from initialized channel */ +int ares_save_options(ares_channel channel, struct ares_options *options, + int *optmask) +{ + size_t i; + size_t j; + size_t ipv4_nservers = 0; + + /* Zero everything out */ + memset(options, 0, sizeof(struct ares_options)); + + if (!ARES_CONFIG_CHECK(channel)) { + return ARES_ENODATA; + } + + /* Traditionally the optmask wasn't saved in the channel struct so it was + recreated here. ROTATE is the first option that has no struct field of + its own in the public config struct */ + (*optmask) = (ARES_OPT_FLAGS | ARES_OPT_TRIES | ARES_OPT_NDOTS | + ARES_OPT_UDP_PORT | ARES_OPT_TCP_PORT | ARES_OPT_SOCK_STATE_CB | + ARES_OPT_SERVERS | ARES_OPT_DOMAINS | ARES_OPT_LOOKUPS | + ARES_OPT_SORTLIST | ARES_OPT_TIMEOUTMS); + (*optmask) |= (channel->rotate ? ARES_OPT_ROTATE : ARES_OPT_NOROTATE); + + if (channel->resolvconf_path) { + (*optmask) |= ARES_OPT_RESOLVCONF; + } + + if (channel->hosts_path) { + (*optmask) |= ARES_OPT_HOSTS_FILE; + } + + /* Copy easy stuff */ + options->flags = (int)channel->flags; + + /* We return full millisecond resolution but that's only because we don't + set the ARES_OPT_TIMEOUT anymore, only the new ARES_OPT_TIMEOUTMS */ + options->timeout = (int)channel->timeout; + options->tries = (int)channel->tries; + options->ndots = (int)channel->ndots; + options->udp_port = ntohs(channel->udp_port); + options->tcp_port = ntohs(channel->tcp_port); + options->sock_state_cb = channel->sock_state_cb; + options->sock_state_cb_data = channel->sock_state_cb_data; + + /* Copy IPv4 servers that use the default port */ + if (channel->nservers) { + for (i = 0; i < channel->nservers; i++) { + if ((channel->servers[i].addr.family == AF_INET) && + (channel->servers[i].addr.udp_port == 0) && + (channel->servers[i].addr.tcp_port == 0)) { + ipv4_nservers++; + } + } + if (ipv4_nservers) { + options->servers = ares_malloc(ipv4_nservers * sizeof(struct in_addr)); + if (!options->servers) { + return ARES_ENOMEM; + } + + for (i = j = 0; i < channel->nservers; i++) { + if ((channel->servers[i].addr.family == AF_INET) && + (channel->servers[i].addr.udp_port == 0) && + (channel->servers[i].addr.tcp_port == 0)) { + memcpy(&options->servers[j++], &channel->servers[i].addr.addrV4, + sizeof(channel->servers[i].addr.addrV4)); + } + } + } + } + options->nservers = (int)ipv4_nservers; + + /* copy domains */ + if (channel->ndomains) { + options->domains = ares_malloc(channel->ndomains * sizeof(char *)); + if (!options->domains) { + return ARES_ENOMEM; + } + + for (i = 0; i < channel->ndomains; i++) { + options->domains[i] = ares_strdup(channel->domains[i]); + if (!options->domains[i]) { + options->ndomains = (int)i; + return ARES_ENOMEM; + } + } + } + options->ndomains = (int)channel->ndomains; + + /* copy lookups */ + if (channel->lookups) { + options->lookups = ares_strdup(channel->lookups); + if (!options->lookups && channel->lookups) { + return ARES_ENOMEM; + } + } + + /* copy sortlist */ + if (channel->nsort) { + options->sortlist = ares_malloc(channel->nsort * sizeof(struct apattern)); + if (!options->sortlist) { + return ARES_ENOMEM; + } + for (i = 0; i < channel->nsort; i++) { + options->sortlist[i] = channel->sortlist[i]; + } + } + options->nsort = (int)channel->nsort; + + /* copy path for resolv.conf file */ + if (channel->resolvconf_path) { + options->resolvconf_path = ares_strdup(channel->resolvconf_path); + if (!options->resolvconf_path) { + return ARES_ENOMEM; + } + } + + /* copy path for hosts file */ + if (channel->hosts_path) { + options->hosts_path = ares_strdup(channel->hosts_path); + if (!options->hosts_path) { + return ARES_ENOMEM; + } + } + + if (channel->udp_max_queries > 0) { + (*optmask) |= ARES_OPT_UDP_MAX_QUERIES; + options->udp_max_queries = (int)channel->udp_max_queries; + } + + return ARES_SUCCESS; +} + +ares_status_t ares__init_by_options(ares_channel channel, + const struct ares_options *options, + int optmask) +{ + size_t i; + + /* Easy stuff. */ + if (optmask & ARES_OPT_FLAGS) { + channel->flags = (unsigned int)options->flags; + } + + if (optmask & ARES_OPT_TIMEOUTMS) { + channel->timeout = (unsigned int)options->timeout; + } else if (optmask & ARES_OPT_TIMEOUT) { + channel->timeout = (unsigned int)options->timeout * 1000; + } + + if (optmask & ARES_OPT_TRIES) { + channel->tries = (size_t)options->tries; + } + + if (optmask & ARES_OPT_NDOTS) { + channel->ndots = (size_t)options->ndots; + } + + if (optmask & ARES_OPT_ROTATE) { + channel->rotate = ARES_TRUE; + } + + if (optmask & ARES_OPT_NOROTATE) { + channel->rotate = ARES_FALSE; + } + + if ((optmask & ARES_OPT_UDP_PORT) && channel->udp_port == 0) { + channel->udp_port = htons(options->udp_port); + } + + if ((optmask & ARES_OPT_TCP_PORT) && channel->tcp_port == 0) { + channel->tcp_port = htons(options->tcp_port); + } + + if ((optmask & ARES_OPT_SOCK_STATE_CB) && channel->sock_state_cb == NULL) { + channel->sock_state_cb = options->sock_state_cb; + channel->sock_state_cb_data = options->sock_state_cb_data; + } + + if (optmask & ARES_OPT_SOCK_SNDBUF && options->socket_send_buffer_size > 0) { + channel->socket_send_buffer_size = options->socket_send_buffer_size; + } + + if (optmask & ARES_OPT_SOCK_RCVBUF && + channel->socket_receive_buffer_size > 0) { + channel->socket_receive_buffer_size = options->socket_receive_buffer_size; + } + + if (optmask & ARES_OPT_EDNSPSZ) { + channel->ednspsz = (size_t)options->ednspsz; + } + + /* Copy the IPv4 servers, if given. */ + if (optmask & ARES_OPT_SERVERS) { + /* Avoid zero size allocations at any cost */ + if (options->nservers > 0) { + channel->servers = + ares_malloc((size_t)options->nservers * sizeof(*channel->servers)); + if (!channel->servers) { + return ARES_ENOMEM; + } + memset(channel->servers, 0, + (size_t)options->nservers * sizeof(*channel->servers)); + for (i = 0; i < (size_t)options->nservers; i++) { + channel->servers[i].addr.family = AF_INET; + channel->servers[i].addr.udp_port = 0; + channel->servers[i].addr.tcp_port = 0; + memcpy(&channel->servers[i].addr.addrV4, &options->servers[i], + sizeof(channel->servers[i].addr.addrV4)); + } + } + channel->nservers = (size_t)options->nservers; + } + + /* Copy the domains, if given. Keep channel->ndomains consistent so + * we can clean up in case of error. + */ + if (optmask & ARES_OPT_DOMAINS) { + /* Avoid zero size allocations at any cost */ + if (options->ndomains > 0) { + channel->domains = + ares_malloc((size_t)options->ndomains * sizeof(char *)); + if (!channel->domains) { + return ARES_ENOMEM; + } + for (i = 0; i < (size_t)options->ndomains; i++) { + channel->domains[i] = ares_strdup(options->domains[i]); + if (!channel->domains[i]) { + return ARES_ENOMEM; + } + } + } + channel->ndomains = (size_t)options->ndomains; + } + + /* Set lookups, if given. */ + if ((optmask & ARES_OPT_LOOKUPS) && !channel->lookups) { + channel->lookups = ares_strdup(options->lookups); + if (!channel->lookups) { + return ARES_ENOMEM; + } + } + + /* copy sortlist */ + if (optmask & ARES_OPT_SORTLIST && options->nsort > 0) { + channel->nsort = (size_t)options->nsort; + channel->sortlist = + ares_malloc((size_t)options->nsort * sizeof(struct apattern)); + if (!channel->sortlist) { + return ARES_ENOMEM; + } + for (i = 0; i < (size_t)options->nsort; i++) { + channel->sortlist[i] = options->sortlist[i]; + } + } + + /* Set path for resolv.conf file, if given. */ + if ((optmask & ARES_OPT_RESOLVCONF) && !channel->resolvconf_path) { + channel->resolvconf_path = ares_strdup(options->resolvconf_path); + if (!channel->resolvconf_path && options->resolvconf_path) { + return ARES_ENOMEM; + } + } + + /* Set path for hosts file, if given. */ + if ((optmask & ARES_OPT_HOSTS_FILE) && !channel->hosts_path) { + channel->hosts_path = ares_strdup(options->hosts_path); + if (!channel->hosts_path && options->hosts_path) { + return ARES_ENOMEM; + } + } + + if (optmask & ARES_OPT_UDP_MAX_QUERIES) { + channel->udp_max_queries = (size_t)options->udp_max_queries; + } + + channel->optmask = (unsigned int)optmask; + + return ARES_SUCCESS; +} diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h index e5baa3cf..7cb834bf 100644 --- a/src/lib/ares_private.h +++ b/src/lib/ares_private.h @@ -117,7 +117,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_szvp.h" #include "ares__htable_asvp.h" #include "ares__buf.h" #include "ares_dns_record.h" @@ -309,7 +309,7 @@ struct ares_channeldata { /* All active queries in a single list */ ares__llist_t *all_queries; /* Queries bucketed by qid, for quickly dispatching DNS responses: */ - ares__htable_stvp_t *queries_by_qid; + ares__htable_szvp_t *queries_by_qid; /* Queries bucketed by timeout, for quickly handling timeouts: */ ares__slist_t *queries_by_timeout; @@ -395,6 +395,9 @@ ares_status_t ares_expand_string_ex(const unsigned char *encoded, const unsigned char *abuf, size_t alen, unsigned char **s, size_t *enclen); ares_status_t ares__init_servers_state(ares_channel channel); +ares_status_t ares__init_by_options(ares_channel channel, + const struct ares_options *options, + int optmask); void ares__destroy_servers_state(ares_channel channel); ares_status_t ares__single_domain(ares_channel channel, const char *name, char **s); @@ -440,12 +443,23 @@ ares_status_t ares__addrinfo2addrttl(const struct ares_addrinfo *ai, int family, ares_status_t ares__addrinfo_localhost(const char *name, unsigned short port, const struct ares_addrinfo_hints *hints, struct ares_addrinfo *ai); - +ares_status_t ares__open_connection(ares_channel channel, + struct server_state *server, + ares_bool_t is_tcp); ares_socket_t ares__open_socket(ares_channel channel, int af, int type, int protocol); +ares_ssize_t ares__socket_write(ares_channel channel, ares_socket_t s, + const void *data, size_t len); +ares_ssize_t ares__socket_recvfrom(ares_channel channel, ares_socket_t s, + void *data, size_t data_len, int flags, + struct sockaddr *from, + ares_socklen_t *from_len); +ares_ssize_t ares__socket_recv(ares_channel channel, ares_socket_t s, + void *data, size_t data_len); void ares__close_socket(ares_channel, ares_socket_t); int ares__connect_socket(ares_channel channel, ares_socket_t sockfd, - const struct sockaddr *addr, ares_socklen_t addrlen); + const struct sockaddr *addr, + ares_socklen_t addrlen); #define ARES_SWAP_BYTE(a, b) \ do { \ @@ -461,6 +475,9 @@ int ares__connect_socket(ares_channel channel, ares_socket_t sockfd, } \ } while (0) +#define ARES_CONFIG_CHECK(x) \ + (x->lookups && x->nservers > 0 && x->ndots > 0 && x->timeout > 0 && \ + x->tries > 0) size_t ares__round_up_pow2(size_t n); size_t ares__log2(size_t n); diff --git a/src/lib/ares_process.c b/src/lib/ares_process.c index 65ae8654..9a0e0acc 100644 --- a/src/lib/ares_process.c +++ b/src/lib/ares_process.c @@ -27,23 +27,6 @@ #include "ares_setup.h" -#ifdef HAVE_SYS_UIO_H -# include -#endif -#ifdef HAVE_NETINET_IN_H -# include -#endif -#ifdef HAVE_NETINET_TCP_H -# include -#endif -#ifdef HAVE_NETDB_H -# include -#endif -#ifdef HAVE_ARPA_INET_H -# include -#endif - -#include "ares_nameser.h" #ifdef HAVE_STRINGS_H # include @@ -60,8 +43,9 @@ #include #include "ares.h" -#include "ares_dns.h" #include "ares_private.h" +#include "ares_nameser.h" +#include "ares_dns.h" static ares_bool_t try_again(int errnum); static void write_tcp_data(ares_channel channel, fd_set *write_fds, @@ -77,9 +61,6 @@ static void skip_server(ares_channel channel, struct query *query, struct server_state *server); static ares_status_t next_server(ares_channel channel, struct query *query, struct timeval *now); -static ares_status_t open_socket(ares_channel channel, - struct server_state *server, - ares_bool_t is_tcp); static ares_bool_t same_questions(const unsigned char *qbuf, size_t qlen, ares_dns_record_t *arec); static ares_bool_t same_address(struct sockaddr *sa, struct ares_addr *aa); @@ -87,8 +68,7 @@ static ares_bool_t has_opt_rr(ares_dns_record_t *arec); static void end_query(ares_channel channel, struct query *query, ares_status_t status, const unsigned char *abuf, size_t alen); -static ares_ssize_t ares__socket_write(ares_channel channel, ares_socket_t s, - const void *data, size_t len); + /* return true if now is exactly check time or later */ ares_bool_t ares__timedout(struct timeval *now, struct timeval *check) @@ -240,33 +220,6 @@ static void write_tcp_data(ares_channel channel, fd_set *write_fds, } } -static ares_ssize_t socket_recvfrom(ares_channel channel, ares_socket_t s, - void *data, size_t data_len, int flags, - struct sockaddr *from, - ares_socklen_t *from_len) -{ - if (channel->sock_funcs && channel->sock_funcs->arecvfrom) { - return channel->sock_funcs->arecvfrom(s, data, data_len, flags, from, - from_len, channel->sock_func_cb_data); - } - -#ifdef HAVE_RECVFROM - return recvfrom(s, data, data_len, flags, from, from_len); -#else - return sread(s, data, data_len); -#endif -} - -static ares_ssize_t socket_recv(ares_channel channel, ares_socket_t s, - void *data, size_t data_len) -{ - if (channel->sock_funcs && channel->sock_funcs->arecvfrom) { - return channel->sock_funcs->arecvfrom(s, data, data_len, 0, 0, 0, - channel->sock_func_cb_data); - } - - return sread(s, data, data_len); -} /* If any TCP socket selects true for reading, read some data, * allocate a buffer if we finish reading the length word, and process @@ -289,7 +242,7 @@ static void read_tcp_data(ares_channel channel, struct server_connection *conn, } /* Read from socket */ - count = socket_recv(channel, conn->fd, ptr, ptr_len); + count = ares__socket_recv(channel, conn->fd, ptr, ptr_len); if (count <= 0) { ares__buf_append_finish(server->tcp_parser, 0); if (!(count == -1 && try_again(SOCKERRNO))) { @@ -428,8 +381,8 @@ static void read_udp_packets_fd(ares_channel channel, } else { fromlen = sizeof(from.sa6); } - read_len = socket_recvfrom(channel, conn->fd, (void *)buf, sizeof(buf), 0, - &from.sa, &fromlen); + read_len = ares__socket_recvfrom(channel, conn->fd, (void *)buf, + sizeof(buf), 0, &from.sa, &fromlen); } if (read_len == 0) { @@ -582,7 +535,7 @@ static void process_answer(ares_channel channel, const unsigned char *abuf, /* Find the query corresponding to this packet. The queries are * hashed/bucketed by query id, so this lookup should be quick. */ - query = ares__htable_stvp_get_direct(channel->queries_by_qid, + query = ares__htable_szvp_get_direct(channel->queries_by_qid, ares_dns_record_get_id(dnsrec)); if (!query) { goto cleanup; @@ -776,7 +729,7 @@ ares_status_t ares__send_query(ares_channel channel, struct query *query, * a send request. */ if (server->tcp_conn == NULL) { - status = open_socket(channel, server, 1); + status = ares__open_connection(channel, server, ARES_TRUE); switch (status) { /* Good result, continue on */ case ARES_SUCCESS: @@ -829,7 +782,7 @@ ares_status_t ares__send_query(ares_channel channel, struct query *query, } if (node == NULL) { - status = open_socket(channel, server, 0); + status = ares__open_connection(channel, server, ARES_FALSE); switch (status) { /* Good result, continue on */ case ARES_SUCCESS: @@ -905,305 +858,7 @@ ares_status_t ares__send_query(ares_channel channel, struct query *query, return ARES_SUCCESS; } -/* - * setsocknonblock sets the given socket to either blocking or non-blocking - * mode based on the 'nonblock' boolean argument. This function is highly - * portable. - */ -static int setsocknonblock(ares_socket_t sockfd, /* operate on this */ - int nonblock /* TRUE or FALSE */) -{ -#if defined(USE_BLOCKING_SOCKETS) - - return 0; /* returns success */ - -#elif defined(HAVE_FCNTL_O_NONBLOCK) - - /* most recent unix versions */ - int flags; - flags = fcntl(sockfd, F_GETFL, 0); - if (FALSE != nonblock) { - return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - } else { - return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK)); /* LCOV_EXCL_LINE */ - } - -#elif defined(HAVE_IOCTL_FIONBIO) - - /* older unix versions */ - int flags = nonblock ? 1 : 0; - return ioctl(sockfd, FIONBIO, &flags); - -#elif defined(HAVE_IOCTLSOCKET_FIONBIO) - -# ifdef WATT32 - char flags = nonblock ? 1 : 0; -# else - /* Windows */ - unsigned long flags = nonblock ? 1UL : 0UL; -# endif - return ioctlsocket(sockfd, FIONBIO, &flags); - -#elif defined(HAVE_IOCTLSOCKET_CAMEL_FIONBIO) - - /* Amiga */ - long flags = nonblock ? 1L : 0L; - return IoctlSocket(sockfd, FIONBIO, flags); - -#elif defined(HAVE_SETSOCKOPT_SO_NONBLOCK) - /* BeOS */ - long b = nonblock ? 1L : 0L; - return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b)); - -#else -# error "no non-blocking method was found/used/set" -#endif -} - -#if defined(IPV6_V6ONLY) && defined(WIN32) -/* It makes support for IPv4-mapped IPv6 addresses. - * Linux kernel, NetBSD, FreeBSD and Darwin: default is off; - * Windows Vista and later: default is on; - * DragonFly BSD: acts like off, and dummy setting; - * OpenBSD and earlier Windows: unsupported. - * Linux: controlled by /proc/sys/net/ipv6/bindv6only. - */ -static void set_ipv6_v6only(ares_socket_t sockfd, int on) -{ - (void)setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&on, sizeof(on)); -} -#else -# define set_ipv6_v6only(s, v) -#endif - -static int configure_socket(ares_socket_t s, int family, ares_channel channel) -{ - union { - struct sockaddr sa; - struct sockaddr_in sa4; - struct sockaddr_in6 sa6; - } local; - - /* do not set options for user-managed sockets */ - if (channel->sock_funcs && channel->sock_funcs->asocket) { - return 0; - } - - (void)setsocknonblock(s, TRUE); - -#if defined(FD_CLOEXEC) && !defined(MSDOS) - /* Configure the socket fd as close-on-exec. */ - if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) { - return -1; /* LCOV_EXCL_LINE */ - } -#endif - - /* Set the socket's send and receive buffer sizes. */ - if ((channel->socket_send_buffer_size > 0) && - setsockopt(s, SOL_SOCKET, SO_SNDBUF, - (void *)&channel->socket_send_buffer_size, - sizeof(channel->socket_send_buffer_size)) == -1) { - return -1; - } - - if ((channel->socket_receive_buffer_size > 0) && - setsockopt(s, SOL_SOCKET, SO_RCVBUF, - (void *)&channel->socket_receive_buffer_size, - sizeof(channel->socket_receive_buffer_size)) == -1) { - return -1; - } - -#ifdef SO_BINDTODEVICE - if (channel->local_dev_name[0]) { - if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, channel->local_dev_name, - sizeof(channel->local_dev_name))) { - /* Only root can do this, and usually not fatal if it doesn't work, so */ - /* just continue on. */ - } - } -#endif - - if (family == AF_INET) { - if (channel->local_ip4) { - memset(&local.sa4, 0, sizeof(local.sa4)); - local.sa4.sin_family = AF_INET; - local.sa4.sin_addr.s_addr = htonl(channel->local_ip4); - if (bind(s, &local.sa, sizeof(local.sa4)) < 0) { - return -1; - } - } - } else if (family == AF_INET6) { - if (memcmp(channel->local_ip6, ares_in6addr_any._S6_un._S6_u8, - sizeof(channel->local_ip6)) != 0) { - memset(&local.sa6, 0, sizeof(local.sa6)); - local.sa6.sin6_family = AF_INET6; - memcpy(&local.sa6.sin6_addr, channel->local_ip6, - sizeof(channel->local_ip6)); - if (bind(s, &local.sa, sizeof(local.sa6)) < 0) { - return -1; - } - } - set_ipv6_v6only(s, 0); - } - - return 0; -} - -static ares_status_t open_socket(ares_channel channel, - struct server_state *server, - ares_bool_t is_tcp) -{ - ares_socket_t s; - int opt; - ares_socklen_t salen; - - union { - struct sockaddr_in sa4; - struct sockaddr_in6 sa6; - } saddr; - struct sockaddr *sa; - unsigned short port; - struct server_connection *conn; - ares__llist_node_t *node; - int type = is_tcp ? SOCK_STREAM : SOCK_DGRAM; - - if (is_tcp) { - port = server->addr.tcp_port ? server->addr.tcp_port : channel->tcp_port; - } else { - port = server->addr.udp_port ? server->addr.udp_port : channel->udp_port; - } - - switch (server->addr.family) { - case AF_INET: - sa = (void *)&saddr.sa4; - salen = sizeof(saddr.sa4); - memset(sa, 0, salen); - saddr.sa4.sin_family = AF_INET; - saddr.sa4.sin_port = port; - memcpy(&saddr.sa4.sin_addr, &server->addr.addrV4, - sizeof(server->addr.addrV4)); - break; - case AF_INET6: - sa = (void *)&saddr.sa6; - salen = sizeof(saddr.sa6); - memset(sa, 0, salen); - saddr.sa6.sin6_family = AF_INET6; - saddr.sa6.sin6_port = port; - memcpy(&saddr.sa6.sin6_addr, &server->addr.addrV6, - sizeof(server->addr.addrV6)); - break; - default: - return ARES_EBADFAMILY; /* LCOV_EXCL_LINE */ - } - - /* Acquire a socket. */ - s = ares__open_socket(channel, server->addr.family, type, 0); - if (s == ARES_SOCKET_BAD) { - return ARES_ECONNREFUSED; - } - - /* Configure it. */ - if (configure_socket(s, server->addr.family, channel) < 0) { - ares__close_socket(channel, s); - return ARES_ECONNREFUSED; - } - -#ifdef TCP_NODELAY - if (is_tcp) { - /* - * Disable the Nagle algorithm (only relevant for TCP sockets, and thus not - * in configure_socket). In general, in DNS lookups we're pretty much - * interested in firing off a single request and then waiting for a reply, - * so batching isn't very interesting. - */ - opt = 1; - if (!channel->sock_funcs || !channel->sock_funcs->asocket) { - if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *)&opt, sizeof(opt)) == - -1) { - ares__close_socket(channel, s); - return ARES_ECONNREFUSED; - } - } - } -#endif - - if (channel->sock_config_cb) { - int err = channel->sock_config_cb(s, type, channel->sock_config_cb_data); - if (err < 0) { - ares__close_socket(channel, s); - return ARES_ECONNREFUSED; - } - } - - /* Connect to the server. */ - if (ares__connect_socket(channel, s, sa, salen) == -1) { - int err = SOCKERRNO; - - if (err != EINPROGRESS && err != EWOULDBLOCK) { - ares__close_socket(channel, s); - return ARES_ECONNREFUSED; - } - } - - if (channel->sock_create_cb) { - int err = channel->sock_create_cb(s, type, channel->sock_create_cb_data); - if (err < 0) { - ares__close_socket(channel, s); - return ARES_ECONNREFUSED; - } - } - - conn = ares_malloc(sizeof(*conn)); - if (conn == NULL) { - ares__close_socket(channel, s); - return ARES_ENOMEM; - } - memset(conn, 0, sizeof(*conn)); - conn->fd = s; - conn->server = server; - conn->queries_to_conn = ares__llist_create(NULL); - conn->is_tcp = is_tcp; - if (conn->queries_to_conn == NULL) { - ares__close_socket(channel, s); - ares_free(conn); - return ARES_ENOMEM; - } - - /* TCP connections are thrown to the end as we don't spawn multiple TCP - * connections. UDP connections are put on front where the newest connection - * can be quickly pulled */ - if (is_tcp) { - node = ares__llist_insert_last(server->connections, conn); - } else { - node = ares__llist_insert_first(server->connections, conn); - } - if (node == NULL) { - ares__close_socket(channel, s); - ares__llist_destroy(conn->queries_to_conn); - ares_free(conn); - return ARES_ENOMEM; - } - - /* Register globally to quickly map event on file descriptor to connection - * node object */ - if (!ares__htable_asvp_insert(channel->connnode_by_socket, s, node)) { - ares__close_socket(channel, s); - ares__llist_destroy(conn->queries_to_conn); - ares__llist_node_claim(node); - ares_free(conn); - return ARES_ENOMEM; - } - - SOCK_STATE_CALLBACK(channel, s, 1, 0); - - if (is_tcp) { - server->tcp_connection_generation = ++channel->tcp_connection_generation; - server->tcp_conn = conn; - } - - return ARES_SUCCESS; -} static ares_bool_t same_questions(const unsigned char *qbuf, size_t qlen, ares_dns_record_t *arec) @@ -1297,7 +952,7 @@ static ares_bool_t has_opt_rr(ares_dns_record_t *arec) static void ares_detach_query(struct query *query) { /* Remove the query from all the lists in which it is linked */ - ares__htable_stvp_remove(query->channel->queries_by_qid, query->qid); + ares__htable_szvp_remove(query->channel->queries_by_qid, query->qid); ares__slist_node_destroy(query->node_queries_by_timeout); ares__llist_node_destroy(query->node_queries_to_conn); ares__llist_node_destroy(query->node_all_queries); @@ -1334,53 +989,4 @@ void ares__free_query(struct query *query) ares_free(query); } -ares_socket_t ares__open_socket(ares_channel channel, int af, int type, - int protocol) -{ - if (channel->sock_funcs && channel->sock_funcs->asocket) { - return channel->sock_funcs->asocket(af, type, protocol, - channel->sock_func_cb_data); - } - - return socket(af, type, protocol); -} - -int ares__connect_socket(ares_channel channel, ares_socket_t sockfd, - const struct sockaddr *addr, ares_socklen_t addrlen) -{ - if (channel->sock_funcs && channel->sock_funcs->aconnect) { - return channel->sock_funcs->aconnect(sockfd, addr, addrlen, - channel->sock_func_cb_data); - } - - return connect(sockfd, addr, addrlen); -} - -void ares__close_socket(ares_channel channel, ares_socket_t s) -{ - if (channel->sock_funcs && channel->sock_funcs->aclose) { - channel->sock_funcs->aclose(s, channel->sock_func_cb_data); - } else { - sclose(s); - } -} -#ifndef HAVE_WRITEV -/* Structure for scatter/gather I/O. */ -struct iovec { - void *iov_base; /* Pointer to data. */ - size_t iov_len; /* Length of data. */ -}; -#endif - -static ares_ssize_t ares__socket_write(ares_channel channel, ares_socket_t s, - const void *data, size_t len) -{ - if (channel->sock_funcs && channel->sock_funcs->asendv) { - struct iovec vec; - vec.iov_base = (void *)data; - vec.iov_len = len; - return channel->sock_funcs->asendv(s, &vec, 1, channel->sock_func_cb_data); - } - return swrite(s, data, len); -} diff --git a/src/lib/ares_query.c b/src/lib/ares_query.c index 4996732d..75717117 100644 --- a/src/lib/ares_query.c +++ b/src/lib/ares_query.c @@ -56,7 +56,7 @@ static unsigned short generate_unique_id(ares_channel channel) do { id = ares__generate_new_id(channel->rand_state); - } while (ares__htable_stvp_get(channel->queries_by_qid, id, NULL)); + } while (ares__htable_szvp_get(channel->queries_by_qid, id, NULL)); return (unsigned short)id; } diff --git a/src/lib/ares_send.c b/src/lib/ares_send.c index bc3aeed0..4ce43262 100644 --- a/src/lib/ares_send.c +++ b/src/lib/ares_send.c @@ -134,7 +134,7 @@ ares_status_t ares_send_ex(ares_channel channel, const unsigned char *qbuf, /* Keep track of queries bucketed by qid, so we can process DNS * responses quickly. */ - if (!ares__htable_stvp_insert(channel->queries_by_qid, query->qid, query)) { + if (!ares__htable_szvp_insert(channel->queries_by_qid, query->qid, query)) { callback(arg, ARES_ENOMEM, 0, NULL, 0); ares__free_query(query); return ARES_ENOMEM;