// 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 #include #include #include #include #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 optional_parameters, base::WeakPtr resolve_context, base::WeakPtr 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* HostResolverManager::RequestImpl::GetEndpointResults() const { DCHECK(complete_); return base::OptionalToPtr(endpoint_results_); } const std::vector* HostResolverManager::RequestImpl::GetTextResults() const { DCHECK(complete_); return results_ ? &results_.value().text_records() : nullptr; } const std::vector* HostResolverManager::RequestImpl::GetHostnameResults() const { DCHECK(complete_); return results_ ? &results_.value().hostnames() : nullptr; } const std::set* 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 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* 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& 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) { 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_; } } 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()); 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 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(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_; DEPRECATED_UMA_HISTOGRAM_MEDIUM_TIMES("Net.DNS.Request.TotalTime", duration); if (async_completion) { DEPRECATED_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