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