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

Commit d40018ac authored by Hungming Chen's avatar Hungming Chen Committed by android-build-merger
Browse files

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

am: 0ec0d080

Change-Id: I0ae00a5232e50609c08f559941430b458a241dd9
parents 5a8b0715 0ec0d080
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);