mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-26 07:16:10 +03:00
826 lines
30 KiB
C++
826 lines
30 KiB
C++
// Copyright 2019 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/http/http_proxy_connect_job.h"
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/metrics/field_trial.h"
|
|
#include "base/metrics/field_trial_params.h"
|
|
#include "base/metrics/histogram_functions.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_util.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/values.h"
|
|
#include "build/build_config.h"
|
|
#include "http_proxy_client_socket.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/http_user_agent_settings.h"
|
|
#include "net/base/net_errors.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/quic/quic_http_utils.h"
|
|
#include "net/quic/quic_proxy_client_socket.h"
|
|
#include "net/quic/quic_stream_factory.h"
|
|
#include "net/socket/client_socket_handle.h"
|
|
#include "net/socket/next_proto.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 "third_party/abseil-cpp/absl/types/optional.h"
|
|
#include "third_party/abseil-cpp/absl/types/variant.h"
|
|
#include "url/gurl.h"
|
|
#include "url/scheme_host_port.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 BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
|
|
constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(10);
|
|
#else
|
|
constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(30);
|
|
#endif
|
|
|
|
class HttpProxyTimeoutExperiments {
|
|
public:
|
|
HttpProxyTimeoutExperiments() { Init(); }
|
|
|
|
~HttpProxyTimeoutExperiments() = default;
|
|
|
|
void Init() {
|
|
min_proxy_connection_timeout_ =
|
|
base::Seconds(GetInt32Param("min_proxy_connection_timeout_seconds", 8));
|
|
max_proxy_connection_timeout_ = base::Seconds(
|
|
GetInt32Param("max_proxy_connection_timeout_seconds", 30));
|
|
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),
|
|
¶m)) {
|
|
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 HttpProxyTimeoutExperiments proxy_timeout_experiments;
|
|
return &proxy_timeout_experiments;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
HttpProxySocketParams::HttpProxySocketParams(
|
|
scoped_refptr<TransportSocketParams> transport_params,
|
|
scoped_refptr<SSLSocketParams> ssl_params,
|
|
bool is_quic,
|
|
const HostPortPair& endpoint,
|
|
bool tunnel,
|
|
const NetworkTrafficAnnotationTag traffic_annotation,
|
|
const NetworkAnonymizationKey& network_anonymization_key)
|
|
: transport_params_(std::move(transport_params)),
|
|
ssl_params_(std::move(ssl_params)),
|
|
is_quic_(is_quic),
|
|
endpoint_(endpoint),
|
|
tunnel_(tunnel),
|
|
network_anonymization_key_(network_anonymization_key),
|
|
traffic_annotation_(traffic_annotation) {
|
|
// This is either a connection to an HTTP proxy or an SSL/QUIC proxy.
|
|
DCHECK(transport_params_ || ssl_params_);
|
|
DCHECK(!transport_params_ || !ssl_params_);
|
|
|
|
// If connecting to a QUIC proxy, and |ssl_params_| must be valid. This also
|
|
// implies |transport_params_| is null, per the above DCHECKs.
|
|
if (is_quic_)
|
|
DCHECK(ssl_params_);
|
|
|
|
// Only supports proxy endpoints without scheme for now.
|
|
// TODO(crbug.com/1206799): Handle scheme.
|
|
if (transport_params_) {
|
|
DCHECK(absl::holds_alternative<HostPortPair>(
|
|
transport_params_->destination()));
|
|
} else {
|
|
DCHECK(absl::holds_alternative<HostPortPair>(
|
|
ssl_params_->GetDirectConnectionParams()->destination()));
|
|
}
|
|
}
|
|
|
|
HttpProxySocketParams::~HttpProxySocketParams() = default;
|
|
|
|
std::unique_ptr<HttpProxyConnectJob> HttpProxyConnectJob::Factory::Create(
|
|
RequestPriority priority,
|
|
const SocketTag& socket_tag,
|
|
const CommonConnectJobParams* common_connect_job_params,
|
|
scoped_refptr<HttpProxySocketParams> params,
|
|
ConnectJob::Delegate* delegate,
|
|
const NetLogWithSource* net_log) {
|
|
return std::make_unique<HttpProxyConnectJob>(
|
|
priority, socket_tag, common_connect_job_params, std::move(params),
|
|
delegate, net_log);
|
|
}
|
|
|
|
HttpProxyConnectJob::HttpProxyConnectJob(
|
|
RequestPriority priority,
|
|
const SocketTag& socket_tag,
|
|
const CommonConnectJobParams* common_connect_job_params,
|
|
scoped_refptr<HttpProxySocketParams> params,
|
|
ConnectJob::Delegate* delegate,
|
|
const NetLogWithSource* net_log)
|
|
: ConnectJob(priority,
|
|
socket_tag,
|
|
base::TimeDelta() /* The socket takes care of timeouts */,
|
|
common_connect_job_params,
|
|
delegate,
|
|
net_log,
|
|
NetLogSourceType::HTTP_PROXY_CONNECT_JOB,
|
|
NetLogEventType::HTTP_PROXY_CONNECT_JOB_CONNECT),
|
|
params_(std::move(params)),
|
|
http_auth_controller_(
|
|
params_->tunnel()
|
|
? base::MakeRefCounted<HttpAuthController>(
|
|
HttpAuth::AUTH_PROXY,
|
|
GURL((params_->ssl_params() ? "https://" : "http://") +
|
|
GetDestination().ToString()),
|
|
params_->network_anonymization_key(),
|
|
common_connect_job_params->http_auth_cache,
|
|
common_connect_job_params->http_auth_handler_factory,
|
|
host_resolver())
|
|
: nullptr) {}
|
|
|
|
HttpProxyConnectJob::~HttpProxyConnectJob() = default;
|
|
|
|
const RequestPriority HttpProxyConnectJob::kH2QuicTunnelPriority =
|
|
DEFAULT_PRIORITY;
|
|
|
|
LoadState HttpProxyConnectJob::GetLoadState() const {
|
|
switch (next_state_) {
|
|
case STATE_TRANSPORT_CONNECT_COMPLETE:
|
|
return nested_connect_job_->GetLoadState();
|
|
case STATE_HTTP_PROXY_CONNECT:
|
|
case STATE_HTTP_PROXY_CONNECT_COMPLETE:
|
|
case STATE_SPDY_PROXY_CREATE_STREAM:
|
|
case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
|
|
case STATE_QUIC_PROXY_CREATE_SESSION:
|
|
case STATE_QUIC_PROXY_CREATE_STREAM:
|
|
case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
|
|
case STATE_RESTART_WITH_AUTH:
|
|
case STATE_RESTART_WITH_AUTH_COMPLETE:
|
|
return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
|
|
// This state shouldn't be possible to be called in.
|
|
case STATE_TRANSPORT_CONNECT:
|
|
NOTREACHED();
|
|
[[fallthrough]];
|
|
case STATE_BEGIN_CONNECT:
|
|
case STATE_NONE:
|
|
// May be possible for this method to be called after an error, shouldn't
|
|
// be called after a successful connect.
|
|
break;
|
|
}
|
|
return LOAD_STATE_IDLE;
|
|
}
|
|
|
|
bool HttpProxyConnectJob::HasEstablishedConnection() const {
|
|
if (has_established_connection_)
|
|
return true;
|
|
|
|
// It's possible the nested connect job has established a connection, but
|
|
// hasn't completed yet (For example, an SSLConnectJob may be negotiating
|
|
// SSL).
|
|
if (nested_connect_job_)
|
|
return nested_connect_job_->HasEstablishedConnection();
|
|
return false;
|
|
}
|
|
|
|
ResolveErrorInfo HttpProxyConnectJob::GetResolveErrorInfo() const {
|
|
return resolve_error_info_;
|
|
}
|
|
|
|
bool HttpProxyConnectJob::IsSSLError() const {
|
|
return ssl_cert_request_info_ != nullptr;
|
|
}
|
|
|
|
scoped_refptr<SSLCertRequestInfo> HttpProxyConnectJob::GetCertRequestInfo() {
|
|
return ssl_cert_request_info_;
|
|
}
|
|
|
|
void HttpProxyConnectJob::OnConnectJobComplete(int result, ConnectJob* job) {
|
|
DCHECK_EQ(nested_connect_job_.get(), job);
|
|
DCHECK_EQ(next_state_, STATE_TRANSPORT_CONNECT_COMPLETE);
|
|
OnIOComplete(result);
|
|
}
|
|
|
|
void HttpProxyConnectJob::OnNeedsProxyAuth(
|
|
const HttpResponseInfo& response,
|
|
HttpAuthController* auth_controller,
|
|
base::OnceClosure restart_with_auth_callback,
|
|
ConnectJob* job) {
|
|
// None of the nested ConnectJob used by this class can encounter auth
|
|
// challenges. Instead, the challenges are returned by the ProxyClientSocket
|
|
// implementations after nested_connect_job_ has already established a
|
|
// connection.
|
|
NOTREACHED();
|
|
}
|
|
|
|
base::TimeDelta HttpProxyConnectJob::AlternateNestedConnectionTimeout(
|
|
const HttpProxySocketParams& params,
|
|
const NetworkQualityEstimator* network_quality_estimator) {
|
|
base::TimeDelta default_alternate_timeout;
|
|
|
|
// On Android and iOS, a default proxy connection timeout is used instead of
|
|
// the actual TCP/SSL timeouts of nested jobs.
|
|
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
|
|
default_alternate_timeout = kHttpProxyConnectJobTunnelTimeout;
|
|
#endif // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
|
|
|
|
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)
|
|
return default_alternate_timeout;
|
|
|
|
absl::optional<base::TimeDelta> http_rtt_estimate =
|
|
network_quality_estimator->GetHttpRTT();
|
|
if (!http_rtt_estimate)
|
|
return default_alternate_timeout;
|
|
|
|
int32_t multiplier =
|
|
is_https ? GetProxyTimeoutExperiments()->ssl_http_rtt_multiplier()
|
|
: GetProxyTimeoutExperiments()->non_ssl_http_rtt_multiplier();
|
|
base::TimeDelta timeout = multiplier * http_rtt_estimate.value();
|
|
// Ensure that connection timeout is between
|
|
// |min_proxy_connection_timeout_| and |max_proxy_connection_timeout_|.
|
|
return std::clamp(
|
|
timeout, GetProxyTimeoutExperiments()->min_proxy_connection_timeout(),
|
|
GetProxyTimeoutExperiments()->max_proxy_connection_timeout());
|
|
}
|
|
|
|
base::TimeDelta HttpProxyConnectJob::TunnelTimeoutForTesting() {
|
|
return kHttpProxyConnectJobTunnelTimeout;
|
|
}
|
|
|
|
void HttpProxyConnectJob::UpdateFieldTrialParametersForTesting() {
|
|
GetProxyTimeoutExperiments()->Init();
|
|
}
|
|
|
|
int HttpProxyConnectJob::ConnectInternal() {
|
|
DCHECK_EQ(next_state_, STATE_NONE);
|
|
next_state_ = STATE_BEGIN_CONNECT;
|
|
return DoLoop(OK);
|
|
}
|
|
|
|
ProxyServer::Scheme HttpProxyConnectJob::GetProxyServerScheme() const {
|
|
if (params_->is_quic())
|
|
return ProxyServer::SCHEME_QUIC;
|
|
|
|
if (params_->transport_params())
|
|
return ProxyServer::SCHEME_HTTP;
|
|
|
|
return ProxyServer::SCHEME_HTTPS;
|
|
}
|
|
|
|
void HttpProxyConnectJob::OnIOComplete(int result) {
|
|
int rv = DoLoop(result);
|
|
if (rv != ERR_IO_PENDING) {
|
|
// May delete |this|.
|
|
NotifyDelegateOfCompletion(rv);
|
|
}
|
|
}
|
|
|
|
void HttpProxyConnectJob::RestartWithAuthCredentials() {
|
|
DCHECK(transport_socket_);
|
|
DCHECK_EQ(STATE_NONE, next_state_);
|
|
|
|
// Always do this asynchronously, to avoid re-entrancy.
|
|
next_state_ = STATE_RESTART_WITH_AUTH;
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
|
|
weak_ptr_factory_.GetWeakPtr(), net::OK));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoLoop(int result) {
|
|
DCHECK_NE(next_state_, STATE_NONE);
|
|
|
|
int rv = result;
|
|
do {
|
|
State state = next_state_;
|
|
next_state_ = STATE_NONE;
|
|
switch (state) {
|
|
case STATE_BEGIN_CONNECT:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoBeginConnect();
|
|
break;
|
|
case STATE_TRANSPORT_CONNECT:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoTransportConnect();
|
|
break;
|
|
case STATE_TRANSPORT_CONNECT_COMPLETE:
|
|
rv = DoTransportConnectComplete(rv);
|
|
break;
|
|
case STATE_HTTP_PROXY_CONNECT:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoHttpProxyConnect();
|
|
break;
|
|
case STATE_HTTP_PROXY_CONNECT_COMPLETE:
|
|
rv = DoHttpProxyConnectComplete(rv);
|
|
break;
|
|
case STATE_SPDY_PROXY_CREATE_STREAM:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoSpdyProxyCreateStream();
|
|
break;
|
|
case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
|
|
rv = DoSpdyProxyCreateStreamComplete(rv);
|
|
break;
|
|
case STATE_QUIC_PROXY_CREATE_SESSION:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoQuicProxyCreateSession();
|
|
break;
|
|
case STATE_QUIC_PROXY_CREATE_STREAM:
|
|
rv = DoQuicProxyCreateStream(rv);
|
|
break;
|
|
case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
|
|
rv = DoQuicProxyCreateStreamComplete(rv);
|
|
break;
|
|
case STATE_RESTART_WITH_AUTH:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoRestartWithAuth();
|
|
break;
|
|
case STATE_RESTART_WITH_AUTH_COMPLETE:
|
|
rv = DoRestartWithAuthComplete(rv);
|
|
break;
|
|
default:
|
|
NOTREACHED() << "bad state";
|
|
rv = ERR_FAILED;
|
|
break;
|
|
}
|
|
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoBeginConnect() {
|
|
connect_start_time_ = base::TimeTicks::Now();
|
|
ResetTimer(
|
|
AlternateNestedConnectionTimeout(*params_, network_quality_estimator()));
|
|
switch (GetProxyServerScheme()) {
|
|
case ProxyServer::SCHEME_QUIC:
|
|
next_state_ = STATE_QUIC_PROXY_CREATE_SESSION;
|
|
// QUIC connections are always considered to have been established.
|
|
// |has_established_connection_| is only used to start retries if a
|
|
// connection hasn't been established yet, and QUIC has its own connection
|
|
// establishment logic.
|
|
has_established_connection_ = true;
|
|
break;
|
|
case ProxyServer::SCHEME_HTTP:
|
|
case ProxyServer::SCHEME_HTTPS:
|
|
next_state_ = STATE_TRANSPORT_CONNECT;
|
|
break;
|
|
default:
|
|
NOTREACHED();
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoTransportConnect() {
|
|
ProxyServer::Scheme scheme = GetProxyServerScheme();
|
|
if (scheme == ProxyServer::SCHEME_HTTP) {
|
|
nested_connect_job_ = std::make_unique<TransportConnectJob>(
|
|
priority(), socket_tag(), common_connect_job_params(),
|
|
params_->transport_params(), this, &net_log());
|
|
} else {
|
|
DCHECK_EQ(scheme, ProxyServer::SCHEME_HTTPS);
|
|
DCHECK(params_->ssl_params());
|
|
// Skip making a new connection if we have an existing HTTP/2 session.
|
|
if (params_->tunnel() &&
|
|
common_connect_job_params()->spdy_session_pool->FindAvailableSession(
|
|
CreateSpdySessionKey(), /*enable_ip_based_pooling=*/false,
|
|
/*is_websocket=*/false, net_log())) {
|
|
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
|
|
return OK;
|
|
}
|
|
|
|
nested_connect_job_ = std::make_unique<SSLConnectJob>(
|
|
priority(), socket_tag(), common_connect_job_params(),
|
|
params_->ssl_params(), this, &net_log());
|
|
}
|
|
|
|
next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
|
|
return nested_connect_job_->Connect();
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoTransportConnectComplete(int result) {
|
|
resolve_error_info_ = nested_connect_job_->GetResolveErrorInfo();
|
|
ProxyServer::Scheme scheme = GetProxyServerScheme();
|
|
if (result != OK) {
|
|
base::UmaHistogramMediumTimes(
|
|
scheme == ProxyServer::SCHEME_HTTP
|
|
? "Net.HttpProxy.ConnectLatency.Insecure.Error"
|
|
: "Net.HttpProxy.ConnectLatency.Secure.Error",
|
|
base::TimeTicks::Now() - connect_start_time_);
|
|
|
|
if (IsCertificateError(result)) {
|
|
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
|
|
// TODO(rch): allow the user to deal with proxy cert errors in the
|
|
// same way as server cert errors.
|
|
return ERR_PROXY_CERTIFICATE_INVALID;
|
|
}
|
|
|
|
if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
|
|
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
|
|
ssl_cert_request_info_ = nested_connect_job_->GetCertRequestInfo();
|
|
DCHECK(ssl_cert_request_info_);
|
|
ssl_cert_request_info_->is_proxy = true;
|
|
return result;
|
|
}
|
|
|
|
return ERR_PROXY_CONNECTION_FAILED;
|
|
}
|
|
|
|
base::UmaHistogramMediumTimes(
|
|
scheme == ProxyServer::SCHEME_HTTP
|
|
? "Net.HttpProxy.ConnectLatency.Insecure.Success"
|
|
: "Net.HttpProxy.ConnectLatency.Secure.Success",
|
|
base::TimeTicks::Now() - connect_start_time_);
|
|
|
|
has_established_connection_ = true;
|
|
|
|
if (!params_->tunnel()) {
|
|
// If not tunneling, this is an HTTP URL being fetched directly over the
|
|
// proxy. Return the underlying socket directly. The caller will handle the
|
|
// ALPN protocol, etc., from here. Clear the DNS aliases to match the other
|
|
// proxy codepaths.
|
|
SetSocket(nested_connect_job_->PassSocket(),
|
|
/*dns_aliases=*/std::set<std::string>());
|
|
return result;
|
|
}
|
|
|
|
// Establish a tunnel over the proxy by making a CONNECT request. HTTP/1.1 and
|
|
// HTTP/2 handle CONNECT differently.
|
|
if (nested_connect_job_->socket()->GetNegotiatedProtocol() == kProtoHTTP2) {
|
|
DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
|
|
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
|
|
} else {
|
|
next_state_ = STATE_HTTP_PROXY_CONNECT;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoHttpProxyConnect() {
|
|
DCHECK(params_->tunnel());
|
|
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
|
|
|
|
// Reset the timer to just the length of time allowed for HttpProxy handshake
|
|
// so that a fast TCP connection plus a slow HttpProxy failure doesn't take
|
|
// longer to timeout than it should.
|
|
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
|
|
|
|
// Add a HttpProxy connection on top of the tcp socket.
|
|
transport_socket_ = std::make_unique<HttpProxyClientSocket>(
|
|
nested_connect_job_->PassSocket(), GetUserAgent(), params_->endpoint(),
|
|
ProxyServer(GetProxyServerScheme(), GetDestination()),
|
|
http_auth_controller_, common_connect_job_params()->proxy_delegate,
|
|
params_->traffic_annotation());
|
|
nested_connect_job_.reset();
|
|
return transport_socket_->Connect(base::BindOnce(
|
|
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
|
|
// Always inform caller of auth requests asynchronously.
|
|
if (result == ERR_PROXY_AUTH_REQUESTED) {
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnAuthChallenge,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
return ERR_IO_PENDING;
|
|
}
|
|
|
|
if (result == ERR_HTTP_1_1_REQUIRED)
|
|
return ERR_PROXY_HTTP_1_1_REQUIRED;
|
|
|
|
// In TLS 1.2 with False Start or TLS 1.3, alerts from the server rejecting
|
|
// our client certificate are received at the first Read(), not Connect(), so
|
|
// the error mapping in DoTransportConnectComplete does not apply. Repeat the
|
|
// mapping here.
|
|
if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT)
|
|
return ERR_PROXY_CONNECTION_FAILED;
|
|
|
|
if (result == OK) {
|
|
SetSocket(std::move(transport_socket_), /*dns_aliases=*/absl::nullopt);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoSpdyProxyCreateStream() {
|
|
DCHECK(params_->tunnel());
|
|
DCHECK(params_->ssl_params());
|
|
|
|
// Reset the timer to just the length of time allowed for HttpProxy handshake
|
|
// so that a fast TCP connection plus a slow HttpProxy failure doesn't take
|
|
// longer to timeout than it should.
|
|
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
|
|
|
|
SpdySessionKey key = CreateSpdySessionKey();
|
|
base::WeakPtr<SpdySession> spdy_session =
|
|
common_connect_job_params()->spdy_session_pool->FindAvailableSession(
|
|
key, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, net_log());
|
|
// It's possible that a session to the proxy has recently been created
|
|
if (spdy_session) {
|
|
nested_connect_job_.reset();
|
|
} else {
|
|
// Create a session direct to the proxy itself
|
|
spdy_session = common_connect_job_params()
|
|
->spdy_session_pool->CreateAvailableSessionFromSocket(
|
|
key, nested_connect_job_->PassSocket(),
|
|
nested_connect_job_->connect_timing(), net_log());
|
|
DCHECK(spdy_session);
|
|
nested_connect_job_.reset();
|
|
}
|
|
|
|
next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
|
|
spdy_stream_request_ = std::make_unique<SpdyStreamRequest>();
|
|
return spdy_stream_request_->StartRequest(
|
|
SPDY_BIDIRECTIONAL_STREAM, spdy_session,
|
|
GURL("https://" + params_->endpoint().ToString()),
|
|
false /* no early data */, kH2QuicTunnelPriority, socket_tag(),
|
|
spdy_session->net_log(),
|
|
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
|
|
base::Unretained(this)),
|
|
params_->traffic_annotation());
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) {
|
|
if (result < 0) {
|
|
// See the comment in DoHttpProxyConnectComplete(). HTTP/2 proxies will
|
|
// typically also fail here, as a result of SpdyProxyClientSocket::Connect()
|
|
// below, but the error may surface out of SpdyStreamRequest if there were
|
|
// enough requests in parallel that stream creation became asynchronous.
|
|
if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT)
|
|
result = ERR_PROXY_CONNECTION_FAILED;
|
|
|
|
spdy_stream_request_.reset();
|
|
return result;
|
|
}
|
|
|
|
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
|
|
base::WeakPtr<SpdyStream> stream = spdy_stream_request_->ReleaseStream();
|
|
spdy_stream_request_.reset();
|
|
DCHECK(stream.get());
|
|
// |transport_socket_| will set itself as |stream|'s delegate.
|
|
transport_socket_ = std::make_unique<SpdyProxyClientSocket>(
|
|
stream, ProxyServer(GetProxyServerScheme(), GetDestination()),
|
|
GetUserAgent(), params_->endpoint(), net_log(), http_auth_controller_,
|
|
common_connect_job_params()->proxy_delegate);
|
|
return transport_socket_->Connect(base::BindOnce(
|
|
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoQuicProxyCreateSession() {
|
|
SSLSocketParams* ssl_params = params_->ssl_params().get();
|
|
DCHECK(ssl_params);
|
|
DCHECK(params_->tunnel());
|
|
DCHECK(!common_connect_job_params()->quic_supported_versions->empty());
|
|
|
|
// Reset the timer to just the length of time allowed for HttpProxy handshake
|
|
// so that a fast QUIC connection plus a slow tunnel setup doesn't take longer
|
|
// to timeout than it should.
|
|
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
|
|
|
|
next_state_ = STATE_QUIC_PROXY_CREATE_STREAM;
|
|
const HostPortPair& proxy_server = GetDestination();
|
|
quic_stream_request_ = std::make_unique<QuicStreamRequest>(
|
|
common_connect_job_params()->quic_stream_factory);
|
|
|
|
// Use default QUIC version, which is the version listed supported version.
|
|
quic::ParsedQuicVersion quic_version =
|
|
common_connect_job_params()->quic_supported_versions->front();
|
|
return quic_stream_request_->Request(
|
|
// TODO(crbug.com/1206799) Pass the destination directly once it's
|
|
// converted to contain scheme.
|
|
url::SchemeHostPort(url::kHttpsScheme, proxy_server.host(),
|
|
proxy_server.port()),
|
|
quic_version, ssl_params->privacy_mode(), kH2QuicTunnelPriority,
|
|
socket_tag(), params_->network_anonymization_key(),
|
|
ssl_params->GetDirectConnectionParams()->secure_dns_policy(),
|
|
/*use_dns_aliases=*/false, /*require_dns_https_alpn=*/false,
|
|
ssl_params->ssl_config().GetCertVerifyFlags(),
|
|
GURL("https://" + proxy_server.ToString()), net_log(),
|
|
&quic_net_error_details_,
|
|
/*failed_on_default_network_callback=*/CompletionOnceCallback(),
|
|
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoQuicProxyCreateStream(int result) {
|
|
if (result < 0) {
|
|
quic_stream_request_.reset();
|
|
return result;
|
|
}
|
|
|
|
next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE;
|
|
quic_session_ = quic_stream_request_->ReleaseSessionHandle();
|
|
quic_stream_request_.reset();
|
|
|
|
return quic_session_->RequestStream(
|
|
false,
|
|
base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
|
|
base::Unretained(this)),
|
|
params_->traffic_annotation());
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoQuicProxyCreateStreamComplete(int result) {
|
|
if (result < 0)
|
|
return result;
|
|
|
|
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
|
|
std::unique_ptr<QuicChromiumClientStream::Handle> quic_stream =
|
|
quic_session_->ReleaseStream();
|
|
|
|
uint8_t urgency = ConvertRequestPriorityToQuicPriority(kH2QuicTunnelPriority);
|
|
bool incremental = quic::HttpStreamPriority::kDefaultIncremental;
|
|
if (base::FeatureList::IsEnabled(features::kPriorityIncremental)) {
|
|
incremental = kDefaultPriorityIncremental;
|
|
}
|
|
quic_stream->SetPriority(
|
|
quic::QuicStreamPriority(quic::HttpStreamPriority{urgency, incremental}));
|
|
|
|
transport_socket_ = std::make_unique<QuicProxyClientSocket>(
|
|
std::move(quic_stream), std::move(quic_session_),
|
|
ProxyServer(GetProxyServerScheme(), GetDestination()), GetUserAgent(),
|
|
params_->endpoint(), net_log(), http_auth_controller_,
|
|
common_connect_job_params()->proxy_delegate);
|
|
return transport_socket_->Connect(base::BindOnce(
|
|
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoRestartWithAuth() {
|
|
DCHECK(transport_socket_);
|
|
|
|
// Start the timeout timer again.
|
|
ResetTimer(kHttpProxyConnectJobTunnelTimeout);
|
|
|
|
next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE;
|
|
return transport_socket_->RestartWithAuth(base::BindOnce(
|
|
&HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
|
|
}
|
|
|
|
int HttpProxyConnectJob::DoRestartWithAuthComplete(int result) {
|
|
DCHECK_NE(ERR_IO_PENDING, result);
|
|
|
|
if (result == OK && !transport_socket_->IsConnected())
|
|
result = ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
|
|
|
|
// If the connection could not be reused to attempt to send proxy auth
|
|
// credentials, try reconnecting. Do not reset the HttpAuthController in this
|
|
// case; the server may, for instance, send "Proxy-Connection: close" and
|
|
// expect that each leg of the authentication progress on separate
|
|
// connections.
|
|
bool reconnect = result == ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
|
|
|
|
// If auth credentials were sent but the connection was closed, the server may
|
|
// have timed out while the user was selecting credentials. Retry once.
|
|
if (!has_restarted_ &&
|
|
(result == ERR_CONNECTION_CLOSED || result == ERR_CONNECTION_RESET ||
|
|
result == ERR_CONNECTION_ABORTED ||
|
|
result == ERR_SOCKET_NOT_CONNECTED)) {
|
|
reconnect = true;
|
|
has_restarted_ = true;
|
|
|
|
// Release any auth state bound to the connection. The new connection will
|
|
// start the current scheme and identity from scratch.
|
|
if (http_auth_controller_)
|
|
http_auth_controller_->OnConnectionClosed();
|
|
}
|
|
|
|
if (reconnect) {
|
|
// Attempt to create a new one.
|
|
transport_socket_.reset();
|
|
next_state_ = STATE_BEGIN_CONNECT;
|
|
return OK;
|
|
}
|
|
|
|
// If not reconnecting, treat the result as the result of establishing a
|
|
// tunnel through the proxy. This is important in the case another auth
|
|
// challenge is seen.
|
|
next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
|
|
return result;
|
|
}
|
|
|
|
void HttpProxyConnectJob::ChangePriorityInternal(RequestPriority priority) {
|
|
// Do not set the priority on |spdy_stream_request_| or
|
|
// |quic_stream_request_|, since those should always use
|
|
// kH2QuicTunnelPriority.
|
|
if (nested_connect_job_)
|
|
nested_connect_job_->ChangePriority(priority);
|
|
|
|
if (transport_socket_)
|
|
transport_socket_->SetStreamPriority(priority);
|
|
}
|
|
|
|
void HttpProxyConnectJob::OnTimedOutInternal() {
|
|
if (next_state_ == STATE_TRANSPORT_CONNECT_COMPLETE) {
|
|
base::UmaHistogramMediumTimes(
|
|
GetProxyServerScheme() == ProxyServer::SCHEME_HTTP
|
|
? "Net.HttpProxy.ConnectLatency.Insecure.TimedOut"
|
|
: "Net.HttpProxy.ConnectLatency.Secure.TimedOut",
|
|
base::TimeTicks::Now() - connect_start_time_);
|
|
}
|
|
}
|
|
|
|
void HttpProxyConnectJob::OnAuthChallenge() {
|
|
// Stop timer while potentially waiting for user input.
|
|
ResetTimer(base::TimeDelta());
|
|
|
|
NotifyDelegateOfProxyAuth(
|
|
*transport_socket_->GetConnectResponseInfo(),
|
|
transport_socket_->GetAuthController().get(),
|
|
base::BindOnce(&HttpProxyConnectJob::RestartWithAuthCredentials,
|
|
weak_ptr_factory_.GetWeakPtr()));
|
|
}
|
|
|
|
const HostPortPair& HttpProxyConnectJob::GetDestination() const {
|
|
const TransportSocketParams* transport_params;
|
|
if (params_->transport_params()) {
|
|
transport_params = params_->transport_params().get();
|
|
} else {
|
|
transport_params = params_->ssl_params()->GetDirectConnectionParams().get();
|
|
}
|
|
|
|
// TODO(crbug.com/1206799): Handle proxy destination with scheme.
|
|
DCHECK(
|
|
absl::holds_alternative<HostPortPair>(transport_params->destination()));
|
|
return absl::get<HostPortPair>(transport_params->destination());
|
|
}
|
|
|
|
std::string HttpProxyConnectJob::GetUserAgent() const {
|
|
if (!http_user_agent_settings())
|
|
return std::string();
|
|
return http_user_agent_settings()->GetUserAgent();
|
|
}
|
|
|
|
SpdySessionKey HttpProxyConnectJob::CreateSpdySessionKey() const {
|
|
return SpdySessionKey(
|
|
GetDestination(), ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kTrue, socket_tag(),
|
|
params_->network_anonymization_key(),
|
|
params_->ssl_params()->GetDirectConnectionParams()->secure_dns_policy());
|
|
}
|
|
|
|
} // namespace net
|