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
diff --git a/DnsStats.cpp b/DnsStats.cpp
index ef236f3..671732c 100644
--- a/DnsStats.cpp
+++ b/DnsStats.cpp
@@ -81,7 +81,8 @@
 }
 
 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 @@
             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)
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
index f439a55..796d73c 100644
--- a/DnsStatsTest.cpp
+++ b/DnsStatsTest.cpp
@@ -142,7 +142,8 @@
 
             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();
@@ -237,10 +238,10 @@
 
 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", 0),    IPSockAddr::toIPSockAddr("fe80::1", 0),
+            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.
diff --git a/PrivateDnsCommon.h b/PrivateDnsCommon.h
index 2a92d30..919fc43 100644
--- a/PrivateDnsCommon.h
+++ b/PrivateDnsCommon.h
@@ -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.
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 54a4239..5c0894d 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -253,7 +253,7 @@
             .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 @@
     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 @@
         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 @@
     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 @@
     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 @@
     // 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 @@
             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) {
diff --git a/tests/resolv_private_dns_test.cpp b/tests/resolv_private_dns_test.cpp
index c5c07a5..4c14fe8 100644
--- a/tests/resolv_private_dns_test.cpp
+++ b/tests/resolv_private_dns_test.cpp
@@ -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 @@
 
 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 @@
                        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 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 @@
     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 @@
     // 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 @@
     // 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 @@
     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>", ""));
+}