blob: 904b2daf936294797cba59c88266786f1355185f [file] [log] [blame]
// Copyright 2012 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.h"
#include <cmath>
#include <cstdint>
#include <iterator>
#include <limits>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/circular_deque.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/containers/linked_list.h"
#include "base/debug/debugger.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/safe_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/not_fatal_until.h"
#include "base/numerics/safe_conversions.h"
#include "base/observer_list.h"
#include "base/ranges/algorithm.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "base/types/optional_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "net/base/address_family.h"
#include "net/base/address_list.h"
#include "net/base/completion_once_callback.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/network_change_notifier.h"
#include "net/base/network_interfaces.h"
#include "net/base/prioritized_dispatcher.h"
#include "net/base/request_priority.h"
#include "net/base/trace_constants.h"
#include "net/base/tracing.h"
#include "net/base/url_util.h"
#include "net/dns/dns_alias_utility.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_names_util.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_response_result_extractor.h"
#include "net/dns/dns_transaction.h"
#include "net/dns/dns_util.h"
#include "net/dns/host_cache.h"
#include "net/dns/host_resolver_dns_task.h"
#include "net/dns/host_resolver_internal_result.h"
#include "net/dns/host_resolver_manager_job.h"
#include "net/dns/host_resolver_manager_request_impl.h"
#include "net/dns/host_resolver_manager_service_endpoint_request_impl.h"
#include "net/dns/host_resolver_mdns_listener_impl.h"
#include "net/dns/host_resolver_mdns_task.h"
#include "net/dns/host_resolver_nat64_task.h"
#include "net/dns/host_resolver_proc.h"
#include "net/dns/host_resolver_system_task.h"
#include "net/dns/httpssvc_metrics.h"
#include "net/dns/loopback_only.h"
#include "net/dns/mdns_client.h"
#include "net/dns/public/dns_protocol.h"
#include "net/dns/public/dns_query_type.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/dns/public/resolve_error_info.h"
#include "net/dns/public/secure_dns_mode.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/dns/public/util.h"
#include "net/dns/record_parsed.h"
#include "net/dns/resolve_context.h"
#include "net/dns/test_dns_config_service.h"
#include "net/http/http_network_session.h"
#include "net/log/net_log.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/socket/client_socket_factory.h"
#include "net/url_request/url_request_context.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
#if BUILDFLAG(ENABLE_MDNS)
#include "net/dns/mdns_client_impl.h"
#endif
#if BUILDFLAG(IS_WIN)
#include <Winsock2.h>
#include "net/base/winsock_init.h"
#endif
#if BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#include <net/if.h>
#include "net/base/sys_addrinfo.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/build_info.h"
#else // !BUILDFLAG(IS_ANDROID)
#include <ifaddrs.h>
#endif // BUILDFLAG(IS_ANDROID)
#endif // BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
namespace net {
namespace {
// Limit the size of hostnames that will be resolved to combat issues in
// some platform's resolvers.
const size_t kMaxHostLength = 4096;
// Time between IPv6 probes, i.e. for how long results of each IPv6 probe are
// cached.
const int kIPv6ProbePeriodMs = 1000;
// Google DNS address used for IPv6 probes.
const uint8_t kIPv6ProbeAddress[] = {0x20, 0x01, 0x48, 0x60, 0x48, 0x60,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x88, 0x88};
// True if |hostname| ends with either ".local" or ".local.".
bool ResemblesMulticastDNSName(std::string_view hostname) {
return hostname.ends_with(".local") || hostname.ends_with(".local.");
}
bool ConfigureAsyncDnsNoFallbackFieldTrial() {
const bool kDefault = false;
// Configure the AsyncDns field trial as follows:
// groups AsyncDnsNoFallbackA and AsyncDnsNoFallbackB: return true,
// groups AsyncDnsA and AsyncDnsB: return false,
// groups SystemDnsA and SystemDnsB: return false,
// otherwise (trial absent): return default.
std::string group_name = base::FieldTrialList::FindFullName("AsyncDns");
if (!group_name.empty()) {
return base::StartsWith(group_name, "AsyncDnsNoFallback",
base::CompareCase::INSENSITIVE_ASCII);
}
return kDefault;
}
base::Value::Dict NetLogIPv6AvailableParams(bool ipv6_available, bool cached) {
base::Value::Dict dict;
dict.Set("ipv6_available", ipv6_available);
dict.Set("cached", cached);
return dict;
}
// Maximum of 64 concurrent resolver calls (excluding retries).
// Between 2010 and 2020, the limit was set to 6 because of a report of a broken
// home router that would fail in the presence of more simultaneous queries.
// In 2020, we conducted an experiment to see if this kind of router was still
// present on the Internet, and found no evidence of any remaining issues, so
// we increased the limit to 64 at that time.
const size_t kDefaultMaxSystemTasks = 64u;
PrioritizedDispatcher::Limits GetDispatcherLimits(
const HostResolver::ManagerOptions& options) {
PrioritizedDispatcher::Limits limits(NUM_PRIORITIES,
options.max_concurrent_resolves);
// If not using default, do not use the field trial.
if (limits.total_jobs != HostResolver::ManagerOptions::kDefaultParallelism)
return limits;
// Default, without trial is no reserved slots.
limits.total_jobs = kDefaultMaxSystemTasks;
// Parallelism is determined by the field trial.
std::string group =
base::FieldTrialList::FindFullName("HostResolverDispatch");
if (group.empty())
return limits;
// The format of the group name is a list of non-negative integers separated
// by ':'. Each of the elements in the list corresponds to an element in
// |reserved_slots|, except the last one which is the |total_jobs|.
std::vector<std::string_view> group_parts = base::SplitStringPiece(
group, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (group_parts.size() != NUM_PRIORITIES + 1) {
NOTREACHED_IN_MIGRATION();
return limits;
}
std::vector<size_t> parsed(group_parts.size());
for (size_t i = 0; i < group_parts.size(); ++i) {
if (!base::StringToSizeT(group_parts[i], &parsed[i])) {
NOTREACHED_IN_MIGRATION();
return limits;
}
}
const size_t total_jobs = parsed.back();
parsed.pop_back();
const size_t total_reserved_slots =
std::accumulate(parsed.begin(), parsed.end(), 0u);
// There must be some unreserved slots available for the all priorities.
if (total_reserved_slots > total_jobs ||
(total_reserved_slots == total_jobs && parsed[MINIMUM_PRIORITY] == 0)) {
NOTREACHED_IN_MIGRATION();
return limits;
}
limits.total_jobs = total_jobs;
limits.reserved_slots = parsed;
return limits;
}
base::Value::Dict NetLogResults(const HostCache::Entry& results) {
base::Value::Dict dict;
dict.Set("results", results.NetLogParams());
return dict;
}
std::vector<IPEndPoint> FilterAddresses(std::vector<IPEndPoint> addresses,
DnsQueryTypeSet query_types) {
DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
DCHECK(!query_types.empty());
const AddressFamily want_family =
HostResolver::DnsQueryTypeSetToAddressFamily(query_types);
if (want_family == ADDRESS_FAMILY_UNSPECIFIED)
return addresses;
// Keep only the endpoints that match `want_family`.
addresses.erase(
base::ranges::remove_if(
addresses,
[want_family](AddressFamily family) { return family != want_family; },
&IPEndPoint::GetFamily),
addresses.end());
return addresses;
}
int GetPortForGloballyReachableCheck() {
if (!base::FeatureList::IsEnabled(
features::kUseAlternativePortForGloballyReachableCheck)) {
return 443;
}
return features::kAlternativePortForGloballyReachableCheck.Get();
}
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(DnsClientCapability)
enum class DnsClientCapability {
kSecureDisabledInsecureDisabled = 0,
kSecureDisabledInsecureEnabled = 1,
kSecureEnabledInsecureDisabled = 2,
kSecureEnabledInsecureEnabled = 3,
kMaxValue = kSecureEnabledInsecureEnabled,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/net/enums.xml:DnsClientCapability)
void RecordDnsClientCapabilityMetrics(const DnsClient* dns_client) {
if (!dns_client) {
return;
}
DnsClientCapability capability;
if (dns_client->CanUseSecureDnsTransactions()) {
if (dns_client->CanUseInsecureDnsTransactions()) {
capability = DnsClientCapability::kSecureEnabledInsecureEnabled;
} else {
capability = DnsClientCapability::kSecureEnabledInsecureDisabled;
}
} else {
if (dns_client->CanUseInsecureDnsTransactions()) {
capability = DnsClientCapability::kSecureDisabledInsecureEnabled;
} else {
capability = DnsClientCapability::kSecureDisabledInsecureDisabled;
}
}
base::UmaHistogramEnumeration("Net.DNS.DnsConfig.DnsClientCapability",
capability);
}
} // namespace
//-----------------------------------------------------------------------------
bool ResolveLocalHostname(std::string_view host,
std::vector<IPEndPoint>* address_list) {
address_list->clear();
if (!IsLocalHostname(host))
return false;
address_list->emplace_back(IPAddress::IPv6Localhost(), 0);
address_list->emplace_back(IPAddress::IPv4Localhost(), 0);
return true;
}
class HostResolverManager::ProbeRequestImpl
: public HostResolver::ProbeRequest,
public ResolveContext::DohStatusObserver {
public:
ProbeRequestImpl(base::WeakPtr<ResolveContext> context,
base::WeakPtr<HostResolverManager> resolver)
: context_(std::move(context)), resolver_(std::move(resolver)) {}
ProbeRequestImpl(const ProbeRequestImpl&) = delete;
ProbeRequestImpl& operator=(const ProbeRequestImpl&) = delete;
~ProbeRequestImpl() override {
// Ensure that observers are deregistered to avoid wasting memory.
if (context_)
context_->UnregisterDohStatusObserver(this);
}
int Start() override {
DCHECK(resolver_);
DCHECK(!runner_);
if (!context_)
return ERR_CONTEXT_SHUT_DOWN;
context_->RegisterDohStatusObserver(this);
StartRunner(false /* network_change */);
return ERR_IO_PENDING;
}
// ResolveContext::DohStatusObserver
void OnSessionChanged() override { CancelRunner(); }
void OnDohServerUnavailable(bool network_change) override {
// Start the runner asynchronously, as this may trigger reentrant calls into
// HostResolverManager, which are not allowed during notification handling.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&ProbeRequestImpl::StartRunner,
weak_ptr_factory_.GetWeakPtr(), network_change));
}
private:
void StartRunner(bool network_change) {
DCHECK(resolver_);
DCHECK(!resolver_->invalidation_in_progress_);
if (!context_)
return; // Reachable if the context ends before a posted task runs.
if (!runner_)
runner_ = resolver_->CreateDohProbeRunner(context_.get());
if (runner_)
runner_->Start(network_change);
}
void CancelRunner() {
runner_.reset();
// Cancel any asynchronous StartRunner() calls.
weak_ptr_factory_.InvalidateWeakPtrs();
}
base::WeakPtr<ResolveContext> context_;
std::unique_ptr<DnsProbeRunner> runner_;
base::WeakPtr<HostResolverManager> resolver_;
base::WeakPtrFactory<ProbeRequestImpl> weak_ptr_factory_{this};
};
//-----------------------------------------------------------------------------
HostResolverManager::HostResolverManager(
const HostResolver::ManagerOptions& options,
SystemDnsConfigChangeNotifier* system_dns_config_notifier,
NetLog* net_log)
: HostResolverManager(PassKey(),
options,
system_dns_config_notifier,
handles::kInvalidNetworkHandle,
net_log) {}
HostResolverManager::HostResolverManager(
base::PassKey<HostResolverManager>,
const HostResolver::ManagerOptions& options,
SystemDnsConfigChangeNotifier* system_dns_config_notifier,
handles::NetworkHandle target_network,
NetLog* net_log)
: host_resolver_system_params_(nullptr, options.max_system_retry_attempts),
net_log_(net_log),
system_dns_config_notifier_(system_dns_config_notifier),
target_network_(target_network),
check_ipv6_on_wifi_(options.check_ipv6_on_wifi),
ipv6_reachability_override_(base::FeatureList::IsEnabled(
features::kEnableIPv6ReachabilityOverride)),
tick_clock_(base::DefaultTickClock::GetInstance()),
https_svcb_options_(
options.https_svcb_options
? *options.https_svcb_options
: HostResolver::HttpsSvcbOptions::FromFeatures()) {
PrioritizedDispatcher::Limits job_limits = GetDispatcherLimits(options);
dispatcher_ = std::make_unique<PrioritizedDispatcher>(job_limits);
max_queued_jobs_ = job_limits.total_jobs * 100u;
DCHECK_GE(dispatcher_->num_priorities(), static_cast<size_t>(NUM_PRIORITIES));
#if BUILDFLAG(IS_WIN)
EnsureWinsockInit();
#endif
#if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID)) || \
BUILDFLAG(IS_FUCHSIA)
RunLoopbackProbeJob();
#endif
// Network-bound HostResolverManagers don't need to act on network changes.
if (!IsBoundToNetwork()) {
NetworkChangeNotifier::AddIPAddressObserver(this);
NetworkChangeNotifier::AddConnectionTypeObserver(this);
}
if (system_dns_config_notifier_)
system_dns_config_notifier_->AddObserver(this);
EnsureSystemHostResolverCallReady();
auto connection_type =
IsBoundToNetwork()
? NetworkChangeNotifier::GetNetworkConnectionType(target_network)
: NetworkChangeNotifier::GetConnectionType();
UpdateConnectionType(connection_type);
#if defined(ENABLE_BUILT_IN_DNS)
dns_client_ = DnsClient::CreateClient(net_log_);
dns_client_->SetInsecureEnabled(
options.insecure_dns_client_enabled,
options.additional_types_via_insecure_dns_enabled);
dns_client_->SetConfigOverrides(options.dns_config_overrides);
#else
DCHECK(options.dns_config_overrides == DnsConfigOverrides());
#endif
allow_fallback_to_systemtask_ = !ConfigureAsyncDnsNoFallbackFieldTrial();
}
HostResolverManager::~HostResolverManager() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// Prevent the dispatcher from starting new jobs.
dispatcher_->SetLimitsToZero();
// It's now safe for Jobs to call KillDnsTask on destruction, because
// OnJobComplete will not start any new jobs.
jobs_.clear();
if (target_network_ == handles::kInvalidNetworkHandle) {
NetworkChangeNotifier::RemoveIPAddressObserver(this);
NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
}
if (system_dns_config_notifier_)
system_dns_config_notifier_->RemoveObserver(this);
}
// static
std::unique_ptr<HostResolverManager>
HostResolverManager::CreateNetworkBoundHostResolverManager(
const HostResolver::ManagerOptions& options,
handles::NetworkHandle target_network,
NetLog* net_log) {
#if BUILDFLAG(IS_ANDROID)
DCHECK(NetworkChangeNotifier::AreNetworkHandlesSupported());
return std::make_unique<HostResolverManager>(
PassKey(), options, nullptr /* system_dns_config_notifier */,
target_network, net_log);
#else // !BUILDFLAG(IS_ANDROID)
NOTIMPLEMENTED();
return nullptr;
#endif // BUILDFLAG(IS_ANDROID)
}
std::unique_ptr<HostResolver::ResolveHostRequest>
HostResolverManager::CreateRequest(
absl::variant<url::SchemeHostPort, HostPortPair> host,
NetworkAnonymizationKey network_anonymization_key,
NetLogWithSource net_log,
std::optional<ResolveHostParameters> optional_parameters,
ResolveContext* resolve_context) {
return CreateRequest(HostResolver::Host(std::move(host)),
std::move(network_anonymization_key), std::move(net_log),
std::move(optional_parameters), resolve_context);
}
std::unique_ptr<HostResolver::ResolveHostRequest>
HostResolverManager::CreateRequest(
HostResolver::Host host,
NetworkAnonymizationKey network_anonymization_key,
NetLogWithSource net_log,
std::optional<ResolveHostParameters> optional_parameters,
ResolveContext* resolve_context) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!invalidation_in_progress_);
DCHECK_EQ(resolve_context->GetTargetNetwork(), target_network_);
// ResolveContexts must register (via RegisterResolveContext()) before use to
// ensure cached data is invalidated on network and configuration changes.
DCHECK(registered_contexts_.HasObserver(resolve_context));
return std::make_unique<RequestImpl>(
std::move(net_log), std::move(host), std::move(network_anonymization_key),
std::move(optional_parameters), resolve_context->GetWeakPtr(),
weak_ptr_factory_.GetWeakPtr(), tick_clock_);
}
std::unique_ptr<HostResolver::ProbeRequest>
HostResolverManager::CreateDohProbeRequest(ResolveContext* context) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return std::make_unique<ProbeRequestImpl>(context->GetWeakPtr(),
weak_ptr_factory_.GetWeakPtr());
}
std::unique_ptr<HostResolver::MdnsListener>
HostResolverManager::CreateMdnsListener(const HostPortPair& host,
DnsQueryType query_type) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK_NE(DnsQueryType::UNSPECIFIED, query_type);
auto listener =
std::make_unique<HostResolverMdnsListenerImpl>(host, query_type);
MDnsClient* client;
int rv = GetOrCreateMdnsClient(&client);
if (rv == OK) {
std::unique_ptr<net::MDnsListener> inner_listener = client->CreateListener(
DnsQueryTypeToQtype(query_type), host.host(), listener.get());
listener->set_inner_listener(std::move(inner_listener));
} else {
listener->set_initialization_error(rv);
}
return listener;
}
std::unique_ptr<HostResolver::ServiceEndpointRequest>
HostResolverManager::CreateServiceEndpointRequest(
url::SchemeHostPort scheme_host_port,
NetworkAnonymizationKey network_anonymization_key,
NetLogWithSource net_log,
ResolveHostParameters parameters,
ResolveContext* resolve_context) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!invalidation_in_progress_);
DCHECK_EQ(resolve_context->GetTargetNetwork(), target_network_);
if (resolve_context) {
DCHECK(registered_contexts_.HasObserver(resolve_context));
}
return std::make_unique<ServiceEndpointRequestImpl>(
std::move(scheme_host_port), std::move(network_anonymization_key),
std::move(net_log), std::move(parameters),
resolve_context ? resolve_context->GetWeakPtr() : nullptr,
weak_ptr_factory_.GetWeakPtr(), tick_clock_);
}
void HostResolverManager::SetInsecureDnsClientEnabled(
bool enabled,
bool additional_dns_types_enabled) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!dns_client_)
return;
bool enabled_before = dns_client_->CanUseInsecureDnsTransactions();
bool additional_types_before =
enabled_before && dns_client_->CanQueryAdditionalTypesViaInsecureDns();
dns_client_->SetInsecureEnabled(enabled, additional_dns_types_enabled);
// Abort current tasks if `CanUseInsecureDnsTransactions()` changes or if
// insecure transactions are enabled and
// `CanQueryAdditionalTypesViaInsecureDns()` changes. Changes to allowing
// additional types don't matter if insecure transactions are completely
// disabled.
if (dns_client_->CanUseInsecureDnsTransactions() != enabled_before ||
(dns_client_->CanUseInsecureDnsTransactions() &&
dns_client_->CanQueryAdditionalTypesViaInsecureDns() !=
additional_types_before)) {
AbortInsecureDnsTasks(ERR_NETWORK_CHANGED, false /* fallback_only */);
}
}
base::Value::Dict HostResolverManager::GetDnsConfigAsValue() const {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
return dns_client_ ? dns_client_->GetDnsConfigAsValueForNetLog()
: base::Value::Dict();
}
void HostResolverManager::SetDnsConfigOverrides(DnsConfigOverrides overrides) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (!dns_client_ && overrides == DnsConfigOverrides())
return;
// Not allowed to set overrides if compiled without DnsClient.
DCHECK(dns_client_);
bool transactions_allowed_before =
dns_client_->CanUseSecureDnsTransactions() ||
dns_client_->CanUseInsecureDnsTransactions();
bool changed = dns_client_->SetConfigOverrides(std::move(overrides));
if (changed) {
NetworkChangeNotifier::TriggerNonSystemDnsChange();
// Only invalidate cache if new overrides have resulted in a config change.
InvalidateCaches();
// Need to update jobs iff transactions were previously allowed because
// in-progress jobs may be running using a now-invalid configuration.
if (transactions_allowed_before) {
UpdateJobsForChangedConfig();
}
}
}
void HostResolverManager::RegisterResolveContext(ResolveContext* context) {
registered_contexts_.AddObserver(context);
context->InvalidateCachesAndPerSessionData(
dns_client_ ? dns_client_->GetCurrentSession() : nullptr,
false /* network_change */);
}
void HostResolverManager::DeregisterResolveContext(
const ResolveContext* context) {
registered_contexts_.RemoveObserver(context);
// Destroy Jobs when their context is closed.
RemoveAllJobs(context);
}
void HostResolverManager::SetTickClockForTesting(
const base::TickClock* tick_clock) {
tick_clock_ = tick_clock;
}
void HostResolverManager::SetIPv6ReachabilityOverride(
bool reachability_override) {
ipv6_reachability_override_ = reachability_override;
}
void HostResolverManager::SetMaxQueuedJobsForTesting(size_t value) {
DCHECK_EQ(0u, dispatcher_->num_queued_jobs());
DCHECK_GE(value, 0u);
max_queued_jobs_ = value;
}
void HostResolverManager::SetHaveOnlyLoopbackAddresses(bool result) {
if (result) {
additional_resolver_flags_ |= HOST_RESOLVER_LOOPBACK_ONLY;
} else {
additional_resolver_flags_ &= ~HOST_RESOLVER_LOOPBACK_ONLY;
}
}
void HostResolverManager::SetMdnsSocketFactoryForTesting(
std::unique_ptr<MDnsSocketFactory> socket_factory) {
DCHECK(!mdns_client_);
mdns_socket_factory_ = std::move(socket_factory);
}
void HostResolverManager::SetMdnsClientForTesting(
std::unique_ptr<MDnsClient> client) {
mdns_client_ = std::move(client);
}
void HostResolverManager::SetDnsClientForTesting(
std::unique_ptr<DnsClient> dns_client) {
DCHECK(dns_client);
if (dns_client_) {
if (!dns_client->GetSystemConfigForTesting())
dns_client->SetSystemConfig(dns_client_->GetSystemConfigForTesting());
dns_client->SetConfigOverrides(dns_client_->GetConfigOverridesForTesting());
}
dns_client_ = std::move(dns_client);
// Inform `registered_contexts_` of the new `DnsClient`.
InvalidateCaches();
}
void HostResolverManager::SetLastIPv6ProbeResultForTesting(
bool last_ipv6_probe_result) {
SetLastIPv6ProbeResult(last_ipv6_probe_result);
}
// static
bool HostResolverManager::IsLocalTask(TaskType task) {
switch (task) {
case TaskType::SECURE_CACHE_LOOKUP:
case TaskType::INSECURE_CACHE_LOOKUP:
case TaskType::CACHE_LOOKUP:
case TaskType::CONFIG_PRESET:
case TaskType::HOSTS:
return true;
default:
return false;
}
}
void HostResolverManager::InitializeJobKeyAndIPAddress(
const NetworkAnonymizationKey& network_anonymization_key,
const ResolveHostParameters& parameters,
const NetLogWithSource& source_net_log,
JobKey& out_job_key,
IPAddress& out_ip_address) {
out_job_key.network_anonymization_key = network_anonymization_key;
out_job_key.source = parameters.source;
const bool is_ip = out_ip_address.AssignFromIPLiteral(
out_job_key.host.GetHostnameWithoutBrackets());
out_job_key.secure_dns_mode =
GetEffectiveSecureDnsMode(parameters.secure_dns_policy);
out_job_key.flags = HostResolver::ParametersToHostResolverFlags(parameters) |
additional_resolver_flags_;
if (parameters.dns_query_type != DnsQueryType::UNSPECIFIED) {
out_job_key.query_types = {parameters.dns_query_type};
return;
}
DnsQueryTypeSet effective_types = {DnsQueryType::A, DnsQueryType::AAAA};
// Disable AAAA queries when we cannot do anything with the results.
bool use_local_ipv6 = true;
if (dns_client_) {
const DnsConfig* config = dns_client_->GetEffectiveConfig();
if (config) {
use_local_ipv6 = config->use_local_ipv6;
}
}
// When resolving IPv4 literals, there's no need to probe for IPv6. When
// resolving IPv6 literals, there's no benefit to artificially limiting our
// resolution based on a probe. Prior logic ensures that this is an automatic
// query, so the code requesting the resolution should be amenable to
// receiving an IPv6 resolution.
if (!use_local_ipv6 && !is_ip && !last_ipv6_probe_result_ &&
!ipv6_reachability_override_) {
out_job_key.flags |= HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
effective_types.Remove(DnsQueryType::AAAA);
}
// Optimistically enable feature-controlled queries. These queries may be
// skipped at a later point.
// `https_svcb_options_.enable` has precedence, so if enabled, ignore any
// other related features.
if (https_svcb_options_.enable && out_job_key.host.HasScheme()) {
static const char* const kSchemesForHttpsQuery[] = {
url::kHttpScheme, url::kHttpsScheme, url::kWsScheme, url::kWssScheme};
if (base::Contains(kSchemesForHttpsQuery, out_job_key.host.GetScheme())) {
effective_types.Put(DnsQueryType::HTTPS);
}
}
out_job_key.query_types = effective_types;
}
HostCache::Entry HostResolverManager::ResolveLocally(
bool only_ipv6_reachable,
const JobKey& job_key,
const IPAddress& ip_address,
ResolveHostParameters::CacheUsage cache_usage,
SecureDnsPolicy secure_dns_policy,
HostResolverSource source,
const NetLogWithSource& source_net_log,
HostCache* cache,
std::deque<TaskType>* out_tasks,
std::optional<HostCache::EntryStaleness>* out_stale_info) {
DCHECK(out_stale_info);
*out_stale_info = std::nullopt;
CreateTaskSequence(job_key, cache_usage, secure_dns_policy, out_tasks);
if (!ip_address.IsValid()) {
// Check that the caller supplied a valid hostname to resolve. For
// MULTICAST_DNS, we are less restrictive.
// TODO(ericorth): Control validation based on an explicit flag rather
// than implicitly based on |source|.
const bool is_valid_hostname =
job_key.source == HostResolverSource::MULTICAST_DNS
? dns_names_util::IsValidDnsName(job_key.host.GetHostname())
: IsCanonicalizedHostCompliant(job_key.host.GetHostname());
if (!is_valid_hostname) {
return HostCache::Entry(ERR_NAME_NOT_RESOLVED,
HostCache::Entry::SOURCE_UNKNOWN);
}
}
bool resolve_canonname = job_key.flags & HOST_RESOLVER_CANONNAME;
bool default_family_due_to_no_ipv6 =
job_key.flags & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6;
// The result of |getaddrinfo| for empty hosts is inconsistent across systems.
// On Windows it gives the default interface's address, whereas on Linux it
// gives an error. We will make it fail on all platforms for consistency.
if (job_key.host.GetHostname().empty() ||
job_key.host.GetHostname().size() > kMaxHostLength) {
return HostCache::Entry(ERR_NAME_NOT_RESOLVED,
HostCache::Entry::SOURCE_UNKNOWN);
}
if (ip_address.IsValid()) {
// Use NAT64Task for IPv4 literal when the network is IPv6 only.
if (HostResolver::MayUseNAT64ForIPv4Literal(job_key.flags, source,
ip_address) &&
only_ipv6_reachable) {
out_tasks->push_front(TaskType::NAT64);
return HostCache::Entry(ERR_DNS_CACHE_MISS,
HostCache::Entry::SOURCE_UNKNOWN);
}
return ResolveAsIP(job_key.query_types, resolve_canonname, ip_address);
}
// Special-case localhost names, as per the recommendations in
// https://tools.ietf.org/html/draft-west-let-localhost-be-localhost.
std::optional<HostCache::Entry> resolved =
ServeLocalhost(job_key.host.GetHostname(), job_key.query_types,
default_family_due_to_no_ipv6);
if (resolved)
return resolved.value();
// Do initial cache lookups.
while (!out_tasks->empty() && IsLocalTask(out_tasks->front())) {
TaskType task = out_tasks->front();
out_tasks->pop_front();
if (task == TaskType::SECURE_CACHE_LOOKUP ||
task == TaskType::INSECURE_CACHE_LOOKUP ||
task == TaskType::CACHE_LOOKUP) {
bool secure = task == TaskType::SECURE_CACHE_LOOKUP;
HostCache::Key key = job_key.ToCacheKey(secure);
bool ignore_secure = task == TaskType::CACHE_LOOKUP;
resolved = MaybeServeFromCache(cache, key, cache_usage, ignore_secure,
source_net_log, out_stale_info);
if (resolved) {
// |MaybeServeFromCache()| will update |*out_stale_info| as needed.
DCHECK(out_stale_info->has_value());
source_net_log.AddEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_CACHE_HIT,
[&] { return NetLogResults(resolved.value()); });
// TODO(crbug.com/40178456): Call StartBootstrapFollowup() if the Secure
// DNS Policy is kBootstrap and the result is not secure. Note: A naive
// implementation could cause an infinite loop if |resolved| always
// expires or is evicted before the followup runs.
return resolved.value();
}
DCHECK(!out_stale_info->has_value());
} else if (task == TaskType::CONFIG_PRESET) {
resolved = MaybeReadFromConfig(job_key);
if (resolved) {
source_net_log.AddEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_CONFIG_PRESET_MATCH,
[&] { return NetLogResults(resolved.value()); });
StartBootstrapFollowup(job_key, cache, source_net_log);
return resolved.value();
}
} else if (task == TaskType::HOSTS) {
resolved = ServeFromHosts(job_key.host.GetHostname(), job_key.query_types,
default_family_due_to_no_ipv6, *out_tasks);
if (resolved) {
source_net_log.AddEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_HOSTS_HIT,
[&] { return NetLogResults(resolved.value()); });
return resolved.value();
}
} else {
NOTREACHED_IN_MIGRATION();
}
}
return HostCache::Entry(ERR_DNS_CACHE_MISS, HostCache::Entry::SOURCE_UNKNOWN);
}
void HostResolverManager::CreateAndStartJob(JobKey key,
std::deque<TaskType> tasks,
RequestImpl* request) {
DCHECK(!tasks.empty());
auto jobit = jobs_.find(key);
Job* job;
if (jobit == jobs_.end()) {
job = AddJobWithoutRequest(key, request->parameters().cache_usage,
request->host_cache(), std::move(tasks),
request->priority(), request->source_net_log());
job->AddRequest(request);
job->RunNextTask();
} else {
job = jobit->second.get();
job->AddRequest(request);
}
}
HostResolverManager::Job* HostResolverManager::AddJobWithoutRequest(
JobKey key,
ResolveHostParameters::CacheUsage cache_usage,
HostCache* host_cache,
std::deque<TaskType> tasks,
RequestPriority priority,
const NetLogWithSource& source_net_log) {
auto new_job =
std::make_unique<Job>(weak_ptr_factory_.GetWeakPtr(), key, cache_usage,
host_cache, std::move(tasks), priority,
source_net_log, tick_clock_, https_svcb_options_);
auto insert_result = jobs_.emplace(std::move(key), std::move(new_job));
auto& iterator = insert_result.first;
bool is_new = insert_result.second;
DCHECK(is_new);
auto& job = iterator->second;
job->OnAddedToJobMap(iterator);
return job.get();
}
void HostResolverManager::CreateAndStartJobForServiceEndpointRequest(
JobKey key,
std::deque<TaskType> tasks,
ServiceEndpointRequestImpl* request) {
CHECK(!tasks.empty());
auto jobit = jobs_.find(key);
if (jobit == jobs_.end()) {
Job* job = AddJobWithoutRequest(key, request->parameters().cache_usage,
request->host_cache(), std::move(tasks),
request->priority(), request->net_log());
job->AddServiceEndpointRequest(request);
job->RunNextTask();
} else {
jobit->second->AddServiceEndpointRequest(request);
}
}
HostCache::Entry HostResolverManager::ResolveAsIP(DnsQueryTypeSet query_types,
bool resolve_canonname,
const IPAddress& ip_address) {
DCHECK(ip_address.IsValid());
DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
// IP literals cannot resolve unless the query type is an address query that
// allows addresses with the same address family as the literal. E.g., don't
// return IPv6 addresses for IPv4 queries or anything for a non-address query.
AddressFamily family = GetAddressFamily(ip_address);
if (!query_types.Has(AddressFamilyToDnsQueryType(family))) {
return HostCache::Entry(ERR_NAME_NOT_RESOLVED,
HostCache::Entry::SOURCE_UNKNOWN);
}
std::set<std::string> aliases;
if (resolve_canonname) {
aliases = {ip_address.ToString()};
}
return HostCache::Entry(OK, {IPEndPoint(ip_address, 0)}, std::move(aliases),
HostCache::Entry::SOURCE_UNKNOWN);
}
std::optional<HostCache::Entry> HostResolverManager::MaybeServeFromCache(
HostCache* cache,
const HostCache::Key& key,
ResolveHostParameters::CacheUsage cache_usage,
bool ignore_secure,
const NetLogWithSource& source_net_log,
std::optional<HostCache::EntryStaleness>* out_stale_info) {
DCHECK(out_stale_info);
*out_stale_info = std::nullopt;
if (!cache)
return std::nullopt;
if (cache_usage == ResolveHostParameters::CacheUsage::DISALLOWED)
return std::nullopt;
// Local-only requests search the cache for non-local-only results.
HostCache::Key effective_key = key;
if (effective_key.host_resolver_source == HostResolverSource::LOCAL_ONLY)
effective_key.host_resolver_source = HostResolverSource::ANY;
const std::pair<const HostCache::Key, HostCache::Entry>* cache_result;
HostCache::EntryStaleness staleness;
if (cache_usage == ResolveHostParameters::CacheUsage::STALE_ALLOWED) {
cache_result = cache->LookupStale(effective_key, tick_clock_->NowTicks(),
&staleness, ignore_secure);
} else {
DCHECK(cache_usage == ResolveHostParameters::CacheUsage::ALLOWED);
cache_result =
cache->Lookup(effective_key, tick_clock_->NowTicks(), ignore_secure);
staleness = HostCache::kNotStale;
}
if (cache_result) {
*out_stale_info = std::move(staleness);
source_net_log.AddEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_CACHE_HIT,
[&] { return NetLogResults(cache_result->second); });
return cache_result->second;
}
return std::nullopt;
}
std::optional<HostCache::Entry> HostResolverManager::MaybeReadFromConfig(
const JobKey& key) {
DCHECK(HasAddressType(key.query_types));
if (!key.host.HasScheme()) {
return std::nullopt;
}
std::optional<std::vector<IPEndPoint>> preset_addrs =
dns_client_->GetPresetAddrs(key.host.AsSchemeHostPort());
if (!preset_addrs)
return std::nullopt;
std::vector<IPEndPoint> filtered_addresses =
FilterAddresses(std::move(*preset_addrs), key.query_types);
if (filtered_addresses.empty())
return std::nullopt;
return HostCache::Entry(OK, std::move(filtered_addresses), /*aliases=*/{},
HostCache::Entry::SOURCE_CONFIG);
}
void HostResolverManager::StartBootstrapFollowup(
JobKey key,
HostCache* host_cache,
const NetLogWithSource& source_net_log) {
DCHECK_EQ(SecureDnsMode::kOff, key.secure_dns_mode);
DCHECK(host_cache);
key.secure_dns_mode = SecureDnsMode::kSecure;
if (jobs_.count(key) != 0)
return;
Job* job = AddJobWithoutRequest(
key, ResolveHostParameters::CacheUsage::ALLOWED, host_cache,
{TaskType::SECURE_DNS}, RequestPriority::LOW, source_net_log);
job->RunNextTask();
}
std::optional<HostCache::Entry> HostResolverManager::ServeFromHosts(
std::string_view hostname,
DnsQueryTypeSet query_types,
bool default_family_due_to_no_ipv6,
const std::deque<TaskType>& tasks) {
DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
// Don't attempt a HOSTS lookup if there is no DnsConfig or the HOSTS lookup
// is going to be done next as part of a system lookup.
if (!dns_client_ || !HasAddressType(query_types) ||
(!tasks.empty() && tasks.front() == TaskType::SYSTEM))
return std::nullopt;
const DnsHosts* hosts = dns_client_->GetHosts();
if (!hosts || hosts->empty())
return std::nullopt;
// HOSTS lookups are case-insensitive.
std::string effective_hostname = base::ToLowerASCII(hostname);
// If |address_family| is ADDRESS_FAMILY_UNSPECIFIED other implementations
// (glibc and c-ares) return the first matching line. We have more
// flexibility, but lose implicit ordering.
// We prefer IPv6 because "happy eyeballs" will fall back to IPv4 if
// necessary.
std::vector<IPEndPoint> addresses;
if (query_types.Has(DnsQueryType::AAAA)) {
auto it = hosts->find(DnsHostsKey(effective_hostname, ADDRESS_FAMILY_IPV6));
if (it != hosts->end()) {
addresses.emplace_back(it->second, 0);
}
}
if (query_types.Has(DnsQueryType::A)) {
auto it = hosts->find(DnsHostsKey(effective_hostname, ADDRESS_FAMILY_IPV4));
if (it != hosts->end()) {
addresses.emplace_back(it->second, 0);
}
}
// If got only loopback addresses and the family was restricted, resolve
// again, without restrictions. See SystemHostResolverCall for rationale.
if (default_family_due_to_no_ipv6 &&
base::ranges::all_of(addresses, &IPAddress::IsIPv4,
&IPEndPoint::address) &&
base::ranges::all_of(addresses, &IPAddress::IsLoopback,
&IPEndPoint::address)) {
query_types.Put(DnsQueryType::AAAA);
return ServeFromHosts(hostname, query_types, false, tasks);
}
if (addresses.empty())
return std::nullopt;
return HostCache::Entry(OK, std::move(addresses),
/*aliases=*/{}, HostCache::Entry::SOURCE_HOSTS);
}
std::optional<HostCache::Entry> HostResolverManager::ServeLocalhost(
std::string_view hostname,
DnsQueryTypeSet query_types,
bool default_family_due_to_no_ipv6) {
DCHECK(!query_types.Has(DnsQueryType::UNSPECIFIED));
std::vector<IPEndPoint> resolved_addresses;
if (!HasAddressType(query_types) ||
!ResolveLocalHostname(hostname, &resolved_addresses)) {
return std::nullopt;
}
if (default_family_due_to_no_ipv6 && query_types.Has(DnsQueryType::A) &&
!query_types.Has(DnsQueryType::AAAA)) {
// The caller disabled the AAAA query due to lack of detected IPv6 support.
// (See SystemHostResolverCall for rationale).
query_types.Put(DnsQueryType::AAAA);
}
std::vector<IPEndPoint> filtered_addresses =
FilterAddresses(std::move(resolved_addresses), query_types);
return HostCache::Entry(OK, std::move(filtered_addresses), /*aliases=*/{},
HostCache::Entry::SOURCE_UNKNOWN);
}
void HostResolverManager::CacheResult(HostCache* cache,
const HostCache::Key& key,
const HostCache::Entry& entry,
base::TimeDelta ttl) {
// Don't cache an error unless it has a positive TTL.
if (cache && (entry.error() == OK || ttl.is_positive()))
cache->Set(key, entry, tick_clock_->NowTicks(), ttl);
}
std::unique_ptr<HostResolverManager::Job> HostResolverManager::RemoveJob(
JobMap::iterator job_it) {
CHECK(job_it != jobs_.end(), base::NotFatalUntil::M130);
DCHECK(job_it->second);
DCHECK_EQ(1u, jobs_.count(job_it->first));
std::unique_ptr<Job> job;
job_it->second.swap(job);
jobs_.erase(job_it);
job->OnRemovedFromJobMap();
return job;
}
SecureDnsMode HostResolverManager::GetEffectiveSecureDnsMode(
SecureDnsPolicy secure_dns_policy) {
// Use switch() instead of if() to ensure that all policies are handled.
switch (secure_dns_policy) {
case SecureDnsPolicy::kDisable:
case SecureDnsPolicy::kBootstrap:
return SecureDnsMode::kOff;
case SecureDnsPolicy::kAllow:
break;
}
const DnsConfig* config =
dns_client_ ? dns_client_->GetEffectiveConfig() : nullptr;
SecureDnsMode secure_dns_mode = SecureDnsMode::kOff;
if (config) {
secure_dns_mode = config->secure_dns_mode;
}
return secure_dns_mode;
}
bool HostResolverManager::ShouldForceSystemResolverDueToTestOverride() const {
// If tests have provided a catch-all DNS block and then disabled it, check
// that we are not at risk of sending queries beyond the local network.
if (HostResolverProc::GetDefault() && system_resolver_disabled_for_testing_) {
DCHECK(dns_client_);
DCHECK(dns_client_->GetEffectiveConfig());
DCHECK(base::ranges::none_of(dns_client_->GetEffectiveConfig()->nameservers,
&IPAddress::IsPubliclyRoutable,
&IPEndPoint::address))
<< "Test could query a publicly-routable address.";
}
return !host_resolver_system_params_.resolver_proc &&
HostResolverProc::GetDefault() &&
!system_resolver_disabled_for_testing_;
}
void HostResolverManager::PushDnsTasks(bool system_task_allowed,
SecureDnsMode secure_dns_mode,
bool insecure_tasks_allowed,
bool allow_cache,
bool prioritize_local_lookups,
ResolveContext* resolve_context,
std::deque<TaskType>* out_tasks) {
DCHECK(dns_client_);
DCHECK(dns_client_->GetEffectiveConfig());
// If a catch-all DNS block has been set for unit tests, we shouldn't send
// DnsTasks. It is still necessary to call this method, however, so that the
// correct cache tasks for the secure dns mode are added.
const bool dns_tasks_allowed = !ShouldForceSystemResolverDueToTestOverride();
// Upgrade the insecure DnsTask depending on the secure dns mode.
switch (secure_dns_mode) {
case SecureDnsMode::kSecure:
DCHECK(!allow_cache ||
out_tasks->front() == TaskType::SECURE_CACHE_LOOKUP);
// Policy misconfiguration can put us in secure DNS mode without any DoH
// servers to query. See https://crbug.com/1326526.
if (dns_tasks_allowed && dns_client_->CanUseSecureDnsTransactions())
out_tasks->push_back(TaskType::SECURE_DNS);
break;
case SecureDnsMode::kAutomatic:
DCHECK(!allow_cache || out_tasks->front() == TaskType::CACHE_LOOKUP);
if (dns_client_->FallbackFromSecureTransactionPreferred(
resolve_context)) {
// Don't run a secure DnsTask if there are no available DoH servers.
if (dns_tasks_allowed && insecure_tasks_allowed)
out_tasks->push_back(TaskType::DNS);
} else if (prioritize_local_lookups) {
// If local lookups are prioritized, the cache should be checked for
// both secure and insecure results prior to running a secure DnsTask.
// The task sequence should already contain the appropriate cache task.
if (dns_tasks_allowed) {
out_tasks->push_back(TaskType::SECURE_DNS);
if (insecure_tasks_allowed)
out_tasks->push_back(TaskType::DNS);
}
} else {
if (allow_cache) {
// Remove the initial cache lookup task so that the secure and
// insecure lookups can be separated.
out_tasks->pop_front();
out_tasks->push_back(TaskType::SECURE_CACHE_LOOKUP);
}
if (dns_tasks_allowed)
out_tasks->push_back(TaskType::SECURE_DNS);
if (allow_cache)
out_tasks->push_back(TaskType::INSECURE_CACHE_LOOKUP);
if (dns_tasks_allowed && insecure_tasks_allowed)
out_tasks->push_back(TaskType::DNS);
}
break;
case SecureDnsMode::kOff:
DCHECK(!allow_cache || IsLocalTask(out_tasks->front()));
if (dns_tasks_allowed && insecure_tasks_allowed)
out_tasks->push_back(TaskType::DNS);
break;
default:
NOTREACHED_IN_MIGRATION();
break;
}
constexpr TaskType kWantTasks[] = {TaskType::DNS, TaskType::SECURE_DNS};
const bool no_dns_or_secure_tasks =
base::ranges::find_first_of(*out_tasks, kWantTasks) == out_tasks->end();
// The system resolver can be used as a fallback for a non-existent or
// failing DnsTask if allowed by the request parameters.
if (system_task_allowed &&
(no_dns_or_secure_tasks || allow_fallback_to_systemtask_))
out_tasks->push_back(TaskType::SYSTEM);
}
void HostResolverManager::CreateTaskSequence(
const JobKey& job_key,
ResolveHostParameters::CacheUsage cache_usage,
SecureDnsPolicy secure_dns_policy,
std::deque<TaskType>* out_tasks) {
DCHECK(out_tasks->empty());
// A cache lookup should generally be performed first. For jobs involving a
// DnsTask, this task may be replaced.
bool allow_cache =
cache_usage != ResolveHostParameters::CacheUsage::DISALLOWED;
if (secure_dns_policy == SecureDnsPolicy::kBootstrap) {
DCHECK_EQ(SecureDnsMode::kOff, job_key.secure_dns_mode);
if (allow_cache)
out_tasks->push_front(TaskType::INSECURE_CACHE_LOOKUP);
out_tasks->push_front(TaskType::CONFIG_PRESET);
if (allow_cache)
out_tasks->push_front(TaskType::SECURE_CACHE_LOOKUP);
} else if (allow_cache) {
if (job_key.secure_dns_mode == SecureDnsMode::kSecure) {
out_tasks->push_front(TaskType::SECURE_CACHE_LOOKUP);
} else {
out_tasks->push_front(TaskType::CACHE_LOOKUP);
}
}
out_tasks->push_back(TaskType::HOSTS);
// Determine what type of task a future Job should start.
bool prioritize_local_lookups =
cache_usage ==
HostResolver::ResolveHostParameters::CacheUsage::STALE_ALLOWED;
const bool has_address_type = HasAddressType(job_key.query_types);
switch (job_key.source) {
case HostResolverSource::ANY:
// Records DnsClient capability metrics, only when `source` is ANY. This
// is to avoid the metrics being skewed by mechanical requests of other
// source types.
RecordDnsClientCapabilityMetrics(dns_client_.get());
// Force address queries with canonname to use HostResolverSystemTask to
// counter poor CNAME support in DnsTask. See https://crbug.com/872665
//
// Otherwise, default to DnsTask (with allowed fallback to
// HostResolverSystemTask for address queries). But if hostname appears to
// be an MDNS name (ends in *.local), go with HostResolverSystemTask for
// address queries and MdnsTask for non- address queries.
if ((job_key.flags & HOST_RESOLVER_CANONNAME) && has_address_type) {
out_tasks->push_back(TaskType::SYSTEM);
} else if (!ResemblesMulticastDNSName(job_key.host.GetHostname())) {
bool system_task_allowed =
has_address_type &&
job_key.secure_dns_mode != SecureDnsMode::kSecure;
if (dns_client_ && dns_client_->GetEffectiveConfig()) {
bool insecure_allowed =
dns_client_->CanUseInsecureDnsTransactions() &&
!dns_client_->FallbackFromInsecureTransactionPreferred() &&
(has_address_type ||
dns_client_->CanQueryAdditionalTypesViaInsecureDns());
PushDnsTasks(system_task_allowed, job_key.secure_dns_mode,
insecure_allowed, allow_cache, prioritize_local_lookups,
&*job_key.resolve_context, out_tasks);
} else if (system_task_allowed) {
out_tasks->push_back(TaskType::SYSTEM);
}
} else if (has_address_type) {
// For *.local address queries, try the system resolver even if the
// secure dns mode is SECURE. Public recursive resolvers aren't expected
// to handle these queries.
out_tasks->push_back(TaskType::SYSTEM);
} else {
out_tasks->push_back(TaskType::MDNS);
}
break;
case HostResolverSource::SYSTEM:
out_tasks->push_back(TaskType::SYSTEM);
break;
case HostResolverSource::DNS:
if (dns_client_ && dns_client_->GetEffectiveConfig()) {
bool insecure_allowed =
dns_client_->CanUseInsecureDnsTransactions() &&
(has_address_type ||
dns_client_->CanQueryAdditionalTypesViaInsecureDns());
PushDnsTasks(false /* system_task_allowed */, job_key.secure_dns_mode,
insecure_allowed, allow_cache, prioritize_local_lookups,
&*job_key.resolve_context, out_tasks);
}
break;
case HostResolverSource::MULTICAST_DNS:
out_tasks->push_back(TaskType::MDNS);
break;
case HostResolverSource::LOCAL_ONLY:
// If no external source allowed, a job should not be created or started
break;
}
// `HOST_RESOLVER_CANONNAME` is only supported through system resolution.
if (job_key.flags & HOST_RESOLVER_CANONNAME) {
DCHECK(base::ranges::find(*out_tasks, TaskType::DNS) == out_tasks->end());
DCHECK(base::ranges::find(*out_tasks, TaskType::MDNS) == out_tasks->end());
}
}
namespace {
bool RequestWillUseWiFi(handles::NetworkHandle network) {
NetworkChangeNotifier::ConnectionType connection_type;
if (network == handles::kInvalidNetworkHandle)
connection_type = NetworkChangeNotifier::GetConnectionType();
else
connection_type = NetworkChangeNotifier::GetNetworkConnectionType(network);
return connection_type == NetworkChangeNotifier::CONNECTION_WIFI;
}
} // namespace
void HostResolverManager::FinishIPv6ReachabilityCheck(
CompletionOnceCallback callback,
int rv) {
SetLastIPv6ProbeResult((rv == OK) ? true : false);
std::move(callback).Run(OK);
if (!ipv6_request_callbacks_.empty()) {
std::vector<CompletionOnceCallback> tmp_request_callbacks;
ipv6_request_callbacks_.swap(tmp_request_callbacks);
for (auto& request_callback : tmp_request_callbacks) {
std::move(request_callback).Run(OK);
}
}
}
int HostResolverManager::StartIPv6ReachabilityCheck(
const NetLogWithSource& net_log,
ClientSocketFactory* client_socket_factory,
CompletionOnceCallback callback) {
// Don't bother checking if the request will use WiFi and IPv6 is assumed to
// not work on WiFi.
if (!check_ipv6_on_wifi_ && RequestWillUseWiFi(target_network_)) {
probing_ipv6_ = false;
last_ipv6_probe_result_ = false;
last_ipv6_probe_time_ = base::TimeTicks();
return OK;
}
if (probing_ipv6_) {
ipv6_request_callbacks_.push_back(std::move(callback));
return ERR_IO_PENDING;
}
// Cache the result for kIPv6ProbePeriodMs (measured from after
// StartGloballyReachableCheck() completes).
int rv = OK;
bool cached = true;
if (last_ipv6_probe_time_.is_null() ||
(tick_clock_->NowTicks() - last_ipv6_probe_time_).InMilliseconds() >
kIPv6ProbePeriodMs) {
probing_ipv6_ = true;
rv = StartGloballyReachableCheck(
IPAddress(kIPv6ProbeAddress), net_log, client_socket_factory,
base::BindOnce(&HostResolverManager::FinishIPv6ReachabilityCheck,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
if (rv != ERR_IO_PENDING) {
SetLastIPv6ProbeResult((rv == OK) ? true : false);
rv = OK;
}
cached = false;
}
net_log.AddEvent(
NetLogEventType::HOST_RESOLVER_MANAGER_IPV6_REACHABILITY_CHECK, [&] {
return NetLogIPv6AvailableParams(last_ipv6_probe_result_, cached);
});
return rv;
}
void HostResolverManager::SetLastIPv6ProbeResult(bool last_ipv6_probe_result) {
probing_ipv6_ = false;
last_ipv6_probe_result_ = last_ipv6_probe_result;
last_ipv6_probe_time_ = tick_clock_->NowTicks();
}
int HostResolverManager::StartGloballyReachableCheck(
const IPAddress& dest,
const NetLogWithSource& net_log,
ClientSocketFactory* client_socket_factory,
CompletionOnceCallback callback) {
std::unique_ptr<DatagramClientSocket> probing_socket =
client_socket_factory->CreateDatagramClientSocket(
DatagramSocket::DEFAULT_BIND, net_log.net_log(), net_log.source());
DatagramClientSocket* probing_socket_ptr = probing_socket.get();
auto refcounted_socket = base::MakeRefCounted<
base::RefCountedData<std::unique_ptr<DatagramClientSocket>>>(
std::move(probing_socket));
int rv = probing_socket_ptr->ConnectAsync(
IPEndPoint(dest, GetPortForGloballyReachableCheck()),
base::BindOnce(&HostResolverManager::RunFinishGloballyReachableCheck,
weak_ptr_factory_.GetWeakPtr(), refcounted_socket,
std::move(callback)));
if (rv != ERR_IO_PENDING) {
rv = FinishGloballyReachableCheck(probing_socket_ptr, rv) ? OK : ERR_FAILED;
}
return rv;
}
bool HostResolverManager::FinishGloballyReachableCheck(
DatagramClientSocket* socket,
int rv) {
if (rv != OK) {
return false;
}
IPEndPoint endpoint;
rv = socket->GetLocalAddress(&endpoint);
if (rv != OK) {
return false;
}
const IPAddress& address = endpoint.address();
if (address.IsLinkLocal()) {
return false;
}
if (address.IsIPv6()) {
const uint8_t kTeredoPrefix[] = {0x20, 0x01, 0, 0};
if (IPAddressStartsWith(address, kTeredoPrefix)) {
return false;
}
}
return true;
}
void HostResolverManager::RunFinishGloballyReachableCheck(
scoped_refptr<base::RefCountedData<std::unique_ptr<DatagramClientSocket>>>
socket,
CompletionOnceCallback callback,
int rv) {
bool is_reachable = FinishGloballyReachableCheck(socket->data.get(), rv);
std::move(callback).Run(is_reachable ? OK : ERR_FAILED);
}
void HostResolverManager::RunLoopbackProbeJob() {
RunHaveOnlyLoopbackAddressesJob(
base::BindOnce(&HostResolverManager::SetHaveOnlyLoopbackAddresses,
weak_ptr_factory_.GetWeakPtr()));
}
void HostResolverManager::RemoveAllJobs(const ResolveContext* context) {
for (auto it = jobs_.begin(); it != jobs_.end();) {
const JobKey& key = it->first;
if (&*key.resolve_context == context) {
RemoveJob(it++);
} else {
++it;
}
}
}
void HostResolverManager::AbortJobsWithoutTargetNetwork(bool in_progress_only) {
// In Abort, a Request callback could spawn new Jobs with matching keys, so
// first collect and remove all running jobs from `jobs_`.
std::vector<std::unique_ptr<Job>> jobs_to_abort;
for (auto it = jobs_.begin(); it != jobs_.end();) {
Job* job = it->second.get();
if (!job->HasTargetNetwork() && (!in_progress_only || job->is_running())) {
jobs_to_abort.push_back(RemoveJob(it++));
} else {
++it;
}
}
// Pause the dispatcher so it won't start any new dispatcher jobs while
// aborting the old ones. This is needed so that it won't start the second
// DnsTransaction for a job in `jobs_to_abort` if the DnsConfig just became
// invalid.
PrioritizedDispatcher::Limits limits = dispatcher_->GetLimits();
dispatcher_->SetLimits(
PrioritizedDispatcher::Limits(limits.reserved_slots.size(), 0));
// Life check to bail once `this` is deleted.
base::WeakPtr<HostResolverManager> self = weak_ptr_factory_.GetWeakPtr();
// Then Abort them.
for (size_t i = 0; self.get() && i < jobs_to_abort.size(); ++i) {
jobs_to_abort[i]->Abort();
}
if (self)
dispatcher_->SetLimits(limits);
}
void HostResolverManager::AbortInsecureDnsTasks(int error, bool fallback_only) {
// Aborting jobs potentially modifies |jobs_| and may even delete some jobs.
// Create safe closures of all current jobs.
std::vector<base::OnceClosure> job_abort_closures;
for (auto& job : jobs_) {
job_abort_closures.push_back(
job.second->GetAbortInsecureDnsTaskClosure(error, fallback_only));
}
// Pause the dispatcher so it won't start any new dispatcher jobs while
// aborting the old ones. This is needed so that it won't start the second
// DnsTransaction for a job if the DnsConfig just changed.
PrioritizedDispatcher::Limits limits = dispatcher_->GetLimits();
dispatcher_->SetLimits(
PrioritizedDispatcher::Limits(limits.reserved_slots.size(), 0));
for (base::OnceClosure& closure : job_abort_closures)
std::move(closure).Run();
dispatcher_->SetLimits(limits);
}
// TODO(crbug.com/40641277): Consider removing this and its usage.
void HostResolverManager::TryServingAllJobsFromHosts() {
if (!dns_client_ || !dns_client_->GetEffectiveConfig())
return;
// TODO(szym): Do not do this if nsswitch.conf instructs not to.
// http://crbug.com/117655
// Life check to bail once |this| is deleted.
base::WeakPtr<HostResolverManager> self = weak_ptr_factory_.GetWeakPtr();
for (auto it = jobs_.begin(); self.get() && it != jobs_.end();) {
Job* job = it->second.get();
++it;
// This could remove |job| from |jobs_|, but iterator will remain valid.
job->ServeFromHosts();
}
}
void HostResolverManager::OnIPAddressChanged() {
DCHECK(!IsBoundToNetwork());
last_ipv6_probe_time_ = base::TimeTicks();
// Abandon all ProbeJobs.
probe_weak_ptr_factory_.InvalidateWeakPtrs();
InvalidateCaches();
#if (BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_ANDROID)) || \
BUILDFLAG(IS_FUCHSIA)
RunLoopbackProbeJob();
#endif
AbortJobsWithoutTargetNetwork(true /* in_progress_only */);
// `this` may be deleted inside AbortJobsWithoutTargetNetwork().
}
void HostResolverManager::OnConnectionTypeChanged(
NetworkChangeNotifier::ConnectionType type) {
DCHECK(!IsBoundToNetwork());
UpdateConnectionType(type);
}
void HostResolverManager::OnSystemDnsConfigChanged(
std::optional<DnsConfig> config) {
DCHECK(!IsBoundToNetwork());
// If tests have provided a catch-all DNS block and then disabled it, check
// that we are not at risk of sending queries beyond the local network.
if (HostResolverProc::GetDefault() && system_resolver_disabled_for_testing_ &&
config.has_value()) {
DCHECK(base::ranges::none_of(config->nameservers,
&IPAddress::IsPubliclyRoutable,
&IPEndPoint::address))
<< "Test could query a publicly-routable address.";
}
bool changed = false;
bool transactions_allowed_before = false;
if (dns_client_) {
transactions_allowed_before = dns_client_->CanUseSecureDnsTransactions() ||
dns_client_->CanUseInsecureDnsTransactions();
changed = dns_client_->SetSystemConfig(std::move(config));
}
// Always invalidate cache, even if no change is seen.
InvalidateCaches();
if (changed) {
// Need to update jobs iff transactions were previously allowed because
// in-progress jobs may be running using a now-invalid configuration.
if (transactions_allowed_before)
UpdateJobsForChangedConfig();
}
}
void HostResolverManager::UpdateJobsForChangedConfig() {
// Life check to bail once `this` is deleted.
base::WeakPtr<HostResolverManager> self = weak_ptr_factory_.GetWeakPtr();
// Existing jobs that were set up using the nameservers and secure dns mode
// from the original config need to be aborted (does not apply to jobs
// targeting a specific network).
AbortJobsWithoutTargetNetwork(false /* in_progress_only */);
// `this` may be deleted inside AbortJobsWithoutTargetNetwork().
if (self.get())
TryServingAllJobsFromHosts();
}
void HostResolverManager::OnFallbackResolve(int dns_task_error) {
DCHECK(dns_client_);
DCHECK_NE(OK, dns_task_error);
// Nothing to do if DnsTask is already not preferred.
if (dns_client_->FallbackFromInsecureTransactionPreferred())
return;
dns_client_->IncrementInsecureFallbackFailures();
// If DnsClient became not preferred, fallback all fallback-allowed insecure
// DnsTasks to HostResolverSystemTasks.
if (dns_client_->FallbackFromInsecureTransactionPreferred())
AbortInsecureDnsTasks(ERR_FAILED, true /* fallback_only */);
}
int HostResolverManager::GetOrCreateMdnsClient(MDnsClient** out_client) {
#if BUILDFLAG(ENABLE_MDNS)
if (!mdns_client_) {
if (!mdns_socket_factory_)
mdns_socket_factory_ = std::make_unique<MDnsSocketFactoryImpl>(net_log_);
mdns_client_ = MDnsClient::CreateDefault();
}
int rv = OK;
if (!mdns_client_->IsListening())
rv = mdns_client_->StartListening(mdns_socket_factory_.get());
DCHECK_NE(ERR_IO_PENDING, rv);
DCHECK(rv != OK || mdns_client_->IsListening());
if (rv == OK)
*out_client = mdns_client_.get();
return rv;
#else
// Should not request MDNS resoltuion unless MDNS is enabled.
NOTREACHED_IN_MIGRATION();
return ERR_UNEXPECTED;
#endif
}
void HostResolverManager::InvalidateCaches(bool network_change) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!invalidation_in_progress_);
#if DCHECK_IS_ON()
base::WeakPtr<HostResolverManager> self_ptr = weak_ptr_factory_.GetWeakPtr();
size_t num_jobs = jobs_.size();
#endif
invalidation_in_progress_ = true;
for (auto& context : registered_contexts_) {
context.InvalidateCachesAndPerSessionData(
dns_client_ ? dns_client_->GetCurrentSession() : nullptr,
network_change);
}
invalidation_in_progress_ = false;
#if DCHECK_IS_ON()
// Sanity checks that invalidation does not have reentrancy issues.
DCHECK(self_ptr);
DCHECK_EQ(num_jobs, jobs_.size());
#endif
}
void HostResolverManager::UpdateConnectionType(
NetworkChangeNotifier::ConnectionType type) {
host_resolver_system_params_.unresponsive_delay =
GetTimeDeltaForConnectionTypeFromFieldTrialOrDefault(
"DnsUnresponsiveDelayMsByConnectionType",
HostResolverSystemTask::Params::kDnsDefaultUnresponsiveDelay, type);
// Note that NetworkChangeNotifier always sends a CONNECTION_NONE notification
// before non-NONE notifications. This check therefore just ensures each
// connection change notification is handled once and has nothing to do with
// whether the change is to offline or online.
if (type == NetworkChangeNotifier::CONNECTION_NONE && dns_client_) {
dns_client_->ReplaceCurrentSession();
InvalidateCaches(true /* network_change */);
}
}
std::unique_ptr<DnsProbeRunner> HostResolverManager::CreateDohProbeRunner(
ResolveContext* resolve_context) {
DCHECK(resolve_context);
DCHECK(registered_contexts_.HasObserver(resolve_context));
if (!dns_client_ || !dns_client_->CanUseSecureDnsTransactions()) {
return nullptr;
}
return dns_client_->GetTransactionFactory()->CreateDohProbeRunner(
resolve_context);
}
} // namespace net