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