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);