Auto reload config on changes (requires EventThread) (#759)

Automatically detect configuration changes and reload. On systems which
provide notification mechanisms, use those, otherwise fallback to
polling. When a system configuration change is detected, it
asynchronously applies the configuration in order to ensure it is a
non-blocking operation for any queries which may still be being
processed.

On Windows, however, changes aren't detected if a user manually
sets/changes the DNS servers on an interface, it doesn't appear there is
any mechanism capable of this. We are relying on
`NotifyIpInterfaceChange()` for notifications.

Fixes Issue: #613
Fix By: Brad House (@bradh352)
pull/762/head
Brad House 6 months ago committed by GitHub
parent 2f200b9170
commit 8d80486e04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 11
      .github/workflows/netbsd.yml
  2. 9
      .github/workflows/openbsd.yml
  3. 3
      docs/ares_init_options.3
  4. 11
      docs/ares_reinit.3
  5. 3
      src/lib/Makefile.inc
  6. 195
      src/lib/ares__htable_vpvp.c
  7. 127
      src/lib/ares__htable_vpvp.h
  8. 17
      src/lib/ares_destroy.c
  9. 20
      src/lib/ares_event.h
  10. 565
      src/lib/ares_event_configchg.c
  11. 2
      src/lib/ares_event_epoll.c
  12. 2
      src/lib/ares_event_kqueue.c
  13. 6
      src/lib/ares_event_poll.c
  14. 12
      src/lib/ares_event_select.c
  15. 45
      src/lib/ares_event_thread.c
  16. 4
      src/lib/ares_getnameinfo.c
  17. 68
      src/lib/ares_init.c
  18. 9
      src/lib/ares_ipv6.h
  19. 8
      src/lib/ares_private.h
  20. 1
      src/lib/ares_sysconfig.c
  21. 6
      src/lib/ares_sysconfig_mac.c
  22. 4
      test/CMakeLists.txt
  23. 5
      test/Makefile.am
  24. 2
      test/Makefile.inc
  25. 222
      test/ares-test-internal.cc
  26. 136
      test/ares_queryloop.c

@ -2,7 +2,13 @@
# SPDX-License-Identifier: MIT
name: NetBSD
on: [push]
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
@ -18,11 +24,12 @@ jobs:
CMAKE_FLAGS: "-DCMAKE_BUILD_TYPE=DEBUG -DCARES_STATIC=ON -DCARES_STATIC_PIC=ON -G Ninja"
CMAKE_TEST_FLAGS: "-DCARES_BUILD_TESTS=ON"
PKG_PATH: https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.0/All/
TEST_FILTER: "--gtest_filter=-*LiveSearchTXT*:*LiveSearchANY*:*Parallel*"
with:
operating_system: netbsd
version: '10.0'
shell: 'bash'
environment_variables: DIST BUILD_TYPE CMAKE_FLAGS CMAKE_TEST_FLAGS PKG_PATH
environment_variables: DIST BUILD_TYPE CMAKE_FLAGS CMAKE_TEST_FLAGS PKG_PATH TEST_FILTER
run: |
echo "BUILD_TYPE: $BUILD_TYPE"
echo "CMAKE_FLAGS: $CMAKE_FLAGS"

@ -1,8 +1,13 @@
# Copyright (C) The c-ares project and its contributors
# SPDX-License-Identifier: MIT
name: OpenBSD
on: [push]
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:

@ -315,6 +315,9 @@ When enabled, the integrator is no longer responsible for notifying c-ares of
any events on the file descriptors, so \fIares_process(3)\fP nor
\fIares_process_fd(3)\fP should ever be called when this option is enabled.
As of c-ares 1.29.0, when enabled, it will also automatically re-load the
system configuration when changes are detected.
Use \fIares_threadsafety(3)\fP to determine if this option is available to be
used.

@ -21,10 +21,13 @@ Any existing queries will be automatically requeued if the server they are
currently assigned to is removed from the system configuration.
This function may cause additional file descriptors to be created, and existing
ones to be destroyed if server configuration has changed. If this is called from
a thread other than which the main program event loop is running, care needs to
be taken to ensure any file descriptor lists are updated immediately within
the eventloop.
ones to be destroyed if server configuration has changed.
\Bares_reinit(3)\fP, when compiled with thread safety, will spawn a background
thread to read the configuration and apply it. It is crucial that developers
use the \fBARES_OPT_SOCK_STATE_CB\fP or \fBARES_OPT_EVENT_THREAD\fP so that
notifications of changes are alerted. If using \fBares_getsock(3)\fP or
\fBares_fds(3)\fP, no notification is possible which could cause a stall.
.SH RETURN VALUES
\fIares_reinit(3)\fP can return any of the following values:

@ -10,6 +10,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares__htable_asvp.c \
ares__htable_strvp.c \
ares__htable_szvp.c \
ares__htable_vpvp.c \
ares__iface_ips.c \
ares__llist.c \
ares__parse_into_addrinfo.c \
@ -27,6 +28,7 @@ CSOURCES = ares__addrinfo2hostent.c \
ares_dns_parse.c \
ares_dns_record.c \
ares_dns_write.c \
ares_event_configchg.c \
ares_event_epoll.c \
ares_event_kqueue.c \
ares_event_poll.c \
@ -88,6 +90,7 @@ HHEADERS = ares__buf.h \
ares__htable_asvp.h \
ares__htable_strvp.h \
ares__htable_szvp.h \
ares__htable_vpvp.h \
ares__iface_ips.h \
ares__llist.h \
ares__slist.h \

@ -0,0 +1,195 @@
/* MIT License
*
* Copyright (c) 2024 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_vpvp.h"
struct ares__htable_vpvp {
ares__htable_vpvp_key_free_t free_key;
ares__htable_vpvp_val_free_t free_val;
ares__htable_t *hash;
};
typedef struct {
void *key;
void *val;
ares__htable_vpvp_t *parent;
} ares__htable_vpvp_bucket_t;
void ares__htable_vpvp_destroy(ares__htable_vpvp_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)
{
return ares__htable_hash_FNV1a((const unsigned char *)&key, sizeof(key),
seed);
}
static const void *bucket_key(const void *bucket)
{
const ares__htable_vpvp_bucket_t *arg = bucket;
return arg->key;
}
static void bucket_free(void *bucket)
{
ares__htable_vpvp_bucket_t *arg = bucket;
if (arg->parent->free_key) {
arg->parent->free_key(arg->key);
}
if (arg->parent->free_val) {
arg->parent->free_val(arg->val);
}
ares_free(arg);
}
static ares_bool_t key_eq(const void *key1, const void *key2)
{
if (key1 == key2) {
return ARES_TRUE;
}
return ARES_FALSE;
}
ares__htable_vpvp_t *
ares__htable_vpvp_create(ares__htable_vpvp_key_free_t key_free,
ares__htable_vpvp_val_free_t val_free)
{
ares__htable_vpvp_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_key = key_free;
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_vpvp_insert(ares__htable_vpvp_t *htable, void *key,
void *val)
{
ares__htable_vpvp_bucket_t *bucket = NULL;
if (htable == NULL) {
goto fail;
}
bucket = ares_malloc(sizeof(*bucket));
if (bucket == NULL) {
goto fail;
}
bucket->parent = htable;
bucket->key = key;
bucket->val = val;
if (!ares__htable_insert(htable->hash, bucket)) {
goto fail;
}
return ARES_TRUE;
fail:
if (bucket) {
ares_free(bucket);
}
return ARES_FALSE;
}
ares_bool_t ares__htable_vpvp_get(const ares__htable_vpvp_t *htable, void *key,
void **val)
{
ares__htable_vpvp_bucket_t *bucket = NULL;
if (val) {
*val = NULL;
}
if (htable == 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_vpvp_get_direct(const ares__htable_vpvp_t *htable, void *key)
{
void *val = NULL;
ares__htable_vpvp_get(htable, key, &val);
return val;
}
ares_bool_t ares__htable_vpvp_remove(ares__htable_vpvp_t *htable, void *key)
{
if (htable == NULL) {
return ARES_FALSE;
}
return ares__htable_remove(htable->hash, key);
}
size_t ares__htable_vpvp_num_keys(const ares__htable_vpvp_t *htable)
{
if (htable == NULL) {
return 0;
}
return ares__htable_num_keys(htable->hash);
}

@ -0,0 +1,127 @@
/* MIT License
*
* Copyright (c) 2024 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_VPVP_H
#define __ARES__HTABLE_VPVP_H
/*! \addtogroup ares__htable_vpvp HashTable with void pointer 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 size_t and void pointer, respectively.
*
* Average time complexity:
* - Insert: O(1)
* - Search: O(1)
* - Delete: O(1)
*
* @{
*/
struct ares__htable_vpvp;
/*! Opaque data type for size_t key, void pointer hash table implementation */
typedef struct ares__htable_vpvp ares__htable_vpvp_t;
/*! Callback to free key stored in hashtable
*
* \param[in] key user-supplied key
*/
typedef void (*ares__htable_vpvp_key_free_t)(void *key);
/*! Callback to free value stored in hashtable
*
* \param[in] val user-supplied value
*/
typedef void (*ares__htable_vpvp_val_free_t)(void *val);
/*! Destroy hashtable
*
* \param[in] htable Initialized hashtable
*/
void ares__htable_vpvp_destroy(ares__htable_vpvp_t *htable);
/*! Create size_t key, void pointer value hash table
*
* \param[in] key_free Optional. Call back to free user-supplied key. If
* NULL it is expected the caller will clean up any user
* supplied keys.
* \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_vpvp_t *
ares__htable_vpvp_create(ares__htable_vpvp_key_free_t key_free,
ares__htable_vpvp_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_vpvp_insert(ares__htable_vpvp_t *htable, void *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_vpvp_get(const ares__htable_vpvp_t *htable, void *key,
void **val);
/*! Retrieve value from hashtable directly as return value. Caveat to this
* function over ares__htable_vpvp_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_vpvp_get_direct(const ares__htable_vpvp_t *htable,
void *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_vpvp_remove(ares__htable_vpvp_t *htable, void *key);
/*! Retrieve the number of keys stored in the hash table
*
* \param[in] htable Initialized hash table
* \return count
*/
size_t ares__htable_vpvp_num_keys(const ares__htable_vpvp_t *htable);
/*! @} */
#endif /* __ARES__HTABLE_VPVP_H */

@ -31,6 +31,7 @@
#include "ares.h"
#include "ares_private.h"
#include "ares_event.h"
void ares_destroy(ares_channel_t *channel)
{
@ -41,6 +42,22 @@ void ares_destroy(ares_channel_t *channel)
return;
}
/* Disable configuration change monitoring */
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
ares_event_thread_t *e = channel->sock_state_cb_data;
if (e && e->configchg) {
ares_event_configchg_destroy(e->configchg);
e->configchg = NULL;
}
}
/* Wait for reinit thread to exit if there was one pending */
if (channel->reinit_thread != NULL) {
void *rv;
ares__thread_join(channel->reinit_thread, &rv);
channel->reinit_thread = NULL;
}
/* Lock because callbacks will be triggered */
ares__channel_lock(channel);

