Compare commits

..

9 Commits

Author SHA1 Message Date
Brad House 1d1e7dbd54 Empty TXT records were not being preserved (#922) 2 weeks ago
Brad House 9f81bf6fde Android: <= 7 might require sys/system_properties.h 2 weeks ago
Brad House 93635b7854 fix backport issue 2 weeks ago
Brad House ed86a80634 Some upstream DNS servers are non-compliant with EDNS options 2 weeks ago
Jiwoo Park 15883a95f2 Silence TSAN, lock before checking `ares_event_thread::isup` (#915) 2 weeks ago
Jiwoo Park 0b80ed1735 Use `_GNU_SOURCE` macro on Android (#914) 2 weeks ago
Brad House 3899f7dba9 ares_getaddrinfo() for AF_UNSPEC should retry if ipv6 received 2 weeks ago
Brad House 9ccecbee62 Backport parts of bbea2cd from main 2 weeks ago
Brad House dfc4866c36 rewrite EBADRESP to EBADQUERY on ares_send 2 weeks ago
  1. 1
      .github/workflows/msys2.yml
  2. 2
      CMakeLists.txt
  3. 9
      ci/distcheck.sh
  4. 5
      configure.ac
  5. 34
      src/lib/ares_getaddrinfo.c
  6. 55
      src/lib/ares_process.c
  7. 5
      src/lib/ares_send.c
  8. 8
      src/lib/event/ares_event_thread.c
  9. 12
      src/lib/record/ares_dns_multistring.c
  10. 46
      src/lib/str/ares__buf.c
  11. 66
      test/ares-test-mock-ai.cc
  12. 23
      test/ares-test-mock.cc
  13. 47
      test/ares-test-parse-txt.cc

@ -25,7 +25,6 @@ jobs:
matrix:
include:
- { msystem: CLANG64, env: clang-x86_64, extra_packages: "mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-clang-analyzer" }
- { msystem: CLANG32, env: clang-i686, extra_packages: "mingw-w64-clang-i686-clang mingw-w64-clang-i686-clang-analyzer" }
- { msystem: MINGW64, env: x86_64, extra_packages: "mingw-w64-x86_64-gcc" }
- { msystem: MINGW32, env: i686, extra_packages: "mingw-w64-i686-gcc" }
# No need to test UCRT64 since clang64 uses UCRT

@ -263,7 +263,7 @@ ENDIF ()
# Set system-specific compiler flags
IF (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
LIST (APPEND SYSFLAGS -D_DARWIN_C_SOURCE)
ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "Linux")
ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "Android")
LIST (APPEND SYSFLAGS -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700)
ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
LIST (APPEND SYSFLAGS -D__EXTENSIONS__ -D_REENTRANT -D_XOPEN_SOURCE=600)

@ -3,14 +3,9 @@
# SPDX-License-Identifier: MIT
set -e -x
OS=""
if [ "$TRAVIS_OS_NAME" != "" ]; then
OS="$TRAVIS_OS_NAME"
elif [ "$CIRRUS_OS" != "" ]; then
OS="$CIRRUS_OS"
fi
OS=`uname -s || true`
if [ "$OS" = "linux" ]; then
if [ "$OS" = "Linux" ]; then
# Make distribution tarball
autoreconf -fi
./configure

@ -373,7 +373,7 @@ AS_HELP_STRING([--enable-libgcc],[use libgcc when linking]),
dnl check for a few basic system headers we need. It would be nice if we could
dnl split these on separate lines, but for some reason autotools on Windows doesn't
dnl allow this, even tried ending lines with a backslash.
AC_CHECK_HEADERS([malloc.h memory.h AvailabilityMacros.h sys/types.h sys/time.h sys/select.h sys/socket.h sys/filio.h sys/ioctl.h sys/param.h sys/uio.h sys/random.h sys/event.h sys/epoll.h assert.h iphlpapi.h netioapi.h netdb.h netinet/in.h netinet6/in6.h netinet/tcp.h net/if.h ifaddrs.h fcntl.h errno.h socket.h strings.h stdbool.h time.h poll.h limits.h arpa/nameser.h arpa/nameser_compat.h arpa/inet.h ],
AC_CHECK_HEADERS([malloc.h memory.h AvailabilityMacros.h sys/types.h sys/time.h sys/select.h sys/socket.h sys/filio.h sys/ioctl.h sys/param.h sys/uio.h sys/random.h sys/event.h sys/epoll.h assert.h iphlpapi.h netioapi.h netdb.h netinet/in.h netinet6/in6.h netinet/tcp.h net/if.h ifaddrs.h fcntl.h errno.h socket.h strings.h stdbool.h time.h poll.h limits.h arpa/nameser.h arpa/nameser_compat.h arpa/inet.h sys/system_properties.h ],
dnl to do if not found
[],
dnl to do if found
@ -488,6 +488,9 @@ cares_all_includes="
#ifdef HAVE_RESOLV_H
# include <resolv.h>
#endif
#ifdef HAVE_SYS_SYSTEM_PROPERTIES_H
# include <sys/system_properties.h>
#endif
#ifdef HAVE_IPHLPAPI_H
# include <iphlpapi.h>
#endif

@ -484,6 +484,18 @@ static void terminate_retries(const struct host_query *hquery,
query->no_retries = ARES_TRUE;
}
static ares_bool_t ai_has_ipv4(struct ares_addrinfo *ai)
{
struct ares_addrinfo_node *node;
for (node = ai->nodes; node != NULL; node = node->ai_next) {
if (node->ai_family == AF_INET) {
return ARES_TRUE;
}
}
return ARES_FALSE;
}
static void host_callback(void *arg, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec)
{
@ -499,7 +511,27 @@ static void host_callback(void *arg, ares_status_t status, size_t timeouts,
addinfostatus =
ares__parse_into_addrinfo(dnsrec, ARES_TRUE, hquery->port, hquery->ai);
}
if (addinfostatus == ARES_SUCCESS) {
/* We sent out ipv4 and ipv6 requests simultaneously. If we got a
* successful ipv4 response, we want to go ahead and tell the ipv6 request
* that if it fails or times out to not try again since we have the data
* we need.
*
* Our initial implementation of this would terminate retries if we got any
* successful response (ipv4 _or_ ipv6). But we did get some user-reported
* issues with this that had bad system configs and odd behavior:
* https://github.com/alpinelinux/docker-alpine/issues/366
*
* Essentially the ipv6 query succeeded but the ipv4 query failed or timed
* out, and so we only returned the ipv6 address, but the host couldn't
* use ipv6. If we continued to allow ipv4 retries it would have found a
* server that worked and returned both address classes (this is clearly
* unexpected behavior).
*
* At some point down the road if ipv6 actually becomes required and
* reliable we can drop this ipv4 check.
*/
if (addinfostatus == ARES_SUCCESS && ai_has_ipv4(hquery->ai)) {
terminate_retries(hquery, ares_dns_record_get_id(dnsrec));
}
}

@ -602,6 +602,51 @@ done:
return status;
}
static ares_bool_t issue_might_be_edns(const ares_dns_record_t *req,
const ares_dns_record_t *rsp)
{
const ares_dns_rr_t *rr;
/* If we use EDNS and server answers with FORMERR without an OPT RR, the
* protocol extension is not understood by the responder. We must retry the
* query without EDNS enabled. */
if (ares_dns_record_get_rcode(rsp) != ARES_RCODE_FORMERR) {
return ARES_FALSE;
}
rr = ares_dns_get_opt_rr_const(req);
if (rr == NULL) {
/* We didn't send EDNS */
return ARES_FALSE;
}
if (ares_dns_get_opt_rr_const(rsp) == NULL) {
/* Spec says EDNS won't be echo'd back on non-supporting servers, so
* retry without EDNS */
return ARES_TRUE;
}
/* As per issue #911 some non-compliant servers that do indeed support EDNS
* but don't support unrecognized option codes exist. At this point we
* expect them to have also returned an EDNS opt record, but we may remove
* that check in the future. Lets detect this situation if we're sending
* option codes */
if (ares_dns_rr_get_opt_cnt(rr, ARES_RR_OPT_OPTIONS) == 0) {
/* We didn't send any option codes */
return ARES_FALSE;
}
if (ares_dns_get_opt_rr_const(rsp) != NULL) {
/* At this time we're requiring the server to respond with EDNS opt
* records since that's what has been observed in the field. We might
* find in the future we have to remove this, who knows. Lets go
* ahead and force a retry without EDNS*/
return ARES_TRUE;
}
return ARES_FALSE;
}
/* Handle an answer from a server. This must NEVER cleanup the
* server connection! Return something other than ARES_SUCCESS to cause
* the connection to be terminated after this call. */
@ -660,12 +705,10 @@ static ares_status_t process_answer(ares_channel_t *channel,
ares__llist_node_destroy(query->node_queries_to_conn);
query->node_queries_to_conn = NULL;
/* If we use EDNS and server answers with FORMERR without an OPT RR, the
* protocol extension is not understood by the responder. We must retry the
* query without EDNS enabled. */
if (ares_dns_record_get_rcode(rdnsrec) == ARES_RCODE_FORMERR &&
ares_dns_get_opt_rr_const(query->query) != NULL &&
ares_dns_get_opt_rr_const(rdnsrec) == NULL) {
/* There are old servers that don't understand EDNS at all, then some servers
* that have non-compliant implementations. Lets try to detect this sort
* of thing. */
if (issue_might_be_edns(query->query, rdnsrec)) {
status = rewrite_without_edns(query);
if (status != ARES_SUCCESS) {
end_query(channel, server, query, status, NULL);

@ -150,6 +150,11 @@ ares_status_t ares_send_nolock(ares_channel_t *channel,
/* Duplicate Query */
status = ares_dns_record_duplicate_ex(&query->query, dnsrec);
if (status != ARES_SUCCESS) {
/* Sometimes we might get a EBADRESP response from duplicate due to
* the way it works (write and parse), rewrite it to EBADQUERY. */
if (status == ARES_EBADRESP) {
status = ARES_EBADQUERY;
}
ares_free(query);
callback(arg, status, 0, NULL);
return status;

@ -326,13 +326,15 @@ static void *ares_event_thread(void *arg)
e->ev_sys->wait(e, timeout_ms);
/* Relock before we loop again */
ares__thread_mutex_lock(e->mutex);
/* Each iteration should do timeout processing */
if (e->isup) {
ares__thread_mutex_unlock(e->mutex);
ares_process_fd(e->channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
ares__thread_mutex_lock(e->mutex);
}
/* Relock before we loop again */
ares__thread_mutex_lock(e->mutex);
}
/* Lets cleanup while we're in the thread itself */

@ -147,6 +147,18 @@ ares_status_t ares__dns_multistring_add_own(ares__dns_multistring_t *strs,
return status;
}
/* Issue #921, ares_dns_multistring_get() doesn't have a way to indicate
* success or fail on a zero-length string which is actually valid. So we
* are going to allocate a 1-byte buffer to use as a placeholder in this
* case */
if (str == NULL) {
str = ares_malloc_zero(1);
if (str == NULL) {
ares__array_remove_last(strs->strs);
return ARES_ENOMEM;
}
}
data->data = str;
data->len = len;

@ -971,36 +971,38 @@ ares_status_t ares__buf_parse_dns_abinstr(ares__buf_t *buf,
break; /* LCOV_EXCL_LINE: DefensiveCoding */
}
if (len) {
/* When used by the _str() parser, it really needs to be validated to
* be a valid printable ascii string. Do that here */
if (validate_printable && ares__buf_len(buf) >= len) {
size_t mylen;
const char *data = (const char *)ares__buf_peek(buf, &mylen);
if (!ares__str_isprint(data, len)) {
status = ARES_EBADSTR;
break;
}
/* When used by the _str() parser, it really needs to be validated to
* be a valid printable ascii string. Do that here */
if (len && validate_printable && ares__buf_len(buf) >= len) {
size_t mylen;
const char *data = (const char *)ares__buf_peek(buf, &mylen);
if (!ares__str_isprint(data, len)) {
status = ARES_EBADSTR;
break;
}
}
if (strs != NULL) {
unsigned char *data = NULL;
if (strs != NULL) {
unsigned char *data = NULL;
if (len) {
status = ares__buf_fetch_bytes_dup(buf, len, ARES_TRUE, &data);
if (status != ARES_SUCCESS) {
break;
}
status = ares__dns_multistring_add_own(*strs, data, len);
if (status != ARES_SUCCESS) {
ares_free(data);
break;
}
} else {
status = ares__buf_consume(buf, len);
if (status != ARES_SUCCESS) {
break;
}
}
status = ares__dns_multistring_add_own(*strs, data, len);
if (status != ARES_SUCCESS) {
ares_free(data);
break;
}
} else {
status = ares__buf_consume(buf, len);
if (status != ARES_SUCCESS) {
break;
}
}
}
if (status != ARES_SUCCESS && strs != NULL) {

@ -841,6 +841,72 @@ class NoRotateMultiMockTestAI : public MockMultiServerChannelTestAI {
NoRotateMultiMockTestAI() : MockMultiServerChannelTestAI(nullptr, ARES_OPT_NOROTATE) {}
};
/* We want to terminate retries of other address classes on getaddrinfo if one
* address class is returned already to return replies faster.
* UPDATE: actually we want to do this only if the address class we received
* was ipv4. We've seen issues if ipv6 was returned but the host was
* really only capable of ipv4.
*/
TEST_P(NoRotateMultiMockTestAI, v4Worksv6Timesout) {
std::vector<byte> nothing;
DNSPacket rsp4;
rsp4.set_response().set_aa()
.add_question(new DNSQuestion("www.example.com", T_A))
.add_answer(new DNSARR("www.example.com", 0x0100, {0x01, 0x02, 0x03, 0x04}));
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_A))
.WillOnce(SetReply(servers_[0].get(), &rsp4));
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_AAAA))
.WillOnce(SetReplyData(servers_[0].get(), nothing));
AddrInfoResult result;
struct ares_addrinfo_hints hints = {0, 0, 0, 0};
hints.ai_family = AF_UNSPEC;
hints.ai_flags = ARES_AI_NOSORT;
ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result);
Process();
EXPECT_TRUE(result.done_);
EXPECT_EQ(result.status_, ARES_SUCCESS);
EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
}
TEST_P(NoRotateMultiMockTestAI, v6Worksv4TimesoutFirst) {
std::vector<byte> nothing;
DNSPacket rsp4;
rsp4.set_response().set_aa()
.add_question(new DNSQuestion("www.example.com", T_A))
.add_answer(new DNSARR("www.example.com", 0x0100, {0x01, 0x02, 0x03, 0x04}));
DNSPacket rsp6;
rsp6.set_response().set_aa()
.add_question(new DNSQuestion("www.example.com", T_AAAA))
.add_answer(new DNSAaaaRR("www.example.com", 100,
{0x21, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03}));
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_A))
.WillOnce(SetReplyData(servers_[0].get(), nothing));
EXPECT_CALL(*servers_[0], OnRequest("www.example.com", T_AAAA))
.WillOnce(SetReply(servers_[0].get(), &rsp6));
EXPECT_CALL(*servers_[1], OnRequest("www.example.com", T_A))
.WillOnce(SetReply(servers_[1].get(), &rsp4));
AddrInfoResult result;
struct ares_addrinfo_hints hints = {0, 0, 0, 0};
hints.ai_family = AF_UNSPEC;
hints.ai_flags = ARES_AI_NOSORT;
ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result);
Process();
EXPECT_TRUE(result.done_);
EXPECT_EQ(result.status_, ARES_SUCCESS);
EXPECT_THAT(result.ai_, IncludesNumAddresses(2));
EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
EXPECT_THAT(result.ai_, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
}
TEST_P(NoRotateMultiMockTestAI, ThirdServer) {
struct ares_options opts;

@ -790,6 +790,29 @@ TEST_P(MockEDNSChannelTest, RetryWithoutEDNS) {
EXPECT_EQ("{'www.google.com' aliases=[] addrs=[1.2.3.4]}", ss.str());
}
// Issue #911
TEST_P(MockUDPChannelTest, RetryWithoutEDNSNonCompliant) {
DNSPacket rspfail;
rspfail.set_response().set_aa().set_rcode(FORMERR)
.add_question(new DNSQuestion("www.google.com", T_A))
.add_additional(new DNSOptRR(0, 0, 0, 1280, { }, { }, false));
DNSPacket rspok;
rspok.set_response()
.add_question(new DNSQuestion("www.google.com", T_A))
.add_answer(new DNSARR("www.google.com", 100, {1, 2, 3, 4}));
EXPECT_CALL(server_, OnRequest("www.google.com", T_A))
.WillOnce(SetReply(&server_, &rspfail))
.WillOnce(SetReply(&server_, &rspok));
HostResult result;
ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result);
Process();
EXPECT_TRUE(result.done_);
std::stringstream ss;
ss << result.host_;
EXPECT_EQ("{'www.google.com' aliases=[] addrs=[1.2.3.4]}", ss.str());
}
TEST_P(MockChannelTest, SearchDomains) {
DNSPacket nofirst;
nofirst.set_response().set_aa().set_rcode(NXDOMAIN)

@ -38,7 +38,7 @@ TEST_F(LibraryTest, ParseTxtReplyOK) {
std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_question(new DNSQuestion("example.com", T_TXT))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
@ -68,7 +68,7 @@ TEST_F(LibraryTest, ParseTxtExtReplyOK) {
std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_question(new DNSQuestion("example.com", T_TXT))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
@ -95,6 +95,39 @@ TEST_F(LibraryTest, ParseTxtExtReplyOK) {
ares_free_data(txt);
}
TEST_F(LibraryTest, ParseTxtEmpty) {
DNSPacket pkt;
std::string expected1 = "";
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_TXT))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}));
std::vector<byte> data = pkt.data();
ares_dns_record_t *dnsrec = NULL;
ares_dns_rr_t *rr = NULL;
EXPECT_EQ(ARES_SUCCESS, ares_dns_parse(data.data(), data.size(), 0, &dnsrec));
EXPECT_EQ(1, ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_ANSWER));
rr = ares_dns_record_rr_get(dnsrec, ARES_SECTION_ANSWER, 0);
ASSERT_NE(nullptr, rr);
EXPECT_EQ(ARES_REC_TYPE_TXT, ares_dns_rr_get_type(rr));
size_t txtdata_len;
const unsigned char *txtdata;
/* Using array methodology */
EXPECT_EQ(1, ares_dns_rr_get_abin_cnt(rr, ARES_RR_TXT_DATA));
txtdata = ares_dns_rr_get_abin(rr, ARES_RR_TXT_DATA, 0, &txtdata_len);
EXPECT_EQ(txtdata_len, 0);
EXPECT_NE(nullptr, txtdata);
/* Using combined methodology */
txtdata = ares_dns_rr_get_bin(rr, ARES_RR_TXT_DATA, &txtdata_len);
EXPECT_EQ(txtdata_len, 0);
EXPECT_NE(nullptr, txtdata);
ares_dns_record_destroy(dnsrec); dnsrec = NULL;
}
TEST_F(LibraryTest, ParseTxtMalformedReply1) {
std::vector<byte> data = {
0x12, 0x34, // qid
@ -213,7 +246,7 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
std::string expected2a = "txt2a";
std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_question(new DNSQuestion("example.com", T_TXT))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
@ -227,7 +260,7 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt);
pkt.add_question(new DNSQuestion("example.com", T_MX));
pkt.add_question(new DNSQuestion("example.com", T_TXT));
#ifdef DISABLED
// Question != answer
@ -240,13 +273,13 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
#endif
// Two questions.
pkt.add_question(new DNSQuestion("example.com", T_MX));
pkt.add_question(new DNSQuestion("example.com", T_TXT));
data = pkt.data();
txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt);
pkt.questions_.clear();
pkt.add_question(new DNSQuestion("example.com", T_MX));
pkt.add_question(new DNSQuestion("example.com", T_TXT));
// No answer.
pkt.answers_.clear();
@ -274,7 +307,7 @@ TEST_F(LibraryTest, ParseTxtReplyAllocFail) {
std::string expected2a = "txt2a";
std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_question(new DNSQuestion("example.com", T_TXT))
.add_answer(new DNSCnameRR("example.com", 300, "c.example.com"))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected1}))

Loading…
Cancel
Save