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