@ -79,6 +79,14 @@ typedef struct {
size_t (*wait)(ares_event_thread_t *e, unsigned long timeout_ms);
} ares_event_sys_t;
struct ares_event_configchg;
typedef struct ares_event_configchg ares_event_configchg_t;
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e);
void ares_event_configchg_destroy(ares_event_configchg_t *configchg);
struct ares_event_thread {
/*! Whether the event thread should be online or not. Checked on every wake
* event before sleeping. */
@ -94,13 +102,18 @@ struct ares_event_thread {
* thread other than the event thread itself. The event thread will then
* be woken then process these updates itself */
ares__llist_t *ev_updates;
/*! Registered event handles. */
ares__htable_asvp_t *ev_handles;
/*! Registered socket event handles */
ares__htable_asvp_t *ev_sock_handles;
/*! Registered custom event handles. Typically used for external triggering.
*/
ares__htable_vpvp_t *ev_cust_handles;
/*! Pointer to the event handle which is used to signal and wake the event
* thread itself. This is needed to be able to do things like update the
* file descriptors being waited on and to wake the event subsystem during
* shutdown */
ares_event_t *ev_signal;
/*! Handle for configuration change monitoring */
ares_event_configchg_t *configchg;
/* Event subsystem callbacks */
const ares_event_sys_t *ev_sys;
/* Event subsystem private data */
@ -124,7 +137,8 @@ struct ares_event_thread {
* Non-socket events cannot be removed, and must have
* ARES_EVENT_FLAG_OTHER set.
* \param[in] cb Callback to call when
* event is triggered. Required. Not allowed to be
* event is triggered. Required if flags is not
* ARES_EVENT_FLAG_NONE. Not allowed to be
* changed, ignored on modification.
* \param[in] fd File descriptor/socket to monitor. May
* be ARES_SOCKET_BAD if not monitoring file

@ -0,0 +1,565 @@
/* MIT License
*
* Copyright (c) 2024 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_event.h"
static void ares_event_configchg_reload(ares_event_thread_t *e)
{
ares_reinit(e->channel);
}
#ifdef __linux__
# include <sys/inotify.h>
struct ares_event_configchg {
int inotify_fd;
ares_event_thread_t *e;
};
void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
{
if (configchg == NULL) {
return;
}
/* Tell event system to stop monitoring for changes. This will cause the
* cleanup to be called */
ares_event_update(NULL, configchg->e, ARES_EVENT_FLAG_NONE, NULL,
configchg->inotify_fd, NULL, NULL, NULL);
}
static void ares_event_configchg_free(void *data)
{
ares_event_configchg_t *configchg = data;
if (configchg == NULL) {
return;
}
if (configchg->inotify_fd >= 0) {
close(configchg->inotify_fd);
configchg->inotify_fd = -1;
}
ares_free(configchg);
}
static void ares_event_configchg_cb(ares_event_thread_t *e, ares_socket_t fd,
void *data, ares_event_flags_t flags)
{
ares_event_configchg_t *configchg = data;
/* Some systems cannot read integer variables if they are not
* properly aligned. On other systems, incorrect alignment may
* decrease performance. Hence, the buffer used for reading from
* the inotify file descriptor should have the same alignment as
* struct inotify_event. */
unsigned char buf[4096]
__attribute__((aligned(__alignof__(struct inotify_event))));
const struct inotify_event *event;
ssize_t len;
ares_bool_t triggered = ARES_FALSE;
(void)fd;
(void)flags;
while (1) {
const unsigned char *ptr;
len = read(configchg->inotify_fd, buf, sizeof(buf));
if (len <= 0) {
break;
}
/* Loop over all events in the buffer. Says kernel will check the buffer
* size provided, so I assume it won't ever return partial events. */
for (ptr = buf; ptr < buf + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event *)ptr;
if (ares_strlen(event->name) == 0) {
continue;
}
if (strcasecmp(event->name, "resolv.conf") == 0 ||
strcasecmp(event->name, "nsswitch.conf") == 0) {
triggered = ARES_TRUE;
}
}
}
/* Only process after all events are read. No need to process more often as
* we don't want to reload the config back to back */
if (triggered) {
ares_event_configchg_reload(e);
}
}
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e)
{
ares_status_t status = ARES_SUCCESS;
ares_event_configchg_t *c;
(void)e;
/* Not used by this implementation */
*configchg = NULL;
c = ares_malloc_zero(sizeof(*c));
if (c == NULL) {
return ARES_ENOMEM;
}
c->e = e;
c->inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
if (c->inotify_fd == -1) {
status = ARES_ESERVFAIL;
goto done;
}
/* We need to monitor /etc/resolv.conf, /etc/nsswitch.conf */
if (inotify_add_watch(c->inotify_fd, "/etc",
IN_CREATE | IN_MODIFY | IN_MOVED_TO | IN_ONLYDIR) ==
-1) {
status = ARES_ESERVFAIL;
goto done;
}
status =
ares_event_update(NULL, e, ARES_EVENT_FLAG_READ, ares_event_configchg_cb,
c->inotify_fd, c, ares_event_configchg_free, NULL);
done:
if (status != ARES_SUCCESS) {
ares_event_configchg_free(c);
}
return status;
}
#elif defined(_WIN32)
# include <winsock2.h>
# include <iphlpapi.h>
# include <stdio.h>
# include <windows.h>
struct ares_event_configchg {
HANDLE ifchg_hnd;
ares_event_thread_t *e;
};
void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
{
# ifdef __WATCOMC__
/* Not supported */
# else
if (configchg == NULL) {
return;
}
if (configchg->ifchg_hnd != NULL) {
CancelMibChangeNotify2(configchg->ifchg_hnd);
configchg->ifchg_hnd = NULL;
}
ares_free(configchg);
# endif
}
# ifndef __WATCOMC__
static void ares_event_configchg_cb(PVOID CallerContext,
PMIB_IPINTERFACE_ROW Row,
MIB_NOTIFICATION_TYPE NotificationType)
{
ares_event_configchg_t *configchg = CallerContext;
(void)Row;
(void)NotificationType;
ares_event_configchg_reload(configchg->e);
}
# endif
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e)
{
# ifdef __WATCOMC__
return ARES_ENOTIMP;
# else
ares_status_t status = ARES_SUCCESS;
*configchg = ares_malloc_zero(sizeof(**configchg));
if (*configchg == NULL) {
return ARES_ENOMEM;
}
(*configchg)->e = e;
/* NOTE: If a user goes into the control panel and changes the network
* adapter DNS addresses manually, this will NOT trigger a notification.
* We've also tried listening on NotifyUnicastIpAddressChange(), but
* that didn't get triggered either.
*/
if (NotifyIpInterfaceChange(
AF_UNSPEC, (PIPINTERFACE_CHANGE_CALLBACK)ares_event_configchg_cb,
*configchg, FALSE, &(*configchg)->ifchg_hnd) != NO_ERROR) {
status = ARES_ESERVFAIL;
goto done;
}
done:
if (status != ARES_SUCCESS) {
ares_event_configchg_destroy(*configchg);
*configchg = NULL;
}
return status;
# endif
}
#elif defined(__APPLE__)
# include <sys/types.h>
# include <unistd.h>
# include <notify.h>
# include <dlfcn.h>
struct ares_event_configchg {
int fd;
int token;
};
void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
{
(void)configchg;
/* Cleanup happens automatically */
}
static void ares_event_configchg_free(void *data)
{
ares_event_configchg_t *configchg = data;
if (configchg == NULL) {
return;
}
if (configchg->fd >= 0) {
notify_cancel(configchg->token);
/* automatically closes fd */
configchg->fd = -1;
}
ares_free(configchg);
}
static void ares_event_configchg_cb(ares_event_thread_t *e, ares_socket_t fd,
void *data, ares_event_flags_t flags)
{
ares_event_configchg_t *configchg = data;
ares_bool_t triggered = ARES_FALSE;
(void)fd;
(void)flags;
while (1) {
int t = 0;
ssize_t len;
len = read(configchg->fd, &t, sizeof(t));
if (len < (ssize_t)sizeof(t)) {
break;
}
/* Token is read in network byte order (yeah, docs don't mention this) */
t = (int)ntohl(t);
if (t != configchg->token) {
continue;
}
triggered = ARES_TRUE;
}
/* Only process after all events are read. No need to process more often as
* we don't want to reload the config back to back */
if (triggered) {
ares_event_configchg_reload(e);
}
}
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e)
{
ares_status_t status = ARES_SUCCESS;
void *handle = NULL;
const char *(*pdns_configuration_notify_key)(void) = NULL;
const char *notify_key = NULL;
int flags;
*configchg = ares_malloc_zero(sizeof(**configchg));
if (*configchg == NULL) {
return ARES_ENOMEM;
}
/* Load symbol as it isn't normally public */
handle = dlopen("/usr/lib/libSystem.dylib", RTLD_LAZY | RTLD_NOLOAD);
if (handle == NULL) {
status = ARES_ESERVFAIL;
goto done;
}
pdns_configuration_notify_key = dlsym(handle, "dns_configuration_notify_key");
if (pdns_configuration_notify_key == NULL) {
status = ARES_ESERVFAIL;
goto done;
}
notify_key = pdns_configuration_notify_key();
if (notify_key == NULL) {
status = ARES_ESERVFAIL;
goto done;
}
if (notify_register_file_descriptor(notify_key, &(*configchg)->fd, 0,
&(*configchg)->token) !=
NOTIFY_STATUS_OK) {
status = ARES_ESERVFAIL;
goto done;
}
/* Set file descriptor to non-blocking */
flags = fcntl((*configchg)->fd, F_GETFL, 0);
fcntl((*configchg)->fd, F_SETFL, flags | O_NONBLOCK);
/* Register file descriptor with event subsystem */
status = ares_event_update(NULL, e, ARES_EVENT_FLAG_READ,
ares_event_configchg_cb, (*configchg)->fd,
*configchg, ares_event_configchg_free, NULL);
done:
if (status != ARES_SUCCESS) {
ares_event_configchg_free(*configchg);
*configchg = NULL;
}
if (handle) {
dlclose(handle);
}
return status;
}
#elif defined(HAVE_STAT)
# ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
# endif
# ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
# endif
typedef struct {
size_t size;
time_t mtime;
} fileinfo_t;
struct ares_event_configchg {
ares_bool_t isup;
ares__thread_t *thread;
ares__htable_strvp_t *filestat;
ares__thread_mutex_t *lock;
ares__thread_cond_t *wake;
const char *resolvconf_path;
ares_event_thread_t *e;
};
static ares_status_t config_change_check(ares__htable_strvp_t *filestat,
const char *resolvconf_path)
{
size_t i;
const char *configfiles[] = { resolvconf_path, "/etc/nsswitch.conf",
"/etc/netsvc.conf", "/etc/svc.conf", NULL };
ares_bool_t changed = ARES_FALSE;
for (i = 0; configfiles[i] != NULL; i++) {
fileinfo_t *fi = ares__htable_strvp_get_direct(filestat, configfiles[i]);
struct stat st;
if (stat(configfiles[i], &st) == 0) {
if (fi == NULL) {
fi = ares_malloc_zero(sizeof(*fi));
if (fi == NULL) {
return ARES_ENOMEM;
}
if (!ares__htable_strvp_insert(filestat, configfiles[i], fi)) {
ares_free(fi);
return ARES_ENOMEM;
}
}
if (fi->size != (size_t)st.st_size || fi->mtime != (time_t)st.st_mtime) {
changed = ARES_TRUE;
}
fi->size = (size_t)st.st_size;
fi->mtime = (time_t)st.st_mtime;
} else if (fi != NULL) {
/* File no longer exists, remove */
ares__htable_strvp_remove(filestat, configfiles[i]);
changed = ARES_TRUE;
}
}
if (changed) {
return ARES_SUCCESS;
}
return ARES_ENOTFOUND;
}
static void *ares_event_configchg_thread(void *arg)
{
ares_event_configchg_t *c = arg;
ares__thread_mutex_lock(c->lock);
while (c->isup) {
ares_status_t status;
if (ares__thread_cond_timedwait(c->wake, c->lock, 30000) != ARES_ETIMEOUT) {
continue;
}
/* make sure status didn't change even though we got a timeout */
if (!c->isup) {
break;
}
status = config_change_check(c->filestat, c->resolvconf_path);
if (status == ARES_SUCCESS) {
ares_event_configchg_reload(c->e);
}
}
ares__thread_mutex_unlock(c->lock);
return NULL;
}
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e)
{
ares_status_t status = ARES_SUCCESS;
ares_event_configchg_t *c = NULL;
*configchg = NULL;
c = ares_malloc_zero(sizeof(*c));
if (c == NULL) {
status = ARES_ENOMEM;
goto done;
}
c->e = e;
c->filestat = ares__htable_strvp_create(ares_free);
if (c->filestat == NULL) {
status = ARES_ENOMEM;
goto done;
}
c->wake = ares__thread_cond_create();
if (c->wake == NULL) {
status = ARES_ENOMEM;
goto done;
}
c->resolvconf_path = c->e->channel->resolvconf_path;
if (c->resolvconf_path == NULL) {
c->resolvconf_path = PATH_RESOLV_CONF;
}
status = config_change_check(c->filestat, c->resolvconf_path);
if (status == ARES_ENOMEM) {
goto done;
}
c->isup = ARES_TRUE;
status = ares__thread_create(&c->thread, ares_event_configchg_thread, c);
done:
if (status != ARES_SUCCESS) {
ares_event_configchg_destroy(c);
} else {
*configchg = c;
}
return status;
}
void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
{
if (configchg == NULL) {
return;
}
if (configchg->lock) {
ares__thread_mutex_lock(configchg->lock);
}
configchg->isup = ARES_FALSE;
if (configchg->wake) {
ares__thread_cond_signal(configchg->wake);
}
if (configchg->lock) {
ares__thread_mutex_unlock(configchg->lock);
}
if (configchg->thread) {
void *rv = NULL;
ares__thread_join(configchg->thread, &rv);
}
ares__thread_mutex_destroy(configchg->lock);
ares__thread_cond_destroy(configchg->wake);
ares__htable_strvp_destroy(configchg->filestat);
ares_free(configchg);
}
#else
ares_status_t ares_event_configchg_init(ares_event_configchg_t **configchg,
ares_event_thread_t *e)
{
/* No ability */
return ARES_ENOTIMP;
}
void ares_event_configchg_destroy(ares_event_configchg_t *configchg)
{
/* No-op */
}
#endif

