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

Commit f9cd4ebc authored by Hungming Chen's avatar Hungming Chen
Browse files

resolv_gold_test: Initial release

The target is that capturing packets from real life network and use them
to do unit test. It makes that unit test could have more testing packets
from real device usage. In the initial release, the DNS responder has
been added the capability for responding predefined packets by a
registered query-response packet mapping.

Test: cd packages/modules/DnsResolver && atest

Change-Id: Ib524e978ba9464d521e72878ae723b573888b38f
parent effead99
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
{
{
    "presubmit": [
    "presubmit": [
        { "name": "resolv_integration_test" },
        { "name": "resolv_integration_test" },
        { "name": "resolv_gold_test" },
        { "name": "resolv_unit_test" },
        { "name": "resolv_unit_test" },
        { "name": "resolv_stress_test" }
        { "name": "resolv_stress_test" }
    ]
    ]
+30 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,36 @@ cc_test_library {
    ],
    ],
}
}


cc_test {
    name: "resolv_gold_test",
    test_suites: ["device-tests"],
    require_root: true,
    defaults: ["netd_defaults"],
    srcs: [
        "resolv_gold_test.cpp",
    ],
    header_libs: [
        "libnetd_resolv_internal_headers",
    ],
    shared_libs: [
        "libcrypto",
        "libprotobuf-cpp-lite",
        "libssl",
    ],
    static_libs: [
        "dnsresolver_aidl_interface-cpp",
        "libbase",
        "libgmock",
        "liblog",
        "libnetd_resolv",
        "libnetd_test_dnsresponder",
        "libnetd_test_resolv_utils",
        "libnetdutils",
        "server_configurable_flags",
        "stats_proto",
    ],
}

