Add flag to not use a default local named server on channel initialization (#713)

Hello, I work on an application for Microsoft which uses c-ares to
perform DNS lookups. We have made some minor changes to the library over
time, and would like to contribute these back to the project in case
they are useful more widely. This PR adds a new channel init flag,
described below.

Please let me know if I can include any more information to make this PR
better/easier for you to review. Thanks!

**Summary**
When initializing a channel with `ares_init_options()`, if there are no
nameservers available (because `ARES_OPT_SERVERS` is not used and
`/etc/resolv.conf` is either empty or not available) then a default
local named server will be added to the channel.

However in some applications a local named server will never be
available. In this case, all subsequent queries on the channel will
fail.

If we know this ahead of time, then it may be preferred to fail channel
initialization directly rather than wait for the queries to fail. This
gives better visibility, since we know that the failure is due to
missing servers rather than something going wrong with the queries.

This PR adds a new flag `ARES_FLAG_NO_DFLT_SVR`, to indicate that a
default local named server should not be added to a channel in this
scenario. Instead, a new error `ARES_EINITNOSERVER` is returned and
initialization fails.

**Testing**
I have added 2 new FV tests:
- `ContainerNoDfltSvrEmptyInit` to test that initialization fails when
no nameservers are available and the flag is set.
- `ContainerNoDfltSvrFullInit` to test that initialization still
succeeds when the flag is set but other nameservers are available.

Existing FVs are all passing.

**Documentation**
I have had a go at manually updating the docs to describe the new
flag/error, but couldn't see any contributing guidance about testing
this. Please let me know if you'd like anything more here.

---------

Fix By: Oliver Welsh (@oliverwelsh)
pull/706/head
Oliver Welsh 9 months ago committed by GitHub
parent 440b2c3458
commit 035c4c3776
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      docs/ares_init_options.3
  2. 4
      docs/ares_query.3
  3. 3
      docs/ares_search.3
  4. 4
      docs/ares_send.3
  5. 5
      include/ares.h
  6. 16
      src/lib/ares_init.c
  7. 4
      src/lib/ares_process.c
  8. 4
      src/lib/ares_send.c
  9. 2
      src/lib/ares_strerror.c
  10. 31
      test/ares-test-init.cc
  11. 2
      test/ares-test-misc.cc

@ -120,6 +120,10 @@ used to test or debug name servers.
.B ARES_FLAG_EDNS .B ARES_FLAG_EDNS
Include an EDNS pseudo-resource record (RFC 2671) in generated requests. As of Include an EDNS pseudo-resource record (RFC 2671) in generated requests. As of
v1.22, this is on by default if flags are otherwise not set. v1.22, this is on by default if flags are otherwise not set.
.TP 23
.B ARES_FLAG_NO_DFLT_SVR
Do not attempt to add a default local named server if there are no other
servers available. Instead, fail initialization with \fIARES_ENOSERVER\fP.
.RE .RE
.TP 18 .TP 18
.B ARES_OPT_TIMEOUT .B ARES_OPT_TIMEOUT
@ -342,6 +346,9 @@ The process's available memory was exhausted.
.TP 14 .TP 14
.B ARES_ENOTINITIALIZED .B ARES_ENOTINITIALIZED
c-ares library initialization not yet performed. c-ares library initialization not yet performed.
.TP 14
.B ARES_ENOSERVER
No DNS servers were available to use.
.SH NOTES .SH NOTES
When initializing from When initializing from
.B /etc/resolv.conf, .B /etc/resolv.conf,

@ -121,6 +121,10 @@ The query was cancelled.
The name service channel The name service channel
.I channel .I channel
is being destroyed; the query will not be completed. is being destroyed; the query will not be completed.
.TP 19
.B ARES_ENOSERVER
The query will not be completed because no DNS servers were configured on the
channel.
.PP .PP
The callback argument The callback argument
.I timeouts .I timeouts

@ -122,6 +122,9 @@ The query was cancelled.
The name service channel The name service channel
.I channel .I channel
is being destroyed; the query will not be completed. is being destroyed; the query will not be completed.
.TP 19
.B ARES_ENOSERVER
No query completed successfully; no DNS servers were configured on the channel.
.PP .PP
The callback argument The callback argument
.I timeouts .I timeouts

@ -77,6 +77,10 @@ The query was cancelled.
The name service channel The name service channel
.I channel .I channel
is being destroyed; the query will not be completed. is being destroyed; the query will not be completed.
.TP 19
.B ARES_ENOSERVER
The query will not be completed because no DNS servers were configured on the
channel.
.PP .PP
The callback argument The callback argument
.I timeouts .I timeouts

@ -161,8 +161,10 @@ typedef enum {
ARES_ECANCELLED = 24, /* introduced in 1.7.0 */ ARES_ECANCELLED = 24, /* introduced in 1.7.0 */
/* More ares_getaddrinfo error codes */ /* More ares_getaddrinfo error codes */
ARES_ESERVICE = 25 /* ares_getaddrinfo() was passed a text service name that ARES_ESERVICE = 25, /* ares_getaddrinfo() was passed a text service name that
* is not recognized. introduced in 1.16.0 */ * is not recognized. introduced in 1.16.0 */
ARES_ENOSERVER = 26 /* No DNS servers were configured */
} ares_status_t; } ares_status_t;
typedef enum { typedef enum {
@ -196,6 +198,7 @@ typedef enum {
#define ARES_FLAG_NOALIASES (1 << 6) #define ARES_FLAG_NOALIASES (1 << 6)
#define ARES_FLAG_NOCHECKRESP (1 << 7) #define ARES_FLAG_NOCHECKRESP (1 << 7)
#define ARES_FLAG_EDNS (1 << 8) #define ARES_FLAG_EDNS (1 << 8)
#define ARES_FLAG_NO_DFLT_SVR (1 << 9)
/* Option mask values */ /* Option mask values */
#define ARES_OPT_FLAGS (1 << 0) #define ARES_OPT_FLAGS (1 << 0)

@ -133,6 +133,8 @@ static ares_status_t init_by_defaults(ares_channel_t *channel)
#ifdef HAVE_GETHOSTNAME #ifdef HAVE_GETHOSTNAME
const char *dot; const char *dot;
#endif #endif
struct ares_addr addr;
ares__llist_t *sconfig = NULL;
/* Enable EDNS by default */ /* Enable EDNS by default */
if (!(channel->optmask & ARES_OPT_FLAGS)) { if (!(channel->optmask & ARES_OPT_FLAGS)) {
@ -155,22 +157,27 @@ static ares_status_t init_by_defaults(ares_channel_t *channel)
} }
if (ares__slist_len(channel->servers) == 0) { if (ares__slist_len(channel->servers) == 0) {
struct ares_addr addr; /* Add a default local named server to the channel unless configured not
ares__llist_t *sconfig = NULL; * to (in which case return an error).
*/
if (channel->flags & ARES_FLAG_NO_DFLT_SVR) {
rc = ARES_ENOSERVER;
goto error;
}
addr.family = AF_INET; addr.family = AF_INET;
addr.addr.addr4.s_addr = htonl(INADDR_LOOPBACK); addr.addr.addr4.s_addr = htonl(INADDR_LOOPBACK);
rc = ares__sconfig_append(&sconfig, &addr, 0, 0, NULL); rc = ares__sconfig_append(&sconfig, &addr, 0, 0, NULL);
if (rc != ARES_SUCCESS) { if (rc != ARES_SUCCESS) {
return rc; goto error;
} }
rc = ares__servers_update(channel, sconfig, ARES_FALSE); rc = ares__servers_update(channel, sconfig, ARES_FALSE);
ares__llist_destroy(sconfig); ares__llist_destroy(sconfig);
if (rc != ARES_SUCCESS) { if (rc != ARES_SUCCESS) {
return rc; goto error;
} }
} }
@ -387,6 +394,7 @@ int ares_init_options(ares_channel_t **channelptr,
if (status != ARES_SUCCESS) { if (status != ARES_SUCCESS) {
DEBUGF(fprintf(stderr, "Error: init_by_defaults failed: %s\n", DEBUGF(fprintf(stderr, "Error: init_by_defaults failed: %s\n",
ares_strerror(status))); ares_strerror(status)));
goto done;
} }
/* Initialize the event thread */ /* Initialize the event thread */

@ -893,8 +893,8 @@ ares_status_t ares__send_query(struct query *query, struct timeval *now)
} }
if (server == NULL) { if (server == NULL) {
end_query(channel, query, ARES_ESERVFAIL /* ? */, NULL, 0); end_query(channel, query, ARES_ENOSERVER /* ? */, NULL, 0);
return ARES_ECONNREFUSED; return ARES_ENOSERVER;
} }
if (query->using_tcp) { if (query->using_tcp) {

@ -66,8 +66,8 @@ ares_status_t ares_send_ex(ares_channel_t *channel, const unsigned char *qbuf,
return ARES_EBADQUERY; return ARES_EBADQUERY;
} }
if (ares__slist_len(channel->servers) == 0) { if (ares__slist_len(channel->servers) == 0) {
callback(arg, ARES_ESERVFAIL, 0, NULL, 0); callback(arg, ARES_ENOSERVER, 0, NULL, 0);
return ARES_ESERVFAIL; return ARES_ENOSERVER;
} }
/* Check query cache */ /* Check query cache */

@ -85,6 +85,8 @@ const char *ares_strerror(int code)
return "DNS query cancelled"; return "DNS query cancelled";
case ARES_ESERVICE: case ARES_ESERVICE:
return "Invalid service name or number"; return "Invalid service name or number";
case ARES_ENOSERVER:
return "No DNS servers were configured";
} }
return "unknown"; return "unknown";

@ -713,6 +713,37 @@ CONTAINED_TEST_F(LibraryTest, ContainerEmptyInit,
return HasFailure(); return HasFailure();
} }
// Test that init fails if the flag to not use a default local named server is
// enabled and no other nameservers are available.
CONTAINED_TEST_F(LibraryTest, ContainerNoDfltSvrEmptyInit,
"myhostname", "mydomainname.org", empty) {
ares_channel_t *channel = nullptr;
struct ares_options opts = {0};
int optmask = ARES_OPT_FLAGS;
opts.flags = ARES_FLAG_NO_DFLT_SVR;
EXPECT_EQ(ARES_ENOSERVER, ares_init_options(&channel, &opts, optmask));
EXPECT_EQ(nullptr, channel);
return HasFailure();
}
// Test that init succeeds if the flag to not use a default local named server
// is enabled but other nameservers are available.
CONTAINED_TEST_F(LibraryTest, ContainerNoDfltSvrFullInit,
"myhostname", "mydomainname.org", filelist) {
ares_channel_t *channel = nullptr;
struct ares_options opts = {0};
int optmask = ARES_OPT_FLAGS;
opts.flags = ARES_FLAG_NO_DFLT_SVR;
EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel, &opts, optmask));
std::string actual = GetNameServers(channel);
std::string expected = "1.2.3.4:53";
EXPECT_EQ(expected, actual);
ares_destroy(channel);
return HasFailure();
}
#endif #endif
} // namespace test } // namespace test

@ -64,7 +64,7 @@ TEST_F(DefaultChannelTest, SetServers) {
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result);
Process(); Process();
EXPECT_TRUE(result.done_); EXPECT_TRUE(result.done_);
EXPECT_EQ(ARES_ESERVFAIL, result.status_); EXPECT_EQ(ARES_ENOSERVER, result.status_);
struct ares_addr_node server1; struct ares_addr_node server1;

Loading…
Cancel
Save