Merge "DO NOT MERGE - Merge Android 10 into master"
diff --git a/resolv_unit_test.cpp b/resolv_unit_test.cpp
index 2820b07..f9accd0 100644
--- a/resolv_unit_test.cpp
+++ b/resolv_unit_test.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "resolv"
-#include <gtest/gtest.h>
-
#include <android-base/stringprintf.h>
#include <arpa/inet.h>
+#include <gmock/gmock-matchers.h>
+#include <gtest/gtest.h>
#include <netdb.h>
#include <netdutils/InternetAddresses.h>
@@ -39,10 +39,14 @@
using android::net::NetworkDnsEventReported;
using android::netdutils::ScopedAddrinfo;
-// Minimize class ResolverTest to be class TestBase because class TestBase doesn't need all member
-// functions of class ResolverTest and class DnsResponderClient.
class TestBase : public ::testing::Test {
protected:
+ struct DnsMessage {
+ std::string host_name; // host name
+ ns_type type; // record type
+ test::DNSHeader header; // dns header
+ };
+
void SetUp() override {
// Create cache for test
resolv_create_cache_for_net(TEST_NETID);
@@ -52,7 +56,64 @@
resolv_delete_cache_for_net(TEST_NETID);
}
- int setResolvers() {
+ test::DNSRecord MakeAnswerRecord(const std::string& name, unsigned rclass, unsigned rtype,
+ const std::string& rdata, unsigned ttl = kAnswerRecordTtlSec) {
+ test::DNSRecord record{
+ .name = {.name = name},
+ .rtype = rtype,
+ .rclass = rclass,
+ .ttl = ttl,
+ };
+ EXPECT_TRUE(test::DNSResponder::fillAnswerRdata(rdata, record));
+ return record;
+ }
+
+ DnsMessage MakeDnsMessage(const std::string& qname, ns_type qtype,
+ const std::vector<std::string>& rdata) {
+ const unsigned qclass = ns_c_in;
+ // Build a DNSHeader in the following format.
+ // Question
+ // <qname> IN <qtype>
+ // Answer
+ // <qname> IN <qtype> <rdata[0]>
+ // ..
+ // <qname> IN <qtype> <rdata[n]>
+ //
+ // Example:
+ // Question
+ // hello.example.com. IN A
+ // Answer
+ // hello.example.com. IN A 1.2.3.1
+ // ..
+ // hello.example.com. IN A 1.2.3.9
+ test::DNSHeader header(kDefaultDnsHeader);
+
+ // Question section
+ test::DNSQuestion question{
+ .qname = {.name = qname},
+ .qtype = qtype,
+ .qclass = qclass,
+ };
+ header.questions.push_back(std::move(question));
+
+ // Answer section
+ for (const auto& r : rdata) {
+ test::DNSRecord record = MakeAnswerRecord(qname, qclass, qtype, r);
+ header.answers.push_back(std::move(record));
+ }
+ // TODO: Perhaps add support for authority RRs and additional RRs.
+ return {qname, qtype, header};
+ }
+
+ void StartDns(test::DNSResponder& dns, const std::vector<DnsMessage>& messages) {
+ for (const auto& m : messages) {
+ dns.addMappingDnsHeader(m.host_name, m.type, m.header);
+ }
+ ASSERT_TRUE(dns.startServer());
+ dns.clearQueries();
+ }
+
+ int SetResolvers() {
const std::vector<std::string> servers = {test::kDefaultListenAddr};
const std::vector<std::string> domains = {"example.com"};
const res_params params = {
@@ -326,7 +387,7 @@
test::DNSResponder dns;
dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
// Want AAAA answer but DNS server has A answer only.
addrinfo* result = nullptr;
@@ -348,7 +409,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
int ai_family;
@@ -377,7 +438,7 @@
TEST_F(ResolvGetAddrInfoTest, IllegalHostname) {
test::DNSResponder dns;
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
// Illegal hostname is verified by res_hnok() in system/netd/resolv/res_comp.cpp.
static constexpr char const* illegalHostnames[] = {
@@ -440,7 +501,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
dns.setResponseProbability(0.0); // always ignore requests and response preset rcode
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
addrinfo* result = nullptr;
const addrinfo hints = {.ai_family = AF_UNSPEC};
@@ -457,7 +518,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
dns.setResponseProbability(0.0); // always ignore requests and don't response
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
addrinfo* result = nullptr;
const addrinfo hints = {.ai_family = AF_UNSPEC};
@@ -474,7 +535,7 @@
dns.addMapping("cnames.example.com.", ns_type::ns_t_cname, "acname.example.com.");
dns.addMapping("acname.example.com.", ns_type::ns_t_cname, "hello.example.com.");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
const char* name;
@@ -507,7 +568,7 @@
TEST_F(ResolvGetAddrInfoTest, CnamesBrokenChainByIllegalCname) {
test::DNSResponder dns;
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
const char* name;
@@ -561,7 +622,7 @@
dns.addMapping("hello.example.com.", ns_type::ns_t_cname, "a.example.com.");
dns.addMapping("a.example.com.", ns_type::ns_t_cname, "hello.example.com.");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
SCOPED_TRACE(StringPrintf("family: %d", family));
@@ -576,6 +637,52 @@
}
}
+TEST_F(ResolvGetAddrInfoTest, MultiAnswerSections) {
+ test::DNSResponder dns(test::DNSResponder::MappingType::DNS_HEADER);
+ // Answer section for query type {A, AAAA}
+ // Type A:
+ // hello.example.com. IN A 1.2.3.1
+ // hello.example.com. IN A 1.2.3.2
+ // Type AAAA:
+ // hello.example.com. IN AAAA 2001:db8::41
+ // hello.example.com. IN AAAA 2001:db8::42
+ StartDns(dns, {MakeDnsMessage(kHelloExampleCom, ns_type::ns_t_a, {"1.2.3.1", "1.2.3.2"}),
+ MakeDnsMessage(kHelloExampleCom, ns_type::ns_t_aaaa,
+ {"2001:db8::41", "2001:db8::42"})});
+ ASSERT_EQ(0, SetResolvers());
+
+ for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
+ SCOPED_TRACE(StringPrintf("family: %d", family));
+
+ addrinfo* res = nullptr;
+ // If the socket type is not specified, every address will appear twice, once for
+ // SOCK_STREAM and one for SOCK_DGRAM. Just pick one because the addresses for
+ // the second query of different socket type are responded by the cache.
+ const addrinfo hints = {.ai_family = family, .ai_socktype = SOCK_STREAM};
+ NetworkDnsEventReported event;
+ int rv = resolv_getaddrinfo("hello", nullptr, &hints, &mNetcontext, &res, &event);
+ ScopedAddrinfo result(res);
+ ASSERT_NE(nullptr, result);
+ ASSERT_EQ(0, rv);
+
+ const std::vector<std::string> result_strs = ToStrings(result);
+ if (family == AF_INET) {
+ EXPECT_EQ(1U, GetNumQueries(dns, kHelloExampleCom));
+ EXPECT_THAT(result_strs, testing::UnorderedElementsAreArray({"1.2.3.1", "1.2.3.2"}));
+ } else if (family == AF_INET6) {
+ EXPECT_EQ(1U, GetNumQueries(dns, kHelloExampleCom));
+ EXPECT_THAT(result_strs,
+ testing::UnorderedElementsAreArray({"2001:db8::41", "2001:db8::42"}));
+ } else if (family == AF_UNSPEC) {
+ EXPECT_EQ(0U, GetNumQueries(dns, kHelloExampleCom)); // no query because of the cache
+ EXPECT_THAT(result_strs,
+ testing::UnorderedElementsAreArray(
+ {"1.2.3.1", "1.2.3.2", "2001:db8::41", "2001:db8::42"}));
+ }
+ dns.clearQueries();
+ }
+}
+
TEST_F(GetHostByNameForNetContextTest, AlphabeticalHostname) {
constexpr char host_name[] = "jiababuei.example.com.";
constexpr char v4addr[] = "1.2.3.4";
@@ -585,7 +692,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, v4addr);
dns.addMapping(host_name, ns_type::ns_t_aaaa, v6addr);
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
int ai_family;
@@ -613,7 +720,7 @@
TEST_F(GetHostByNameForNetContextTest, IllegalHostname) {
test::DNSResponder dns;
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
// Illegal hostname is verified by res_hnok() in system/netd/resolv/res_comp.cpp.
static constexpr char const* illegalHostnames[] = {
@@ -655,7 +762,7 @@
test::DNSResponder dns;
dns.addMapping(v4_host_name, ns_type::ns_t_a, "1.2.3.3");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
dns.clearQueries();
// Want AAAA answer but DNS server has A answer only.
@@ -695,7 +802,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
dns.setResponseProbability(0.0); // always ignore requests and response preset rcode
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
hostent* hp = nullptr;
NetworkDnsEventReported event;
@@ -712,7 +819,7 @@
dns.addMapping(host_name, ns_type::ns_t_a, "1.2.3.4");
dns.setResponseProbability(0.0); // always ignore requests and don't response
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
hostent* hp = nullptr;
NetworkDnsEventReported event;
@@ -728,7 +835,7 @@
dns.addMapping("cnames.example.com.", ns_type::ns_t_cname, "acname.example.com.");
dns.addMapping("acname.example.com.", ns_type::ns_t_cname, "hello.example.com.");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
const char* name;
@@ -756,7 +863,7 @@
TEST_F(GetHostByNameForNetContextTest, CnamesBrokenChainByIllegalCname) {
test::DNSResponder dns;
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
static const struct TestConfig {
const char* name;
@@ -809,7 +916,7 @@
dns.addMapping("hello.example.com.", ns_type::ns_t_cname, "a.example.com.");
dns.addMapping("a.example.com.", ns_type::ns_t_cname, "hello.example.com.");
ASSERT_TRUE(dns.startServer());
- ASSERT_EQ(0, setResolvers());
+ ASSERT_EQ(0, SetResolvers());
for (const auto& family : {AF_INET, AF_INET6}) {
SCOPED_TRACE(StringPrintf("family: %d", family));
@@ -825,8 +932,6 @@
// Note that local host file function, files_getaddrinfo(), of resolv_getaddrinfo()
// is not tested because it only returns a boolean (success or failure) without any error number.
-// TODO: Simplify the DNS server configuration, DNSResponder and resolv_set_nameservers, as
-// ResolverTest does.
// TODO: Add test for resolv_getaddrinfo().
// - DNS response message parsing.
// - Unexpected type of resource record (RR).
diff --git a/tests/dns_responder/dns_responder.cpp b/tests/dns_responder/dns_responder.cpp
index 51eb7dd..4bac4b0 100644
--- a/tests/dns_responder/dns_responder.cpp
+++ b/tests/dns_responder/dns_responder.cpp
@@ -434,10 +434,11 @@
/* DNS responder */
DNSResponder::DNSResponder(std::string listen_address, std::string listen_service,
- ns_rcode error_rcode)
+ ns_rcode error_rcode, MappingType mapping_type)
: listen_address_(std::move(listen_address)),
listen_service_(std::move(listen_service)),
- error_rcode_(error_rcode) {}
+ error_rcode_(error_rcode),
+ mapping_type_(mapping_type) {}
DNSResponder::~DNSResponder() {
stopServer();
@@ -445,6 +446,7 @@
void DNSResponder::addMapping(const std::string& name, ns_type type, const std::string& addr) {
std::lock_guard lock(mappings_mutex_);
+ // TODO: Consider using std::map::insert_or_assign().
auto it = mappings_.find(QueryKey(name, type));
if (it != mappings_.end()) {
LOG(INFO) << "Overwriting mapping for (" << name << ", " << dnstype2str(type)
@@ -455,6 +457,23 @@
mappings_.try_emplace({name, type}, addr);
}
+void DNSResponder::addMappingDnsHeader(const std::string& name, ns_type type,
+ const DNSHeader& header) {
+ std::lock_guard lock(mappings_mutex_);
+ // TODO: Consider using std::map::insert_or_assign().
+ auto it = dnsheader_mappings_.find(QueryKey(name, type));
+ if (it != dnsheader_mappings_.end()) {
+ // TODO: Perhaps replace header pointer with header content once DNSHeader::toString() has
+ // been implemented.
+ LOG(INFO) << "Overwriting mapping for (" << name << ", " << dnstype2str(type)
+ << "), previous header " << (void*)&it->second << " new header "
+ << (void*)&header;
+ it->second = header;
+ return;
+ }
+ dnsheader_mappings_.try_emplace({name, type}, header);
+}
+
void DNSResponder::removeMapping(const std::string& name, ns_type type) {
std::lock_guard lock(mappings_mutex_);
auto it = mappings_.find(QueryKey(name, type));
@@ -463,7 +482,18 @@
return;
}
LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
- << "), not present";
+ << "), not present in registered mappings";
+}
+
+void DNSResponder::removeMappingDnsHeader(const std::string& name, ns_type type) {
+ std::lock_guard lock(mappings_mutex_);
+ auto it = dnsheader_mappings_.find(QueryKey(name, type));
+ if (it != dnsheader_mappings_.end()) {
+ dnsheader_mappings_.erase(it);
+ return;
+ }
+ LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
+ << "), not present in registered DnsHeader mappings";
}
void DNSResponder::setResponseProbability(double response_probability) {
@@ -669,7 +699,13 @@
// Make the response. The query has been read into |header| which is used to build and return
// the response as well.
- return makeResponse(&header, response, response_len);
+ switch (mapping_type_) {
+ case MappingType::DNS_HEADER:
+ return makeResponseFromDnsHeader(&header, response, response_len);
+ case MappingType::ADDRESS_OR_HOSTNAME:
+ default:
+ return makeResponse(&header, response, response_len);
+ }
}
bool DNSResponder::addAnswerRecords(const DNSQuestion& question,
@@ -700,7 +736,7 @@
.name = {.name = it->first.name},
.rtype = it->first.type,
.rclass = ns_class::ns_c_in,
- .ttl = 5, // seconds
+ .ttl = kAnswerRecordTtlSec, // seconds
};
if (!fillAnswerRdata(it->second, record)) return false;
answers->push_back(std::move(record));
@@ -802,6 +838,58 @@
return true;
}
+bool DNSResponder::makeResponseFromDnsHeader(DNSHeader* header, char* response,
+ size_t* response_len) const {
+ std::lock_guard guard(mappings_mutex_);
+
+ // Support single question record only. It should be okay because res_mkquery() sets "qdcount"
+ // as one for the operation QUERY and handleDNSRequest() checks ns_opcode::ns_o_query before
+ // making a response. In other words, only need to handle the query which has single question
+ // section. See also res_mkquery() in system/netd/resolv/res_mkquery.cpp.
+ // TODO: Perhaps add support for multi-question records.
+ const std::vector<DNSQuestion>& questions = header->questions;
+ if (questions.size() != 1) {
+ LOG(INFO) << "unsupported question count " << questions.size();
+ return makeErrorResponse(header, ns_rcode::ns_r_notimpl, response, response_len);
+ }
+
+ if (questions[0].qclass != ns_class::ns_c_in && questions[0].qclass != ns_class::ns_c_any) {
+ LOG(INFO) << "unsupported question class " << questions[0].qclass;
+ return makeErrorResponse(header, ns_rcode::ns_r_notimpl, response, response_len);
+ }
+
+ const std::string name = questions[0].qname.name;
+ const int qtype = questions[0].qtype;
+ const auto it = dnsheader_mappings_.find(QueryKey(name, qtype));
+ if (it != dnsheader_mappings_.end()) {
+ // Store both "id" and "rd" which comes from query.
+ const unsigned id = header->id;
+ const bool rd = header->rd;
+
+ // Build a response from the registered DNSHeader mapping.
+ *header = it->second;
+ // Assign both "ID" and "RD" fields from query to response. See RFC 1035 section 4.1.1.
+ header->id = id;
+ header->rd = rd;
+ } else {
+ // TODO: handle correctly. See also TODO in addAnswerRecords().
+ LOG(INFO) << "no mapping found for " << name << " " << dnstype2str(qtype)
+ << ", couldn't build a response from DNSHeader mapping";
+
+ // Note that do nothing as makeResponse() if no mapping is found. It just changes the QR
+ // flag from query (0) to response (1) in the query. Then, send the modified query back as
+ // a response.
+ header->qr = true;
+ }
+
+ char* response_cur = header->write(response, response + *response_len);
+ if (response_cur == nullptr) {
+ return false;
+ }
+ *response_len = response_cur - response;
+ return true;
+}
+
void DNSResponder::setDeferredResp(bool deferred_resp) {
std::lock_guard<std::mutex> guard(cv_mutex_for_deferred_resp_);
deferred_resp_ = deferred_resp;
diff --git a/tests/dns_responder/dns_responder.h b/tests/dns_responder/dns_responder.h
index 41e7312..ed4b089 100644
--- a/tests/dns_responder/dns_responder.h
+++ b/tests/dns_responder/dns_responder.h
@@ -31,6 +31,9 @@
#include <android-base/thread_annotations.h>
#include "android-base/unique_fd.h"
+// Default TTL of the DNS answer record.
+constexpr unsigned kAnswerRecordTtlSec = 5;
+
namespace test {
struct DNSName {
@@ -73,6 +76,8 @@
char* writeIntFields(unsigned rdlen, char* buffer, const char* buffer_end) const;
};
+// TODO: Perhaps rename to DNSMessage. Per RFC 1035 section 4.1, struct DNSHeader more likes a
+// message because it has not only header section but also question section and other RRs.
struct DNSHeader {
unsigned id;
bool ra;
@@ -108,6 +113,7 @@
inline const std::string kDefaultListenAddr = "127.0.0.3";
inline const std::string kDefaultListenService = "53";
+inline const ns_rcode kDefaultErrorCode = ns_rcode::ns_r_servfail;
/*
* Simple DNS responder, which replies to queries with the registered response
@@ -122,18 +128,36 @@
FORMERR_UNCOND, // DNS server reply FORMERR unconditionally
DROP // DNS server not supporting EDNS will not do any response.
};
+ // Indicate which mapping the DNS server used to build the response.
+ // See also addMapping(), addMappingDnsHeader(), removeMapping(), removeMappingDnsHeader(),
+ // makeResponse(), makeResponseFromDnsHeader().
+ // TODO: Perhaps break class DNSResponder for each mapping.
+ // TODO: Add the mapping from (raw dns query) to (raw dns response).
+ enum class MappingType : uint8_t {
+ ADDRESS_OR_HOSTNAME, // Use the mapping from (name, type) to (address or hostname)
+ DNS_HEADER, // Use the mapping from (name, type) to (DNSHeader)
+ };
DNSResponder(std::string listen_address = kDefaultListenAddr,
std::string listen_service = kDefaultListenService,
- ns_rcode error_rcode = ns_rcode::ns_r_servfail);
+ ns_rcode error_rcode = kDefaultErrorCode,
+ DNSResponder::MappingType mapping_type = MappingType::ADDRESS_OR_HOSTNAME);
DNSResponder(ns_rcode error_rcode)
: DNSResponder(kDefaultListenAddr, kDefaultListenService, error_rcode){};
+ DNSResponder(MappingType mapping_type)
+ : DNSResponder(kDefaultListenAddr, kDefaultListenService, kDefaultErrorCode,
+ mapping_type){};
+
~DNSResponder();
+ // Functions used for accessing mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER}.
void addMapping(const std::string& name, ns_type type, const std::string& addr);
+ void addMappingDnsHeader(const std::string& name, ns_type type, const DNSHeader& header);
void removeMapping(const std::string& name, ns_type type);
+ void removeMappingDnsHeader(const std::string& name, ns_type type);
+
void setResponseProbability(double response_probability);
void setEdns(Edns edns);
bool running() const;
@@ -182,9 +206,13 @@
bool generateErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
size_t* response_len) const;
+ // TODO: Change makeErrorResponse and makeResponse{, FromDnsHeader} to use C++ containers
+ // instead of the unsafe pointer + length buffer.
bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
size_t* response_len) const;
+ // Build a response from mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER}.
bool makeResponse(DNSHeader* header, char* response, size_t* response_len) const;
+ bool makeResponseFromDnsHeader(DNSHeader* header, char* response, size_t* response_len) const;
// Add a new file descriptor to be polled by the handler thread.
bool addFd(int fd, uint32_t events);
@@ -204,6 +232,8 @@
const std::string listen_service_;
// Error code to return for requests for an unknown name.
const ns_rcode error_rcode_;
+ // Mapping type the DNS server used to build the response.
+ const MappingType mapping_type_;
// Probability that a valid response is being sent instead of being sent
// instead of returning error_rcode_.
std::atomic<double> response_probability_ = 1.0;
@@ -217,9 +247,14 @@
// ignoring the requests.
std::atomic<Edns> edns_ = Edns::ON;
- // Mappings from (name, type) to registered response and the
- // mutex protecting them.
+ // Mappings used for building the DNS response by registered mapping items. |mapping_type_|
+ // decides which mapping is used. See also makeResponse{, FromDnsHeader}.
+ // - mappings_: Mapping from (name, type) to (address or hostname).
+ // - dnsheader_mappings_: Mapping from (name, type) to (DNSHeader).
std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_ GUARDED_BY(mappings_mutex_);
+ std::unordered_map<QueryKey, DNSHeader, QueryKeyHash> dnsheader_mappings_
+ GUARDED_BY(mappings_mutex_);
+
mutable std::mutex mappings_mutex_;
// Query names received so far and the corresponding mutex.
mutable std::vector<std::pair<std::string, ns_type>> queries_ GUARDED_BY(queries_mutex_);
diff --git a/tests/resolv_test_utils.h b/tests/resolv_test_utils.h
index 7c43ed9..987d289 100644
--- a/tests/resolv_test_utils.h
+++ b/tests/resolv_test_utils.h
@@ -50,6 +50,20 @@
static constexpr char kBadCharAtTheEndHost[] = "hello.example.com^.";
static constexpr char kBadCharInTheMiddleOfLabelHost[] = "hello.ex^ample.com.";
+static const test::DNSHeader kDefaultDnsHeader = {
+ // Don't need to initialize the flag "id" and "rd" because DNS responder assigns them from
+ // query to response. See RFC 1035 section 4.1.1.
+ .id = 0, // unused. should be assigned from query to response
+ .ra = false, // recursive query support is not available
+ .rcode = ns_r_noerror, // no error
+ .qr = true, // message is a response
+ .opcode = QUERY, // a standard query
+ .aa = false, // answer/authority portion was not authenticated by the server
+ .tr = false, // message is not truncated
+ .rd = false, // unused. should be assigned from query to response
+ .ad = false, // non-authenticated data is unacceptable
+};
+
size_t GetNumQueries(const test::DNSResponder& dns, const char* name);
size_t GetNumQueriesForType(const test::DNSResponder& dns, ns_type type, const char* name);
std::string ToString(const hostent* he);