mirror of https://github.com/c-ares/c-ares.git
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
parent
148b6e93b9
commit
af3ee9a8ba
20 changed files with 2918 additions and 0 deletions
@ -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…
Reference in new issue