Modularize resolver statistics
am: e655b1d822
Change-Id: Ie03618f5ccb5dce3326844a41ee4b13fc5d1707a
diff --git a/Android.bp b/Android.bp
index bc2c0cd..625e7c8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -55,6 +55,7 @@
"DnsProxyListener.cpp",
"DnsResolver.cpp",
"DnsResolverService.cpp",
+ "DnsStats.cpp",
"DnsTlsDispatcher.cpp",
"DnsTlsQueryMap.cpp",
"DnsTlsTransport.cpp",
@@ -202,6 +203,7 @@
"resolv_cache_unit_test.cpp",
"resolv_tls_unit_test.cpp",
"resolv_unit_test.cpp",
+ "DnsStatsTest.cpp",
],
shared_libs: [
"libbase",
diff --git a/DnsStats.cpp b/DnsStats.cpp
new file mode 100644
index 0000000..970e30e
--- /dev/null
+++ b/DnsStats.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2019 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.
+ *
+ */
+
+#define LOG_TAG "resolv"
+
+#include "DnsStats.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+
+namespace android::net {
+
+using base::StringPrintf;
+using netdutils::DumpWriter;
+using netdutils::IPAddress;
+using netdutils::IPSockAddr;
+using netdutils::ScopedIndent;
+using std::chrono::duration_cast;
+using std::chrono::microseconds;
+using std::chrono::milliseconds;
+using std::chrono::seconds;
+
+namespace {
+
+static constexpr IPAddress INVALID_IPADDRESS = IPAddress();
+
+std::string rcodeToName(int rcode) {
+ // clang-format off
+ switch (rcode) {
+ case NS_R_NO_ERROR: return "NOERROR";
+ case NS_R_FORMERR: return "FORMERR";
+ case NS_R_SERVFAIL: return "SERVFAIL";
+ case NS_R_NXDOMAIN: return "NXDOMAIN";
+ case NS_R_NOTIMPL: return "NOTIMP";
+ case NS_R_REFUSED: return "REFUSED";
+ case NS_R_YXDOMAIN: return "YXDOMAIN";
+ case NS_R_YXRRSET: return "YXRRSET";
+ case NS_R_NXRRSET: return "NXRRSET";
+ case NS_R_NOTAUTH: return "NOTAUTH";
+ case NS_R_NOTZONE: return "NOTZONE";
+ case NS_R_INTERNAL_ERROR: return "INTERNAL_ERROR";
+ case NS_R_TIMEOUT: return "TIMEOUT";
+ default: return StringPrintf("UNKNOWN(%d)", rcode);
+ }
+ // clang-format on
+}
+
+bool ensureNoInvalidIp(const std::vector<IPSockAddr>& servers) {
+ for (const auto& server : servers) {
+ if (server.ip() == INVALID_IPADDRESS || server.port() == 0) {
+ LOG(WARNING) << "Invalid server: " << server;
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+// The comparison ignores the last update time.
+bool StatsData::operator==(const StatsData& o) const {
+ return std::tie(serverSockAddr, total, rcodeCounts, latencyUs) ==
+ std::tie(o.serverSockAddr, o.total, o.rcodeCounts, o.latencyUs);
+}
+
+std::string StatsData::toString() const {
+ if (total == 0) return StringPrintf("%s <no data>", serverSockAddr.ip().toString().c_str());
+
+ const auto now = std::chrono::steady_clock::now();
+ const int meanLatencyMs = duration_cast<milliseconds>(latencyUs).count() / total;
+ const int lastUpdateSec = duration_cast<seconds>(now - lastUpdate).count();
+ std::string buf;
+ for (const auto& [rcode, counts] : rcodeCounts) {
+ if (counts != 0) {
+ buf += StringPrintf("%s:%d ", rcodeToName(rcode).c_str(), counts);
+ }
+ }
+ return StringPrintf("%s (%d, %dms, [%s], %ds)", serverSockAddr.ip().toString().c_str(), total,
+ meanLatencyMs, buf.c_str(), lastUpdateSec);
+}
+
+StatsRecords::StatsRecords(const IPSockAddr& ipSockAddr, size_t size)
+ : mCapacity(size), mStatsData(ipSockAddr) {}
+
+void StatsRecords::push(const Record& record) {
+ updateStatsData(record, true);
+ mRecords.push_back(record);
+
+ if (mRecords.size() > mCapacity) {
+ updateStatsData(mRecords.front(), false);
+ mRecords.pop_front();
+ }
+}
+
+void StatsRecords::updateStatsData(const Record& record, const bool add) {
+ const int rcode = record.rcode;
+ if (add) {
+ mStatsData.total += 1;
+ mStatsData.rcodeCounts[rcode] += 1;
+ mStatsData.latencyUs += record.latencyUs;
+ } else {
+ mStatsData.total -= 1;
+ mStatsData.rcodeCounts[rcode] -= 1;
+ mStatsData.latencyUs -= record.latencyUs;
+ }
+ mStatsData.lastUpdate = std::chrono::steady_clock::now();
+}
+
+bool DnsStats::setServers(const std::vector<netdutils::IPSockAddr>& servers, Protocol protocol) {
+ if (!ensureNoInvalidIp(servers)) return false;
+
+ ServerStatsMap& statsMap = mStats[protocol];
+ for (const auto& server : servers) {
+ statsMap.try_emplace(server, StatsRecords(server, kLogSize));
+ }
+
+ // Clean up the map to eliminate the nodes not belonging to the given list of servers.
+ const auto cleanup = [&](ServerStatsMap* statsMap) {
+ ServerStatsMap tmp;
+ for (const auto& server : servers) {
+ if (statsMap->find(server) != statsMap->end()) {
+ tmp.insert(statsMap->extract(server));
+ }
+ }
+ statsMap->swap(tmp);
+ };
+
+ cleanup(&statsMap);
+
+ return true;
+}
+
+bool DnsStats::addStats(const IPSockAddr& ipSockAddr, const DnsQueryEvent& record) {
+ if (ipSockAddr.ip() == INVALID_IPADDRESS) return false;
+
+ for (auto& [serverSockAddr, statsRecords] : mStats[record.protocol()]) {
+ if (serverSockAddr == ipSockAddr) {
+ const StatsRecords::Record rec = {
+ .rcode = record.rcode(),
+ .latencyUs = microseconds(record.latency_micros()),
+ };
+ statsRecords.push(rec);
+ return true;
+ }
+ }
+ return false;
+}
+
+std::vector<StatsData> DnsStats::getStats(Protocol protocol) const {
+ std::vector<StatsData> ret;
+
+ if (mStats.find(protocol) != mStats.end()) {
+ for (const auto& [_, statsRecords] : mStats.at(protocol)) {
+ ret.push_back(statsRecords.getStatsData());
+ }
+ }
+ return ret;
+}
+
+void DnsStats::dump(DumpWriter& dw) {
+ const auto dumpStatsMap = [&](ServerStatsMap& statsMap) {
+ ScopedIndent indentLog(dw);
+ if (statsMap.size() == 0) {
+ dw.println("<no server>");
+ return;
+ }
+ for (const auto& [_, statsRecords] : statsMap) {
+ dw.println("%s", statsRecords.getStatsData().toString().c_str());
+ }
+ };
+
+ dw.println("Server statistics: (total, RTT avg, {rcode:counts}, last update)");
+ ScopedIndent indentStats(dw);
+
+ dw.println("over UDP");
+ dumpStatsMap(mStats[PROTO_UDP]);
+
+ dw.println("over TLS");
+ dumpStatsMap(mStats[PROTO_DOT]);
+
+ dw.println("over TCP");
+ dumpStatsMap(mStats[PROTO_TCP]);
+}
+
+} // namespace android::net
diff --git a/DnsStats.h b/DnsStats.h
new file mode 100644
index 0000000..526f468
--- /dev/null
+++ b/DnsStats.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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 <chrono>
+#include <deque>
+#include <map>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
+#include <netdutils/InternetAddresses.h>
+
+#include "ResolverStats.h"
+#include "stats.pb.h"
+
+namespace android::net {
+
+// The overall information of a StatsRecords.
+struct StatsData {
+ StatsData(const netdutils::IPSockAddr& ipSockAddr) : serverSockAddr(ipSockAddr) {
+ lastUpdate = std::chrono::steady_clock::now();
+ };
+
+ // Server socket address.
+ netdutils::IPSockAddr serverSockAddr;
+
+ // The most recent number of records being accumulated.
+ int total = 0;
+
+ // The map used to store the number of each rcode.
+ std::map<int, int> rcodeCounts;
+
+ // The aggregated RTT in microseconds.
+ // For DNS-over-TCP, it includes TCP handshake.
+ // For DNS-over-TLS, it might include TCP handshake plus SSL handshake.
+ std::chrono::microseconds latencyUs = {};
+
+ // The last update timestamp.
+ std::chrono::time_point<std::chrono::steady_clock> lastUpdate;
+
+ std::string toString() const;
+
+ // For testing.
+ bool operator==(const StatsData& o) const;
+ friend std::ostream& operator<<(std::ostream& os, const StatsData& data) {
+ return os << data.toString();
+ }
+};
+
+// A circular buffer based class used to store the statistics for a server with a protocol.
+class StatsRecords {
+ public:
+ struct Record {
+ int rcode;
+ std::chrono::microseconds latencyUs;
+ };
+
+ StatsRecords(const netdutils::IPSockAddr& ipSockAddr, size_t size);
+
+ void push(const Record& record);
+
+ const StatsData& getStatsData() const { return mStatsData; }
+
+ private:
+ void updateStatsData(const Record& record, const bool add);
+
+ std::deque<Record> mRecords;
+ size_t mCapacity;
+ StatsData mStatsData;
+};
+
+// DnsStats class manages the statistics of DNS servers per netId.
+// The class itself is not thread-safe.
+class DnsStats {
+ public:
+ using ServerStatsMap = std::map<netdutils::IPSockAddr, StatsRecords>;
+
+ // Add |servers| to the map, and remove no-longer-used servers.
+ // Return true if they are successfully added; otherwise, return false.
+ bool setServers(const std::vector<netdutils::IPSockAddr>& servers, Protocol protocol);
+
+ // Return true if |record| is successfully added into |server|'s stats; otherwise, return false.
+ bool addStats(const netdutils::IPSockAddr& server, const DnsQueryEvent& record);
+
+ void dump(netdutils::DumpWriter& dw);
+
+ // For testing.
+ std::vector<StatsData> getStats(Protocol protocol) const;
+
+ // TODO: Compatible support for getResolverInfo().
+ // TODO: Support getSortedServers().
+
+ private:
+ std::map<Protocol, ServerStatsMap> mStats;
+
+ static constexpr size_t kLogSize = 128;
+};
+
+} // namespace android::net
diff --git a/DnsStatsTest.cpp b/DnsStatsTest.cpp
new file mode 100644
index 0000000..525b611
--- /dev/null
+++ b/DnsStatsTest.cpp
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2019 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "DnsStats.h"
+
+namespace android::net {
+
+using namespace std::chrono_literals;
+using android::netdutils::IPSockAddr;
+using std::chrono::milliseconds;
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+DnsQueryEvent makeDnsQueryEvent(const Protocol protocol, const NsRcode rcode,
+ const milliseconds& latency) {
+ DnsQueryEvent event;
+ event.set_protocol(protocol);
+ event.set_rcode(rcode);
+ event.set_latency_micros(latency.count() * 1000);
+ return event;
+}
+
+StatsData makeStatsData(const IPSockAddr& server, const int total, const milliseconds& latencyMs,
+ const std::map<int, int>& rcodeCounts) {
+ StatsData ret(server);
+ ret.total = total;
+ ret.latencyUs = latencyMs;
+ ret.rcodeCounts = rcodeCounts;
+ return ret;
+}
+
+} // namespace
+
+class StatsRecordsTest : public ::testing::Test {};
+
+TEST_F(StatsRecordsTest, PushRecord) {
+ const IPSockAddr server = IPSockAddr::toIPSockAddr("127.0.0.2", 53);
+ constexpr size_t size = 3;
+ const StatsRecords::Record recordNoError = {NS_R_NO_ERROR, 10ms};
+ const StatsRecords::Record recordTimeout = {NS_R_TIMEOUT, 250ms};
+
+ StatsRecords sr(server, size);
+ EXPECT_EQ(sr.getStatsData(), makeStatsData(server, 0, 0ms, {}));
+
+ sr.push(recordNoError);
+ EXPECT_EQ(sr.getStatsData(), makeStatsData(server, 1, 10ms, {{NS_R_NO_ERROR, 1}}));
+
+ sr.push(recordNoError);
+ EXPECT_EQ(sr.getStatsData(), makeStatsData(server, 2, 20ms, {{NS_R_NO_ERROR, 2}}));
+
+ sr.push(recordTimeout);
+ EXPECT_EQ(sr.getStatsData(),
+ makeStatsData(server, 3, 270ms, {{NS_R_NO_ERROR, 2}, {NS_R_TIMEOUT, 1}}));
+
+ sr.push(recordTimeout);
+ EXPECT_EQ(sr.getStatsData(),
+ makeStatsData(server, 3, 510ms, {{NS_R_NO_ERROR, 1}, {NS_R_TIMEOUT, 2}}));
+
+ sr.push(recordTimeout);
+ EXPECT_EQ(sr.getStatsData(),
+ makeStatsData(server, 3, 750ms, {{NS_R_NO_ERROR, 0}, {NS_R_TIMEOUT, 3}}));
+}
+
+class DnsStatsTest : public ::testing::Test {
+ protected:
+ DnsStats mDnsStats;
+};
+
+TEST_F(DnsStatsTest, SetServers) {
+ static const struct {
+ std::vector<std::string> servers;
+ std::vector<std::string> expectation;
+ bool isSuccess;
+ } tests[] = {
+ // Normal case.
+ {
+ {"127.0.0.1", "127.0.0.2", "fe80::1%22", "2001:db8::2", "::1"},
+ {"127.0.0.1", "127.0.0.2", "fe80::1%22", "2001:db8::2", "::1"},
+ true,
+ },
+ // Duplicate servers.
+ {
+ {"127.0.0.1", "2001:db8::2", "127.0.0.1", "2001:db8::2"},
+ {"127.0.0.1", "2001:db8::2"},
+ true,
+ },
+ // Invalid server addresses. The state remains in previous state.
+ {
+ {"not_an_ip", "127.0.0.3", "127.a.b.2"},
+ {"127.0.0.1", "2001:db8::2"},
+ false,
+ },
+ // Clean up the old servers 127.0.0.1 and 127.0.0.2.
+ {
+ {"127.0.0.4", "2001:db8::5"},
+ {"127.0.0.4", "2001:db8::5"},
+ true,
+ },
+ // Empty list.
+ {{}, {}, true},
+ };
+
+ for (const auto& [servers, expectation, isSuccess] : tests) {
+ std::vector<IPSockAddr> ipSockAddrs;
+ ipSockAddrs.reserve(servers.size());
+ for (const auto& server : servers) {
+ ipSockAddrs.push_back(IPSockAddr::toIPSockAddr(server, 53));
+ }
+
+ EXPECT_TRUE(mDnsStats.setServers(ipSockAddrs, PROTO_TCP) == isSuccess);
+ EXPECT_TRUE(mDnsStats.setServers(ipSockAddrs, PROTO_UDP) == isSuccess);
+ EXPECT_TRUE(mDnsStats.setServers(ipSockAddrs, PROTO_DOT) == isSuccess);
+
+ std::vector<StatsData> expectedStats;
+ expectedStats.reserve(expectation.size());
+ for (const auto& exp : expectation) {
+ expectedStats.push_back(makeStatsData(IPSockAddr::toIPSockAddr(exp, 53), 0, 0ms, {}));
+ }
+
+ EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStats));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), UnorderedElementsAreArray(expectedStats));
+ }
+}
+
+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),
+ };
+
+ // Servers setup fails due to port unset.
+ EXPECT_FALSE(mDnsStats.setServers(servers, PROTO_TCP));
+ EXPECT_FALSE(mDnsStats.setServers(servers, PROTO_UDP));
+ EXPECT_FALSE(mDnsStats.setServers(servers, PROTO_DOT));
+
+ EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), IsEmpty());
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), IsEmpty());
+ EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+
+ EXPECT_TRUE(mDnsStats.setServers(std::vector(servers.begin() + 2, servers.end()), PROTO_TCP));
+ EXPECT_TRUE(mDnsStats.setServers(std::vector(servers.begin() + 2, servers.end()), PROTO_UDP));
+ EXPECT_TRUE(mDnsStats.setServers(std::vector(servers.begin() + 2, servers.end()), PROTO_DOT));
+
+ const std::vector<StatsData> expectedStats = {
+ makeStatsData(servers[2], 0, 0ms, {}), makeStatsData(servers[3], 0, 0ms, {}),
+ makeStatsData(servers[4], 0, 0ms, {}), makeStatsData(servers[5], 0, 0ms, {}),
+ makeStatsData(servers[6], 0, 0ms, {}), makeStatsData(servers[7], 0, 0ms, {}),
+ };
+
+ EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStats));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), UnorderedElementsAreArray(expectedStats));
+}
+
+TEST_F(DnsStatsTest, AddStatsAndClear) {
+ const std::vector<IPSockAddr> servers = {
+ IPSockAddr::toIPSockAddr("127.0.0.1", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.2", 53),
+ };
+ const DnsQueryEvent record = makeDnsQueryEvent(PROTO_UDP, NS_R_NO_ERROR, 10ms);
+
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_TCP));
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_UDP));
+
+ // Fail to add stats because of incorrect arguments.
+ EXPECT_FALSE(mDnsStats.addStats(IPSockAddr::toIPSockAddr("127.0.0.4", 53), record));
+ EXPECT_FALSE(mDnsStats.addStats(IPSockAddr::toIPSockAddr("127.a.b.4", 53), record));
+
+ EXPECT_TRUE(mDnsStats.addStats(servers[0], record));
+ EXPECT_TRUE(mDnsStats.addStats(servers[0], record));
+ EXPECT_TRUE(mDnsStats.addStats(servers[1], record));
+
+ const std::vector<StatsData> expectedStatsForTcp = {
+ makeStatsData(servers[0], 0, 0ms, {}),
+ makeStatsData(servers[1], 0, 0ms, {}),
+ };
+ const std::vector<StatsData> expectedStatsForUdp = {
+ makeStatsData(servers[0], 2, 20ms, {{NS_R_NO_ERROR, 2}}),
+ makeStatsData(servers[1], 1, 10ms, {{NS_R_NO_ERROR, 1}}),
+ };
+
+ EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), UnorderedElementsAreArray(expectedStatsForTcp));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStatsForUdp));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+
+ // Clear stats.
+ EXPECT_TRUE(mDnsStats.setServers({}, PROTO_TCP));
+ EXPECT_TRUE(mDnsStats.setServers({}, PROTO_UDP));
+ EXPECT_TRUE(mDnsStats.setServers({}, PROTO_DOT));
+ EXPECT_THAT(mDnsStats.getStats(PROTO_TCP), IsEmpty());
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), IsEmpty());
+ EXPECT_THAT(mDnsStats.getStats(PROTO_DOT), IsEmpty());
+}
+
+TEST_F(DnsStatsTest, StatsRemainsInExistentServer) {
+ std::vector<IPSockAddr> servers = {
+ IPSockAddr::toIPSockAddr("127.0.0.1", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.2", 53),
+ };
+ const DnsQueryEvent recordNoError = makeDnsQueryEvent(PROTO_UDP, NS_R_NO_ERROR, 10ms);
+ const DnsQueryEvent recordTimeout = makeDnsQueryEvent(PROTO_UDP, NS_R_TIMEOUT, 250ms);
+
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_UDP));
+
+ // Add a record to 127.0.0.1.
+ EXPECT_TRUE(mDnsStats.addStats(servers[0], recordNoError));
+
+ // Add four records to 127.0.0.2.
+ EXPECT_TRUE(mDnsStats.addStats(servers[1], recordNoError));
+ EXPECT_TRUE(mDnsStats.addStats(servers[1], recordNoError));
+ EXPECT_TRUE(mDnsStats.addStats(servers[1], recordTimeout));
+ EXPECT_TRUE(mDnsStats.addStats(servers[1], recordTimeout));
+
+ std::vector<StatsData> expectedStats = {
+ makeStatsData(servers[0], 1, 10ms, {{NS_R_NO_ERROR, 1}}),
+ makeStatsData(servers[1], 4, 520ms, {{NS_R_NO_ERROR, 2}, {NS_R_TIMEOUT, 2}}),
+ };
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+
+ // Update the server list, the stats of 127.0.0.2 will remain.
+ servers = {
+ IPSockAddr::toIPSockAddr("127.0.0.2", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.3", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.4", 53),
+ };
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_UDP));
+ expectedStats = {
+ makeStatsData(servers[0], 4, 520ms, {{NS_R_NO_ERROR, 2}, {NS_R_TIMEOUT, 2}}),
+ makeStatsData(servers[1], 0, 0ms, {}),
+ makeStatsData(servers[2], 0, 0ms, {}),
+ };
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+
+ // Let's add a record to 127.0.0.2 again.
+ EXPECT_TRUE(mDnsStats.addStats(servers[0], recordNoError));
+ expectedStats = {
+ makeStatsData(servers[0], 5, 530ms, {{NS_R_NO_ERROR, 3}, {NS_R_TIMEOUT, 2}}),
+ makeStatsData(servers[1], 0, 0ms, {}),
+ makeStatsData(servers[2], 0, 0ms, {}),
+ };
+ EXPECT_THAT(mDnsStats.getStats(PROTO_UDP), UnorderedElementsAreArray(expectedStats));
+}
+
+TEST_F(DnsStatsTest, AddStatsRecords_100000) {
+ constexpr int num = 100000;
+ const std::vector<IPSockAddr> servers = {
+ IPSockAddr::toIPSockAddr("127.0.0.1", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.2", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.3", 53),
+ IPSockAddr::toIPSockAddr("127.0.0.4", 53),
+ };
+
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_TCP));
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_UDP));
+ EXPECT_TRUE(mDnsStats.setServers(servers, PROTO_DOT));
+
+ for (int i = 0; i < num; i++) {
+ const auto eventTcp = makeDnsQueryEvent(PROTO_TCP, static_cast<NsRcode>(i % 10), 10ms);
+ const auto eventUdp = makeDnsQueryEvent(PROTO_UDP, static_cast<NsRcode>(i % 10), 10ms);
+ const auto eventDot = makeDnsQueryEvent(PROTO_DOT, static_cast<NsRcode>(i % 10), 10ms);
+ for (const auto& server : servers) {
+ const std::string trace = server.toString() + "-" + std::to_string(i);
+ SCOPED_TRACE(trace);
+ EXPECT_TRUE(mDnsStats.addStats(server, eventTcp));
+ EXPECT_TRUE(mDnsStats.addStats(server, eventUdp));
+ EXPECT_TRUE(mDnsStats.addStats(server, eventDot));
+ }
+ }
+}
+
+} // namespace android::net
diff --git a/DnsTlsDispatcher.cpp b/DnsTlsDispatcher.cpp
index 58ef2a7..7ca0464 100644
--- a/DnsTlsDispatcher.cpp
+++ b/DnsTlsDispatcher.cpp
@@ -17,8 +17,11 @@
#define LOG_TAG "resolv"
#include "DnsTlsDispatcher.h"
+
#include <netdutils/Stopwatch.h>
+
#include "DnsTlsSocketFactory.h"
+#include "resolv_cache.h"
#include "resolv_private.h"
#include "stats.pb.h"
@@ -27,6 +30,7 @@
namespace android {
namespace net {
+using android::netdutils::IPSockAddr;
using android::netdutils::Stopwatch;
using netdutils::Slice;
@@ -112,19 +116,23 @@
case DnsTlsTransport::Response::success:
dnsQueryEvent->set_rcode(
static_cast<NsRcode>(reinterpret_cast<HEADER*>(ans.base())->rcode));
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent);
return code;
case DnsTlsTransport::Response::limit_error:
dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent);
return code;
// These response codes might differ when trying other servers, so
// keep iterating to see if we can get a different (better) result.
case DnsTlsTransport::Response::network_error:
// Sync from res_tls_send in res_send.cpp
dnsQueryEvent->set_rcode(NS_R_TIMEOUT);
- continue;
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent);
+ break;
case DnsTlsTransport::Response::internal_error:
dnsQueryEvent->set_rcode(NS_R_INTERNAL_ERROR);
- continue;
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(server.ss), dnsQueryEvent);
+ break;
// No "default" statement.
}
}
diff --git a/PrivateDnsConfiguration.cpp b/PrivateDnsConfiguration.cpp
index 9242222..f59e64e 100644
--- a/PrivateDnsConfiguration.cpp
+++ b/PrivateDnsConfiguration.cpp
@@ -28,6 +28,7 @@
#include "ResolverEventReporter.h"
#include "netd_resolv/resolv.h"
#include "netdutils/BackoffSequence.h"
+#include "resolv_cache.h"
using std::chrono::milliseconds;
@@ -95,6 +96,7 @@
} else {
mPrivateDnsModes[netId] = PrivateDnsMode::OFF;
mPrivateDnsTransports.erase(netId);
+ resolv_stats_set_servers_for_dot(netId, {});
return 0;
}
@@ -126,7 +128,8 @@
validatePrivateDnsProvider(server, tracker, netId, mark);
}
}
- return 0;
+
+ return resolv_stats_set_servers_for_dot(netId, servers);
}
PrivateDnsStatus PrivateDnsConfiguration::getStatus(unsigned netId) {
diff --git a/ResolverController.cpp b/ResolverController.cpp
index 6927e5f..5fecc61 100644
--- a/ResolverController.cpp
+++ b/ResolverController.cpp
@@ -356,6 +356,7 @@
dw.decIndent();
}
dw.println("Concurrent DNS query timeout: %d", wait_for_pending_req_timeout_count[0]);
+ resolv_stats_dump(dw, netId);
}
dw.decIndent();
}
diff --git a/res_cache.cpp b/res_cache.cpp
index d26ad76..56b15e7 100644
--- a/res_cache.cpp
+++ b/res_cache.cpp
@@ -58,10 +58,15 @@
#include <server_configurable_flags/get_flags.h>
+#include "DnsStats.h"
#include "res_debug.h"
#include "resolv_private.h"
using android::base::StringAppendF;
+using android::net::DnsQueryEvent;
+using android::net::DnsStats;
+using android::netdutils::DumpWriter;
+using android::netdutils::IPSockAddr;
/* This code implements a small and *simple* DNS resolver cache.
*
@@ -939,6 +944,7 @@
int wait_for_pending_req_timeout_count;
// Map format: ReturnCode:rate_denom
std::unordered_map<int, uint32_t> dns_event_subsampling_map;
+ std::unique_ptr<DnsStats> dnsStats;
};
/* gets cache associated with a network, or NULL if none exists */
@@ -1362,6 +1368,7 @@
cache_info->netid = netid;
cache_info->cache = new Cache;
cache_info->dns_event_subsampling_map = resolv_get_dns_event_subsampling_map();
+ cache_info->dnsStats.reset(new DnsStats());
insert_cache_info_locked(cache_info);
return 0;
@@ -1379,6 +1386,11 @@
prev_cache_info->next = cache_info->next;
delete cache_info->cache;
free_nameservers_locked(cache_info);
+
+ // It won't be necessary after the memory of cache_info can be deallocated by the
+ // C++ delete expression.
+ cache_info->dnsStats.reset();
+
free(cache_info);
break;
}
@@ -1539,6 +1551,18 @@
// since the stored cache entries do contain the domain, not just the host name.
cache_info->search_domains = filter_domains(domains);
+ std::vector<IPSockAddr> serverSockAddrs;
+ serverSockAddrs.reserve(cache_info->nameservers.size());
+ for (const auto& server : cache_info->nameservers) {
+ serverSockAddrs.push_back(IPSockAddr::toIPSockAddr(server, 53));
+ }
+
+ if (!cache_info->dnsStats->setServers(serverSockAddrs, android::net::PROTO_TCP) ||
+ !cache_info->dnsStats->setServers(serverSockAddrs, android::net::PROTO_UDP)) {
+ LOG(WARNING) << __func__ << ": netid = " << netid << ", failed to set dns stats";
+ return -EINVAL;
+ }
+
return 0;
}
@@ -1780,3 +1804,41 @@
*expiration = e->expires;
return 0;
}
+
+int resolv_stats_set_servers_for_dot(unsigned netid, const std::vector<std::string>& servers) {
+ std::lock_guard guard(cache_mutex);
+ const auto info = find_cache_info_locked(netid);
+
+ if (info == nullptr) return -ENONET;
+
+ std::vector<IPSockAddr> serverSockAddrs;
+ serverSockAddrs.reserve(servers.size());
+ for (const auto& server : servers) {
+ serverSockAddrs.push_back(IPSockAddr::toIPSockAddr(server, 853));
+ }
+
+ if (!info->dnsStats->setServers(serverSockAddrs, android::net::PROTO_DOT)) {
+ LOG(WARNING) << __func__ << ": netid = " << netid << ", failed to set dns stats";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+bool resolv_stats_add(unsigned netid, const android::netdutils::IPSockAddr& server,
+ const DnsQueryEvent* record) {
+ if (record == nullptr) return false;
+
+ std::lock_guard guard(cache_mutex);
+ if (const auto info = find_cache_info_locked(netid); info != nullptr) {
+ return info->dnsStats->addStats(server, *record);
+ }
+ return false;
+}
+
+void resolv_stats_dump(DumpWriter& dw, unsigned netid) {
+ std::lock_guard guard(cache_mutex);
+ if (const auto info = find_cache_info_locked(netid); info != nullptr) {
+ info->dnsStats->dump(dw);
+ }
+}
diff --git a/res_send.cpp b/res_send.cpp
index 9eee05d..1dc5f57 100644
--- a/res_send.cpp
+++ b/res_send.cpp
@@ -130,6 +130,7 @@
using android::net::PrivateDnsStatus;
using android::net::PROTO_TCP;
using android::net::PROTO_UDP;
+using android::netdutils::IPSockAddr;
using android::netdutils::Slice;
using android::netdutils::Stopwatch;
@@ -544,6 +545,7 @@
_res_stats_set_sample(&sample, now, *rcode, delay);
_resolv_cache_add_resolver_stats_sample(statp->netid, revision_id, ns, &sample,
params.max_samples);
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(*nsap), dnsQueryEvent);
}
LOG(INFO) << __func__ << ": used send_vc " << n;
@@ -577,6 +579,7 @@
_res_stats_set_sample(&sample, now, *rcode, delay);
_resolv_cache_add_resolver_stats_sample(statp->netid, revision_id, ns, &sample,
params.max_samples);
+ resolv_stats_add(statp->netid, IPSockAddr::toIPSockAddr(*nsap), dnsQueryEvent);
}
LOG(INFO) << __func__ << ": used send_dg " << n;
diff --git a/resolv_cache.h b/resolv_cache.h
index 2b372f6..6ede2e0 100644
--- a/resolv_cache.h
+++ b/resolv_cache.h
@@ -28,13 +28,16 @@
#pragma once
-#include "netd_resolv/resolv.h"
-
-#include <stddef.h>
-
#include <unordered_map>
#include <vector>
+#include <netdutils/DumpWriter.h>
+#include <netdutils/InternetAddresses.h>
+#include <stats.pb.h>
+
+#include "ResolverStats.h"
+#include "netd_resolv/params.h"
+
// Sets the name server addresses to the provided ResState.
// The name servers are retrieved from the cache which is associated
// with the network to which ResState is associated.
@@ -83,3 +86,12 @@
// Get the expiration time of a cache entry. Return 0 on success; otherwise, an negative error is
// returned if the expiration time can't be acquired.
int resolv_cache_get_expiration(unsigned netid, const std::vector<char>& query, time_t* expiration);
+
+// Set private DNS servers to DnsStats for a given network.
+int resolv_stats_set_servers_for_dot(unsigned netid, const std::vector<std::string>& servers);
+
+// Add a statistics record to DnsStats for a given network.
+bool resolv_stats_add(unsigned netid, const android::netdutils::IPSockAddr& server,
+ const android::net::DnsQueryEvent* record);
+
+void resolv_stats_dump(android::netdutils::DumpWriter& dw, unsigned netid);
diff --git a/resolv_private.h b/resolv_private.h
index 3272844..ea7cdc2 100644
--- a/resolv_private.h
+++ b/resolv_private.h
@@ -104,7 +104,6 @@
};
// TODO: remove these legacy aliases
-typedef ResState __res_state;
typedef ResState* res_state;
/* Retrieve a local copy of the stats for the given netid. The buffer must have space for