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

Commit 39df0b13 authored by Mike Yu's avatar Mike Yu
Browse files

Add unit tests for DnsQueryLog

Ensure the basic functions of DnsQueryLog work as expected.

Bug: 139040977
Test: cd packages/modules/DnsResolver && atest
Change-Id: I6b318beeed7ff5942e8d08474c354e48ebdac936
parent 939e3690
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -171,6 +171,7 @@ cc_test {
        "resolv_tls_unit_test.cpp",
        "resolv_unit_test.cpp",
        "DnsStatsTest.cpp",
        "DnsQueryLogTest.cpp",
    ],
    shared_libs: [
        "libbase",
+3 −3
Original line number Diff line number Diff line
@@ -61,19 +61,19 @@ std::string timestampToString(const std::chrono::system_clock::time_point& ts) {
void DnsQueryLog::push(Record&& record) {
    std::lock_guard guard(mLock);
    mQueue.push_back(std::move(record));
    if (mQueue.size() > kLogSize) {
    if (mQueue.size() > mCapacity) {
        mQueue.pop_front();
    }
}

void DnsQueryLog::dump(netdutils::DumpWriter& dw) const {
    dw.println("DNS query log (last %ld minutes):", kValidityMinutes.count());
    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 > kValidityMinutes) continue;
        if (now - record.timestamp > mValidityTimeMs) continue;

        const std::string maskedHostname = maskHostname(record.hostname);
        const std::string maskedIpsStr = maskIps(record.addrs);
+10 −3
Original line number Diff line number Diff line
@@ -49,18 +49,25 @@ class DnsQueryLog {
        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 kLogSize = 200;
    static constexpr size_t kDefaultLogSize = 200;

    // Limit to dump the queries within last |kValidityMinutes| minutes.
    static constexpr std::chrono::minutes kValidityMinutes{60};
    // 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