blob: 91e17faa17c7c387637590524ded2a64ddeff8d1 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/simple_host_resolver.h"
#include <string_view>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/host_port_pair.h"
#include "net/dns/host_resolver.h"
#include "services/network/test/test_network_context_with_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace network {
namespace {
net::IPEndPoint CreateExpectedEndPoint(std::string_view address,
uint16_t port) {
net::IPAddress ip_address;
CHECK(ip_address.AssignFromIPLiteral(address));
return net::IPEndPoint(ip_address, port);
}
std::string CreateMappingRules(
std::vector<std::pair<std::string_view, std::string_view>>
host_ip_address_pairs) {
std::vector<std::string> map_rules;
for (auto [host, ip_address] : host_ip_address_pairs) {
map_rules.push_back(
base::StringPrintf("MAP %s %s", host.data(), ip_address.data()));
}
return base::JoinString(map_rules, ",");
}
class MockNetworkContext : public TestNetworkContextWithHostResolver {
public:
explicit MockNetworkContext(std::unique_ptr<net::HostResolver> host_resolver)
: TestNetworkContextWithHostResolver(std::move(host_resolver)) {}
static std::unique_ptr<MockNetworkContext> CreateNetworkContext(
std::string_view host_mapping_rules) {
return std::make_unique<MockNetworkContext>(
net::HostResolver::CreateStandaloneResolver(
net::NetLog::Get(), /*options=*/std::nullopt, host_mapping_rules,
/*enable_caching=*/false));
}
// Resets ResolveHostClient when matching |host| is supplied in ResolveHost().
void SetResetClientFor(std::optional<net::HostPortPair> reset_client_for) {
reset_client_for_ = std::move(reset_client_for);
}
void ResolveHost(
network::mojom::HostResolverHostPtr host,
const ::net::NetworkAnonymizationKey& network_anonymization_key,
network::mojom::ResolveHostParametersPtr optional_parameters,
::mojo::PendingRemote<network::mojom::ResolveHostClient>
pending_response_client) override {
DCHECK(host->is_host_port_pair());
if (reset_client_for_ == host->get_host_port_pair()) {
pending_response_client.reset();
return;
}
ResolveHostImpl(std::move(host), network_anonymization_key,
std::move(optional_parameters),
std::move(pending_response_client));
}
std::optional<net::HostPortPair> reset_client_for_;
};
class SimpleHostResolverTest : public testing::Test {
public:
SimpleHostResolverTest() = default;
protected:
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::MainThreadType::IO};
};
struct HostResolverResult {
int result;
std::optional<net::IPEndPoint> resolved_address;
};
struct HostResolverRequest {
net::HostPortPair host_port_pair;
// Simulates mojo pipe break for a specific request.
bool reset_client = false;
};
using ResolveHostFuture =
base::test::TestFuture<int,
const net::ResolveErrorInfo&,
const net::AddressList&,
const net::HostResolverEndpointResults&>;
TEST_F(SimpleHostResolverTest, ResolveFourAddresses) {
auto network_context = MockNetworkContext::CreateNetworkContext(
CreateMappingRules({{"example.test", "98.76.54.32"},
{"another-example.test", "11.22.33.44"}}));
auto simple_resolver = SimpleHostResolver::Create(network_context.get());
// Send four ResolveHost requests:
// * #1 passes
// * #2 gets ERR_NAME_NOT_RESOLVED
// * #3 passes
// * #4 receives a pipe break and responds with ERR_FAILED
std::vector<std::pair<HostResolverRequest, HostResolverResult>> test_cases = {
{{.host_port_pair = net::HostPortPair("example.test", 160)},
{.result = net::OK,
.resolved_address = CreateExpectedEndPoint("98.76.54.32", 160)}},
{{.host_port_pair = net::HostPortPair("unknown", 15)},
{.result = net::ERR_NAME_NOT_RESOLVED}},
{{.host_port_pair = net::HostPortPair("another-example.test", 85)},
{.result = net::OK,
.resolved_address = CreateExpectedEndPoint("11.22.33.44", 85)}},
{{.host_port_pair = net::HostPortPair("example.test", 3800),
.reset_client = true},
{.result = net::ERR_FAILED}}};
EXPECT_EQ(simple_resolver->GetNumOutstandingRequestsForTesting(), 0U);
std::vector<std::pair<std::unique_ptr<ResolveHostFuture>, HostResolverResult>>
futures;
for (auto [request, result] : test_cases) {
auto future = std::make_unique<ResolveHostFuture>();
if (request.reset_client) {
network_context->SetResetClientFor(request.host_port_pair);
}
// The ios simulator may return the NAT64 address rather than
// the IPv4 address. The parameters specify the dns query type
// to resolve the issue.
auto resolver_parameters = network::mojom::ResolveHostParameters::New();
resolver_parameters->dns_query_type = net::DnsQueryType::A;
simple_resolver->ResolveHost(
network::mojom::HostResolverHost::NewHostPortPair(
request.host_port_pair),
net::NetworkAnonymizationKey(), std::move(resolver_parameters),
future->GetCallback());
futures.emplace_back(std::move(future), std::move(result));
}
EXPECT_EQ(simple_resolver->GetNumOutstandingRequestsForTesting(), 4U);
for (const auto& [future, resolver_result] : futures) {
const auto& [result, resolve_error_info, resolved_addresses,
alternative_endpoints] = future->Get();
EXPECT_EQ(result, resolver_result.result);
if (!resolver_result.resolved_address) {
EXPECT_THAT(resolved_addresses, testing::IsEmpty());
} else {
EXPECT_THAT(resolved_addresses.endpoints(),
testing::ElementsAre(*resolver_result.resolved_address));
}
}
// Verify that all receivers have been erased.
EXPECT_EQ(simple_resolver->GetNumOutstandingRequestsForTesting(), 0U);
}
} // namespace
} // namespace network