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

Commit bc3be382 authored by waynema's avatar waynema
Browse files

Add the DoH test in resolv_private_dns_test.

This test checks whether DoH stats shows up in dumpsys correctly.

Bug: 201735918
Test: cd packages/modules/DnsResolver && atest
Change-Id: I9609198c895e7e574c8d297f67dc8e175a42e31d
parent 971821fb
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -81,7 +81,8 @@ int StatsData::averageLatencyMs() const {
}

std::string StatsData::toString() const {
    if (total == 0) return fmt::format("{} <no data>", sockAddr.ip().toString());
    if (total == 0)
        return fmt::format("{}:{} <no data>", sockAddr.ip().toString(), sockAddr.port());

    const auto now = std::chrono::steady_clock::now();
    const int lastUpdateSec = duration_cast<seconds>(now - lastUpdate).count();
@@ -91,8 +92,8 @@ std::string StatsData::toString() const {
            buf += fmt::format("{}:{} ", rcodeToName(rcode), counts);
        }
    }
    return fmt::format("{} ({}, {}ms, [{}], {}s)", sockAddr.ip().toString(), total,
                       averageLatencyMs(), buf, lastUpdateSec);
    return fmt::format("{}:{} ({}, {}ms, [{}], {}s)", sockAddr.ip().toString(), sockAddr.port(),
                       total, averageLatencyMs(), buf, lastUpdateSec);
}

