blob: a258b6a89ed275b7e0a56e01d14c1c9a03e1a8b7 [file] [log] [blame]
// Copyright 2020 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/proxy_resolution/configured_proxy_resolution_request.h"
#include <optional>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "net/base/net_errors.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_source_type.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_info.h"
#include "third_party/abseil-cpp/absl/container/flat_hash_set.h"
namespace net {
namespace {
bool CheckDnsCondition(
const ProxyConfig::ProxyOverrideRule::DnsProbeCondition& dns_condition,
const ResolveHostResult& dns_result) {
switch (dns_condition.result) {
case ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kNotFound:
return dns_result.is_address_list_empty;
case ProxyConfig::ProxyOverrideRule::DnsProbeCondition::Result::kResolved:
return !dns_result.is_address_list_empty;
}
}
base::Value::Dict CreateEndHostResolutionNetLogParams(
const url::SchemeHostPort& host,
const ResolveHostResult& result,
bool was_resolved_sync) {
base::Value::Dict dict;
dict.Set("host", host.Serialize());
dict.Set("was_resolved_sync", was_resolved_sync);
result.AddToDict(dict);
return dict;
}
} // namespace
ConfiguredProxyResolutionRequest::ConfiguredProxyResolutionRequest(
ConfiguredProxyResolutionService* service,
const GURL& url,
const std::string& method,
const NetworkAnonymizationKey& network_anonymization_key,
ProxyInfo* results,
CompletionOnceCallback user_callback,
const NetLogWithSource& net_log,
RequestPriority priority)
: service_(service),
user_callback_(std::move(user_callback)),
results_(results),
url_(url),
method_(method),
network_anonymization_key_(network_anonymization_key),
net_log_(net_log),
priority_(priority),
creation_time_(base::TimeTicks::Now()) {
CHECK(!user_callback_.is_null());
}
ConfiguredProxyResolutionRequest::~ConfiguredProxyResolutionRequest() {
if (service_) {
service_->RemovePendingRequest(this);
Cancel();
// This should be emitted last, after any message `Cancel()` may
// trigger.
net_log_.EndEvent(NetLogEventType::PROXY_RESOLUTION_SERVICE);
}
}
// Starts the resolve proxy request.
int ConfiguredProxyResolutionRequest::Start() {
CHECK(!was_completed());
CHECK(!is_started());
CHECK(service_->config_);
traffic_annotation_ = MutableNetworkTrafficAnnotationTag(
service_->config_->traffic_annotation());
if (!service_->config_->value().proxy_override_rules().empty()) {
net_log_.BeginEvent(NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES);
for (const auto& rule : service_->config_->value().proxy_override_rules()) {
if (rule.MatchesDestination(url_)) {
bool skip_rule = false;
for (const auto& dns_condition : rule.dns_conditions) {
// DNS resolution requests are keyed by the host and the network
// anonymization key, but not by listeners. This means that a given
// request may end up as listener multiple times for the same DNS host
// resolution requests (e.g. if the same host is in multiple rules).
// This is fine, as long as `OnDnsHostResolved` knows how to handle
// multiple calls for the same host.
auto result = service_->RequestHostResolution(
dns_condition, weak_factory_.GetWeakPtr(),
network_anonymization_key_, net_log_, priority_);
if (result) {
net_log_.AddEvent(
NetLogEventType::PROXY_OVERRIDE_END_HOST_RESOLUTION, [&] {
return CreateEndHostResolutionNetLogParams(
dns_condition.host, result.value(),
/*was_resolved_sync=*/true);
});
if (!CheckDnsCondition(dns_condition, result.value())) {
// No need to trigger DNS resolutions for the remaining hosts
// in this rule, as it does not apply.
skip_rule = true;
break;
}
dns_results_.emplace(dns_condition.host, result.value());
}
}
if (!skip_rule) {
applicable_override_rules_.push(rule);
}
}
}
if (applicable_override_rules_.empty()) {
// No override rules applied.
net_log_.EndEvent(NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES);
}
}
return ContinueProxyResolution();
}
void ConfiguredProxyResolutionRequest::
StartAndCompleteCheckingForSynchronous() {
int rv = service_->TryToCompleteSynchronously(
url_, /*bypass_override_rules=*/false, net_log_, results_);
if (rv == ERR_IO_PENDING) {
rv = Start();
}
if (rv != ERR_IO_PENDING) {
QueryComplete(rv);
}
}
void ConfiguredProxyResolutionRequest::Cancel() {
net_log_.AddEvent(NetLogEventType::CANCELLED);
if (!applicable_override_rules_.empty()) {
net_log_.EndEvent(NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES);
}
Reset();
CHECK(!is_started());
}
int ConfiguredProxyResolutionRequest::QueryDidComplete(int result_code) {
CHECK(!was_completed());
// Clear state so that `is_started()` returns false while
// DidFinishResolvingProxy() runs.
if (is_started()) {
Reset();
}
// Note that DidFinishResolvingProxy might modify `results_`.
int rv = service_->DidFinishResolvingProxy(url_, network_anonymization_key_,
method_, results_, result_code,
net_log_);
// Make a note in the results which configuration was in use at the
// time of the resolve.
results_->set_proxy_resolve_start_time(creation_time_);
results_->set_proxy_resolve_end_time(base::TimeTicks::Now());
// If annotation is not already set, e.g. through TryToCompleteSynchronously
// function, use in-progress-resolve annotation.
if (!results_->traffic_annotation().is_valid())
results_->set_traffic_annotation(traffic_annotation_);
// If proxy is set without error, ensure that an annotation is provided.
if (result_code != ERR_ABORTED && !rv) {
// TODO(crbug.com/469006851): Turn this back into a CHECK once crashes were
// fully fixed.
DCHECK(results_->traffic_annotation().is_valid());
}
// Reset the state associated with in-progress-resolve.
traffic_annotation_.reset();
return rv;
}
int ConfiguredProxyResolutionRequest::QueryDidCompleteSynchronously(
int result_code) {
int rv = QueryDidComplete(result_code);
service_ = nullptr;
return rv;
}
LoadState ConfiguredProxyResolutionRequest::GetLoadState() const {
LoadState load_state = LOAD_STATE_IDLE;
if (service_ && service_->GetLoadStateIfAvailable(&load_state))
return load_state;
if (is_started())
return resolve_job_->GetLoadState();
return LOAD_STATE_RESOLVING_PROXY_FOR_URL;
}
// Callback for when the ProxyResolver request has completed.
void ConfiguredProxyResolutionRequest::QueryComplete(int result_code) {
result_code = QueryDidComplete(result_code);
CompletionOnceCallback callback = std::move(user_callback_);
service_->RemovePendingRequest(this);
service_ = nullptr;
user_callback_.Reset();
std::move(callback).Run(result_code);
}
int ConfiguredProxyResolutionRequest::ContinueProxyResolution() {
if (!applicable_override_rules_.empty()) {
int rv = EvaluateApplicableOverrideRules();
if (rv == ERR_IO_PENDING || !results_->is_empty()) {
return rv;
}
}
if (service_->ApplyPacBypassRules(url_, results_)) {
return OK;
}
// Required in case some override rules needed to run asynchronously, but
// ended up not applying, and there are e.g. manual proxy settings as
// fallback.
int rv = service_->TryToCompleteSynchronously(
url_, /*bypass_override_rules=*/true, net_log_, results_);
if (rv != ERR_IO_PENDING) {
return rv;
}
return service_->GetProxyResolver()->GetProxyForURL(
url_, network_anonymization_key_, results_,
base::BindOnce(&ConfiguredProxyResolutionRequest::QueryComplete,
base::Unretained(this)),
&resolve_job_, net_log_);
}
void ConfiguredProxyResolutionRequest::OnDnsHostResolved(
const url::SchemeHostPort& host,
const ResolveHostResult& result) {
if (applicable_override_rules_.empty()) {
// DNS resolution is only required for override rules. Ignore these function
// calls if the current request is not resolving override rules.
return;
}
// Using `try_emplace` as this line may be hit multiple times for the same
// host (see comment in `Start`).
auto [unused, inserted] = dns_results_.try_emplace(host, result);
// Entry already existed, likely with the same value as before. Same value or
// not, we keep the old result, so there's nothing else to do.
if (!inserted) {
return;
}
net_log_.AddEvent(NetLogEventType::PROXY_OVERRIDE_END_HOST_RESOLUTION, [&] {
return CreateEndHostResolutionNetLogParams(host, result,
/*was_resolved_sync=*/false);
});
if (!service_ || !service_->IsReady()) {
// Something happened during DNS resolution which invalidated the
// service's state (e.g. config update). The service will resume
// the current request if/when needed.
return;
}
int rv = ContinueProxyResolution();
if (rv == ERR_IO_PENDING) {
// Missing a required DNS resolution.
return;
}
QueryComplete(rv);
}
int ConfiguredProxyResolutionRequest::EvaluateApplicableOverrideRules() {
while (!applicable_override_rules_.empty()) {
const auto& applicable_rule = applicable_override_rules_.front();
// Not having any conditions means they are all satisfied.
bool conditions_satisfied = true;
for (const auto& dns_condition : applicable_rule.dns_conditions) {
// If no results are found in the DNS results map, it means that the DNS
// resolution request owned by the service is still pending.
auto dns_result_it = dns_results_.find(dns_condition.host);
if (dns_result_it == dns_results_.end()) {
return ERR_IO_PENDING;
}
conditions_satisfied =
conditions_satisfied &&
CheckDnsCondition(dns_condition, dns_result_it->second);
}
if (conditions_satisfied) {
net_log_.AddEvent(NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULE_APPLIED,
[&] { return applicable_rule.ToDict(); });
results_->UseProxyList(applicable_rule.proxy_list);
break;
}
// Conditions were evaluated successfully, but are not satisfied. This
// current rule is therefore no longer applicable.
applicable_override_rules_.pop();
}
dns_results_.clear();
net_log_.EndEvent(NetLogEventType::PROXY_RESOLUTION_OVERRIDE_RULES);
return OK;
}
void ConfiguredProxyResolutionRequest::Reset() {
// The request may already be running in the resolver.
resolve_job_.reset();
applicable_override_rules_ = base::queue<ProxyConfig::ProxyOverrideRule>();
dns_results_.clear();
}
} // namespace net