Implement ares_reinit() to reload system configuration into existing channel (#614)

This PR implements ares_reinit() to safely reload a channel's configuration even if there are existing queries.  This function can be called when system configuration is detected to be changed, however since c-ares isn't thread aware, care must be taken to ensure no other c-ares calls are in progress at the time this function is called.  Also, this function may update the open file descriptor list so care must also be taken to wake any event loops and reprocess the list of file descriptors.

Fixes Bug #301

Fix By: Brad House (@bradh352)
pull/617/head
Brad House 1 year ago committed by GitHub
parent 9a01e766ed
commit 0cc570eabe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      TODO
  2. 1
      docs/Makefile.inc
  3. 48
      docs/ares_reinit.3
  4. 2
      include/ares.h
  5. 40
      src/lib/ares_getaddrinfo.c
  6. 13
      src/lib/ares_gethostbyaddr.c
  7. 20
      src/lib/ares_init.c
  8. 27
      src/lib/ares_search.c
  9. 23
      src/lib/ares_strsplit.c
  10. 2
      src/lib/ares_strsplit.h
  11. 37
      src/lib/ares_sysconfig.c
  12. 18
      test/ares-test-mock.cc

21
TODO

@ -1,23 +1,4 @@
TODO
====
ares_reinit()
- To allow an app to force a re-read of /etc/resolv.conf etc, pretty much
like the res_init() resolver function offers
ares_gethostbyname
- When built to support IPv6, it needs to also support PF_UNSPEC or similar,
so that an application can ask for any protocol and then c-ares would return
all known resolves and not just explicitly IPv4 _or_ IPv6 resolves.
ares_process
- Upon next ABI breakage ares_process() should be changed to return 'int'
and return ARES_ENOTINITIALIZED if ares_library_init() has not been called.
ares_process_fd
- Upon next ABI breakage ares_process_fd() should be changed to return
'int' and return ARES_ENOTINITIALIZED if library has not been initialized.
Please see https://github.com/c-ares/c-ares/issues

@ -42,6 +42,7 @@ MANPAGES = ares_cancel.3 \
ares_parse_uri_reply.3 \
ares_process.3 \
ares_query.3 \
ares_reinit.3 \
ares_save_options.3 \
ares_search.3 \
ares_send.3 \

@ -0,0 +1,48 @@
.\"
.\" SPDX-License-Identifier: MIT
.\"
.TH ARES_REINIT 3 "12 November 2023"
.SH NAME
ares_reinit \- ReInitialize a resolver channel from system configuration.
.SH SYNOPSIS
.nf
#include <ares.h>
int ares_reinit(ares_channel_t *\fIchannel\fP)
.fi
.SH DESCRIPTION
The \fBares_reinit(3)\fP function re-reads the system configuration and safely
applies the configuration to the existing channel. System configuration will
never override user-provided settings such as provided via
\fBares_init_options(3)\fP or \fBares_set_servers(3)\fP.
Any existing queries will be automatically requeued if the server they are
currently assigned to is removed from the system configuration.
It is important to remember that c-ares is not currently thread-aware and this
function cannot be called while any other c-ares functions are being called.
This function may also cause additional file descriptors to be created if
server configuration has changed, so care needs to be taken to ensure any
event loop is updated with new file descriptors.
.SH RETURN VALUES
\fIares_reinit(3)\fP can return any of the following values:
.TP 14
.B ARES_SUCCESS
Initialization succeeded.
.TP 14
.B ARES_EFILE
A configuration file could not be read.
.TP 14
.B ARES_ENOMEM
The process's available memory was exhausted.
.SH SEE ALSO
.BR ares_init (3),
.BR ares_init_options (3),
.BR ares_destroy (3),
.BR ares_dup (3),
.BR ares_library_init (3),
.BR ares_set_servers (3)
.SH AUTHOR
Copyright (C) 2023 The c-ares project and its members.

@ -370,6 +370,8 @@ CARES_EXTERN void ares_destroy_options(struct ares_options *options);
CARES_EXTERN int ares_dup(ares_channel_t **dest, ares_channel_t *src);
CARES_EXTERN ares_status_t ares_reinit(ares_channel_t *channel);
CARES_EXTERN void ares_destroy(ares_channel_t *channel);
CARES_EXTERN void ares_cancel(ares_channel_t *channel);

@ -77,8 +77,11 @@ struct host_query {
struct ares_addrinfo_hints hints;
int sent_family; /* this family is what was is being used */
size_t timeouts; /* number of timeouts we saw for this request */
char *lookups; /* Duplicate memory from channel because of ares_reinit() */
const char *remaining_lookups; /* types of lookup we need to perform ("fb" by
default, file and dns respectively) */
char **domains; /* duplicate from channel for ares_reinit() safety */
size_t ndomains;
struct ares_addrinfo *ai; /* store results between lookups */
unsigned short qid_a; /* qid for A request */
unsigned short qid_aaaa; /* qid for AAAA request */
@ -347,6 +350,8 @@ static void end_hquery(struct host_query *hquery, ares_status_t status)
}
hquery->callback(hquery->arg, (int)status, (int)hquery->timeouts, hquery->ai);
ares__strsplit_free(hquery->domains, hquery->ndomains);
ares_free(hquery->lookups);
ares_free(hquery->name);
ares_free(hquery);
}
@ -609,7 +614,7 @@ void ares_getaddrinfo(ares_channel_t *channel, const char *name,
}
/* Allocate and fill in the host query structure. */
hquery = ares_malloc(sizeof(*hquery));
hquery = ares_malloc_zero(sizeof(*hquery));
if (!hquery) {
ares_free(alias_name);
ares_freeaddrinfo(ai);
@ -625,6 +630,29 @@ void ares_getaddrinfo(ares_channel_t *channel, const char *name,
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
hquery->lookups = ares_strdup(channel->lookups);
if (!hquery->lookups) {
ares_free(hquery->name);
ares_free(hquery);
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
if (channel->ndomains) {
/* Duplicate for ares_reinit() safety */
hquery->domains =
ares__strsplit_duplicate(channel->domains, channel->ndomains);
if (hquery->domains == NULL) {
ares_free(hquery->lookups);
ares_free(hquery->name);
ares_free(hquery);
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
hquery->ndomains = channel->ndomains;
}
hquery->port = port;
hquery->channel = channel;
@ -632,7 +660,7 @@ void ares_getaddrinfo(ares_channel_t *channel, const char *name,
hquery->sent_family = -1; /* nothing is sent yet */
hquery->callback = callback;
hquery->arg = arg;
hquery->remaining_lookups = channel->lookups;
hquery->remaining_lookups = hquery->lookups;
hquery->ai = ai;
hquery->next_domain = -1;
@ -655,17 +683,17 @@ static ares_bool_t next_dns_lookup(struct host_query *hquery)
}
/* if as_is_first is false, try hquery->name at last */
if (!s && (size_t)hquery->next_domain == hquery->channel->ndomains) {
if (!s && (size_t)hquery->next_domain == hquery->ndomains) {
if (!as_is_first(hquery)) {
s = hquery->name;
}
hquery->next_domain++;
}
if (!s && (size_t)hquery->next_domain < hquery->channel->ndomains &&
if (!s && (size_t)hquery->next_domain < hquery->ndomains &&
!as_is_only(hquery)) {
status = ares__cat_domain(
hquery->name, hquery->channel->domains[hquery->next_domain++], &s);
status = ares__cat_domain(hquery->name,
hquery->domains[hquery->next_domain++], &s);
if (status == ARES_SUCCESS) {
is_s_allocated = ARES_TRUE;
}

@ -54,7 +54,7 @@ struct addr_query {
struct ares_addr addr;
ares_host_callback callback;
void *arg;
char *lookups; /* duplicate memory from channel for ares_reinit() */
const char *remaining_lookups;
size_t timeouts;
};
@ -89,6 +89,12 @@ void ares_gethostbyaddr(ares_channel_t *channel, const void *addr, int addrlen,
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
aquery->lookups = ares_strdup(channel->lookups);
if (aquery->lookups == NULL) {
ares_free(aquery);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
aquery->channel = channel;
if (family == AF_INET) {
memcpy(&aquery->addr.addr.addr4, addr, sizeof(aquery->addr.addr.addr4));
@ -98,13 +104,12 @@ void ares_gethostbyaddr(ares_channel_t *channel, const void *addr, int addrlen,
aquery->addr.family = family;
aquery->callback = callback;
aquery->arg = arg;
aquery->remaining_lookups = channel->lookups;
aquery->remaining_lookups = aquery->lookups;
aquery->timeouts = 0;
next_lookup(aquery);
}
static void next_lookup(struct addr_query *aquery)
{
const char *p;
@ -175,6 +180,7 @@ static void end_aquery(struct addr_query *aquery, ares_status_t status,
if (host) {
ares_free_hostent(host);
}
ares_free(aquery->lookups);
ares_free(aquery);
}
@ -213,4 +219,3 @@ static ares_status_t file_lookup(ares_channel_t *channel,
return ARES_SUCCESS;
}

@ -296,14 +296,12 @@ int ares_init_options(ares_channel_t **channelptr,
return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */
}
channel = ares_malloc(sizeof(*channel));
channel = ares_malloc_zero(sizeof(*channel));
if (!channel) {
*channelptr = NULL;
return ARES_ENOMEM;
}
memset(channel, 0, sizeof(*channel));
/* Generate random key */
channel->rand_state = ares__init_rand_state();
if (channel->rand_state == NULL) {
@ -388,6 +386,22 @@ done:
return ARES_SUCCESS;
}
ares_status_t ares_reinit(ares_channel_t *channel)
{
ares_status_t status;
if (channel == NULL) {
return ARES_EFORMERR;
}
status = ares__init_by_sysconfig(channel);
if (status != ARES_SUCCESS) {
DEBUGF(fprintf(stderr, "Error: init_by_sysconfig failed: %s\n",
ares_strerror(status)));
}
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, ares_channel_t *src)

@ -42,6 +42,8 @@ struct search_query {
int type;
ares_callback callback;
void *arg;
char **domains; /* duplicate for ares_reinit() safety */
size_t ndomains;
int status_as_is; /* error status from trying as-is */
size_t next_domain; /* next search domain to try */
@ -87,7 +89,7 @@ void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
/* Allocate a search_query structure to hold the state necessary for
* doing multiple lookups.
*/
squery = ares_malloc(sizeof(struct search_query));
squery = ares_malloc_zero(sizeof(*squery));
if (!squery) {
callback(arg, ARES_ENOMEM, 0, NULL, 0);
return;
@ -99,6 +101,20 @@ void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
callback(arg, ARES_ENOMEM, 0, NULL, 0);
return;
}
/* Duplicate domains for safety during ares_reinit() */
if (channel->ndomains) {
squery->domains =
ares__strsplit_duplicate(channel->domains, channel->ndomains);
if (squery->domains == NULL) {
ares_free(squery->name);
ares_free(squery);
callback(arg, ARES_ENOMEM, 0, NULL, 0);
return;
}
squery->ndomains = channel->ndomains;
}
squery->dnsclass = dnsclass;
squery->type = type;
squery->status_as_is = -1;
@ -119,7 +135,7 @@ void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
* then we try the name as-is first. Otherwise, we try the name
* as-is last.
*/
if (ndots >= channel->ndots) {
if (ndots >= channel->ndots || squery->ndomains == 0) {
/* Try the name as-is first. */
squery->next_domain = 0;
squery->trying_as_is = ARES_TRUE;
@ -128,7 +144,7 @@ void ares_search(ares_channel_t *channel, const char *name, int dnsclass,
/* Try the name as-is last; start with the first search domain. */
squery->next_domain = 1;
squery->trying_as_is = ARES_FALSE;
status = ares__cat_domain(name, channel->domains[0], &s);
status = ares__cat_domain(name, squery->domains[0], &s);
if (status == ARES_SUCCESS) {
ares_query(channel, s, dnsclass, type, search_callback, squery);
ares_free(s);
@ -171,11 +187,11 @@ static void search_callback(void *arg, int status, int timeouts,
squery->ever_got_nodata = ARES_TRUE;
}
if (squery->next_domain < channel->ndomains) {
if (squery->next_domain < squery->ndomains) {
ares_status_t mystatus;
/* Try the next domain. */
mystatus = ares__cat_domain(squery->name,
channel->domains[squery->next_domain], &s);
squery->domains[squery->next_domain], &s);
if (mystatus != ARES_SUCCESS) {
end_squery(squery, mystatus, NULL, 0);
} else {
@ -205,6 +221,7 @@ static void end_squery(struct search_query *squery, ares_status_t status,
{
squery->callback(squery->arg, (int)status, (int)squery->timeouts, abuf,
(int)alen);
ares__strsplit_free(squery->domains, squery->ndomains);
ares_free(squery->name);
ares_free(squery);
}

@ -46,6 +46,29 @@ void ares__strsplit_free(char **elms, size_t num_elm)
ares_free(elms);
}
char **ares__strsplit_duplicate(char **elms, size_t num_elm)
{
size_t i;
char **out;
if (elms == NULL || num_elm == 0)
return NULL;
out = ares_malloc_zero(sizeof(*elms) * num_elm);
if (out == NULL)
return NULL;
for (i=0; i<num_elm; i++) {
out[i] = ares_strdup(elms[i]);
if (out[i] == NULL) {
ares__strsplit_free(out, num_elm);
return NULL;
}
}
return out;
}
char **ares__strsplit(const char *in, const char *delms, size_t *num_elm)
{
const char *p;

@ -47,5 +47,7 @@ char **ares__strsplit(const char *in, const char *delms, size_t *num_elm);
/* Frees the result returned from ares__strsplit(). */
void ares__strsplit_free(char **elms, size_t num_elm);
/* Duplicate the array */
char **ares__strsplit_duplicate(char **elms, size_t num_elm);
#endif /* HEADER_CARES_STRSPLIT_H */

@ -927,41 +927,40 @@ static ares_status_t ares_sysconfig_apply(ares_channel_t *channel,
}
if (sysconfig->domains && !(channel->optmask & ARES_OPT_DOMAINS)) {
size_t i;
ares__strsplit_free(channel->domains, channel->ndomains);
channel->domains =
ares_malloc_zero(sizeof(*channel->domains) * sysconfig->ndomains);
if (channel->domains == NULL) {
/* Make sure we duplicate first then replace so even if there is
* ARES_ENOMEM, the channel stays in a good state */
char **temp =
ares__strsplit_duplicate(sysconfig->domains, sysconfig->ndomains);
if (temp == NULL) {
return ARES_ENOMEM;
}
ares__strsplit_free(channel->domains, channel->ndomains);
channel->domains = temp;
channel->ndomains = sysconfig->ndomains;
for (i = 0; i < channel->ndomains; i++) {
channel->domains[i] = ares_strdup(sysconfig->domains[i]);
if (channel->domains[i] == NULL) {
return ARES_ENOMEM;
}
}
}
if (sysconfig->lookups && !(channel->optmask & ARES_OPT_LOOKUPS)) {
ares_free(channel->lookups);
channel->lookups = ares_strdup(sysconfig->lookups);
if (channel->lookups == NULL) {
char *temp = ares_strdup(sysconfig->lookups);
if (temp == NULL) {
return ARES_ENOMEM;
}
ares_free(channel->lookups);
channel->lookups = temp;
}
if (sysconfig->sortlist && !(channel->optmask & ARES_OPT_SORTLIST)) {
ares_free(channel->sortlist);
channel->sortlist =
struct apattern *temp =
ares_malloc(sizeof(*channel->sortlist) * sysconfig->nsortlist);
if (channel->sortlist == NULL) {
if (temp == NULL) {
return ARES_ENOMEM;
}
memcpy(channel->sortlist, sysconfig->sortlist,
memcpy(temp, sysconfig->sortlist,
sizeof(*channel->sortlist) * sysconfig->nsortlist);
ares_free(channel->sortlist);
channel->sortlist = temp;
channel->nsort = sysconfig->nsortlist;
}

@ -219,6 +219,24 @@ TEST_P(MockChannelTest, SockConfigureFailCallback) {
EXPECT_EQ(ARES_ECONNREFUSED, result.status_);
}
TEST_P(MockChannelTest, ReInit) {
DNSPacket rsp;
rsp.set_response().set_aa()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 100, {2, 3, 4, 5}));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &rsp));
HostResult result;
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result);
EXPECT_EQ(ARES_SUCCESS, ares_reinit(channel_));
Process();
EXPECT_TRUE(result.done_);
std::stringstream ss;
ss << result.host_;
EXPECT_EQ("{'www.google.com' aliases=[] addrs=[2.3.4.5]}", ss.str());
}
#define MAXUDPQUERIES_TOTAL 32
#define MAXUDPQUERIES_LIMIT 8

Loading…
Cancel
Save