blob: 3e167da2d755171e8bf706aca42f2f6a8b1a965b [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/common/data_reduction_proxy_bypass_protocol.h"
#include <vector>
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.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_retry_info.h"
#include "url/gurl.h"
namespace data_reduction_proxy {
namespace {
static const char kDataReductionCoreProxy[] = "proxy.googlezip.net";
// Returns the Data Reduction Proxy servers in |proxy_type_info| that should be
// marked bad according to |data_reduction_proxy_info|.
std::vector<net::ProxyServer> GetProxiesToMarkBad(
const DataReductionProxyInfo& data_reduction_proxy_info,
const DataReductionProxyTypeInfo& proxy_type_info) {
DCHECK_GT(proxy_type_info.proxy_servers.size(), proxy_type_info.proxy_index);
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;
std::vector<net::ProxyServer> bad_proxies;
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());
bad_proxies.push_back(bad_proxy);
}
return bad_proxies;
}
void ReportResponseProxyServerStatusHistogram(
DataReductionProxyBypassProtocol::ResponseProxyServerStatus status) {
UMA_HISTOGRAM_ENUMERATION(
"DataReductionProxy.ResponseProxyServerStatus", status,
DataReductionProxyBypassProtocol::RESPONSE_PROXY_SERVER_STATUS_MAX);
}
} // namespace
void RecordDataReductionProxyBypassInfo(
bool is_primary,
bool bypass_all,
DataReductionProxyBypassType bypass_type) {
if (bypass_all) {
if (is_primary) {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypePrimary",
bypass_type, BYPASS_EVENT_TYPE_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BlockTypeFallback",
bypass_type, BYPASS_EVENT_TYPE_MAX);
}
} else {
if (is_primary) {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypePrimary",
bypass_type, BYPASS_EVENT_TYPE_MAX);
} else {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.BypassTypeFallback",
bypass_type, BYPASS_EVENT_TYPE_MAX);
}
}
}
void DetectAndRecordMissingViaHeaderResponseCode(
bool is_primary,
const net::HttpResponseHeaders& headers) {
if (HasDataReductionProxyViaHeader(headers, nullptr)) {
// The data reduction proxy via header is present, so don't record anything.
return;
}
if (is_primary) {
base::UmaHistogramSparse(
"DataReductionProxy.MissingViaHeader.ResponseCode.Primary",
headers.response_code());
} else {
base::UmaHistogramSparse(
"DataReductionProxy.MissingViaHeader.ResponseCode.Fallback",
headers.response_code());
}
}
DataReductionProxyBypassProtocol::DataReductionProxyBypassProtocol() = default;
bool DataReductionProxyBypassProtocol::MaybeBypassProxyAndPrepareToRetry(
const std::string& method,
const std::vector<GURL>& url_chain,
const net::HttpResponseHeaders* response_headers,
const net::ProxyServer& proxy_server,
net::Error net_error,
const net::ProxyRetryInfoMap& proxy_retry_info,
const base::Optional<DataReductionProxyTypeInfo>& proxy_type_info,
DataReductionProxyBypassType* proxy_bypass_type,
DataReductionProxyInfo* data_reduction_proxy_info,
std::vector<net::ProxyServer>* bad_proxies,
int* additional_load_flags_for_restart) {
DCHECK(thread_checker_.CalledOnValidThread());
bad_proxies->clear();
*additional_load_flags_for_restart = 0;
DataReductionProxyBypassType bypass_type;
bool retry;
std::vector<DataReductionProxyServer> placeholder_proxy_servers;
DataReductionProxyTypeInfo placeholder_proxy_type_info(
placeholder_proxy_servers, 0U /* proxy_index */);
const DataReductionProxyTypeInfo& proxy_type_info_value =
proxy_type_info ? *proxy_type_info : placeholder_proxy_type_info;
if (!response_headers) {
retry = HandleInvalidResponseHeadersCase(
url_chain, net_error, proxy_type_info, data_reduction_proxy_info,
&bypass_type);
} else {
if (!proxy_type_info) {
if (!proxy_server.is_valid() || proxy_server.is_direct() ||
proxy_server.host_port_pair().IsEmpty()) {
ReportResponseProxyServerStatusHistogram(
RESPONSE_PROXY_SERVER_STATUS_EMPTY);
return false;
}
if (!HasDataReductionProxyViaHeader(*response_headers, nullptr)) {
ReportResponseProxyServerStatusHistogram(
RESPONSE_PROXY_SERVER_STATUS_NON_DRP_NO_VIA);
return false;
}
ReportResponseProxyServerStatusHistogram(
RESPONSE_PROXY_SERVER_STATUS_NON_DRP_WITH_VIA);
// If the |proxy_server| doesn't match any of the currently configured
// Data Reduction Proxies, but it still has the Data Reduction Proxy via
// header, then apply the bypass logic regardless.
// TODO(sclittle): Remove this workaround once http://crbug.com/876776 is
// fixed.
placeholder_proxy_servers.push_back(
DataReductionProxyServer(proxy_server));
} else {
ReportResponseProxyServerStatusHistogram(
RESPONSE_PROXY_SERVER_STATUS_DRP);
}
retry = HandleValidResponseHeadersCase(
url_chain, response_headers, proxy_retry_info, proxy_type_info_value,
proxy_bypass_type, data_reduction_proxy_info, &bypass_type);
}
if (!retry)
return false;
if (data_reduction_proxy_info->mark_proxies_as_bad) {
*bad_proxies =
GetProxiesToMarkBad(*data_reduction_proxy_info, proxy_type_info_value);
} else {
*additional_load_flags_for_restart =
net::LOAD_BYPASS_CACHE | net::LOAD_BYPASS_PROXY;
}
return bypass_type == BYPASS_EVENT_TYPE_CURRENT || !response_headers ||
net::HttpUtil::IsMethodIdempotent(method);
}
bool DataReductionProxyBypassProtocol::HandleInvalidResponseHeadersCase(
const std::vector<GURL>& url_chain,
net::Error net_error,
const base::Optional<DataReductionProxyTypeInfo>&
data_reduction_proxy_type_info,
DataReductionProxyInfo* data_reduction_proxy_info,
DataReductionProxyBypassType* bypass_type) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!data_reduction_proxy_type_info)
return false;
DCHECK(url_chain.back().SchemeIs(url::kHttpScheme));
DCHECK_NE(net::OK, net_error);
if (net_error == net::ERR_IO_PENDING ||
net_error == net::ERR_NETWORK_CHANGED ||
net_error == net::ERR_INTERNET_DISCONNECTED ||
net_error == net::ERR_NETWORK_IO_SUSPENDED ||
net_error == net::ERR_ABORTED ||
net_error == net::ERR_INSUFFICIENT_RESOURCES ||
net_error == net::ERR_OUT_OF_MEMORY ||
net_error == net::ERR_NAME_NOT_RESOLVED ||
net_error == net::ERR_NAME_RESOLUTION_FAILED ||
net_error == net::ERR_ADDRESS_UNREACHABLE || std::abs(net_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", -net_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);
*bypass_type = BYPASS_EVENT_TYPE_MEDIUM;
return true;
}
bool DataReductionProxyBypassProtocol::HandleValidResponseHeadersCase(
const std::vector<GURL>& url_chain,
const net::HttpResponseHeaders* response_headers,
const net::ProxyRetryInfoMap& proxy_retry_info,
const DataReductionProxyTypeInfo& data_reduction_proxy_type_info,
DataReductionProxyBypassType* proxy_bypass_type,
DataReductionProxyInfo* data_reduction_proxy_info,
DataReductionProxyBypassType* bypass_type) {
DCHECK(thread_checker_.CalledOnValidThread());
// Lack of headers implies either that the request was served from cache or
// that request was served directly from the origin.
DCHECK(response_headers);
// 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.
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(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_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 (!IsProxyBypassedAtTime(proxy_retry_info, proxy_server,
base::TimeTicks::Now(), nullptr)) {
RecordDataReductionProxyBypassInfo(
data_reduction_proxy_type_info.proxy_index == 0U,
data_reduction_proxy_info->bypass_all, *bypass_type);
}
return true;
}
bool IsProxyBypassedAtTime(const net::ProxyRetryInfoMap& retry_map,
const net::ProxyServer& proxy_server,
base::TimeTicks t,
base::TimeDelta* retry_delay) {
auto found = retry_map.find(proxy_server.ToURI());
if (found == retry_map.end() || found->second.bad_until < t) {
return false;
}
if (retry_delay)
*retry_delay = found->second.current_delay;
return true;
}
bool IsQuicProxy(const net::ProxyServer& proxy_server) {
// Enable QUIC for whitelisted proxies.
return (proxy_server.is_https() || proxy_server.is_quic()) &&
(proxy_server.host_port_pair() ==
net::HostPortPair(kDataReductionCoreProxy, 443));
}
void RecordQuicProxyStatus(QuicProxyStatus status) {
UMA_HISTOGRAM_ENUMERATION("DataReductionProxy.Quic.ProxyStatus", status,
QUIC_PROXY_STATUS_BOUNDARY);
}
} // namespace data_reduction_proxy