mirror of https://github.com/c-ares/c-ares.git
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
parent
2f200b9170
commit
8d80486e04
26 changed files with 1447 additions and 52 deletions
@ -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 */ |
@ -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 |
@ -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…
Reference in new issue