Replace hosts parser, add caching capabilities (#591)

HOSTS FILE PROCESSING OVERVIEW
==============================
The hosts file on the system contains static entries to be processed locally
rather than querying the nameserver.  Each row is an IP address followed by
a list of space delimited hostnames that match the ip address.  This is used
for both forward and reverse lookups.

We are caching the entire parsed hosts file for performance reasons.  Some
files may be quite sizable and as per Issue #458 can approach 1/2MB in size,
and the parse overhead on a rapid succession of queries can be quite large.
The entries are stored in forwards and backwards hashtables so we can get
O(1) performance on lookup.  The file is cached until the file modification
timestamp changes (or 60s if there is no implemented stat() capability).

The hosts file processing is quite unique. It has to merge all related hosts
and ips into a single entry due to file formatting requirements.  For
instance take the below:
```
127.0.0.1    localhost.localdomain localhost
::1          localhost.localdomain localhost
192.168.1.1  host.example.com host
192.168.1.5  host.example.com host
2620🔢:1 host.example.com host6.example.com host6 host
```
This will yield 2 entries.
1) ips: `127.0.0.1,::1`
    hosts: `localhost.localdomain,localhost`
2) ips: `192.168.1.1,192.168.1.5,2620🔢:1`
    hosts: `host.example.com,host,host6.example.com,host6`

It could be argued that if searching for `192.168.1.1` that the `host6`
hostnames should not be returned, but this implementation will return them
since they are related (both ips have the fqdn of host.example.com).  It is
unlikely this will matter in the real world.


Fix By: Brad House (@bradh352)
pull/593/head
Brad House 1 year ago committed by GitHub
parent 58e6f1fa81
commit 8a3664b8cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CMakeLists.txt
  2. 2
      configure.ac
  3. 105
      m4/cares-functions.m4
  4. 3
      src/lib/Makefile.inc
  5. 7
      src/lib/ares__buf.c
  6. 289
      src/lib/ares__get_hostent.c
  7. 1027
      src/lib/ares__hosts_file.c
  8. 6
      src/lib/ares__htable_strvp.c
  9. 264
      src/lib/ares__readaddrinfo.c
  10. 3
      src/lib/ares_config.h.cmake
  11. 2
      src/lib/ares_destroy.c
  12. 1
      src/lib/ares_freeaddrinfo.c
  13. 169
      src/lib/ares_getaddrinfo.c
  14. 97
      src/lib/ares_gethostbyaddr.c
  15. 109
      src/lib/ares_gethostbyname.c
  16. 34
      src/lib/ares_private.h
  17. 3
      src/lib/config-win32.h
  18. 95
      test/ares-test-internal.cc
  19. 7
      test/ares-test.cc
  20. 26
      test/ares-test.h

