From af3ee9a8baf2624029cd0ea8177118112b7cecd0 Mon Sep 17 00:00:00 2001 From: David Drysdale Date: Tue, 29 Sep 2015 10:22:51 +0100 Subject: [PATCH] test: Add initial unit tests for c-ares library The tests are written in C++11, using the GoogleTest and GoogleMock frameworks. They have their own independent autoconf setup, so that users of the library need not have a C++ compiler just to get c-ares working (however, the test/configure.ac file does assume the use of a shared top-level m4/ directory). However, this autoconf setup has only been tested on Linux and OSX so far. Run with "./arestest", or "./arestest -v" to see extra debug info. The GoogleTest options for running specific tests are also available (e.g. "./arestest --gtest_filter=*Live*"). The tests are nowhere near complete yet (currently hitting around 60% coverage as reported by gcov), but they do include examples of a few different styles of testing: - There are live tests (ares-test-live.cc), which assume that the current machine has a valid DNS setup and connection to the internet; these tests issue queries for real domains but don't particularly check what gets returned. The tests will fail on an offline machine. - There a few mock tests (ares-test-mock.cc) that set up a fake DNS server and inject its port into the c-ares library configuration. These tests allow specific response messages to be crafted and injected, and so are likely to be used for many more tests in future. - To make this generation/injection easier, the dns-proto.h file includes C++ helper classes for building DNS packets. - Other library entrypoints that don't require network activity (e.g. ares_parse_*_reply) are tested directly. - There are few tests of library-internal functions that are not normally visible to API users (in ares-test-internal.cc). - A couple of the tests use a helper method of the test fixture to inject memory allocation failures, using the earlier change to the library to allow override of malloc/realloc/free. - There is also an entrypoint to allow Clang's libfuzzer to drive the packet parsing code in ares_parse_*_reply, together with a standalone wrapper for it (./aresfuzz) to allow use of afl-fuzz for further fuzz testing. --- test/.gitignore | 9 + test/Makefile.am | 33 ++ test/ares-fuzz.cc | 20 ++ test/ares-test-fuzz.cc | 44 +++ test/ares-test-init.cc | 71 ++++ test/ares-test-internal.cc | 56 ++++ test/ares-test-live.cc | 121 +++++++ test/ares-test-main.cc | 19 ++ test/ares-test-misc.cc | 222 +++++++++++++ test/ares-test-mock.cc | 139 ++++++++ test/ares-test-parse.cc | 640 +++++++++++++++++++++++++++++++++++++ test/ares-test.cc | 385 ++++++++++++++++++++++ test/ares-test.h | 197 ++++++++++++ test/buildconf | 2 + test/configure.ac | 17 + test/dns-proto-test.cc | 131 ++++++++ test/dns-proto.cc | 577 +++++++++++++++++++++++++++++++++ test/dns-proto.h | 235 ++++++++++++++ test/fuzzinput/answer_a | Bin 0 -> 62 bytes test/fuzzinput/answer_aaaa | Bin 0 -> 62 bytes 20 files changed, 2918 insertions(+) create mode 100644 test/.gitignore create mode 100644 test/Makefile.am create mode 100644 test/ares-fuzz.cc create mode 100644 test/ares-test-fuzz.cc create mode 100644 test/ares-test-init.cc create mode 100644 test/ares-test-internal.cc create mode 100644 test/ares-test-live.cc create mode 100644 test/ares-test-main.cc create mode 100644 test/ares-test-misc.cc create mode 100644 test/ares-test-mock.cc create mode 100644 test/ares-test-parse.cc create mode 100644 test/ares-test.cc create mode 100644 test/ares-test.h create mode 100755 test/buildconf create mode 100644 test/configure.ac create mode 100644 test/dns-proto-test.cc create mode 100644 test/dns-proto.cc create mode 100644 test/dns-proto.h create mode 100644 test/fuzzinput/answer_a create mode 100644 test/fuzzinput/answer_aaaa diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000..278673b1 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,9 @@ +*.o +libgtest.a +libgmock.a +arestest +aresfuzz +arestest.log +arestest.trs +test-suite.log +fuzzoutput \ No newline at end of file diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 00000000..ae4a553c --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,33 @@ +# Where to find the c-ares source code; needed because the tests use library-internal headers +ARES_SRC_DIR = .. +# Where to find the built c-ares static library +ARES_BLD_DIR = .. +AUTOMAKE_OPTIONS = foreign +ACLOCAL_AMFLAGS = -I ../m4 +GMOCK_DIR = gmock-1.7.0 +GTEST_DIR = $(GMOCK_DIR)/gtest +# Note use of -isystem to force use of local gMock/gTest even if there's an installed version. +CPPFLAGS += -I$(ARES_SRC_DIR) -isystem $(GTEST_DIR)/include -isystem $(GMOCK_DIR)/include +CXXFLAGS += -Wall --std=c++11 -g $(PTHREAD_CFLAGS) + +TESTS = arestest + +noinst_PROGRAMS = arestest aresfuzz +arestest_SOURCES = ares-test-main.cc ares-test-init.cc ares-test.cc ares-test-parse.cc ares-test-misc.cc ares-test-live.cc ares-test-mock.cc ares-test-internal.cc dns-proto.cc dns-proto-test.cc ares-test.h dns-proto.h +arestest_LDADD = libgmock.la libgtest.la $(ARES_BLD_DIR)/libcares.la $(PTHREAD_LIBS) + +# Not interested in coverage of test code, but linking the test binary needs the coverage option +@CODE_COVERAGE_RULES@ +arestest_LDFLAGS = $(CODE_COVERAGE_LDFLAGS) + +noinst_LTLIBRARIES = libgmock.la libgtest.la + +libgmock_la_SOURCES = $(GMOCK_DIR)/src/gmock-all.cc +libgmock_la_CPPFLAGS = -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -isystem $(GMOCK_DIR)/include -I$(GMOCK_DIR) +libgtest_la_SOURCES = $(GTEST_DIR)/src/gtest-all.cc +libgtest_la_CPPFLAGS = -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -isystem $(GMOCK_DIR)/include -I$(GMOCK_DIR) + +aresfuzz_SOURCES = ares-test-fuzz.cc ares-fuzz.cc +aresfuzz_LDADD = $(ARES_BLD_DIR)/libcares.la + +test: check diff --git a/test/ares-fuzz.cc b/test/ares-fuzz.cc new file mode 100644 index 00000000..4680b727 --- /dev/null +++ b/test/ares-fuzz.cc @@ -0,0 +1,20 @@ +// General driver to allow command-line fuzzer (i.e. afl) to +// fuzz the libfuzzer entrypoint. +#include +#include + +#include + +extern "C" void LLVMFuzzerTestOneInput(const unsigned char *data, + unsigned long size); +int main() { + std::vector input; + while (true) { + unsigned char buffer[1024]; + int len = read(fileno(stdin), buffer, sizeof(buffer)); + if (len <= 0) break; + input.insert(input.end(), buffer, buffer + len); + } + LLVMFuzzerTestOneInput(input.data(), input.size()); + return 0; +} diff --git a/test/ares-test-fuzz.cc b/test/ares-test-fuzz.cc new file mode 100644 index 00000000..49d844c5 --- /dev/null +++ b/test/ares-test-fuzz.cc @@ -0,0 +1,44 @@ +#include "ares-test.h" +#include + +// Entrypoint for Clang's libfuzzer +extern "C" void LLVMFuzzerTestOneInput(const unsigned char *data, + unsigned long size) { + // Feed the data into each of the ares_parse_*_reply functions. + struct hostent *host = nullptr; + struct ares_addrttl info[5]; + int count = 5; + ares_parse_a_reply(data, size, &host, info, &count); + if (host) ares_free_hostent(host); + + host = nullptr; + struct ares_addr6ttl info6[5]; + count = 5; + ares_parse_aaaa_reply(data, size, &host, info6, &count); + if (host) ares_free_hostent(host); + + host = nullptr; + ares::byte addrv4[4] = {0x10, 0x20, 0x30, 0x40}; + ares_parse_ptr_reply(data, size, addrv4, sizeof(addrv4), AF_INET, &host); + if (host) ares_free_hostent(host); + + host = nullptr; + ares_parse_ns_reply(data, size, &host); + if (host) ares_free_hostent(host); + + struct ares_srv_reply* srv = nullptr; + ares_parse_srv_reply(data, size, &srv); + if (srv) ares_free_data(srv); + + struct ares_mx_reply* mx = nullptr; + ares_parse_mx_reply(data, size, &mx); + if (mx) ares_free_data(mx); + + struct ares_txt_reply* txt = nullptr; + ares_parse_txt_reply(data, size, &txt); + if (txt) ares_free_data(txt); + + struct ares_soa_reply* soa = nullptr; + ares_parse_soa_reply(data, size, &soa); + if (soa) ares_free_data(soa); +} diff --git a/test/ares-test-init.cc b/test/ares-test-init.cc new file mode 100644 index 00000000..14397844 --- /dev/null +++ b/test/ares-test-init.cc @@ -0,0 +1,71 @@ + +#include "ares-test.h" + +// library initialization is only needed for windows builds +#ifdef USE_WINSOCK +#define EXPECTED_NONINIT ARES_ENOTINITIALIZED +#else +#define EXPECTED_NONINIT ARES_SUCCESS +#endif + +namespace ares { +namespace test { + +TEST(LibraryInit, Basic) { + EXPECT_EQ(EXPECTED_NONINIT, ares_library_initialized()); + EXPECT_EQ(ARES_SUCCESS, ares_library_init(ARES_LIB_INIT_ALL)); + EXPECT_EQ(ARES_SUCCESS, ares_library_initialized()); + ares_library_cleanup(); + EXPECT_EQ(EXPECTED_NONINIT, ares_library_initialized()); +} + +TEST(LibraryInit, DISABLED_InvalidParam) { + // TODO: police flags argument to ares_library_init() + EXPECT_EQ(ARES_EBADQUERY, ares_library_init(ARES_LIB_INIT_ALL << 2)); + EXPECT_EQ(EXPECTED_NONINIT, ares_library_initialized()); + ares_library_cleanup(); +} + +TEST(LibraryInit, Nested) { + EXPECT_EQ(EXPECTED_NONINIT, ares_library_initialized()); + EXPECT_EQ(ARES_SUCCESS, ares_library_init(ARES_LIB_INIT_ALL)); + EXPECT_EQ(ARES_SUCCESS, ares_library_initialized()); + EXPECT_EQ(ARES_SUCCESS, ares_library_init(ARES_LIB_INIT_ALL)); + EXPECT_EQ(ARES_SUCCESS, ares_library_initialized()); + ares_library_cleanup(); + EXPECT_EQ(ARES_SUCCESS, ares_library_initialized()); + ares_library_cleanup(); + EXPECT_EQ(EXPECTED_NONINIT, ares_library_initialized()); +} + +TEST_F(LibraryTest, BasicChannelInit) { + EXPECT_EQ(ARES_SUCCESS, ares_library_init(ARES_LIB_INIT_ALL)); + ares_channel channel = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_init(&channel)); + EXPECT_NE(nullptr, channel); + ares_destroy(channel); + ares_library_cleanup(); +} + +TEST_F(LibraryTest, FailChannelInit) { + EXPECT_EQ(ARES_SUCCESS, + ares_library_init_mem(ARES_LIB_INIT_ALL, + &LibraryTest::amalloc, + &LibraryTest::afree, + &LibraryTest::arealloc)); + SetAllocFail(1); + ares_channel channel = nullptr; + EXPECT_EQ(ARES_ENOMEM, ares_init(&channel)); + EXPECT_EQ(nullptr, channel); + ares_library_cleanup(); +} + +#ifdef USE_WINSOCK +TEST(Init, NoLibraryInit) { + ares_channel channel = nullptr; + EXPECT_EQ(ARES_ENOTINITIALIZED, ares_init(&channel)); +} +#endif + +} // namespace test +} // namespace ares diff --git a/test/ares-test-internal.cc b/test/ares-test-internal.cc new file mode 100644 index 00000000..3bda6f0c --- /dev/null +++ b/test/ares-test-internal.cc @@ -0,0 +1,56 @@ +#include "ares-test.h" +#include "dns-proto.h" + +extern "C" { +#include "ares_nowarn.h" +#include "ares_inet_net_pton.h" +#include "bitncmp.h" +} + +#include +#include + +namespace ares { +namespace test { + +TEST_F(LibraryTest, InetPtoN) { + struct in_addr a4; + struct in6_addr a6; +#ifdef DISABLED + EXPECT_EQ(1, ares_inet_net_pton(AF_INET, "1.2.3.4", &a4, sizeof(a4))); + EXPECT_EQ(1, ares_inet_net_pton(AF_INET6, "12:34::ff", &a6, sizeof(a6))); + EXPECT_EQ(1, ares_inet_net_pton(AF_INET6, "12:34::ffff:1.2.3.4", &a6, sizeof(a6))); + EXPECT_EQ(0, ares_inet_net_pton(AF_INET, "xyzzy", &a4, sizeof(a4))); + EXPECT_EQ(-1, ares_inet_net_pton(AF_INET+AF_INET6, "1.2.3.4", &a4, sizeof(a4))); +#endif + EXPECT_EQ(1, ares_inet_pton(AF_INET, "1.2.3.4", &a4)); + EXPECT_EQ(1, ares_inet_pton(AF_INET6, "12:34::ff", &a6)); + EXPECT_EQ(1, ares_inet_pton(AF_INET6, "12:34::ffff:1.2.3.4", &a6)); + EXPECT_EQ(0, ares_inet_pton(AF_INET, "xyzzy", &a4)); + EXPECT_EQ(-1, ares_inet_pton(AF_INET+AF_INET6, "1.2.3.4", &a4)); +} + +#ifdef DISABLED +TEST(Misc, Bitncmp) { + byte a[4] = {0x80, 0x01, 0x02, 0x03}; + byte b[4] = {0x80, 0x01, 0x02, 0x04}; + EXPECT_EQ(-1, ares__bitncmp(a, b, sizeof(a)*8)); + EXPECT_EQ(1, ares__bitncmp(b, a, sizeof(a)*8)); + EXPECT_EQ(0, ares__bitncmp(a, a, sizeof(a)*8)); +} + +TEST_F(LibraryTest, Casts) { + ssize_t ssz = 100; + unsigned int u = 100; + int i = 100; + + unsigned int ru = aresx_sztoui(ssz); + EXPECT_EQ(u, ru); + int ri = aresx_sztosi(ssz); + EXPECT_EQ(i, ri); +} +#endif + + +} // namespace test +} // namespace ares diff --git a/test/ares-test-live.cc b/test/ares-test-live.cc new file mode 100644 index 00000000..7d4b85c0 --- /dev/null +++ b/test/ares-test-live.cc @@ -0,0 +1,121 @@ +// This file includes tests that attempt to do real lookups +// of DNS names using the local machine's live infrastructure. + +#include "ares-test.h" + +#include + +namespace ares { +namespace test { + +TEST_F(DefaultChannelTest, LiveGetHostByNameV4) { + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_LT(0, (int)result.host_.addrs_.size()); + EXPECT_EQ(AF_INET, result.host_.addrtype_); +} + +TEST_F(DefaultChannelTest, LiveGetHostByNameV6) { + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET6, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_LT(0, (int)result.host_.addrs_.size()); + EXPECT_EQ(AF_INET6, result.host_.addrtype_); +} + +TEST_F(DefaultChannelTest, LiveGetHostByAddrV4) { + HostResult result; + unsigned char addr[4] = {8, 8, 8, 8}; + ares_gethostbyaddr(channel_, addr, sizeof(addr), AF_INET, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_LT(0, (int)result.host_.addrs_.size()); + EXPECT_EQ(AF_INET, result.host_.addrtype_); +} + +TEST_F(DefaultChannelTest, LiveGetHostByAddrV6) { + HostResult result; + unsigned char addr[16] = {0x20, 0x01, 0x48, 0x60, 0x48, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0x88}; + ares_gethostbyaddr(channel_, addr, sizeof(addr), AF_INET6, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_LT(0, (int)result.host_.addrs_.size()); + EXPECT_EQ(AF_INET6, result.host_.addrtype_); +} + +TEST_F(DefaultChannelTest, LiveSearchA) { + SearchResult result; + ares_search(channel_, "www.facebook.com.", ns_c_in, ns_t_a, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveSearchNS) { + SearchResult result; + ares_search(channel_, "google.com.", ns_c_in, ns_t_ns, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveSearchMX) { + SearchResult result; + ares_search(channel_, "google.com.", ns_c_in, ns_t_mx, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveSearchTXT) { + SearchResult result; + ares_search(channel_, "google.com.", ns_c_in, ns_t_txt, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveSearchSOA) { + SearchResult result; + ares_search(channel_, "google.com.", ns_c_in, ns_t_soa, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveSearchANY) { + SearchResult result; + ares_search(channel_, "facebook.com.", ns_c_in, ns_t_any, + SearchCallback, &result); + Process(); + EXPECT_TRUE(result.done_); +} + +TEST_F(DefaultChannelTest, LiveGetNameInfo) { + NameInfoResult result; + struct sockaddr_in sockaddr; + memset(&sockaddr, 0, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(53); + sockaddr.sin_addr.s_addr = htonl(0x08080808); + ares_getnameinfo(channel_, (const struct sockaddr*)&sockaddr, sizeof(sockaddr), + ARES_NI_LOOKUPHOST|ARES_NI_LOOKUPSERVICE|ARES_NI_UDP, + NameInfoCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + +CARES_EXTERN void ares_getnameinfo(ares_channel channel, + const struct sockaddr *sa, + ares_socklen_t salen, + int flags, + ares_nameinfo_callback callback, + void *arg); + +} + +} // namespace test +} // namespace ares diff --git a/test/ares-test-main.cc b/test/ares-test-main.cc new file mode 100644 index 00000000..9a5a3250 --- /dev/null +++ b/test/ares-test-main.cc @@ -0,0 +1,19 @@ +#include +#include +#include +#include +#include + +#include "ares-test.h" + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + for (int ii = 1; ii < argc; ii++) { + if (strcmp(argv[ii], "-v") == 0) { + ares::test::verbose = true; + } + } + + int rc = RUN_ALL_TESTS(); + return rc; +} diff --git a/test/ares-test-misc.cc b/test/ares-test-misc.cc new file mode 100644 index 00000000..a9583dbd --- /dev/null +++ b/test/ares-test-misc.cc @@ -0,0 +1,222 @@ +#include "ares-test.h" +#include "dns-proto.h" + +#include +#include + +namespace ares { +namespace test { + +std::vector GetNameServers(ares_channel channel) { + struct ares_addr_node* servers = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_get_servers(channel, &servers)); + struct ares_addr_node* server = servers; + std::vector results; + while (server) { + switch (server->family) { + case AF_INET: + results.push_back(AddressToString((char*)&server->addr.addr4, 4)); + break; + case AF_INET6: + results.push_back(AddressToString((char*)&server->addr.addr6, 16)); + break; + default: + results.push_back(""); + break; + } + server = server->next; + } + ares_free_data(servers); + return results; +} + +TEST_F(DefaultChannelTest, GetServers) { + std::vector servers = GetNameServers(channel_); + if (verbose) { + for (const std::string& server : servers) { + std::cerr << "Nameserver: " << server << std::endl; + } + } +} + +TEST_F(DefaultChannelTest, SetServers) { + EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, nullptr)); + std::vector empty; + EXPECT_EQ(empty, GetNameServers(channel_)); + + struct ares_addr_node server1; + struct ares_addr_node server2; + server1.next = &server2; + server1.family = AF_INET; + server1.addr.addr4.s_addr = htonl(0x01020304); + server2.next = nullptr; + server2.family = AF_INET; + server2.addr.addr4.s_addr = htonl(0x02030405); + EXPECT_EQ(ARES_ENODATA, ares_set_servers(nullptr, &server1)); + + EXPECT_EQ(ARES_SUCCESS, ares_set_servers(channel_, &server1)); + std::vector expected = {"1.2.3.4", "2.3.4.5"}; + EXPECT_EQ(expected, GetNameServers(channel_)); +} + +TEST_F(DefaultChannelTest, SetServersCSV) { + EXPECT_EQ(ARES_ENODATA, ares_set_servers_csv(nullptr, "1.2.3.4")); + EXPECT_EQ(ARES_ENODATA, ares_set_servers_csv(nullptr, "xyzzy,plugh")); + EXPECT_EQ(ARES_ENODATA, ares_set_servers_csv(nullptr, "256.1.2.3")); + EXPECT_EQ(ARES_ENODATA, ares_set_servers_csv(nullptr, "1.2.3.4.5")); + EXPECT_EQ(ARES_ENODATA, ares_set_servers_csv(nullptr, "1:2:3:4:5")); + + EXPECT_EQ(ARES_SUCCESS, + ares_set_servers_csv(channel_, "1.2.3.4,0102:0304:0506:0708:0910:1112:1314:1516,2.3.4.5")); + std::vector expected = {"1.2.3.4", "0102:0304:0506:0708:0910:1112:1314:1516", "2.3.4.5"}; + EXPECT_EQ(expected, GetNameServers(channel_)); + + // Same, with spaces + EXPECT_EQ(ARES_EBADSTR, + ares_set_servers_csv(channel_, "1.2.3.4 , 0102:0304:0506:0708:0910:1112:1314:1516, 2.3.4.5")); + + // Same, with ports -- currently ignored + EXPECT_EQ(ARES_SUCCESS, + ares_set_servers_csv(channel_, "1.2.3.4:54,[0102:0304:0506:0708:0910:1112:1314:1516]:80,2.3.4.5:55")); + EXPECT_EQ(expected, GetNameServers(channel_)); +} + +TEST_F(DefaultChannelTest, TimeoutValue) { + struct timeval tinfo; + tinfo.tv_sec = 0; + tinfo.tv_usec = 0; + struct timeval tmax; + tmax.tv_sec = 0; + tmax.tv_usec = 10; + struct timeval* pt; + + // No timers => get max back. + pt = ares_timeout(channel_, &tmax, &tinfo); + EXPECT_EQ(&tmax, pt); + EXPECT_EQ(0, pt->tv_sec); + EXPECT_EQ(10, pt->tv_usec); + + pt = ares_timeout(channel_, nullptr, &tinfo); + EXPECT_EQ(nullptr, pt); + + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + + // Now there's a timer running. + pt = ares_timeout(channel_, &tmax, &tinfo); + EXPECT_EQ(&tmax, pt); + EXPECT_EQ(0, pt->tv_sec); + EXPECT_EQ(10, pt->tv_usec); + + tmax.tv_sec = 100; + pt = ares_timeout(channel_, &tmax, &tinfo); + EXPECT_EQ(&tinfo, pt); + + pt = ares_timeout(channel_, nullptr, &tinfo); + EXPECT_EQ(&tinfo, pt); + + Process(); +} + +TEST_F(LibraryTest, InetNtoP) { + struct in_addr addr; + addr.s_addr = htonl(0x01020304); + char buffer[256]; + EXPECT_EQ(buffer, ares_inet_ntop(AF_INET, &addr, buffer, sizeof(buffer))); + EXPECT_EQ("1.2.3.4", std::string(buffer)); +} + +TEST_F(LibraryTest, Mkquery) { + byte* p; + int len; + ares_mkquery("example.com", ns_c_in, ns_t_a, 0x1234, 0, &p, &len); + std::vector data(p, p + len); + ares_free_string(p); + + std::string actual = PacketToString(data); + DNSPacket pkt; + pkt.set_qid(0x1234).add_question(new DNSQuestion("example.com", ns_t_a)); + std::string expected = PacketToString(pkt.data()); + EXPECT_EQ(expected, actual); +} + +TEST_F(LibraryTest, CreateQuery) { + byte* p; + int len; + ares_create_query("exam\\@le.com", ns_c_in, ns_t_a, 0x1234, 0, &p, &len, 0); + std::vector data(p, p + len); + ares_free_string(p); + + std::string actual = PacketToString(data); + DNSPacket pkt; + pkt.set_qid(0x1234).add_question(new DNSQuestion("exam@le.com", ns_t_a)); + std::string expected = PacketToString(pkt.data()); + EXPECT_EQ(expected, actual); +} + +TEST_F(LibraryTest, CreateEDNSQuery) { + byte* p; + int len; + ares_create_query("example.com", ns_c_in, ns_t_a, 0x1234, 0, &p, &len, 1280); + std::vector data(p, p + len); + ares_free_string(p); + + std::string actual = PacketToString(data); + DNSPacket pkt; + pkt.set_qid(0x1234).add_question(new DNSQuestion("example.com", ns_t_a)) + .add_additional(new DNSOptRR(0, 1280)); + std::string expected = PacketToString(pkt.data()); + EXPECT_EQ(expected, actual); +} + +TEST_F(LibraryTest, CreateRootQuery) { + byte* p; + int len; + ares_create_query(".", ns_c_in, ns_t_a, 0x1234, 0, &p, &len, 0); + std::vector data(p, p + len); + ares_free_string(p); + + std::string actual = PacketToString(data); + DNSPacket pkt; + pkt.set_qid(0x1234).add_question(new DNSQuestion("", ns_t_a)); + std::string expected = PacketToString(pkt.data()); + EXPECT_EQ(expected, actual); +} + +TEST_F(LibraryTest, Version) { + // Assume linked to same version + EXPECT_EQ(std::string(ARES_VERSION_STR), + std::string(ares_version(nullptr))); + int version; + ares_version(&version); + EXPECT_EQ(ARES_VERSION, version); +} + +TEST_F(LibraryTest, Strerror) { + EXPECT_EQ("Successful completion", + std::string(ares_strerror(ARES_SUCCESS))); + EXPECT_EQ("DNS query cancelled", + std::string(ares_strerror(ARES_ECANCELLED))); + EXPECT_EQ("unknown", + std::string(ares_strerror(99))); +} + +TEST_F(LibraryTest, ExpandString) { + std::vector s1 = { 3, 'a', 'b', 'c'}; + char* result; + long len; + EXPECT_EQ(ARES_SUCCESS, + ares_expand_string(s1.data(), s1.data(), s1.size(), + (unsigned char**)&result, &len)); + EXPECT_EQ("abc", std::string(result)); + EXPECT_EQ(1 + 3, len); // amount of data consumed includes 1 byte len + EXPECT_EQ(ARES_EBADSTR, + ares_expand_string(s1.data() + 1, s1.data(), s1.size(), + (unsigned char**)&result, &len)); + EXPECT_EQ(ARES_EBADSTR, + ares_expand_string(s1.data() + 4, s1.data(), s1.size(), + (unsigned char**)&result, &len)); +} + +} // namespace test +} // namespace ares diff --git a/test/ares-test-mock.cc b/test/ares-test-mock.cc new file mode 100644 index 00000000..ec23e342 --- /dev/null +++ b/test/ares-test-mock.cc @@ -0,0 +1,139 @@ +#include "ares-test.h" +#include "dns-proto.h" + +#include + +#include +#include + +using testing::InvokeWithoutArgs; + +namespace ares { +namespace test { + +TEST_F(MockChannelTest, Basic) { + std::vector reply = { + 0x00, 0x00, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // 1 question + 0x00, 0x01, // 1 answer RRs + 0x00, 0x00, // 0 authority RRs + 0x00, 0x00, // 0 additional RRs + // Question + 0x03, 'w', 'w', 'w', + 0x06, 'g', 'o', 'o', 'g', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer + 0x03, 'w', 'w', 'w', + 0x06, 'g', 'o', 'o', 'g', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + 0x00, 0x00, 0x01, 0x00, // TTL + 0x00, 0x04, // rdata length + 0x01, 0x02, 0x03, 0x04 + }; + + EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a)) + .WillOnce(SetReplyData(&server_, reply)); + + 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_F(MockChannelTest, SearchDomains) { + DNSPacket nofirst; + nofirst.set_response().set_aa().set_rcode(ns_r_nxdomain) + .add_question(new DNSQuestion("www.first.com", ns_t_a)); + EXPECT_CALL(server_, OnRequest("www.first.com", ns_t_a)) + .WillOnce(SetReply(&server_, &nofirst)); + DNSPacket nosecond; + nosecond.set_response().set_aa().set_rcode(ns_r_nxdomain) + .add_question(new DNSQuestion("www.second.org", ns_t_a)); + EXPECT_CALL(server_, OnRequest("www.second.org", ns_t_a)) + .WillOnce(SetReply(&server_, &nosecond)); + DNSPacket yesthird; + yesthird.set_response().set_aa() + .add_question(new DNSQuestion("www.third.gov", ns_t_a)) + .add_answer(new DNSARR("www.third.gov", 0x0200, {2, 3, 4, 5})); + EXPECT_CALL(server_, OnRequest("www.third.gov", ns_t_a)) + .WillOnce(SetReply(&server_, &yesthird)); + + HostResult result; + ares_gethostbyname(channel_, "www", AF_INET, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + std::stringstream ss; + ss << result.host_; + EXPECT_EQ("{'www.third.gov' aliases=[] addrs=[2.3.4.5]}", ss.str()); +} + +TEST_F(MockChannelTest, Resend) { + std::vector nothing; + DNSPacket reply; + reply.set_response().set_aa() + .add_question(new DNSQuestion("www.google.com", ns_t_a)) + .add_answer(new DNSARR("www.google.com", 0x0100, {0x01, 0x02, 0x03, 0x04})); + + EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a)) + .WillOnce(SetReplyData(&server_, nothing)) + .WillOnce(SetReplyData(&server_, nothing)) + .WillOnce(SetReply(&server_, &reply)); + + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_EQ(2, result.timeouts_); + std::stringstream ss; + ss << result.host_; + EXPECT_EQ("{'www.google.com' aliases=[] addrs=[1.2.3.4]}", ss.str()); +} + +TEST_F(MockChannelTest, CancelImmediate) { + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + ares_cancel(channel_); + EXPECT_TRUE(result.done_); + EXPECT_EQ(ARES_ECANCELLED, result.status_); + EXPECT_EQ(0, result.timeouts_); +} + +TEST_F(MockChannelTest, CancelLater) { + std::vector nothing; + + // On second request, cancel the channel. + EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a)) + .WillOnce(SetReplyData(&server_, nothing)) + .WillOnce(CancelChannel(&server_, channel_)); + + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + Process(); + EXPECT_TRUE(result.done_); + EXPECT_EQ(ARES_ECANCELLED, result.status_); + EXPECT_EQ(0, result.timeouts_); +} + +TEST_F(MockChannelTest, Destroy) { + HostResult result; + ares_gethostbyname(channel_, "www.google.com.", AF_INET, HostCallback, &result); + ares_destroy(channel_); + channel_ = nullptr; + EXPECT_TRUE(result.done_); + EXPECT_EQ(ARES_EDESTRUCTION, result.status_); + EXPECT_EQ(0, result.timeouts_); +} + +} // namespace test +} // namespace ares diff --git a/test/ares-test-parse.cc b/test/ares-test-parse.cc new file mode 100644 index 00000000..39fd4cb5 --- /dev/null +++ b/test/ares-test-parse.cc @@ -0,0 +1,640 @@ +#include "ares-test.h" +#include "dns-proto.h" + +#include +#include + +namespace ares { +namespace test { + +TEST_F(LibraryTest, ParseAReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_a)) + .add_answer(new DNSARR("example.com", 0x01020304, {0x02, 0x03, 0x04, 0x05})); + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + EXPECT_EQ(data, pkt.data()); + struct hostent *host = nullptr; + struct ares_addrttl info[5]; + int count = 5; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(1, count); + EXPECT_EQ(0x01020304, info[0].ttl); + unsigned long expected_addr = htonl(0x02030405); + EXPECT_EQ(expected_addr, info[0].ipaddr.s_addr); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'example.com' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseAReplyErrors) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_answer(new DNSARR("example.com", 0x01020304, {0x02, 0x03, 0x04, 0x05})); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + // No question + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // Question != answer + pkt.add_question(new DNSQuestion("Axample.com", ns_t_a)); + data = pkt.data(); + EXPECT_EQ(ARES_ENODATA, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.questions_.clear(); + pkt.add_question(new DNSQuestion("example.com", ns_t_a)); + +#ifdef DISABLED + // Not a response. + pkt.set_response(false); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.set_response(true); + + // Bad return code. + pkt.set_rcode(ns_r_formerr); + data = pkt.data(); + EXPECT_EQ(ARES_ENODATA, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.set_rcode(ns_r_noerror); +#endif + + // 2 questions + pkt.add_question(new DNSQuestion("example.com", ns_t_a)); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // Wrong sort of answer. + pkt.answers_.clear(); + pkt.add_answer(new DNSMxRR("example.com", 0x01020304, 100, "mx1.example.com")); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // No answer + pkt.answers_.clear(); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.add_answer(new DNSARR("example.com", 0x01020304, {0x02, 0x03, 0x04, 0x05})); + + // Truncated packets. + data = pkt.data(); + for (size_t len = 1; len < data.size(); len++) { + EXPECT_EQ(ARES_EBADRESP, ares_parse_a_reply(data.data(), len, + &host, info, &count)); + EXPECT_EQ(nullptr, host); + } +} + +TEST_F(LibraryTest, ParseAReplyAllocFail) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("Axample.com", ns_t_a)) + .add_answer(new DNSARR("example.com", 0x01020304, {0x02, 0x03, 0x04, 0x05})); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + SetAllocSizeFail(1 * sizeof(struct in_addr)); + EXPECT_EQ(ARES_ENOMEM, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + SetAllocSizeFail(2 * sizeof(char *)); + EXPECT_EQ(ARES_ENOMEM, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + +} + +TEST_F(LibraryTest, ParseAaaaReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_aaaa)) + .add_answer(new DNSAaaaRR("example.com", 0x01020304, + {0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04})); + std::vector data = pkt.data(); + struct hostent *host = nullptr; + struct ares_addr6ttl info[5]; + int count = 5; + EXPECT_EQ(ARES_SUCCESS, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(1, count); + EXPECT_EQ(0x01020304, info[0].ttl); + EXPECT_EQ(0x01, info[0].ip6addr._S6_un._S6_u8[0]); + EXPECT_EQ(0x02, info[0].ip6addr._S6_un._S6_u8[4]); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'example.com' aliases=[] addrs=[0101:0101:0202:0202:0303:0303:0404:0404]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseAaaaReplyErrors) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_answer(new DNSAaaaRR("example.com", 0x01020304, + {0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04})); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + struct ares_addr6ttl info[2]; + int count = 2; + // No question + EXPECT_EQ(ARES_EBADRESP, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // Question != answer + pkt.add_question(new DNSQuestion("Axample.com", ns_t_aaaa)); + data = pkt.data(); + EXPECT_EQ(ARES_ENODATA, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.questions_.clear(); + pkt.add_question(new DNSQuestion("example.com", ns_t_aaaa)); + + // 2 questions + pkt.add_question(new DNSQuestion("example.com", ns_t_aaaa)); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // Wrong sort of answer. + pkt.answers_.clear(); + pkt.add_answer(new DNSMxRR("example.com", 0x01020304, 100, "mx1.example.com")); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + + // No answer + pkt.answers_.clear(); + data = pkt.data(); + EXPECT_EQ(ARES_EBADRESP, ares_parse_aaaa_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(nullptr, host); + pkt.add_answer(new DNSAaaaRR("example.com", 0x01020304, + {0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04})); + + // Truncated packets. + data = pkt.data(); + for (size_t len = 1; len < data.size(); len++) { + EXPECT_EQ(ARES_EBADRESP, ares_parse_aaaa_reply(data.data(), len, + &host, info, &count)); + EXPECT_EQ(nullptr, host); + } +} + +TEST_F(LibraryTest, ParsePtrReplyOK) { + byte addrv4[4] = {0x10, 0x20, 0x30, 0x40}; + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("64.48.32.16.in-addr.arpa", ns_t_ptr)) + .add_answer(new DNSPtrRR("64.48.32.16.in-addr.arpa", 0x01020304, "other.com")); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_ptr_reply(data.data(), data.size(), + addrv4, sizeof(addrv4), AF_INET, &host)); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'other.com' aliases=[other.com] addrs=[16.32.48.64]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseNsReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_ns)) + .add_answer(new DNSNsRR("example.com", 0x01020304, "ns.example.com")); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_ns_reply(data.data(), data.size(), &host)); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'example.com' aliases=[ns.example.com] addrs=[]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseSrvReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_srv)) + .add_answer(new DNSSrvRR("example.com", 0x01020304, 10, 20, 30, "srv.example.com")) + .add_answer(new DNSSrvRR("example.com", 0x01020304, 11, 21, 31, "srv2.example.com")); + std::vector data = pkt.data(); + + struct ares_srv_reply* srv = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_srv_reply(data.data(), data.size(), &srv)); + ASSERT_NE(nullptr, srv); + + EXPECT_EQ("srv.example.com", std::string(srv->host)); + EXPECT_EQ(10, srv->priority); + EXPECT_EQ(20, srv->weight); + EXPECT_EQ(30, srv->port); + + struct ares_srv_reply* srv2 = srv->next; + ASSERT_NE(nullptr, srv2); + EXPECT_EQ("srv2.example.com", std::string(srv2->host)); + EXPECT_EQ(11, srv2->priority); + EXPECT_EQ(21, srv2->weight); + EXPECT_EQ(31, srv2->port); + EXPECT_EQ(nullptr, srv2->next); + + ares_free_data(srv); +} + +TEST_F(LibraryTest, ParseMxReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_mx)) + .add_answer(new DNSMxRR("example.com", 0x01020304, 100, "mx1.example.com")) + .add_answer(new DNSMxRR("example.com", 0x01020304, 200, "mx2.example.com")); + std::vector data = pkt.data(); + + struct ares_mx_reply* mx = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_mx_reply(data.data(), data.size(), &mx)); + ASSERT_NE(nullptr, mx); + EXPECT_EQ("mx1.example.com", std::string(mx->host)); + EXPECT_EQ(100, mx->priority); + + struct ares_mx_reply* mx2 = mx->next; + ASSERT_NE(nullptr, mx2); + EXPECT_EQ("mx2.example.com", std::string(mx2->host)); + EXPECT_EQ(200, mx2->priority); + EXPECT_EQ(nullptr, mx2->next); + + ares_free_data(mx); +} + +TEST_F(LibraryTest, ParseTxtReplyOK) { + DNSPacket pkt; + std::string expected1 = "txt1.example.com"; + std::string expected2a = "txt2a"; + std::string expected2b("ABC\0ABC", 7); + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_mx)) + .add_answer(new DNSTxtRR("example.com", 0x01020304, {expected1})) + .add_answer(new DNSTxtRR("example.com", 0x01020304, {expected2a, expected2b})); + std::vector data = pkt.data(); + + struct ares_txt_reply* txt = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_txt_reply(data.data(), data.size(), &txt)); + ASSERT_NE(nullptr, txt); + EXPECT_EQ(std::vector(expected1.data(), expected1.data() + expected1.size()), + std::vector(txt->txt, txt->txt + txt->length)); + + struct ares_txt_reply* txt2 = txt->next; + ASSERT_NE(nullptr, txt2); + EXPECT_EQ(std::vector(expected2a.data(), expected2a.data() + expected2a.size()), + std::vector(txt2->txt, txt2->txt + txt2->length)); + + struct ares_txt_reply* txt3 = txt2->next; + ASSERT_NE(nullptr, txt3); + EXPECT_EQ(std::vector(expected2b.data(), expected2b.data() + expected2b.size()), + std::vector(txt3->txt, txt3->txt + txt3->length)); + EXPECT_EQ(nullptr, txt3->next); + + ares_free_data(txt); +} + +TEST_F(LibraryTest, ParseTxtReplyErrors) { + DNSPacket pkt; + std::string expected1 = "txt1.example.com"; + std::string expected2a = "txt2a"; + std::string expected2b = "txt2b"; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_mx)) + .add_answer(new DNSTxtRR("example.com", 0x01020304, {expected1})) + .add_answer(new DNSTxtRR("example.com", 0x01020304, {expected1})) + .add_answer(new DNSTxtRR("example.com", 0x01020304, {expected2a, expected2b})); + std::vector data = pkt.data(); + struct ares_txt_reply* txt = nullptr; + + // Truncated packets. + for (size_t len = 1; len < data.size(); len++) { + txt = nullptr; + EXPECT_NE(ARES_SUCCESS, ares_parse_txt_reply(data.data(), len, &txt)); + EXPECT_EQ(nullptr, txt); + } + + // No question + pkt.questions_.clear(); + data = pkt.data(); + txt = nullptr; + EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), data.size(), &txt)); + EXPECT_EQ(nullptr, txt); + pkt.add_question(new DNSQuestion("example.com", ns_t_mx)); + + // No answer + pkt.answers_.clear(); + data = pkt.data(); + txt = nullptr; + EXPECT_EQ(ARES_ENODATA, ares_parse_txt_reply(data.data(), data.size(), &txt)); + EXPECT_EQ(nullptr, txt); + pkt.add_answer(new DNSTxtRR("example.com", 0x01020304, {expected1})); +} + +TEST_F(LibraryTest, ParseSoaReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_soa)) + .add_answer(new DNSSoaRR("example.com", 0x01020304, + "soa1.example.com", "fred.example.com", + 1, 2, 3, 4, 5)); + std::vector data = pkt.data(); + + struct ares_soa_reply* soa = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_soa_reply(data.data(), data.size(), &soa)); + ASSERT_NE(nullptr, soa); + EXPECT_EQ("soa1.example.com", std::string(soa->nsname)); + EXPECT_EQ("fred.example.com", std::string(soa->hostmaster)); + EXPECT_EQ(1, soa->serial); + EXPECT_EQ(2, soa->refresh); + EXPECT_EQ(3, soa->retry); + EXPECT_EQ(4, soa->expire); + EXPECT_EQ(5, soa->minttl); + ares_free_data(soa); +} + +TEST_F(LibraryTest, ParseNaptrReplyOK) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com", ns_t_soa)) + .add_answer(new DNSNaptrRR("example.com", 0x01020304, + 10, 20, "SP", "service", "regexp", "replace")); + std::vector data = pkt.data(); + + struct ares_naptr_reply* naptr = nullptr; + EXPECT_EQ(ARES_SUCCESS, ares_parse_naptr_reply(data.data(), data.size(), &naptr)); + ASSERT_NE(nullptr, naptr); + EXPECT_EQ("SP", std::string((char*)naptr->flags)); + EXPECT_EQ("service", std::string((char*)naptr->service)); + EXPECT_EQ("regexp", std::string((char*)naptr->regexp)); + EXPECT_EQ("replace", std::string((char*)naptr->replacement)); + EXPECT_EQ(10, naptr->order); + EXPECT_EQ(20, naptr->preference); + EXPECT_EQ(nullptr, naptr->next); + + ares_free_data(naptr); +} + +TEST_F(LibraryTest, ParseRootName) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion(".", ns_t_a)) + .add_answer(new DNSARR(".", 0x01020304, {0x02, 0x03, 0x04, 0x05})); + std::vector data = pkt.data(); + + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(1, count); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseIndirectRootName) { + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0xC0, 0x04, // weird: pointer to a random zero earlier in the message + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0xC0, 0x04, + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(1, count); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseEscapedName) { + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0x05, 'a', '\\', 'b', '.', 'c', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0x05, 'a', '\\', 'b', '.', 'c', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + EXPECT_EQ(1, count); + HostEnt hent(host); + std::stringstream ss; + ss << hent; + // The printable name is expanded with escapes. + EXPECT_EQ(11, hent.name_.size()); + EXPECT_EQ('a', hent.name_[0]); + EXPECT_EQ('\\', hent.name_[1]); + EXPECT_EQ('\\', hent.name_[2]); + EXPECT_EQ('b', hent.name_[3]); + EXPECT_EQ('\\', hent.name_[4]); + EXPECT_EQ('.', hent.name_[5]); + EXPECT_EQ('c', hent.name_[6]); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParsePartialCompressedName) { + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0x03, 'w', 'w', 'w', + 0xc0, 0x10, // offset 16 + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'www.example.com' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseFullyCompressedName) { + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0xc0, 0x0c, // offset 12 + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'www.example.com' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +TEST_F(LibraryTest, ParseFullyCompressedName2) { + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x01, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question + 0xC0, 0x12, // pointer to later in message + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Answer 1 + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + }; + struct hostent *host = nullptr; + struct ares_addrttl info[2]; + int count = 2; + EXPECT_EQ(ARES_SUCCESS, ares_parse_a_reply(data.data(), data.size(), + &host, info, &count)); + ASSERT_NE(nullptr, host); + std::stringstream ss; + ss << HostEnt(host); + EXPECT_EQ("{'www.example.com' aliases=[] addrs=[2.3.4.5]}", ss.str()); + ares_free_hostent(host); +} + +} // namespace test +} // namespace ares diff --git a/test/ares-test.cc b/test/ares-test.cc new file mode 100644 index 00000000..f3cb71ff --- /dev/null +++ b/test/ares-test.cc @@ -0,0 +1,385 @@ +#include "ares-test.h" +#include "dns-proto.h" + +// Include ares internal files for DNS protocol details +#include "nameser.h" +#include "ares_dns.h" + +#include + +#include +#include + +namespace ares { +namespace test { + +namespace { + +void ProcessWork(ares_channel channel, + int extrafd, std::function process_extra) { + int nfds, count; + fd_set readers, writers; + struct timeval tv; + struct timeval* tvp; + while (true) { + // Retrieve the set of file descriptors that the library wants us to monitor. + FD_ZERO(&readers); + FD_ZERO(&writers); + nfds = ares_fds(channel, &readers, &writers); + + // Add in the extra FD if present + if (extrafd >= 0) + FD_SET(extrafd, &readers); + + if (nfds == 0) // no work left to do + return; + + // Also retrieve the timeout value that the library wants us to use. + tvp = ares_timeout(channel, nullptr, &tv); + EXPECT_EQ(tvp, &tv); + + // Wait for activity or timeout. + count = select(nfds, &readers, &writers, nullptr, tvp); + if (count < 0) { + fprintf(stderr, "select() failed, errno %d\n", errno); + return; + } + + // Let the library process any activity. + ares_process(channel, &readers, &writers); + // Let the provided callback process any activity on the extra FD. + if (extrafd > 0 && FD_ISSET(extrafd, &readers)) + process_extra(extrafd); + } +} + +} // namespace + +bool verbose = false; + +unsigned long LibraryTest::fails_ = 0; +std::map LibraryTest::size_fails_; + +// static +void LibraryTest::SetAllocFail(int nth) { + assert(nth > 0); + fails_ |= (1 << (nth - 1)); +} + +// static +void LibraryTest::SetAllocSizeFail(size_t size) { + size_fails_[size]++; +} + +// static +void LibraryTest::ClearFails() { + fails_ = 0; + size_fails_.clear(); +} + + +// static +bool LibraryTest::ShouldAllocFail(size_t size) { + bool fail = (fails_ & 0x01); + fails_ >>= 1; + if (size_fails_[size] > 0) { + size_fails_[size]--; + fail = true; + } + return fail; +} + +// static +void* LibraryTest::amalloc(size_t size) { + if (ShouldAllocFail(size)) { + if (verbose) std::cerr << "Failing malloc(" << size << ") request" << std::endl; + return nullptr; + } else { + return malloc(size); + } +} + +// static +void* LibraryTest::arealloc(void *ptr, size_t size) { + if (ShouldAllocFail(size)) { + if (verbose) std::cerr << "Failing realloc(" << ptr << ", " << size << ") request" << std::endl; + return nullptr; + } else { + return realloc(ptr, size); + } +} + +// static +void LibraryTest::afree(void *ptr) { + free(ptr); +} + +void DefaultChannelTest::Process() { + ProcessWork(channel_, -1, nullptr); +} + +MockServer::MockServer(int port) : port_(port) { + // Create a UDP socket to receive data on. + sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); + EXPECT_NE(-1, sockfd_); + + // Bind it to the given port. + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port_); + int rc = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr)); + EXPECT_EQ(0, rc) << "Failed to bind to port " << port_; +} + +MockServer::~MockServer() { + close(sockfd_); + sockfd_ = -1; +} + +void MockServer::Process(int fd) { + if (fd != sockfd_) return; + struct sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + byte buffer[2048]; + int len = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr *)&addr, &addrlen); + + // Assume the packet is a well-formed DNS request and extract the request + // details. + if (len < NS_HFIXEDSZ) { + std::cerr << "Packet too short (" << len << ")" << std::endl; + return; + } + int qid = DNS_HEADER_QID(buffer); + if (DNS_HEADER_QR(buffer) != 0) { + std::cerr << "Not a request" << std::endl; + return; + } + if (DNS_HEADER_OPCODE(buffer) != ns_o_query) { + std::cerr << "Not a query (opcode " << DNS_HEADER_OPCODE(buffer) + << ")" << std::endl; + return; + } + if (DNS_HEADER_QDCOUNT(buffer) != 1) { + std::cerr << "Unexpected question count (" << DNS_HEADER_QDCOUNT(buffer) + << ")" << std::endl; + return; + } + byte* question = buffer + 12; + int qlen = len - 12; + + char *name = nullptr; + long enclen; + ares_expand_name(question, buffer, len, &name, &enclen); + qlen -= enclen; + question += enclen; + std::string namestr(name); + free(name); + + if (qlen < 4) { + std::cerr << "Unexpected question size (" << qlen + << " bytes after name)" << std::endl; + return; + } + if (DNS_QUESTION_CLASS(question) != ns_c_in) { + std::cerr << "Unexpected question class (" << DNS_QUESTION_CLASS(question) + << ")" << std::endl; + return; + } + int rrtype = DNS_QUESTION_TYPE(question); + + if (verbose) std::cerr << "ProcessRequest(" << qid << ", '" << namestr + << "', " << RRTypeToString(rrtype) << ")" << std::endl; + ProcessRequest(&addr, addrlen, qid, namestr, rrtype); +} + +void MockServer::ProcessRequest(struct sockaddr_storage* addr, int addrlen, + int qid, const std::string& name, int rrtype) { + // Before processing, let gMock know the request is happening. + OnRequest(name, rrtype); + + // Send the current pending reply. First overwrite the qid with the value + // from the argument. + if (reply_.size() < 2) { + if (verbose) std::cerr << "Skipping reply as not-present/too-short" << std::endl; + return; + } + reply_[0] = (byte)((qid >> 8) & 0xff); + reply_[1] = (byte)(qid & 0xff); + if (verbose) std::cerr << "sending reply " << PacketToString(reply_) << std::endl; + int rc = sendto(sockfd_, reply_.data(), reply_.size(), 0, + (struct sockaddr *)addr, addrlen); + if (rc < static_cast(reply_.size())) { + std::cerr << "Failed to send full reply, rc=" << rc << std::endl; + } +} + +MockChannelOptsTest::MockChannelOptsTest(struct ares_options* givenopts, + int optmask) + : server_(5300), channel_(nullptr) { + // Set up channel options. + struct ares_options opts; + if (givenopts) { + memcpy(&opts, givenopts, sizeof(opts)); + } else { + EXPECT_EQ(0, optmask); + memset(&opts, 0, sizeof(opts)); + } + + // Force communication with the mock server. + opts.udp_port = server_.port(); + optmask |= ARES_OPT_UDP_PORT; + opts.tcp_port = server_.port(); + optmask |= ARES_OPT_TCP_PORT; + opts.nservers = 1; + struct in_addr server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.s_addr = htonl(0x7F000001); + opts.servers = &server_addr; + optmask |= ARES_OPT_SERVERS; + + // If not already overridden, set short timeouts. + if (!(optmask & (ARES_OPT_TIMEOUTMS|ARES_OPT_TIMEOUT))) { + opts.timeout = 100; + optmask |= ARES_OPT_TIMEOUTMS; + } + // If not already overridden, set 3 retries. + if (!(optmask & ARES_OPT_TRIES)) { + opts.tries = 3; + optmask |= ARES_OPT_TRIES; + } + // If not already overridden, set search domains. + const char *domains[3] = {"first.com", "second.org", "third.gov"}; + if (!(optmask & ARES_OPT_DOMAINS)) { + opts.ndomains = 3; + opts.domains = (char**)domains; + optmask |= ARES_OPT_DOMAINS; + } + + EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel_, &opts, optmask)); + EXPECT_NE(nullptr, channel_); +} + +MockChannelOptsTest::~MockChannelOptsTest() { + if (channel_) { + ares_destroy(channel_); + } + channel_ = nullptr; +} + +void MockChannelOptsTest::Process() { + using namespace std::placeholders; + ProcessWork(channel_, server_.sockfd(), + std::bind(&MockServer::Process, &server_, _1)); +} + +std::ostream& operator<<(std::ostream& os, const HostResult& result) { + os << '{'; + if (result.done_) { + os << StatusToString(result.status_) << " " << result.host_; + } else { + os << "(incomplete)"; + } + os << '}'; + return os; +} + +HostEnt::HostEnt(const struct hostent *hostent) { + if (!hostent) return; + if (hostent->h_name) name_ = hostent->h_name; + if (hostent->h_aliases) { + char** palias = hostent->h_aliases; + while (*palias != nullptr) { + aliases_.push_back(*palias); + palias++; + } + } + addrtype_ = hostent->h_addrtype; + if (hostent->h_addr_list) { + char** paddr = hostent->h_addr_list; + while (*paddr != nullptr) { + std::string addr = AddressToString(*paddr, hostent->h_length); + addrs_.push_back(addr); + paddr++; + } + } +} + +std::ostream& operator<<(std::ostream& os, const HostEnt& host) { + os << '{'; + os << "'" << host.name_ << "' " + << "aliases=["; + for (size_t ii = 0; ii < host.aliases_.size(); ii++) { + if (ii > 0) os << ", "; + os << host.aliases_[ii]; + } + os << "] "; + os << "addrs=["; + for (size_t ii = 0; ii < host.addrs_.size(); ii++) { + if (ii > 0) os << ", "; + os << host.addrs_[ii]; + } + os << "]"; + os << '}'; + return os; +} +void HostCallback(void *data, int status, int timeouts, + struct hostent *hostent) { + EXPECT_NE(nullptr, data); + HostResult* result = reinterpret_cast(data); + result->done_ = true; + result->status_ = status; + result->timeouts_ = timeouts; + result->host_ = HostEnt(hostent); + if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl; +} + +std::ostream& operator<<(std::ostream& os, const SearchResult& result) { + os << '{'; + if (result.done_) { + os << StatusToString(result.status_) << " " << PacketToString(result.data_); + } else { + os << "(incomplete)"; + } + os << '}'; + return os; +} + +void SearchCallback(void *data, int status, int timeouts, + unsigned char *abuf, int alen) { + EXPECT_NE(nullptr, data); + SearchResult* result = reinterpret_cast(data); + result->done_ = true; + result->status_ = status; + result->timeouts_ = timeouts; + result->data_.assign(abuf, abuf + alen); + if (verbose) std::cerr << "SearchCallback(" << *result << ")" << std::endl; +} + +std::ostream& operator<<(std::ostream& os, const NameInfoResult& result) { + os << '{'; + if (result.done_) { + os << StatusToString(result.status_) << " " << result.node_ << " " << result.service_; + } else { + os << "(incomplete)"; + } + os << '}'; + return os; +} + +void NameInfoCallback(void *data, int status, int timeouts, + char *node, char *service) { + EXPECT_NE(nullptr, data); + NameInfoResult* result = reinterpret_cast(data); + result->done_ = true; + result->status_ = status; + result->timeouts_ = timeouts; + result->node_ = std::string(node ? node : ""); + result->service_ = std::string(service ? service : ""); + if (verbose) std::cerr << "NameInfoCallback(" << *result << ")" << std::endl; +} + +} // namespace test +} // namespace ares diff --git a/test/ares-test.h b/test/ares-test.h new file mode 100644 index 00000000..7838a77f --- /dev/null +++ b/test/ares-test.h @@ -0,0 +1,197 @@ +// -*- mode: c++ -*- +#ifndef ARES_TEST_H +#define ARES_TEST_H + +#include "ares.h" + +#include "dns-proto.h" + +// Include ares internal file for DNS protocol constants +#include "nameser.h" + +#include "gtest/gtest.h" +#include "gmock/gmock.h" + +#include + +namespace ares { + +typedef unsigned char byte; + +namespace test { + +extern bool verbose; + +// Test fixture that ensures library initialization, and allows +// memory allocations to be failed. +class LibraryTest : public ::testing::Test { + public: + LibraryTest() { + EXPECT_EQ(ARES_SUCCESS, + ares_library_init_mem(ARES_LIB_INIT_ALL, + &LibraryTest::amalloc, + &LibraryTest::afree, + &LibraryTest::arealloc)); + } + ~LibraryTest() { + ares_library_cleanup(); + ClearFails(); + } + // Set the n-th malloc call (of any size) from the library to fail. + // (nth == 1 means the next call) + static void SetAllocFail(int nth); + // Set the next malloc call for the given size to fail. + static void SetAllocSizeFail(size_t size); + // Remove any pending alloc failures. + static void ClearFails(); + + static void *amalloc(size_t size); + static void* arealloc(void *ptr, size_t size); + static void afree(void *ptr); + private: + static bool ShouldAllocFail(size_t size); + static unsigned long fails_; + static std::map size_fails_; +}; + +// Test fixture that uses a default channel. +class DefaultChannelTest : public LibraryTest { + public: + DefaultChannelTest() : channel_(nullptr) { + EXPECT_EQ(ARES_SUCCESS, ares_init(&channel_)); + EXPECT_NE(nullptr, channel_); + } + + ~DefaultChannelTest() { + ares_destroy(channel_); + channel_ = nullptr; + } + + // Process all pending work on ares-owned file descriptors. + void Process(); + + protected: + ares_channel channel_; +}; + +// Mock DNS server to allow responses to be scripted by tests. +class MockServer { + public: + MockServer(int port); + ~MockServer(); + + // Mock method indicating the processing of a particular + // request. + MOCK_METHOD2(OnRequest, void(const std::string& name, int rrtype)); + + // Set the reply to be sent next; the query ID field will be overwritten + // with the value from the request. + void SetReplyData(const std::vector& reply) { reply_ = reply; } + void SetReply(const DNSPacket* reply) { SetReplyData(reply->data()); } + + // Process activity on the mock server's socket FD. + void Process(int fd); + + int port() const { return port_; } + int sockfd() const { return sockfd_; } + + private: + void ProcessRequest(struct sockaddr_storage* addr, int addrlen, + int qid, const std::string& name, int rrtype); + + int port_; + int sockfd_; + std::vector reply_; +}; + +// Test fixture that uses a mock DNS server. +class MockChannelOptsTest : public LibraryTest { + public: + MockChannelOptsTest(struct ares_options* givenopts, int optmask); + ~MockChannelOptsTest(); + + // Process all pending work on ares-owned and mock-server-owned file descriptors. + void Process(); + + protected: + testing::NiceMock server_; + ares_channel channel_; +}; + +class MockChannelTest : public MockChannelOptsTest { + public: + MockChannelTest() : MockChannelOptsTest(nullptr, 0) {} +}; + +// gMock action to set the reply for a mock server. +ACTION_P2(SetReplyData, mockserver, data) { + mockserver->SetReplyData(data); +} +ACTION_P2(SetReply, mockserver, reply) { + mockserver->SetReply(reply); +} +// gMock action to cancel a channel. +ACTION_P2(CancelChannel, mockserver, channel) { + ares_cancel(channel); +} + +// C++ wrapper for struct hostent. +struct HostEnt { + HostEnt() : addrtype_(-1) {} + HostEnt(const struct hostent* hostent); + std::string name_; + std::vector aliases_; + int addrtype_; // AF_INET or AF_INET6 + std::vector addrs_; +}; +std::ostream& operator<<(std::ostream& os, const HostEnt& result); + +// Structure that describes the result of an ares_host_callback invocation. +struct HostResult { + // Whether the callback has been invoked. + bool done_; + // Explicitly provided result information. + int status_; + int timeouts_; + // Contents of the hostent structure, if provided. + HostEnt host_; +}; +std::ostream& operator<<(std::ostream& os, const HostResult& result); + +// Structure that describes the result of an ares_callback invocation. +struct SearchResult { + // Whether the callback has been invoked. + bool done_; + // Explicitly provided result information. + int status_; + int timeouts_; + std::vector data_; +}; +std::ostream& operator<<(std::ostream& os, const SearchResult& result); + +// Structure that describes the result of an ares_nameinfo_callback invocation. +struct NameInfoResult { + // Whether the callback has been invoked. + bool done_; + // Explicitly provided result information. + int status_; + int timeouts_; + std::string node_; + std::string service_; +}; +std::ostream& operator<<(std::ostream& os, const NameInfoResult& result); + +// Standard implementation of ares callbacks that fill out the corresponding +// structures. +void HostCallback(void *data, int status, int timeouts, + struct hostent *hostent); +void SearchCallback(void *data, int status, int timeouts, + unsigned char *abuf, int alen); +void NameInfoCallback(void *data, int status, int timeouts, + char *node, char *service); + + +} // namespace test +} // namespace ares + +#endif diff --git a/test/buildconf b/test/buildconf new file mode 100755 index 00000000..5a9d7a34 --- /dev/null +++ b/test/buildconf @@ -0,0 +1,2 @@ +#!/bin/sh +autoreconf -iv \ No newline at end of file diff --git a/test/configure.ac b/test/configure.ac new file mode 100644 index 00000000..9a891577 --- /dev/null +++ b/test/configure.ac @@ -0,0 +1,17 @@ +AC_PREREQ(2.57) +AC_INIT([c-ares-test],[-],[-]) +AC_CONFIG_SRCDIR([ares-test.cc]) +AC_CONFIG_MACRO_DIR([../m4]) + +AM_INIT_AUTOMAKE() + +dnl Checks for programs. +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX_11([noext],[mandatory]) +LT_INIT +AC_SUBST(LIBTOOL_DEPS) +AX_PTHREAD +AX_CODE_COVERAGE + +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/test/dns-proto-test.cc b/test/dns-proto-test.cc new file mode 100644 index 00000000..0c36a0c9 --- /dev/null +++ b/test/dns-proto-test.cc @@ -0,0 +1,131 @@ +#include "ares-test.h" +#include "dns-proto.h" + +#include + +namespace ares { +namespace test { + +TEST(DNSProto, EncodeQuestions) { + DNSPacket pkt; + pkt.set_qid(0x1234).set_response().set_aa() + .add_question(new DNSQuestion("example.com.", ns_t_a)) + .add_question(new DNSQuestion("www.example.com", ns_t_aaaa, ns_c_chaos)); + + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x02, // num questions + 0x00, 0x00, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x00, // num additional RRs + // Question 1 + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // type A + 0x00, 0x01, // class IN + // Question 2 + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x1C, // type AAAA = 28 + 0x00, 0x03, // class CHAOS = 3 + }; + EXPECT_EQ(data, pkt.data()); +} + +TEST(DNSProto, EncodeSingleNameAnswers) { + DNSPacket pkt; + pkt.qid_ = 0x1234; + pkt.response_ = true; + pkt.aa_ = true; + pkt.opcode_ = ns_o_query; + pkt.add_answer(new DNSCnameRR("example.com", 0x01020304, "other.com.")); + pkt.add_auth(new DNSPtrRR("www.example.com", 0x01020304, "www.other.com")); + + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x00, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x01, // num authority RRs + 0x00, 0x00, // num additional RRs + // Answer 1 + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x05, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x0B, // rdata length + 0x05, 'o', 't', 'h', 'e', 'r', + 0x03, 'c', 'o', 'm', + 0x00, + // Authority 1 + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x0c, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x0F, // rdata length + 0x03, 'w', 'w', 'w', + 0x05, 'o', 't', 'h', 'e', 'r', + 0x03, 'c', 'o', 'm', + 0x00, + }; + EXPECT_EQ(data, pkt.data()); +} + +TEST(DNSProto, EncodeAddressAnswers) { + DNSPacket pkt; + pkt.qid_ = 0x1234; + pkt.response_ = true; + pkt.aa_ = true; + pkt.opcode_ = ns_o_query; + std::vector addrv4 = {0x02, 0x03, 0x04, 0x05}; + pkt.add_answer(new DNSARR("example.com", 0x01020304, addrv4)); + byte addrv6[16] = {0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04}; + pkt.add_additional(new DNSAaaaRR("www.example.com", 0x01020304, addrv6, 16)); + + std::vector data = { + 0x12, 0x34, // qid + 0x84, // response + query + AA + not-TC + not-RD + 0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError + 0x00, 0x00, // num questions + 0x00, 0x01, // num answer RRs + 0x00, 0x00, // num authority RRs + 0x00, 0x01, // num additional RRs + // Answer 1 + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x01, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x04, // rdata length + 0x02, 0x03, 0x04, 0x05, + // Additional 1 + 0x03, 'w', 'w', 'w', + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', + 0x03, 'c', 'o', 'm', + 0x00, + 0x00, 0x1c, // RR type + 0x00, 0x01, // class IN + 0x01, 0x02, 0x03, 0x04, // TTL + 0x00, 0x10, // rdata length + 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04 + }; + EXPECT_EQ(data, pkt.data()); +} + + +} // namespace test +} // namespace ares diff --git a/test/dns-proto.cc b/test/dns-proto.cc new file mode 100644 index 00000000..89582e15 --- /dev/null +++ b/test/dns-proto.cc @@ -0,0 +1,577 @@ +#include "dns-proto.h" + +// Include ares internal file for DNS protocol details +#include "ares.h" +#include "ares_dns.h" + +#include + +namespace ares { + +std::string HexDump(std::vector data) { + std::stringstream ss; + for (size_t ii = 0; ii < data.size(); ii++) { + char buffer[2 + 1]; + sprintf(buffer, "%02x", data[ii]); + ss << buffer; + } + return ss.str(); +} + +std::string HexDump(const byte *data, int len) { + return HexDump(std::vector(data, data + len)); +} + +std::string HexDump(const char *data, int len) { + return HexDump(reinterpret_cast(data), len); +} + +std::string StatusToString(int status) { + switch (status) { + case ARES_SUCCESS: return "ARES_SUCCESS"; + case ARES_ENODATA: return "ARES_ENODATA"; + case ARES_EFORMERR: return "ARES_EFORMERR"; + case ARES_ESERVFAIL: return "ARES_ESERVFAIL"; + case ARES_ENOTFOUND: return "ARES_ENOTFOUND"; + case ARES_ENOTIMP: return "ARES_ENOTIMP"; + case ARES_EREFUSED: return "ARES_EREFUSED"; + case ARES_EBADQUERY: return "ARES_EBADQUERY"; + case ARES_EBADNAME: return "ARES_EBADNAME"; + case ARES_EBADFAMILY: return "ARES_EBADFAMILY"; + case ARES_EBADRESP: return "ARES_EBADRESP"; + case ARES_ECONNREFUSED: return "ARES_ECONNREFUSED"; + case ARES_ETIMEOUT: return "ARES_ETIMEOUT"; + case ARES_EOF: return "ARES_EOF"; + case ARES_EFILE: return "ARES_EFILE"; + case ARES_ENOMEM: return "ARES_ENOMEM"; + case ARES_EDESTRUCTION: return "ARES_EDESTRUCTION"; + case ARES_EBADSTR: return "ARES_EBADSTR"; + case ARES_EBADFLAGS: return "ARES_EBADFLAGS"; + case ARES_ENONAME: return "ARES_ENONAME"; + case ARES_EBADHINTS: return "ARES_EBADHINTS"; + case ARES_ENOTINITIALIZED: return "ARES_ENOTINITIALIZED"; + case ARES_ELOADIPHLPAPI: return "ARES_ELOADIPHLPAPI"; + case ARES_EADDRGETNETWORKPARAMS: return "ARES_EADDRGETNETWORKPARAMS"; + case ARES_ECANCELLED: return "ARES_ECANCELLED"; + default: return "UNKNOWN"; + } +} + +std::string RcodeToString(int rcode) { + switch (rcode) { + case ns_r_noerror: return "NOERROR"; + case ns_r_formerr: return "FORMERR"; + case ns_r_servfail: return "SERVFAIL"; + case ns_r_nxdomain: return "NXDOMAIN"; + case ns_r_notimpl: return "NOTIMPL"; + case ns_r_refused: return "REFUSED"; + case ns_r_yxdomain: return "YXDOMAIN"; + case ns_r_yxrrset: return "YXRRSET"; + case ns_r_nxrrset: return "NXRRSET"; + case ns_r_notauth: return "NOTAUTH"; + case ns_r_notzone: return "NOTZONE"; + case ns_r_badsig: return "BADSIG"; + case ns_r_badkey: return "BADKEY"; + case ns_r_badtime: return "BADTIME"; + default: return "UNKNOWN"; + } +} + +std::string RRTypeToString(int rrtype) { + switch (rrtype) { + case ns_t_a: return "A"; + case ns_t_ns: return "NS"; + case ns_t_md: return "MD"; + case ns_t_mf: return "MF"; + case ns_t_cname: return "CNAME"; + case ns_t_soa: return "SOA"; + case ns_t_mb: return "MB"; + case ns_t_mg: return "MG"; + case ns_t_mr: return "MR"; + case ns_t_null: return "NULL"; + case ns_t_wks: return "WKS"; + case ns_t_ptr: return "PTR"; + case ns_t_hinfo: return "HINFO"; + case ns_t_minfo: return "MINFO"; + case ns_t_mx: return "MX"; + case ns_t_txt: return "TXT"; + case ns_t_rp: return "RP"; + case ns_t_afsdb: return "AFSDB"; + case ns_t_x25: return "X25"; + case ns_t_isdn: return "ISDN"; + case ns_t_rt: return "RT"; + case ns_t_nsap: return "NSAP"; + case ns_t_nsap_ptr: return "NSAP_PTR"; + case ns_t_sig: return "SIG"; + case ns_t_key: return "KEY"; + case ns_t_px: return "PX"; + case ns_t_gpos: return "GPOS"; + case ns_t_aaaa: return "AAAA"; + case ns_t_loc: return "LOC"; + case ns_t_nxt: return "NXT"; + case ns_t_eid: return "EID"; + case ns_t_nimloc: return "NIMLOC"; + case ns_t_srv: return "SRV"; + case ns_t_atma: return "ATMA"; + case ns_t_naptr: return "NAPTR"; + case ns_t_kx: return "KX"; + case ns_t_cert: return "CERT"; + case ns_t_a6: return "A6"; + case ns_t_dname: return "DNAME"; + case ns_t_sink: return "SINK"; + case ns_t_opt: return "OPT"; + case ns_t_apl: return "APL"; + case ns_t_ds: return "DS"; + case ns_t_sshfp: return "SSHFP"; + case ns_t_rrsig: return "RRSIG"; + case ns_t_nsec: return "NSEC"; + case ns_t_dnskey: return "DNSKEY"; + case ns_t_tkey: return "TKEY"; + case ns_t_tsig: return "TSIG"; + case ns_t_ixfr: return "IXFR"; + case ns_t_axfr: return "AXFR"; + case ns_t_mailb: return "MAILB"; + case ns_t_maila: return "MAILA"; + case ns_t_any: return "ANY"; + case ns_t_zxfr: return "ZXFR"; + case ns_t_max: return "MAX"; + default: return "UNKNOWN"; + } +} + +std::string ClassToString(int qclass) { + switch (qclass) { + case ns_c_in: return "IN"; + case ns_c_chaos: return "CHAOS"; + case ns_c_hs: return "HESIOD"; + case ns_c_none: return "NONE"; + case ns_c_any: return "ANY"; + default: return "UNKNOWN"; + } +} + +std::string AddressToString(const byte* addr, int len) { + std::stringstream ss; + if (len == 4) { + char buffer[4*4 + 3 + 1]; + sprintf(buffer, "%u.%u.%u.%u", + (unsigned char)addr[0], + (unsigned char)addr[1], + (unsigned char)addr[2], + (unsigned char)addr[3]); + ss << buffer; + } else if (len == 16) { + for (int ii = 0; ii < 16; ii+=2) { + if (ii > 0) ss << ':'; + char buffer[4 + 1]; + sprintf(buffer, "%02x%02x", (unsigned char)addr[ii], (unsigned char)addr[ii+1]); + ss << buffer; + } + } else { + ss << "!" << HexDump(addr, len) << "!"; + } + return ss.str(); +} + +std::string AddressToString(const char* addr, int len) { + return AddressToString(reinterpret_cast(addr), len); +} + +std::string PacketToString(const std::vector& packet) { + const byte* data = packet.data(); + int len = packet.size(); + std::stringstream ss; + if (len < NS_HFIXEDSZ) { + ss << "(too short, len " << len << ")"; + return ss.str(); + } + ss << ((DNS_HEADER_QR(data) == 0) ? "REQ " : "RSP "); + switch (DNS_HEADER_OPCODE(data)) { + case ns_o_query: ss << "QRY "; break; + case ns_o_iquery: ss << "IQRY "; break; + case ns_o_status: ss << "STATUS "; break; + case ns_o_notify: ss << "NOTIFY "; break; + case ns_o_update: ss << "UPDATE "; break; + default: ss << "UNKNOWN(" << DNS_HEADER_OPCODE(data) << ") "; break; + } + if (DNS_HEADER_AA(data)) ss << "AA "; + if (DNS_HEADER_TC(data)) ss << "TC "; + if (DNS_HEADER_RD(data)) ss << "RD "; + if (DNS_HEADER_RA(data)) ss << "RA "; + if (DNS_HEADER_Z(data)) ss << "Z "; + if (DNS_HEADER_QR(data) == 1) ss << RcodeToString(DNS_HEADER_RCODE(data)); + + int nquestions = DNS_HEADER_QDCOUNT(data); + int nanswers = DNS_HEADER_ANCOUNT(data); + int nauths = DNS_HEADER_NSCOUNT(data); + int nadds = DNS_HEADER_ARCOUNT(data); + + const byte* pq = data + NS_HFIXEDSZ; + len -= NS_HFIXEDSZ; + for (int ii = 0; ii < nquestions; ii++) { + ss << " Q:" << QuestionToString(packet, &pq, &len); + } + const byte* prr = pq; + for (int ii = 0; ii < nanswers; ii++) { + ss << " A:" << RRToString(packet, &prr, &len); + } + for (int ii = 0; ii < nauths; ii++) { + ss << " AUTH:" << RRToString(packet, &prr, &len); + } + for (int ii = 0; ii < nadds; ii++) { + ss << " ADD:" << RRToString(packet, &prr, &len); + } + return ss.str(); +} + +std::string QuestionToString(const std::vector& packet, + const byte** data, int* len) { + std::stringstream ss; + ss << "{"; + if (*len < NS_QFIXEDSZ) { + ss << "(too short, len " << *len << ")"; + return ss.str(); + } + + char *name = nullptr; + long enclen; + ares_expand_name(*data, packet.data(), packet.size(), &name, &enclen); + *len -= enclen; + *data += enclen; + ss << "'" << name << "' "; + free(name); + if (*len < NS_QFIXEDSZ) { + ss << "(too short, len left " << *len << ")"; + return ss.str(); + } + ss << ClassToString(DNS_QUESTION_CLASS(*data)) << " "; + ss << RRTypeToString(DNS_QUESTION_TYPE(*data)); + *data += NS_QFIXEDSZ; + *len -= NS_QFIXEDSZ; + ss << "}"; + return ss.str(); +} + +std::string RRToString(const std::vector& packet, + const byte** data, int* len) { + std::stringstream ss; + ss << "{"; + if (*len < NS_RRFIXEDSZ) { + ss << "too short, len " << *len << ")"; + return ss.str(); + } + + char *name = nullptr; + long enclen; + ares_expand_name(*data, packet.data(), packet.size(), &name, &enclen); + *len -= enclen; + *data += enclen; + ss << "'" << name << "' "; + free(name); + name = nullptr; + + if (*len < NS_QFIXEDSZ) { + ss << "(too short, len left " << *len << ")"; + return ss.str(); + } + int rrtype = DNS_RR_TYPE(*data); + if (rrtype == ns_t_opt) { + ss << "MAXUDP=" << DNS_RR_CLASS(*data) << " "; + ss << RRTypeToString(rrtype) << " "; + ss << "RCODE2=" << DNS_RR_TTL(*data); + } else { + ss << ClassToString(DNS_RR_CLASS(*data)) << " "; + ss << RRTypeToString(rrtype) << " "; + ss << "TTL=" << DNS_RR_TTL(*data); + } + int rdatalen = DNS_RR_LEN(*data); + + *data += NS_RRFIXEDSZ; + *len -= NS_RRFIXEDSZ; + switch (rrtype) { + case ns_t_a: + case ns_t_aaaa: + ss << " " << AddressToString(*data, rdatalen); + break; + case ns_t_txt: { + const byte* p = *data; + while (p < (*data + rdatalen)) { + int len = *p++; + std::string txt(p, p + len); + ss << " " << len << ":'" << txt << "'"; + p += len; + } + break; + } + case ns_t_cname: + case ns_t_ns: + case ns_t_ptr: + ares_expand_name(*data, packet.data(), packet.size(), &name, &enclen); + ss << " '" << name << "'"; + free(name); + break; + case ns_t_mx: + ares_expand_name(*data + 2, packet.data(), packet.size(), &name, &enclen); + ss << " " << DNS__16BIT(*data) << " '" << name << "'"; + free(name); + break; + case ns_t_srv: { + const byte* p = *data; + unsigned long prio = DNS__16BIT(p); + unsigned long weight = DNS__16BIT(p + 2); + unsigned long port = DNS__16BIT(p + 4); + p += 6; + ares_expand_name(p, packet.data(), packet.size(), &name, &enclen); + ss << prio << " " << weight << " " << port << " '" << name << "'"; + free(name); + break; + } + case ns_t_soa: { + const byte* p = *data; + ares_expand_name(p, packet.data(), packet.size(), &name, &enclen); + ss << " '" << name << "'"; + free(name); + p += enclen; + ares_expand_name(p, packet.data(), packet.size(), &name, &enclen); + ss << " '" << name << "'"; + free(name); + p += enclen; + unsigned long serial = DNS__32BIT(p); + unsigned long refresh = DNS__32BIT(p + 4); + unsigned long retry = DNS__32BIT(p + 8); + unsigned long expire = DNS__32BIT(p + 12); + unsigned long minimum = DNS__32BIT(p + 16); + ss << " " << serial << " " << refresh << " " << retry << " " << expire << " " << minimum; + break; + } + case ns_t_naptr: { + const byte* p = *data; + unsigned long order = DNS__16BIT(p); + unsigned long pref = DNS__16BIT(p + 2); + p += 4; + ss << order << " " << pref; + + int len = *p++; + std::string flags(p, p + len); + ss << " " << flags; + p += len; + + len = *p++; + std::string service(p, p + len); + ss << " '" << service << "'"; + p += len; + + len = *p++; + std::string regexp(p, p + len); + ss << " '" << regexp << "'"; + p += len; + + ares_expand_name(p, packet.data(), packet.size(), &name, &enclen); + ss << " '" << name << "'"; + free(name); + p += enclen; + break; + } + default: + ss << " " << HexDump(*data, rdatalen); + break; + } + *data += rdatalen; + *len -= rdatalen; + + ss << "}"; + return ss.str(); +} + +namespace { + +void PushInt32(std::vector* data, int value) { + data->push_back((value & 0xff000000) >> 24); + data->push_back((value & 0x00ff0000) >> 16); + data->push_back((value & 0x0000ff00) >> 8); + data->push_back(value & 0x000000ff); +} + +void PushInt16(std::vector* data, int value) { + data->push_back((value & 0xff00) >> 8); + data->push_back(value & 0x00ff); +} + +std::vector EncodeString(const std::string& name) { + std::vector data; + std::stringstream ss(name); + std::string label; + // TODO: cope with escapes + while (std::getline(ss, label, '.')) { + data.push_back(label.length()); + data.insert(data.end(), label.begin(), label.end()); + } + data.push_back(0); + return data; +} + +} // namespace + +std::vector DNSQuestion::data() const { + std::vector data; + std::vector encname = EncodeString(name_); + data.insert(data.end(), encname.begin(), encname.end()); + PushInt16(&data, rrtype_); + PushInt16(&data, qclass_); + return data; +} + +std::vector DNSRR::data() const { + std::vector data = DNSQuestion::data(); + PushInt32(&data, ttl_); + return data; +} + +std::vector DNSSingleNameRR::data() const { + std::vector data = DNSRR::data(); + std::vector encname = EncodeString(other_); + int len = encname.size(); + PushInt16(&data, len); + data.insert(data.end(), encname.begin(), encname.end()); + return data; +} + +std::vector DNSTxtRR::data() const { + std::vector data = DNSRR::data(); + int len = 0; + for (const std::string& txt : txt_) { + len += (1 + txt.size()); + } + PushInt16(&data, len); + for (const std::string& txt : txt_) { + data.push_back(txt.size()); + data.insert(data.end(), txt.begin(), txt.end()); + } + return data; +} + +std::vector DNSMxRR::data() const { + std::vector data = DNSRR::data(); + std::vector encname = EncodeString(other_); + int len = 2 + encname.size(); + PushInt16(&data, len); + PushInt16(&data, pref_); + data.insert(data.end(), encname.begin(), encname.end()); + return data; +} + +std::vector DNSSrvRR::data() const { + std::vector data = DNSRR::data(); + std::vector encname = EncodeString(target_); + int len = 6 + encname.size(); + PushInt16(&data, len); + PushInt16(&data, prio_); + PushInt16(&data, weight_); + PushInt16(&data, port_); + data.insert(data.end(), encname.begin(), encname.end()); + return data; +} + +std::vector DNSAddressRR::data() const { + std::vector data = DNSRR::data(); + int len = addr_.size(); + PushInt16(&data, len); + data.insert(data.end(), addr_.begin(), addr_.end()); + return data; +} + +std::vector DNSSoaRR::data() const { + std::vector data = DNSRR::data(); + std::vector encname1 = EncodeString(nsname_); + std::vector encname2 = EncodeString(rname_); + int len = encname1.size() + encname2.size() + 5*4; + PushInt16(&data, len); + data.insert(data.end(), encname1.begin(), encname1.end()); + data.insert(data.end(), encname2.begin(), encname2.end()); + PushInt32(&data, serial_); + PushInt32(&data, refresh_); + PushInt32(&data, retry_); + PushInt32(&data, expire_); + PushInt32(&data, minimum_); + return data; +} + +std::vector DNSOptRR::data() const { + std::vector data = DNSRR::data(); + int len = 0; + for (const DNSOption& opt : opts_) { + len += (4 + opt.data_.size()); + } + PushInt16(&data, len); + for (const DNSOption& opt : opts_) { + PushInt16(&data, opt.code_); + PushInt16(&data, opt.data_.size()); + data.insert(data.end(), opt.data_.begin(), opt.data_.end()); + } + return data; +} + +std::vector DNSNaptrRR::data() const { + std::vector data = DNSRR::data(); + std::vector encname = EncodeString(replacement_); + int len = (4 + 1 + flags_.size() + 1 + service_.size() + 1 + regexp_.size() + encname.size()); + PushInt16(&data, len); + PushInt16(&data, order_); + PushInt16(&data, pref_); + data.push_back(flags_.size()); + data.insert(data.end(), flags_.begin(), flags_.end()); + data.push_back(service_.size()); + data.insert(data.end(), service_.begin(), service_.end()); + data.push_back(regexp_.size()); + data.insert(data.end(), regexp_.begin(), regexp_.end()); + data.insert(data.end(), encname.begin(), encname.end()); + return data; +} + +std::vector DNSPacket::data() const { + std::vector data; + PushInt16(&data, qid_); + byte b = 0x00; + if (response_) b |= 0x80; + b |= ((opcode_ & 0x0f) << 3); + if (aa_) b |= 0x04; + if (tc_) b |= 0x02; + if (rd_) b |= 0x01; + data.push_back(b); + b = 0x00; + if (ra_) b |= 0x80; + if (z_) b |= 0x40; + if (ad_) b |= 0x20; + if (cd_) b |= 0x10; + b |= (rcode_ & 0x0f); + data.push_back(b); + + int count = questions_.size(); + PushInt16(&data, count); + count = answers_.size(); + PushInt16(&data, count); + count = auths_.size(); + PushInt16(&data, count); + count = adds_.size(); + PushInt16(&data, count); + + for (const std::unique_ptr& question : questions_) { + std::vector qdata = question->data(); + data.insert(data.end(), qdata.begin(), qdata.end()); + } + for (const std::unique_ptr& rr : answers_) { + std::vector rrdata = rr->data(); + data.insert(data.end(), rrdata.begin(), rrdata.end()); + } + for (const std::unique_ptr& rr : auths_) { + std::vector rrdata = rr->data(); + data.insert(data.end(), rrdata.begin(), rrdata.end()); + } + for (const std::unique_ptr& rr : adds_) { + std::vector rrdata = rr->data(); + data.insert(data.end(), rrdata.begin(), rrdata.end()); + } + return data; +} + +} // namespace ares diff --git a/test/dns-proto.h b/test/dns-proto.h new file mode 100644 index 00000000..24ff6615 --- /dev/null +++ b/test/dns-proto.h @@ -0,0 +1,235 @@ +// -*- mode: c++ -*- +#ifndef DNS_PROTO_H +#define DNS_PROTO_H +// Utilities for processing DNS packet contents + +// Include ares internal file for DNS protocol constants +#include "nameser.h" + +#include +#include +#include + +namespace ares { + +typedef unsigned char byte; + +std::string HexDump(std::vector data); +std::string HexDump(const byte *data, int len); +std::string HexDump(const char *data, int len); + +std::string StatusToString(int status); +std::string RcodeToString(int rcode); +std::string RRTypeToString(int rrtype); +std::string ClassToString(int qclass); +std::string AddressToString(const byte* addr, int len); +std::string AddressToString(const char* addr, int len); + +// Convert DNS protocol data to strings. +// Note that these functions are not defensive; they assume +// a validly formatted input, and so should not be used on +// externally-determined inputs. +std::string PacketToString(const std::vector& packet); +std::string QuestionToString(const std::vector& packet, + const byte** data, int* len); +std::string RRToString(const std::vector& packet, + const byte** data, int* len); + +struct DNSQuestion { + DNSQuestion(const std::string& name, ns_type rrtype, ns_class qclass) + : name_(name), rrtype_(rrtype), qclass_(qclass) {} + DNSQuestion(const std::string& name, ns_type rrtype) + : name_(name), rrtype_(rrtype), qclass_(ns_c_in) {} + virtual std::vector data() const; + std::string name_; + ns_type rrtype_; + ns_class qclass_; +}; + +struct DNSRR : public DNSQuestion { + DNSRR(const std::string& name, ns_type rrtype, ns_class qclass, int ttl) + : DNSQuestion(name, rrtype, qclass), ttl_(ttl) {} + DNSRR(const std::string& name, ns_type rrtype, int ttl) + : DNSQuestion(name, rrtype), ttl_(ttl) {} + virtual std::vector data() const = 0; + int ttl_; +}; + +struct DNSAddressRR : public DNSRR { + DNSAddressRR(const std::string& name, ns_type rrtype, int ttl, + const byte* addr, int addrlen) + : DNSRR(name, rrtype, ttl), addr_(addr, addr + addrlen) {} + DNSAddressRR(const std::string& name, ns_type rrtype, int ttl, + const std::vector& addr) + : DNSRR(name, rrtype, ttl), addr_(addr) {} + virtual std::vector data() const; + std::vector addr_; +}; + +struct DNSARR : public DNSAddressRR { + DNSARR(const std::string& name, int ttl, const byte* addr, int addrlen) + : DNSAddressRR(name, ns_t_a, ttl, addr, addrlen) {} + DNSARR(const std::string& name, int ttl, const std::vector& addr) + : DNSAddressRR(name, ns_t_a, ttl, addr) {} +}; + +struct DNSAaaaRR : public DNSAddressRR { + DNSAaaaRR(const std::string& name, int ttl, const byte* addr, int addrlen) + : DNSAddressRR(name, ns_t_aaaa, ttl, addr, addrlen) {} + DNSAaaaRR(const std::string& name, int ttl, const std::vector& addr) + : DNSAddressRR(name, ns_t_aaaa, ttl, addr) {} +}; + +struct DNSSingleNameRR : public DNSRR { + DNSSingleNameRR(const std::string& name, ns_type rrtype, int ttl, + const std::string& other) + : DNSRR(name, rrtype, ttl), other_(other) {} + virtual std::vector data() const; + std::string other_; +}; + +struct DNSCnameRR : public DNSSingleNameRR { + DNSCnameRR(const std::string& name, int ttl, const std::string& other) + : DNSSingleNameRR(name, ns_t_cname, ttl, other) {} +}; + +struct DNSNsRR : public DNSSingleNameRR { + DNSNsRR(const std::string& name, int ttl, const std::string& other) + : DNSSingleNameRR(name, ns_t_ns, ttl, other) {} +}; + +struct DNSPtrRR : public DNSSingleNameRR { + DNSPtrRR(const std::string& name, int ttl, const std::string& other) + : DNSSingleNameRR(name, ns_t_ptr, ttl, other) {} +}; + +struct DNSTxtRR : public DNSRR { + DNSTxtRR(const std::string& name, int ttl, const std::vector& txt) + : DNSRR(name, ns_t_txt, ttl), txt_(txt) {} + virtual std::vector data() const; + std::vector txt_; +}; + +struct DNSMxRR : public DNSRR { + DNSMxRR(const std::string& name, int ttl, int pref, const std::string& other) + : DNSRR(name, ns_t_mx, ttl), pref_(pref), other_(other) {} + virtual std::vector data() const; + int pref_; + std::string other_; +}; + +struct DNSSrvRR : public DNSRR { + DNSSrvRR(const std::string& name, int ttl, + int prio, int weight, int port, const std::string& target) + : DNSRR(name, ns_t_srv, ttl), prio_(prio), weight_(weight), port_(port), target_(target) {} + virtual std::vector data() const; + int prio_; + int weight_; + int port_; + std::string target_; +}; + +struct DNSSoaRR : public DNSRR { + DNSSoaRR(const std::string& name, int ttl, + const std::string& nsname, const std::string& rname, + int serial, int refresh, int retry, int expire, int minimum) + : DNSRR(name, ns_t_soa, ttl), nsname_(nsname), rname_(rname), + serial_(serial), refresh_(refresh), retry_(retry), + expire_(expire), minimum_(minimum) {} + virtual std::vector data() const; + std::string nsname_; + std::string rname_; + int serial_; + int refresh_; + int retry_; + int expire_; + int minimum_; +}; + +struct DNSNaptrRR : public DNSRR { + DNSNaptrRR(const std::string& name, int ttl, + int order, int pref, + const std::string& flags, + const std::string& service, + const std::string& regexp, + const std::string& replacement) + : DNSRR(name, ns_t_naptr, ttl), order_(order), pref_(pref), + flags_(flags), service_(service), regexp_(regexp), replacement_(replacement) {} + virtual std::vector data() const; + int order_; + int pref_; + std::string flags_; + std::string service_; + std::string regexp_; + std::string replacement_; +}; + +struct DNSOption { + int code_; + std::vector data_; +}; + +struct DNSOptRR : public DNSRR { + DNSOptRR(int extrcode, int udpsize) + : DNSRR("", ns_t_opt, static_cast(udpsize), extrcode) {} + virtual std::vector data() const; + std::vector opts_; +}; + +struct DNSPacket { + DNSPacket() + : qid_(0), response_(false), opcode_(ns_o_query), + aa_(false), tc_(false), rd_(false), ra_(false), + z_(false), ad_(false), cd_(false), rcode_(ns_r_noerror) {} + // Convenience functions that take ownership of given pointers. + DNSPacket& add_question(DNSQuestion *q) { + questions_.push_back(std::unique_ptr(q)); + return *this; + } + DNSPacket& add_answer(DNSRR *q) { + answers_.push_back(std::unique_ptr(q)); + return *this; + } + DNSPacket& add_auth(DNSRR *q) { + auths_.push_back(std::unique_ptr(q)); + return *this; + } + DNSPacket& add_additional(DNSRR *q) { + adds_.push_back(std::unique_ptr(q)); + return *this; + } + // Chainable setters. + DNSPacket& set_qid(int qid) { qid_ = qid; return *this; } + DNSPacket& set_response(bool v = true) { response_ = v; return *this; } + DNSPacket& set_aa(bool v = true) { aa_ = v; return *this; } + DNSPacket& set_tc(bool v = true) { tc_ = v; return *this; } + DNSPacket& set_rd(bool v = true) { rd_ = v; return *this; } + DNSPacket& set_ra(bool v = true) { ra_ = v; return *this; } + DNSPacket& set_z(bool v = true) { z_ = v; return *this; } + DNSPacket& set_ad(bool v = true) { ad_ = v; return *this; } + DNSPacket& set_cd(bool v = true) { cd_ = v; return *this; } + DNSPacket& set_rcode(ns_rcode rcode) { rcode_ = rcode; return *this; } + + // Return the encoded packet. + std::vector data() const; + + int qid_; + bool response_; + ns_opcode opcode_; + bool aa_; + bool tc_; + bool rd_; + bool ra_; + bool z_; + bool ad_; + bool cd_; + ns_rcode rcode_; + std::vector> questions_; + std::vector> answers_; + std::vector> auths_; + std::vector> adds_; +}; + +} // namespace ares + +#endif diff --git a/test/fuzzinput/answer_a b/test/fuzzinput/answer_a new file mode 100644 index 0000000000000000000000000000000000000000..57840a144cf7883359e39c377cb80f78f904068e GIT binary patch literal 62 rcmY#TXklPr1VSKSE-x=