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

Commit 9618d9c1 authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge changes from topic "querylog" am: c727677f am: b3e6f7d8

Change-Id: I726072d8b2660b1f12b92d4452ba2ebb6638641c
parents 50835aab b3e6f7d8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ cc_library {
        "util.cpp",
        "Dns64Configuration.cpp",
        "DnsProxyListener.cpp",
        "DnsQueryLog.cpp",
        "DnsResolver.cpp",
        "DnsResolverService.cpp",
        "DnsStats.cpp",
@@ -174,6 +175,7 @@ cc_test {
        "resolv_tls_unit_test.cpp",
        "resolv_unit_test.cpp",
        "DnsStatsTest.cpp",
        "DnsQueryLogTest.cpp",
    ],
    shared_libs: [
        "libbase",
+20 −0
Original line number Diff line number Diff line
@@ -329,6 +329,24 @@ uint32_t getDnsEventSubsamplingRate(int netid, int returnCode) {
    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 @@ void reportDnsEvent(int eventType, const android_net_context& netContext, int la
                                         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__

DnsQueryLog.cpp

0 → 100644
+87 −0
Original line number Diff line number Diff line
/*
 * 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

DnsQueryLog.h

0 → 100644
+73 −0
Original line number Diff line number Diff line
/*
 * 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

DnsQueryLogTest.cpp

0 → 100644
+164 −0
Original line number Diff line number Diff line
/*
 * 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
Loading