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