• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/http/http_proxy_connect_job.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/functional/bind.h"
12 #include "base/functional/callback.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/field_trial_params.h"
15 #include "base/metrics/histogram_functions.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/task/single_thread_task_runner.h"
19 #include "base/values.h"
20 #include "build/build_config.h"
21 #include "http_proxy_client_socket.h"
22 #include "net/base/features.h"
23 #include "net/base/host_port_pair.h"
24 #include "net/base/http_user_agent_settings.h"
25 #include "net/base/net_errors.h"
26 #include "net/log/net_log_source_type.h"
27 #include "net/log/net_log_with_source.h"
28 #include "net/nqe/network_quality_estimator.h"
29 #include "net/quic/quic_http_utils.h"
30 #include "net/quic/quic_proxy_client_socket.h"
31 #include "net/quic/quic_stream_factory.h"
32 #include "net/socket/client_socket_handle.h"
33 #include "net/socket/next_proto.h"
34 #include "net/socket/ssl_client_socket.h"
35 #include "net/socket/ssl_connect_job.h"
36 #include "net/socket/transport_client_socket_pool.h"
37 #include "net/socket/transport_connect_job.h"
38 #include "net/spdy/spdy_proxy_client_socket.h"
39 #include "net/spdy/spdy_session.h"
40 #include "net/spdy/spdy_session_pool.h"
41 #include "net/spdy/spdy_stream.h"
42 #include "net/ssl/ssl_cert_request_info.h"
43 #include "third_party/abseil-cpp/absl/types/optional.h"
44 #include "third_party/abseil-cpp/absl/types/variant.h"
45 #include "url/gurl.h"
46 #include "url/scheme_host_port.h"
47 
48 namespace net {
49 
50 namespace {
51 
52 // HttpProxyConnectJobs will time out after this many seconds.  Note this is in
53 // addition to the timeout for the transport socket.
54 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
55 constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(10);
56 #else
57 constexpr base::TimeDelta kHttpProxyConnectJobTunnelTimeout = base::Seconds(30);
58 #endif
59 
60 class HttpProxyTimeoutExperiments {
61  public:
HttpProxyTimeoutExperiments()62   HttpProxyTimeoutExperiments() { Init(); }
63 
64   ~HttpProxyTimeoutExperiments() = default;
65 
Init()66   void Init() {
67     min_proxy_connection_timeout_ =
68         base::Seconds(GetInt32Param("min_proxy_connection_timeout_seconds", 8));
69     max_proxy_connection_timeout_ = base::Seconds(
70         GetInt32Param("max_proxy_connection_timeout_seconds", 30));
71     ssl_http_rtt_multiplier_ = GetInt32Param("ssl_http_rtt_multiplier", 10);
72     non_ssl_http_rtt_multiplier_ =
73         GetInt32Param("non_ssl_http_rtt_multiplier", 5);
74 
75     DCHECK_LT(0, ssl_http_rtt_multiplier_);
76     DCHECK_LT(0, non_ssl_http_rtt_multiplier_);
77     DCHECK_LE(base::TimeDelta(), min_proxy_connection_timeout_);
78     DCHECK_LE(base::TimeDelta(), max_proxy_connection_timeout_);
79     DCHECK_LE(min_proxy_connection_timeout_, max_proxy_connection_timeout_);
80   }
81 
min_proxy_connection_timeout() const82   base::TimeDelta min_proxy_connection_timeout() const {
83     return min_proxy_connection_timeout_;
84   }
max_proxy_connection_timeout() const85   base::TimeDelta max_proxy_connection_timeout() const {
86     return max_proxy_connection_timeout_;
87   }
ssl_http_rtt_multiplier() const88   int32_t ssl_http_rtt_multiplier() const { return ssl_http_rtt_multiplier_; }
non_ssl_http_rtt_multiplier() const89   int32_t non_ssl_http_rtt_multiplier() const {
90     return non_ssl_http_rtt_multiplier_;
91   }
92 
93  private:
94   // Returns the value of the parameter |param_name| for the field trial
95   // "NetAdaptiveProxyConnectionTimeout". If the value of the parameter is
96   // unavailable, then |default_value| is available.
GetInt32Param(const std::string & param_name,int32_t default_value)97   static int32_t GetInt32Param(const std::string& param_name,
98                                int32_t default_value) {
99     int32_t param;
100     if (!base::StringToInt(base::GetFieldTrialParamValue(
101                                "NetAdaptiveProxyConnectionTimeout", param_name),
102                            &param)) {
103       return default_value;
104     }
105     return param;
106   }
107 
108   // For secure proxies, the connection timeout is set to
109   // |ssl_http_rtt_multiplier_| times the HTTP RTT estimate. For insecure
110   // proxies, the connection timeout is set to |non_ssl_http_rtt_multiplier_|
111   // times the HTTP RTT estimate. In either case, the connection timeout
112   // is clamped to be between |min_proxy_connection_timeout_| and
113   // |max_proxy_connection_timeout_|.
114   base::TimeDelta min_proxy_connection_timeout_;
115   base::TimeDelta max_proxy_connection_timeout_;
116   int32_t ssl_http_rtt_multiplier_;
117   int32_t non_ssl_http_rtt_multiplier_;
118 };
119 
GetProxyTimeoutExperiments()120 HttpProxyTimeoutExperiments* GetProxyTimeoutExperiments() {
121   static HttpProxyTimeoutExperiments proxy_timeout_experiments;
122   return &proxy_timeout_experiments;
123 }
124 
125 }  // namespace
126 
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)127 HttpProxySocketParams::HttpProxySocketParams(
128     scoped_refptr<TransportSocketParams> transport_params,
129     scoped_refptr<SSLSocketParams> ssl_params,
130     bool is_quic,
131     const HostPortPair& endpoint,
132     bool tunnel,
133     const NetworkTrafficAnnotationTag traffic_annotation,
134     const NetworkAnonymizationKey& network_anonymization_key)
135     : transport_params_(std::move(transport_params)),
136       ssl_params_(std::move(ssl_params)),
137       is_quic_(is_quic),
138       endpoint_(endpoint),
139       tunnel_(tunnel),
140       network_anonymization_key_(network_anonymization_key),
141       traffic_annotation_(traffic_annotation) {
142   // This is either a connection to an HTTP proxy or an SSL/QUIC proxy.
143   DCHECK(transport_params_ || ssl_params_);
144   DCHECK(!transport_params_ || !ssl_params_);
145 
146   // If connecting to a QUIC proxy, and |ssl_params_| must be valid. This also
147   // implies |transport_params_| is null, per the above DCHECKs.
148   if (is_quic_)
149     DCHECK(ssl_params_);
150 
151   // Only supports proxy endpoints without scheme for now.
152   // TODO(crbug.com/1206799): Handle scheme.
153   if (transport_params_) {
154     DCHECK(absl::holds_alternative<HostPortPair>(
155         transport_params_->destination()));
156   } else {
157     DCHECK(absl::holds_alternative<HostPortPair>(
158         ssl_params_->GetDirectConnectionParams()->destination()));
159   }
160 }
161 
162 HttpProxySocketParams::~HttpProxySocketParams() = default;
163 
Create(RequestPriority priority,const SocketTag & socket_tag,const CommonConnectJobParams * common_connect_job_params,scoped_refptr<HttpProxySocketParams> params,ConnectJob::Delegate * delegate,const NetLogWithSource * net_log)164 std::unique_ptr<HttpProxyConnectJob> HttpProxyConnectJob::Factory::Create(
165     RequestPriority priority,
166     const SocketTag& socket_tag,
167     const CommonConnectJobParams* common_connect_job_params,
168     scoped_refptr<HttpProxySocketParams> params,
169     ConnectJob::Delegate* delegate,
170     const NetLogWithSource* net_log) {
171   return std::make_unique<HttpProxyConnectJob>(
172       priority, socket_tag, common_connect_job_params, std::move(params),
173       delegate, net_log);
174 }
175 
HttpProxyConnectJob(RequestPriority priority,const SocketTag & socket_tag,const CommonConnectJobParams * common_connect_job_params,scoped_refptr<HttpProxySocketParams> params,ConnectJob::Delegate * delegate,const NetLogWithSource * net_log)176 HttpProxyConnectJob::HttpProxyConnectJob(
177     RequestPriority priority,
178     const SocketTag& socket_tag,
179     const CommonConnectJobParams* common_connect_job_params,
180     scoped_refptr<HttpProxySocketParams> params,
181     ConnectJob::Delegate* delegate,
182     const NetLogWithSource* net_log)
183     : ConnectJob(priority,
184                  socket_tag,
185                  base::TimeDelta() /* The socket takes care of timeouts */,
186                  common_connect_job_params,
187                  delegate,
188                  net_log,
189                  NetLogSourceType::HTTP_PROXY_CONNECT_JOB,
190                  NetLogEventType::HTTP_PROXY_CONNECT_JOB_CONNECT),
191       params_(std::move(params)),
192       http_auth_controller_(
193           params_->tunnel()
194               ? base::MakeRefCounted<HttpAuthController>(
195                     HttpAuth::AUTH_PROXY,
196                     GURL((params_->ssl_params() ? "https://" : "http://") +
197                          GetDestination().ToString()),
198                     params_->network_anonymization_key(),
199                     common_connect_job_params->http_auth_cache,
200                     common_connect_job_params->http_auth_handler_factory,
201                     host_resolver())
202               : nullptr) {}
203 
204 HttpProxyConnectJob::~HttpProxyConnectJob() = default;
205 
206 const RequestPriority HttpProxyConnectJob::kH2QuicTunnelPriority =
207     DEFAULT_PRIORITY;
208 
GetLoadState() const209 LoadState HttpProxyConnectJob::GetLoadState() const {
210   switch (next_state_) {
211     case STATE_TRANSPORT_CONNECT_COMPLETE:
212       return nested_connect_job_->GetLoadState();
213     case STATE_HTTP_PROXY_CONNECT:
214     case STATE_HTTP_PROXY_CONNECT_COMPLETE:
215     case STATE_SPDY_PROXY_CREATE_STREAM:
216     case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
217     case STATE_QUIC_PROXY_CREATE_SESSION:
218     case STATE_QUIC_PROXY_CREATE_STREAM:
219     case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
220     case STATE_RESTART_WITH_AUTH:
221     case STATE_RESTART_WITH_AUTH_COMPLETE:
222       return LOAD_STATE_ESTABLISHING_PROXY_TUNNEL;
223     // This state shouldn't be possible to be called in.
224     case STATE_TRANSPORT_CONNECT:
225       NOTREACHED();
226       [[fallthrough]];
227     case STATE_BEGIN_CONNECT:
228     case STATE_NONE:
229       // May be possible for this method to be called after an error, shouldn't
230       // be called after a successful connect.
231       break;
232   }
233   return LOAD_STATE_IDLE;
234 }
235 
HasEstablishedConnection() const236 bool HttpProxyConnectJob::HasEstablishedConnection() const {
237   if (has_established_connection_)
238     return true;
239 
240   // It's possible the nested connect job has established a connection, but
241   // hasn't completed yet (For example, an SSLConnectJob may be negotiating
242   // SSL).
243   if (nested_connect_job_)
244     return nested_connect_job_->HasEstablishedConnection();
245   return false;
246 }
247 
GetResolveErrorInfo() const248 ResolveErrorInfo HttpProxyConnectJob::GetResolveErrorInfo() const {
249   return resolve_error_info_;
250 }
251 
IsSSLError() const252 bool HttpProxyConnectJob::IsSSLError() const {
253   return ssl_cert_request_info_ != nullptr;
254 }
255 
GetCertRequestInfo()256 scoped_refptr<SSLCertRequestInfo> HttpProxyConnectJob::GetCertRequestInfo() {
257   return ssl_cert_request_info_;
258 }
259 
OnConnectJobComplete(int result,ConnectJob * job)260 void HttpProxyConnectJob::OnConnectJobComplete(int result, ConnectJob* job) {
261   DCHECK_EQ(nested_connect_job_.get(), job);
262   DCHECK_EQ(next_state_, STATE_TRANSPORT_CONNECT_COMPLETE);
263   OnIOComplete(result);
264 }
265 
OnNeedsProxyAuth(const HttpResponseInfo & response,HttpAuthController * auth_controller,base::OnceClosure restart_with_auth_callback,ConnectJob * job)266 void HttpProxyConnectJob::OnNeedsProxyAuth(
267     const HttpResponseInfo& response,
268     HttpAuthController* auth_controller,
269     base::OnceClosure restart_with_auth_callback,
270     ConnectJob* job) {
271   // None of the nested ConnectJob used by this class can encounter auth
272   // challenges. Instead, the challenges are returned by the ProxyClientSocket
273   // implementations after nested_connect_job_ has already established a
274   // connection.
275   NOTREACHED();
276 }
277 
AlternateNestedConnectionTimeout(const HttpProxySocketParams & params,const NetworkQualityEstimator * network_quality_estimator)278 base::TimeDelta HttpProxyConnectJob::AlternateNestedConnectionTimeout(
279     const HttpProxySocketParams& params,
280     const NetworkQualityEstimator* network_quality_estimator) {
281   base::TimeDelta default_alternate_timeout;
282 
283   // On Android and iOS, a default proxy connection timeout is used instead of
284   // the actual TCP/SSL timeouts of nested jobs.
285 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
286   default_alternate_timeout = kHttpProxyConnectJobTunnelTimeout;
287 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
288 
289   bool is_https = params.ssl_params() != nullptr;
290   // HTTP proxy connections can't be on top of proxy connections.
291   DCHECK(!is_https ||
292          params.ssl_params()->GetConnectionType() == SSLSocketParams::DIRECT);
293 
294   if (!network_quality_estimator)
295     return default_alternate_timeout;
296 
297   absl::optional<base::TimeDelta> http_rtt_estimate =
298       network_quality_estimator->GetHttpRTT();
299   if (!http_rtt_estimate)
300     return default_alternate_timeout;
301 
302   int32_t multiplier =
303       is_https ? GetProxyTimeoutExperiments()->ssl_http_rtt_multiplier()
304                : GetProxyTimeoutExperiments()->non_ssl_http_rtt_multiplier();
305   base::TimeDelta timeout = multiplier * http_rtt_estimate.value();
306   // Ensure that connection timeout is between
307   // |min_proxy_connection_timeout_| and |max_proxy_connection_timeout_|.
308   return std::clamp(
309       timeout, GetProxyTimeoutExperiments()->min_proxy_connection_timeout(),
310       GetProxyTimeoutExperiments()->max_proxy_connection_timeout());
311 }
312 
TunnelTimeoutForTesting()313 base::TimeDelta HttpProxyConnectJob::TunnelTimeoutForTesting() {
314   return kHttpProxyConnectJobTunnelTimeout;
315 }
316 
UpdateFieldTrialParametersForTesting()317 void HttpProxyConnectJob::UpdateFieldTrialParametersForTesting() {
318   GetProxyTimeoutExperiments()->Init();
319 }
320 
ConnectInternal()321 int HttpProxyConnectJob::ConnectInternal() {
322   DCHECK_EQ(next_state_, STATE_NONE);
323   next_state_ = STATE_BEGIN_CONNECT;
324   return DoLoop(OK);
325 }
326 
GetProxyServerScheme() const327 ProxyServer::Scheme HttpProxyConnectJob::GetProxyServerScheme() const {
328   if (params_->is_quic())
329     return ProxyServer::SCHEME_QUIC;
330 
331   if (params_->transport_params())
332     return ProxyServer::SCHEME_HTTP;
333 
334   return ProxyServer::SCHEME_HTTPS;
335 }
336 
OnIOComplete(int result)337 void HttpProxyConnectJob::OnIOComplete(int result) {
338   int rv = DoLoop(result);
339   if (rv != ERR_IO_PENDING) {
340     // May delete |this|.
341     NotifyDelegateOfCompletion(rv);
342   }
343 }
344 
RestartWithAuthCredentials()345 void HttpProxyConnectJob::RestartWithAuthCredentials() {
346   DCHECK(transport_socket_);
347   DCHECK_EQ(STATE_NONE, next_state_);
348 
349   // Always do this asynchronously, to avoid re-entrancy.
350   next_state_ = STATE_RESTART_WITH_AUTH;
351   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
352       FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
353                                 weak_ptr_factory_.GetWeakPtr(), net::OK));
354 }
355 
DoLoop(int result)356 int HttpProxyConnectJob::DoLoop(int result) {
357   DCHECK_NE(next_state_, STATE_NONE);
358 
359   int rv = result;
360   do {
361     State state = next_state_;
362     next_state_ = STATE_NONE;
363     switch (state) {
364       case STATE_BEGIN_CONNECT:
365         DCHECK_EQ(OK, rv);
366         rv = DoBeginConnect();
367         break;
368       case STATE_TRANSPORT_CONNECT:
369         DCHECK_EQ(OK, rv);
370         rv = DoTransportConnect();
371         break;
372       case STATE_TRANSPORT_CONNECT_COMPLETE:
373         rv = DoTransportConnectComplete(rv);
374         break;
375       case STATE_HTTP_PROXY_CONNECT:
376         DCHECK_EQ(OK, rv);
377         rv = DoHttpProxyConnect();
378         break;
379       case STATE_HTTP_PROXY_CONNECT_COMPLETE:
380         rv = DoHttpProxyConnectComplete(rv);
381         break;
382       case STATE_SPDY_PROXY_CREATE_STREAM:
383         DCHECK_EQ(OK, rv);
384         rv = DoSpdyProxyCreateStream();
385         break;
386       case STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE:
387         rv = DoSpdyProxyCreateStreamComplete(rv);
388         break;
389       case STATE_QUIC_PROXY_CREATE_SESSION:
390         DCHECK_EQ(OK, rv);
391         rv = DoQuicProxyCreateSession();
392         break;
393       case STATE_QUIC_PROXY_CREATE_STREAM:
394         rv = DoQuicProxyCreateStream(rv);
395         break;
396       case STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE:
397         rv = DoQuicProxyCreateStreamComplete(rv);
398         break;
399       case STATE_RESTART_WITH_AUTH:
400         DCHECK_EQ(OK, rv);
401         rv = DoRestartWithAuth();
402         break;
403       case STATE_RESTART_WITH_AUTH_COMPLETE:
404         rv = DoRestartWithAuthComplete(rv);
405         break;
406       default:
407         NOTREACHED() << "bad state";
408         rv = ERR_FAILED;
409         break;
410     }
411   } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
412 
413   return rv;
414 }
415 
DoBeginConnect()416 int HttpProxyConnectJob::DoBeginConnect() {
417   connect_start_time_ = base::TimeTicks::Now();
418   ResetTimer(
419       AlternateNestedConnectionTimeout(*params_, network_quality_estimator()));
420   switch (GetProxyServerScheme()) {
421     case ProxyServer::SCHEME_QUIC:
422       next_state_ = STATE_QUIC_PROXY_CREATE_SESSION;
423       // QUIC connections are always considered to have been established.
424       // |has_established_connection_| is only used to start retries if a
425       // connection hasn't been established yet, and QUIC has its own connection
426       // establishment logic.
427       has_established_connection_ = true;
428       break;
429     case ProxyServer::SCHEME_HTTP:
430     case ProxyServer::SCHEME_HTTPS:
431       next_state_ = STATE_TRANSPORT_CONNECT;
432       break;
433     default:
434       NOTREACHED();
435   }
436   return OK;
437 }
438 
DoTransportConnect()439 int HttpProxyConnectJob::DoTransportConnect() {
440   ProxyServer::Scheme scheme = GetProxyServerScheme();
441   if (scheme == ProxyServer::SCHEME_HTTP) {
442     nested_connect_job_ = std::make_unique<TransportConnectJob>(
443         priority(), socket_tag(), common_connect_job_params(),
444         params_->transport_params(), this, &net_log());
445   } else {
446     DCHECK_EQ(scheme, ProxyServer::SCHEME_HTTPS);
447     DCHECK(params_->ssl_params());
448     // Skip making a new connection if we have an existing HTTP/2 session.
449     if (params_->tunnel() &&
450         common_connect_job_params()->spdy_session_pool->FindAvailableSession(
451             CreateSpdySessionKey(), /*enable_ip_based_pooling=*/false,
452             /*is_websocket=*/false, net_log())) {
453       next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
454       return OK;
455     }
456 
457     nested_connect_job_ = std::make_unique<SSLConnectJob>(
458         priority(), socket_tag(), common_connect_job_params(),
459         params_->ssl_params(), this, &net_log());
460   }
461 
462   next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
463   return nested_connect_job_->Connect();
464 }
465 
DoTransportConnectComplete(int result)466 int HttpProxyConnectJob::DoTransportConnectComplete(int result) {
467   resolve_error_info_ = nested_connect_job_->GetResolveErrorInfo();
468   ProxyServer::Scheme scheme = GetProxyServerScheme();
469   if (result != OK) {
470     base::UmaHistogramMediumTimes(
471         scheme == ProxyServer::SCHEME_HTTP
472             ? "Net.HttpProxy.ConnectLatency.Insecure.Error"
473             : "Net.HttpProxy.ConnectLatency.Secure.Error",
474         base::TimeTicks::Now() - connect_start_time_);
475 
476     if (IsCertificateError(result)) {
477       DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
478       // TODO(rch): allow the user to deal with proxy cert errors in the
479       // same way as server cert errors.
480       return ERR_PROXY_CERTIFICATE_INVALID;
481     }
482 
483     if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
484       DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
485       ssl_cert_request_info_ = nested_connect_job_->GetCertRequestInfo();
486       DCHECK(ssl_cert_request_info_);
487       ssl_cert_request_info_->is_proxy = true;
488       return result;
489     }
490 
491     return ERR_PROXY_CONNECTION_FAILED;
492   }
493 
494   base::UmaHistogramMediumTimes(
495       scheme == ProxyServer::SCHEME_HTTP
496           ? "Net.HttpProxy.ConnectLatency.Insecure.Success"
497           : "Net.HttpProxy.ConnectLatency.Secure.Success",
498       base::TimeTicks::Now() - connect_start_time_);
499 
500   has_established_connection_ = true;
501 
502   if (!params_->tunnel()) {
503     // If not tunneling, this is an HTTP URL being fetched directly over the
504     // proxy. Return the underlying socket directly. The caller will handle the
505     // ALPN protocol, etc., from here. Clear the DNS aliases to match the other
506     // proxy codepaths.
507     SetSocket(nested_connect_job_->PassSocket(),
508               /*dns_aliases=*/std::set<std::string>());
509     return result;
510   }
511 
512   // Establish a tunnel over the proxy by making a CONNECT request. HTTP/1.1 and
513   // HTTP/2 handle CONNECT differently.
514   if (nested_connect_job_->socket()->GetNegotiatedProtocol() == kProtoHTTP2) {
515     DCHECK_EQ(ProxyServer::SCHEME_HTTPS, scheme);
516     next_state_ = STATE_SPDY_PROXY_CREATE_STREAM;
517   } else {
518     next_state_ = STATE_HTTP_PROXY_CONNECT;
519   }
520   return result;
521 }
522 
DoHttpProxyConnect()523 int HttpProxyConnectJob::DoHttpProxyConnect() {
524   DCHECK(params_->tunnel());
525   next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
526 
527   // Reset the timer to just the length of time allowed for HttpProxy handshake
528   // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
529   // longer to timeout than it should.
530   ResetTimer(kHttpProxyConnectJobTunnelTimeout);
531 
532   // Add a HttpProxy connection on top of the tcp socket.
533   transport_socket_ = std::make_unique<HttpProxyClientSocket>(
534       nested_connect_job_->PassSocket(), GetUserAgent(), params_->endpoint(),
535       ProxyServer(GetProxyServerScheme(), GetDestination()),
536       http_auth_controller_, common_connect_job_params()->proxy_delegate,
537       params_->traffic_annotation());
538   nested_connect_job_.reset();
539   return transport_socket_->Connect(base::BindOnce(
540       &HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
541 }
542 
DoHttpProxyConnectComplete(int result)543 int HttpProxyConnectJob::DoHttpProxyConnectComplete(int result) {
544   // Always inform caller of auth requests asynchronously.
545   if (result == ERR_PROXY_AUTH_REQUESTED) {
546     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
547         FROM_HERE, base::BindOnce(&HttpProxyConnectJob::OnAuthChallenge,
548                                   weak_ptr_factory_.GetWeakPtr()));
549     return ERR_IO_PENDING;
550   }
551 
552   if (result == ERR_HTTP_1_1_REQUIRED)
553     return ERR_PROXY_HTTP_1_1_REQUIRED;
554 
555   // In TLS 1.2 with False Start or TLS 1.3, alerts from the server rejecting
556   // our client certificate are received at the first Read(), not Connect(), so
557   // the error mapping in DoTransportConnectComplete does not apply. Repeat the
558   // mapping here.
559   if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT)
560     return ERR_PROXY_CONNECTION_FAILED;
561 
562   if (result == OK) {
563     SetSocket(std::move(transport_socket_), /*dns_aliases=*/absl::nullopt);
564   }
565 
566   return result;
567 }
568 
DoSpdyProxyCreateStream()569 int HttpProxyConnectJob::DoSpdyProxyCreateStream() {
570   DCHECK(params_->tunnel());
571   DCHECK(params_->ssl_params());
572 
573   // Reset the timer to just the length of time allowed for HttpProxy handshake
574   // so that a fast TCP connection plus a slow HttpProxy failure doesn't take
575   // longer to timeout than it should.
576   ResetTimer(kHttpProxyConnectJobTunnelTimeout);
577 
578   SpdySessionKey key = CreateSpdySessionKey();
579   base::WeakPtr<SpdySession> spdy_session =
580       common_connect_job_params()->spdy_session_pool->FindAvailableSession(
581           key, /* enable_ip_based_pooling = */ false,
582           /* is_websocket = */ false, net_log());
583   // It's possible that a session to the proxy has recently been created
584   if (spdy_session) {
585     nested_connect_job_.reset();
586   } else {
587     // Create a session direct to the proxy itself
588     spdy_session = common_connect_job_params()
589                        ->spdy_session_pool->CreateAvailableSessionFromSocket(
590                            key, nested_connect_job_->PassSocket(),
591                            nested_connect_job_->connect_timing(), net_log());
592     DCHECK(spdy_session);
593     nested_connect_job_.reset();
594   }
595 
596   next_state_ = STATE_SPDY_PROXY_CREATE_STREAM_COMPLETE;
597   spdy_stream_request_ = std::make_unique<SpdyStreamRequest>();
598   return spdy_stream_request_->StartRequest(
599       SPDY_BIDIRECTIONAL_STREAM, spdy_session,
600       GURL("https://" + params_->endpoint().ToString()),
601       false /* no early data */, kH2QuicTunnelPriority, socket_tag(),
602       spdy_session->net_log(),
603       base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
604                      base::Unretained(this)),
605       params_->traffic_annotation());
606 }
607 
DoSpdyProxyCreateStreamComplete(int result)608 int HttpProxyConnectJob::DoSpdyProxyCreateStreamComplete(int result) {
609   if (result < 0) {
610     // See the comment in DoHttpProxyConnectComplete(). HTTP/2 proxies will
611     // typically also fail here, as a result of SpdyProxyClientSocket::Connect()
612     // below, but the error may surface out of SpdyStreamRequest if there were
613     // enough requests in parallel that stream creation became asynchronous.
614     if (result == ERR_BAD_SSL_CLIENT_AUTH_CERT)
615       result = ERR_PROXY_CONNECTION_FAILED;
616 
617     spdy_stream_request_.reset();
618     return result;
619   }
620 
621   next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
622   base::WeakPtr<SpdyStream> stream = spdy_stream_request_->ReleaseStream();
623   spdy_stream_request_.reset();
624   DCHECK(stream.get());
625   // |transport_socket_| will set itself as |stream|'s delegate.
626   transport_socket_ = std::make_unique<SpdyProxyClientSocket>(
627       stream, ProxyServer(GetProxyServerScheme(), GetDestination()),
628       GetUserAgent(), params_->endpoint(), net_log(), http_auth_controller_,
629       common_connect_job_params()->proxy_delegate);
630   return transport_socket_->Connect(base::BindOnce(
631       &HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
632 }
633 
DoQuicProxyCreateSession()634 int HttpProxyConnectJob::DoQuicProxyCreateSession() {
635   SSLSocketParams* ssl_params = params_->ssl_params().get();
636   DCHECK(ssl_params);
637   DCHECK(params_->tunnel());
638   DCHECK(!common_connect_job_params()->quic_supported_versions->empty());
639 
640   // Reset the timer to just the length of time allowed for HttpProxy handshake
641   // so that a fast QUIC connection plus a slow tunnel setup doesn't take longer
642   // to timeout than it should.
643   ResetTimer(kHttpProxyConnectJobTunnelTimeout);
644 
645   next_state_ = STATE_QUIC_PROXY_CREATE_STREAM;
646   const HostPortPair& proxy_server = GetDestination();
647   quic_stream_request_ = std::make_unique<QuicStreamRequest>(
648       common_connect_job_params()->quic_stream_factory);
649 
650   // Use default QUIC version, which is the version listed supported version.
651   quic::ParsedQuicVersion quic_version =
652       common_connect_job_params()->quic_supported_versions->front();
653   return quic_stream_request_->Request(
654       // TODO(crbug.com/1206799) Pass the destination directly once it's
655       // converted to contain scheme.
656       url::SchemeHostPort(url::kHttpsScheme, proxy_server.host(),
657                           proxy_server.port()),
658       quic_version, ssl_params->privacy_mode(), kH2QuicTunnelPriority,
659       socket_tag(), params_->network_anonymization_key(),
660       ssl_params->GetDirectConnectionParams()->secure_dns_policy(),
661       /*use_dns_aliases=*/false, /*require_dns_https_alpn=*/false,
662       ssl_params->ssl_config().GetCertVerifyFlags(),
663       GURL("https://" + proxy_server.ToString()), net_log(),
664       &quic_net_error_details_,
665       /*failed_on_default_network_callback=*/CompletionOnceCallback(),
666       base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
667                      base::Unretained(this)));
668 }
669 
DoQuicProxyCreateStream(int result)670 int HttpProxyConnectJob::DoQuicProxyCreateStream(int result) {
671   if (result < 0) {
672     quic_stream_request_.reset();
673     return result;
674   }
675 
676   next_state_ = STATE_QUIC_PROXY_CREATE_STREAM_COMPLETE;
677   quic_session_ = quic_stream_request_->ReleaseSessionHandle();
678   quic_stream_request_.reset();
679 
680   return quic_session_->RequestStream(
681       false,
682       base::BindOnce(&HttpProxyConnectJob::OnIOComplete,
683                      base::Unretained(this)),
684       params_->traffic_annotation());
685 }
686 
DoQuicProxyCreateStreamComplete(int result)687 int HttpProxyConnectJob::DoQuicProxyCreateStreamComplete(int result) {
688   if (result < 0)
689     return result;
690 
691   next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
692   std::unique_ptr<QuicChromiumClientStream::Handle> quic_stream =
693       quic_session_->ReleaseStream();
694 
695   uint8_t urgency = ConvertRequestPriorityToQuicPriority(kH2QuicTunnelPriority);
696   bool incremental = quic::HttpStreamPriority::kDefaultIncremental;
697   if (base::FeatureList::IsEnabled(features::kPriorityIncremental)) {
698     incremental = kDefaultPriorityIncremental;
699   }
700   quic_stream->SetPriority(
701       quic::QuicStreamPriority(quic::HttpStreamPriority{urgency, incremental}));
702 
703   transport_socket_ = std::make_unique<QuicProxyClientSocket>(
704       std::move(quic_stream), std::move(quic_session_),
705       ProxyServer(GetProxyServerScheme(), GetDestination()), GetUserAgent(),
706       params_->endpoint(), net_log(), http_auth_controller_,
707       common_connect_job_params()->proxy_delegate);
708   return transport_socket_->Connect(base::BindOnce(
709       &HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
710 }
711 
DoRestartWithAuth()712 int HttpProxyConnectJob::DoRestartWithAuth() {
713   DCHECK(transport_socket_);
714 
715   // Start the timeout timer again.
716   ResetTimer(kHttpProxyConnectJobTunnelTimeout);
717 
718   next_state_ = STATE_RESTART_WITH_AUTH_COMPLETE;
719   return transport_socket_->RestartWithAuth(base::BindOnce(
720       &HttpProxyConnectJob::OnIOComplete, base::Unretained(this)));
721 }
722 
DoRestartWithAuthComplete(int result)723 int HttpProxyConnectJob::DoRestartWithAuthComplete(int result) {
724   DCHECK_NE(ERR_IO_PENDING, result);
725 
726   if (result == OK && !transport_socket_->IsConnected())
727     result = ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
728 
729   // If the connection could not be reused to attempt to send proxy auth
730   // credentials, try reconnecting. Do not reset the HttpAuthController in this
731   // case; the server may, for instance, send "Proxy-Connection: close" and
732   // expect that each leg of the authentication progress on separate
733   // connections.
734   bool reconnect = result == ERR_UNABLE_TO_REUSE_CONNECTION_FOR_PROXY_AUTH;
735 
736   // If auth credentials were sent but the connection was closed, the server may
737   // have timed out while the user was selecting credentials. Retry once.
738   if (!has_restarted_ &&
739       (result == ERR_CONNECTION_CLOSED || result == ERR_CONNECTION_RESET ||
740        result == ERR_CONNECTION_ABORTED ||
741        result == ERR_SOCKET_NOT_CONNECTED)) {
742     reconnect = true;
743     has_restarted_ = true;
744 
745     // Release any auth state bound to the connection. The new connection will
746     // start the current scheme and identity from scratch.
747     if (http_auth_controller_)
748       http_auth_controller_->OnConnectionClosed();
749   }
750 
751   if (reconnect) {
752     // Attempt to create a new one.
753     transport_socket_.reset();
754     next_state_ = STATE_BEGIN_CONNECT;
755     return OK;
756   }
757 
758   // If not reconnecting, treat the result as the result of establishing a
759   // tunnel through the proxy. This is important in the case another auth
760   // challenge is seen.
761   next_state_ = STATE_HTTP_PROXY_CONNECT_COMPLETE;
762   return result;
763 }
764 
ChangePriorityInternal(RequestPriority priority)765 void HttpProxyConnectJob::ChangePriorityInternal(RequestPriority priority) {
766   // Do not set the priority on |spdy_stream_request_| or
767   // |quic_stream_request_|, since those should always use
768   // kH2QuicTunnelPriority.
769   if (nested_connect_job_)
770     nested_connect_job_->ChangePriority(priority);
771 
772   if (transport_socket_)
773     transport_socket_->SetStreamPriority(priority);
774 }
775 
OnTimedOutInternal()776 void HttpProxyConnectJob::OnTimedOutInternal() {
777   if (next_state_ == STATE_TRANSPORT_CONNECT_COMPLETE) {
778     base::UmaHistogramMediumTimes(
779         GetProxyServerScheme() == ProxyServer::SCHEME_HTTP
780             ? "Net.HttpProxy.ConnectLatency.Insecure.TimedOut"
781             : "Net.HttpProxy.ConnectLatency.Secure.TimedOut",
782         base::TimeTicks::Now() - connect_start_time_);
783   }
784 }
785 
OnAuthChallenge()786 void HttpProxyConnectJob::OnAuthChallenge() {
787   // Stop timer while potentially waiting for user input.
788   ResetTimer(base::TimeDelta());
789 
790   NotifyDelegateOfProxyAuth(
791       *transport_socket_->GetConnectResponseInfo(),
792       transport_socket_->GetAuthController().get(),
793       base::BindOnce(&HttpProxyConnectJob::RestartWithAuthCredentials,
794                      weak_ptr_factory_.GetWeakPtr()));
795 }
796 
GetDestination() const797 const HostPortPair& HttpProxyConnectJob::GetDestination() const {
798   const TransportSocketParams* transport_params;
799   if (params_->transport_params()) {
800     transport_params = params_->transport_params().get();
801   } else {
802     transport_params = params_->ssl_params()->GetDirectConnectionParams().get();
803   }
804 
805   // TODO(crbug.com/1206799): Handle proxy destination with scheme.
806   DCHECK(
807       absl::holds_alternative<HostPortPair>(transport_params->destination()));
808   return absl::get<HostPortPair>(transport_params->destination());
809 }
810 
GetUserAgent() const811 std::string HttpProxyConnectJob::GetUserAgent() const {
812   if (!http_user_agent_settings())
813     return std::string();
814   return http_user_agent_settings()->GetUserAgent();
815 }
816 
CreateSpdySessionKey() const817 SpdySessionKey HttpProxyConnectJob::CreateSpdySessionKey() const {
818   return SpdySessionKey(
819       GetDestination(), ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
820       SpdySessionKey::IsProxySession::kTrue, socket_tag(),
821       params_->network_anonymization_key(),
822       params_->ssl_params()->GetDirectConnectionParams()->secure_dns_policy());
823 }
824 
825 }  // namespace net
826