@ -309,6 +309,7 @@ CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_SELECT_H sys/select.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_SOCKET_H sys/socket.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_SOCKIO_H sys/sockio.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_TIME_H sys/time.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_STAT_H sys/stat.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_SYS_UIO_H sys/uio.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_TIME_H time.h)
CARES_EXTRAINCLUDE_IFSET (HAVE_FCNTL_H fcntl.h)
@ -419,7 +420,7 @@ CHECK_SYMBOL_EXISTS (strncmpi "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STRNCMP
CHECK_SYMBOL_EXISTS (strnicmp "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STRNICMP)
CHECK_SYMBOL_EXISTS (writev "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_WRITEV)
CHECK_SYMBOL_EXISTS (arc4random_buf "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_ARC4RANDOM_BUF)
CHECK_SYMBOL_EXISTS (stat "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_STAT)
# On Android, the system headers may define __system_property_get(), but excluded
# from libc. We need to perform a link test instead of a header/symbol test.

@ -744,7 +744,7 @@ CARES_CHECK_FUNC_STRNCMPI
CARES_CHECK_FUNC_STRNICMP
CARES_CHECK_FUNC_WRITEV
CARES_CHECK_FUNC_ARC4RANDOM_BUF
CARES_CHECK_FUNC_STAT
dnl check for AF_INET6
CARES_CHECK_CONSTANT(

@ -265,6 +265,26 @@ cares_includes_sys_uio="\
])
dnl CARES_INCLUDES_SYS_STAT
dnl -------------------------------------------------
dnl Set up variable with list of headers that must be
dnl included when sys/stat.h is to be included.
AC_DEFUN([CARES_INCLUDES_SYS_STAT], [
cares_includes_sys_stat="\
/* includes start */
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
/* includes end */"
AC_CHECK_HEADERS(
sys/types.h sys/stat.h,
[], [], [$cares_includes_sys_stat])
])
dnl CARES_INCLUDES_UNISTD
dnl -------------------------------------------------
dnl Set up variable with list of headers that must be
@ -3857,6 +3877,91 @@ AC_DEFUN([CARES_CHECK_FUNC_WRITEV], [
fi
])
dnl CARES_CHECK_FUNC_STAT
dnl -------------------------------------------------
dnl Verify if stat is available, prototyped, and
dnl can be compiled. If all of these are true, and
dnl usage has not been previously disallowed with
dnl shell variable cares_disallow_stat, then
dnl HAVE_STAT will be defined.
AC_DEFUN([CARES_CHECK_FUNC_STAT], [
AC_REQUIRE([CARES_INCLUDES_SYS_STAT])dnl
#
tst_links_stat="unknown"
tst_proto_stat="unknown"
tst_compi_stat="unknown"
tst_allow_stat="unknown"
#
AC_MSG_CHECKING([if stat can be linked])
AC_LINK_IFELSE([
AC_LANG_FUNC_LINK_TRY([stat])
],[
AC_MSG_RESULT([yes])
tst_links_stat="yes"
],[
AC_MSG_RESULT([no])
tst_links_stat="no"
])
#
if test "$tst_links_stat" = "yes"; then
AC_MSG_CHECKING([if stat is prototyped])
AC_EGREP_CPP([stat],[
$cares_includes_sys_stat
],[
AC_MSG_RESULT([yes])
tst_proto_stat="yes"
],[
AC_MSG_RESULT([no])
tst_proto_stat="no"
])
fi
#
if test "$tst_proto_stat" = "yes"; then
AC_MSG_CHECKING([if stat is compilable])
AC_COMPILE_IFELSE([
AC_LANG_PROGRAM([[
$cares_includes_sys_types
$cares_includes_sys_stat
]],[[
if(0 != stat(0, 0))
return 1;
]])
],[
AC_MSG_RESULT([yes])
tst_compi_stat="yes"
],[
AC_MSG_RESULT([no])
tst_compi_stat="no"
])
fi
#
if test "$tst_compi_stat" = "yes"; then
AC_MSG_CHECKING([if stat usage allowed])
if test "x$cares_disallow_stat" != "xyes"; then
AC_MSG_RESULT([yes])
tst_allow_stat="yes"
else
AC_MSG_RESULT([no])
tst_allow_stat="no"
fi
fi
#
AC_MSG_CHECKING([if stat might be used])
if test "$tst_links_stat" = "yes" &&
test "$tst_proto_stat" = "yes" &&
test "$tst_compi_stat" = "yes" &&
test "$tst_allow_stat" = "yes"; then
AC_MSG_RESULT([yes])
AC_DEFINE_UNQUOTED(HAVE_STAT, 1,
[Define to 1 if you have the stat function.])
ac_cv_func_stat="yes"
else
AC_MSG_RESULT([no])
ac_cv_func_stat="no"
fi
])
dnl CARES_CHECK_FUNC_ARC4RANDOM_BUF
dnl -------------------------------------------------
dnl Verify if arc4random_buf is available, prototyped, and

@ -5,7 +5,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__addrinfo_localhost.c \
ares__buf.c \
ares__close_sockets.c \
ares__get_hostent.c \
ares__hosts_file.c \
ares__htable.c \
ares__htable_asvp.c \
ares__htable_strvp.c \
@ -13,7 +13,6 @@ CSOURCES = ares__addrinfo2hostent.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 \

@ -86,7 +86,7 @@ static ares_bool_t ares__isprint(int ch)
* 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)
ares_bool_t ares__is_hostnamech(int ch)
{
/* [A-Za-z0-9-*._/]
* Don't use isalnum() as it is locale-specific
@ -593,7 +593,7 @@ static ares_status_t ares__buf_fetch_dnsname_into_buf(ares__buf_t *buf,
/* Hostnames have a very specific allowed character set. Anything outside
* of that (non-printable and reserved included) are disallowed */
if (is_hostname && !is_hostnamech(c)) {
if (is_hostname && !ares__is_hostnamech(c)) {
status = ARES_EBADRESP;
goto fail;
}
@ -725,9 +725,10 @@ size_t ares__buf_consume_line(ares__buf_t *buf, ares_bool_t include_linefeed)
}
done:
if (include_linefeed && i > 0 && i < remaining_len && ptr[i] == '\n') {
if (include_linefeed && i < remaining_len && ptr[i] == '\n') {
i++;
}
if (i > 0) {
ares__buf_consume(buf, i);
}

@ -1,289 +0,0 @@
/* MIT License
*
* Copyright (c) 1998, 2011 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_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#include "ares.h"
#include "ares_inet_net_pton.h"
#include "ares_private.h"
ares_status_t ares__get_hostent(FILE *fp, int family, struct hostent **host)
{
char *line = NULL;
char *p;
char *q;
char **alias;
const char *txtaddr;
const char *txthost;
char *txtalias;
ares_status_t status;
size_t addrlen;
size_t linesize;
size_t naliases;
struct ares_addr addr;
struct hostent *hostent = NULL;
*host = NULL; /* Assume failure */
/* Validate family */
switch (family) {
case AF_INET:
case AF_INET6:
case AF_UNSPEC:
break;
default:
return ARES_EBADFAMILY;
}
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
/* Trim line comment. */
p = line;
while (*p && (*p != '#')) {
p++;
}
*p = '\0';
/* Trim trailing whitespace. */
q = p - 1;
while ((q >= line) && ISSPACE(*q)) {
q--;
}
*++q = '\0';
/* Skip leading whitespace. */
p = line;
while (*p && ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if empty. */
continue;
}
/* Pointer to start of IPv4 or IPv6 address part. */
txtaddr = p;
/* Advance past address part. */
while (*p && !ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if reached end of line. */
continue;
}
/* Null terminate address part. */
*p = '\0';
/* Advance to host name */
p++;
while (*p && ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if reached end of line. */
continue; /* LCOV_EXCL_LINE: trailing whitespace already stripped */
}
/* Pointer to start of host name. */
txthost = p;
/* Advance past host name. */
while (*p && !ISSPACE(*p)) {
p++;
}
/* Pointer to start of first alias. */
txtalias = NULL;
if (*p) {
q = p + 1;
while (*q && ISSPACE(*q)) {
q++;
}
if (*q) {
txtalias = q;
}
}
/* Null terminate host name. */
*p = '\0';
/* find out number of aliases. */
naliases = 0;
if (txtalias) {
p = txtalias;
while (*p) {
while (*p && !ISSPACE(*p)) {
p++;
}
while (*p && ISSPACE(*p)) {
p++;
}
naliases++;
}
}
/* Convert address string to network address for the requested family. */
addrlen = 0;
addr.family = AF_UNSPEC;
addr.addrV4.s_addr = INADDR_NONE;
if ((family == AF_INET || family == AF_UNSPEC) &&
ares_inet_pton(AF_INET, txtaddr, &addr.addrV4) > 0) {
/* Actual network address family and length. */
addr.family = AF_INET;
addrlen = sizeof(addr.addrV4);
}
if ((family == AF_INET6 || (family == AF_UNSPEC && !addrlen)) &&
ares_inet_pton(AF_INET6, txtaddr, &addr.addrV6) > 0) {
/* Actual network address family and length. */
addr.family = AF_INET6;
addrlen = sizeof(addr.addrV6);
}
if (!addrlen) {
/* Ignore line if invalid address string for the requested family. */
continue;
}
/*
** Actual address family possible values are AF_INET and AF_INET6 only.
*/
/* Allocate memory for the hostent structure. */
hostent = ares_malloc(sizeof(struct hostent));
if (!hostent) {
break;
}
/* Initialize fields for out of memory condition. */
hostent->h_aliases = NULL;
hostent->h_addr_list = NULL;
/* Copy official host name. */
hostent->h_name = ares_strdup(txthost);
if (!hostent->h_name) {
break;
}
/* Copy network address. */
hostent->h_addr_list = ares_malloc(2 * sizeof(char *));
if (!hostent->h_addr_list) {
break;
}
hostent->h_addr_list[1] = NULL;
hostent->h_addr_list[0] = ares_malloc(addrlen);
if (!hostent->h_addr_list[0]) {
break;
}
if (addr.family == AF_INET) {
memcpy(hostent->h_addr_list[0], &addr.addrV4, sizeof(addr.addrV4));
} else {
memcpy(hostent->h_addr_list[0], &addr.addrV6, sizeof(addr.addrV6));
}
/* Copy aliases. */
hostent->h_aliases = ares_malloc((naliases + 1) * sizeof(char *));
if (!hostent->h_aliases) {
break;
}
alias = hostent->h_aliases;
while (naliases) {
*(alias + naliases--) = NULL;
}
*alias = NULL;
while (txtalias) {
p = txtalias;
while (*p && !ISSPACE(*p)) {
p++;
}
q = p;
while (*q && ISSPACE(*q)) {
q++;
}
*p = '\0';
if ((*alias = ares_strdup(txtalias)) == NULL) {
break;
}
alias++;
txtalias = *q ? q : NULL;
}
if (txtalias) {
/* Alias memory allocation failure. */
break;
}
/* Copy actual network address family and length. */
hostent->h_addrtype = addr.family;
hostent->h_length = (int)addrlen;
/* Free line buffer. */
ares_free(line);
/* Return hostent successfully */
*host = hostent;
return ARES_SUCCESS;
}
/* If allocated, free line buffer. */
if (line) {
ares_free(line);
}
if (status == ARES_SUCCESS) {
/* Memory allocation failure; clean up. */
if (hostent) {
if (hostent->h_name) {
ares_free(hostent->h_name);
}
if (hostent->h_aliases) {
for (alias = hostent->h_aliases; *alias; alias++) {
ares_free(*alias);
}
ares_free(hostent->h_aliases);
}
if (hostent->h_addr_list) {
if (hostent->h_addr_list[0]) {
ares_free(hostent->h_addr_list[0]);
}
ares_free(hostent->h_addr_list);
}
ares_free(hostent);
}
return ARES_ENOMEM;
}
return status;
}

File diff suppressed because it is too large Load Diff

@ -129,6 +129,9 @@ ares_bool_t ares__htable_strvp_insert(ares__htable_strvp_t *htable,
bucket->parent = htable;
bucket->key = ares_strdup(key);
if (bucket->key == NULL) {
goto fail;
}
bucket->val = val;
if (!ares__htable_insert(htable->hash, bucket)) {
@ -139,6 +142,7 @@ ares_bool_t ares__htable_strvp_insert(ares__htable_strvp_t *htable,
fail:
if (bucket) {
ares_free(bucket->key);
ares_free(bucket);
}
return ARES_FALSE;
@ -157,7 +161,7 @@ ares_bool_t ares__htable_strvp_get(const ares__htable_strvp_t *htable,
return ARES_FALSE;
}
bucket = ares__htable_get(htable->hash, &key);
bucket = ares__htable_get(htable->hash, key);
if (bucket == NULL) {
return ARES_FALSE;
}

@ -1,264 +0,0 @@
/* MIT License
*
* Copyright (c) 2019 Andrew Selivanov
*
* 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_NETINET_IN_H
# include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
# include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#include "ares.h"
#include "ares_inet_net_pton.h"
#include "ares_private.h"
#define MAX_ALIASES 40
ares_status_t ares__readaddrinfo(FILE *fp, const char *name,
unsigned short port,
const struct ares_addrinfo_hints *hints,
struct ares_addrinfo *ai)
{
char *line = NULL;
char *p;
char *q;
const char *txtaddr;
const char *txthost;
char *txtalias;
char *aliases[MAX_ALIASES];
size_t i;
size_t alias_count;
ares_status_t status = ARES_SUCCESS;
size_t linesize;
struct ares_addrinfo_cname *cname = NULL;
struct ares_addrinfo_cname *cnames = NULL;
struct ares_addrinfo_node *nodes = NULL;
ares_bool_t match_with_alias;
ares_bool_t match_with_canonical;
ares_bool_t want_cname =
(hints->ai_flags & ARES_AI_CANONNAME) ? ARES_TRUE : ARES_FALSE;
/* Validate family */
switch (hints->ai_family) {
case AF_INET:
case AF_INET6:
case AF_UNSPEC:
break;
default:
return ARES_EBADFAMILY;
}
ai->name = ares_strdup(name);
if (!ai->name) {
status = ARES_ENOMEM;
goto fail;
}
while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) {
match_with_alias = ARES_FALSE;
match_with_canonical = ARES_FALSE;
alias_count = 0;
/* Trim line comment. */
p = line;
while (*p && (*p != '#')) {
p++;
}
*p = '\0';
/* Trim trailing whitespace. */
q = p - 1;
while ((q >= line) && ISSPACE(*q)) {
q--;
}
*++q = '\0';
/* Skip leading whitespace. */
p = line;
while (*p && ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if empty. */
continue;
}
/* Pointer to start of IPv4 or IPv6 address part. */
txtaddr = p;
/* Advance past address part. */
while (*p && !ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if reached end of line. */
continue;
}
/* Null terminate address part. */
*p = '\0';
/* Advance to host name */
p++;
while (*p && ISSPACE(*p)) {
p++;
}
if (!*p) {
/* Ignore line if reached end of line. */
continue; /* LCOV_EXCL_LINE: trailing whitespace already stripped */
}
/* Pointer to start of host name. */
txthost = p;
/* Advance past host name. */
while (*p && !ISSPACE(*p)) {
p++;
}
/* Pointer to start of first alias. */
txtalias = NULL;
if (*p) {
q = p + 1;
while (*q && ISSPACE(*q)) {
q++;
}
if (*q) {
txtalias = q;
}
}
/* Null terminate host name. */
*p = '\0';
/* Find out if host name matches with canonical host name. */
if (strcasecmp(txthost, name) == 0) {
match_with_canonical = ARES_TRUE;
}
/* Find out if host name matches with one of the aliases. */
while (txtalias) {
p = txtalias;
while (*p && !ISSPACE(*p)) {
p++;
}
q = p;
while (*q && ISSPACE(*q)) {
q++;
}
*p = '\0';
if (strcasecmp(txtalias, name) == 0) {
match_with_alias = ARES_TRUE;
if (!want_cname) {
break;
}
}
if (alias_count < MAX_ALIASES) {
aliases[alias_count++] = txtalias;
}
txtalias = *q ? q : NULL;
}
/* Try next line if host does not match. */
if (!match_with_alias && !match_with_canonical) {
continue;
}
/*
* Convert address string to network address for the requested families.
* Actual address family possible values are AF_INET and AF_INET6 only.
*/
if ((hints->ai_family == AF_INET) || (hints->ai_family == AF_UNSPEC)) {
struct in_addr addr4;
if (ares_inet_pton(AF_INET, txtaddr, &addr4) == 1) {
status = ares_append_ai_node(AF_INET, port, 0, &addr4, &nodes);
if (status != ARES_SUCCESS) {
goto fail;
}
}
}
if ((hints->ai_family == AF_INET6) || (hints->ai_family == AF_UNSPEC)) {
struct ares_in6_addr addr6;
if (ares_inet_pton(AF_INET6, txtaddr, &addr6) == 1) {
status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &nodes);
if (status != ARES_SUCCESS) {
goto fail;
}
}
}
if (want_cname) {
for (i = 0; i < alias_count; ++i) {
cname = ares__append_addrinfo_cname(&cnames);
if (!cname) {
status = ARES_ENOMEM;
goto fail;
}
cname->alias = ares_strdup(aliases[i]);
cname->name = ares_strdup(txthost);
}
/* No aliases, cname only. */
if (!alias_count) {
cname = ares__append_addrinfo_cname(&cnames);
if (!cname) {
status = ARES_ENOMEM;
goto fail;
}
cname->name = ares_strdup(txthost);
}
}
}
/* Last read failed. */
if (status == ARES_ENOMEM) {
goto fail;
}
/* If no results, its a failure */
if (!nodes) {
status = ARES_ENOTFOUND;
goto fail;
}
/* Free line buffer. */
ares_free(line);
ares__addrinfo_cat_cnames(&ai->cnames, cnames);
ares__addrinfo_cat_nodes(&ai->nodes, nodes);
return ARES_SUCCESS;
fail:
ares_free(line);
ares__freeaddrinfo_cnames(cnames);
ares__freeaddrinfo_nodes(nodes);
ares_free(ai->name);
ai->name = NULL;
return status;
}

@ -356,6 +356,9 @@
/* Define if have arc4random_buf() */
#cmakedefine HAVE_ARC4RANDOM_BUF
/* Define if have stat() */
#cmakedefine HAVE_STAT
/* a suitable file/device to read random data from */
#cmakedefine CARES_RANDOM_FILE "@CARES_RANDOM_FILE@"

@ -129,6 +129,8 @@ void ares_destroy(ares_channel channel)
ares__destroy_rand_state(channel->rand_state);
}
ares__hosts_file_destroy(channel->hf);
ares_free(channel);
}

@ -64,6 +64,7 @@ void ares_freeaddrinfo(struct ares_addrinfo *ai)
}
ares__freeaddrinfo_cnames(ai->cnames);
ares__freeaddrinfo_nodes(ai->nodes);
ares_free(ai->name);
ares_free(ai);
}

@ -95,30 +95,6 @@ static const struct ares_addrinfo_hints default_hints = {
0, /* ai_protocol */
};
static const struct ares_addrinfo_cname empty_addrinfo_cname = {
INT_MAX, /* ttl */
NULL, /* alias */
NULL, /* name */
NULL, /* next */
};
static const struct ares_addrinfo_node empty_addrinfo_node = {
0, /* ai_ttl */
0, /* ai_flags */
0, /* ai_family */
0, /* ai_socktype */
0, /* ai_protocol */
0, /* ai_addrlen */
NULL, /* ai_addr */
NULL /* ai_next */
};
static const struct ares_addrinfo empty_addrinfo = {
NULL, /* cnames */
NULL, /* nodes */
NULL /* name */
};
/* forward declarations */
static void host_callback(void *arg, int status, int timeouts,
unsigned char *abuf, int alen);
@ -126,23 +102,15 @@ static ares_bool_t as_is_first(const struct host_query *hquery);
static ares_bool_t as_is_only(const struct host_query *hquery);
static ares_bool_t next_dns_lookup(struct host_query *hquery);
static struct ares_addrinfo_cname *ares__malloc_addrinfo_cname(void)
{
struct ares_addrinfo_cname *cname =
ares_malloc(sizeof(struct ares_addrinfo_cname));
if (!cname) {
return NULL;
}
*cname = empty_addrinfo_cname;
return cname;
}
struct ares_addrinfo_cname *
ares__append_addrinfo_cname(struct ares_addrinfo_cname **head)
{
struct ares_addrinfo_cname *tail = ares__malloc_addrinfo_cname();
struct ares_addrinfo_cname *tail = ares_malloc_zero(sizeof(*tail));
struct ares_addrinfo_cname *last = *head;
if (tail == NULL)
return NULL;
if (!last) {
*head = tail;
return tail;
@ -172,34 +140,16 @@ void ares__addrinfo_cat_cnames(struct ares_addrinfo_cname **head,
last->next = tail;
}
static struct ares_addrinfo *ares__malloc_addrinfo(void)
{
struct ares_addrinfo *ai = ares_malloc(sizeof(struct ares_addrinfo));
if (!ai) {
return NULL;
}
*ai = empty_addrinfo;
return ai;
}
static struct ares_addrinfo_node *ares__malloc_addrinfo_node(void)
{
struct ares_addrinfo_node *node = ares_malloc(sizeof(*node));
if (!node) {
return NULL;
}
*node = empty_addrinfo_node;
return node;
}
/* Allocate new addrinfo and append to the tail. */
struct ares_addrinfo_node *
ares__append_addrinfo_node(struct ares_addrinfo_node **head)
{
struct ares_addrinfo_node *tail = ares__malloc_addrinfo_node();
struct ares_addrinfo_node *tail = ares_malloc_zero(sizeof(*tail));
struct ares_addrinfo_node *last = *head;
if (tail == NULL)
return NULL;
if (!last) {
*head = tail;
return tail;
@ -427,98 +377,41 @@ static ares_bool_t is_localhost(const char *name)
static ares_status_t file_lookup(struct host_query *hquery)
{
FILE *fp;
int error;
ares_status_t status;
char *path_hosts = NULL;
const ares_hosts_entry_t *entry;
ares_status_t status;
if (hquery->channel->hosts_path) {
path_hosts = ares_strdup(hquery->channel->hosts_path);
if (!path_hosts) {
return ARES_ENOMEM;
}
/* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
if (ares__is_onion_domain(hquery->name)) {
return ARES_ENOTFOUND;
}
if (hquery->hints.ai_flags & ARES_AI_ENVHOSTS) {
if (path_hosts) {
ares_free(path_hosts);
}
status = ares__hosts_search_host(hquery->channel,
(hquery->hints.ai_flags & ARES_AI_ENVHOSTS)?ARES_TRUE:ARES_FALSE,
hquery->name, &entry);
path_hosts = ares_strdup(getenv("CARES_HOSTS"));
if (!path_hosts) {
return ARES_ENOMEM;
}
if (status != ARES_SUCCESS) {
goto done;
}
if (!path_hosts) {
#ifdef WIN32
char PATH_HOSTS[MAX_PATH];
win_platform platform;
PATH_HOSTS[0] = '\0';
platform = ares__getplatform();
status = ares__hosts_entry_to_addrinfo(entry, hquery->name,
hquery->hints.ai_family,
hquery->port,
(hquery->hints.ai_flags & ARES_AI_CANONNAME)?ARES_TRUE:ARES_FALSE,
hquery->ai);
if (platform == WIN_NT) {
char tmp[MAX_PATH];
HKEY hkeyHosts;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
&hkeyHosts) == ERROR_SUCCESS) {
DWORD dwLength = MAX_PATH;
RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
}
} else if (platform == WIN_9X) {
GetWindowsDirectoryA(PATH_HOSTS, MAX_PATH);
} else {
return ARES_ENOTFOUND;
}
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
if (!PATH_HOSTS) {
return ARES_ENOTFOUND;
}
#endif
path_hosts = ares_strdup(PATH_HOSTS);
if (!path_hosts) {
return ARES_ENOMEM;
}
if (status != ARES_SUCCESS) {
goto done;
}
fp = fopen(path_hosts, "r");
if (!fp) {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
status = ARES_ENOTFOUND;
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", path_hosts));
status = ARES_EFILE;
break;
}
} else {
status = ares__readaddrinfo(fp, hquery->name, hquery->port, &hquery->hints,
hquery->ai);
fclose(fp);
}
ares_free(path_hosts);
done:
/* RFC6761 section 6.3 #3 states that "Name resolution APIs and libraries
* SHOULD recognize localhost names as special and SHOULD always return the
* IP loopback address for address queries".
* We will also ignore ALL errors when trying to resolve localhost, such
* as permissions errors reading /etc/hosts or a malformed /etc/hosts */
if (status != ARES_SUCCESS && is_localhost(hquery->name)) {
if (status != ARES_SUCCESS && status != ARES_ENOMEM &&
is_localhost(hquery->name)) {
return ares__addrinfo_localhost(hquery->name, hquery->port, &hquery->hints,
hquery->ai);
}
@ -700,7 +593,7 @@ void ares_getaddrinfo(ares_channel channel, const char *name,
}
}
ai = ares__malloc_addrinfo();
ai = ares_malloc_zero(sizeof(*ai));
if (!ai) {
ares_free(alias_name);
callback(arg, ARES_ENOMEM, 0, NULL);

@ -64,7 +64,8 @@ static void addr_callback(void *arg, int status, int timeouts,
unsigned char *abuf, int alen);
static void end_aquery(struct addr_query *aquery, ares_status_t status,
struct hostent *host);
static ares_status_t file_lookup(const struct ares_addr *addr,
static ares_status_t file_lookup(ares_channel channel,
const struct ares_addr *addr,
struct hostent **host);
static void ptr_rr_name(char *name, size_t name_size,
const struct ares_addr *addr);
@ -120,7 +121,7 @@ static void next_lookup(struct addr_query *aquery)
ares_query(aquery->channel, name, C_IN, T_PTR, addr_callback, aquery);
return;
case 'f':
status = file_lookup(&aquery->addr, &host);
status = file_lookup(aquery->channel, &aquery->addr, &host);
/* this status check below previously checked for !ARES_ENOTFOUND,
but we should not assume that this single error code is the one
@ -173,87 +174,37 @@ static void end_aquery(struct addr_query *aquery, ares_status_t status,
ares_free(aquery);
}
static ares_status_t file_lookup(const struct ares_addr *addr,
static ares_status_t file_lookup(ares_channel channel,
const struct ares_addr *addr,
struct hostent **host)
{
FILE *fp;
ares_status_t status;
int error;
char ipaddr[INET6_ADDRSTRLEN];
const void *ptr = NULL;
const ares_hosts_entry_t *entry;
ares_status_t status;
#ifdef WIN32
char PATH_HOSTS[MAX_PATH];
win_platform platform;
PATH_HOSTS[0] = '\0';
platform = ares__getplatform();
if (addr->family == AF_INET) {
ptr = &addr->addrV4;
} else if (addr->family == AF_INET6) {
ptr = &addr->addrV6;
}
if (platform == WIN_NT) {
char tmp[MAX_PATH];
HKEY hkeyHosts;
if (ptr == NULL)
return ARES_ENOTFOUND;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
&hkeyHosts) == ERROR_SUCCESS) {
DWORD dwLength = MAX_PATH;
RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
}
} else if (platform == WIN_9X) {
GetWindowsDirectoryA(PATH_HOSTS, MAX_PATH);
} else {
if (!ares_inet_ntop(addr->family, ptr, ipaddr, sizeof(ipaddr))) {
return ARES_ENOTFOUND;
}
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
status = ares__hosts_search_ipaddr(channel, ARES_FALSE, ipaddr, &entry);
if (status != ARES_SUCCESS)
return status;
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
status = ares__hosts_entry_to_hostent(entry, addr->family, host);
if (status != ARES_SUCCESS)
return status;
if (!PATH_HOSTS) {
return ARES_ENOTFOUND;
}
#endif
fp = fopen(PATH_HOSTS, "r");
if (!fp) {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
return ARES_ENOTFOUND;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", PATH_HOSTS));
*host = NULL;
return ARES_EFILE;
}
}
while ((status = ares__get_hostent(fp, addr->family, host)) == ARES_SUCCESS) {
if (addr->family != (*host)->h_addrtype) {
ares_free_hostent(*host);
continue;
}
if (addr->family == AF_INET &&
memcmp((*host)->h_addr, &addr->addrV4, sizeof(addr->addrV4)) == 0) {
break;
} else if (addr->family == AF_INET6 &&
memcmp((*host)->h_addr, addr->addrV6._S6_un._S6_u8,
sizeof(addr->addrV6)) == 0) {
break;
}
ares_free_hostent(*host);
}
fclose(fp);
if (status == ARES_EOF) {
status = ARES_ENOTFOUND;
}
if (status != ARES_SUCCESS) {
*host = NULL;
}
return status;
return ARES_SUCCESS;
}
static void ptr_rr_name(char *name, size_t name_size,

@ -230,120 +230,37 @@ static size_t get6_address_index(const struct ares_in6_addr *addr,
return i;
}
static ares_status_t file_lookup(const char *name, int family,
struct hostent **host);
/* I really have no idea why this is exposed as a public function, but since
* it is, we can't kill this legacy function. */
int ares_gethostbyname_file(ares_channel channel, const char *name, int family,
struct hostent **host)
{
ares_status_t status;
const ares_hosts_entry_t *entry;
ares_status_t status;
/* We only take the channel to ensure that ares_init() been called. */
if (channel == NULL) {
/* Anything will do, really. This seems fine, and is consistent with
other error cases. */
*host = NULL;
return ARES_ENOTFOUND;
return (int)ARES_ENOTFOUND;
}
/* Just chain to the internal implementation we use here; it's exactly
* what we want.
*/
status = file_lookup(name, family, host);
if (status != ARES_SUCCESS) {
/* We guarantee a NULL hostent on failure. */
*host = NULL;
}
return (int)status;
}
static ares_status_t file_lookup(const char *name, int family,
struct hostent **host)
{
FILE *fp;
char **alias;
ares_status_t status;
int error;
#ifdef WIN32
char PATH_HOSTS[MAX_PATH];
win_platform platform;
PATH_HOSTS[0] = '\0';
platform = ares__getplatform();
if (platform == WIN_NT) {
char tmp[MAX_PATH];
HKEY hkeyHosts;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
&hkeyHosts) == ERROR_SUCCESS) {
DWORD dwLength = MAX_PATH;
RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
}
} else if (platform == WIN_9X) {
GetWindowsDirectoryA(PATH_HOSTS, MAX_PATH);
} else {
return ARES_ENOTFOUND;
}
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
if (!PATH_HOSTS) {
return ARES_ENOTFOUND;
}
#endif
/* Per RFC 7686, reject queries for ".onion" domain names with NXDOMAIN. */
if (ares__is_onion_domain(name)) {
return ARES_ENOTFOUND;
}
status = ares__hosts_search_host(channel, ARES_FALSE, name, &entry);
if (status != ARES_SUCCESS)
return (int)status;
fp = fopen(PATH_HOSTS, "r");
if (!fp) {
error = ERRNO;
switch (error) {
case ENOENT:
case ESRCH:
return ARES_ENOTFOUND;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", PATH_HOSTS));
*host = NULL;
return ARES_EFILE;
}
}
while ((status = ares__get_hostent(fp, family, host)) == ARES_SUCCESS) {
if (strcasecmp((*host)->h_name, name) == 0) {
break;
}
for (alias = (*host)->h_aliases; *alias; alias++) {
if (strcasecmp(*alias, name) == 0) {
break;
}
}
if (*alias) {
break;
}
ares_free_hostent(*host);
}
fclose(fp);
if (status == ARES_EOF) {
status = ARES_ENOTFOUND;
}
if (status != ARES_SUCCESS) {
*host = NULL;
}
return status;
status = ares__hosts_entry_to_hostent(entry, family, host);
if (status != ARES_SUCCESS)
return (int)status;
return (int)ARES_SUCCESS;
}

@ -117,6 +117,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__llist.h"
#include "ares__slist.h"
#include "ares__htable_strvp.h"
#include "ares__htable_szvp.h"
#include "ares__htable_asvp.h"
#include "ares__buf.h"
@ -267,6 +268,9 @@ struct apattern {
unsigned short type;
};
struct ares_hosts_file;
typedef struct ares_hosts_file ares_hosts_file_t;
struct ares_channeldata {
/* Configuration data */
unsigned int flags;
@ -340,6 +344,9 @@ struct ares_channeldata {
/* Maximum UDP queries per connection allowed */
size_t udp_max_queries;
/* Cache of local hosts file */
ares_hosts_file_t *hf;
};
/* Does the domain end in ".onion" or ".onion."? Case-insensitive. */
@ -372,7 +379,6 @@ ares_status_t ares_send_ex(ares_channel channel, const unsigned char *qbuf,
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);
ares_status_t ares__get_hostent(FILE *fp, int family, struct hostent **host);
ares_status_t ares__read_line(FILE *fp, char **buf, size_t *bufsize);
void ares__free_query(struct query *query);
@ -404,10 +410,6 @@ ares_status_t ares__single_domain(ares_channel channel, const char *name,
ares_status_t ares__cat_domain(const char *name, const char *domain, char **s);
ares_status_t ares__sortaddrinfo(ares_channel channel,
struct ares_addrinfo_node *ai_node);
ares_status_t ares__readaddrinfo(FILE *fp, const char *name,
unsigned short port,
const struct ares_addrinfo_hints *hints,
struct ares_addrinfo *ai);
void ares__freeaddrinfo_nodes(struct ares_addrinfo_node *ai_node);
@ -460,6 +462,28 @@ 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);
ares_bool_t ares__is_hostnamech(int ch);
struct ares_hosts_entry;
typedef struct ares_hosts_entry ares_hosts_entry_t;
void ares__hosts_file_destroy(ares_hosts_file_t *hf);
ares_status_t ares__hosts_search_ipaddr(ares_channel channel,
ares_bool_t use_env, const char *ipaddr,
const ares_hosts_entry_t **entry);
ares_status_t ares__hosts_search_host(ares_channel channel,
ares_bool_t use_env, const char *host,
const ares_hosts_entry_t **entry);
ares_status_t ares__hosts_entry_to_hostent(const ares_hosts_entry_t *entry,
int family,
struct hostent **hostent);
ares_status_t ares__hosts_entry_to_addrinfo(const ares_hosts_entry_t *entry,
const char *name,
int family,
unsigned short port,
ares_bool_t want_cnames,
struct ares_addrinfo *ai);
#define ARES_SWAP_BYTE(a, b) \
do { \

@ -84,6 +84,9 @@
# define HAVE_WS2TCPIP_H 1
#endif
#define HAVE_SYS_TYPES_H 1
#define HAVE_SYS_STAT_H 1
/* ---------------------------------------------------------------- */
/* OTHER HEADER INFO */
/* ---------------------------------------------------------------- */

@ -340,66 +340,8 @@ TEST_F(LibraryTest, ReadLineNoBuf) {
ares_free(buf);
}
TEST(Misc, GetHostent) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"
"#comment\n"
"4.5.6.7\n"
"1.3.5.7 \n"
"::1 ipv6.com");
struct hostent *host = nullptr;
FILE *fp = fopen(hostsfile.filename(), "r");
ASSERT_NE(nullptr, fp);
EXPECT_EQ(ARES_EBADFAMILY, ares__get_hostent(fp, AF_INET+AF_INET6, &host));
rewind(fp);
EXPECT_EQ(ARES_SUCCESS, ares__get_hostent(fp, AF_INET, &host));
ASSERT_NE(nullptr, host);
std::stringstream ss1;
ss1 << HostEnt(host);
EXPECT_EQ("{'example.com' aliases=[] addrs=[1.2.3.4]}", ss1.str());
ares_free_hostent(host);
host = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares__get_hostent(fp, AF_INET, &host));
ASSERT_NE(nullptr, host);
std::stringstream ss2;
ss2 << HostEnt(host);
EXPECT_EQ("{'google.com' aliases=[www.google.com, www2.google.com] addrs=[2.3.4.5]}", ss2.str());
ares_free_hostent(host);
host = nullptr;
EXPECT_EQ(ARES_EOF, ares__get_hostent(fp, AF_INET, &host));
rewind(fp);
EXPECT_EQ(ARES_SUCCESS, ares__get_hostent(fp, AF_INET6, &host));
ASSERT_NE(nullptr, host);
std::stringstream ss3;
ss3 << HostEnt(host);
EXPECT_EQ("{'ipv6.com' aliases=[] addrs=[0000:0000:0000:0000:0000:0000:0000:0001]}", ss3.str());
ares_free_hostent(host);
host = nullptr;
EXPECT_EQ(ARES_EOF, ares__get_hostent(fp, AF_INET6, &host));
fclose(fp);
}
TEST_F(LibraryTest, GetHostentAllocFail) {
TempFile hostsfile("1.2.3.4 example.com alias1 alias2\n");
struct hostent *host = nullptr;
FILE *fp = fopen(hostsfile.filename(), "r");
ASSERT_NE(nullptr, fp);
for (int ii = 1; ii <= 8; ii++) {
rewind(fp);
ClearFails();
SetAllocFail(ii);
host = nullptr;
EXPECT_EQ(ARES_ENOMEM, ares__get_hostent(fp, AF_INET, &host)) << ii;
}
fclose(fp);
}
TEST_F(DefaultChannelTest, GetAddrInfoHostsPositive) {
TEST_F(FileChannelTest, GetAddrInfoHostsPositive) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"
"#comment\n"
@ -419,7 +361,7 @@ TEST_F(DefaultChannelTest, GetAddrInfoHostsPositive) {
EXPECT_EQ("{example.com addr=[1.2.3.4]}", ss.str());
}
TEST_F(DefaultChannelTest, GetAddrInfoHostsSpaces) {
TEST_F(FileChannelTest, GetAddrInfoHostsSpaces) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"
"#comment\n"
@ -439,7 +381,7 @@ TEST_F(DefaultChannelTest, GetAddrInfoHostsSpaces) {
EXPECT_EQ("{www.google.com->google.com, www2.google.com->google.com addr=[2.3.4.5]}", ss.str());
}
TEST_F(DefaultChannelTest, GetAddrInfoHostsByALias) {
TEST_F(FileChannelTest, GetAddrInfoHostsByALias) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"
"#comment\n"
@ -459,7 +401,7 @@ TEST_F(DefaultChannelTest, GetAddrInfoHostsByALias) {
EXPECT_EQ("{www.google.com->google.com, www2.google.com->google.com addr=[2.3.4.5]}", ss.str());
}
TEST_F(DefaultChannelTest, GetAddrInfoHostsIPV6) {
TEST_F(FileChannelTest, GetAddrInfoHostsIPV6) {
TempFile hostsfile("1.2.3.4 example.com \n"
" 2.3.4.5\tgoogle.com www.google.com\twww2.google.com\n"
"#comment\n"
@ -479,25 +421,34 @@ TEST_F(DefaultChannelTest, GetAddrInfoHostsIPV6) {
EXPECT_EQ("{ipv6.com addr=[[0000:0000:0000:0000:0000:0000:0000:0001]]}", ss.str());
}
TEST_F(LibraryTest, GetAddrInfoAllocFail) {
TEST_F(FileChannelTest, GetAddrInfoAllocFail) {
TempFile hostsfile("1.2.3.4 example.com alias1 alias2\n");
EnvValue with_env("CARES_HOSTS", hostsfile.filename());
struct ares_addrinfo_hints hints;
unsigned short port = 80;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
FILE *fp = fopen(hostsfile.filename(), "r");
ASSERT_NE(nullptr, fp);
for (int ii = 1; ii <= 3; ii++) {
rewind(fp);
// Fail a variety of different memory allocations, and confirm
// that the operation either fails with ENOMEM or succeeds
// with the expected result.
const int kCount = 34;
AddrInfoResult results[kCount];
for (int ii = 1; ii <= kCount; ii++) {
AddrInfoResult* result = &(results[ii - 1]);
ClearFails();
SetAllocFail(ii);
struct ares_addrinfo ai;
EXPECT_EQ(ARES_ENOMEM, ares__readaddrinfo(fp, "example.com", port, &hints, &ai)) << ii;
ares_getaddrinfo(channel_, "example.com", NULL, &hints, AddrInfoCallback, result);
Process();
EXPECT_TRUE(result->done_);
if (result->status_ == ARES_SUCCESS) {
std::stringstream ss;
ss << result->ai_;
EXPECT_EQ("{alias1->example.com, alias2->example.com addr=[1.2.3.4]}", ss.str()) << " failed alloc #" << ii;
if (verbose) std::cerr << "Succeeded despite failure of alloc #" << ii << std::endl;
}
}
fclose(fp);
}
TEST(Misc, OnionDomain) {

7
test/ares-test.cc vendored

@ -187,6 +187,10 @@ void DefaultChannelTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
void FileChannelTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
void DefaultChannelModeTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
@ -725,7 +729,8 @@ void AddrInfoCallback(void *data, int status, int timeouts,
result->done_ = true;
result->status_ = status;
result->timeouts_= timeouts;
result->ai_ = AddrInfo(ai);
if (ai)
result->ai_ = AddrInfo(ai);
if (verbose) std::cerr << "AddrInfoCallback(" << *result << ")" << std::endl;
}

26
test/ares-test.h vendored

@ -129,6 +129,32 @@ protected:
ares_channel channel_;
};
// Test fixture that uses a file-only channel.
class FileChannelTest : public LibraryTest {
public:
FileChannelTest() : channel_(nullptr)
{
struct ares_options opts = { 0 };
opts.lookups = strdup("f");
int optmask = ARES_OPT_LOOKUPS;
EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel_, &opts, optmask));
EXPECT_NE(nullptr, channel_);
free(opts.lookups);
}
~FileChannelTest()
{
ares_destroy(channel_);
channel_ = nullptr;
}
// Process all pending work on ares-owned file descriptors.
void Process();
protected:
ares_channel channel_;
};
// Test fixture that uses a default channel with the specified lookup mode.
class DefaultChannelModeTest
: public LibraryTest,

Loading…
Cancel
Save