cc_test {
cc_test {
    name: "resolv_stress_test",
    name: "resolv_stress_test",
    test_suites: ["device-tests"],
    test_suites: ["device-tests"],
+88 −49
Original line number Original line Diff line number Diff line
@@ -36,10 +36,12 @@
#include <android-base/logging.h>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <android-base/strings.h>
#include <netdutils/InternetAddresses.h>
#include <netdutils/InternetAddresses.h>
#include <netdutils/Slice.h>
#include <netdutils/SocketOption.h>
#include <netdutils/SocketOption.h>


using android::netdutils::enableSockopt;
using android::netdutils::enableSockopt;
using android::netdutils::ScopedAddrinfo;
using android::netdutils::ScopedAddrinfo;
using android::netdutils::Slice;


namespace test {
namespace test {


@@ -402,6 +404,15 @@ char* DNSHeader::write(char* buffer, const char* buffer_end) const {
    return buffer_cur;
    return buffer_cur;
}
}


// TODO: convert all callers to this interface, then delete the old one.
bool DNSHeader::write(std::vector<uint8_t>* out) const {
    char buffer[4096];
    char* end = this->write(buffer, buffer + sizeof buffer);
    if (end == nullptr) return false;
    out->insert(out->end(), buffer, end);
    return true;
}

std::string DNSHeader::toString() const {
std::string DNSHeader::toString() const {
    // TODO
    // TODO
    return std::string();
    return std::string();
@@ -446,55 +457,46 @@ DNSResponder::~DNSResponder() {


void DNSResponder::addMapping(const std::string& name, ns_type type, const std::string& addr) {
void DNSResponder::addMapping(const std::string& name, ns_type type, const std::string& addr) {
    std::lock_guard lock(mappings_mutex_);
    std::lock_guard lock(mappings_mutex_);
    // TODO: Consider using std::map::insert_or_assign().
    mappings_[{name, type}] = addr;
    auto it = mappings_.find(QueryKey(name, type));
    if (it != mappings_.end()) {
        LOG(INFO) << "Overwriting mapping for (" << name << ", " << dnstype2str(type)
                  << "), previous address " << it->second << " new address " << addr;
        it->second = addr;
        return;
    }
    mappings_.try_emplace({name, type}, addr);
}
}


void DNSResponder::addMappingDnsHeader(const std::string& name, ns_type type,
void DNSResponder::addMappingDnsHeader(const std::string& name, ns_type type,
                                       const DNSHeader& header) {
                                       const DNSHeader& header) {
    std::lock_guard lock(mappings_mutex_);
    std::lock_guard lock(mappings_mutex_);
    // TODO: Consider using std::map::insert_or_assign().
    dnsheader_mappings_[{name, type}] = header;
    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::addMappingBinaryPacket(const std::vector<uint8_t>& query,
                                          const std::vector<uint8_t>& response) {
    std::lock_guard lock(mappings_mutex_);
    packet_mappings_[query] = response;
}
}


void DNSResponder::removeMapping(const std::string& name, ns_type type) {
void DNSResponder::removeMapping(const std::string& name, ns_type type) {
    std::lock_guard lock(mappings_mutex_);
    std::lock_guard lock(mappings_mutex_);
    auto it = mappings_.find(QueryKey(name, type));
    if (!mappings_.erase({name, type})) {
    if (it != mappings_.end()) {
        mappings_.erase(it);
        return;
    }
        LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
        LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
                   << "), not present in registered mappings";
                   << "), not present in registered mappings";
    }
    }
}


void DNSResponder::removeMappingDnsHeader(const std::string& name, ns_type type) {
void DNSResponder::removeMappingDnsHeader(const std::string& name, ns_type type) {
    std::lock_guard lock(mappings_mutex_);
    std::lock_guard lock(mappings_mutex_);
    auto it = dnsheader_mappings_.find(QueryKey(name, type));
    if (!dnsheader_mappings_.erase({name, type})) {
    if (it != dnsheader_mappings_.end()) {
        dnsheader_mappings_.erase(it);
        return;
    }
        LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
        LOG(ERROR) << "Cannot remove mapping from (" << name << ", " << dnstype2str(type)
                   << "), not present in registered DnsHeader mappings";
                   << "), not present in registered DnsHeader mappings";
    }
    }
}

void DNSResponder::removeMappingBinaryPacket(const std::vector<uint8_t>& query) {
    std::lock_guard lock(mappings_mutex_);
    if (!packet_mappings_.erase(query)) {
        LOG(ERROR) << "Cannot remove mapping, not present in registered BinaryPacket mappings";
        LOG(INFO) << "Hex dump:";
        LOG(INFO) << android::netdutils::toHex(
                Slice(const_cast<uint8_t*>(query.data()), query.size()), 32);
    }
}


void DNSResponder::setResponseProbability(double response_probability) {
void DNSResponder::setResponseProbability(double response_probability) {
    response_probability_ = response_probability;
    response_probability_ = response_probability;
@@ -706,6 +708,8 @@ bool DNSResponder::handleDNSRequest(const char* buffer, ssize_t len, char* respo
    switch (mapping_type_) {
    switch (mapping_type_) {
        case MappingType::DNS_HEADER:
        case MappingType::DNS_HEADER:
            return makeResponseFromDnsHeader(&header, response, response_len);
            return makeResponseFromDnsHeader(&header, response, response_len);
        case MappingType::BINARY_PACKET:
            return makeResponseFromBinaryPacket(&header, response, response_len);
        case MappingType::ADDRESS_OR_HOSTNAME:
        case MappingType::ADDRESS_OR_HOSTNAME:
        default:
        default:
            return makeResponse(&header, response, response_len);
            return makeResponse(&header, response, response_len);
@@ -808,6 +812,16 @@ bool DNSResponder::fillAnswerRdata(const std::string& rdatastr, DNSRecord& recor
    return true;
    return true;
}
}


bool DNSResponder::writePacket(const DNSHeader* header, char* response,
                               size_t* response_len) const {
    char* response_cur = header->write(response, response + *response_len);
    if (response_cur == nullptr) {
        return false;
    }
    *response_len = response_cur - response;
    return true;
}

bool DNSResponder::makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
bool DNSResponder::makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
                                     size_t* response_len) const {
                                     size_t* response_len) const {
    header->answers.clear();
    header->answers.clear();
@@ -815,10 +829,7 @@ bool DNSResponder::makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* re
    header->additionals.clear();
    header->additionals.clear();
    header->rcode = rcode;
    header->rcode = rcode;
    header->qr = true;
    header->qr = true;
    char* response_cur = header->write(response, response + *response_len);
    return writePacket(header, response, response_len);
    if (response_cur == nullptr) return false;
    *response_len = response_cur - response;
    return true;
}
}


bool DNSResponder::makeResponse(DNSHeader* header, char* response, size_t* response_len) const {
bool DNSResponder::makeResponse(DNSHeader* header, char* response, size_t* response_len) const {
@@ -832,14 +843,8 @@ bool DNSResponder::makeResponse(DNSHeader* header, char* response, size_t* respo
            return makeErrorResponse(header, ns_rcode::ns_r_servfail, response, response_len);
            return makeErrorResponse(header, ns_rcode::ns_r_servfail, response, response_len);
        }
        }
    }
    }

    header->qr = true;
    header->qr = true;
    char* response_cur = header->write(response, response + *response_len);
    return writePacket(header, response, response_len);
    if (response_cur == nullptr) {
        return false;
    }
    *response_len = response_cur - response;
    return true;
}
}


bool DNSResponder::makeResponseFromDnsHeader(DNSHeader* header, char* response,
bool DNSResponder::makeResponseFromDnsHeader(DNSHeader* header, char* response,
@@ -885,14 +890,46 @@ bool DNSResponder::makeResponseFromDnsHeader(DNSHeader* header, char* response,
        // a response.
        // a response.
        header->qr = true;
        header->qr = true;
    }
    }
    return writePacket(header, response, response_len);
}


    char* response_cur = header->write(response, response + *response_len);
bool DNSResponder::makeResponseFromBinaryPacket(DNSHeader* header, char* response,
    if (response_cur == nullptr) {
                                                size_t* response_len) const {
    std::lock_guard guard(mappings_mutex_);

    // Build a search key of mapping from the query.
    // TODO: Perhaps pass the query packet buffer directly from the caller.
    std::vector<uint8_t> queryKey;
    if (!header->write(&queryKey)) return false;
    // Clear ID field (byte 0-1) because it is not required by the mapping key.
    queryKey[0] = 0;
    queryKey[1] = 0;

    const auto it = packet_mappings_.find(queryKey);
    if (it != packet_mappings_.end()) {
        if (it->second.size() > *response_len) {
            LOG(ERROR) << "buffer overflow on line " << __LINE__;
            return false;
            return false;
    }
        } else {
    *response_len = response_cur - response;
            std::copy(it->second.begin(), it->second.end(), response);
            // Leave the "RD" flag assignment for testing. The "RD" flag of the response keep
            // using the one from the raw packet mapping but the received query.
            // Assign "ID" field from query to response. See RFC 1035 section 4.1.1.
            reinterpret_cast<uint16_t*>(response)[0] = htons(header->id);  // bytes 0-1: id
            *response_len = it->second.size();
            return true;
            return true;
        }
        }
    } else {
        // TODO: handle correctly. See also TODO in addAnswerRecords().
        // TODO: Perhaps dump packet content to indicate which query failed.
        LOG(INFO) << "no mapping found, couldn't build a response from BinaryPacket 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;
        return writePacket(header, response, response_len);
    }
}


void DNSResponder::setDeferredResp(bool deferred_resp) {
void DNSResponder::setDeferredResp(bool deferred_resp) {
    std::lock_guard<std::mutex> guard(cv_mutex_for_deferred_resp_);
    std::lock_guard<std::mutex> guard(cv_mutex_for_deferred_resp_);
@@ -945,6 +982,8 @@ void DNSResponder::handleQuery() {
            PLOG(INFO) << "sendto() failed for " << host_str;
            PLOG(INFO) << "sendto() failed for " << host_str;
        }
        }
        // Test that the response is actually a correct DNS message.
        // Test that the response is actually a correct DNS message.
        // TODO: Make DNS message test to support name compression. Or it throws a warning for
        // a valid DNS message with name compression while the raw packet mapping is used.
        const char* response_end = response + len;
        const char* response_end = response + len;
        DNSHeader header;
        DNSHeader header;
        const char* cur = header.read(response, response_end);
        const char* cur = header.read(response, response_end);
+37 −7
Original line number Original line Diff line number Diff line
@@ -94,6 +94,7 @@ struct DNSHeader {
    std::vector<DNSRecord> additionals;
    std::vector<DNSRecord> additionals;
    const char* read(const char* buffer, const char* buffer_end);
    const char* read(const char* buffer, const char* buffer_end);
    char* write(char* buffer, const char* buffer_end) const;
    char* write(char* buffer, const char* buffer_end) const;
    bool write(std::vector<uint8_t>* out) const;
    std::string toString() const;
    std::string toString() const;


  private:
  private:
@@ -129,13 +130,13 @@ class DNSResponder {
        DROP              // DNS server not supporting EDNS will not do any response.
        DROP              // DNS server not supporting EDNS will not do any response.
    };
    };
    // Indicate which mapping the DNS server used to build the response.
    // Indicate which mapping the DNS server used to build the response.
    // See also addMapping(), addMappingDnsHeader(), removeMapping(), removeMappingDnsHeader(),
    // See also addMapping{, DnsHeader, BinaryPacket}, removeMapping{, DnsHeader, BinaryPacket},
    // makeResponse(), makeResponseFromDnsHeader().
    // makeResponse{, FromDnsHeader, FromBinaryPacket}.
    // TODO: Perhaps break class DNSResponder for each mapping.
    // 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 {
    enum class MappingType : uint8_t {
        ADDRESS_OR_HOSTNAME,  // Use the mapping from (name, type) to (address or hostname)
        ADDRESS_OR_HOSTNAME,  // Use the mapping from (name, type) to (address or hostname)
        DNS_HEADER,           // Use the mapping from (name, type) to (DNSHeader)
        DNS_HEADER,           // Use the mapping from (name, type) to (DNSHeader)
        BINARY_PACKET,        // Use the mapping from (query packet) to (response packet)
    };
    };


    DNSResponder(std::string listen_address = kDefaultListenAddr,
    DNSResponder(std::string listen_address = kDefaultListenAddr,
@@ -152,11 +153,14 @@ class DNSResponder {


    ~DNSResponder();
    ~DNSResponder();


    // Functions used for accessing mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER}.
    // Functions used for accessing mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER, BINARY_PACKET}.
    void addMapping(const std::string& name, ns_type type, const std::string& addr);
    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 addMappingDnsHeader(const std::string& name, ns_type type, const DNSHeader& header);
    void addMappingBinaryPacket(const std::vector<uint8_t>& query,
                                const std::vector<uint8_t>& response);
    void removeMapping(const std::string& name, ns_type type);
    void removeMapping(const std::string& name, ns_type type);
    void removeMappingDnsHeader(const std::string& name, ns_type type);
    void removeMappingDnsHeader(const std::string& name, ns_type type);
    void removeMappingBinaryPacket(const std::vector<uint8_t>& query);


    void setResponseProbability(double response_probability);
    void setResponseProbability(double response_probability);
    void setEdns(Edns edns);
    void setEdns(Edns edns);
@@ -173,6 +177,9 @@ class DNSResponder {
    void setDeferredResp(bool deferred_resp);
    void setDeferredResp(bool deferred_resp);
    static bool fillAnswerRdata(const std::string& rdatastr, DNSRecord& record);
    static bool fillAnswerRdata(const std::string& rdatastr, DNSRecord& record);


    // TODO: Make DNSResponder record unknown queries in a vector for improving the debugging.
    // Unit test could dump the unexpected query for further debug if any unexpected failure.

  private:
  private:
    // Key used for accessing mappings.
    // Key used for accessing mappings.
    struct QueryKey {
    struct QueryKey {
@@ -194,6 +201,21 @@ class DNSResponder {
        }
        }
    };
    };


    // Used for generating combined hash value of a vector.
    // std::hash<T> doesn't provide a specialization for std::vector<T>.
    struct QueryKeyVectorHash {
        std::size_t operator()(const std::vector<uint8_t>& v) const {
            std::size_t combined = 0;
            for (const uint8_t i : v) {
                // Hash combination comes from boost::hash_combine
                // See also system/extras/simpleperf/utils.h
                combined ^=
                        std::hash<uint8_t>{}(i) + 0x9e3779b9 + (combined << 6) + (combined >> 2);
            }
            return combined;
        }
    };

    void requestHandler();
    void requestHandler();


    // Parses and generates a response message for incoming DNS requests.
    // Parses and generates a response message for incoming DNS requests.
@@ -206,13 +228,18 @@ class DNSResponder {


    bool generateErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
    bool generateErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
                               size_t* response_len) const;
                               size_t* response_len) const;
    // TODO: Change makeErrorResponse and makeResponse{, FromDnsHeader} to use C++ containers

    // instead of the unsafe pointer + length buffer.
    // TODO: Change writePacket, makeErrorResponse and
    // makeResponse{, FromDnsHeader, FromBinaryPacket} to use C++ containers instead of the unsafe
    // pointer + length buffer.
    bool writePacket(const DNSHeader* header, char* response, size_t* response_len) const;
    bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
    bool makeErrorResponse(DNSHeader* header, ns_rcode rcode, char* response,
                           size_t* response_len) const;
                           size_t* response_len) const;
    // Build a response from mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER}.
    // Build a response from mapping {ADDRESS_OR_HOSTNAME, DNS_HEADER, BINARY_PACKET}.
    bool makeResponse(DNSHeader* header, char* response, size_t* response_len) const;
    bool makeResponse(DNSHeader* header, char* response, size_t* response_len) const;
    bool makeResponseFromDnsHeader(DNSHeader* header, char* response, size_t* response_len) const;
    bool makeResponseFromDnsHeader(DNSHeader* header, char* response, size_t* response_len) const;
    bool makeResponseFromBinaryPacket(DNSHeader* header, char* response,
                                      size_t* response_len) const;


    // Add a new file descriptor to be polled by the handler thread.
    // Add a new file descriptor to be polled by the handler thread.
    bool addFd(int fd, uint32_t events);
    bool addFd(int fd, uint32_t events);
@@ -251,9 +278,12 @@ class DNSResponder {
    // decides which mapping is used. See also makeResponse{, FromDnsHeader}.
    // decides which mapping is used. See also makeResponse{, FromDnsHeader}.
    // - mappings_: Mapping from (name, type) to (address or hostname).
    // - mappings_: Mapping from (name, type) to (address or hostname).
    // - dnsheader_mappings_: Mapping from (name, type) to (DNSHeader).
    // - dnsheader_mappings_: Mapping from (name, type) to (DNSHeader).
    // - packet_mappings_: Mapping from (query packet) to (response packet).
    std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_ GUARDED_BY(mappings_mutex_);
    std::unordered_map<QueryKey, std::string, QueryKeyHash> mappings_ GUARDED_BY(mappings_mutex_);
    std::unordered_map<QueryKey, DNSHeader, QueryKeyHash> dnsheader_mappings_
    std::unordered_map<QueryKey, DNSHeader, QueryKeyHash> dnsheader_mappings_
            GUARDED_BY(mappings_mutex_);
            GUARDED_BY(mappings_mutex_);
    std::unordered_map<std::vector<uint8_t>, std::vector<uint8_t>, QueryKeyVectorHash>
            packet_mappings_ GUARDED_BY(mappings_mutex_);


    mutable std::mutex mappings_mutex_;
    mutable std::mutex mappings_mutex_;
    // Query names received so far and the corresponding mutex.
    // Query names received so far and the corresponding mutex.
+187 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#define LOG_TAG "resolv_gold_test"

#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>

#include "dns_responder/dns_responder.h"
#include "getaddrinfo.h"
#include "resolv_cache.h"
#include "resolv_test_utils.h"

namespace android {
namespace net {
using android::net::NetworkDnsEventReported;
using android::netdutils::ScopedAddrinfo;

static const std::vector<uint8_t> kHelloExampleComQueryV4 = {
        /* Header */
        0x00, 0x00, /* Transaction ID: 0x0000 */
        0x01, 0x00, /* Flags: rd */
        0x00, 0x01, /* Questions: 1 */
        0x00, 0x00, /* Answer RRs: 0 */
        0x00, 0x00, /* Authority RRs: 0 */
        0x00, 0x00, /* Additional RRs: 0 */
        /* Queries */
        0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
        0x63, 0x6f, 0x6d, 0x00, /* Name: hello.example.com */
        0x00, 0x01,             /* Type: A */
        0x00, 0x01              /* Class: IN */
};

static const std::vector<uint8_t> kHelloExampleComResponseV4 = {
        /* Header */
        0x00, 0x00, /* Transaction ID: 0x0000 */
        0x81, 0x80, /* Flags: qr rd ra */
        0x00, 0x01, /* Questions: 1 */
        0x00, 0x01, /* Answer RRs: 1 */
        0x00, 0x00, /* Authority RRs: 0 */
        0x00, 0x00, /* Additional RRs: 0 */
        /* Queries */
        0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
        0x63, 0x6f, 0x6d, 0x00, /* Name: hello.example.com */
        0x00, 0x01,             /* Type: A */
        0x00, 0x01,             /* Class: IN */
        /* Answers */
        0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03,
        0x63, 0x6f, 0x6d, 0x00, /* Name: hello.example.com */
        0x00, 0x01,             /* Type: A */
        0x00, 0x01,             /* Class: IN */
        0x00, 0x00, 0x00, 0x00, /* Time to live: 0 */
        0x00, 0x04,             /* Data length: 4 */
        0x01, 0x02, 0x03, 0x04  /* Address: 1.2.3.4 */
};

class TestBase : public ::testing::Test {
  protected:
    void SetUp() override {
        // Create cache for test
        resolv_create_cache_for_net(TEST_NETID);
    }

    void TearDown() override {
        // Delete cache for test
        resolv_delete_cache_for_net(TEST_NETID);
    }

    int SetResolvers() {
        const std::vector<std::string> servers = {test::kDefaultListenAddr};
        const std::vector<std::string> domains = {"example.com"};
        return resolv_set_nameservers(TEST_NETID, servers, domains, kParams);
    }

    static constexpr res_params kParams = {
            .sample_validity = 300,
            .success_threshold = 25,
            .min_samples = 8,
            .max_samples = 8,
            .base_timeout_msec = 1000,
            .retry_count = 2,
    };

    static constexpr android_net_context kNetcontext = {
            .app_netid = TEST_NETID,
            .app_mark = MARK_UNSET,
            .dns_netid = TEST_NETID,
            .dns_mark = MARK_UNSET,
            .uid = NET_CONTEXT_INVALID_UID,
    };
};

class ResolvGetAddrInfo : public TestBase {};

TEST_F(ResolvGetAddrInfo, RemovePacketMapping) {
    test::DNSResponder dns(test::DNSResponder::MappingType::BINARY_PACKET);
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    dns.addMappingBinaryPacket(kHelloExampleComQueryV4, kHelloExampleComResponseV4);

    addrinfo* res = nullptr;
    const addrinfo hints = {.ai_family = AF_INET};
    NetworkDnsEventReported event;
    int rv = resolv_getaddrinfo(kHelloExampleCom, nullptr, &hints, &kNetcontext, &res, &event);
    ScopedAddrinfo result(res);
    ASSERT_NE(nullptr, result);
    ASSERT_EQ(0, rv);
    EXPECT_EQ("1.2.3.4", ToString(result));

    // Remove existing DNS record.
    dns.removeMappingBinaryPacket(kHelloExampleComQueryV4);

    // Expect to have no answer in DNS query result.
    rv = resolv_getaddrinfo(kHelloExampleCom, nullptr, &hints, &kNetcontext, &res, &event);
    result.reset(res);
    ASSERT_EQ(nullptr, result);
    ASSERT_EQ(EAI_NODATA, rv);
}

TEST_F(ResolvGetAddrInfo, ReplacePacketMapping) {
    test::DNSResponder dns(test::DNSResponder::MappingType::BINARY_PACKET);
    ASSERT_TRUE(dns.startServer());
    ASSERT_EQ(0, SetResolvers());

    // Register the record which uses IPv4 address 1.2.3.4.
    dns.addMappingBinaryPacket(kHelloExampleComQueryV4, kHelloExampleComResponseV4);

    // Expect that the DNS query returns IPv4 address 1.2.3.4.
    addrinfo* res = nullptr;
    const addrinfo hints = {.ai_family = AF_INET};
    NetworkDnsEventReported event;
    int rv = resolv_getaddrinfo(kHelloExampleCom, nullptr, &hints, &kNetcontext, &res, &event);
    ScopedAddrinfo result(res);
    ASSERT_NE(nullptr, result);
    ASSERT_EQ(0, rv);
    EXPECT_EQ("1.2.3.4", ToString(result));

    // Replace the registered record with a record which uses new IPv4 address 5.6.7.8.
    std::vector<uint8_t> newHelloExampleComResponseV4 = {
            /* Header */
            0x00, 0x00, /* Transaction ID: 0x0000 */
            0x81, 0x80, /* Flags: qr rd ra */
            0x00, 0x01, /* Questions: 1 */
            0x00, 0x01, /* Answer RRs: 1 */
            0x00, 0x00, /* Authority RRs: 0 */
            0x00, 0x00, /* Additional RRs: 0 */
            /* Queries */
            0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name: hello.example.com */
            0x00, 0x01,                   /* Type: A */
            0x00, 0x01,                   /* Class: IN */
            /* Answers */
            0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65,
            0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name: hello.example.com */
            0x00, 0x01,                   /* Type: A */
            0x00, 0x01,                   /* Class: IN */
            0x00, 0x00, 0x00, 0x00,       /* Time to live: 0 */
            0x00, 0x04,                   /* Data length: 4 */
            0x05, 0x06, 0x07, 0x08        /* Address: 5.6.7.8 */
    };
    dns.addMappingBinaryPacket(kHelloExampleComQueryV4, newHelloExampleComResponseV4);

    // Expect that DNS query returns new IPv4 address 5.6.7.8.
    rv = resolv_getaddrinfo(kHelloExampleCom, nullptr, &hints, &kNetcontext, &res, &event);
    result.reset(res);
    ASSERT_NE(nullptr, result);
    ASSERT_EQ(0, rv);
    EXPECT_EQ("5.6.7.8", ToString(result));
}

}  // end of namespace net
}  // end of namespace android