Merge "DoH: Use DoH feature in T if the flag is unset"
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index f901831..4dfb4a2 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -1159,7 +1159,9 @@
     event.set_latency_micros(latencyUs);
     event.set_event_type(EVENT_GETHOSTBYNAME);
 
-    LOG(DEBUG) << "GetHostByNameHandler::run: result: " << gai_strerror(rv);
+    if (rv) {
+        LOG(DEBUG) << "GetHostByNameHandler::run: result failed: " << gai_strerror(rv);
+    }
 
     bool success = true;
     if (hp) {
@@ -1314,7 +1316,9 @@
     event.set_latency_micros(latencyUs);
     event.set_event_type(EVENT_GETHOSTBYADDR);
 
-    LOG(DEBUG) << "GetHostByAddrHandler::run: result: " << gai_strerror(rv);
+    if (rv) {
+        LOG(DEBUG) << "GetHostByAddrHandler::run: result failed: " << gai_strerror(rv);
+    }
 
     bool success = true;
     if (hp) {
diff --git a/DnsQueryLogTest.cpp b/DnsQueryLogTest.cpp
index b652412..d4616db 100644
--- a/DnsQueryLogTest.cpp
+++ b/DnsQueryLogTest.cpp
@@ -22,6 +22,7 @@
 #include <gtest/gtest.h>
 
 #include "DnsQueryLog.h"
+#include "tests/resolv_test_base.h"
 
 using namespace std::chrono_literals;
 
@@ -58,7 +59,7 @@
 
 }  // namespace
 
-class DnsQueryLogTest : public ::testing::Test {
+class DnsQueryLogTest : public ResolvTestBase {
   protected:
     const std::vector<std::string> serversV4 = {"127.0.0.1", "1.2.3.4"};
     const std::vector<std::string> serversV4V6 = {"127.0.0.1", "1.2.3.4", "2001:db8::1",
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
index e541b16..ee9ce4f 100644
--- a/DnsStatsTest.cpp
+++ b/DnsStatsTest.cpp
@@ -21,6 +21,7 @@
 #include <gtest/gtest.h>
 
 #include "DnsStats.h"
+#include "tests/resolv_test_base.h"
 
 namespace android::net {
 
@@ -66,7 +67,7 @@
 
 // TODO: add StatsDataTest to ensure its methods return correct outputs.
 
-class StatsRecordsTest : public ::testing::Test {};
+class StatsRecordsTest : public ResolvTestBase {};
 
 TEST_F(StatsRecordsTest, PushRecord) {
     const IPSockAddr server = IPSockAddr::toIPSockAddr("127.0.0.2", 53);
@@ -104,7 +105,7 @@
               makeStatsData(server, 3, 750ms, {{NS_R_NO_ERROR, 0}, {NS_R_TIMEOUT, 3}}));
 }
 
-class DnsStatsTest : public ::testing::Test {
+class DnsStatsTest : public ResolvTestBase {
   protected:
     std::string captureDumpOutput() {
         netdutils::DumpWriter dw(STDOUT_FILENO);
diff --git a/ExperimentsTest.cpp b/ExperimentsTest.cpp
index 9533788..159bec2 100644
--- a/ExperimentsTest.cpp
+++ b/ExperimentsTest.cpp
@@ -25,10 +25,11 @@
 #include <gtest/gtest.h>
 
 #include "Experiments.h"
+#include "tests/resolv_test_base.h"
 
 namespace android::net {
 
-class ExperimentsTest : public ::testing::Test {
+class ExperimentsTest : public ResolvTestBase {
   public:
     ExperimentsTest() : mExperiments(fakeGetExperimentFlagInt) {}
 
diff --git a/PrivateDnsConfigurationTest.cpp b/PrivateDnsConfigurationTest.cpp
index e2d6586..98ef53b 100644
--- a/PrivateDnsConfigurationTest.cpp
+++ b/PrivateDnsConfigurationTest.cpp
@@ -20,13 +20,14 @@
 #include "PrivateDnsConfiguration.h"
 #include "tests/dns_responder/dns_responder.h"
 #include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 
 namespace android::net {
 
 using namespace std::chrono_literals;
 
-class PrivateDnsConfigurationTest : public ::testing::Test {
+class PrivateDnsConfigurationTest : public ResolvTestBase {
   public:
     using ServerIdentity = PrivateDnsConfiguration::ServerIdentity;
 
diff --git a/apex/Android.bp b/apex/Android.bp
index 5b3e026..f487d38 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -24,7 +24,6 @@
 
 apex {
     name: "com.android.resolv",
-    updatable: true,
     manifest: "manifest.json",
     multilib: {
         first: {
@@ -41,14 +40,13 @@
     // Whether it actually will be compressed is controlled on per-device basis.
     compressible: true,
 
-    // IMPORTANT: For the APEX to be installed on Android 10,
-    // min_sdk_version should be 29. This enables the build system to make
+    // IMPORTANT:  q-launched-apex-module enables the build system to make
     // sure the package compatible to Android 10 in two ways:
     // - build the APEX package compatible to Android 10
     //   so that the package can be installed.
     // - build artifacts (lib/javalib/bin) against Android 10 SDK
     //   so that the artifacts can run.
-    min_sdk_version: "29",
+    defaults: ["q-launched-apex-module"],
 }
 
 apex_key {
diff --git a/doh/boot_time.rs b/doh/boot_time.rs
index 1f6f97d..ff7462c 100644
--- a/doh/boot_time.rs
+++ b/doh/boot_time.rs
@@ -123,6 +123,7 @@
     }
 
     fn set(&self, duration: Duration) {
+        assert_ne!(duration, Duration::from_millis(0));
         let timer = libc::itimerspec {
             it_interval: libc::timespec { tv_sec: 0, tv_nsec: 0 },
             it_value: libc::timespec {
@@ -147,6 +148,13 @@
     // Ideally, all timeouts in a runtime would share a timerfd. That will be much more
     // straightforwards to implement when moving this functionality into `tokio`.
 
+    // According to timerfd_settime(), setting zero duration will disarm the timer, so
+    // we return immediate timeout here.
+    // Can't use is_zero() for now because sc-mainline-prod's Rust version is below 1.53.
+    if duration == Duration::from_millis(0) {
+        return Err(Elapsed(()));
+    }
+
     // The failure conditions for this are rare (see `man 2 timerfd_create`) and the caller would
     // not be able to do much in response to them. When integrated into tokio, this would be called
     // during runtime setup.
@@ -204,3 +212,11 @@
         assert!(drift < Duration::from_millis(5));
     }
 }
+
+#[tokio::test]
+async fn timeout_duration_zero() {
+    let start = BootTime::now();
+    assert!(timeout(Duration::from_millis(0), pending::<()>()).await.is_err());
+    let taken = start.elapsed();
+    assert!(taken < Duration::from_millis(5));
+}
diff --git a/res_send.cpp b/res_send.cpp
index 58ba226..70837d6 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -483,17 +483,15 @@
         mDnsQueryEvent->set_linux_errno(static_cast<LinuxErrno>(terrno));
         resolv_stats_add(statp->netid, receivedMdnsAddr, mDnsQueryEvent);
 
-        if (resplen <= 0) {
-            _resolv_cache_query_failed(statp->netid, msg, flags);
-            return -terrno;
-        }
-        LOG(DEBUG) << __func__ << ": got answer:";
-        res_pquery(ans.first(resplen));
+        if (resplen > 0) {
+            LOG(DEBUG) << __func__ << ": got answer from mDNS:";
+            res_pquery(ans.first(resplen));
 
-        if (cache_status == RESOLV_CACHE_NOTFOUND) {
-            resolv_cache_add(statp->netid, msg, {ans.data(), resplen});
+            if (cache_status == RESOLV_CACHE_NOTFOUND) {
+                resolv_cache_add(statp->netid, msg, {ans.data(), resplen});
+            }
+            return resplen;
         }
-        return resplen;
     }
 
     if (statp->nameserverCount() == 0) {
diff --git a/tests/dnsresolver_binder_test.cpp b/tests/dnsresolver_binder_test.cpp
index 48fdd5e..20284d5 100644
--- a/tests/dnsresolver_binder_test.cpp
+++ b/tests/dnsresolver_binder_test.cpp
@@ -44,6 +44,7 @@
 #include "ResolverStats.h"
 #include "dns_responder.h"
 #include "dns_responder_client_ndk.h"
+#include "tests/resolv_test_base.h"
 
 using aidl::android::net::IDnsResolver;
 using aidl::android::net::ResolverHostsParcel;
@@ -92,7 +93,7 @@
 
 }  // namespace
 
-class DnsResolverBinderTest : public ::testing::Test {
+class DnsResolverBinderTest : public ResolvTestBase {
   public:
     DnsResolverBinderTest() {
         ndk::SpAIBinder resolvBinder = ndk::SpAIBinder(AServiceManager_getService("dnsresolver"));
diff --git a/tests/resolv_cache_unit_test.cpp b/tests/resolv_cache_unit_test.cpp
index 050964e..2011415 100644
--- a/tests/resolv_cache_unit_test.cpp
+++ b/tests/resolv_cache_unit_test.cpp
@@ -34,6 +34,7 @@
 #include "resolv_private.h"
 #include "stats.h"
 #include "tests/dns_responder/dns_responder.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 
 using namespace std::chrono_literals;
@@ -128,7 +129,7 @@
 
 }  // namespace
 
-class ResolvCacheTest : public ::testing::Test {
+class ResolvCacheTest : public ResolvTestBase {
   protected:
     static constexpr res_params kParams = {
             .sample_validity = 300,
diff --git a/tests/resolv_callback_unit_test.cpp b/tests/resolv_callback_unit_test.cpp
index 48e4937..2bc9d90 100644
--- a/tests/resolv_callback_unit_test.cpp
+++ b/tests/resolv_callback_unit_test.cpp
@@ -27,6 +27,7 @@
 #include "getaddrinfo.h"
 #include "resolv_cache.h"
 #include "resolv_private.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 
 namespace android::net {
@@ -101,7 +102,7 @@
     testUid = 0;
 }
 
-class CallbackTest : public ::testing::Test {
+class CallbackTest : public ResolvTestBase {
   protected:
     void SetUp() override {
         initDnsResolverCallbacks();
diff --git a/tests/resolv_gold_test.cpp b/tests/resolv_gold_test.cpp
index 7c79342..86a58be 100644
--- a/tests/resolv_gold_test.cpp
+++ b/tests/resolv_gold_test.cpp
@@ -35,6 +35,7 @@
 #include "tests/dns_responder/dns_responder_client_ndk.h"
 #include "tests/dns_responder/dns_tls_certificate.h"
 #include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_base.h"
 
 namespace android::net {
 
@@ -62,7 +63,7 @@
         "gethostbyname.tls.topsite.youtube.pb"};
 
 // Fixture test class definition.
-class TestBase : public ::testing::Test {
+class TestBase : public ResolvTestBase {
   protected:
     static void SetUpTestSuite() {
         // Unzip *.pb from pb.zip. The unzipped files get 777 permission by default. Remove execute
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index 34ba786..51144af 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -69,6 +69,7 @@
 #include "tests/dns_responder/dns_responder_client_ndk.h"
 #include "tests/dns_responder/dns_tls_certificate.h"
 #include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 #include "tests/tun_forwarder.h"
 #include "tests/unsolicited_listener/unsolicited_event_listener.h"
@@ -164,7 +165,7 @@
 
 }  // namespace
 
-class ResolverTest : public ::testing::Test {
+class ResolverTest : public ResolvTestBase {
   public:
     static void SetUpTestSuite() {
         // Get binder service.
@@ -6519,6 +6520,65 @@
     EXPECT_TRUE(result == nullptr);
 }
 
+// Test if .local resolution will try unicast when multicast is failed.
+TEST_F(ResolverTest, MdnsGetAddrInfo_fallback) {
+    constexpr char v6addr[] = "::1.2.3.4";
+    constexpr char v4addr[] = "1.2.3.4";
+    constexpr char host_name[] = "hello.local.";
+    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
+                              static_cast<ns_rcode>(-1));
+    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
+    // Set unresponsive on multicast.
+    mdnsv4.setResponseProbability(0.0);
+    mdnsv6.setResponseProbability(0.0);
+    ASSERT_TRUE(mdnsv4.startServer());
+    ASSERT_TRUE(mdnsv6.startServer());
+
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, v4addr},
+            {host_name, ns_type::ns_t_aaaa, v6addr},
+    };
+    test::DNSResponder dns("127.0.0.3");
+    StartDns(dns, records);
+    ASSERT_TRUE(mDnsClient.SetResolversForNetwork());
+
+    static const struct TestConfig {
+        int ai_family;
+        const std::vector<std::string> expected_addr;
+    } testConfigs[]{
+            {AF_INET, {v4addr}},
+            {AF_INET6, {v6addr}},
+            {AF_UNSPEC, {v4addr, v6addr}},
+    };
+
+    for (const auto& config : testConfigs) {
+        SCOPED_TRACE(fmt::format("family: {}", config.ai_family));
+        addrinfo hints = {.ai_family = config.ai_family, .ai_socktype = SOCK_DGRAM};
+        ScopedAddrinfo result = safe_getaddrinfo("hello.local", nullptr, &hints);
+        EXPECT_TRUE(result != nullptr);
+        if (config.ai_family == AF_INET) {
+            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
+            EXPECT_EQ(0U, GetNumQueries(mdnsv6, host_name));
+            EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+        } else if (config.ai_family == AF_INET6) {
+            EXPECT_EQ(0U, GetNumQueries(mdnsv4, host_name));
+            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
+            EXPECT_EQ(1U, GetNumQueries(dns, host_name));
+        } else {
+            EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
+            EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
+            EXPECT_EQ(2U, GetNumQueries(dns, host_name));
+        }
+        std::string result_str = ToString(result);
+        EXPECT_THAT(ToStrings(result), testing::UnorderedElementsAreArray(config.expected_addr));
+
+        mdnsv4.clearQueries();
+        mdnsv6.clearQueries();
+        dns.clearQueries();
+        ASSERT_TRUE(mDnsClient.resolvService()->flushNetworkCache(TEST_NETID).isOk());
+    }
+}
+
 // ResolverMultinetworkTest is used to verify multinetwork functionality. Here's how it works:
 // The resolver sends queries to address A, and then there will be a TunForwarder helping forward
 // the packets to address B, which is the address on which the testing server is listening. The
diff --git a/tests/resolv_private_dns_test.cpp b/tests/resolv_private_dns_test.cpp
index 9f218b2..bf19623 100644
--- a/tests/resolv_private_dns_test.cpp
+++ b/tests/resolv_private_dns_test.cpp
@@ -34,6 +34,7 @@
 #include "tests/dns_responder/dns_responder.h"
 #include "tests/dns_responder/dns_responder_client_ndk.h"
 #include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 #include "tests/unsolicited_listener/unsolicited_event_listener.h"
 
@@ -146,7 +147,7 @@
 
 // Base class to deal with netd binder service and resolver binder service.
 // TODO: derive ResolverTest from this base class.
-class BaseTest : public ::testing::Test {
+class BaseTest : public ResolvTestBase {
   public:
     static void SetUpTestSuite() {
         // Get binder service.
@@ -307,9 +308,9 @@
         BaseTest::TearDown();
     }
 
-    void sendQueryAndCheckResult() {
+    void sendQueryAndCheckResult(const char* host_name = kQueryHostname) {
         const addrinfo hints = {.ai_socktype = SOCK_DGRAM};
-        ScopedAddrinfo result = safe_getaddrinfo(kQueryHostname, nullptr, &hints);
+        ScopedAddrinfo result = safe_getaddrinfo(host_name, nullptr, &hints);
         EXPECT_THAT(ToStrings(result),
                     testing::ElementsAreArray({kQueryAnswerAAAA, kQueryAnswerA}));
     };
@@ -380,6 +381,12 @@
             ASSERT_TRUE(doh_backend.startServer());
             ASSERT_TRUE(doh.startServer());
         }
+        SetMdnsRoute();
+    }
+
+    void TearDown() override {
+        RemoveMdnsRoute();
+        BasePrivateDnsTest::TearDown();
     }
 
     bool testParamHasDot() { return GetParam() & kDotBit; }
@@ -437,6 +444,75 @@
     }
 }
 
+TEST_P(TransportParameterizedTest, MdnsGetAddrInfo_fallback) {
+    constexpr char host_name[] = "hello.local.";
+    test::DNSResponder mdnsv4("127.0.0.3", test::kDefaultMdnsListenService,
+                              static_cast<ns_rcode>(-1));
+    test::DNSResponder mdnsv6("::1", test::kDefaultMdnsListenService, static_cast<ns_rcode>(-1));
+    // Set unresponsive on multicast.
+    mdnsv4.setResponseProbability(0.0);
+    mdnsv6.setResponseProbability(0.0);
+    ASSERT_TRUE(mdnsv4.startServer());
+    ASSERT_TRUE(mdnsv6.startServer());
+
+    const std::vector<DnsRecord> records = {
+            {host_name, ns_type::ns_t_a, kQueryAnswerA},
+            {host_name, ns_type::ns_t_aaaa, kQueryAnswerAAAA},
+    };
+
+    for (const auto& r : records) {
+        dns.addMapping(r.host_name, r.type, r.addr);
+        dot_backend.addMapping(r.host_name, r.type, r.addr);
+        doh_backend.addMapping(r.host_name, r.type, r.addr);
+    }
+
+    auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
+    ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
+
+    if (testParamHasDoh()) EXPECT_TRUE(WaitForDohValidation(test::kDefaultListenAddr, true));
+    if (testParamHasDot()) EXPECT_TRUE(WaitForDotValidation(test::kDefaultListenAddr, true));
+
+    // This waiting time is expected to avoid that the DoH validation event interferes other tests.
+    if (!testParamHasDoh()) waitForDohValidationFailed();
+
+    // Have the test independent of the number of sent queries in private DNS validation, because
+    // the DnsResolver can send either 1 or 2 queries in DoT validation.
+    if (testParamHasDoh()) {
+        doh.clearQueries();
+    }
+    if (testParamHasDot()) {
+        EXPECT_TRUE(dot.waitForQueries(1));
+        dot.clearQueries();
+    }
+    dns.clearQueries();
+
+    EXPECT_NO_FAILURE(sendQueryAndCheckResult("hello.local"));
+    EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
+    EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
+    if (testParamHasDoh()) {
+        EXPECT_NO_FAILURE(expectQueries(0 /* dns */, 0 /* dot */, 2 /* doh */));
+    } else {
+        EXPECT_NO_FAILURE(expectQueries(0 /* dns */, 2 /* dot */, 0 /* doh */));
+    }
+
+    // Stop the private DNS servers. Since we are in opportunistic mode, queries will
+    // fall back to the cleartext nameserver.
+    flushCache();
+    dot.stopServer();
+    doh.stopServer();
+    mdnsv4.clearQueries();
+    mdnsv6.clearQueries();
+
+    EXPECT_NO_FAILURE(sendQueryAndCheckResult("hello.local"));
+    EXPECT_EQ(1U, GetNumQueries(mdnsv4, host_name));
+    EXPECT_EQ(1U, GetNumQueries(mdnsv6, host_name));
+    if (testParamHasDoh()) {
+        EXPECT_NO_FAILURE(expectQueries(2 /* dns */, 0 /* dot */, 2 /* doh */));
+    } else {
+        EXPECT_NO_FAILURE(expectQueries(2 /* dns */, 2 /* dot */, 0 /* doh */));
+    }
+}
+
 class PrivateDnsDohTest : public BasePrivateDnsTest {
   protected:
     void SetUp() override {
@@ -974,7 +1050,14 @@
 TEST_F(PrivateDnsDohTest, SessionResumption) {
     const int initial_max_idle_timeout_ms = 1000;
     for (const auto& flag : {"0", "1"}) {
+        SCOPED_TRACE(fmt::format("flag: {}", flag));
         auto sp = make_unique<ScopedSystemProperties>(kDohSessionResumptionFlag, flag);
+
+        // Each loop takes around 3 seconds, if the system property "doh" is reset in the middle
+        // of the first loop, this test will fail when running the second loop because DnsResolver
+        // updates its "doh" flag when resetNetwork() is called. Therefore, add another
+        // ScopedSystemProperties for "doh" to make the test more robust.
+        auto sp2 = make_unique<ScopedSystemProperties>(kDohFlag, "1");
         resetNetwork();
 
         ASSERT_TRUE(doh.stopServer());
diff --git a/tests/resolv_stress_test.cpp b/tests/resolv_stress_test.cpp
index 027fd46..947d662 100644
--- a/tests/resolv_stress_test.cpp
+++ b/tests/resolv_stress_test.cpp
@@ -28,10 +28,11 @@
 #include "dns_responder/dns_responder_client_ndk.h"
 #include "params.h"  // MAX_NS
 #include "resolv_test_utils.h"
+#include "tests/resolv_test_base.h"
 
 using android::net::ResolverStats;
 
-class ResolverStressTest : public ::testing::Test {
+class ResolverStressTest : public ResolvTestBase {
   public:
     ResolverStressTest() { mDnsClient.SetUp(); }
     ~ResolverStressTest() { mDnsClient.TearDown(); }
diff --git a/tests/resolv_test_base.h b/tests/resolv_test_base.h
new file mode 100644
index 0000000..29ae44e
--- /dev/null
+++ b/tests/resolv_test_base.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ *
+ */
+#pragma once
+
+#include <android-base/format.h>
+#include <android-base/logging.h>
+#include "gtest/gtest.h"
+
+using ::testing::TestInfo;
+using ::testing::UnitTest;
+
+#define DGB_TEST 1
+
+/*
+ * Test base class for resolv tests to support common usage.
+ */
+class ResolvTestBase : public ::testing::Test {
+  public:
+    // TODO: update the logging when gtest supports logging the life cycle on each test.
+    ResolvTestBase() {
+        if (DGB_TEST) LOG(INFO) << getTestCaseLog(true);
+    }
+    ~ResolvTestBase() {
+        if (DGB_TEST) LOG(INFO) << getTestCaseLog(false);
+    }
+
+    std::string getTestCaseLog(bool running) {
+        const TestInfo* const test_info = UnitTest::GetInstance()->current_test_info();
+        return fmt::format("{}: {}#{}", (running ? "started" : "finished"),
+                           test_info->test_suite_name(), test_info->name());
+    }
+};
diff --git a/tests/resolv_tls_unit_test.cpp b/tests/resolv_tls_unit_test.cpp
index 758976c..bd11930 100644
--- a/tests/resolv_tls_unit_test.cpp
+++ b/tests/resolv_tls_unit_test.cpp
@@ -37,6 +37,7 @@
 #include "IDnsTlsSocketFactory.h"
 #include "IDnsTlsSocketObserver.h"
 #include "tests/dns_responder/dns_tls_frontend.h"
+#include "tests/resolv_test_base.h"
 
 namespace android {
 namespace net {
@@ -57,7 +58,7 @@
 static const IPAddress V6ADDR2 = IPAddress::forString("2001:db8::2");
 
 // BaseTest just provides constants that are useful for the tests.
-class BaseTest : public ::testing::Test {
+class BaseTest : public ResolvTestBase {
   protected:
     BaseTest() {
         SERVER1.name = SERVERNAME1;
@@ -966,7 +967,7 @@
     EXPECT_FALSE(map.recordQuery(makeSlice(QUERY)));
 }
 
-class DnsTlsSocketTest : public ::testing::Test {
+class DnsTlsSocketTest : public ResolvTestBase {
   protected:
     class MockDnsTlsSocketObserver : public IDnsTlsSocketObserver {
       public:
diff --git a/tests/resolv_unit_test.cpp b/tests/resolv_unit_test.cpp
index ea8fa2f..dac60e8 100644
--- a/tests/resolv_unit_test.cpp
+++ b/tests/resolv_unit_test.cpp
@@ -31,6 +31,7 @@
 #include "gethnamaddr.h"
 #include "resolv_cache.h"
 #include "stats.pb.h"
+#include "tests/resolv_test_base.h"
 #include "tests/resolv_test_utils.h"
 
 #define NAME(variable) #variable
@@ -49,7 +50,7 @@
 // that any type or protocol can be returned by getaddrinfo().
 constexpr unsigned int ANY = 0;
 
-class TestBase : public ::testing::Test {
+class TestBase : public ResolvTestBase {
   protected:
     struct DnsMessage {
         std::string host_name;   // host name
@@ -1037,6 +1038,9 @@
     ASSERT_TRUE(mdnsv4.startServer());
     ASSERT_TRUE(mdnsv6.startServer());
     ASSERT_EQ(0, SetResolvers());
+    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
+    dns.setResponseProbability(0.0);
+    ASSERT_TRUE(dns.startServer());
 
     for (const auto& family : {AF_INET, AF_INET6, AF_UNSPEC}) {
         SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));
@@ -1825,6 +1829,9 @@
     ASSERT_TRUE(mdnsv4.startServer());
     ASSERT_TRUE(mdnsv6.startServer());
     ASSERT_EQ(0, SetResolvers());
+    test::DNSResponder dns("127.0.0.3", test::kDefaultListenService, static_cast<ns_rcode>(-1));
+    dns.setResponseProbability(0.0);
+    ASSERT_TRUE(dns.startServer());
 
     for (const auto& family : {AF_INET, AF_INET6}) {
         SCOPED_TRACE(fmt::format("family: {}, host_name: {}", family, host_name));