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 ¶m)) {
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