Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30013fc8 authored by Hungming Chen's avatar Hungming Chen
Browse files

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

Currently, the DNS response is built from the registered hostname or
address mapping. The response header fields have been predefined mostly
and that may limit the test coverage indirectly.

An alternative way is to add an optional DNS header mapping to build
a response. The response could be built from a preconfigured DNS
header if necessary. That helps to extend the test coverage.

Test: cd packages/modules/DnsResolver && atest
Change-Id: Ib56ae7b076e8e1e9ebbbca187b66ee91643dd0b0
parent ba69fc1e
Loading
Loading
Loading
Loading
+128 −23
Original line number Diff line number Diff line
@@ -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::base::StringPrintf;
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 @@ class TestBase : public ::testing::Test {
        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_F(ResolvGetAddrInfoTest, AlphabeticalHostname_NoData) {
    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 @@ TEST_F(ResolvGetAddrInfoTest, AlphabeticalHostname) {
    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, AlphabeticalHostname) {
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 @@ TEST_F(ResolvGetAddrInfoTest, ServerResponseError) {
        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 @@ TEST_F(ResolvGetAddrInfoTest, ServerTimeout) {
    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 @@ TEST_F(ResolvGetAddrInfoTest, CnamesNoIpAddress) {
    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, CnamesNoIpAddress) {
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 @@ TEST_F(ResolvGetAddrInfoTest, CnamesInfiniteLoop) {
    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, CnamesInfiniteLoop) {
    }
}

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 @@ TEST_F(GetHostByNameForNetContextTest, AlphabeticalHostname) {
    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, AlphabeticalHostname) {
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_F(GetHostByNameForNetContextTest, NoData) {
    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 @@ TEST_F(GetHostByNameForNetContextTest, ServerResponseError) {
        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 @@ TEST_F(GetHostByNameForNetContextTest, ServerTimeout) {
    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 @@ TEST_F(GetHostByNameForNetContextTest, CnamesNoIpAddress) {
    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, CnamesNoIpAddress) {
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 @@ TEST_F(GetHostByNameForNetContextTest, CnamesInfiniteLoop) {
    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 @@ TEST_F(GetHostByNameForNetContextTest, CnamesInfiniteLoop) {
// 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).
+93 −5
Original line number Diff line number Diff line
@@ -434,10 +434,11 @@ const char* DNSHeader::readHeader(const char* buffer, const char* buffer_end, un
/* 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 @@ DNSResponder::~DNSResponder() {

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 @@ void DNSResponder::addMapping(const std::string& name, ns_type type, const std::
    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 @@ void DNSResponder::removeMapping(const std::string& name, ns_type type) {
        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,8 +699,14 @@ bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, char* respo

    // Make the response. The query has been read into |header| which is used to build and return
    // the response as well.
    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,
                                    std::vector<DNSRecord>* answers) const {
@@ -700,7 +736,7 @@ bool DNSResponder::addAnswerRecords(const DNSQuestion& question,
                    .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 @@ bool DNSResponder::makeResponse(DNSHeader* header, char* response, size_t* respo
    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;
+38 −3
Original line number Diff line number Diff line
@@ -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 @@ struct DNSRecord {
    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 @@ struct DNSHeader {

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 @@ class DNSResponder {
        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 @@ class DNSResponder {

    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 @@ class DNSResponder {
    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 @@ class DNSResponder {
    // 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_);
+14 −0
Original line number Diff line number Diff line
@@ -50,6 +50,20 @@ static constexpr char kBadCharBeforePeriodHost[] = "hello.example^.com.";
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);