A C library for asynchronous DNS requests (grpc依赖)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

386 lines
10 KiB

test: Add initial unit tests for c-ares library The tests are written in C++11, using the GoogleTest and GoogleMock frameworks. They have their own independent autoconf setup, so that users of the library need not have a C++ compiler just to get c-ares working (however, the test/configure.ac file does assume the use of a shared top-level m4/ directory). However, this autoconf setup has only been tested on Linux and OSX so far. Run with "./arestest", or "./arestest -v" to see extra debug info. The GoogleTest options for running specific tests are also available (e.g. "./arestest --gtest_filter=*Live*"). The tests are nowhere near complete yet (currently hitting around 60% coverage as reported by gcov), but they do include examples of a few different styles of testing: - There are live tests (ares-test-live.cc), which assume that the current machine has a valid DNS setup and connection to the internet; these tests issue queries for real domains but don't particularly check what gets returned. The tests will fail on an offline machine. - There a few mock tests (ares-test-mock.cc) that set up a fake DNS server and inject its port into the c-ares library configuration. These tests allow specific response messages to be crafted and injected, and so are likely to be used for many more tests in future. - To make this generation/injection easier, the dns-proto.h file includes C++ helper classes for building DNS packets. - Other library entrypoints that don't require network activity (e.g. ares_parse_*_reply) are tested directly. - There are few tests of library-internal functions that are not normally visible to API users (in ares-test-internal.cc). - A couple of the tests use a helper method of the test fixture to inject memory allocation failures, using the earlier change to the library to allow override of malloc/realloc/free. - There is also an entrypoint to allow Clang's libfuzzer to drive the packet parsing code in ares_parse_*_reply, together with a standalone wrapper for it (./aresfuzz) to allow use of afl-fuzz for further fuzz testing.
9 years ago
#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