Merge changes from topic "querylog"

* changes:
  Add unit tests for DnsQueryLog
  Log DNS resolved answers
diff --git a/Android.bp b/Android.bp
index 0faf45f..28cb03e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -60,6 +60,7 @@
         "util.cpp",
         "Dns64Configuration.cpp",
         "DnsProxyListener.cpp",
+        "DnsQueryLog.cpp",
         "DnsResolver.cpp",
         "DnsResolverService.cpp",
         "DnsStats.cpp",
@@ -174,6 +175,7 @@
         "resolv_tls_unit_test.cpp",
         "resolv_unit_test.cpp",
         "DnsStatsTest.cpp",
+        "DnsQueryLogTest.cpp",
     ],
     shared_libs: [
         "libbase",
diff --git a/DnsProxyListener.cpp b/DnsProxyListener.cpp
index 341cc76..4eef162 100644
--- a/DnsProxyListener.cpp
+++ b/DnsProxyListener.cpp
@@ -329,6 +329,24 @@
     return (arc4random_uniform(subsampling_denom) == 0) ? subsampling_denom : 0;
 }
 
+void maybeLogQuery(int eventType, const android_net_context& netContext,
+                   const NetworkDnsEventReported& event, const std::string& query_name,
+                   const std::vector<std::string>& ip_addrs) {
+    // Skip reverse queries.
+    if (eventType == INetdEventListener::EVENT_GETHOSTBYADDR) return;
+
+    for (const auto& query : event.dns_query_events().dns_query_event()) {
+        // Log it when the cache misses.
+        if (query.cache_hit() != CS_FOUND) {
+            const int timeTakenMs = event.latency_micros() / 1000;
+            DnsQueryLog::Record record(netContext.dns_netid, netContext.uid, netContext.pid,
+                                       query_name, ip_addrs, timeTakenMs);
+            gDnsResolv->dnsQueryLog().push(std::move(record));
+            return;
+        }
+    }
+}
+
 void reportDnsEvent(int eventType, const android_net_context& netContext, int latencyUs,
                     int returnCode, NetworkDnsEventReported& event, const std::string& query_name,
                     const std::vector<std::string>& ip_addrs = {}, int total_ip_addr_count = 0) {
@@ -343,6 +361,8 @@
                                          event.private_dns_modes(), dnsQueryBytesField, rate);
     }
 