@ -167,7 +167,7 @@ static size_t ares_evsys_epoll_wait(ares_event_thread_t *e,
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles,
ev = ares__htable_asvp_get_direct(e->ev_sock_handles,
(ares_socket_t)events[i].data.fd);
if (ev == NULL || ev->cb == NULL) {
continue;

@ -218,7 +218,7 @@ static size_t ares_evsys_kqueue_wait(ares_event_thread_t *e,
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles,
ev = ares__htable_asvp_get_direct(e->ev_sock_handles,
(ares_socket_t)events[i].ident);
if (ev == NULL || ev->cb == NULL) {
continue;

@ -69,7 +69,7 @@ static size_t ares_evsys_poll_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
size_t num_fds = 0;
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_handles, &num_fds);
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_sock_handles, &num_fds);
struct pollfd *pollfd = NULL;
int rv;
size_t cnt = 0;
@ -82,7 +82,7 @@ static size_t ares_evsys_poll_wait(ares_event_thread_t *e,
}
for (i = 0; i < num_fds; i++) {
const ares_event_t *ev =
ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
ares__htable_asvp_get_direct(e->ev_sock_handles, fdlist[i]);
pollfd[i].fd = ev->fd;
if (ev->flags & ARES_EVENT_FLAG_READ) {
pollfd[i].events |= POLLIN;
@ -109,7 +109,7 @@ static size_t ares_evsys_poll_wait(ares_event_thread_t *e,
cnt++;
ev = ares__htable_asvp_get_direct(e->ev_handles, pollfd[i].fd);
ev = ares__htable_asvp_get_direct(e->ev_sock_handles, pollfd[i].fd);
if (ev == NULL || ev->cb == NULL) {
continue;
}

@ -23,6 +23,12 @@
*
* SPDX-License-Identifier: MIT
*/
/* Some systems might default to something low like 256 (NetBSD), lets define
* this to assist. Really, no one should be using select, but lets be safe
* anyhow */
#define FD_SETSIZE 4096
#include "ares_setup.h"
#include "ares.h"
#include "ares_private.h"
@ -71,7 +77,7 @@ static size_t ares_evsys_select_wait(ares_event_thread_t *e,
unsigned long timeout_ms)
{
size_t num_fds = 0;
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_handles, &num_fds);
ares_socket_t *fdlist = ares__htable_asvp_keys(e->ev_sock_handles, &num_fds);
int rv;
size_t cnt = 0;
size_t i;
@ -86,7 +92,7 @@ static size_t ares_evsys_select_wait(ares_event_thread_t *e,
for (i = 0; i < num_fds; i++) {
const ares_event_t *ev =
ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
ares__htable_asvp_get_direct(e->ev_sock_handles, fdlist[i]);
if (ev->flags & ARES_EVENT_FLAG_READ) {
FD_SET(ev->fd, &read_fds);
}
@ -110,7 +116,7 @@ static size_t ares_evsys_select_wait(ares_event_thread_t *e,
ares_event_t *ev;
ares_event_flags_t flags = 0;
ev = ares__htable_asvp_get_direct(e->ev_handles, fdlist[i]);
ev = ares__htable_asvp_get_direct(e->ev_sock_handles, fdlist[i]);
if (ev == NULL || ev->cb == NULL) {
continue;
}

@ -89,7 +89,12 @@ ares_status_t ares_event_update(ares_event_t **event, ares_event_thread_t *e,
{
ares_event_t *ev = NULL;
if (e == NULL || cb == NULL) {
if (e == NULL) {
return ARES_EFORMERR;
}
/* Callback must be specified if not a removal event. */
if (flags != ARES_EVENT_FLAG_NONE && cb == NULL) {
return ARES_EFORMERR;
}
@ -211,8 +216,13 @@ static void ares_event_process_updates(ares_event_thread_t *e)
* list */
while ((node = ares__llist_node_first(e->ev_updates)) != NULL) {
ares_event_t *newev = ares__llist_node_claim(node);
ares_event_t *oldev =
ares__htable_asvp_get_direct(e->ev_handles, newev->fd);
ares_event_t *oldev;
if (newev->fd == ARES_SOCKET_BAD) {
oldev = ares__htable_vpvp_get_direct(e->ev_cust_handles, newev->data);
} else {
oldev = ares__htable_asvp_get_direct(e->ev_sock_handles, newev->fd);
}
/* Adding new */
if (oldev == NULL) {
@ -225,7 +235,11 @@ static void ares_event_process_updates(ares_event_thread_t *e)
newev->e = NULL;
ares_event_destroy_cb(newev);
} else {
ares__htable_asvp_insert(e->ev_handles, newev->fd, newev);
if (newev->fd == ARES_SOCKET_BAD) {
ares__htable_vpvp_insert(e->ev_cust_handles, newev->data, newev);
} else {
ares__htable_asvp_insert(e->ev_sock_handles, newev->fd, newev);
}
}
continue;
}
@ -234,7 +248,11 @@ static void ares_event_process_updates(ares_event_thread_t *e)
if (newev->flags == ARES_EVENT_FLAG_NONE) {
/* the callback for the removal will call e->ev_sys->event_del(e, event)
*/
ares__htable_asvp_remove(e->ev_handles, newev->fd);
if (newev->fd == ARES_SOCKET_BAD) {
ares__htable_vpvp_remove(e->ev_cust_handles, newev->data);
} else {
ares__htable_asvp_remove(e->ev_sock_handles, newev->fd);
}
ares_free(newev);
continue;
}
@ -306,8 +324,11 @@ static void ares_event_thread_destroy_int(ares_event_thread_t *e)
ares__llist_destroy(e->ev_updates);
e->ev_updates = NULL;
ares__htable_asvp_destroy(e->ev_handles);
e->ev_handles = NULL;
ares__htable_asvp_destroy(e->ev_sock_handles);
e->ev_sock_handles = NULL;
ares__htable_vpvp_destroy(e->ev_cust_handles);
e->ev_cust_handles = NULL;
if (e->ev_sys && e->ev_sys->destroy) {
e->ev_sys->destroy(e);
@ -409,8 +430,14 @@ ares_status_t ares_event_thread_init(ares_channel_t *channel)
return ARES_ENOMEM;
}
e->ev_handles = ares__htable_asvp_create(ares_event_destroy_cb);
if (e->ev_handles == NULL) {
e->ev_sock_handles = ares__htable_asvp_create(ares_event_destroy_cb);
if (e->ev_sock_handles == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOMEM;
}
e->ev_cust_handles = ares__htable_vpvp_create(NULL, ares_event_destroy_cb);
if (e->ev_cust_handles == NULL) {
ares_event_thread_destroy_int(e);
return ARES_ENOMEM;
}

@ -99,11 +99,11 @@ static void ares_getnameinfo_int(ares_channel_t *channel,
/* Validate socket address family and length */
if (sa && sa->sa_family == AF_INET &&
salen >= sizeof(struct sockaddr_in)) {
salen >= (ares_socklen_t)sizeof(struct sockaddr_in)) {
addr = CARES_INADDR_CAST(struct sockaddr_in *, sa);
port = addr->sin_port;
} else if (sa && sa->sa_family == AF_INET6 &&
salen >= sizeof(struct sockaddr_in6)) {
salen >= (ares_socklen_t)sizeof(struct sockaddr_in6)) {
addr6 = CARES_INADDR_CAST(struct sockaddr_in6 *, sa);
port = addr6->sin6_port;
} else {

@ -65,6 +65,7 @@
#include "ares_inet_net_pton.h"
#include "ares_platform.h"
#include "ares_private.h"
#include "ares_event.h"
#ifdef WATT32
# undef WIN32 /* Redefined in MingW/MSVC headers */
@ -379,10 +380,21 @@ int ares_init_options(ares_channel_t **channelptr,
/* Initialize the event thread */
if (channel->optmask & ARES_OPT_EVENT_THREAD) {
ares_event_thread_t *e = NULL;
status = ares_event_thread_init(channel);
if (status != ARES_SUCCESS) {
goto done;
}
/* Initialize monitor for configuration changes. In some rare cases,
* ARES_ENOTIMP may occur (OpenWatcom), ignore this. */
e = channel->sock_state_cb_data;
status = ares_event_configchg_init(&e->configchg, e);
if (status != ARES_SUCCESS && status != ARES_ENOTIMP) {
goto done;
}
status = ARES_SUCCESS;
}
done:
@ -395,17 +407,13 @@ done:
return ARES_SUCCESS;
}
ares_status_t ares_reinit(ares_channel_t *channel)
static void *ares_reinit_thread(void *arg)
{
ares_channel_t *channel = arg;
ares_status_t status;
if (channel == NULL) {
return ARES_EFORMERR;
}
/* ares__init_by_sysconfig() will lock when applying the config, but not
* when retrieving. */
status = ares__init_by_sysconfig(channel);
if (status != ARES_SUCCESS) {
DEBUGF(fprintf(stderr, "Error: init_by_sysconfig failed: %s\n",
@ -415,15 +423,61 @@ ares_status_t ares_reinit(ares_channel_t *channel)
ares__channel_lock(channel);
/* Flush cached queries on reinit */
if (channel->qcache) {
if (status == ARES_SUCCESS && channel->qcache) {
ares__qcache_flush(channel->qcache);
}
channel->reinit_pending = ARES_FALSE;
ares__channel_unlock(channel);
return NULL;
}
ares_status_t ares_reinit(ares_channel_t *channel)
{
ares_status_t status = ARES_SUCCESS;
if (channel == NULL) {
return ARES_EFORMERR;
}
ares__channel_lock(channel);
/* If a reinit is already in process, lets not do it again */
if (channel->reinit_pending) {
ares__channel_unlock(channel);
return ARES_SUCCESS;
}
channel->reinit_pending = ARES_TRUE;
ares__channel_unlock(channel);
if (ares_threadsafety()) {
/* clean up the prior reinit process's thread. We know the thread isn't
* running since reinit_pending was false */
if (channel->reinit_thread != NULL) {
void *rv;
ares__thread_join(channel->reinit_thread, &rv);
channel->reinit_thread = NULL;
}
/* Spawn a new thread */
status = ares__thread_create(&channel->reinit_thread, ares_reinit_thread,
channel);
if (status != ARES_SUCCESS) {
ares__channel_lock(channel);
channel->reinit_pending = ARES_FALSE;
ares__channel_unlock(channel);
}
} else {
/* Threading support not available, call directly */
ares_reinit_thread(channel);
}
return status;
}
/* ares_dup() duplicates a channel handle with all its options and returns a
new channel handle */
int ares_dup(ares_channel_t **dest, const ares_channel_t *src)

@ -31,6 +31,15 @@
# include <netinet6/in6.h>
#endif
#if defined(USE_WINSOCK)
# if defined(HAVE_IPHLPAPI_H)
# include <iphlpapi.h>
# endif
# if defined(HAVE_NETIOAPI_H)
# include <netioapi.h>
# endif
#endif
#ifndef HAVE_PF_INET6
# define PF_INET6 AF_INET6
#endif

@ -114,6 +114,7 @@ typedef struct ares_rand_state ares_rand_state;
#include "ares__htable_strvp.h"
#include "ares__htable_szvp.h"
#include "ares__htable_asvp.h"
#include "ares__htable_vpvp.h"
#include "ares__buf.h"
#include "ares_dns_private.h"
#include "ares__iface_ips.h"
@ -337,6 +338,13 @@ struct ares_channeldata {
/* Callback triggered when a server has a successful or failed response */
ares_server_state_callback server_state_cb;
void *server_state_cb_data;
/* TRUE if a reinit is pending. Reinit spawns a thread to read the system
* configuration and then apply the configuration since configuration
* reading may block. The thread handle is provided for waiting on thread
* exit. */
ares_bool_t reinit_pending;
ares__thread_t *reinit_thread;
};
/* Does the domain end in ".onion" or ".onion."? Case-insensitive. */

@ -1106,6 +1106,7 @@ ares_status_t ares__init_by_sysconfig(ares_channel_t *channel)
* lock prior to this. */
ares__channel_lock(channel);
status = ares_sysconfig_apply(channel, &sysconfig);
ares__channel_unlock(channel);

@ -58,7 +58,6 @@
typedef struct {
void *handle;
const char *(*dns_configuration_notify_key)(void);
dns_config_t *(*dns_configuration_copy)(void);
void (*dns_configuration_free)(dns_config_t *config);
} dnsinfo_t;
@ -101,15 +100,12 @@ static ares_status_t dnsinfo_init(dnsinfo_t **dnsinfo_out)
goto done;
}
dnsinfo->dns_configuration_notify_key =
dlsym(dnsinfo->handle, "dns_configuration_notify_key");
dnsinfo->dns_configuration_copy =
dlsym(dnsinfo->handle, "dns_configuration_copy");
dnsinfo->dns_configuration_free =
dlsym(dnsinfo->handle, "dns_configuration_free");
if (dnsinfo->dns_configuration_notify_key == NULL ||
dnsinfo->dns_configuration_copy == NULL ||
if (dnsinfo->dns_configuration_copy == NULL ||
dnsinfo->dns_configuration_free == NULL) {
status = ARES_ESERVFAIL;
goto done;

@ -56,6 +56,10 @@ add_executable(dnsdump ${DUMPSOURCES})
target_compile_definitions(dnsdump PRIVATE CARES_NO_DEPRECATED)
target_link_libraries(dnsdump PRIVATE caresinternal)
add_executable(ares_queryloop ${LOOPSOURCES})
target_compile_definitions(ares_queryloop PRIVATE CARES_NO_DEPRECATED)
target_link_libraries(ares_queryloop PRIVATE caresinternal)
# register tests
add_test(NAME arestest COMMAND $<TARGET_FILE:arestest>)

5
test/Makefile.am vendored

@ -19,7 +19,7 @@ include Makefile.inc
TESTS = arestest fuzzcheck.sh
noinst_PROGRAMS = arestest aresfuzz aresfuzzname dnsdump
noinst_PROGRAMS = arestest aresfuzz aresfuzzname dnsdump ares_queryloop
EXTRA_DIST = fuzzcheck.sh CMakeLists.txt Makefile.m32 Makefile.msvc README.md $(srcdir)/fuzzinput/* $(srcdir)/fuzznames/*
arestest_SOURCES = $(TESTSOURCES) $(TESTHEADERS)
@ -36,4 +36,7 @@ aresfuzzname_LDADD = $(top_builddir)/src/lib/libcares.la $(PTHREAD_LIBS) $(CODE_
dnsdump_SOURCES = $(DUMPSOURCES)
dnsdump_LDADD = $(top_builddir)/src/lib/libcares.la $(PTHREAD_LIBS) $(CODE_COVERAGE_LIBS)
ares_queryloop_SOURCES = $(LOOPSOURCES)
ares_queryloop_LDADD = $(top_builddir)/src/lib/libcares.la $(PTHREAD_LIBS) $(CODE_COVERAGE_LIBS)
test: check

2
test/Makefile.inc vendored

@ -38,3 +38,5 @@ FUZZNAMESOURCES = ares-test-fuzz-name.c \
DUMPSOURCES = dns-proto.cc \
dns-dump.cc
LOOPSOURCES = ares_queryloop.c

@ -405,6 +405,8 @@ TEST(Misc, OnionDomain) {
EXPECT_EQ(1, ares__is_onion_domain("YES.ONION."));
}
#endif
TEST_F(LibraryTest, DNSRecord) {
ares_dns_record_t *dnsrec = NULL;
ares_dns_rr_t *rr = NULL;
@ -433,14 +435,14 @@ TEST_F(LibraryTest, DNSRecord) {
EXPECT_EQ(ARES_SUCCESS,
ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ANSWER, "example.com",
ARES_REC_TYPE_A, ARES_CLASS_IN, 300));
EXPECT_LT(0, ares_inet_net_pton(AF_INET, "1.1.1.1", &addr, sizeof(addr)));
EXPECT_LT(0, ares_inet_pton(AF_INET, "1.1.1.1", &addr));
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_set_addr(rr, ARES_RR_A_ADDR, &addr));
/* AAAA */
EXPECT_EQ(ARES_SUCCESS,
ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ANSWER, "example.com",
ARES_REC_TYPE_AAAA, ARES_CLASS_IN, 300));
EXPECT_LT(0, ares_inet_net_pton(AF_INET6, "2600::4", &addr6, sizeof(addr6)));
EXPECT_LT(0, ares_inet_pton(AF_INET6, "2600::4", &addr6));
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_set_addr6(rr, ARES_RR_AAAA_ADDR, &addr6));
/* MX */
@ -645,16 +647,19 @@ TEST_F(LibraryTest, DNSRecord) {
/* Write */
EXPECT_EQ(ARES_SUCCESS, ares_dns_write(dnsrec, &msg, &msglen));
#ifndef CARES_SYMBOL_HIDING
ares__buf_t *hexdump = ares__buf_create();
EXPECT_EQ(ARES_SUCCESS, ares__buf_hexdump(hexdump, msg, msglen));
char *hexdata = ares__buf_finish_str(hexdump, NULL);
//printf("HEXDUMP\n%s", hexdata);
ares_free(hexdata);
#endif
ares_dns_record_destroy(dnsrec); dnsrec = NULL;
/* Parse */
EXPECT_EQ(ARES_SUCCESS, ares_dns_parse(msg, msglen, 0, &dnsrec));
ares_free(msg); msg = NULL;
ares_free_string(msg); msg = NULL;
/* Re-write */
EXPECT_EQ(ARES_SUCCESS, ares_dns_write(dnsrec, &msg, &msglen));
@ -664,6 +669,7 @@ TEST_F(LibraryTest, DNSRecord) {
EXPECT_EQ(nscount, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY));
EXPECT_EQ(arcount, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ADDITIONAL));
#ifndef CARES_SYMBOL_HIDING
/* Iterate and print */
ares__buf_t *printmsg = ares__buf_create();
ares__buf_append_str(printmsg, ";; ->>HEADER<<- opcode: ");
@ -768,9 +774,10 @@ TEST_F(LibraryTest, DNSRecord) {
char *printdata = ares__buf_finish_str(printmsg, NULL);
//printf("%s", printdata);
ares_free(printdata);
#endif
ares_dns_record_destroy(dnsrec);
ares_free(msg);
ares_free_string(msg);
}
TEST_F(LibraryTest, DNSParseFlags) {
@ -796,7 +803,7 @@ TEST_F(LibraryTest, DNSParseFlags) {
EXPECT_EQ(ARES_SUCCESS,
ares_dns_record_rr_add(&rr, dnsrec, ARES_SECTION_ANSWER, "example.com",
ARES_REC_TYPE_A, ARES_CLASS_IN, 300));
EXPECT_LT(0, ares_inet_net_pton(AF_INET, "1.1.1.1", &addr, sizeof(addr)));
EXPECT_LT(0, ares_inet_pton(AF_INET, "1.1.1.1", &addr));
EXPECT_EQ(ARES_SUCCESS,
ares_dns_rr_set_addr(rr, ARES_RR_A_ADDR, &addr));
/* TLSA */
@ -886,9 +893,11 @@ TEST_F(LibraryTest, DNSParseFlags) {
EXPECT_EQ(ARES_REC_TYPE_PTR, ares_dns_rr_get_type(rr));
ares_dns_record_destroy(dnsrec);
ares_free(msg); msg = NULL;
ares_free_string(msg); msg = NULL;
}
#ifndef CARES_SYMBOL_HIDING
TEST_F(LibraryTest, CatDomain) {
char *s;
@ -963,6 +972,13 @@ TEST_F(LibraryTest, HtableSzvpMisuse) {
EXPECT_EQ((size_t)0, ares__htable_szvp_num_keys(NULL));
}
TEST_F(LibraryTest, HtableVpvpMisuse) {
EXPECT_EQ(ARES_FALSE, ares__htable_vpvp_insert(NULL, NULL, NULL));
EXPECT_EQ(ARES_FALSE, ares__htable_vpvp_get(NULL, NULL, NULL));
EXPECT_EQ(ARES_FALSE, ares__htable_vpvp_remove(NULL, NULL));
EXPECT_EQ((size_t)0, ares__htable_vpvp_num_keys(NULL));
}
TEST_F(LibraryTest, LlistMisuse) {
ares__llist_replace_destructor(NULL, NULL);
EXPECT_EQ(NULL, ares__llist_insert_before(NULL, NULL));
@ -992,6 +1008,200 @@ TEST_F(LibraryTest, SlistMisuse) {
EXPECT_EQ(NULL, ares__slist_last_val(NULL));
EXPECT_EQ(NULL, ares__slist_node_claim(NULL));
}
TEST_F(LibraryTest, HtableVpvp) {
ares__llist_t *l = NULL;
ares__htable_vpvp_t *h = NULL;
ares__llist_node_t *n = NULL;
size_t i;
#define VPVP_TABLE_SIZE 1000
l = ares__llist_create(NULL);
EXPECT_NE((void *)NULL, l);
h = ares__htable_vpvp_create(NULL, ares_free);
EXPECT_NE((void *)NULL, h);
for (i=0; i<VPVP_TABLE_SIZE; i++) {
void *p = ares_malloc_zero(4);
EXPECT_NE((void *)NULL, p);
EXPECT_NE((void *)NULL, ares__llist_insert_last(l, p));
EXPECT_TRUE(ares__htable_vpvp_insert(h, p, p));
}
EXPECT_EQ(VPVP_TABLE_SIZE, ares__llist_len(l));
EXPECT_EQ(VPVP_TABLE_SIZE, ares__htable_vpvp_num_keys(h));
n = ares__llist_node_first(l);
EXPECT_NE((void *)NULL, n);
while (n != NULL) {
ares__llist_node_t *next = ares__llist_node_next(n);
void *p = ares__llist_node_val(n);
EXPECT_NE((void *)NULL, p);
EXPECT_EQ(p, ares__htable_vpvp_get_direct(h, p));
EXPECT_TRUE(ares__htable_vpvp_get(h, p, NULL));
EXPECT_TRUE(ares__htable_vpvp_remove(h, p));
ares__llist_node_destroy(n);
n = next;
}
EXPECT_EQ(0, ares__llist_len(l));
EXPECT_EQ(0, ares__htable_vpvp_num_keys(h));
ares__llist_destroy(l);
ares__htable_vpvp_destroy(h);
}
typedef struct {
ares_socket_t s;
} test_htable_asvp_t;
TEST_F(LibraryTest, HtableAsvp) {
ares__llist_t *l = NULL;
ares__htable_asvp_t *h = NULL;
ares__llist_node_t *n = NULL;
size_t i;
#define ASVP_TABLE_SIZE 1000
l = ares__llist_create(NULL);
EXPECT_NE((void *)NULL, l);
h = ares__htable_asvp_create(ares_free);
EXPECT_NE((void *)NULL, h);
for (i=0; i<ASVP_TABLE_SIZE; i++) {
test_htable_asvp_t *a = (test_htable_asvp_t *)ares_malloc_zero(sizeof(*a));
EXPECT_NE((void *)NULL, a);
a->s = (ares_socket_t)i+1;
EXPECT_NE((void *)NULL, ares__llist_insert_last(l, a));
EXPECT_TRUE(ares__htable_asvp_insert(h, a->s, a));
}
EXPECT_EQ(ASVP_TABLE_SIZE, ares__llist_len(l));
EXPECT_EQ(ASVP_TABLE_SIZE, ares__htable_asvp_num_keys(h));
n = ares__llist_node_first(l);
EXPECT_NE((void *)NULL, n);
while (n != NULL) {
ares__llist_node_t *next = ares__llist_node_next(n);
test_htable_asvp_t *a = (test_htable_asvp_t *)ares__llist_node_val(n);
EXPECT_NE((void *)NULL, a);
EXPECT_EQ(a, ares__htable_asvp_get_direct(h, a->s));
EXPECT_TRUE(ares__htable_asvp_get(h, a->s, NULL));
EXPECT_TRUE(ares__htable_asvp_remove(h, a->s));
ares__llist_node_destroy(n);
n = next;
}
EXPECT_EQ(0, ares__llist_len(l));
EXPECT_EQ(0, ares__htable_asvp_num_keys(h));
ares__llist_destroy(l);
ares__htable_asvp_destroy(h);
}
typedef struct {
size_t s;
} test_htable_szvp_t;
TEST_F(LibraryTest, HtableSzvp) {
ares__llist_t *l = NULL;
ares__htable_szvp_t *h = NULL;
ares__llist_node_t *n = NULL;
size_t i;
#define SZVP_TABLE_SIZE 1000
l = ares__llist_create(NULL);
EXPECT_NE((void *)NULL, l);
h = ares__htable_szvp_create(ares_free);
EXPECT_NE((void *)NULL, h);
for (i=0; i<SZVP_TABLE_SIZE; i++) {
test_htable_szvp_t *s = (test_htable_szvp_t *)ares_malloc_zero(sizeof(*s));
EXPECT_NE((void *)NULL, s);
s->s = i+1;
EXPECT_NE((void *)NULL, ares__llist_insert_last(l, s));
EXPECT_TRUE(ares__htable_szvp_insert(h, s->s, s));
}
EXPECT_EQ(SZVP_TABLE_SIZE, ares__llist_len(l));
EXPECT_EQ(SZVP_TABLE_SIZE, ares__htable_szvp_num_keys(h));
n = ares__llist_node_first(l);
EXPECT_NE((void *)NULL, n);
while (n != NULL) {
ares__llist_node_t *next = ares__llist_node_next(n);
test_htable_szvp_t *s = (test_htable_szvp_t *)ares__llist_node_val(n);
EXPECT_NE((void *)NULL, s);
EXPECT_EQ(s, ares__htable_szvp_get_direct(h, s->s));
EXPECT_TRUE(ares__htable_szvp_get(h, s->s, NULL));
EXPECT_TRUE(ares__htable_szvp_remove(h, s->s));
ares__llist_node_destroy(n);
n = next;
}
EXPECT_EQ(0, ares__llist_len(l));
EXPECT_EQ(0, ares__htable_szvp_num_keys(h));
ares__llist_destroy(l);
ares__htable_szvp_destroy(h);
}
typedef struct {
char s[32];
} test_htable_strvp_t;
TEST_F(LibraryTest, HtableStrvp) {
ares__llist_t *l = NULL;
ares__htable_strvp_t *h = NULL;
ares__llist_node_t *n = NULL;
size_t i;
#define STRVP_TABLE_SIZE 1000
l = ares__llist_create(NULL);
EXPECT_NE((void *)NULL, l);
h = ares__htable_strvp_create(ares_free);
EXPECT_NE((void *)NULL, h);
for (i=0; i<STRVP_TABLE_SIZE; i++) {
test_htable_strvp_t *s = (test_htable_strvp_t *)ares_malloc_zero(sizeof(*s));
EXPECT_NE((void *)NULL, s);
snprintf(s->s, sizeof(s->s), "%d", (int)i);
EXPECT_NE((void *)NULL, ares__llist_insert_last(l, s));
EXPECT_TRUE(ares__htable_strvp_insert(h, s->s, s));
}
EXPECT_EQ(STRVP_TABLE_SIZE, ares__llist_len(l));
EXPECT_EQ(STRVP_TABLE_SIZE, ares__htable_strvp_num_keys(h));
n = ares__llist_node_first(l);
EXPECT_NE((void *)NULL, n);
while (n != NULL) {
ares__llist_node_t *next = ares__llist_node_next(n);
test_htable_strvp_t *s = (test_htable_strvp_t *)ares__llist_node_val(n);
EXPECT_NE((void *)NULL, s);
EXPECT_EQ(s, ares__htable_strvp_get_direct(h, s->s));
EXPECT_TRUE(ares__htable_strvp_get(h, s->s, NULL));
EXPECT_TRUE(ares__htable_strvp_remove(h, s->s));
ares__llist_node_destroy(n);
n = next;
}
EXPECT_EQ(0, ares__llist_len(l));
EXPECT_EQ(0, ares__htable_strvp_num_keys(h));
ares__llist_destroy(l);
ares__htable_strvp_destroy(h);
}
#endif
TEST_F(DefaultChannelTest, SaveInvalidChannel) {

@ -0,0 +1,136 @@
/* MIT License
*
* 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
*/
/* This test program is meant to loop indefinitely performing a query for the
* same domain once per second. The purpose of this is to test the event loop
* configuration change detection. You can modify the system configuration
* and verify queries work or don't work as expected. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
# include <winsock2.h>
# include <windows.h>
#else
# include <unistd.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif
#include "ares.h"
static void ai_callback(void *arg, int status, int timeouts,
struct ares_addrinfo *result)
{
struct ares_addrinfo_node *node = NULL;
(void)timeouts;
if (status != ARES_SUCCESS) {
fprintf(stderr, "%s: %s\n", (char *)arg, ares_strerror(status));
return;
}
for (node = result->nodes; node != NULL; node = node->ai_next) {
char addr_buf[64] = "";
const void *ptr = NULL;
if (node->ai_family == AF_INET) {
const struct sockaddr_in *in_addr =
(const struct sockaddr_in *)((void *)node->ai_addr);
ptr = &in_addr->sin_addr;
} else if (node->ai_family == AF_INET6) {
const struct sockaddr_in6 *in_addr =
(const struct sockaddr_in6 *)((void *)node->ai_addr);
ptr = &in_addr->sin6_addr;
} else {
continue;
}
ares_inet_ntop(node->ai_family, ptr, addr_buf, sizeof(addr_buf));
printf("%-32s\t%s\n", result->name, addr_buf);
}
ares_freeaddrinfo(result);
}
int main(int argc, char *argv[])
{
struct ares_options options;
int optmask = 0;
ares_channel_t *channel;
size_t count;
ares_status_t status;
#ifdef _WIN32
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
WSAStartup(wVersionRequested, &wsaData);
#endif
if (argc != 2) {
printf("Usage: %s domain\n", argv[0]);
return 1;
}
status = (ares_status_t)ares_library_init(ARES_LIB_INIT_ALL);
if (status != ARES_SUCCESS) {
fprintf(stderr, "ares_library_init: %s\n", ares_strerror((int)status));
return 1;
}
memset(&options, 0, sizeof(options));
optmask |= ARES_OPT_EVENT_THREAD;
options.evsys = ARES_EVSYS_DEFAULT;
status = (ares_status_t)ares_init_options(&channel, &options, optmask);
if (status != ARES_SUCCESS) {
fprintf(stderr, "ares_init: %s\n", ares_strerror((int)status));
return 1;
}
printf("Querying for %s every 1s, press CTRL-C to quit...\n", argv[1]);
for (count = 1; ; count++) {
struct ares_addrinfo_hints hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
printf("Attempt %zu...\n", count);
ares_getaddrinfo(channel, argv[1], NULL, &hints, ai_callback, argv[1]);
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
}
ares_destroy(channel);
ares_library_cleanup();
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
Loading…
Cancel
Save