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.
pull/34/head
David Drysdale 9 years ago
parent 148b6e93b9
commit af3ee9a8ba
  1. 9
      test/.gitignore
  2. 33
      test/Makefile.am
  3. 20
      test/ares-fuzz.cc
  4. 44
      test/ares-test-fuzz.cc
  5. 71
      test/ares-test-init.cc
  6. 56
      test/ares-test-internal.cc
  7. 121
      test/ares-test-live.cc
  8. 19
      test/ares-test-main.cc
  9. 222
      test/ares-test-misc.cc
  10. 139
      test/ares-test-mock.cc
  11. 640
      test/ares-test-parse.cc
  12. 385
      test/ares-test.cc
  13. 197
      test/ares-test.h
  14. 2
      test/buildconf
  15. 17
      test/configure.ac
  16. 131
      test/dns-proto-test.cc
  17. 577
      test/dns-proto.cc
  18. 235
      test/dns-proto.h
  19. BIN
      test/fuzzinput/answer_a
  20. BIN
      test/fuzzinput/answer_aaaa

9
test/.gitignore vendored

@ -0,0 +1,9 @@
*.o
libgtest.a
libgmock.a
arestest
aresfuzz
arestest.log
arestest.trs
test-suite.log
fuzzoutput

@ -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

@ -0,0 +1,20 @@
// General driver to allow command-line fuzzer (i.e. afl) to
// fuzz the libfuzzer entrypoint.
#include <stdio.h>
#include <unistd.h>
#include <vector>
extern "C" void LLVMFuzzerTestOneInput(const unsigned char *data,
unsigned long size);
int main() {
std::vector<unsigned char> 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;
}

@ -0,0 +1,44 @@
#include "ares-test.h"
#include <vector>
// 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);
}

@ -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

@ -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 <string>
#include <vector>
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

@ -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 <netdb.h>
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

@ -0,0 +1,19 @@
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <pwd.h>
#include <iostream>
#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;
}

