// 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 #include #include #include #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/base/proxy_chain.h" #include "net/base/session_usage.h" #include "net/dns/public/secure_dns_policy.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_session_key.h" #include "net/quic/quic_session_pool.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/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; } // Make a URL for a proxy, for use in proxy auth challenges. GURL MakeProxyUrl(const HttpProxySocketParams& params) { const bool is_https = params.is_over_ssl() || params.is_over_quic(); return GURL((is_https ? "https://" : "http://") + params.proxy_server().host_port_pair().ToString()); } } // namespace HttpProxySocketParams::HttpProxySocketParams( ConnectJobParams nested_params, const HostPortPair& endpoint, const ProxyChain& proxy_chain, size_t proxy_chain_index, bool tunnel, const NetworkTrafficAnnotationTag traffic_annotation, const NetworkAnonymizationKey& network_anonymization_key, SecureDnsPolicy secure_dns_policy) : HttpProxySocketParams(std::move(nested_params), std::nullopt, endpoint, proxy_chain, proxy_chain_index, tunnel, std::move(traffic_annotation), network_anonymization_key, secure_dns_policy) {} HttpProxySocketParams::HttpProxySocketParams( SSLConfig quic_ssl_config, const HostPortPair& endpoint, const ProxyChain& proxy_chain, size_t proxy_chain_index, bool tunnel, const NetworkTrafficAnnotationTag traffic_annotation, const NetworkAnonymizationKey& network_anonymization_key, SecureDnsPolicy secure_dns_policy) : HttpProxySocketParams(std::nullopt, std::move(quic_ssl_config), endpoint, proxy_chain, proxy_chain_index, tunnel, std::move(traffic_annotation), network_anonymization_key, secure_dns_policy) {} HttpProxySocketParams::HttpProxySocketParams( std::optional nested_params, std::optional quic_ssl_config, const HostPortPair& endpoint, const ProxyChain& proxy_chain, size_t proxy_chain_index, bool tunnel, const NetworkTrafficAnnotationTag traffic_annotation, const NetworkAnonymizationKey& network_anonymization_key, SecureDnsPolicy secure_dns_policy) : nested_params_(std::move(nested_params)), quic_ssl_config_(std::move(quic_ssl_config)), endpoint_(endpoint), proxy_chain_(proxy_chain), proxy_chain_index_(proxy_chain_index), tunnel_(tunnel), network_anonymization_key_(network_anonymization_key), traffic_annotation_(traffic_annotation), secure_dns_policy_(secure_dns_policy) { DCHECK(!proxy_chain_.is_direct()); DCHECK(proxy_chain_.IsValid()); CHECK(proxy_chain_index_ < proxy_chain_.length()); // This is either a connection to an HTTP proxy,an SSL proxy, or a QUIC proxy. DCHECK(nested_params_ || quic_ssl_config_); DCHECK(!(nested_params_ && quic_ssl_config_)); // Only supports proxy endpoints without scheme for now. // TODO(crbug.com/1206799): Handle scheme. if (is_over_transport()) { DCHECK(absl::holds_alternative( nested_params_->transport()->destination())); } else if (is_over_ssl() && nested_params_->ssl()->GetConnectionType() == SSLSocketParams::ConnectionType::DIRECT) { DCHECK(absl::holds_alternative( nested_params_->ssl()->GetDirectConnectionParams()->destination())); } } HttpProxySocketParams::~HttpProxySocketParams() = default; std::unique_ptr HttpProxyConnectJob::Factory::Create( RequestPriority priority, const SocketTag& socket_tag, const CommonConnectJobParams* common_connect_job_params, scoped_refptr params, ConnectJob::Delegate* delegate, const NetLogWithSource* net_log) { return std::make_unique( 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 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( HttpAuth::AUTH_PROXY, MakeProxyUrl(*params_), 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 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.proxy_server().is_https(); if (!network_quality_estimator) { return default_alternate_timeout; } std::optional 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 { return params_->proxy_server().scheme(); } 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( priority(), socket_tag(), common_connect_job_params(), params_->transport_params(), this, &net_log()); } else { DCHECK_EQ(scheme, ProxyServer::SCHEME_HTTPS); DCHECK(params_->is_over_ssl()); // 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( 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(); if (params_->proxy_chain().is_multi_proxy() && !ssl_cert_request_info_) { // When multi-proxy chains are in use, it's possible that a client auth // cert is requested by the first proxy after the transport connection // to it has been established. When this occurs, // ERR_SSL_CLIENT_AUTH_CERT_NEEDED will get passed back to the parent // SSLConnectJob and then to the parent HttpProxyConnectJob, but the SSL // cert request info won't have been set up for the parent // HttpProxyConnectJob to use it in this method. Fail gracefully when // this case is encountered. // TODO(https://crbug.com/1491092): Investigate whether changes are // needed to support making the SSL cert request info available here in // the case described above. Just returning `result` here makes the // behavior for multi-proxy chains match that of single-proxy chains // (where the proxied request fails with ERR_SSL_CLIENT_AUTH_CERT_NEEDED // and no `SSLCertRequestInfo` is available from the corresponding // `ResponseInfo`), though, so it could be that no further action is // needed here. return result; } DCHECK(ssl_cert_request_info_); ssl_cert_request_info_->is_proxy = true; return result; } // If this transport connection was attempting to be made through other // proxies, prefer to propagate errors from attempting to establish the // previous proxy connection(s) instead of returning // `ERR_PROXY_CONNECTION_FAILED`. For instance, if the attempt to connect to // the first proxy resulted in `ERR_PROXY_HTTP_1_1_REQUIRED`, return that so // that the whole job will be restarted using HTTP/1.1. if (params_->proxy_chain_index() != 0) { 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()); 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( nested_connect_job_->PassSocket(), GetUserAgent(), params_->endpoint(), params_->proxy_chain(), params_->proxy_chain_index(), 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=*/std::nullopt); } return result; } int HttpProxyConnectJob::DoSpdyProxyCreateStream() { DCHECK(params_->tunnel()); DCHECK(params_->is_over_ssl()); // 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 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(); 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 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( stream, params_->proxy_chain(), params_->proxy_chain_index(), 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() { DCHECK(params_->tunnel()); DCHECK(!common_connect_job_params()->quic_supported_versions->empty()); const SSLConfig& ssl_config = params_->quic_ssl_config().value(); // 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 = params_->proxy_server().host_port_pair(); quic_session_request_ = std::make_unique( common_connect_job_params()->quic_session_pool); // Use default QUIC version, which is the version listed supported version. quic::ParsedQuicVersion quic_version = common_connect_job_params()->quic_supported_versions->front(); // The QuicSessionRequest will handle connecting to any proxies earlier in the // chain to this one, but expects a ProxyChain containing only QUIC proxies. ProxyChain quic_proxies = params_->proxy_chain().Prefix(params_->proxy_chain_index()); // The ConnectJobParamsFactory ensures that this prefix is all QUIC proxies. for (const ProxyServer& ps : quic_proxies.proxy_servers()) { CHECK(ps.is_quic()); } return quic_session_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, quic_proxies, params_->traffic_annotation(), http_user_agent_settings(), SessionUsage::kProxy, ssl_config.privacy_mode, kH2QuicTunnelPriority, socket_tag(), params_->network_anonymization_key(), params_->secure_dns_policy(), /*require_dns_https_alpn=*/false, 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_session_request_.reset(); return result; } next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE; quic_session_ = quic_session_request_->ReleaseSessionHandle(); quic_session_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 quic_stream = quic_session_->ReleaseStream(); uint8_t urgency = ConvertRequestPriorityToQuicPriority(kH2QuicTunnelPriority); quic_stream->SetPriority(quic::QuicStreamPriority( quic::HttpStreamPriority{urgency, kDefaultPriorityIncremental})); transport_socket_ = std::make_unique( std::move(quic_stream), std::move(quic_session_), params_->proxy_chain(), params_->proxy_chain_index(), 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_session_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())); } std::string HttpProxyConnectJob::GetUserAgent() const { if (!http_user_agent_settings()) { return std::string(); } return http_user_agent_settings()->GetUserAgent(); } SpdySessionKey HttpProxyConnectJob::CreateSpdySessionKey() const { // Construct the SpdySessionKey using a ProxyChain that corresponds to what we // are sending the CONNECT to. For the first proxy server use // `ProxyChain::Direct()`, and for the others use a proxy chain containing all // proxy servers that we have already connected through. std::vector intermediate_proxy_servers; for (size_t proxy_index = 0; proxy_index < params_->proxy_chain_index(); ++proxy_index) { intermediate_proxy_servers.push_back( params_->proxy_chain().GetProxyServer(proxy_index)); } ProxyChain session_key_proxy_chain(std::move(intermediate_proxy_servers)); if (params_->proxy_chain_index() == 0) { DCHECK(session_key_proxy_chain.is_direct()); } // Note that `disable_cert_network_fetches` must be true for proxies to avoid // deadlock. See comment on // `SSLConfig::disable_cert_verification_network_fetches`. return SpdySessionKey( params_->proxy_server().host_port_pair(), PRIVACY_MODE_DISABLED, session_key_proxy_chain, SessionUsage::kProxy, socket_tag(), params_->network_anonymization_key(), params_->secure_dns_policy(), /*disable_cert_verification_network_fetches=*/true); } } // namespace net