#include "net/dns/host_resolver_dns_task.h"
#include <algorithm>
#include <string_view>
#include <variant>
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/tick_clock.h"
#include "base/types/optional_util.h"
#include "net/base/features.h"
#include "net/dns/address_sorter.h"
#include "net/dns/dns_client.h"
#include "net/dns/dns_names_util.h"
#include "net/dns/dns_response.h"
#include "net/dns/dns_transaction.h"
#include "net/dns/dns_util.h"
#include "net/dns/host_resolver.h"
#include "net/dns/host_resolver_cache.h"
#include "net/dns/host_resolver_internal_result.h"
#include "net/dns/public/util.h"
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
#include "arkweb/chromium_ext/content/public/common/content_switches_ext.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "net/base/features.h"
#include "services/network/public/cpp/features.h"
#endif
namespace net {
BASE_FEATURE(kPrioritizeHttpsResourceRecord, base::FEATURE_ENABLED_BY_DEFAULT);
namespace {
DnsResponse CreateFakeEmptyResponse(std::string_view hostname,
DnsQueryType query_type) {
std::optional<std::vector<uint8_t>> qname =
dns_names_util::DottedNameToNetwork(
hostname, true);
CHECK(qname.has_value());
return DnsResponse::CreateEmptyNoDataResponse(
0u, true, qname.value(),
DnsQueryTypeToQtype(query_type));
}
base::Value::Dict NetLogDnsTaskExtractionFailureParams(
DnsResponseResultExtractor::ExtractionError extraction_error,
DnsQueryType dns_query_type) {
base::Value::Dict dict;
dict.Set("extraction_error", base::strict_cast<int>(extraction_error));
dict.Set("dns_query_type", kDnsQueryTypes.at(dns_query_type));
return dict;
}
base::Value::Dict NetLogDnsTaskFailedParams(
const HostResolverInternalErrorResult& failure_result,
const HostResolverDnsTask::Results& saved_results) {
base::Value::Dict dict;
dict.Set("failure_result", failure_result.ToValue());
if (!saved_results.empty()) {
base::Value::List list;
for (const std::unique_ptr<HostResolverInternalResult>& result :
saved_results) {
list.Append(result->ToValue());
}
dict.Set("saved_results", std::move(list));
}
return dict;
}
base::Value::Dict NetLogResults(const HostResolverDnsTask::Results& results) {
base::Value::List list;
for (const std::unique_ptr<HostResolverInternalResult>& result : results) {
list.Append(result->ToValue());
}
base::Value::Dict dict;
dict.Set("results", std::move(list));
return dict;
}
void RecordResolveTimeDiff(const char* histogram_variant,
base::TimeTicks start_time,
base::TimeTicks first_record_end_time,
base::TimeTicks second_record_end_time) {
CHECK_LE(start_time, first_record_end_time);
CHECK_LE(first_record_end_time, second_record_end_time);
base::TimeDelta diff = second_record_end_time - first_record_end_time;
base::UmaHistogramTimes(
base::StrCat({"Net.Dns.ResolveTimeDiff2.", histogram_variant}), diff);
}
std::vector<IPEndPoint> ExtractAddressResultsForSort(
HostResolverDnsTask::Results& results,
bool is_happy_eyeballs_v3_enabled) {
CHECK(!base::FeatureList::IsEnabled(features::kUseHostResolverCache) &&
!is_happy_eyeballs_v3_enabled);
CHECK_LE(
std::ranges::count_if(
results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return (result->type() == HostResolverInternalResult::Type::kData ||
result->type() ==
HostResolverInternalResult::Type::kError) &&
result->query_type() == DnsQueryType::A;
}),
1);
CHECK_LE(
std::ranges::count_if(
results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return (result->type() == HostResolverInternalResult::Type::kData ||
result->type() ==
HostResolverInternalResult::Type::kError) &&
result->query_type() == DnsQueryType::AAAA;
}),
1);
auto a_result_it = std::ranges::find_if(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return (result->type() == HostResolverInternalResult::Type::kData ||
result->type() == HostResolverInternalResult::Type::kError) &&
result->query_type() == DnsQueryType::A;
});
auto aaaa_result_it = std::ranges::find_if(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return (result->type() == HostResolverInternalResult::Type::kData ||
result->type() == HostResolverInternalResult::Type::kError) &&
result->query_type() == DnsQueryType::AAAA;
});
if (aaaa_result_it == results.end() ||
(*aaaa_result_it)->type() == HostResolverInternalResult::Type::kError ||
(*aaaa_result_it)->AsData().endpoints().empty()) {
return {};
}
std::string domain_name = (*aaaa_result_it)->domain_name();
CHECK_EQ((*aaaa_result_it)->source(),
HostResolverInternalResult::Source::kDns);
std::optional<base::TimeTicks> expiration = (*aaaa_result_it)->expiration();
base::Time timed_expiration = (*aaaa_result_it)->timed_expiration().value();
std::vector<IPEndPoint> endpoints_to_sort =
(*aaaa_result_it)->AsData().endpoints();
CHECK((*aaaa_result_it)->AsData().strings().empty());
CHECK((*aaaa_result_it)->AsData().hosts().empty());
results.erase(aaaa_result_it);
if (a_result_it != results.end()) {
CHECK_EQ((*a_result_it)->source(),
HostResolverInternalResult::Source::kDns);
if (expiration.has_value()) {
expiration = std::min(
expiration.value(),
(*a_result_it)->expiration().value_or(base::TimeTicks::Max()));
} else {
expiration = (*a_result_it)->expiration();
}
timed_expiration =
std::min(timed_expiration, (*a_result_it)->timed_expiration().value());
if ((*a_result_it)->type() == HostResolverInternalResult::Type::kData) {
endpoints_to_sort.insert(endpoints_to_sort.end(),
(*a_result_it)->AsData().endpoints().begin(),
(*a_result_it)->AsData().endpoints().end());
CHECK((*a_result_it)->AsData().strings().empty());
CHECK((*a_result_it)->AsData().hosts().empty());
}
results.erase(a_result_it);
}
CHECK(!std::ranges::any_of(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kData;
}));
CHECK(!std::ranges::any_of(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kError &&
result->query_type() == DnsQueryType::UNSPECIFIED;
}));
results.insert(std::make_unique<HostResolverInternalErrorResult>(
std::move(domain_name), DnsQueryType::UNSPECIFIED, expiration,
timed_expiration, HostResolverInternalResult::Source::kDns,
ERR_NAME_NOT_RESOLVED));
return endpoints_to_sort;
}
}
HostResolverDnsTask::SingleTransactionResults::SingleTransactionResults(
DnsQueryType query_type,
ResultRefs results)
: query_type(query_type), results(results) {}
HostResolverDnsTask::SingleTransactionResults::~SingleTransactionResults() =
default;
HostResolverDnsTask::SingleTransactionResults::SingleTransactionResults(
SingleTransactionResults&&) = default;
HostResolverDnsTask::SingleTransactionResults&
HostResolverDnsTask::SingleTransactionResults::operator=(
SingleTransactionResults&&) = default;
HostResolverDnsTask::TransactionInfo::TransactionInfo(
DnsQueryType type,
TransactionErrorBehavior error_behavior)
: type(type), error_behavior(error_behavior) {}
HostResolverDnsTask::TransactionInfo::~TransactionInfo() = default;
HostResolverDnsTask::TransactionInfo::TransactionInfo(
HostResolverDnsTask::TransactionInfo&& other) = default;
HostResolverDnsTask::TransactionInfo&
HostResolverDnsTask::TransactionInfo::operator=(
HostResolverDnsTask::TransactionInfo&& other) = default;
bool HostResolverDnsTask::TransactionInfo::operator<(
const HostResolverDnsTask::TransactionInfo& other) const {
return std::tie(type, error_behavior, transaction) <
std::tie(other.type, other.error_behavior, other.transaction);
}
HostResolverDnsTask::HostResolverDnsTask(
DnsClient* client,
HostResolver::Host host,
NetworkAnonymizationKey anonymization_key,
DnsQueryTypeSet query_types,
ResolveContext* resolve_context,
bool secure,
SecureDnsMode secure_dns_mode,
Delegate* delegate,
const NetLogWithSource& job_net_log,
const base::TickClock* tick_clock,
bool fallback_available,
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
bool secure_fallback,
#endif
const HostResolver::HttpsSvcbOptions& https_svcb_options)
: client_(client),
host_(std::move(host)),
anonymization_key_(std::move(anonymization_key)),
resolve_context_(resolve_context->AsSafeRef()),
secure_(secure),
secure_dns_mode_(secure_dns_mode),
delegate_(delegate),
net_log_(job_net_log),
tick_clock_(tick_clock),
task_start_time_(tick_clock_->NowTicks()),
fallback_available_(fallback_available),
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
secure_fallback_(secure_fallback),
#endif
https_svcb_options_(https_svcb_options) {
DCHECK(client_);
DCHECK(delegate_);
if (!secure_) {
DCHECK(client_->CanUseInsecureDnsTransactions());
}
PushTransactionsNeeded(MaybeDisableAdditionalQueries(query_types));
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
utils = std::make_unique<ArkWebHostResolverDnsTaskExt>(this);
delegate_->InitReportInfoForDohFallback();
#endif
}
HostResolverDnsTask::~HostResolverDnsTask() = default;
void HostResolverDnsTask::StartNextTransaction() {
DCHECK_GE(num_additional_transactions_needed(), 1);
if (!any_transaction_started_) {
net_log_.BeginEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK,
[&] { return NetLogDnsTaskCreationParams(); });
}
any_transaction_started_ = true;
TransactionInfo transaction_info = std::move(transactions_needed_.front());
transactions_needed_.pop_front();
DCHECK(IsAddressType(transaction_info.type) || secure_ ||
client_->CanQueryAdditionalTypesViaInsecureDns());
base::TimeDelta time_queued = tick_clock_->NowTicks() - task_start_time_;
UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.JobQueueTime.PerTransaction",
time_queued);
delegate_->AddTransactionTimeQueued(time_queued);
CreateAndStartTransaction(std::move(transaction_info));
}
base::Value::Dict HostResolverDnsTask::NetLogDnsTaskCreationParams() {
base::Value::Dict dict;
dict.Set("secure", secure());
base::Value::List transactions_needed_value;
for (const TransactionInfo& info : transactions_needed_) {
base::Value::Dict transaction_dict;
transaction_dict.Set("dns_query_type", kDnsQueryTypes.at(info.type));
transactions_needed_value.Append(std::move(transaction_dict));
}
dict.Set("transactions_needed", std::move(transactions_needed_value));
return dict;
}
base::Value::Dict HostResolverDnsTask::NetLogDnsTaskTimeoutParams() {
base::Value::Dict dict;
if (!transactions_in_progress_.empty()) {
base::Value::List list;
for (const TransactionInfo& info : transactions_in_progress_) {
base::Value::Dict transaction_dict;
transaction_dict.Set("dns_query_type", kDnsQueryTypes.at(info.type));
list.Append(std::move(transaction_dict));
}
dict.Set("started_transactions", std::move(list));
}
if (!transactions_needed_.empty()) {
base::Value::List list;
for (const TransactionInfo& info : transactions_needed_) {
base::Value::Dict transaction_dict;
transaction_dict.Set("dns_query_type", kDnsQueryTypes.at(info.type));
list.Append(std::move(transaction_dict));
}
dict.Set("queued_transactions", std::move(list));
}
return dict;
}
DnsQueryTypeSet HostResolverDnsTask::MaybeDisableAdditionalQueries(
DnsQueryTypeSet types) {
DCHECK(!types.empty());
DCHECK(!types.Has(DnsQueryType::UNSPECIFIED));
if (types.size() == 1) {
return types;
}
if (types.Has(DnsQueryType::HTTPS)) {
if (!secure_ && !client_->CanQueryAdditionalTypesViaInsecureDns()) {
https_disabled_ = true;
types.Remove(DnsQueryType::HTTPS);
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
} else if (resolve_context_->IsHttpsDnsFallbackEnabled() &&
!client_->CanQueryAdditionalTypesViaInsecureDns()) {
types.Remove(DnsQueryType::HTTPS);
#endif
} else {
DCHECK(!httpssvc_metrics_);
httpssvc_metrics_.emplace(secure_);
}
}
DCHECK(!types.empty());
return types;
}
void HostResolverDnsTask::PushTransactionsNeeded(DnsQueryTypeSet query_types) {
DCHECK(transactions_needed_.empty());
if (query_types.Has(DnsQueryType::HTTPS) &&
features::kUseDnsHttpsSvcbEnforceSecureResponse.Get() && secure_) {
query_types.Remove(DnsQueryType::HTTPS);
transactions_needed_.emplace_back(DnsQueryType::HTTPS,
TransactionErrorBehavior::kFatalOrEmpty);
}
auto add_transaction = [&](DnsQueryType query) {
if (query == DnsQueryType::HTTPS) {
transactions_needed_.emplace_back(
query, TransactionErrorBehavior::kSynthesizeEmpty);
} else {
transactions_needed_.emplace_back(query);
}
};
if (query_types.Has(DnsQueryType::HTTPS) &&
(base::FeatureList::IsEnabled(kPrioritizeHttpsResourceRecord) ||
base::FeatureList::IsEnabled(features::kHappyEyeballsV3))) {
query_types.Remove(DnsQueryType::HTTPS);
add_transaction(DnsQueryType::HTTPS);
}
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
int ip_address_transactions = 0;
#endif
constexpr DnsQueryType kHighPriorityQueries[] = {DnsQueryType::AAAA,
DnsQueryType::A};
for (DnsQueryType high_priority_query : kHighPriorityQueries) {
if (!query_types.Has(high_priority_query)) {
continue;
}
query_types.Remove(high_priority_query);
add_transaction(high_priority_query);
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
ip_address_transactions++;
#endif
}
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
if (!secure_ && ip_address_transactions == 2) {
need_to_sniff_ip_result_ = true;
}
#endif
for (DnsQueryType remaining_query : query_types) {
add_transaction(remaining_query);
}
}
void HostResolverDnsTask::CreateAndStartTransaction(
TransactionInfo transaction_info) {
DCHECK(!transaction_info.transaction);
DCHECK_NE(DnsQueryType::UNSPECIFIED, transaction_info.type);
std::string transaction_hostname(host_.GetHostnameWithoutBrackets());
uint16_t request_port = 0;
if (transaction_info.type == DnsQueryType::HTTPS && host_.HasScheme()) {
const auto& scheme_host_port = host_.AsSchemeHostPort();
transaction_hostname =
dns_util::GetNameForHttpsQuery(scheme_host_port, &request_port);
}
transaction_info.transaction =
client_->GetTransactionFactory()->CreateTransaction(
std::move(transaction_hostname),
DnsQueryTypeToQtype(transaction_info.type), net_log_, secure_,
secure_dns_mode_, &*resolve_context_,
fallback_available_ );
transaction_info.transaction->SetRequestPriority(delegate_->priority());
auto transaction_info_it =
transactions_in_progress_.insert(std::move(transaction_info)).first;
transaction_info_it->transaction->Start(base::BindOnce(
&HostResolverDnsTask::OnDnsTransactionComplete, base::Unretained(this),
transaction_info_it, request_port));
}
void HostResolverDnsTask::OnTimeout() {
net_log_.AddEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK_TIMEOUT,
[&] { return NetLogDnsTaskTimeoutParams(); });
for (const TransactionInfo& transaction : transactions_in_progress_) {
base::TimeDelta elapsed_time = tick_clock_->NowTicks() - task_start_time_;
switch (transaction.type) {
case DnsQueryType::HTTPS:
DCHECK(!secure_ ||
!features::kUseDnsHttpsSvcbEnforceSecureResponse.Get());
if (httpssvc_metrics_) {
httpssvc_metrics_->SaveForHttps(HttpssvcDnsRcode::kTimedOut,
{},
elapsed_time);
}
break;
default:
NOTREACHED();
}
}
transactions_needed_.clear();
transactions_in_progress_.clear();
OnTransactionsFinished(std::nullopt);
}
void HostResolverDnsTask::OnDnsTransactionComplete(
std::set<TransactionInfo>::iterator transaction_info_it,
uint16_t request_port,
int net_error,
const DnsResponse* response) {
CHECK(transaction_info_it != transactions_in_progress_.end());
DCHECK(base::Contains(transactions_in_progress_, *transaction_info_it));
TransactionInfo transaction_info =
std::move(transactions_in_progress_.extract(transaction_info_it).value());
const base::TimeTicks now = tick_clock_->NowTicks();
base::TimeDelta elapsed_time = now - task_start_time_;
enum HttpssvcDnsRcode rcode_for_httpssvc = HttpssvcDnsRcode::kNoError;
if (httpssvc_metrics_) {
if (net_error == ERR_DNS_TIMED_OUT) {
rcode_for_httpssvc = HttpssvcDnsRcode::kTimedOut;
} else if (net_error == ERR_NAME_NOT_RESOLVED) {
rcode_for_httpssvc = HttpssvcDnsRcode::kNoError;
} else if (response == nullptr) {
rcode_for_httpssvc = HttpssvcDnsRcode::kMissingDnsResponse;
} else {
rcode_for_httpssvc =
TranslateDnsRcodeForHttpssvcExperiment(response->rcode());
}
}
bool fatal_error =
IsFatalTransactionFailure(net_error, transaction_info, response);
std::optional<DnsResponse> fake_response;
if (net_error != OK && !(net_error == ERR_NAME_NOT_RESOLVED && response &&
response->IsValid())) {
if (transaction_info.error_behavior ==
TransactionErrorBehavior::kFallback ||
fatal_error) {
OnFailure(net_error, !fatal_error);
return;
} else {
DCHECK((transaction_info.error_behavior ==
TransactionErrorBehavior::kFatalOrEmpty &&
!fatal_error) ||
transaction_info.error_behavior ==
TransactionErrorBehavior::kSynthesizeEmpty);
fake_response = CreateFakeEmptyResponse(
host_.GetHostnameWithoutBrackets(), transaction_info.type);
response = &fake_response.value();
}
}
DCHECK(response);
DnsResponseResultExtractor::ResultsOrError results;
{
DnsResponseResultExtractor extractor(*response);
results = extractor.ExtractDnsResults(
transaction_info.type,
host_.GetHostnameWithoutBrackets(),
request_port);
}
if (!results.has_value()) {
net_log_.AddEvent(
NetLogEventType::HOST_RESOLVER_DNS_TASK_EXTRACTION_FAILURE, [&] {
return NetLogDnsTaskExtractionFailureParams(results.error(),
transaction_info.type);
});
if (transaction_info.error_behavior ==
TransactionErrorBehavior::kFatalOrEmpty ||
transaction_info.error_behavior ==
TransactionErrorBehavior::kSynthesizeEmpty) {
DCHECK(!fatal_error);
DCHECK_EQ(transaction_info.type, DnsQueryType::HTTPS);
results = Results();
} else {
OnFailure(ERR_DNS_MALFORMED_RESPONSE, true);
return;
}
}
CHECK(results.has_value());
net_log_.AddEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK_EXTRACTION_RESULTS,
[&] {
base::Value::List list;
list.reserve(results.value().size());
for (const auto& result : results.value()) {
list.Append(result->ToValue());
}
base::Value::Dict dict;
dict.Set("results", std::move(list));
return dict;
});
if (httpssvc_metrics_) {
if (transaction_info.type == DnsQueryType::HTTPS) {
bool has_compatible_https = std::ranges::any_of(
results.value(),
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() ==
HostResolverInternalResult::Type::kMetadata;
});
if (has_compatible_https) {
httpssvc_metrics_->SaveForHttps(rcode_for_httpssvc,
std::vector<bool>{true}, elapsed_time);
} else {
httpssvc_metrics_->SaveForHttps(rcode_for_httpssvc, std::vector<bool>(),
elapsed_time);
}
} else {
httpssvc_metrics_->SaveForAddressQuery(elapsed_time, rcode_for_httpssvc);
}
}
switch (transaction_info.type) {
case DnsQueryType::A:
a_record_end_time_ = now;
if (!aaaa_record_end_time_.is_null()) {
RecordResolveTimeDiff("AAAABeforeA", task_start_time_,
aaaa_record_end_time_, a_record_end_time_);
}
break;
case DnsQueryType::AAAA:
aaaa_record_end_time_ = now;
if (!a_record_end_time_.is_null()) {
RecordResolveTimeDiff("ABeforeAAAA", task_start_time_,
a_record_end_time_, aaaa_record_end_time_);
}
break;
case DnsQueryType::HTTPS: {
base::TimeTicks first_address_end_time =
std::min(a_record_end_time_, aaaa_record_end_time_);
if (!first_address_end_time.is_null()) {
RecordResolveTimeDiff("AddressRecordBeforeHTTPS", task_start_time_,
first_address_end_time, now);
}
break;
}
default:
break;
}
if (base::FeatureList::IsEnabled(features::kUseHostResolverCache) ||
delegate_->IsHappyEyeballsV3Enabled()) {
SortTransactionAndHandleResults(std::move(transaction_info),
std::move(results).value());
} else {
HandleTransactionResults(std::move(transaction_info),
std::move(results).value());
}
}
bool HostResolverDnsTask::IsFatalTransactionFailure(
int transaction_error,
const TransactionInfo& transaction_info,
const DnsResponse* response) {
if (transaction_info.type != DnsQueryType::HTTPS) {
DCHECK(transaction_info.error_behavior !=
TransactionErrorBehavior::kFatalOrEmpty);
return false;
}
enum class HttpsTransactionError {
kNoError = 0,
kInsecureError = 1,
kNonFatalError = 2,
kFatalErrorDisabled = 3,
kFatalErrorEnabled = 4,
kMaxValue = kFatalErrorEnabled
} error;
if (transaction_error == OK || (transaction_error == ERR_NAME_NOT_RESOLVED &&
response && response->IsValid())) {
error = HttpsTransactionError::kNoError;
} else if (!secure_) {
DCHECK(transaction_info.error_behavior !=
TransactionErrorBehavior::kFatalOrEmpty);
error = HttpsTransactionError::kInsecureError;
} else if (transaction_error == ERR_DNS_SERVER_FAILED && response &&
response->rcode() != dns_protocol::kRcodeSERVFAIL) {
error = HttpsTransactionError::kNonFatalError;
} else if (features::kUseDnsHttpsSvcbEnforceSecureResponse.Get()) {
DCHECK(transaction_info.error_behavior ==
TransactionErrorBehavior::kFatalOrEmpty);
error = HttpsTransactionError::kFatalErrorEnabled;
} else {
DCHECK(transaction_info.error_behavior !=
TransactionErrorBehavior::kFatalOrEmpty);
error = HttpsTransactionError::kFatalErrorDisabled;
}
UMA_HISTOGRAM_ENUMERATION("Net.DNS.DnsTask.SvcbHttpsTransactionError", error);
return error == HttpsTransactionError::kFatalErrorEnabled;
}
void HostResolverDnsTask::SortTransactionAndHandleResults(
TransactionInfo transaction_info,
Results transaction_results) {
CHECK_LE(std::ranges::count_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() ==
HostResolverInternalResult::Type::kData;
}),
1);
auto data_result_it = std::ranges::find_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kData;
});
std::vector<IPEndPoint> endpoints_to_sort;
if (data_result_it != transaction_results.end()) {
const HostResolverInternalDataResult& data_result =
(*data_result_it)->AsData();
endpoints_to_sort.insert(endpoints_to_sort.end(),
data_result.endpoints().begin(),
data_result.endpoints().end());
}
if (!endpoints_to_sort.empty()) {
auto insertion_result =
transactions_in_progress_.insert(std::move(transaction_info));
CHECK(insertion_result.second);
client_->GetAddressSorter()->Sort(
endpoints_to_sort,
base::BindOnce(&HostResolverDnsTask::OnTransactionSorted,
weak_ptr_factory_.GetWeakPtr(), insertion_result.first,
std::move(transaction_results)));
} else {
HandleTransactionResults(std::move(transaction_info),
std::move(transaction_results));
}
}
void HostResolverDnsTask::OnTransactionSorted(
std::set<TransactionInfo>::iterator transaction_info_it,
Results transaction_results,
bool success,
std::vector<IPEndPoint> sorted) {
CHECK(transaction_info_it != transactions_in_progress_.end());
if (transactions_in_progress_.find(*transaction_info_it) ==
transactions_in_progress_.end()) {
return;
}
TransactionInfo transaction_info =
std::move(transactions_in_progress_.extract(transaction_info_it).value());
auto data_result_it = std::ranges::find_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kData;
});
CHECK(data_result_it != transaction_results.end());
DCHECK_EQ(std::ranges::count_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() ==
HostResolverInternalResult::Type::kData;
}),
1);
if (!success) {
auto error_replacement = std::make_unique<HostResolverInternalErrorResult>(
(*data_result_it)->domain_name(), (*data_result_it)->query_type(),
(*data_result_it)->expiration(), (*data_result_it)->timed_expiration(),
HostResolverInternalResult::Source::kUnknown, ERR_DNS_SORT_ERROR);
CHECK(error_replacement->expiration().has_value());
CHECK(error_replacement->timed_expiration().has_value());
transaction_results.erase(data_result_it);
transaction_results.insert(std::move(error_replacement));
} else if (sorted.empty()) {
auto error_replacement = std::make_unique<HostResolverInternalErrorResult>(
(*data_result_it)->domain_name(), (*data_result_it)->query_type(),
(*data_result_it)->expiration(), (*data_result_it)->timed_expiration(),
(*data_result_it)->source(), ERR_NAME_NOT_RESOLVED);
CHECK(error_replacement->expiration().has_value());
CHECK(error_replacement->timed_expiration().has_value());
transaction_results.erase(data_result_it);
transaction_results.insert(std::move(error_replacement));
} else {
(*data_result_it)->AsData().set_endpoints(std::move(sorted));
}
HandleTransactionResults(std::move(transaction_info),
std::move(transaction_results));
}
void HostResolverDnsTask::HandleTransactionResults(
TransactionInfo transaction_info,
Results transaction_results) {
CHECK(transactions_in_progress_.find(transaction_info) ==
transactions_in_progress_.end());
if (base::FeatureList::IsEnabled(features::kUseHostResolverCache) &&
resolve_context_->host_resolver_cache() != nullptr) {
for (const std::unique_ptr<HostResolverInternalResult>& result :
transaction_results) {
resolve_context_->host_resolver_cache()->Set(
result->Clone(), anonymization_key_, HostResolverSource::DNS,
secure_);
}
}
if (transaction_info.type == DnsQueryType::HTTPS &&
ShouldTriggerHttpToHttpsUpgrade(transaction_results)) {
OnFailure(ERR_DNS_NAME_HTTPS_ONLY, false,
&transaction_results);
return;
}
auto failure_result_it = std::ranges::find_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kError;
});
DCHECK_LE(std::ranges::count_if(
transaction_results,
[](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() ==
HostResolverInternalResult::Type::kError;
}),
1);
if (failure_result_it != transaction_results.end() &&
(*failure_result_it)->AsError().error() != ERR_NAME_NOT_RESOLVED) {
OnFailure((*failure_result_it)->AsError().error(), true,
&transaction_results);
return;
}
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
if (base::FeatureList::IsEnabled(
network::features::kEnableNwebExHttpDnsFallback) &&
utils) {
bool secure_dns_fallback_available = false;
if (delegate_) {
secure_dns_fallback_available =
delegate_->CanUseSecureDnsFallback(&*resolve_context_);
}
utils->MaybeModifyInsecureDnsTaskResolveResults(
std::string(host_.GetHostnameWithoutBrackets()),
secure_dns_fallback_available, transaction_results);
}
#endif
if (deferred_failure_) {
OnDeferredFailure();
return;
}
ResultRefs result_refs;
for (auto it = transaction_results.begin();
it != transaction_results.end();) {
result_refs.insert(it->get());
saved_results_.insert(std::move(transaction_results.extract(it++).value()));
}
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
if (base::FeatureList::IsEnabled(
network::features::kEnableNwebExHttpDnsFallback) && utils) {
HostCache::Entry legacy_results(saved_results_, base::Time::Now(),
tick_clock_->NowTicks(),
HostCache::Entry::SOURCE_DNS);
utils->ArkWebSetNotNeedQueryType(legacy_results.error(), transaction_info.type);
}
#endif
OnTransactionsFinished(
SingleTransactionResults(transaction_info.type, std::move(result_refs)));
}
void HostResolverDnsTask::OnTransactionsFinished(
std::optional<SingleTransactionResults> single_transaction_results) {
if (!transactions_in_progress_.empty() || !transactions_needed_.empty()) {
MaybeStartTimeoutTimer();
delegate_->OnIntermediateTransactionsComplete(
std::move(single_transaction_results));
return;
}
timeout_timer_.Stop();
if (!base::FeatureList::IsEnabled(features::kUseHostResolverCache) &&
!delegate_->IsHappyEyeballsV3Enabled()) {
std::vector<IPEndPoint> endpoints_to_sort = ExtractAddressResultsForSort(
saved_results_, delegate_->IsHappyEyeballsV3Enabled());
if (!endpoints_to_sort.empty()) {
client_->GetAddressSorter()->Sort(
endpoints_to_sort,
base::BindOnce(&HostResolverDnsTask::OnSortComplete,
weak_ptr_factory_.GetWeakPtr(),
tick_clock_->NowTicks(), std::move(saved_results_),
secure_));
return;
}
}
OnSuccess(std::move(saved_results_));
}
void HostResolverDnsTask::OnSortComplete(base::TimeTicks sort_start_time,
Results results,
bool secure,
bool success,
std::vector<IPEndPoint> sorted) {
CHECK(!base::FeatureList::IsEnabled(features::kUseHostResolverCache));
CHECK(!delegate_->IsHappyEyeballsV3Enabled());
if (!success) {
OnFailure(ERR_DNS_SORT_ERROR, true, &results);
return;
}
if (sorted.empty()) {
LOG(WARNING) << "Address list empty after RFC3484 sort";
OnFailure(ERR_NAME_NOT_RESOLVED, true, &results);
return;
}
auto merged_error_it = std::ranges::find_if(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kError &&
result->query_type() == DnsQueryType::UNSPECIFIED &&
result->timed_expiration().has_value();
});
CHECK(merged_error_it != results.end());
results.insert(std::make_unique<HostResolverInternalDataResult>(
(*merged_error_it)->domain_name(), DnsQueryType::UNSPECIFIED,
(*merged_error_it)->expiration(),
(*merged_error_it)->timed_expiration().value(),
(*merged_error_it)->source(), std::move(sorted),
std::vector<std::string>{}, std::vector<HostPortPair>{}));
results.erase(merged_error_it);
OnSuccess(std::move(results));
}
bool HostResolverDnsTask::AnyPotentiallyFatalTransactionsRemain() {
auto is_fatal_or_empty_error = [](TransactionErrorBehavior behavior) {
return behavior == TransactionErrorBehavior::kFatalOrEmpty;
};
return std::ranges::any_of(transactions_needed_, is_fatal_or_empty_error,
&TransactionInfo::error_behavior) ||
std::ranges::any_of(transactions_in_progress_, is_fatal_or_empty_error,
&TransactionInfo::error_behavior);
}
void HostResolverDnsTask::CancelNonFatalTransactions() {
auto has_non_fatal_or_empty_error = [](const TransactionInfo& info) {
return info.error_behavior != TransactionErrorBehavior::kFatalOrEmpty;
};
base::EraseIf(transactions_needed_, has_non_fatal_or_empty_error);
std::erase_if(transactions_in_progress_, has_non_fatal_or_empty_error);
}
void HostResolverDnsTask::OnFailure(int net_error,
bool allow_fallback,
const Results* base_results) {
CHECK_NE(net_error, OK);
std::optional<base::TimeTicks> expiration;
std::optional<base::Time> timed_expiration;
if (base_results) {
for (const std::unique_ptr<HostResolverInternalResult>& result :
*base_results) {
if (result->expiration().has_value()) {
expiration = std::min(expiration.value_or(base::TimeTicks::Max()),
result->expiration().value());
}
if (result->timed_expiration().has_value()) {
timed_expiration =
std::min(timed_expiration.value_or(base::Time::Max()),
result->timed_expiration().value());
}
}
}
deferred_failure_ = std::make_unique<HostResolverInternalErrorResult>(
std::string(host_.GetHostnameWithoutBrackets()),
DnsQueryType::UNSPECIFIED, expiration, timed_expiration,
HostResolverInternalResult::Source::kDns, net_error);
OnDeferredFailure(allow_fallback);
}
void HostResolverDnsTask::OnDeferredFailure(bool allow_fallback) {
CHECK(deferred_failure_);
#if BUILDFLAG(ARKWEB_EXT_HTTP_DNS_FALLBACK)
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableNwebExHttpDnsFallback) &&
allow_fallback) {
if (utils->ArkWebFailedTransaction(deferred_failure_->error(), deferred_failure_->query_type())) {
return;
}
} else if (allow_fallback && AnyPotentiallyFatalTransactionsRemain()) {
#else
if (allow_fallback && AnyPotentiallyFatalTransactionsRemain()) {
#endif
CancelNonFatalTransactions();
OnTransactionsFinished(std::nullopt);
return;
}
net_log_.EndEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK, [&] {
return NetLogDnsTaskFailedParams(*deferred_failure_, saved_results_);
});
Results results;
results.insert(std::move(deferred_failure_));
delegate_->OnDnsTaskComplete(task_start_time_, allow_fallback,
std::move(results), secure_
#if BUILDFLAG(ARKWEB_EXT_NAVIGATION)
,
truncation_results_
#endif
);
}
void HostResolverDnsTask::OnSuccess(Results results) {
net_log_.EndEvent(NetLogEventType::HOST_RESOLVER_DNS_TASK,
[&] { return NetLogResults(results); });
delegate_->OnDnsTaskComplete(task_start_time_, true,
std::move(results), secure_
#if BUILDFLAG(ARKWEB_EXT_NAVIGATION)
,
truncation_results_
#endif
);
}
bool HostResolverDnsTask::AnyOfTypeTransactionsRemain(
std::initializer_list<DnsQueryType> types) const {
DCHECK(!transactions_needed_.empty() || !transactions_in_progress_.empty());
if (std::ranges::find_first_of(transactions_in_progress_, types,
{},
&TransactionInfo::type) !=
transactions_in_progress_.end()) {
return true;
}
return std::ranges::find_first_of(transactions_needed_, types, {},
&TransactionInfo::type) !=
transactions_needed_.end();
}
void HostResolverDnsTask::MaybeStartTimeoutTimer() {
DCHECK(!transactions_in_progress_.empty() || !transactions_needed_.empty());
if (timeout_timer_.IsRunning()) {
return;
}
if (AnyOfTypeTransactionsRemain({DnsQueryType::A, DnsQueryType::AAAA})) {
return;
}
base::TimeDelta timeout_max;
int extra_time_percent = 0;
base::TimeDelta timeout_min;
if (AnyOfTypeTransactionsRemain({DnsQueryType::HTTPS})) {
DCHECK(base::FeatureList::IsEnabled(features::kUseDnsHttpsSvcb));
if (secure_) {
timeout_max = https_svcb_options_.secure_extra_time_max;
extra_time_percent = https_svcb_options_.secure_extra_time_percent;
timeout_min = https_svcb_options_.secure_extra_time_min;
} else {
timeout_max = https_svcb_options_.insecure_extra_time_max;
extra_time_percent = https_svcb_options_.insecure_extra_time_percent;
timeout_min = https_svcb_options_.insecure_extra_time_min;
}
if (secure_ && features::kUseDnsHttpsSvcbEnforceSecureResponse.Get()) {
timeout_max = base::TimeDelta();
extra_time_percent = 0;
timeout_min = base::TimeDelta();
}
} else {
NOTREACHED();
}
base::TimeDelta timeout;
if (extra_time_percent > 0) {
base::TimeDelta total_time_for_other_transactions =
tick_clock_->NowTicks() - task_start_time_;
timeout = total_time_for_other_transactions * extra_time_percent / 100;
timeout = std::max(timeout, base::Milliseconds(1));
if (!timeout_max.is_zero()) {
timeout = std::min(timeout, timeout_max);
}
if (!timeout_min.is_zero()) {
timeout = std::max(timeout, timeout_min);
}
} else {
timeout = std::max(timeout_min, timeout_max);
}
if (!timeout.is_zero()) {
timeout_timer_.Start(FROM_HERE, timeout,
base::BindOnce(&HostResolverDnsTask::OnTimeout,
base::Unretained(this)));
}
}
bool HostResolverDnsTask::ShouldTriggerHttpToHttpsUpgrade(
const Results& results) {
if (!host_.HasScheme()) {
return false;
}
const std::string& scheme = host_.GetScheme();
if (scheme != url::kHttpScheme && scheme != url::kWsScheme) {
return false;
}
return std::ranges::any_of(
results, [](const std::unique_ptr<HostResolverInternalResult>& result) {
return result->type() == HostResolverInternalResult::Type::kMetadata;
});
}
}