// 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/dns_client.h" #include #include #include #include #include "base/functional/bind.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/rand_util.h" #include "base/ranges/algorithm.h" #include "base/values.h" #include "net/base/ip_address.h" #include "net/base/ip_endpoint.h" #include "net/dns/address_sorter.h" #include "net/dns/dns_session.h" #include "net/dns/dns_transaction.h" #include "net/dns/dns_util.h" #include "net/dns/public/dns_over_https_config.h" #include "net/dns/public/secure_dns_mode.h" #include "net/dns/resolve_context.h" #include "net/log/net_log.h" #include "net/log/net_log_event_type.h" #include "net/socket/client_socket_factory.h" #include "net/third_party/uri_template/uri_template.h" #include "url/gurl.h" #include "url/scheme_host_port.h" namespace net { namespace { bool IsEqual(const std::optional& c1, const DnsConfig* c2) { if (!c1.has_value() && c2 == nullptr) return true; if (!c1.has_value() || c2 == nullptr) return false; return c1.value() == *c2; } void UpdateConfigForDohUpgrade(DnsConfig* config) { bool has_doh_servers = !config->doh_config.servers().empty(); // Do not attempt upgrade when there are already DoH servers specified or // when there are aspects of the system DNS config that are unhandled. if (!config->unhandled_options && config->allow_dns_over_https_upgrade && !has_doh_servers && config->secure_dns_mode == SecureDnsMode::kAutomatic) { // If we're in strict mode on Android, only attempt to upgrade the // specified DoT hostname. if (!config->dns_over_tls_hostname.empty()) { config->doh_config = DnsOverHttpsConfig( GetDohUpgradeServersFromDotHostname(config->dns_over_tls_hostname)); has_doh_servers = !config->doh_config.servers().empty(); UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.DotUpgradeSucceeded", has_doh_servers); } else { bool all_local = true; for (const auto& server : config->nameservers) { if (server.address().IsPubliclyRoutable()) { all_local = false; break; } } UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.HasPublicInsecureNameserver", !all_local); config->doh_config = DnsOverHttpsConfig( GetDohUpgradeServersFromNameservers(config->nameservers)); has_doh_servers = !config->doh_config.servers().empty(); UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.InsecureUpgradeSucceeded", has_doh_servers); } } else { UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.Ineligible.DohSpecified", has_doh_servers); UMA_HISTOGRAM_BOOLEAN("Net.DNS.UpgradeConfig.Ineligible.UnhandledOptions", config->unhandled_options); } } class DnsClientImpl : public DnsClient { public: DnsClientImpl(NetLog* net_log, const RandIntCallback& rand_int_callback) : net_log_(net_log), rand_int_callback_(rand_int_callback) {} DnsClientImpl(const DnsClientImpl&) = delete; DnsClientImpl& operator=(const DnsClientImpl&) = delete; ~DnsClientImpl() override = default; bool CanUseSecureDnsTransactions() const override { const DnsConfig* config = GetEffectiveConfig(); return config && !config->doh_config.servers().empty(); } bool CanUseInsecureDnsTransactions() const override { const DnsConfig* config = GetEffectiveConfig(); return config && config->nameservers.size() > 0 && insecure_enabled_ && !config->unhandled_options && !config->dns_over_tls_active; } bool CanQueryAdditionalTypesViaInsecureDns() const override { // Only useful information if insecure DNS is usable, so expect this to // never be called if that is not the case. DCHECK(CanUseInsecureDnsTransactions()); return can_query_additional_types_via_insecure_; } void SetInsecureEnabled(bool enabled, bool additional_types_enabled) override { insecure_enabled_ = enabled; can_query_additional_types_via_insecure_ = additional_types_enabled; } bool FallbackFromSecureTransactionPreferred( ResolveContext* context) const override { if (!CanUseSecureDnsTransactions()) return true; DCHECK(session_); // Should be true if CanUseSecureDnsTransactions() true. return context->NumAvailableDohServers(session_.get()) == 0; } bool FallbackFromInsecureTransactionPreferred() const override { return !CanUseInsecureDnsTransactions() || insecure_fallback_failures_ >= kMaxInsecureFallbackFailures; } bool SetSystemConfig(std::optional system_config) override { if (system_config == system_config_) return false; system_config_ = std::move(system_config); return UpdateDnsConfig(); } bool SetConfigOverrides(DnsConfigOverrides config_overrides) override { if (config_overrides == config_overrides_) return false; config_overrides_ = std::move(config_overrides); return UpdateDnsConfig(); } void ReplaceCurrentSession() override { if (!session_) return; UpdateSession(session_->config()); } DnsSession* GetCurrentSession() override { return session_.get(); } const DnsConfig* GetEffectiveConfig() const override { if (!session_) return nullptr; DCHECK(session_->config().IsValid()); return &session_->config(); } const DnsHosts* GetHosts() const override { const DnsConfig* config = GetEffectiveConfig(); if (!config) return nullptr; return &config->hosts; } std::optional> GetPresetAddrs( const url::SchemeHostPort& endpoint) const override { DCHECK(endpoint.IsValid()); if (!session_) return std::nullopt; const auto& servers = session_->config().doh_config.servers(); auto it = base::ranges::find_if(servers, [&](const auto& server) { std::string uri; bool valid = uri_template::Expand(server.server_template(), {}, &uri); // Server templates are validated before being allowed into the config. DCHECK(valid); GURL gurl(uri); return url::SchemeHostPort(gurl) == endpoint; }); if (it == servers.end()) return std::nullopt; std::vector combined; for (const IPAddressList& ips : it->endpoints()) { for (const IPAddress& ip : ips) { combined.emplace_back(ip, endpoint.port()); } } return combined; } DnsTransactionFactory* GetTransactionFactory() override { return session_.get() ? factory_.get() : nullptr; } AddressSorter* GetAddressSorter() override { return address_sorter_.get(); } void IncrementInsecureFallbackFailures() override { ++insecure_fallback_failures_; } void ClearInsecureFallbackFailures() override { insecure_fallback_failures_ = 0; } base::Value::Dict GetDnsConfigAsValueForNetLog() const override { const DnsConfig* config = GetEffectiveConfig(); if (config == nullptr) return base::Value::Dict(); base::Value::Dict dict = config->ToDict(); dict.Set("can_use_secure_dns_transactions", CanUseSecureDnsTransactions()); dict.Set("can_use_insecure_dns_transactions", CanUseInsecureDnsTransactions()); return dict; } std::optional GetSystemConfigForTesting() const override { return system_config_; } DnsConfigOverrides GetConfigOverridesForTesting() const override { return config_overrides_; } void SetTransactionFactoryForTesting( std::unique_ptr factory) override { factory_ = std::move(factory); } void SetAddressSorterForTesting( std::unique_ptr address_sorter) override { NOTIMPLEMENTED(); } private: std::optional BuildEffectiveConfig() const { DnsConfig config; if (config_overrides_.OverridesEverything()) { config = config_overrides_.ApplyOverrides(DnsConfig()); } else { if (!system_config_) return std::nullopt; config = config_overrides_.ApplyOverrides(system_config_.value()); } UpdateConfigForDohUpgrade(&config); // TODO(ericorth): Consider keeping a separate DnsConfig for pure Chrome- // produced configs to allow respecting all fields like |unhandled_options| // while still being able to fallback to system config for DoH. // For now, clear the nameservers for extra security if parts of the system // config are unhandled. if (config.unhandled_options) config.nameservers.clear(); if (!config.IsValid()) return std::nullopt; return config; } bool UpdateDnsConfig() { std::optional new_effective_config = BuildEffectiveConfig(); if (IsEqual(new_effective_config, GetEffectiveConfig())) return false; insecure_fallback_failures_ = 0; UpdateSession(std::move(new_effective_config)); if (net_log_) { net_log_->AddGlobalEntry(NetLogEventType::DNS_CONFIG_CHANGED, [this] { return GetDnsConfigAsValueForNetLog(); }); } return true; } void UpdateSession(std::optional new_effective_config) { factory_.reset(); session_ = nullptr; if (new_effective_config) { DCHECK(new_effective_config.value().IsValid()); session_ = base::MakeRefCounted( std::move(new_effective_config).value(), rand_int_callback_, net_log_); factory_ = DnsTransactionFactory::CreateFactory(session_.get()); } } bool insecure_enabled_ = false; bool can_query_additional_types_via_insecure_ = false; int insecure_fallback_failures_ = 0; std::optional system_config_; DnsConfigOverrides config_overrides_; scoped_refptr session_; std::unique_ptr factory_; std::unique_ptr address_sorter_ = AddressSorter::CreateAddressSorter(); raw_ptr net_log_; const RandIntCallback rand_int_callback_; }; } // namespace // static std::unique_ptr DnsClient::CreateClient(NetLog* net_log) { return std::make_unique(net_log, base::BindRepeating(&base::RandInt)); } // static std::unique_ptr DnsClient::CreateClientForTesting( NetLog* net_log, const RandIntCallback& rand_int_callback) { return std::make_unique(net_log, rand_int_callback); } } // namespace net