Add Experiments to handle experiment flags logic

update experiment flags everytime when a resolver network is created or
destroyed.

Bug: 135717624
Bug: 151698212
Test: atest

Merged-In: I5375e78831c5994af74b9ecaca991f18db03eca6
Change-Id: I6288c1d47085cdb70ab5e6e19f183c289af56815
(cherry picked from commit fe85ad8052e0faaac88b0a14a9e78b2463507dd7)
diff --git a/Android.bp b/Android.bp
index 96a5a83..be4ef71 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,7 @@
         "DnsTlsServer.cpp",
         "DnsTlsSessionCache.cpp",
         "DnsTlsSocket.cpp",
+        "Experiments.cpp",
         "PrivateDnsConfiguration.cpp",
         "ResolverController.cpp",
         "ResolverEventReporter.cpp",
@@ -241,8 +242,9 @@
         "resolv_callback_unit_test.cpp",
         "resolv_tls_unit_test.cpp",
         "resolv_unit_test.cpp",
-        "DnsStatsTest.cpp",
         "DnsQueryLogTest.cpp",
+        "DnsStatsTest.cpp",
+        "ExperimentsTest.cpp",
     ],
     shared_libs: [
         "libcrypto",
diff --git a/DnsResolverService.cpp b/DnsResolverService.cpp
index 12ac191..8fd2012 100644
--- a/DnsResolverService.cpp
+++ b/DnsResolverService.cpp
@@ -30,6 +30,7 @@
 #include <private/android_filesystem_config.h>  // AID_SYSTEM
 
 #include "DnsResolver.h"
+#include "Experiments.h"
 #include "NetdPermissions.h"  // PERM_*
 #include "ResolverEventReporter.h"
 #include "resolv_cache.h"
@@ -115,7 +116,7 @@
         gDnsResolv->resolverCtrl.dump(dw, netId);
         dw.blankline();
     }
-
+    Experiments::getInstance()->dump(dw);
     return STATUS_OK;
 }
 
@@ -252,7 +253,7 @@
     ENFORCE_NETWORK_STACK_PERMISSIONS();
 
     gDnsResolv->resolverCtrl.destroyNetworkCache(netId);
-
+    Experiments::getInstance()->update();
     return ::ndk::ScopedAStatus(AStatus_newOk());
 }
 
@@ -261,7 +262,7 @@
     ENFORCE_NETWORK_STACK_PERMISSIONS();
 
     int res = gDnsResolv->resolverCtrl.createNetworkCache(netId);
-
+    Experiments::getInstance()->update();
     return statusFromErrcode(res);
 }
 
