diff --git a/.github/workflows/netbsd.yml b/.github/workflows/netbsd.yml index 02639031..3772d2ed 100644 --- a/.github/workflows/netbsd.yml +++ b/.github/workflows/netbsd.yml @@ -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" diff --git a/.github/workflows/openbsd.yml b/.github/workflows/openbsd.yml index f125a916..5f535979 100644 --- a/.github/workflows/openbsd.yml +++ b/.github/workflows/openbsd.yml @@ -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: diff --git a/docs/ares_init_options.3 b/docs/ares_init_options.3 index 2da98774..81e53cd0 100644 --- a/docs/ares_init_options.3 +++ b/docs/ares_init_options.3 @@ -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. diff --git a/docs/ares_reinit.3 b/docs/ares_reinit.3 index 5d90107d..020af1db 100644 --- a/docs/ares_reinit.3 +++ b/docs/ares_reinit.3 @@ -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: diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index ceec3062..dc398034 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -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 \ diff --git a/src/lib/ares__htable_vpvp.c b/src/lib/ares__htable_vpvp.c new file mode 100644 index 00000000..828b738e --- /dev/null +++ b/src/lib/ares__htable_vpvp.c @@ -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); +} diff --git a/src/lib/ares__htable_vpvp.h b/src/lib/ares__htable_vpvp.h new file mode 100644 index 00000000..876fc45f --- /dev/null +++ b/src/lib/ares__htable_vpvp.h @@ -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 */ diff --git a/src/lib/ares_destroy.c b/src/lib/ares_destroy.c index 6965b601..a07645a0 100644 --- a/src/lib/ares_destroy.c +++ b/src/lib/ares_destroy.c @@ -31,16 +31,33 @@ #include "ares.h" #include "ares_private.h" +#include "ares_event.h" void ares_destroy(ares_channel_t *channel) { - size_t i; - ares__llist_node_t *node = NULL; + size_t i; + ares__llist_node_t *node = NULL; if (channel == NULL) { 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); diff --git a/src/lib/ares_event.h b/src/lib/ares_event.h index 23e96379..f5d044d2 100644 --- a/src/lib/ares_event.h +++ b/src/lib/ares_event.h @@ -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 diff --git a/src/lib/ares_event_configchg.c b/src/lib/ares_event_configchg.c new file mode 100644 index 00000000..961224a0 --- /dev/null +++ b/src/lib/ares_event_configchg.c @@ -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 + +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 +# include +# include +# include + +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 +# include +# include +# include + +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 +# endif +# ifdef HAVE_SYS_STAT_H +# include +# 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 diff --git a/src/lib/ares_event_epoll.c b/src/lib/ares_event_epoll.c index 9d3c097f..78cbbc36 100644 --- a/src/lib/ares_event_epoll.c +++ b/src/lib/ares_event_epoll.c @@ -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; diff --git a/src/lib/ares_event_kqueue.c b/src/lib/ares_event_kqueue.c index 944c4b00..7e2c60ab 100644 --- a/src/lib/ares_event_kqueue.c +++ b/src/lib/ares_event_kqueue.c @@ -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; diff --git a/src/lib/ares_event_poll.c b/src/lib/ares_event_poll.c index 33b1d6df..a06c6e3f 100644 --- a/src/lib/ares_event_poll.c +++ b/src/lib/ares_event_poll.c @@ -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; } diff --git a/src/lib/ares_event_select.c b/src/lib/ares_event_select.c index 4823e808..6aab51a6 100644 --- a/src/lib/ares_event_select.c +++ b/src/lib/ares_event_select.c @@ -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; } diff --git a/src/lib/ares_event_thread.c b/src/lib/ares_event_thread.c index 3fdfe6b8..d9d904f2 100644 --- a/src/lib/ares_event_thread.c +++ b/src/lib/ares_event_thread.c @@ -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; } diff --git a/src/lib/ares_getnameinfo.c b/src/lib/ares_getnameinfo.c index ff98d17c..d4cf58ea 100644 --- a/src/lib/ares_getnameinfo.c +++ b/src/lib/ares_getnameinfo.c @@ -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 { diff --git a/src/lib/ares_init.c b/src/lib/ares_init.c index 71cdf582..8fbc277d 100644 --- a/src/lib/ares_init.c +++ b/src/lib/ares_init.c @@ -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_status_t status; - - if (channel == NULL) { - return ARES_EFORMERR; - } + ares_channel_t *channel = arg; + ares_status_t status; /* 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) diff --git a/src/lib/ares_ipv6.h b/src/lib/ares_ipv6.h index 28d7851f..e7e0b6d3 100644 --- a/src/lib/ares_ipv6.h +++ b/src/lib/ares_ipv6.h @@ -31,6 +31,15 @@ # include #endif +#if defined(USE_WINSOCK) +# if defined(HAVE_IPHLPAPI_H) +# include +# endif +# if defined(HAVE_NETIOAPI_H) +# include +# endif +#endif + #ifndef HAVE_PF_INET6 # define PF_INET6 AF_INET6 #endif diff --git a/src/lib/ares_private.h b/src/lib/ares_private.h index e60c41ea..fd290c4d 100644 --- a/src/lib/ares_private.h +++ b/src/lib/ares_private.h @@ -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. */ diff --git a/src/lib/ares_sysconfig.c b/src/lib/ares_sysconfig.c index f8c4ac5b..d56ee22e 100644 --- a/src/lib/ares_sysconfig.c +++ b/src/lib/ares_sysconfig.c @@ -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); diff --git a/src/lib/ares_sysconfig_mac.c b/src/lib/ares_sysconfig_mac.c index a3b0110d..4161ba2b 100644 --- a/src/lib/ares_sysconfig_mac.c +++ b/src/lib/ares_sysconfig_mac.c @@ -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; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fbb6a621..e397d6e1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 $) diff --git a/test/Makefile.am b/test/Makefile.am index 8ebcd851..01a8bc30 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -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 diff --git a/test/Makefile.inc b/test/Makefile.inc index 1d8af8c6..5c8b1878 100644 --- a/test/Makefile.inc +++ b/test/Makefile.inc @@ -38,3 +38,5 @@ FUZZNAMESOURCES = ares-test-fuzz-name.c \ DUMPSOURCES = dns-proto.cc \ dns-dump.cc + +LOOPSOURCES = ares_queryloop.c diff --git a/test/ares-test-internal.cc b/test/ares-test-internal.cc index f2b753e9..853fbbb6 100644 --- a/test/ares-test-internal.cc +++ b/test/ares-test-internal.cc @@ -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; is = (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; is = 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; is, 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) { diff --git a/test/ares_queryloop.c b/test/ares_queryloop.c new file mode 100644 index 00000000..91bbe6ad --- /dev/null +++ b/test/ares_queryloop.c @@ -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 +#include +#include +#ifdef _WIN32 +# include +# include +#else +# include +# include +# include +# include +#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; +}