blob: 9861fe3698d313fa26130c998cfb18ed34dcfbe5 [file] [log] [blame]
// Copyright 2019 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 "net/http/http_proxy_connect_job.h"
#include <utility>
#include "base/bind.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_params.h"
#include "base/no_destructor.h"
#include "base/numerics/ranges.h"
#include "base/optional.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/http/http_proxy_client_socket_wrapper.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_with_source.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/socket/client_socket_factory.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/ssl_client_socket.h"
#include "net/socket/ssl_connect_job.h"
#include "net/socket/transport_client_socket_pool.h"
#include "net/socket/transport_connect_job.h"
#include "net/spdy/spdy_proxy_client_socket.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "net/spdy/spdy_stream.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "url/gurl.h"
namespace net {
namespace {
// HttpProxyConnectJobs will time out after this many seconds. Note this is in
// addition to the timeout for the transport socket.
#if defined(OS_ANDROID) || defined(OS_IOS)
const int kHttpProxyConnectJobTimeoutInSeconds = 10;
#else
const int kHttpProxyConnectJobTimeoutInSeconds = 30;
#endif
class HttpProxyTimeoutExperiments {
public:
HttpProxyTimeoutExperiments() { Init(); }
~HttpProxyTimeoutExperiments() = default;
void Init() {
#if defined(OS_ANDROID) || defined(OS_IOS)
min_proxy_connection_timeout_ = base::TimeDelta::FromSeconds(
GetInt32Param("min_proxy_connection_timeout_seconds", 8));
max_proxy_connection_timeout_ = base::TimeDelta::FromSeconds(
GetInt32Param("max_proxy_connection_timeout_seconds", 30));
#else
min_proxy_connection_timeout_ = base::TimeDelta::FromSeconds(
GetInt32Param("min_proxy_connection_timeout_seconds", 30));
max_proxy_connection_timeout_ = base::TimeDelta::FromSeconds(
GetInt32Param("max_proxy_connection_timeout_seconds", 60));
#endif
ssl_http_rtt_multiplier_ = GetInt32Param("ssl_http_rtt_multiplier", 10);
non_ssl_http_rtt_multiplier_ =
GetInt32Param("non_ssl_http_rtt_multiplier", 5);
DCHECK_LT(0, ssl_http_rtt_multiplier_);
DCHECK_LT(0, non_ssl_http_rtt_multiplier_);
DCHECK_LE(base::TimeDelta(), min_proxy_connection_timeout_);
DCHECK_LE(base::TimeDelta(), max_proxy_connection_timeout_);
DCHECK_LE(min_proxy_connection_timeout_, max_proxy_connection_timeout_);
}
base::TimeDelta min_proxy_connection_timeout() const {
return min_proxy_connection_timeout_;
}
base::TimeDelta max_proxy_connection_timeout() const {
return max_proxy_connection_timeout_;
}
int32_t ssl_http_rtt_multiplier() const { return ssl_http_rtt_multiplier_; }
int32_t non_ssl_http_rtt_multiplier() const {
return non_ssl_http_rtt_multiplier_;
}
private:
// Returns the value of the parameter |param_name| for the field trial
// "NetAdaptiveProxyConnectionTimeout". If the value of the parameter is
// unavailable, then |default_value| is available.
static int32_t GetInt32Param(const std::string& param_name,
int32_t default_value) {
int32_t param;
if (!base::StringToInt(base::GetFieldTrialParamValue(
"NetAdaptiveProxyConnectionTimeout", param_name),
&param)) {
return default_value;
}
return param;
}
// For secure proxies, the connection timeout is set to
// |ssl_http_rtt_multiplier_| times the HTTP RTT estimate. For insecure
// proxies, the connection timeout is set to |non_ssl_http_rtt_multiplier_|
// times the HTTP RTT estimate. In either case, the connection timeout
// is clamped to be between |min_proxy_connection_timeout_| and
// |max_proxy_connection_timeout_|.
base::TimeDelta min_proxy_connection_timeout_;
base::TimeDelta max_proxy_connection_timeout_;
int32_t ssl_http_rtt_multiplier_;
int32_t non_ssl_http_rtt_multiplier_;
};
HttpProxyTimeoutExperiments* GetProxyTimeoutExperiments() {
static base::NoDestructor<HttpProxyTimeoutExperiments>
proxy_timeout_experiments;
return proxy_timeout_experiments.get();
}
} // namespace
HttpProxySocketParams::HttpProxySocketParams(
const scoped_refptr<TransportSocketParams>& transport_params,
const scoped_refptr<SSLSocketParams>& ssl_params,
quic::QuicTransportVersion quic_version,
const std::string& user_agent,
const HostPortPair& endpoint,
HttpAuthCache* http_auth_cache,
HttpAuthHandlerFactory* http_auth_handler_factory,
SpdySessionPool* spdy_session_pool,
QuicStreamFactory* quic_stream_factory,
bool is_trusted_proxy,
bool tunnel,
const NetworkTrafficAnnotationTag traffic_annotation)
: transport_params_(transport_params),
ssl_params_(ssl_params),
quic_version_(quic_version),
spdy_session_pool_(spdy_session_pool),
quic_stream_factory_(quic_stream_factory),
user_agent_(user_agent),
endpoint_(endpoint),
http_auth_cache_(tunnel ? http_auth_cache : NULL),
http_auth_handler_factory_(tunnel ? http_auth_handler_factory : NULL),
is_trusted_proxy_(is_trusted_proxy),
tunnel_(tunnel),
traffic_annotation_(traffic_annotation) {
// If doing a QUIC proxy, |quic_version| must not be
// quic::QUIC_VERSION_UNSUPPORTED, and |ssl_params| must be valid while
// |transport_params| is null. Otherwise, |quic_version| must be
// quic::QUIC_VERSION_UNSUPPORTED, and exactly one of |transport_params| or
// |ssl_params| must be set.
DCHECK(quic_version_ == quic::QUIC_VERSION_UNSUPPORTED
? (bool)transport_params != (bool)ssl_params
: !transport_params && ssl_params);
// Exactly one of |transport_params_| and |ssl_params_| must be non-null.
DCHECK(transport_params_ || ssl_params_);
DCHECK(!transport_params_ || !ssl_params_);
}
HttpProxySocketParams::~HttpProxySocketParams() = default;
HttpProxyConnectJob::HttpProxyConnectJob(
RequestPriority priority,
const CommonConnectJobParams& common_connect_job_params,
const scoped_refptr<HttpProxySocketParams>& params,
TransportClientSocketPool* transport_pool,
TransportClientSocketPool* ssl_pool,
Delegate* delegate)
: ConnectJob(
priority,
base::TimeDelta() /* The socket takes care of timeouts */,
common_connect_job_params,
delegate,
NetLogWithSource::Make(common_connect_job_params.net_log,
NetLogSourceType::HTTP_PROXY_CONNECT_JOB)),
client_socket_(std::make_unique<HttpProxyClientSocketWrapper>(
priority,
ConnectionTimeout(
*params,
common_connect_job_params.network_quality_estimator),
base::TimeDelta::FromSeconds(kHttpProxyConnectJobTimeoutInSeconds),
common_connect_job_params,
transport_pool,
ssl_pool,
params->transport_params(),
params->ssl_params(),
params->quic_version(),
params->user_agent(),
params->endpoint(),
params->http_auth_cache(),
params->http_auth_handler_factory(),
params->spdy_session_pool(),
params->quic_stream_factory(),
params->is_trusted_proxy(),
params->tunnel(),
params->traffic_annotation(),
this->net_log())) {}
HttpProxyConnectJob::~HttpProxyConnectJob() = default;
LoadState HttpProxyConnectJob::GetLoadState() const {
return client_socket_->GetConnectLoadState();
}
bool HttpProxyConnectJob::HasEstablishedConnection() const {
// Returning true prevents the socket pool this belongs to from using backup
// jobs.
// TODO(https://crbug.com/472729): Implement this, as nested pools are
// removed.
return true;
}
void HttpProxyConnectJob::GetAdditionalErrorState(ClientSocketHandle* handle) {
if (error_response_info_) {
handle->set_ssl_error_response_info(*error_response_info_);
handle->set_is_ssl_error(true);
}
}
base::TimeDelta HttpProxyConnectJob::ConnectionTimeout(
const HttpProxySocketParams& params,
const NetworkQualityEstimator* network_quality_estimator) {
bool is_https = params.ssl_params() != nullptr;
// HTTP proxy connections can't be on top of proxy connections.
DCHECK(!is_https ||
params.ssl_params()->GetConnectionType() == SSLSocketParams::DIRECT);
if (network_quality_estimator) {
base::Optional<base::TimeDelta> http_rtt_estimate =
network_quality_estimator->GetHttpRTT();
if (http_rtt_estimate) {
int32_t multiplier =
is_https
? GetProxyTimeoutExperiments()->ssl_http_rtt_multiplier()
: GetProxyTimeoutExperiments()->non_ssl_http_rtt_multiplier();
base::TimeDelta timeout = base::TimeDelta::FromMicroseconds(
multiplier * http_rtt_estimate.value().InMicroseconds());
// Ensure that connection timeout is between
// |min_proxy_connection_timeout_| and |max_proxy_connection_timeout_|.
return base::ClampToRange(
timeout, GetProxyTimeoutExperiments()->min_proxy_connection_timeout(),
GetProxyTimeoutExperiments()->max_proxy_connection_timeout());
}
}
// Return the default proxy connection timeout.
base::TimeDelta nested_job_timeout;
#if !defined(OS_ANDROID) && !defined(OS_IOS)
if (is_https) {
nested_job_timeout = SSLConnectJob::ConnectionTimeout(
*params.ssl_params(), network_quality_estimator);
} else {
nested_job_timeout = TransportConnectJob::ConnectionTimeout();
}
#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
return nested_job_timeout +
base::TimeDelta::FromSeconds(kHttpProxyConnectJobTimeoutInSeconds);
}
void HttpProxyConnectJob::UpdateFieldTrialParametersForTesting() {
GetProxyTimeoutExperiments()->Init();
}
int HttpProxyConnectJob::ConnectInternal() {
int result = client_socket_->Connect(base::BindOnce(
&HttpProxyConnectJob::OnConnectComplete, base::Unretained(this)));
return HandleConnectResult(result);
}
void HttpProxyConnectJob::ChangePriorityInternal(RequestPriority priority) {
if (client_socket_)
client_socket_->SetPriority(priority);
}
void HttpProxyConnectJob::OnConnectComplete(int result) {
DCHECK_NE(ERR_IO_PENDING, result);
result = HandleConnectResult(result);
NotifyDelegateOfCompletion(result);
// |this| will have been deleted at this point.
}
int HttpProxyConnectJob::HandleConnectResult(int result) {
if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED)
error_response_info_ = client_socket_->GetAdditionalErrorState();
if (result == OK || result == ERR_PROXY_AUTH_REQUESTED ||
result == ERR_HTTPS_PROXY_TUNNEL_RESPONSE) {
SetSocket(std::move(client_socket_));
}
return result;
}
} // namespace net