diff --git a/Experiments.cpp b/Experiments.cpp
new file mode 100644
index 0000000..79e8bd9
--- /dev/null
+++ b/Experiments.cpp
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2020, 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.
+ */
+
+#include "Experiments.h"
+
+#include <netdutils/DumpWriter.h>
+#include <string>
+
+#include "util.h"
+
+namespace android::net {
+
+using netdutils::DumpWriter;
+using netdutils::ScopedIndent;
+
+Experiments* Experiments::getInstance() {
+    // Instantiated on first use.
+    static Experiments instance{getExperimentFlagInt};
+    return &instance;
+}
+
+Experiments::Experiments(GetExperimentFlagIntFunction getExperimentFlagIntFunction)
+    : mGetExperimentFlagIntFunction(std::move(getExperimentFlagIntFunction)) {
+    updateInternal();
+}
+
+void Experiments::update() {
+    updateInternal();
+}
+
+void Experiments::dump(DumpWriter& dw) const {
+    std::lock_guard guard(mMutex);
+    dw.println("Experiments list: ");
+    for (const auto& [key, value] : mFlagsMapInt) {
+        ScopedIndent indentStats(dw);
+        dw.println("%.*s: %d", static_cast<int>(key.length()), key.data(), value);
+    }
+}
+
+void Experiments::updateInternal() {
+    std::lock_guard guard(mMutex);
+    for (const auto& key : kExperimentFlagKeyList) {
+        mFlagsMapInt[key] = mGetExperimentFlagIntFunction(key, 0);
+    }
+}
+
+int Experiments::getFlag(std::string_view key, int defaultValue) const {
+    std::lock_guard guard(mMutex);
+    auto it = mFlagsMapInt.find(key);
+    if (it != mFlagsMapInt.end()) {
+        return it->second;
+    }
+    return defaultValue;
+}
+
+}  // namespace android::net
diff --git a/Experiments.h b/Experiments.h
new file mode 100644
index 0000000..d8af673
--- /dev/null
+++ b/Experiments.h
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2020, 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 <mutex>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+#include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
+
+namespace android::net {
+
+// TODO: Add some way to update the stored experiment flags periodically.
+// TODO: Refactor this class and make things easier. (e.g. remove string map.)
+class Experiments {
+  public:
+    using GetExperimentFlagIntFunction = std::function<int(const std::string&, int)>;
+    static Experiments* getInstance();
+    int getFlag(std::string_view key, int defaultValue) const EXCLUDES(mMutex);
+    void update();
+    void dump(netdutils::DumpWriter& dw) const EXCLUDES(mMutex);
+
+    Experiments(Experiments const&) = delete;
+    void operator=(Experiments const&) = delete;
+
+  private:
+    explicit Experiments(GetExperimentFlagIntFunction getExperimentFlagIntFunction);
+    Experiments() = delete;
+    void updateInternal() EXCLUDES(mMutex);
+    mutable std::mutex mMutex;
+    std::unordered_map<std::string_view, int> mFlagsMapInt GUARDED_BY(mMutex);
+    // TODO: Migrate other experiment flags to here.
+    // (retry_count, retransmission_time_interval, dot_connect_timeout_ms)
+    static constexpr const char* const kExperimentFlagKeyList[] = {
+            "keep_listening_udp", "parallel_lookup", "parallel_lookup_sleep_time"};
+    // For testing.
+    friend class ExperimentsTest;
+    const GetExperimentFlagIntFunction mGetExperimentFlagIntFunction;
+};
+
+}  // namespace android::net
diff --git a/ExperimentsTest.cpp b/ExperimentsTest.cpp
new file mode 100644
index 0000000..47f6a7d
--- /dev/null
+++ b/ExperimentsTest.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#include <mutex>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
+#include <android-base/format.h>
+#include <android-base/stringprintf.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "Experiments.h"
+
+namespace android::net {
+
+class ExperimentsTest : public ::testing::Test {
+  public:
+    ExperimentsTest() : mExperiments(fakeGetExperimentFlagInt) {}
+
+  protected:
+    static int fakeGetExperimentFlagInt(const std::string& key, int defaultValue) {
+        auto it = sFakeFlagsMapInt.find(key);
+        if (it != sFakeFlagsMapInt.end()) {
+            return it->second;
+        }
+        return defaultValue;
+    }
+
+    void setupFakeMap(int value) {
+        for (const auto& key : Experiments::kExperimentFlagKeyList) {
+            sFakeFlagsMapInt[key] = value;
+        }
+    }
+
+    void setupExperimentsMap(int value) {
+        setupFakeMap(value);
+        std::lock_guard guard(mExperiments.mMutex);
+        mExperiments.mFlagsMapInt = sFakeFlagsMapInt;
+    }
+
+    void expectFlagsMapInt() {
+        std::lock_guard guard(mExperiments.mMutex);
+        EXPECT_THAT(mExperiments.mFlagsMapInt, ::testing::ContainerEq(sFakeFlagsMapInt));
+    }
+
+    void expectGetDnsExperimentFlagInt() {
+        std::unordered_map<std::string_view, int> tempMap;
+        for (const auto& key : Experiments::kExperimentFlagKeyList) {
+            tempMap[key] = mExperiments.getFlag(key, 0);
+        }
+        EXPECT_THAT(tempMap, ::testing::ContainerEq(sFakeFlagsMapInt));
+    }
+
+    void expectDumpOutput() {
+        netdutils::DumpWriter dw(STDOUT_FILENO);
+        CapturedStdout captured;
+        mExperiments.dump(dw);
+        const std::string dumpString = captured.str();
+        const std::string title = "Experiments list:";
+        EXPECT_EQ(dumpString.find(title), 0U);
+        size_t startPos = title.size();
+        std::lock_guard guard(mExperiments.mMutex);
+        for (const auto& [key, value] : mExperiments.mFlagsMapInt) {
+            std::string flagDump = fmt::format("{}: {}", key, value);
+            SCOPED_TRACE(flagDump);
+            size_t pos = dumpString.find(flagDump, startPos);
+            EXPECT_NE(pos, std::string::npos);
+            startPos = pos + flagDump.size();
+        }
+        EXPECT_EQ(startPos + 1, dumpString.size());
+        EXPECT_EQ(dumpString.substr(startPos), "\n");
+    }
+
+    static std::unordered_map<std::string_view, int> sFakeFlagsMapInt;
+    Experiments mExperiments;
+};
+
+std::unordered_map<std::string_view, int> ExperimentsTest::sFakeFlagsMapInt;
+
+TEST_F(ExperimentsTest, update) {
+    std::vector<int> testValues = {50, 3, 5, 0};
+    for (int testValue : testValues) {
+        setupFakeMap(testValue);
+        mExperiments.update();
+        expectFlagsMapInt();
+    }
+}
+
+TEST_F(ExperimentsTest, getDnsExperimentFlagInt) {
+    std::vector<int> testValues = {5, 1, 6, 0};
+    for (int testValue : testValues) {
+        setupExperimentsMap(testValue);
+        expectGetDnsExperimentFlagInt();
+    }
+}
+
+TEST_F(ExperimentsTest, dump) {
+    std::vector<int> testValues = {100, 37, 0, 30};
+    for (int testValue : testValues) {
+        setupFakeMap(testValue);
+        mExperiments.update();
+        expectDumpOutput();
+    }
+}
+
+}  // namespace android::net
diff --git a/getaddrinfo.cpp b/getaddrinfo.cpp
index 2d5d1b0..9a21203 100644
--- a/getaddrinfo.cpp
+++ b/getaddrinfo.cpp
@@ -57,6 +57,7 @@
 
 #include <android-base/logging.h>
 
