blob: 460aceb4fb7005e4f63710d569e313697fda183f [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_protocol.h"
#include <vector>
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_bypass_stats.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_config.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_server.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_type_info.h"
#include "net/base/load_flags.h"
#include "net/base/proxy_server.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/proxy_resolution/proxy_retry_info.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_status.h"
#include "url/gurl.h"
namespace data_reduction_proxy {
namespace {
// Adds the Data Reduction Proxy servers in |proxy_type_info| that should be
// marked bad according to |data_reduction_proxy_info| to the retry map
// maintained by the proxy resolution service of the |request|.
void MarkProxiesAsBad(const net::URLRequest& request,
const DataReductionProxyInfo& data_reduction_proxy_info,
const DataReductionProxyTypeInfo& proxy_type_info) {
DCHECK_GT(proxy_type_info.proxy_servers.size(), proxy_type_info.proxy_index);
// Synthesize a suitable |ProxyInfo| to add the proxies to the
// |ProxyRetryInfoMap| of the proxy service.
net::ProxyList proxy_list;
const size_t bad_proxy_end_index = data_reduction_proxy_info.bypass_all
? proxy_type_info.proxy_servers.size()
: proxy_type_info.proxy_index + 1U;
for (size_t i = proxy_type_info.proxy_index; i < bad_proxy_end_index; ++i) {
const net::ProxyServer& bad_proxy =
proxy_type_info.proxy_servers[i].proxy_server();
DCHECK(bad_proxy.is_valid());
DCHECK(!bad_proxy.is_direct());
proxy_list.AddProxyServer(bad_proxy);
}
std::vector<net::ProxyServer> bad_proxies = proxy_list.GetAll();
proxy_list.AddProxyServer(net::ProxyServer::Direct());
net::ProxyInfo proxy_info;
proxy_info.UseProxyList(proxy_list);
request.context()->proxy_resolution_service()->MarkProxiesAsBadUntil(
proxy_info, data_reduction_proxy_info.bypass_duration, bad_proxies,
request.net_log());
}
} // namespace
DataReductionProxyBypassProtocol::DataReductionProxyBypassProtocol(
DataReductionProxyConfig* config)
: config_(config) {
DCHECK(config_);
}
DataReductionProxyBypassProtocol::~DataReductionProxyBypassProtocol() {
}
bool DataReductionProxyBypassProtocol::MaybeBypassProxyAndPrepareToRetry(
net::URLRequest* request,
DataReductionProxyBypassType* proxy_bypass_type,
DataReductionProxyInfo* data_reduction_proxy_info) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(request);
DataReductionProxyBypassType bypass_type;
const net::HttpResponseHeaders* response_headers =
request->response_info().headers.get();
bool retry;
if (!response_headers) {
retry = HandleInvalidResponseHeadersCase(
*request, data_reduction_proxy_info, &bypass_type);
} else {
retry = HandleValidResponseHeadersCase(
*request, proxy_bypass_type, data_reduction_proxy_info, &bypass_type);
}
if (!retry)
return false;
if (data_reduction_proxy_info->mark_proxies_as_bad) {
base::Optional<DataReductionProxyTypeInfo> proxy_type_info =
config_->FindConfiguredDataReductionProxy(request->proxy_server());
DCHECK(proxy_type_info);
MarkProxiesAsBad(*request, *data_reduction_proxy_info, *proxy_type_info);
} else {
request->SetLoadFlags(request->load_flags() | net::LOAD_BYPASS_CACHE |
net::LOAD_BYPASS_PROXY);
}
return bypass_type == BYPASS_EVENT_TYPE_CURRENT || !response_headers ||
net::HttpUtil::IsMethodIdempotent(request->method());
}
bool DataReductionProxyBypassProtocol::HandleInvalidResponseHeadersCase(
const net::URLRequest& request,
DataReductionProxyInfo* data_reduction_proxy_info,
DataReductionProxyBypassType* bypass_type) const {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(nullptr, request.response_info().headers.get());
// Handling of invalid response headers is enabled by default.
if (!GetFieldTrialParamByFeatureAsBool(
features::kDataReductionProxyRobustConnection,
"handle_invalid_respnse_headers", true)) {
return false;
}
if (!config_->FindConfiguredDataReductionProxy(request.proxy_server()))
return false;
DCHECK(request.url().SchemeIs(url::kHttpScheme));
net::URLRequestStatus status = request.status();
DCHECK(!status.is_success());
DCHECK_NE(net::OK, status.error());
if (status.error() == net::ERR_IO_PENDING ||
status.error() == net::ERR_NETWORK_CHANGED ||
status.error() == net::ERR_INTERNET_DISCONNECTED ||
status.error() == net::ERR_NETWORK_IO_SUSPENDED ||
status.error() == net::ERR_ABORTED ||
status.error() == net::ERR_INSUFFICIENT_RESOURCES ||
status.error() == net::ERR_OUT_OF_MEMORY ||
status.error() == net::ERR_NAME_NOT_RESOLVED ||
status.error() == net::ERR_NAME_RESOLUTION_FAILED ||
status.error() == net::ERR_ADDRESS_UNREACHABLE ||
std::abs(status.error()) >= 400) {
// No need to retry the request or mark the proxy as bad. Only bypass on
// System related errors, connection related errors and certificate errors.
return false;
}
static_assert(
net::ERR_CONNECTION_RESET > -400 && net::ERR_SSL_PROTOCOL_ERROR > -400,
"net error is not handled");
base::UmaHistogramSparse(
"DataReductionProxy.InvalidResponseHeadersReceived.NetError",
-status.error());
data_reduction_proxy_info->bypass_all = false;
data_reduction_proxy_info->mark_proxies_as_bad = true;
data_reduction_proxy_info->bypass_duration = base::TimeDelta::FromMinutes(5);
data_reduction_proxy_info->bypass_action = BYPASS_ACTION_TYPE_BYPASS;
*bypass_type = BYPASS_EVENT_TYPE_MEDIUM;
return true;
}
bool DataReductionProxyBypassProtocol::HandleValidResponseHeadersCase(
const net::URLRequest& request,
DataReductionProxyBypassType* proxy_bypass_type,
DataReductionProxyInfo* data_reduction_proxy_info,
DataReductionProxyBypassType* bypass_type) const {
DCHECK(thread_checker_.CalledOnValidThread());
// Empty implies either that the request was served from cache or that
// request was served directly from the origin.
const net::HttpResponseHeaders* response_headers =
request.response_info().headers.get();
DCHECK(response_headers);
base::Optional<DataReductionProxyTypeInfo> data_reduction_proxy_type_info =
config_->FindConfiguredDataReductionProxy(request.proxy_server());
if (!data_reduction_proxy_type_info)
return false;
// At this point, the response is expected to have the data reduction proxy
// via header, so detect and report cases where the via header is missing.
DataReductionProxyBypassStats::DetectAndRecordMissingViaHeaderResponseCode(
data_reduction_proxy_type_info->proxy_index == 0U, *response_headers);
// GetDataReductionProxyBypassType will only log a net_log event if a bypass
// command was sent via the data reduction proxy headers.
*bypass_type = GetDataReductionProxyBypassType(
request.url_chain(), *response_headers, data_reduction_proxy_info);
if (proxy_bypass_type)
*proxy_bypass_type = *bypass_type;
if (*bypass_type == BYPASS_EVENT_TYPE_MAX)
return false;
DCHECK(request.context());
DCHECK(request.context()->proxy_resolution_service());
DCHECK_GT(data_reduction_proxy_type_info->proxy_servers.size(),
data_reduction_proxy_type_info->proxy_index);
const net::ProxyServer& proxy_server =
data_reduction_proxy_type_info
->proxy_servers[data_reduction_proxy_type_info->proxy_index]
.proxy_server();
// Only record UMA if the proxy isn't already on the retry list.
if (!config_->IsProxyBypassed(
request.context()->proxy_resolution_service()->proxy_retry_info(),
proxy_server, nullptr)) {
DataReductionProxyBypassStats::RecordDataReductionProxyBypassInfo(
data_reduction_proxy_type_info->proxy_index == 0U,
data_reduction_proxy_info->bypass_all, proxy_server, *bypass_type);
}
return true;
}
} // namespace data_reduction_proxy