+    maybeLogQuery(eventType, netContext, event, query_name, ip_addrs);
+
     const auto& listeners = ResolverEventReporter::getInstance().getListeners();
     if (listeners.size() == 0) {
         LOG(ERROR) << __func__
diff --git a/DnsQueryLog.cpp b/DnsQueryLog.cpp
new file mode 100644
index 0000000..6f0e179
--- /dev/null
+++ b/DnsQueryLog.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "DnsQueryLog.h"
+
+#include <android-base/stringprintf.h>
+
+namespace android::net {
+
+namespace {
+
+std::string maskHostname(const std::string& hostname) {
+    // Boundary issue is handled in substr().
+    return hostname.substr(0, 1) + "***";
+}
+
+// Return the string of masked addresses of the first v4 address and the first v6 address.
+std::string maskIps(const std::vector<std::string>& ips) {
+    std::string ret;
+    bool v4Found = false, v6Found = false;
+    for (const auto& ip : ips) {
+        if (auto pos = ip.find_first_of(':'); pos != ip.npos && !v6Found) {
+            ret += ip.substr(0, pos + 1) + "***, ";
+            v6Found = true;
+        } else if (auto pos = ip.find_first_of('.'); pos != ip.npos && !v4Found) {
+            ret += ip.substr(0, pos + 1) + "***, ";
+            v4Found = true;
+        }
+        if (v6Found && v4Found) break;
+    }
+    return ret.empty() ? "" : ret.substr(0, ret.length() - 2);
+}
+
+// Return the readable string format "hr:min:sec.ms".
+std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
+    using std::chrono::duration_cast;
+    using std::chrono::milliseconds;
+    const auto time_sec = std::chrono::system_clock::to_time_t(ts);
+    char buf[32];
+    std::strftime(buf, sizeof(buf), "%H:%M:%S", std::localtime(&time_sec));
+    int ms = duration_cast<milliseconds>(ts.time_since_epoch()).count() % 1000;
+    return android::base::StringPrintf("%s.%03d", buf, ms);
+}
+
+}  // namespace
+
+void DnsQueryLog::push(Record&& record) {
+    std::lock_guard guard(mLock);
+    mQueue.push_back(std::move(record));
+    if (mQueue.size() > mCapacity) {
+        mQueue.pop_front();
+    }
+}
+
+void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
+    dw.println("DNS query log (last %lld minutes):", (mValidityTimeMs / 60000).count());
+    netdutils::ScopedIndent indentStats(dw);
+    const auto now = std::chrono::system_clock::now();
+
+    std::lock_guard guard(mLock);
+    for (const auto& record : mQueue) {
+        if (now - record.timestamp > mValidityTimeMs) continue;
+
+        const std::string maskedHostname = maskHostname(record.hostname);
+        const std::string maskedIpsStr = maskIps(record.addrs);
+        const std::string time = timestampToString(record.timestamp);
+        dw.println("time=%s netId=%u uid=%u pid=%d hostname=%s answer=[%s] (%dms)", time.c_str(),
+                   record.netId, record.uid, record.pid, maskedHostname.c_str(),
+                   maskedIpsStr.c_str(), record.timeTaken);
+    }
+}
+
+}  // namespace android::net
diff --git a/DnsQueryLog.h b/DnsQueryLog.h
new file mode 100644
index 0000000..c19f8db
--- /dev/null
+++ b/DnsQueryLog.h
@@ -0,0 +1,73 @@
+/*
+ * 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 <deque>
+#include <string>
+#include <vector>
+
+#include <android-base/thread_annotations.h>
+#include <netdutils/DumpWriter.h>
+
+namespace android::net {
+
+// A circular buffer based class used for query logging. It's thread-safe for concurrent access.
+class DnsQueryLog {
+  public:
+    static constexpr std::string_view DUMP_KEYWORD = "querylog";
+
+    struct Record {
+        Record(uint32_t netId, uid_t uid, pid_t pid, const std::string& hostname,
+               const std::vector<std::string>& addrs, int timeTaken)
+            : netId(netId),
+              uid(uid),
+              pid(pid),
+              hostname(hostname),
+              addrs(addrs),
+              timeTaken(timeTaken) {}
+        const uint32_t netId;
+        const uid_t uid;
+        const pid_t pid;
+        const std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
+        const std::string hostname;
+        const std::vector<std::string> addrs;
+        const int timeTaken;
+    };
+
+    // Allow the tests to set the capacity and the validaty time in milliseconds.
+    DnsQueryLog(size_t size = kDefaultLogSize,
+                std::chrono::milliseconds time = kDefaultValidityMinutes)
+        : mCapacity(size), mValidityTimeMs(time) {}
+
+    void push(Record&& record) EXCLUDES(mLock);
+    void dump(netdutils::DumpWriter& dw) const EXCLUDES(mLock);
+
+  private:
+    mutable std::mutex mLock;
+    std::deque<Record> mQueue GUARDED_BY(mLock);
+    const size_t mCapacity;
+    const std::chrono::milliseconds mValidityTimeMs;
+
+    // The capacity of the circular buffer.
+    static constexpr size_t kDefaultLogSize = 200;
+
+    // Limit to dump the queries within last |kDefaultValidityMinutes| minutes.
+    static constexpr std::chrono::minutes kDefaultValidityMinutes{60};
+};
+
+}  // namespace android::net
diff --git a/DnsQueryLogTest.cpp b/DnsQueryLogTest.cpp
new file mode 100644
index 0000000..b652412
--- /dev/null
+++ b/DnsQueryLogTest.cpp
@@ -0,0 +1,164 @@
+/*
+ * 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 <regex>
+#include <thread>
+
+#include <android-base/strings.h>
+#include <android-base/test_utils.h>
+#include <gtest/gtest.h>
+
+#include "DnsQueryLog.h"
+
+using namespace std::chrono_literals;
+
+namespace android::net {
+
+namespace {
+
+// Dump the log to STDOUT and capture it.
+std::string captureDumpOutput(const DnsQueryLog& queryLog) {
+    netdutils::DumpWriter dw(STDOUT_FILENO);
+    CapturedStdout captured;
+    queryLog.dump(dw);
+    return captured.str();
+}
+
+// A simple check for the dump result by checking the netIds one by one.
+void verifyDumpOutput(const std::string& dumpLog, const std::vector<int>& expectedNetIds) {
+    // Capture three matches: netId, hostname, and answer (empty allowed).
+    static const std::regex pattern(
+            R"(netId=(\d+).* hostname=([\w\*]+) answer=\[([\w:,\.\*\s]*)\])");
+
+    std::string str(dumpLog);
+    std::smatch sm;
+    for (const auto& netId : expectedNetIds) {
+        SCOPED_TRACE(netId);
+        EXPECT_TRUE(std::regex_search(str, sm, pattern));
+        EXPECT_EQ(sm[1], std::to_string(netId));
+        str = sm.suffix();
+    }
+
+    // Ensure the dumpLog is exactly as expected.
+    EXPECT_FALSE(std::regex_search(str, sm, pattern));
+}
+
+}  // namespace
+
+class DnsQueryLogTest : public ::testing::Test {
+  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",
+                                                  "fe80:1::2%testnet"};
+};
+
+TEST_F(DnsQueryLogTest, Push) {
+    std::vector<DnsQueryLog::Record> records = {
+            DnsQueryLog::Record(30, 1000, 1000, "example.com", serversV4, 10),
+            DnsQueryLog::Record(31, 1000, 1000, "", serversV4, 10),      // Empty hostname.
+            DnsQueryLog::Record(32, 1000, 1000, "example.com", {}, 10),  // No answer.
+            DnsQueryLog::Record(33, 1000, 1000, "example.com", serversV4V6, 10),
+    };
+    DnsQueryLog queryLog;
+    for (auto& r : records) {
+        queryLog.push(std::move(r));
+    }
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {30, 31, 32, 33});
+}
+
+TEST_F(DnsQueryLogTest, PushStressTest) {
+    const int threadNum = 100;
+    const int pushNum = 1000;
+    const size_t size = 500;
+    DnsQueryLog queryLog(size);
+    std::vector<std::thread> threads(threadNum);
+
+    // Launch 'threadNum' threads to push the same queryLog 'pushNum' times.
+    for (auto& thread : threads) {
+        thread = std::thread([&]() {
+            for (int i = 0; i < pushNum; i++) {
+                DnsQueryLog::Record record(30, 1000, 1000, "www.example.com", serversV4, 10);
+                queryLog.push(std::move(record));
+            }
+        });
+    }
+    for (auto& thread : threads) {
+        thread.join();
+    }
+
+    // Verify there are exact 'size' records in queryLog.
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, std::vector(size, 30));
+}
+
+TEST_F(DnsQueryLogTest, ZeroSize) {
+    const size_t size = 0;
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example1.com", serversV4V6, 10);
+    DnsQueryLog::Record r2(31, 1000, 1000, "www.example2.com", serversV4V6, 10);
+    DnsQueryLog::Record r3(32, 1000, 1000, "www.example3.com", serversV4V6, 10);
+
+    DnsQueryLog queryLog(size);
+    queryLog.push(std::move(r1));
+    queryLog.push(std::move(r2));
+    queryLog.push(std::move(r3));
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {});
+}
+
+TEST_F(DnsQueryLogTest, CapacityFull) {
+    const size_t size = 3;
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example1.com", serversV4V6, 10);
+    DnsQueryLog::Record r2(31, 1000, 1000, "www.example2.com", serversV4V6, 10);
+    DnsQueryLog::Record r3(32, 1000, 1000, "www.example3.com", serversV4V6, 10);
+    DnsQueryLog::Record r4(33, 1000, 1000, "www.example4.com", serversV4V6, 10);
+    const std::vector<int> expectedNetIds = {31, 32, 33};
+
+    DnsQueryLog queryLog(size);
+    queryLog.push(std::move(r1));
+    queryLog.push(std::move(r2));
+    queryLog.push(std::move(r3));
+    queryLog.push(std::move(r4));
+
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, expectedNetIds);
+}
+
+TEST_F(DnsQueryLogTest, ValidityTime) {
+    DnsQueryLog::Record r1(30, 1000, 1000, "www.example.com", serversV4, 10);
+    DnsQueryLog queryLog(3, 100ms);
+    queryLog.push(std::move(r1));
+
+    // Dump the output and verify the correctness by checking netId.
+    std::string output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {30});
+
+    std::this_thread::sleep_for(150ms);
+
+    // The record is expired thus not shown in the output.
+    output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {});
+
+    // Push another record to ensure it still works.
+    DnsQueryLog::Record r2(31, 1000, 1000, "example.com", serversV4V6, 10);
+    queryLog.push(std::move(r2));
+    output = captureDumpOutput(queryLog);
+    verifyDumpOutput(output, {31});
+}
+
+}  // namespace android::net
diff --git a/DnsResolver.h b/DnsResolver.h
index 2efa341..863b096 100644
--- a/DnsResolver.h
+++ b/DnsResolver.h
@@ -18,6 +18,7 @@
 #define _DNS_RESOLVER_H_
 
 #include "DnsProxyListener.h"
+#include "DnsQueryLog.h"
 #include "ResolverController.h"
 #include "netd_resolv/resolv.h"
 #include "netdutils/Log.h"
@@ -34,11 +35,14 @@
     DnsResolver(DnsResolver const&) = delete;
     void operator=(DnsResolver const&) = delete;
 
+    DnsQueryLog& dnsQueryLog() { return mQueryLog; }
+
     ResolverController resolverCtrl;
 
   private:
     DnsResolver() {}
     DnsProxyListener mDnsProxyListener;
+    DnsQueryLog mQueryLog;
 };
 
 extern DnsResolver* gDnsResolv;
diff --git a/DnsResolverService.cpp b/DnsResolverService.cpp
index a9ec8e7..c4b42bc 100644
--- a/DnsResolverService.cpp
+++ b/DnsResolverService.cpp
@@ -92,7 +92,7 @@
     return STATUS_OK;
 }
 
-binder_status_t DnsResolverService::dump(int fd, const char**, uint32_t) {
+binder_status_t DnsResolverService::dump(int fd, const char** args, uint32_t numArgs) {
     auto dump_permission = checkAnyPermission({PERM_DUMP});
     if (!dump_permission.isOk()) {
         return STATUS_PERMISSION_DENIED;
@@ -101,6 +101,14 @@
     // This method does not grab any locks. If individual classes need locking
     // their dump() methods MUST handle locking appropriately.
     DumpWriter dw(fd);
+
+    if (numArgs == 1 && std::string(args[0]) == DnsQueryLog::DUMP_KEYWORD) {
+        dw.blankline();
+        gDnsResolv->dnsQueryLog().dump(dw);
+        dw.blankline();
+        return STATUS_OK;
+    }
+
     for (auto netId : resolv_list_caches()) {
         dw.println("NetId: %u", netId);
         gDnsResolv->resolverCtrl.dump(dw, netId);