+#include "Experiments.h"
 #include "netd_resolv/resolv.h"
 #include "res_comp.h"
 #include "res_debug.h"
@@ -1675,7 +1676,10 @@
     for (res_target* t = target; t; t = t->next) {
         results.emplace_back(std::async(std::launch::async, doQuery, name, t, res));
         // Avoiding gateways drop packets if queries are sent too close together
-        if (t->next) usleep(SLEEP_TIME_MS * 1000);
+        int sleepTime = android::net::Experiments::getInstance()->getFlag(
+                "parallel_lookup_sleep_time", SLEEP_TIME_MS);
+        if (sleepTime > 1000) sleepTime = 1000;
+        if (t->next) usleep(sleepTime * 1000);
     }
 
     int ancount = 0;
@@ -1701,7 +1705,8 @@
 }
 
 static int res_queryN_wrapper(const char* name, res_target* target, res_state res, int* herrno) {
-    const bool parallel_lookup = getExperimentFlagInt("parallel_lookup", 0);
+    const bool parallel_lookup =
+            android::net::Experiments::getInstance()->getFlag("parallel_lookup", 0);
     if (parallel_lookup) return res_queryN_parallel(name, target, res, herrno);
 
     return res_queryN(name, target, res, herrno);
diff --git a/res_cache.cpp b/res_cache.cpp
index 43c268e..0cd1f39 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -985,7 +985,6 @@
         cache = std::make_unique<Cache>();
         dns_event_subsampling_map = resolv_get_dns_event_subsampling_map();
     }
-
     int nameserverCount() { return nameserverSockAddrs.size(); }
 
     const unsigned netid;
diff --git a/res_send.cpp b/res_send.cpp
index beb7f13..910026a 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -102,6 +102,7 @@
 #include <netdutils/Stopwatch.h>
 #include "DnsTlsDispatcher.h"
 #include "DnsTlsTransport.h"
