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

Commit e0be8f23 authored by android-build-team Robot's avatar android-build-team Robot Committed by Automerger Merge Worker
Browse files

Snap for 7339742 from 69ae86a8 to sc-release am: 1e7e1aa7

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/DnsResolver/+/14445464

Change-Id: I5a212d6708e7c8e3bc1c58329ed172aef1d3e228
parents ae787f5e 1e7e1aa7
Loading
Loading
Loading
Loading
+39 −6
Original line number Diff line number Diff line
@@ -182,10 +182,7 @@ DnsTlsTransport::Response DnsTlsDispatcher::query(const DnsTlsServer& server, un
    // stuck, this function also gets blocked.
    const int connectCounter = xport->transport.getConnectCounter();

    LOG(DEBUG) << "Sending query of length " << query.size();
    auto res = xport->transport.query(query);
    LOG(DEBUG) << "Awaiting response";
    const auto& result = res.get();
    const auto& result = queryInternal(*xport, query);
    *connectTriggered = (xport->transport.getConnectCounter() > connectCounter);

    DnsTlsTransport::Response code = result.code;
@@ -229,6 +226,34 @@ DnsTlsTransport::Response DnsTlsDispatcher::query(const DnsTlsServer& server, un
    return code;
}

DnsTlsTransport::Result DnsTlsDispatcher::queryInternal(Transport& xport,
                                                        const netdutils::Slice query) {
    LOG(DEBUG) << "Sending query of length " << query.size();

    // If dot_async_handshake is not set, the call might block in some cases; otherwise,
    // the call should return very soon.
    auto res = xport.transport.query(query);
    LOG(DEBUG) << "Awaiting response";

    if (xport.timeout().count() == -1) {
        // Infinite timeout.
        return res.get();
    }

    const auto status = res.wait_for(xport.timeout());
    if (status == std::future_status::timeout) {
        // TODO: notify the Transport to remove the query because no queries will await this future.
        // b/186613628.
        LOG(WARNING) << "DoT query timed out after " << xport.timeout().count() << " ms";
        return DnsTlsTransport::Result{
                .code = DnsTlsTransport::Response::network_error,
                .response = {},
        };
    }

    return res.get();
}

// This timeout effectively controls how long to keep SSL session tickets.
static constexpr std::chrono::minutes IDLE_TIMEOUT(5);
void DnsTlsDispatcher::cleanup(std::chrono::time_point<std::chrono::steady_clock> now) {
@@ -259,6 +284,7 @@ DnsTlsDispatcher::Transport* DnsTlsDispatcher::addTransport(const DnsTlsServer&
            instance->getFlag("dot_revalidation_threshold", Transport::kDotRevalidationThreshold);
    int unusableThr = instance->getFlag("dot_xport_unusable_threshold",
                                        Transport::kDotXportUnusableThreshold);
    int queryTimeout = instance->getFlag("dot_query_timeout_ms", Transport::kDotQueryTimeoutMs);

    // Check and adjust the parameters if they are improperly set.
    bool revalidationEnabled = false;
@@ -269,9 +295,16 @@ DnsTlsDispatcher::Transport* DnsTlsDispatcher::addTransport(const DnsTlsServer&
        triggerThr = -1;
        unusableThr = -1;
    }
    if (queryTimeout < 0) {
        queryTimeout = -1;
    } else if (queryTimeout < 1000) {
        queryTimeout = 1000;
    }

    ret = new Transport(server, mark, mFactory.get(), revalidationEnabled, triggerThr, unusableThr);
    LOG(DEBUG) << "Transport is initialized with { " << triggerThr << ", " << unusableThr << "}"
    ret = new Transport(server, mark, mFactory.get(), revalidationEnabled, triggerThr, unusableThr,
                        queryTimeout);
    LOG(DEBUG) << "Transport is initialized with { " << triggerThr << ", " << unusableThr << ", "
               << queryTimeout << "ms }"
               << " for server { " << server.toIpString() << "/" << server.name << " }";

    mStore[key].reset(ret);
+14 −2
Original line number Diff line number Diff line
@@ -80,11 +80,12 @@ class DnsTlsDispatcher : public PrivateDnsValidationObserver {
    // usage monitoring so we can expire idle sessions from the cache.
    struct Transport {
        Transport(const DnsTlsServer& server, unsigned mark, IDnsTlsSocketFactory* _Nonnull factory,
                  bool revalidationEnabled, int triggerThr, int unusableThr)
                  bool revalidationEnabled, int triggerThr, int unusableThr, int timeout)
            : transport(server, mark, factory),
              revalidationEnabled(revalidationEnabled),
              triggerThreshold(triggerThr),
              unusableThreshold(unusableThr) {}
              unusableThreshold(unusableThr),
              mTimeout(timeout) {}
        // DnsTlsTransport is thread-safe, so it doesn't need to be guarded.
        DnsTlsTransport transport;
        // This use counter and timestamp are used to ensure that only idle sessions are
@@ -99,8 +100,11 @@ class DnsTlsDispatcher : public PrivateDnsValidationObserver {

        bool checkRevalidationNecessary(DnsTlsTransport::Response code) REQUIRES(sLock);

        std::chrono::milliseconds timeout() const { return mTimeout; }

        static constexpr int kDotRevalidationThreshold = -1;
        static constexpr int kDotXportUnusableThreshold = -1;
        static constexpr int kDotQueryTimeoutMs = -1;

      private:
        // Used to track if this Transport is usable.
@@ -123,6 +127,11 @@ class DnsTlsDispatcher : public PrivateDnsValidationObserver {
        // takes effect when DoT revalidation is on. If the value is not a positive value, DoT
        // revalidation is disabled.
        const int unusableThreshold;

        // The time to await a future (the result of a DNS request) from the DnsTlsTransport
        // of this Transport.
        // To set an infinite timeout, assign the value to -1.
        const std::chrono::milliseconds mTimeout;
    };

    Transport* _Nullable addTransport(const DnsTlsServer& server, unsigned mark) REQUIRES(sLock);
@@ -136,6 +145,9 @@ class DnsTlsDispatcher : public PrivateDnsValidationObserver {
    // few minutes.
    std::chrono::time_point<std::chrono::steady_clock> mLastCleanup GUARDED_BY(sLock);

    DnsTlsTransport::Result queryInternal(Transport& transport, const netdutils::Slice query)
            EXCLUDES(sLock);

    // Drop any cache entries whose useCount is zero and which have not been used recently.
    // This function performs a linear scan of mStore.
    void cleanup(std::chrono::time_point<std::chrono::steady_clock> now) REQUIRES(sLock);
+4 −3
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ class Experiments {
            "keep_listening_udp",   "parallel_lookup_release",    "parallel_lookup_sleep_time",
            "sort_nameservers",     "dot_async_handshake",        "dot_connect_timeout_ms",
            "dot_maxtries",         "dot_revalidation_threshold", "dot_xport_unusable_threshold",
            "dot_query_timeout_ms",
    };
    // This value is used in updateInternal as the default value if any flags can't be found.
    static constexpr int kFlagIntDefault = INT_MIN;
+231 −20
Original line number Diff line number Diff line
@@ -83,6 +83,11 @@ const std::string kDotConnectTimeoutMsFlag(
        "persist.device_config.netd_native.dot_connect_timeout_ms");
const std::string kDotAsyncHandshakeFlag("persist.device_config.netd_native.dot_async_handshake");
const std::string kDotMaxretriesFlag("persist.device_config.netd_native.dot_maxtries");
const std::string kDotRevalidationThresholdFlag(
        "persist.device_config.netd_native.dot_revalidation_threshold");
const std::string kDotXportUnusableThresholdFlag(
        "persist.device_config.netd_native.dot_xport_unusable_threshold");
const std::string kDotQueryTimeoutMsFlag("persist.device_config.netd_native.dot_query_timeout_ms");

// Semi-public Bionic hook used by the NDK (frameworks/base/native/android/net.c)
// Tested here for convenience.
@@ -4384,7 +4389,7 @@ TEST_F(ResolverTest, ConnectTlsServerTimeout) {
        int maxRetries;

        // if asyncHandshake:
        //   expectedTimeout = dotConnectTimeoutMs * maxRetries
        //   expectedTimeout = Min(DotQueryTimeoutMs, dotConnectTimeoutMs * maxRetries)
        // otherwise:
        //   expectedTimeout = dotConnectTimeoutMs
        int expectedTimeout;
@@ -4408,11 +4413,13 @@ TEST_F(ResolverTest, ConnectTlsServerTimeout) {
        ASSERT_TRUE(tls.startServer());

        // The resolver will adjust the timeout value to 1000ms since the value is too small.
        ScopedSystemProperties scopedSystemProperties(kDotConnectTimeoutMsFlag, "100");
        ScopedSystemProperties scopedSystemProperties1(kDotAsyncHandshakeFlag,
                                                       config.asyncHandshake ? "1" : "0");
        ScopedSystemProperties scopedSystemProperties2(kDotMaxretriesFlag,
                                                       std::to_string(config.maxRetries));
        ScopedSystemProperties sp1(kDotConnectTimeoutMsFlag, "100");

        // Infinite timeout.
        ScopedSystemProperties sp2(kDotQueryTimeoutMsFlag, "-1");

        ScopedSystemProperties sp3(kDotAsyncHandshakeFlag, config.asyncHandshake ? "1" : "0");
        ScopedSystemProperties sp4(kDotMaxretriesFlag, std::to_string(config.maxRetries));
        resetNetwork();

        // Set up resolver to opportunistic mode.
@@ -4465,25 +4472,28 @@ TEST_F(ResolverTest, ConnectTlsServerTimeout_ConcurrentQueries) {
    const std::vector<DnsRecord> records = {
            {hostname, ns_type::ns_t_a, "1.2.3.4"},
    };
    int testConfigCount = 0;

    static const struct TestConfig {
        bool asyncHandshake;
        int dotConnectTimeoutMs;
        int dotQueryTimeoutMs;
        int maxRetries;
        int concurrency;

        // if asyncHandshake:
        //   expectedTimeout = dotConnectTimeoutMs * maxRetries
        //   expectedTimeout = Min(DotQueryTimeoutMs, dotConnectTimeoutMs * maxRetries)
        // otherwise:
        //   expectedTimeout = dotConnectTimeoutMs * concurrency
        int expectedTimeout;
    } testConfigs[] = {
            // clang-format off
            {false, 1000, 1, 5, 5000},
            {false, 1000, 3, 5, 5000},
            {true, 1000, 1, 5, 1000},
            {true, 2500, 1, 10, 2500},
            {true, 1000, 3, 5, 3000},
            {false, 1000, 3000, 1, 5,  5000},
            {false, 1000, 3000, 3, 5,  5000},
            {false, 2000, 1500, 3, 2,  4000},
            {true,  1000, 3000, 1, 5,  1000},
            {true,  2500, 1500, 1, 10, 1500},
            {true,  1000, 5000, 3, 5,  3000},
            // clang-format on
    };

@@ -4493,16 +4503,17 @@ TEST_F(ResolverTest, ConnectTlsServerTimeout_ConcurrentQueries) {
    // - when dot_async_handshake is enabled, only one handshake is triggered, and then
    //   all of the query threads time out at the same time.
    for (const auto& config : testConfigs) {
        ScopedSystemProperties scopedSystemProperties1(kDotConnectTimeoutMsFlag,
        testConfigCount++;
        ScopedSystemProperties sp1(kDotQueryTimeoutMsFlag,
                                   std::to_string(config.dotQueryTimeoutMs));
        ScopedSystemProperties sp2(kDotConnectTimeoutMsFlag,
                                   std::to_string(config.dotConnectTimeoutMs));
        ScopedSystemProperties scopedSystemProperties2(kDotAsyncHandshakeFlag,
                                                       config.asyncHandshake ? "1" : "0");
        ScopedSystemProperties scopedSystemProperties3(kDotMaxretriesFlag,
                                                       std::to_string(config.maxRetries));
        ScopedSystemProperties sp3(kDotAsyncHandshakeFlag, config.asyncHandshake ? "1" : "0");
        ScopedSystemProperties sp4(kDotMaxretriesFlag, std::to_string(config.maxRetries));
        resetNetwork();

        for (const auto& dnsMode : {"OPPORTUNISTIC", "STRICT"}) {
            SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", config.asyncHandshake, dnsMode));
            SCOPED_TRACE(fmt::format("testConfig: [{}, {}]", testConfigCount, dnsMode));

            // Because a DnsTlsTransport lasts at least 5 minutes in spite of network
            // destroyed, let the resolver creates an unique DnsTlsTransport every time
@@ -4546,8 +4557,208 @@ TEST_F(ResolverTest, ConnectTlsServerTimeout_ConcurrentQueries) {
            // Recover the server from being unresponsive and try again.
            tls.setHangOnHandshakeForTesting(false);
            int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
            if (dnsMode == "STRICT" && config.asyncHandshake &&
                config.dotQueryTimeoutMs < (config.dotConnectTimeoutMs * config.maxRetries)) {
                // In this case, the connection handshake is supposed to be in progress. Queries
                // sent before the handshake finishes will time out (either due to connect timeout
                // or query timeout).
                expectAnswersNotValid(fd, -ETIMEDOUT);
            } else {
                expectAnswersValid(fd, AF_INET, "1.2.3.4");
            }
        }
    }
}

TEST_F(ResolverTest, QueryTlsServerTimeout) {
    constexpr uint32_t cacheFlag = ANDROID_RESOLV_NO_CACHE_LOOKUP;
    constexpr int INFINITE_QUERY_TIMEOUT = -1;
    constexpr int DOT_SERVER_UNRESPONSIVE_TIME_MS = 5000;
    constexpr char hostname1[] = "query1.example.com.";
    constexpr char hostname2[] = "query2.example.com.";
    const std::vector<DnsRecord> records = {
            {hostname1, ns_type::ns_t_a, "1.2.3.4"},
            {hostname2, ns_type::ns_t_a, "1.2.3.5"},
    };

    for (const int queryTimeoutMs : {INFINITE_QUERY_TIMEOUT, 1000}) {
        for (const auto& dnsMode : {"OPPORTUNISTIC", "STRICT"}) {
            SCOPED_TRACE(fmt::format("testConfig: [{}] [{}]", dnsMode, queryTimeoutMs));

            const std::string addr = getUniqueIPv4Address();
            test::DNSResponder dns(addr);
            StartDns(dns, records);
            test::DnsTlsFrontend tls(addr, "853", addr, "53");
            ASSERT_TRUE(tls.startServer());

            ScopedSystemProperties sp(kDotQueryTimeoutMsFlag, std::to_string(queryTimeoutMs));
            resetNetwork();

            auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
            parcel.servers = {addr};
            parcel.tlsServers = {addr};
            if (dnsMode == "STRICT") parcel.tlsName = kDefaultPrivateDnsHostName;

            ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
            EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
            EXPECT_TRUE(tls.waitForQueries(1));
            tls.clearQueries();

            // Set the DoT server to be unresponsive to DNS queries until either it receives
            // 2 queries or 5s later.
            tls.setDelayQueries(2);
            tls.setDelayQueriesTimeout(DOT_SERVER_UNRESPONSIVE_TIME_MS);

            // First query.
            Stopwatch s;
            int fd = resNetworkQuery(TEST_NETID, hostname1, ns_c_in, ns_t_a, cacheFlag);
            if (dnsMode == "STRICT" && queryTimeoutMs != INFINITE_QUERY_TIMEOUT) {
                expectAnswersNotValid(fd, -ETIMEDOUT);
            } else {
                expectAnswersValid(fd, AF_INET, "1.2.3.4");
            }

            // Besides checking the result of the query, check how much time the
            // resolver processed the query.
            int timeTakenMs = s.getTimeAndResetUs() / 1000;
            const int expectedTimeTakenMs = (queryTimeoutMs == INFINITE_QUERY_TIMEOUT)
                                                    ? DOT_SERVER_UNRESPONSIVE_TIME_MS
                                                    : queryTimeoutMs;
            EXPECT_GE(timeTakenMs, expectedTimeTakenMs);
            EXPECT_LE(timeTakenMs, expectedTimeTakenMs + 1000);

            // Second query.
            tls.setDelayQueries(1);
            fd = resNetworkQuery(TEST_NETID, hostname2, ns_c_in, ns_t_a, cacheFlag);
            expectAnswersValid(fd, AF_INET, "1.2.3.5");

            // Also check how much time the resolver processed the query.
            timeTakenMs = s.timeTakenUs() / 1000;
            EXPECT_LE(timeTakenMs, 500);
            EXPECT_EQ(2, tls.queries());
        }
    }
}

// Verifies that the DnsResolver re-validates the DoT server when several DNS queries to
// the server fails in a row.
TEST_F(ResolverTest, TlsServerRevalidation) {
    constexpr uint32_t cacheFlag = ANDROID_RESOLV_NO_CACHE_LOOKUP;
    constexpr int dotXportUnusableThreshold = 10;
    constexpr int dotQueryTimeoutMs = 1000;
    constexpr char hostname[] = "hello.example.com.";
    const std::vector<DnsRecord> records = {
            {hostname, ns_type::ns_t_a, "1.2.3.4"},
    };

    static const struct TestConfig {
        std::string dnsMode;
        int validationThreshold;
        int queries;

        // Expected behavior in the DnsResolver.
        bool expectRevalidationHappen;
        bool expectDotUnusable;
    } testConfigs[] = {
            // clang-format off
            {"OPPORTUNISTIC", -1,  5, false, false},
            {"OPPORTUNISTIC", -1, 10, false, false},
            {"OPPORTUNISTIC",  5,  5,  true, false},
            {"OPPORTUNISTIC",  5, 10,  true,  true},
            {"STRICT",        -1,  5, false, false},
            {"STRICT",        -1, 10, false, false},
            {"STRICT",         5,  5, false, false},
            {"STRICT",         5, 10, false, false},
            // clang-format on
    };

    for (const auto& config : testConfigs) {
        SCOPED_TRACE(fmt::format("testConfig: [{}, {}, {}]", config.dnsMode,
                                 config.validationThreshold, config.queries));
        const int queries = config.queries;
        const int delayQueriesTimeout = dotQueryTimeoutMs + 1000;

        ScopedSystemProperties sp1(kDotRevalidationThresholdFlag,
                                   std::to_string(config.validationThreshold));
        ScopedSystemProperties sp2(kDotXportUnusableThresholdFlag,
                                   std::to_string(dotXportUnusableThreshold));
        ScopedSystemProperties sp3(kDotQueryTimeoutMsFlag, std::to_string(dotQueryTimeoutMs));
        resetNetwork();

        const std::string addr = getUniqueIPv4Address();
        test::DNSResponder dns(addr);
        StartDns(dns, records);
        test::DnsTlsFrontend tls(addr, "853", addr, "53");
        ASSERT_TRUE(tls.startServer());

        auto parcel = DnsResponderClient::GetDefaultResolverParamsParcel();
        parcel.servers = {addr};
        parcel.tlsServers = {addr};
        if (config.dnsMode == "STRICT") parcel.tlsName = kDefaultPrivateDnsHostName;
        ASSERT_TRUE(mDnsClient.SetResolversFromParcel(parcel));
        EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
        EXPECT_TRUE(tls.waitForQueries(1));
        tls.clearQueries();
        dns.clearQueries();

        // Expect the things happening in order:
        // 1. Configure the DoT server to postpone |queries + 1| DNS queries.
        // 2. Send |queries| DNS queries, they will time out in 1 second.
        // 3. 1 second later, the DoT server still waits for one more DNS query until
        //    |delayQueriesTimeout| times out.
        // 4. (opportunistic mode only) Meanwhile, DoT revalidation happens. The DnsResolver
        //    creates a new connection and sends a query to the DoT server.
        // 5. 1 second later, |delayQueriesTimeout| times out. The DoT server flushes all of the
        //    postponed DNS queries, and handles the query which comes from the revalidation.
        // 6. (opportunistic mode only) The revalidation succeeds.
        // 7. Send another DNS query, and expect it will succeed.
        // 8. (opportunistic mode only) If the DoT server has been deemed as unusable, the
        //    DnsResolver skips trying the DoT server.

        // Step 1.
        tls.setDelayQueries(queries + 1);
        tls.setDelayQueriesTimeout(delayQueriesTimeout);

        // Step 2.
        std::vector<std::thread> threads1(queries);
        for (std::thread& thread : threads1) {
            thread = std::thread([&]() {
                int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
                config.dnsMode == "STRICT" ? expectAnswersNotValid(fd, -ETIMEDOUT)
                                           : expectAnswersValid(fd, AF_INET, "1.2.3.4");
            });
        }

        // Step 3 and 4.
        for (std::thread& thread : threads1) {
            thread.join();
        }

        // Recover the config to make the revalidation can succeed.
        tls.setDelayQueries(1);

        // Step 5 and 6.
        int expectedDotQueries = queries;
        if (config.expectRevalidationHappen) {
            EXPECT_TRUE(WaitForPrivateDnsValidation(tls.listen_address(), true));
            expectedDotQueries++;
        }

        // Step 7 and 8.
        int fd = resNetworkQuery(TEST_NETID, hostname, ns_c_in, ns_t_a, cacheFlag);
        expectAnswersValid(fd, AF_INET, "1.2.3.4");
        expectedDotQueries++;

        const int expectedDo53Queries =
                expectedDotQueries + (config.dnsMode == "OPPORTUNISTIC" ? queries : 0);

        if (config.expectDotUnusable) {
            // A DoT server can be deemed as unusable only in opportunistic mode. When it happens,
            // the DnsResolver doesn't use the DoT server for a certain period of time.
            expectedDotQueries--;
        }
        EXPECT_EQ(dns.queries().size(), static_cast<unsigned>(expectedDo53Queries));
        EXPECT_EQ(tls.queries(), expectedDotQueries);
    }
}