@ -0,0 +1,222 @@
#include "ares-test.h"
#include "dns-proto.h"
#include <string>
#include <vector>
namespace ares {
namespace test {
std::vector<std::string> 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<std::string> 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("<unknown family>");
break;
}
server = server->next;
}
ares_free_data(servers);
return results;
}
TEST_F(DefaultChannelTest, GetServers) {
std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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

@ -0,0 +1,139 @@
#include "ares-test.h"
#include "dns-proto.h"
#include <sys/socket.h>
#include <sstream>
#include <vector>
using testing::InvokeWithoutArgs;
namespace ares {
namespace test {
TEST_F(MockChannelTest, Basic) {
std::vector<byte> 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<byte> 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<byte> 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

@ -0,0 +1,640 @@
#include "ares-test.h"
#include "dns-proto.h"
#include <sstream>
#include <vector>
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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte>(expected1.data(), expected1.data() + expected1.size()),
std::vector<byte>(txt->txt, txt->txt + txt->length));
struct ares_txt_reply* txt2 = txt->next;
ASSERT_NE(nullptr, txt2);
EXPECT_EQ(std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size()),
std::vector<byte>(txt2->txt, txt2->txt + txt2->length));
struct ares_txt_reply* txt3 = txt2->next;
ASSERT_NE(nullptr, txt3);
EXPECT_EQ(std::vector<byte>(expected2b.data(), expected2b.data() + expected2b.size()),
std::vector<byte>(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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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

@ -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 <netdb.h>
#include <functional>
#include <sstream>
namespace ares {
namespace test {
namespace {
void ProcessWork(ares_channel channel,
int extrafd, std::function<void(int)> 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<size_t, int> 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<int>(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<HostResult*>(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<SearchResult*>(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<NameInfoResult*>(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

@ -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 <map>
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_t, int> 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 <name, RRtype>
// 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<byte>& 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<byte> 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<MockServer> 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<std::string> aliases_;
int addrtype_; // AF_INET or AF_INET6
std::vector<std::string> 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<byte> 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

@ -0,0 +1,2 @@
#!/bin/sh
autoreconf -iv

@ -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

@ -0,0 +1,131 @@
#include "ares-test.h"
#include "dns-proto.h"
#include <vector>
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<byte> 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<byte> 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<byte> 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<byte> 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

@ -0,0 +1,577 @@
#include "dns-proto.h"
// Include ares internal file for DNS protocol details
#include "ares.h"
#include "ares_dns.h"
#include <sstream>
namespace ares {
std::string HexDump(std::vector<byte> 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<byte>(data, data + len));
}
std::string HexDump(const char *data, int len) {
return HexDump(reinterpret_cast<const byte*>(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<const byte*>(addr), len);
}
std::string PacketToString(const std::vector<byte>& 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<byte>& 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<byte>& 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<byte>* 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<byte>* data, int value) {
data->push_back((value & 0xff00) >> 8);
data->push_back(value & 0x00ff);
}
std::vector<byte> EncodeString(const std::string& name) {
std::vector<byte> 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<byte> DNSQuestion::data() const {
std::vector<byte> data;
std::vector<byte> encname = EncodeString(name_);
data.insert(data.end(), encname.begin(), encname.end());
PushInt16(&data, rrtype_);
PushInt16(&data, qclass_);
return data;
}
std::vector<byte> DNSRR::data() const {
std::vector<byte> data = DNSQuestion::data();
PushInt32(&data, ttl_);
return data;
}
std::vector<byte> DNSSingleNameRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> encname = EncodeString(other_);
int len = encname.size();
PushInt16(&data, len);
data.insert(data.end(), encname.begin(), encname.end());
return data;
}
std::vector<byte> DNSTxtRR::data() const {
std::vector<byte> 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<byte> DNSMxRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> 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<byte> DNSSrvRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> 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<byte> DNSAddressRR::data() const {
std::vector<byte> data = DNSRR::data();
int len = addr_.size();
PushInt16(&data, len);
data.insert(data.end(), addr_.begin(), addr_.end());
return data;
}
std::vector<byte> DNSSoaRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> encname1 = EncodeString(nsname_);
std::vector<byte> 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<byte> DNSOptRR::data() const {
std::vector<byte> 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<byte> DNSNaptrRR::data() const {
std::vector<byte> data = DNSRR::data();
std::vector<byte> 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<byte> DNSPacket::data() const {
std::vector<byte> 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<DNSQuestion>& question : questions_) {
std::vector<byte> qdata = question->data();
data.insert(data.end(), qdata.begin(), qdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : answers_) {
std::vector<byte> rrdata = rr->data();
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : auths_) {
std::vector<byte> rrdata = rr->data();
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
for (const std::unique_ptr<DNSRR>& rr : adds_) {
std::vector<byte> rrdata = rr->data();
data.insert(data.end(), rrdata.begin(), rrdata.end());
}
return data;
}
} // namespace ares

@ -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 <memory>
#include <string>
#include <vector>
namespace ares {
typedef unsigned char byte;
std::string HexDump(std::vector<byte> 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<byte>& packet);
std::string QuestionToString(const std::vector<byte>& packet,
const byte** data, int* len);
std::string RRToString(const std::vector<byte>& 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<byte> 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<byte> 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<byte>& addr)
: DNSRR(name, rrtype, ttl), addr_(addr) {}
virtual std::vector<byte> data() const;
std::vector<byte> 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<byte>& 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<byte>& 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<byte> 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<std::string>& txt)
: DNSRR(name, ns_t_txt, ttl), txt_(txt) {}
virtual std::vector<byte> data() const;
std::vector<std::string> 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<byte> 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<byte> 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<byte> 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<byte> data() const;
int order_;
int pref_;
std::string flags_;
std::string service_;
std::string regexp_;
std::string replacement_;
};
struct DNSOption {
int code_;
std::vector<byte> data_;
};
struct DNSOptRR : public DNSRR {
DNSOptRR(int extrcode, int udpsize)
: DNSRR("", ns_t_opt, static_cast<ns_class>(udpsize), extrcode) {}
virtual std::vector<byte> data() const;
std::vector<DNSOption> 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<DNSQuestion>(q));
return *this;
}
DNSPacket& add_answer(DNSRR *q) {
answers_.push_back(std::unique_ptr<DNSRR>(q));
return *this;
}
DNSPacket& add_auth(DNSRR *q) {
auths_.push_back(std::unique_ptr<DNSRR>(q));
return *this;
}
DNSPacket& add_additional(DNSRR *q) {
adds_.push_back(std::unique_ptr<DNSRR>(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<byte> 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<std::unique_ptr<DNSQuestion>> questions_;
std::vector<std::unique_ptr<DNSRR>> answers_;
std::vector<std::unique_ptr<DNSRR>> auths_;
std::vector<std::unique_ptr<DNSRR>> adds_;
};
} // namespace ares
#endif

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save