+#include "Experiments.h"
 #include "PrivateDnsConfiguration.h"
 #include "netd_resolv/resolv.h"
 #include "private/android_filesystem_config.h"
@@ -925,7 +926,8 @@
 
 static Result<std::vector<int>> udpRetryingPollWrapper(res_state statp, int ns,
                                                        const timespec* finish) {
-    const bool keepListeningUdp = getExperimentFlagInt("keep_listening_udp", 0);
+    const bool keepListeningUdp =
+            android::net::Experiments::getInstance()->getFlag("keep_listening_udp", 0);
     if (keepListeningUdp) return udpRetryingPoll(statp, finish);
 
     if (int n = retrying_poll(statp->nssocks[ns], POLLIN, finish); n <= 0) {
diff --git a/tests/resolv_integration_test.cpp b/tests/resolv_integration_test.cpp
index 7a53bc6..e5850fd 100644
--- a/tests/resolv_integration_test.cpp
+++ b/tests/resolv_integration_test.cpp
@@ -193,6 +193,11 @@
         mDnsClient.TearDown();
     }
 
+    void resetNetwork() {
+        mDnsClient.TearDown();
+        mDnsClient.SetupOemNetwork();
+    }
+
     void StartDns(test::DNSResponder& dns, const std::vector<DnsRecord>& records) {
         for (const auto& r : records) {
             dns.addMapping(r.host_name, r.type, r.addr);
@@ -4730,6 +4735,10 @@
     test::DNSResponder neverRespondDns(listen_addr2, "53", static_cast<ns_rcode>(-1));
     neverRespondDns.setResponseProbability(0.0);
     StartDns(neverRespondDns, records);
+    ScopedSystemProperties scopedSystemProperties(
+            "persist.device_config.netd_native.keep_listening_udp", "1");
+    // Re-setup test network to make experiment flag take effect.
+    resetNetwork();
 
     ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr1, listen_addr2},
                                                   kDefaultSearchDomains, params));
@@ -4740,9 +4749,7 @@
     // because |delayTimeMs| > DNS timeout.
     // Then it's the second try, resolver will send query to |neverRespondDns| and
     // listen on both servers. Resolver will receive the answer coming from |delayedDns|.
-    const std::string udpKeepListeningFlag("persist.device_config.netd_native.keep_listening_udp");
 
-    ScopedSystemProperties scopedSystemProperties(udpKeepListeningFlag, "1");
     test::DNSResponder delayedDns(listen_addr1);
     delayedDns.setResponseDelayMs(delayTimeMs);
     StartDns(delayedDns, records);
@@ -4770,13 +4777,14 @@
     test::DNSResponder neverRespondDns(listen_addr, "53", static_cast<ns_rcode>(-1));
     neverRespondDns.setResponseProbability(0.0);
     StartDns(neverRespondDns, records);
+    ScopedSystemProperties scopedSystemProperties(
+            "persist.device_config.netd_native.parallel_lookup", "1");
+    // Re-setup test network to make experiment flag take effect.
+    resetNetwork();
 
     ASSERT_TRUE(mDnsClient.SetResolversForNetwork({listen_addr}, kDefaultSearchDomains, params));
     neverRespondDns.clearQueries();
 
-    const std::string udpKeepListeningFlag("persist.device_config.netd_native.parallel_lookup");
-    ScopedSystemProperties scopedSystemProperties(udpKeepListeningFlag, "1");
-
     // Use a never respond DNS server to verify if the A/AAAA queries are sent in parallel.
     // The resolver parameters are set to timeout 1s and retry 1 times.
     // So we expect the safe_getaddrinfo_time_taken() might take ~1s to
diff --git a/util.h b/util.h
index 43b1376..d13a032 100644
--- a/util.h
+++ b/util.h
@@ -25,4 +25,5 @@
 socklen_t sockaddrSize(const sockaddr_storage& ss);
 
 // TODO: getExperimentFlagString
+// TODO: Migrate it to DnsResolverExperiments.cpp
 int getExperimentFlagInt(const std::string& flagName, int defaultValue);