StatsRecords::StatsRecords(const IPSockAddr& ipSockAddr, size_t size)
+6 −5
Original line number Diff line number Diff line
@@ -142,7 +142,8 @@ class DnsStatsTest : public ::testing::Test {

            for (const auto& stats : statsData) {
                ASSERT_TRUE(std::regex_search(*dumpString, sm, pattern));
                EXPECT_EQ(sm[1], stats.sockAddr.ip().toString());
                EXPECT_EQ(sm[1], stats.sockAddr.ip().toString() + ":" +
                                         std::to_string(stats.sockAddr.port()));
                EXPECT_FALSE(sm[2].str().empty());
                EXPECT_FALSE(sm[3].str().empty());
                *dumpString = sm.suffix();
@@ -238,9 +239,9 @@ TEST_F(DnsStatsTest, SetAddrs) {
TEST_F(DnsStatsTest, SetServersDifferentPorts) {
    const std::vector<IPSockAddr> servers = {
            IPSockAddr::toIPSockAddr("127.0.0.1", 0),    IPSockAddr::toIPSockAddr("fe80::1", 0),
            IPSockAddr::toIPSockAddr("127.0.0.1", 53),  IPSockAddr::toIPSockAddr("127.0.0.1", 5353),
            IPSockAddr::toIPSockAddr("127.0.0.1", 853), IPSockAddr::toIPSockAddr("fe80::1", 53),
            IPSockAddr::toIPSockAddr("fe80::1", 5353),  IPSockAddr::toIPSockAddr("fe80::1", 853),
            IPSockAddr::toIPSockAddr("127.0.0.1", 53),   IPSockAddr::toIPSockAddr("127.0.0.1", 853),
            IPSockAddr::toIPSockAddr("127.0.0.1", 5353), IPSockAddr::toIPSockAddr("fe80::1", 53),
            IPSockAddr::toIPSockAddr("fe80::1", 853),    IPSockAddr::toIPSockAddr("fe80::1", 5353),
    };

    // Servers setup fails due to port unset.
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@

namespace android::net {

static constexpr int kDohPort = 443;
static constexpr int kDotPort = 853;

enum class PrivateDnsTransport : uint8_t {
    kDot,  // DNS over TLS.
    kDoh,  // DNS over HTTPS.
+9 −8
Original line number Diff line number Diff line
@@ -253,7 +253,7 @@ void PrivateDnsConfiguration::sendPrivateDnsValidationEvent(const ServerIdentity
            .hostname = identity.provider,
            .validation = success ? IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_SUCCESS
                                  : IDnsResolverUnsolicitedEventListener::VALIDATION_RESULT_FAILURE,
            .protocol = (identity.sockaddr.port() == 853)
            .protocol = (identity.sockaddr.port() == kDotPort)
                                ? IDnsResolverUnsolicitedEventListener::PROTOCOL_DOT
                                : IDnsResolverUnsolicitedEventListener::PROTOCOL_DOH,
    };
@@ -391,7 +391,7 @@ base::Result<netdutils::IPSockAddr> PrivateDnsConfiguration::getDohServer(unsign
    std::lock_guard guard(mPrivateDnsLock);
    auto it = mDohTracker.find(netId);
    if (it != mDohTracker.end()) {
        return netdutils::IPSockAddr::toIPSockAddr(it->second.ipAddr, 443);
        return netdutils::IPSockAddr::toIPSockAddr(it->second.ipAddr, kDohPort);
    }

    return Errorf("Failed to get DoH Server: netId {} not found", netId);
@@ -473,11 +473,12 @@ int PrivateDnsConfiguration::setDoh(int32_t netId, uint32_t mark,
        const auto& [dohIt, _] = mDohTracker.insert_or_assign(netId, doh.value());
        const auto& dohId = dohIt->second;

        RecordEntry record(netId, {netdutils::IPSockAddr::toIPSockAddr(dohId.ipAddr, 443), name},
        RecordEntry record(netId,
                           {netdutils::IPSockAddr::toIPSockAddr(dohId.ipAddr, kDohPort), name},
                           dohId.status);
        mPrivateDnsLog.push(std::move(record));
        LOG(INFO) << __func__ << ": Upgrading server to DoH: " << name;
        resolv_stats_set_addrs(netId, PROTO_DOH, {dohId.ipAddr}, 443);
        resolv_stats_set_addrs(netId, PROTO_DOH, {dohId.ipAddr}, kDohPort);

        int probeTimeout = Experiments::getInstance()->getFlag("doh_probe_timeout_ms",
                                                               kDohProbeDefaultTimeoutMs);
@@ -497,7 +498,7 @@ void PrivateDnsConfiguration::clearDohLocked(unsigned netId) {
    LOG(DEBUG) << "PrivateDnsConfiguration::clearDohLocked (" << netId << ")";
    if (mDohDispatcher != nullptr) doh_net_delete(mDohDispatcher, netId);
    mDohTracker.erase(netId);
    resolv_stats_set_addrs(netId, PROTO_DOH, {}, 443);
    resolv_stats_set_addrs(netId, PROTO_DOH, {}, kDohPort);
}

void PrivateDnsConfiguration::clearDoh(unsigned netId) {
@@ -529,7 +530,7 @@ void PrivateDnsConfiguration::onDohStatusUpdate(uint32_t netId, bool success, co
    Validation status = success ? Validation::success : Validation::fail;
    it->second.status = status;
    // Send the events to registered listeners.
    ServerIdentity identity = {netdutils::IPSockAddr::toIPSockAddr(ipAddr, 443), host};
    ServerIdentity identity = {netdutils::IPSockAddr::toIPSockAddr(ipAddr, kDohPort), host};
    if (needReportEvent(netId, identity, success)) {
        sendPrivateDnsValidationEvent(identity, netId, success);
    }
@@ -546,7 +547,7 @@ bool PrivateDnsConfiguration::needReportEvent(uint32_t netId, ServerIdentity ide
    // the event.
    switch (identity.sockaddr.port()) {
        // DoH
        case 443: {
        case kDohPort: {
            auto netPair = mPrivateDnsTransports.find(netId);
            if (netPair == mPrivateDnsTransports.end()) return true;
            for (const auto& [id, server] : netPair->second) {
@@ -562,7 +563,7 @@ bool PrivateDnsConfiguration::needReportEvent(uint32_t netId, ServerIdentity ide
            break;
        }
        // DoT
        case 853: {
        case kDotPort: {
            auto it = mDohTracker.find(netId);
            if (it == mDohTracker.end()) return true;
            if (it->second == identity && it->second.status == Validation::success) {
+108 −9
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@

#define LOG_TAG "resolv_private_dns_test"

#include <regex>

#include <aidl/android/net/IDnsResolver.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
@@ -34,6 +37,7 @@
#include "tests/unsolicited_listener/unsolicited_event_listener.h"

using aidl::android::net::resolv::aidl::IDnsResolverUnsolicitedEventListener;
using android::base::ReadFdToString;
using android::base::unique_fd;
using android::net::resolv::aidl::UnsolicitedEventListener;
using android::netdutils::ScopedAddrinfo;
@@ -46,6 +50,33 @@ const std::string kDohProbeTimeoutFlag("persist.device_config.netd_native.doh_pr

namespace {

std::vector<std::string> dumpService(ndk::SpAIBinder binder) {
    unique_fd localFd, remoteFd;
    bool success = Pipe(&localFd, &remoteFd);
    EXPECT_TRUE(success) << "Failed to open pipe for dumping: " << strerror(errno);
    if (!success) return {};

    // dump() blocks until another thread has consumed all its output.
    std::thread dumpThread = std::thread([binder, remoteFd{std::move(remoteFd)}]() {
        EXPECT_EQ(STATUS_OK, AIBinder_dump(binder.get(), remoteFd, nullptr, 0));
    });

    std::string dumpContent;

    EXPECT_TRUE(ReadFdToString(localFd.get(), &dumpContent))
            << "Error during dump: " << strerror(errno);
    dumpThread.join();

    std::stringstream dumpStream(std::move(dumpContent));
    std::vector<std::string> lines;
    std::string line;
    while (std::getline(dumpStream, line)) {
        lines.push_back(std::move(line));
    }

    return lines;
}

// A helper which can propagate the failure to outside of the stmt to know which line
// of stmt fails. The expectation fails only for the first failed stmt.
#define EXPECT_NO_FAILURE(stmt)                                         \
@@ -140,6 +171,26 @@ class BaseTest : public ::testing::Test {
                       serverAddr, IDnsResolverUnsolicitedEventListener::PROTOCOL_DOH);
    }

    bool expectLog(const std::string& serverAddr, const std::string& listen_address) {
        ndk::SpAIBinder resolvBinder = ndk::SpAIBinder(AServiceManager_getService("dnsresolver"));
        assert(nullptr != resolvBinder.get());
        std::vector<std::string> lines = dumpService(resolvBinder);

        const std::string ipAddr =
                listen_address.empty() ? serverAddr : serverAddr + ":" + listen_address;
        const std::regex pattern(R"(^\s{4,}([0-9a-fA-F:\.]*)[ ]?([<(].*[>)])[ ]?(\S*)$)");

        for (const auto& line : lines) {
            if (line.empty()) continue;

            std::smatch match;
            if (std::regex_match(line, match, pattern)) {
                if (match[1] == ipAddr || match[2] == ipAddr) return true;
            }
        }
        return false;
    }

    DnsResponderClient mDnsClient;

    // Use a shared static DNS listener for all tests to avoid registering lots of listeners
@@ -162,6 +213,10 @@ class BaseTest : public ::testing::Test {

class BasePrivateDnsTest : public BaseTest {
  public:
    static constexpr char kDnsPort[] = "53";
    static constexpr char kDohPort[] = "443";
    static constexpr char kDotPort[] = "853";

    static void SetUpTestSuite() {
        BaseTest::SetUpTestSuite();
        test::DohFrontend::initRustAndroidLogger();
@@ -232,11 +287,11 @@ class BasePrivateDnsTest : public BaseTest {
    static constexpr char kQueryAnswerA[] = "1.2.3.4";
    static constexpr char kQueryAnswerAAAA[] = "2001:db8::100";

    test::DNSResponder dns{test::kDefaultListenAddr, "53"};
    test::DohFrontend doh{test::kDefaultListenAddr, "443", "127.0.1.3", "53"};
    test::DnsTlsFrontend dot{test::kDefaultListenAddr, "853", "127.0.2.3", "53"};
    test::DNSResponder doh_backend{"127.0.1.3", "53"};
    test::DNSResponder dot_backend{"127.0.2.3", "53"};
    test::DNSResponder dns{test::kDefaultListenAddr, kDnsPort};
    test::DohFrontend doh{test::kDefaultListenAddr, kDohPort, "127.0.1.3", kDnsPort};
    test::DnsTlsFrontend dot{test::kDefaultListenAddr, kDotPort, "127.0.2.3", kDnsPort};
    test::DNSResponder doh_backend{"127.0.1.3", kDnsPort};
    test::DNSResponder dot_backend{"127.0.2.3", kDnsPort};

    // Used to enable DoH during the tests and set up a shorter timeout.
    std::unique_ptr<ScopedSystemProperties> mDohScopedProp;
@@ -432,8 +487,8 @@ TEST_F(PrivateDnsDohTest, PreferIpv6) {
    // To simplify the test, set the DoT server broken.
    dot.stopServer();

    test::DNSResponder dns_ipv6{listen_ipv6_addr, "53"};
    test::DohFrontend doh_ipv6{listen_ipv6_addr, "443", listen_ipv6_addr, "53"};
    test::DNSResponder dns_ipv6{listen_ipv6_addr, kDnsPort};
    test::DohFrontend doh_ipv6{listen_ipv6_addr, kDohPort, listen_ipv6_addr, kDnsPort};
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_a, kQueryAnswerA);
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_aaaa, kQueryAnswerAAAA);
    ASSERT_TRUE(dns_ipv6.startServer());
@@ -469,8 +524,8 @@ TEST_F(PrivateDnsDohTest, ChangeAndClearPrivateDnsServer) {
    // To simplify the test, set the DoT server broken.
    dot.stopServer();

    test::DNSResponder dns_ipv6{listen_ipv6_addr, "53"};
    test::DohFrontend doh_ipv6{listen_ipv6_addr, "443", listen_ipv6_addr, "53"};
    test::DNSResponder dns_ipv6{listen_ipv6_addr, kDnsPort};
    test::DohFrontend doh_ipv6{listen_ipv6_addr, kDohPort, listen_ipv6_addr, kDnsPort};
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_a, kQueryAnswerA);
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_aaaa, kQueryAnswerAAAA);
    ASSERT_TRUE(dns_ipv6.startServer());
@@ -517,3 +572,47 @@ TEST_F(PrivateDnsDohTest, ChangeAndClearPrivateDnsServer) {
    EXPECT_EQ(doh_ipv6.queries(), 0);
    EXPECT_EQ(dns_ipv6.queries().size(), 2U);
}

TEST_F(PrivateDnsDohTest, ChangePrivateDnsServerAndVerifyOutput) {
    // To simplify the test, set the DoT server broken.
    dot.stopServer();

    static const std::string ipv4DohServerAddr = "127.0.0.3";
    static const std::string ipv6DohServerAddr = "::1";

    test::DNSResponder dns_ipv6{ipv6DohServerAddr, kDnsPort};
    test::DohFrontend doh_ipv6{ipv6DohServerAddr, kDohPort, ipv6DohServerAddr, kDnsPort};
    dns.addMapping(kQueryHostname, ns_type::ns_t_a, kQueryAnswerA);
    dns.addMapping(kQueryHostname, ns_type::ns_t_aaaa, kQueryAnswerAAAA);
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_a, kQueryAnswerA);
    dns_ipv6.addMapping(kQueryHostname, ns_type::ns_t_aaaa, kQueryAnswerAAAA);
    ASSERT_TRUE(dns_ipv6.startServer());
    ASSERT_TRUE(doh_ipv6.startServer());

    // Start the v4 DoH server.
    auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
    EXPECT_TRUE(WaitForDohValidation(test::kDefaultListenAddr, true));
    EXPECT_TRUE(expectLog(ipv4DohServerAddr, kDohPort));

    // Change to an invalid DoH server.
    parcel.tlsServers = {kHelloExampleComAddrV4};
    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
    EXPECT_FALSE(expectLog(kHelloExampleComAddrV4, kDohPort));
    EXPECT_TRUE(expectLog("<no data>", ""));

    // Change to the v6 DoH server.
    parcel.servers = {ipv6DohServerAddr};
    parcel.tlsServers = {ipv6DohServerAddr};
    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
    EXPECT_TRUE(WaitForDohValidation(ipv6DohServerAddr, true));
    EXPECT_TRUE(expectLog(ipv6DohServerAddr, kDohPort));
    EXPECT_FALSE(expectLog(ipv4DohServerAddr, kDohPort));

    // Remove the private DNS server.
    parcel.tlsServers = {};
    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
    EXPECT_FALSE(expectLog(ipv4DohServerAddr, kDohPort));
    EXPECT_FALSE(expectLog(ipv6DohServerAddr, kDohPort));
    EXPECT_TRUE(expectLog("<no data>", ""));
}