You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
17 KiB

/*
* 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "PrivateDnsConfiguration.h"
#include "tests/dns_responder/dns_responder.h"
#include "tests/dns_responder/dns_tls_frontend.h"
#include "tests/resolv_test_utils.h"
namespace android::net {
using namespace std::chrono_literals;
class PrivateDnsConfigurationTest : public ::testing::Test {
public:
using ServerIdentity = PrivateDnsConfiguration::ServerIdentity;
static void SetUpTestSuite() {
// stopServer() will be called in their destructor.
ASSERT_TRUE(tls1.startServer());
ASSERT_TRUE(tls2.startServer());
ASSERT_TRUE(backend.startServer());
ASSERT_TRUE(backend1ForUdpProbe.startServer());
ASSERT_TRUE(backend2ForUdpProbe.startServer());
}
void SetUp() {
mPdc.setObserver(&mObserver);
mPdc.mBackoffBuilder.withInitialRetransmissionTime(std::chrono::seconds(1))
.withMaximumRetransmissionTime(std::chrono::seconds(1));
// The default and sole action when the observer is notified of onValidationStateUpdate.
// Don't override the action. In other words, don't use WillOnce() or WillRepeatedly()
// when mObserver.onValidationStateUpdate is expected to be called, like:
//
// EXPECT_CALL(mObserver, onValidationStateUpdate).WillOnce(Return());
//
// This is to ensure that tests can monitor how many validation threads are running. Tests
// must wait until every validation thread finishes.
ON_CALL(mObserver, onValidationStateUpdate)
.WillByDefault([&](const std::string& server, Validation validation, uint32_t) {
if (validation == Validation::in_process) {
std::lock_guard guard(mObserver.lock);
auto it = mObserver.serverStateMap.find(server);
if (it == mObserver.serverStateMap.end() ||
it->second != Validation::in_process) {
// Increment runningThreads only when receive the first in_process
// notification. The rest of the continuous in_process notifications
// are due to probe retry which runs on the same thread.
// TODO: consider adding onValidationThreadStart() and
// onValidationThreadEnd() callbacks.
mObserver.runningThreads++;
}
} else if (validation == Validation::success ||
validation == Validation::fail) {
mObserver.runningThreads--;
}
std::lock_guard guard(mObserver.lock);
mObserver.serverStateMap[server] = validation;
});
}
protected:
class MockObserver : public PrivateDnsValidationObserver {
public:
MOCK_METHOD(void, onValidationStateUpdate,
(const std::string& serverIp, Validation validation, uint32_t netId),
(override));
std::map<std::string, Validation> getServerStateMap() const {
std::lock_guard guard(lock);
return serverStateMap;
}
void removeFromServerStateMap(const std::string& server) {
std::lock_guard guard(lock);
if (const auto it = serverStateMap.find(server); it != serverStateMap.end())
serverStateMap.erase(it);
}
// The current number of validation threads running.
std::atomic<int> runningThreads = 0;
mutable std::mutex lock;
std::map<std::string, Validation> serverStateMap GUARDED_BY(lock);
};
void expectPrivateDnsStatus(PrivateDnsMode mode) {
// Use PollForCondition because mObserver is notified asynchronously.
EXPECT_TRUE(PollForCondition([&]() { return checkPrivateDnsStatus(mode); }));
}
bool checkPrivateDnsStatus(PrivateDnsMode mode) {
const PrivateDnsStatus status = mPdc.getStatus(kNetId);
if (status.mode != mode) return false;
std::map<std::string, Validation> serverStateMap;
for (const auto& [server, validation] : status.serversMap) {
serverStateMap[ToString(&server.ss)] = validation;
}
return (serverStateMap == mObserver.getServerStateMap());
}
bool hasPrivateDnsServer(const ServerIdentity& identity, unsigned netId) {
return mPdc.getPrivateDns(identity, netId).ok();
}
static constexpr uint32_t kNetId = 30;
static constexpr uint32_t kMark = 30;
static constexpr char kBackend[] = "127.0.2.1";
static constexpr char kServer1[] = "127.0.2.2";
static constexpr char kServer2[] = "127.0.2.3";
MockObserver mObserver;
PrivateDnsConfiguration mPdc;
// TODO: Because incorrect CAs result in validation failed in strict mode, have
// PrivateDnsConfiguration run mocked code rather than DnsTlsTransport::validate().
inline static test::DnsTlsFrontend tls1{kServer1, "853", kBackend, "53"};
inline static test::DnsTlsFrontend tls2{kServer2, "853", kBackend, "53"};
inline static test::DNSResponder backend{kBackend, "53"};
inline static test::DNSResponder backend1ForUdpProbe{kServer1, "53"};
inline static test::DNSResponder backend2ForUdpProbe{kServer2, "53"};
};
TEST_F(PrivateDnsConfigurationTest, ValidationSuccess) {
testing::InSequence seq;
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
}
TEST_F(PrivateDnsConfigurationTest, ValidationFail_Opportunistic) {
ASSERT_TRUE(backend.stopServer());
testing::InSequence seq;
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
// Strictly wait for all of the validation finish; otherwise, the test can crash somehow.
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
ASSERT_TRUE(backend.startServer());
}
TEST_F(PrivateDnsConfigurationTest, Revalidation_Opportunistic) {
const DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
// Step 1: Set up and wait for validation complete.
testing::InSequence seq;
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
// Step 2: Simulate the DNS is temporarily broken, and then request a validation.
// Expect the validation to run as follows:
// 1. DnsResolver notifies of Validation::in_process when the validation is about to run.
// 2. The first probing fails. DnsResolver notifies of Validation::in_process.
// 3. One second later, the second probing begins and succeeds. DnsResolver notifies of
// Validation::success.
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId))
.Times(2);
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
std::thread t([] {
std::this_thread::sleep_for(1000ms);
backend.startServer();
});
backend.stopServer();
EXPECT_TRUE(mPdc.requestValidation(kNetId, ServerIdentity(server), kMark).ok());
t.join();
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
}
TEST_F(PrivateDnsConfigurationTest, ValidationBlock) {
backend.setDeferredResp(true);
// onValidationStateUpdate() is called in sequence.
{
testing::InSequence seq;
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::in_process, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 2; }));
mObserver.removeFromServerStateMap(kServer1);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
// No duplicate validation as long as not in OFF mode; otherwise, an unexpected
// onValidationStateUpdate() will be caught.
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1, kServer2}, {}, {}), 0);
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer2}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
// The status keeps unchanged if pass invalid arguments.
EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
}
// The update for |kServer1| will be Validation::fail because |kServer1| is not an expected
// server for the network.
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer2, Validation::success, kNetId));
backend.setDeferredResp(false);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
// kServer1 is not a present server and thus should not be available from
// PrivateDnsConfiguration::getStatus().
mObserver.removeFromServerStateMap(kServer1);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
}
TEST_F(PrivateDnsConfigurationTest, Validation_NetworkDestroyedOrOffMode) {
for (const std::string_view config : {"OFF", "NETWORK_DESTROYED"}) {
SCOPED_TRACE(config);
backend.setDeferredResp(true);
testing::InSequence seq;
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 1; }));
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
if (config == "OFF") {
EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
} else if (config == "NETWORK_DESTROYED") {
mPdc.clear(kNetId);
}
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
backend.setDeferredResp(false);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
mObserver.removeFromServerStateMap(kServer1);
expectPrivateDnsStatus(PrivateDnsMode::OFF);
}
}
TEST_F(PrivateDnsConfigurationTest, NoValidation) {
// If onValidationStateUpdate() is called, the test will fail with uninteresting mock
// function calls in the end of the test.
const auto expectStatus = [&]() {
const PrivateDnsStatus status = mPdc.getStatus(kNetId);
EXPECT_EQ(status.mode, PrivateDnsMode::OFF);
EXPECT_THAT(status.serversMap, testing::IsEmpty());
};
EXPECT_EQ(mPdc.set(kNetId, kMark, {"invalid_addr"}, {}, {}), -EINVAL);
expectStatus();
EXPECT_EQ(mPdc.set(kNetId, kMark, {}, {}, {}), 0);
expectStatus();
}
TEST_F(PrivateDnsConfigurationTest, ServerIdentity_Comparison) {
DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 853));
server.name = "dns.example.com";
// Different socket address.
DnsTlsServer other = server;
EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.1", 5353);
EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
other.ss = netdutils::IPSockAddr::toIPSockAddr("127.0.0.2", 853);
EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
// Different provider hostname.
other = server;
EXPECT_EQ(ServerIdentity(server), ServerIdentity(other));
other.name = "other.example.com";
EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
other.name = "";
EXPECT_NE(ServerIdentity(server), ServerIdentity(other));
}
TEST_F(PrivateDnsConfigurationTest, RequestValidation) {
const DnsTlsServer server(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
const ServerIdentity identity(server);
testing::InSequence seq;
for (const std::string_view config : {"SUCCESS", "IN_PROGRESS", "FAIL"}) {
SCOPED_TRACE(config);
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
if (config == "SUCCESS") {
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
} else if (config == "IN_PROGRESS") {
backend.setDeferredResp(true);
} else {
// config = "FAIL"
ASSERT_TRUE(backend.stopServer());
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::fail, kNetId));
}
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
// Wait until the validation state is transitioned.
const int runningThreads = (config == "IN_PROGRESS") ? 1 : 0;
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == runningThreads; }));
if (config == "SUCCESS") {
EXPECT_CALL(mObserver,
onValidationStateUpdate(kServer1, Validation::in_process, kNetId));
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
EXPECT_TRUE(mPdc.requestValidation(kNetId, identity, kMark).ok());
} else if (config == "IN_PROGRESS") {
EXPECT_CALL(mObserver, onValidationStateUpdate(kServer1, Validation::success, kNetId));
EXPECT_FALSE(mPdc.requestValidation(kNetId, identity, kMark).ok());
} else if (config == "FAIL") {
EXPECT_FALSE(mPdc.requestValidation(kNetId, identity, kMark).ok());
}
// Resending the same request or requesting nonexistent servers are denied.
EXPECT_FALSE(mPdc.requestValidation(kNetId, identity, kMark).ok());
EXPECT_FALSE(mPdc.requestValidation(kNetId, identity, kMark + 1).ok());
EXPECT_FALSE(mPdc.requestValidation(kNetId + 1, identity, kMark).ok());
// Reset the test state.
backend.setDeferredResp(false);
backend.startServer();
// Ensure the status of mObserver is synced.
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
mPdc.clear(kNetId);
}
}
TEST_F(PrivateDnsConfigurationTest, GetPrivateDns) {
const DnsTlsServer server1(netdutils::IPSockAddr::toIPSockAddr(kServer1, 853));
const DnsTlsServer server2(netdutils::IPSockAddr::toIPSockAddr(kServer2, 853));
EXPECT_FALSE(hasPrivateDnsServer(ServerIdentity(server1), kNetId));
EXPECT_FALSE(hasPrivateDnsServer(ServerIdentity(server2), kNetId));
// Suppress the warning.
EXPECT_CALL(mObserver, onValidationStateUpdate).Times(2);
EXPECT_EQ(mPdc.set(kNetId, kMark, {kServer1}, {}, {}), 0);
expectPrivateDnsStatus(PrivateDnsMode::OPPORTUNISTIC);
EXPECT_TRUE(hasPrivateDnsServer(ServerIdentity(server1), kNetId));
EXPECT_FALSE(hasPrivateDnsServer(ServerIdentity(server2), kNetId));
EXPECT_FALSE(hasPrivateDnsServer(ServerIdentity(server1), kNetId + 1));
ASSERT_TRUE(PollForCondition([&]() { return mObserver.runningThreads == 0; }));
}
// TODO: add ValidationFail_Strict test.
} // namespace android::net