Compare commits

...

8 Commits

Author SHA1 Message Date
Brad House b335522ce7 Empty TXT records were not being preserved (#922) 2 weeks ago
Brad House 42469b0b11 Android: <= 7 might require sys/system_properties.h 2 weeks ago
Brad House 2e82080a3e fix backport issue 2 weeks ago
Jiwoo Park 76e1a5cf89 Use `_GNU_SOURCE` macro on Android (#914) 2 weeks ago
Jiwoo Park ac352d70dc Silence TSAN, lock before checking `ares_event_thread::isup` (#915) 2 weeks ago
Brad House 3ba577c8c4 ares_getaddrinfo() for AF_UNSPEC should retry if ipv6 received 2 weeks ago
Brad House 6c54b345ae Backport parts of bbea2cd from main 2 weeks ago
Brad House da2b703cef 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. 46
      src/lib/ares__buf.c
  6. 11
      src/lib/ares_dns_multistring.c
  7. 8
      src/lib/ares_event_thread.c
  8. 34
      src/lib/ares_getaddrinfo.c
  9. 5
      src/lib/ares_send.c
  10. 66
      test/ares-test-mock-ai.cc
  11. 47
      test/ares-test-parse-txt.cc

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

@ -262,7 +262,7 @@ ENDIF ()
# Set system-specific compiler flags # Set system-specific compiler flags
IF (CMAKE_SYSTEM_NAME STREQUAL "Darwin") IF (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
LIST (APPEND SYSFLAGS -D_DARWIN_C_SOURCE) 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) LIST (APPEND SYSFLAGS -D_GNU_SOURCE -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700)
ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "SunOS") ELSEIF (CMAKE_SYSTEM_NAME STREQUAL "SunOS")
LIST (APPEND SYSFLAGS -D__EXTENSIONS__ -D_REENTRANT -D_XOPEN_SOURCE=700) LIST (APPEND SYSFLAGS -D__EXTENSIONS__ -D_REENTRANT -D_XOPEN_SOURCE=700)

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

@ -363,7 +363,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 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 split these on separate lines, but for some reason autotools on Windows doesn't
dnl allow this, even tried ending lines with a backslash. 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 not found
[], [],
dnl to do if found dnl to do if found
@ -478,6 +478,9 @@ cares_all_includes="
#ifdef HAVE_RESOLV_H #ifdef HAVE_RESOLV_H
# include <resolv.h> # include <resolv.h>
#endif #endif
#ifdef HAVE_SYS_SYSTEM_PROPERTIES_H
# include <sys/system_properties.h>
#endif
#ifdef HAVE_IPHLPAPI_H #ifdef HAVE_IPHLPAPI_H
# include <iphlpapi.h> # include <iphlpapi.h>
#endif #endif

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

@ -138,6 +138,17 @@ ares_status_t ares__dns_multistring_add_own(ares__dns_multistring_t *strs,
strs->alloc = newalloc; strs->alloc = newalloc;
} }
/* 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) {
return ARES_ENOMEM;
}
}
strs->strs[strs->cnt].data = str; strs->strs[strs->cnt].data = str;
strs->strs[strs->cnt].len = len; strs->strs[strs->cnt].len = len;
strs->cnt++; strs->cnt++;

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

@ -484,6 +484,18 @@ static void terminate_retries(const struct host_query *hquery,
query->no_retries = ARES_TRUE; 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, static void host_callback(void *arg, ares_status_t status, size_t timeouts,
const ares_dns_record_t *dnsrec) const ares_dns_record_t *dnsrec)
{ {
@ -499,7 +511,27 @@ static void host_callback(void *arg, ares_status_t status, size_t timeouts,
addinfostatus = addinfostatus =
ares__parse_into_addrinfo(dnsrec, ARES_TRUE, hquery->port, hquery->ai); 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)); terminate_retries(hquery, ares_dns_record_get_id(dnsrec));
} }
} }

@ -151,6 +151,11 @@ ares_status_t ares_send_nolock(ares_channel_t *channel,
/* Duplicate Query */ /* Duplicate Query */
status = ares_dns_record_duplicate_ex(&query->query, dnsrec); status = ares_dns_record_duplicate_ex(&query->query, dnsrec);
if (status != ARES_SUCCESS) { 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); ares_free(query);
callback(arg, status, 0, NULL); callback(arg, status, 0, NULL);
return status; return status;

@ -724,6 +724,72 @@ class NoRotateMultiMockTestAI : public MockMultiServerChannelTestAI {
NoRotateMultiMockTestAI() : MockMultiServerChannelTestAI(nullptr, ARES_OPT_NOROTATE) {} 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) { TEST_P(NoRotateMultiMockTestAI, ThirdServer) {
struct ares_options opts; struct ares_options opts;

@ -38,7 +38,7 @@ TEST_F(LibraryTest, ParseTxtReplyOK) {
std::string expected2a = "txt2a"; std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7); std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa() 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})); .add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data(); std::vector<byte> data = pkt.data();
@ -68,7 +68,7 @@ TEST_F(LibraryTest, ParseTxtExtReplyOK) {
std::string expected2a = "txt2a"; std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7); std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa() 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})); .add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data(); std::vector<byte> data = pkt.data();
@ -95,6 +95,39 @@ TEST_F(LibraryTest, ParseTxtExtReplyOK) {
ares_free_data(txt); 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) { TEST_F(LibraryTest, ParseTxtMalformedReply1) {
std::vector<byte> data = { std::vector<byte> data = {
0x12, 0x34, // qid 0x12, 0x34, // qid
@ -213,7 +246,7 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
std::string expected2a = "txt2a"; std::string expected2a = "txt2a";
std::string expected2b = "txt2b"; std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa() 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, {expected1})) .add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b})); .add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
@ -227,7 +260,7 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
txt = nullptr; txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt)); EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, 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 #ifdef DISABLED
// Question != answer // Question != answer
@ -240,13 +273,13 @@ TEST_F(LibraryTest, ParseTxtReplyErrors) {
#endif #endif
// Two questions. // Two questions.
pkt.add_question(new DNSQuestion("example.com", T_MX)); pkt.add_question(new DNSQuestion("example.com", T_TXT));
data = pkt.data(); data = pkt.data();
txt = nullptr; txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt)); EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt); EXPECT_EQ(nullptr, txt);
pkt.questions_.clear(); pkt.questions_.clear();
pkt.add_question(new DNSQuestion("example.com", T_MX)); pkt.add_question(new DNSQuestion("example.com", T_TXT));
// No answer. // No answer.
pkt.answers_.clear(); pkt.answers_.clear();
@ -274,7 +307,7 @@ TEST_F(LibraryTest, ParseTxtReplyAllocFail) {
std::string expected2a = "txt2a"; std::string expected2a = "txt2a";
std::string expected2b = "txt2b"; std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa() 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 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}))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected1})) .add_answer(new DNSTxtRR("c.example.com", 100, {expected1}))

Loading…
Cancel
Save