dns_responder: Add an optional DNS header mapping to build a response
am: 30013fc871

Change-Id: I16fae282d7e49c5ebd0ff7be0999eba43d3b42ae
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);