blob: c0adaf56bb178477817183ff4ab7dd4f5e6072d2 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/dns/host_resolver_manager_request_impl.h"
#include <deque>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "base/containers/linked_list.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/safe_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequence_checker.h"
#include "base/time/tick_clock.h"
#include "net/base/address_list.h"
#include "net/base/completion_once_callback.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/request_priority.h"
#include "net/dns/dns_alias_utility.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver.h"
#include "net/dns/host_resolver_manager.h"
#include "net/dns/host_resolver_manager_job.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/dns/public/resolve_error_info.h"
#include "net/http/http_network_session.h"
#include "net/log/net_log_with_source.h"
#include "net/socket/client_socket_factory.h"
#include "net/url_request/url_request_context.h"
namespace net {
HostResolverManager::RequestImpl::RequestImpl(
NetLogWithSource source_net_log,
HostResolver::Host request_host,
NetworkAnonymizationKey network_anonymization_key,
std::optional<ResolveHostParameters> optional_parameters,
base::WeakPtr<ResolveContext> resolve_context,
base::WeakPtr<HostResolverManager> resolver,
const base::TickClock* tick_clock)
: source_net_log_(std::move(source_net_log)),
request_host_(std::move(request_host)),
network_anonymization_key_(
NetworkAnonymizationKey::IsPartitioningEnabled()
? std::move(network_anonymization_key)
: NetworkAnonymizationKey()),
parameters_(optional_parameters ? std::move(optional_parameters).value()
: ResolveHostParameters()),
resolve_context_(std::move(resolve_context)),
priority_(parameters_.initial_priority),
job_key_(request_host_, resolve_context_.get()),
resolver_(std::move(resolver)),
tick_clock_(tick_clock) {}
HostResolverManager::RequestImpl::~RequestImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!job_.has_value()) {
return;
}
job_.value()->CancelRequest(this);
LogCancelRequest();
}
int HostResolverManager::RequestImpl::Start(CompletionOnceCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
// Start() may only be called once per request.
CHECK(!job_.has_value());
DCHECK(!complete_);
DCHECK(!callback_);
// Parent HostResolver must still be alive to call Start().
DCHECK(resolver_);
if (!resolve_context_) {
complete_ = true;
resolver_.reset();
set_error_info(ERR_CONTEXT_SHUT_DOWN, false);
return ERR_NAME_NOT_RESOLVED;
}
LogStartRequest();
next_state_ = STATE_IPV6_REACHABILITY;
callback_ = std::move(callback);
int rv = OK;
rv = DoLoop(rv);
return rv;
}
const AddressList* HostResolverManager::RequestImpl::GetAddressResults() const {
DCHECK(complete_);
return base::OptionalToPtr(legacy_address_results_);
}
const std::vector<HostResolverEndpointResult>*
HostResolverManager::RequestImpl::GetEndpointResults() const {
DCHECK(complete_);
return base::OptionalToPtr(endpoint_results_);
}
const std::vector<std::string>*
HostResolverManager::RequestImpl::GetTextResults() const {
DCHECK(complete_);
return results_ ? &results_.value().text_records() : nullptr;
}
const std::vector<HostPortPair>*
HostResolverManager::RequestImpl::GetHostnameResults() const {
DCHECK(complete_);
return results_ ? &results_.value().hostnames() : nullptr;
}
const std::set<std::string>*
HostResolverManager::RequestImpl::GetDnsAliasResults() const {
DCHECK(complete_);
// If `include_canonical_name` param was true, should only ever have at most
// a single alias, representing the expected "canonical name".
#if DCHECK_IS_ON()
if (parameters().include_canonical_name && fixed_up_dns_alias_results_) {
DCHECK_LE(fixed_up_dns_alias_results_->size(), 1u);
if (GetAddressResults()) {
std::set<std::string> address_list_aliases_set(
GetAddressResults()->dns_aliases().begin(),
GetAddressResults()->dns_aliases().end());
DCHECK(address_list_aliases_set == fixed_up_dns_alias_results_.value());
}
}
#endif // DCHECK_IS_ON()
return base::OptionalToPtr(fixed_up_dns_alias_results_);
}
const std::vector<bool>*
HostResolverManager::RequestImpl::GetExperimentalResultsForTesting() const {
DCHECK(complete_);
return results_ ? &results_.value().https_record_compatibility() : nullptr;
}
net::ResolveErrorInfo HostResolverManager::RequestImpl::GetResolveErrorInfo()
const {
DCHECK(complete_);
return error_info_;
}
const std::optional<HostCache::EntryStaleness>&
HostResolverManager::RequestImpl::GetStaleInfo() const {
DCHECK(complete_);
return stale_info_;
}
void HostResolverManager::RequestImpl::ChangeRequestPriority(
RequestPriority priority) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!job_.has_value()) {
priority_ = priority;
return;
}
job_.value()->ChangeRequestPriority(this, priority);
}
void HostResolverManager::RequestImpl::set_results(HostCache::Entry results) {
// Should only be called at most once and before request is marked
// completed.
DCHECK(!complete_);
DCHECK(!results_);
DCHECK(!parameters_.is_speculative);
results_ = std::move(results);
FixUpEndpointAndAliasResults();
}
void HostResolverManager::RequestImpl::set_error_info(
int error,
bool is_secure_network_error) {
error_info_ = ResolveErrorInfo(error, is_secure_network_error);
}
void HostResolverManager::RequestImpl::set_stale_info(
HostCache::EntryStaleness stale_info) {
// Should only be called at most once and before request is marked
// completed.
DCHECK(!complete_);
DCHECK(!stale_info_);
DCHECK(!parameters_.is_speculative);
stale_info_ = std::move(stale_info);
}
void HostResolverManager::RequestImpl::AssignJob(base::SafeRef<Job> job) {
CHECK(!job_.has_value());
job_ = std::move(job);
}
const HostResolverManager::JobKey& HostResolverManager::RequestImpl::GetJobKey()
const {
CHECK(job_.has_value());
return job_.value()->key();
}
void HostResolverManager::RequestImpl::OnJobCancelled(const JobKey& job_key) {
CHECK(job_.has_value());
CHECK(job_key == job_.value()->key());
job_.reset();
DCHECK(!complete_);
DCHECK(callback_);
callback_.Reset();
// No results should be set.
DCHECK(!results_);
LogCancelRequest();
}
void HostResolverManager::RequestImpl::OnJobCompleted(
const JobKey& job_key,
int error,
bool is_secure_network_error) {
set_error_info(error, is_secure_network_error);
CHECK(job_.has_value());
CHECK(job_key == job_.value()->key());
job_.reset();
DCHECK(!complete_);
complete_ = true;
LogFinishRequest(error, true /* async_completion */);
DCHECK(callback_);
std::move(callback_).Run(HostResolver::SquashErrorCode(error));
}
int HostResolverManager::RequestImpl::DoLoop(int rv) {
do {
ResolveState state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_IPV6_REACHABILITY:
rv = DoIPv6Reachability();
break;
case STATE_GET_PARAMETERS:
DCHECK_EQ(OK, rv);
rv = DoGetParameters();
break;
case STATE_GET_PARAMETERS_COMPLETE:
rv = DoGetParametersComplete(rv);
break;
case STATE_RESOLVE_LOCALLY:
rv = DoResolveLocally();
break;
case STATE_START_JOB:
rv = DoStartJob();
break;
case STATE_FINISH_REQUEST:
rv = DoFinishRequest(rv);
break;
default:
NOTREACHED() << "next_state_: " << next_state_;
break;
}
} while (next_state_ != STATE_NONE && rv != ERR_IO_PENDING);
return rv;
}
void HostResolverManager::RequestImpl::OnIOComplete(int rv) {
rv = DoLoop(rv);
if (rv != ERR_IO_PENDING && !callback_.is_null()) {
std::move(callback_).Run(rv);
}
}
int HostResolverManager::RequestImpl::DoIPv6Reachability() {
next_state_ = STATE_GET_PARAMETERS;
// If a single reachability probe has not been completed, and the latest
// probe will return asynchronously, return ERR_NAME_NOT_RESOLVED when the
// request source is LOCAL_ONLY. This is due to LOCAL_ONLY requiring a
// synchronous response, so it cannot wait on an async probe result and
// cannot make assumptions about reachability.
if (parameters_.source == HostResolverSource::LOCAL_ONLY) {
int rv = resolver_->StartIPv6ReachabilityCheck(
source_net_log_, GetClientSocketFactory(),
base::DoNothingAs<void(int)>());
if (rv == ERR_IO_PENDING) {
next_state_ = STATE_FINISH_REQUEST;
return ERR_NAME_NOT_RESOLVED;
}
return OK;
}
return resolver_->StartIPv6ReachabilityCheck(
source_net_log_, GetClientSocketFactory(),
base::BindOnce(&RequestImpl::OnIOComplete,
weak_ptr_factory_.GetWeakPtr()));
}
int HostResolverManager::RequestImpl::DoGetParameters() {
resolver_->InitializeJobKeyAndIPAddress(network_anonymization_key_,
parameters_, source_net_log_,
job_key_, ip_address_);
// A reachability probe to determine if the network is only reachable on
// IPv6 will be scheduled if the parameters are met for using NAT64 in place
// of an IPv4 address.
if (HostResolver::MayUseNAT64ForIPv4Literal(
job_key_.flags, parameters_.source, ip_address_) &&
resolver_->last_ipv6_probe_result_) {
next_state_ = STATE_GET_PARAMETERS_COMPLETE;
return resolver_->StartGloballyReachableCheck(
ip_address_, source_net_log_, GetClientSocketFactory(),
base::BindOnce(&RequestImpl::OnIOComplete,
weak_ptr_factory_.GetWeakPtr()));
}
next_state_ = STATE_RESOLVE_LOCALLY;
return OK;
}
int HostResolverManager::RequestImpl::DoGetParametersComplete(int rv) {
next_state_ = STATE_RESOLVE_LOCALLY;
only_ipv6_reachable_ = (rv == ERR_FAILED) ? true : false;
return OK;
}
int HostResolverManager::RequestImpl::DoResolveLocally() {
std::optional<HostCache::EntryStaleness> stale_info;
HostCache::Entry results = resolver_->ResolveLocally(
only_ipv6_reachable_, job_key_, ip_address_, parameters_.cache_usage,
parameters_.secure_dns_policy, parameters_.source, source_net_log_,
host_cache(), &tasks_, &stale_info);
if (results.error() != ERR_DNS_CACHE_MISS ||
parameters_.source == HostResolverSource::LOCAL_ONLY || tasks_.empty()) {
if (results.error() == OK && !parameters_.is_speculative) {
set_results(results.CopyWithDefaultPort(request_host_.GetPort()));
}
if (stale_info && !parameters_.is_speculative) {
set_stale_info(std::move(stale_info).value());
}
next_state_ = STATE_FINISH_REQUEST;
return results.error();
}
next_state_ = STATE_START_JOB;
return OK;
}
int HostResolverManager::RequestImpl::DoStartJob() {
resolver_->CreateAndStartJob(std::move(job_key_), std::move(tasks_), this);
DCHECK(!complete_);
resolver_.reset();
return ERR_IO_PENDING;
}
int HostResolverManager::RequestImpl::DoFinishRequest(int rv) {
CHECK(!job_.has_value());
complete_ = true;
set_error_info(rv, /*is_secure_network_error=*/false);
rv = HostResolver::SquashErrorCode(rv);
LogFinishRequest(rv, /*async_completion=*/false);
return rv;
}
void HostResolverManager::RequestImpl::FixUpEndpointAndAliasResults() {
DCHECK(results_.has_value());
DCHECK(!legacy_address_results_.has_value());
DCHECK(!endpoint_results_.has_value());
DCHECK(!fixed_up_dns_alias_results_.has_value());
endpoint_results_ = results_.value().GetEndpoints();
if (endpoint_results_.has_value()) {
fixed_up_dns_alias_results_ = results_.value().aliases();
// Skip fixups for `include_canonical_name` requests. Just use the
// canonical name exactly as it was received from the system resolver.
if (parameters().include_canonical_name) {
DCHECK_LE(fixed_up_dns_alias_results_.value().size(), 1u);
} else {
fixed_up_dns_alias_results_ = dns_alias_utility::FixUpDnsAliases(
fixed_up_dns_alias_results_.value());
}
legacy_address_results_ = HostResolver::EndpointResultToAddressList(
endpoint_results_.value(), fixed_up_dns_alias_results_.value());
}
}
void HostResolverManager::RequestImpl::LogStartRequest() {
DCHECK(request_time_.is_null());
request_time_ = tick_clock_->NowTicks();
source_net_log_.BeginEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_REQUEST, [this] {
base::Value::Dict dict;
dict.Set("host", request_host_.ToString());
dict.Set("dns_query_type",
kDnsQueryTypes.at(parameters_.dns_query_type));
dict.Set("allow_cached_response",
parameters_.cache_usage !=
ResolveHostParameters::CacheUsage::DISALLOWED);
dict.Set("is_speculative", parameters_.is_speculative);
dict.Set("network_anonymization_key",
network_anonymization_key_.ToDebugString());
dict.Set("secure_dns_policy",
base::strict_cast<int>(parameters_.secure_dns_policy));
return dict;
});
}
void HostResolverManager::RequestImpl::LogFinishRequest(int net_error,
bool async_completion) {
source_net_log_.EndEventWithNetErrorCode(
NetLogEventType::HOST_RESOLVER_MANAGER_REQUEST, net_error);
if (!parameters_.is_speculative) {
DCHECK(!request_time_.is_null());
base::TimeDelta duration = tick_clock_->NowTicks() - request_time_;
UMA_HISTOGRAM_MEDIUM_TIMES("Net.DNS.Request.TotalTime", duration);
if (async_completion) {
UMA_HISTOGRAM_MEDIUM_TIMES("Net.DNS.Request.TotalTimeAsync", duration);
}
}
}
void HostResolverManager::RequestImpl::LogCancelRequest() {
source_net_log_.AddEvent(NetLogEventType::CANCELLED);
source_net_log_.EndEvent(NetLogEventType::HOST_RESOLVER_MANAGER_REQUEST);
}
ClientSocketFactory*
HostResolverManager::RequestImpl::GetClientSocketFactory() {
if (resolve_context_->url_request_context()) {
return resolve_context_->url_request_context()
->GetNetworkSessionContext()
->client_socket_factory;
} else {
return ClientSocketFactory::GetDefaultFactory();
}
}
} // namespace net