1 // Copyright 2016 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_stream_factory_job_controller.h"
6
7 #include <string>
8 #include <utility>
9
10 #include "base/containers/contains.h"
11 #include "base/functional/bind.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_util.h"
16 #include "base/task/single_thread_task_runner.h"
17 #include "base/values.h"
18 #include "net/base/host_mapping_rules.h"
19 #include "net/base/load_flags.h"
20 #include "net/base/net_errors.h"
21 #include "net/base/privacy_mode.h"
22 #include "net/base/proxy_chain.h"
23 #include "net/base/proxy_string_util.h"
24 #include "net/base/session_usage.h"
25 #include "net/base/url_util.h"
26 #include "net/http/bidirectional_stream_impl.h"
27 #include "net/http/transport_security_state.h"
28 #include "net/log/net_log.h"
29 #include "net/log/net_log_event_type.h"
30 #include "net/log/net_log_with_source.h"
31 #include "net/proxy_resolution/proxy_resolution_request.h"
32 #include "net/proxy_resolution/proxy_resolution_service.h"
33 #include "net/quic/quic_session_key.h"
34 #include "net/spdy/spdy_session.h"
35 #include "url/gurl.h"
36 #include "url/scheme_host_port.h"
37 #include "url/url_constants.h"
38
39 namespace net {
40
41 namespace {
42
43 // Returns parameters associated with the proxy resolution.
NetLogHttpStreamJobProxyChainResolved(const ProxyChain & proxy_chain)44 base::Value::Dict NetLogHttpStreamJobProxyChainResolved(
45 const ProxyChain& proxy_chain) {
46 base::Value::Dict dict;
47
48 dict.Set("proxy_chain",
49 proxy_chain.IsValid() ? proxy_chain.ToDebugString() : std::string());
50 return dict;
51 }
52
CreateAltSvcUrl(const GURL & origin_url,const HostPortPair & alternative_destination)53 GURL CreateAltSvcUrl(const GURL& origin_url,
54 const HostPortPair& alternative_destination) {
55 DCHECK(origin_url.is_valid());
56 DCHECK(origin_url.IsStandard());
57
58 GURL::Replacements replacements;
59 std::string port_str = base::NumberToString(alternative_destination.port());
60 replacements.SetPortStr(port_str);
61 replacements.SetHostStr(alternative_destination.host());
62
63 return origin_url.ReplaceComponents(replacements);
64 }
65
ConvertWsToHttp(url::SchemeHostPort & input)66 void ConvertWsToHttp(url::SchemeHostPort& input) {
67 if (base::EqualsCaseInsensitiveASCII(input.scheme(), url::kHttpScheme) ||
68 base::EqualsCaseInsensitiveASCII(input.scheme(), url::kHttpsScheme)) {
69 return;
70 }
71
72 if (base::EqualsCaseInsensitiveASCII(input.scheme(), url::kWsScheme)) {
73 input = url::SchemeHostPort(url::kHttpScheme, input.host(), input.port());
74 return;
75 }
76
77 DCHECK(base::EqualsCaseInsensitiveASCII(input.scheme(), url::kWssScheme));
78 input = url::SchemeHostPort(url::kHttpsScheme, input.host(), input.port());
79 }
80
HistogramProxyUsed(const ProxyInfo & proxy_info,bool success)81 void HistogramProxyUsed(const ProxyInfo& proxy_info, bool success) {
82 const ProxyServer::Scheme max_scheme = ProxyServer::Scheme::SCHEME_QUIC;
83 ProxyServer::Scheme proxy_scheme = ProxyServer::Scheme::SCHEME_INVALID;
84 if (!proxy_info.is_empty() && !proxy_info.is_direct()) {
85 if (proxy_info.proxy_chain().is_multi_proxy()) {
86 // TODO(https://crbug.com/1491092): Update this histogram to have a new
87 // bucket for multi-chain proxies. Until then, don't influence the
88 // existing metric counts which have historically been only for single-hop
89 // proxies.
90 return;
91 }
92 proxy_scheme = proxy_info.proxy_chain().is_direct()
93 ? static_cast<ProxyServer::Scheme>(1)
94 : proxy_info.proxy_chain().First().scheme();
95 }
96 if (success) {
97 UMA_HISTOGRAM_ENUMERATION("Net.HttpJob.ProxyTypeSuccess", proxy_scheme,
98 max_scheme);
99 } else {
100 UMA_HISTOGRAM_ENUMERATION("Net.HttpJob.ProxyTypeFailed", proxy_scheme,
101 max_scheme);
102 }
103 }
104
105 // Generate a AlternativeService for DNS alt job. Note: Chrome does not yet
106 // support different port DNS alpn.
GetAlternativeServiceForDnsJob(const GURL & url)107 AlternativeService GetAlternativeServiceForDnsJob(const GURL& url) {
108 return AlternativeService(kProtoQUIC, HostPortPair::FromURL(url));
109 }
110
NetLogAltSvcParams(const AlternativeServiceInfo * alt_svc_info,bool is_broken)111 base::Value::Dict NetLogAltSvcParams(const AlternativeServiceInfo* alt_svc_info,
112 bool is_broken) {
113 base::Value::Dict dict;
114 dict.Set("alt_svc", alt_svc_info->ToString());
115 dict.Set("is_broken", is_broken);
116 return dict;
117 }
118
119 } // namespace
120
121 // The maximum time to wait for the alternate job to complete before resuming
122 // the main job.
123 const int kMaxDelayTimeForMainJobSecs = 3;
124
JobController(HttpStreamFactory * factory,HttpStreamRequest::Delegate * delegate,HttpNetworkSession * session,JobFactory * job_factory,const HttpRequestInfo & http_request_info,bool is_preconnect,bool is_websocket,bool enable_ip_based_pooling,bool enable_alternative_services,bool delay_main_job_with_available_spdy_session,const std::vector<SSLConfig::CertAndStatus> & allowed_bad_certs)125 HttpStreamFactory::JobController::JobController(
126 HttpStreamFactory* factory,
127 HttpStreamRequest::Delegate* delegate,
128 HttpNetworkSession* session,
129 JobFactory* job_factory,
130 const HttpRequestInfo& http_request_info,
131 bool is_preconnect,
132 bool is_websocket,
133 bool enable_ip_based_pooling,
134 bool enable_alternative_services,
135 bool delay_main_job_with_available_spdy_session,
136 const std::vector<SSLConfig::CertAndStatus>& allowed_bad_certs)
137 : factory_(factory),
138 session_(session),
139 job_factory_(job_factory),
140 delegate_(delegate),
141 is_preconnect_(is_preconnect),
142 is_websocket_(is_websocket),
143 enable_ip_based_pooling_(enable_ip_based_pooling),
144 enable_alternative_services_(enable_alternative_services),
145 delay_main_job_with_available_spdy_session_(
146 delay_main_job_with_available_spdy_session),
147 http_request_info_url_(http_request_info.url),
148 origin_url_(DuplicateUrlWithHostMappingRules(http_request_info.url)),
149 request_info_(http_request_info),
150 allowed_bad_certs_(allowed_bad_certs),
151 net_log_(NetLogWithSource::Make(
152 session->net_log(),
153 NetLogSourceType::HTTP_STREAM_JOB_CONTROLLER)) {
154 DCHECK(factory);
155 DCHECK(base::EqualsCaseInsensitiveASCII(origin_url_.scheme_piece(),
156 url::kHttpScheme) ||
157 base::EqualsCaseInsensitiveASCII(origin_url_.scheme_piece(),
158 url::kHttpsScheme) ||
159 base::EqualsCaseInsensitiveASCII(origin_url_.scheme_piece(),
160 url::kWsScheme) ||
161 base::EqualsCaseInsensitiveASCII(origin_url_.scheme_piece(),
162 url::kWssScheme));
163
164 net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER, [&] {
165 base::Value::Dict dict;
166 dict.Set("url", http_request_info.url.possibly_invalid_spec());
167 if (origin_url_ != http_request_info.url) {
168 dict.Set("url_after_host_mapping", origin_url_.possibly_invalid_spec());
169 }
170 dict.Set("is_preconnect", is_preconnect_);
171 dict.Set("privacy_mode",
172 PrivacyModeToDebugString(request_info_.privacy_mode));
173 return dict;
174 });
175 }
176
~JobController()177 HttpStreamFactory::JobController::~JobController() {
178 bound_job_ = nullptr;
179 main_job_.reset();
180 alternative_job_.reset();
181 dns_alpn_h3_job_.reset();
182 if (proxy_resolve_request_) {
183 DCHECK_EQ(STATE_RESOLVE_PROXY_COMPLETE, next_state_);
184 proxy_resolve_request_.reset();
185 }
186 net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER);
187 }
188
Start(HttpStreamRequest::Delegate * delegate,WebSocketHandshakeStreamBase::CreateHelper * websocket_handshake_stream_create_helper,const NetLogWithSource & source_net_log,HttpStreamRequest::StreamType stream_type,RequestPriority priority)189 std::unique_ptr<HttpStreamRequest> HttpStreamFactory::JobController::Start(
190 HttpStreamRequest::Delegate* delegate,
191 WebSocketHandshakeStreamBase::CreateHelper*
192 websocket_handshake_stream_create_helper,
193 const NetLogWithSource& source_net_log,
194 HttpStreamRequest::StreamType stream_type,
195 RequestPriority priority) {
196 DCHECK(factory_);
197 DCHECK(!request_);
198
199 stream_type_ = stream_type;
200 priority_ = priority;
201
202 auto request = std::make_unique<HttpStreamRequest>(
203 this, delegate, websocket_handshake_stream_create_helper, source_net_log,
204 stream_type);
205 // Keep a raw pointer but release ownership of HttpStreamRequest instance.
206 request_ = request.get();
207
208 // Associates |net_log_| with |source_net_log|.
209 source_net_log.AddEventReferencingSource(
210 NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, net_log_.source());
211 net_log_.AddEventReferencingSource(
212 NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND,
213 source_net_log.source());
214
215 RunLoop(OK);
216 return request;
217 }
218
Preconnect(int num_streams)219 void HttpStreamFactory::JobController::Preconnect(int num_streams) {
220 DCHECK(!main_job_);
221 DCHECK(!alternative_job_);
222 DCHECK(is_preconnect_);
223
224 stream_type_ = HttpStreamRequest::HTTP_STREAM;
225 num_streams_ = num_streams;
226
227 RunLoop(OK);
228 }
229
GetLoadState() const230 LoadState HttpStreamFactory::JobController::GetLoadState() const {
231 DCHECK(request_);
232 if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) {
233 return proxy_resolve_request_->GetLoadState();
234 }
235 if (bound_job_) {
236 return bound_job_->GetLoadState();
237 }
238 if (main_job_) {
239 return main_job_->GetLoadState();
240 }
241 if (alternative_job_) {
242 return alternative_job_->GetLoadState();
243 }
244 if (dns_alpn_h3_job_) {
245 return dns_alpn_h3_job_->GetLoadState();
246 }
247
248 // When proxy resolution fails, there is no job created and
249 // NotifyRequestFailed() is executed one message loop iteration later.
250 return LOAD_STATE_IDLE;
251 }
252
OnRequestComplete()253 void HttpStreamFactory::JobController::OnRequestComplete() {
254 DCHECK(request_);
255 request_ = nullptr;
256 // This is called when the delegate is destroying its HttpStreamRequest, so
257 // it's no longer safe to call into it after this point.
258 delegate_ = nullptr;
259
260 if (!job_bound_) {
261 alternative_job_.reset();
262 main_job_.reset();
263 dns_alpn_h3_job_.reset();
264 } else {
265 if (bound_job_->job_type() == MAIN) {
266 bound_job_ = nullptr;
267 main_job_.reset();
268 } else if (bound_job_->job_type() == ALTERNATIVE) {
269 bound_job_ = nullptr;
270 alternative_job_.reset();
271 } else {
272 DCHECK(bound_job_->job_type() == DNS_ALPN_H3);
273 bound_job_ = nullptr;
274 dns_alpn_h3_job_.reset();
275 }
276 }
277 MaybeNotifyFactoryOfCompletion();
278 }
279
RestartTunnelWithProxyAuth()280 int HttpStreamFactory::JobController::RestartTunnelWithProxyAuth() {
281 DCHECK(bound_job_);
282 return bound_job_->RestartTunnelWithProxyAuth();
283 }
284
SetPriority(RequestPriority priority)285 void HttpStreamFactory::JobController::SetPriority(RequestPriority priority) {
286 if (main_job_) {
287 main_job_->SetPriority(priority);
288 }
289 if (alternative_job_) {
290 alternative_job_->SetPriority(priority);
291 }
292 if (dns_alpn_h3_job_) {
293 dns_alpn_h3_job_->SetPriority(priority);
294 }
295 if (preconnect_backup_job_) {
296 preconnect_backup_job_->SetPriority(priority);
297 }
298 }
299
OnStreamReady(Job * job)300 void HttpStreamFactory::JobController::OnStreamReady(Job* job) {
301 DCHECK(job);
302
303 if (IsJobOrphaned(job)) {
304 // We have bound a job to the associated HttpStreamRequest, |job| has been
305 // orphaned.
306 OnOrphanedJobComplete(job);
307 return;
308 }
309 std::unique_ptr<HttpStream> stream = job->ReleaseStream();
310 DCHECK(stream);
311
312 MarkRequestComplete(job);
313
314 if (!request_) {
315 return;
316 }
317 DCHECK(!is_websocket_);
318 DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type());
319 OnJobSucceeded(job);
320
321 // TODO(bnc): Remove when https://crbug.com/461981 is fixed.
322 CHECK(request_);
323
324 DCHECK(request_->completed());
325
326 HistogramProxyUsed(job->proxy_info(), /*success=*/true);
327 delegate_->OnStreamReady(job->proxy_info(), std::move(stream));
328 }
329
OnBidirectionalStreamImplReady(Job * job,const ProxyInfo & used_proxy_info)330 void HttpStreamFactory::JobController::OnBidirectionalStreamImplReady(
331 Job* job,
332 const ProxyInfo& used_proxy_info) {
333 DCHECK(job);
334
335 if (IsJobOrphaned(job)) {
336 // We have bound a job to the associated HttpStreamRequest, |job| has been
337 // orphaned.
338 OnOrphanedJobComplete(job);
339 return;
340 }
341
342 MarkRequestComplete(job);
343
344 if (!request_) {
345 return;
346 }
347 std::unique_ptr<BidirectionalStreamImpl> stream =
348 job->ReleaseBidirectionalStream();
349 DCHECK(stream);
350 DCHECK(!is_websocket_);
351 DCHECK_EQ(HttpStreamRequest::BIDIRECTIONAL_STREAM, request_->stream_type());
352
353 OnJobSucceeded(job);
354 DCHECK(request_->completed());
355 delegate_->OnBidirectionalStreamImplReady(used_proxy_info, std::move(stream));
356 }
357
OnWebSocketHandshakeStreamReady(Job * job,const ProxyInfo & used_proxy_info,std::unique_ptr<WebSocketHandshakeStreamBase> stream)358 void HttpStreamFactory::JobController::OnWebSocketHandshakeStreamReady(
359 Job* job,
360 const ProxyInfo& used_proxy_info,
361 std::unique_ptr<WebSocketHandshakeStreamBase> stream) {
362 DCHECK(job);
363 MarkRequestComplete(job);
364
365 if (!request_) {
366 return;
367 }
368 DCHECK(is_websocket_);
369 DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type());
370 DCHECK(stream);
371
372 OnJobSucceeded(job);
373 DCHECK(request_->completed());
374 delegate_->OnWebSocketHandshakeStreamReady(used_proxy_info,
375 std::move(stream));
376 }
377
OnStreamFailed(Job * job,int status)378 void HttpStreamFactory::JobController::OnStreamFailed(Job* job, int status) {
379 DCHECK_NE(OK, status);
380 if (job->job_type() == MAIN) {
381 DCHECK_EQ(main_job_.get(), job);
382 main_job_net_error_ = status;
383 } else if (job->job_type() == ALTERNATIVE) {
384 DCHECK_EQ(alternative_job_.get(), job);
385 DCHECK_NE(kProtoUnknown, alternative_service_info_.protocol());
386 alternative_job_net_error_ = status;
387 } else {
388 DCHECK_EQ(job->job_type(), DNS_ALPN_H3);
389 DCHECK_EQ(dns_alpn_h3_job_.get(), job);
390 dns_alpn_h3_job_net_error_ = status;
391 }
392
393 MaybeResumeMainJob(job, base::TimeDelta());
394
395 if (IsJobOrphaned(job)) {
396 // We have bound a job to the associated HttpStreamRequest, |job| has been
397 // orphaned.
398 OnOrphanedJobComplete(job);
399 return;
400 }
401
402 if (!request_) {
403 return;
404 }
405 DCHECK_NE(OK, status);
406 DCHECK(job);
407
408 if (!bound_job_) {
409 if (GetJobCount() >= 2) {
410 // Hey, we've got other jobs! Maybe one of them will succeed, let's just
411 // ignore this failure.
412 if (job->job_type() == MAIN) {
413 DCHECK_EQ(main_job_.get(), job);
414 main_job_.reset();
415 } else if (job->job_type() == ALTERNATIVE) {
416 DCHECK_EQ(alternative_job_.get(), job);
417 alternative_job_.reset();
418 } else {
419 DCHECK_EQ(job->job_type(), DNS_ALPN_H3);
420 DCHECK_EQ(dns_alpn_h3_job_.get(), job);
421 dns_alpn_h3_job_.reset();
422 }
423 return;
424 } else {
425 BindJob(job);
426 }
427 }
428
429 status = ReconsiderProxyAfterError(job, status);
430 if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) {
431 if (status == ERR_IO_PENDING) {
432 return;
433 }
434 DCHECK_EQ(OK, status);
435 RunLoop(status);
436 return;
437 }
438
439 HistogramProxyUsed(job->proxy_info(), /*success=*/false);
440 delegate_->OnStreamFailed(status, *job->net_error_details(),
441 job->proxy_info(), job->resolve_error_info());
442 }
443
OnFailedOnDefaultNetwork(Job * job)444 void HttpStreamFactory::JobController::OnFailedOnDefaultNetwork(Job* job) {
445 if (job->job_type() == ALTERNATIVE) {
446 DCHECK_EQ(alternative_job_.get(), job);
447 alternative_job_failed_on_default_network_ = true;
448 } else {
449 DCHECK_EQ(job->job_type(), DNS_ALPN_H3);
450 DCHECK_EQ(dns_alpn_h3_job_.get(), job);
451 dns_alpn_h3_job_failed_on_default_network_ = true;
452 }
453 }
454
OnCertificateError(Job * job,int status,const SSLInfo & ssl_info)455 void HttpStreamFactory::JobController::OnCertificateError(
456 Job* job,
457 int status,
458 const SSLInfo& ssl_info) {
459 MaybeResumeMainJob(job, base::TimeDelta());
460
461 if (IsJobOrphaned(job)) {
462 // We have bound a job to the associated HttpStreamRequest, |job| has been
463 // orphaned.
464 OnOrphanedJobComplete(job);
465 return;
466 }
467
468 if (!request_) {
469 return;
470 }
471 DCHECK_NE(OK, status);
472 if (!bound_job_) {
473 BindJob(job);
474 }
475
476 delegate_->OnCertificateError(status, ssl_info);
477 }
478
OnNeedsClientAuth(Job * job,SSLCertRequestInfo * cert_info)479 void HttpStreamFactory::JobController::OnNeedsClientAuth(
480 Job* job,
481 SSLCertRequestInfo* cert_info) {
482 MaybeResumeMainJob(job, base::TimeDelta());
483
484 if (IsJobOrphaned(job)) {
485 // We have bound a job to the associated HttpStreamRequest, |job| has been
486 // orphaned.
487 OnOrphanedJobComplete(job);
488 return;
489 }
490 if (!request_) {
491 return;
492 }
493 if (!bound_job_) {
494 BindJob(job);
495 }
496
497 delegate_->OnNeedsClientAuth(cert_info);
498 }
499
OnNeedsProxyAuth(Job * job,const HttpResponseInfo & proxy_response,const ProxyInfo & used_proxy_info,HttpAuthController * auth_controller)500 void HttpStreamFactory::JobController::OnNeedsProxyAuth(
501 Job* job,
502 const HttpResponseInfo& proxy_response,
503 const ProxyInfo& used_proxy_info,
504 HttpAuthController* auth_controller) {
505 MaybeResumeMainJob(job, base::TimeDelta());
506
507 if (IsJobOrphaned(job)) {
508 // We have bound a job to the associated HttpStreamRequest, |job| has been
509 // orphaned.
510 OnOrphanedJobComplete(job);
511 return;
512 }
513
514 if (!request_) {
515 return;
516 }
517 if (!bound_job_) {
518 BindJob(job);
519 }
520 delegate_->OnNeedsProxyAuth(proxy_response, used_proxy_info, auth_controller);
521 }
522
OnPreconnectsComplete(Job * job,int result)523 void HttpStreamFactory::JobController::OnPreconnectsComplete(Job* job,
524 int result) {
525 // Preconnects only run as `main_job_`, never `alternative_job_` or
526 // `dns_alpn_h3_job_`.
527 DCHECK_EQ(main_job_.get(), job);
528
529 // If the job failed because there were no matching HTTPS records in DNS, run
530 // the backup job. A TCP-based protocol may work instead.
531 if (result == ERR_DNS_NO_MATCHING_SUPPORTED_ALPN && preconnect_backup_job_) {
532 DCHECK_EQ(job->job_type(), PRECONNECT_DNS_ALPN_H3);
533 main_job_ = std::move(preconnect_backup_job_);
534 main_job_->Preconnect(num_streams_);
535 return;
536 }
537
538 main_job_.reset();
539 preconnect_backup_job_.reset();
540 ResetErrorStatusForJobs();
541 factory_->OnPreconnectsCompleteInternal();
542 MaybeNotifyFactoryOfCompletion();
543 }
544
OnOrphanedJobComplete(const Job * job)545 void HttpStreamFactory::JobController::OnOrphanedJobComplete(const Job* job) {
546 if (job->job_type() == MAIN) {
547 DCHECK_EQ(main_job_.get(), job);
548 main_job_.reset();
549 } else if (job->job_type() == ALTERNATIVE) {
550 DCHECK_EQ(alternative_job_.get(), job);
551 alternative_job_.reset();
552 } else {
553 DCHECK_EQ(job->job_type(), DNS_ALPN_H3);
554 DCHECK_EQ(dns_alpn_h3_job_.get(), job);
555 dns_alpn_h3_job_.reset();
556 }
557
558 MaybeNotifyFactoryOfCompletion();
559 }
560
AddConnectionAttemptsToRequest(Job * job,const ConnectionAttempts & attempts)561 void HttpStreamFactory::JobController::AddConnectionAttemptsToRequest(
562 Job* job,
563 const ConnectionAttempts& attempts) {
564 if (is_preconnect_ || IsJobOrphaned(job)) {
565 return;
566 }
567
568 request_->AddConnectionAttempts(attempts);
569 }
570
ResumeMainJobLater(const base::TimeDelta & delay)571 void HttpStreamFactory::JobController::ResumeMainJobLater(
572 const base::TimeDelta& delay) {
573 net_log_.AddEventWithInt64Params(NetLogEventType::HTTP_STREAM_JOB_DELAYED,
574 "delay", delay.InMilliseconds());
575 resume_main_job_callback_.Reset(
576 base::BindOnce(&HttpStreamFactory::JobController::ResumeMainJob,
577 ptr_factory_.GetWeakPtr()));
578 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
579 FROM_HERE, resume_main_job_callback_.callback(), delay);
580 }
581
ResumeMainJob()582 void HttpStreamFactory::JobController::ResumeMainJob() {
583 DCHECK(main_job_);
584
585 if (main_job_is_resumed_) {
586 return;
587 }
588 main_job_is_resumed_ = true;
589 main_job_->net_log().AddEventWithInt64Params(
590 NetLogEventType::HTTP_STREAM_JOB_RESUMED, "delay",
591 main_job_wait_time_.InMilliseconds());
592
593 main_job_->Resume();
594 main_job_wait_time_ = base::TimeDelta();
595 }
596
ResetErrorStatusForJobs()597 void HttpStreamFactory::JobController::ResetErrorStatusForJobs() {
598 main_job_net_error_ = OK;
599 alternative_job_net_error_ = OK;
600 alternative_job_failed_on_default_network_ = false;
601 dns_alpn_h3_job_net_error_ = OK;
602 dns_alpn_h3_job_failed_on_default_network_ = false;
603 }
604
MaybeResumeMainJob(Job * job,const base::TimeDelta & delay)605 void HttpStreamFactory::JobController::MaybeResumeMainJob(
606 Job* job,
607 const base::TimeDelta& delay) {
608 DCHECK(delay == base::TimeDelta() || delay == main_job_wait_time_);
609 DCHECK(job == main_job_.get() || job == alternative_job_.get() ||
610 job == dns_alpn_h3_job_.get());
611
612 if (job == main_job_.get()) {
613 return;
614 }
615 if (job == dns_alpn_h3_job_.get() && alternative_job_) {
616 return;
617 }
618 if (!main_job_) {
619 return;
620 }
621
622 main_job_is_blocked_ = false;
623
624 if (!main_job_->is_waiting()) {
625 // There are two cases where the main job is not in WAIT state:
626 // 1) The main job hasn't got to waiting state, do not yet post a task to
627 // resume since that will happen in ShouldWait().
628 // 2) The main job has passed waiting state, so the main job does not need
629 // to be resumed.
630 return;
631 }
632
633 main_job_wait_time_ = delay;
634
635 ResumeMainJobLater(main_job_wait_time_);
636 }
637
OnConnectionInitialized(Job * job,int rv)638 void HttpStreamFactory::JobController::OnConnectionInitialized(Job* job,
639 int rv) {
640 if (rv != OK) {
641 // Resume the main job as there's an error raised in connection
642 // initiation.
643 return MaybeResumeMainJob(job, main_job_wait_time_);
644 }
645 }
646
ShouldWait(Job * job)647 bool HttpStreamFactory::JobController::ShouldWait(Job* job) {
648 // The alternative job never waits.
649 if (job == alternative_job_.get() || job == dns_alpn_h3_job_.get()) {
650 return false;
651 }
652 DCHECK_EQ(main_job_.get(), job);
653 if (main_job_is_blocked_) {
654 return true;
655 }
656
657 if (main_job_wait_time_.is_zero()) {
658 return false;
659 }
660
661 ResumeMainJobLater(main_job_wait_time_);
662 return true;
663 }
664
GetNetLog() const665 const NetLogWithSource* HttpStreamFactory::JobController::GetNetLog() const {
666 return &net_log_;
667 }
668
MaybeSetWaitTimeForMainJob(const base::TimeDelta & delay)669 void HttpStreamFactory::JobController::MaybeSetWaitTimeForMainJob(
670 const base::TimeDelta& delay) {
671 if (main_job_is_blocked_) {
672 const bool has_available_spdy_session =
673 main_job_->HasAvailableSpdySession();
674 if (!delay_main_job_with_available_spdy_session_ &&
675 has_available_spdy_session) {
676 main_job_wait_time_ = base::TimeDelta();
677 } else {
678 main_job_wait_time_ =
679 std::min(delay, base::Seconds(kMaxDelayTimeForMainJobSecs));
680 }
681 if (has_available_spdy_session) {
682 UMA_HISTOGRAM_TIMES("Net.HttpJob.MainJobWaitTimeWithAvailableSpdySession",
683 main_job_wait_time_);
684 } else {
685 UMA_HISTOGRAM_TIMES(
686 "Net.HttpJob.MainJobWaitTimeWithoutAvailableSpdySession",
687 main_job_wait_time_);
688 }
689 }
690 }
691
HasPendingMainJob() const692 bool HttpStreamFactory::JobController::HasPendingMainJob() const {
693 return main_job_.get() != nullptr;
694 }
695
HasPendingAltJob() const696 bool HttpStreamFactory::JobController::HasPendingAltJob() const {
697 return alternative_job_.get() != nullptr;
698 }
699
700 WebSocketHandshakeStreamBase::CreateHelper*
websocket_handshake_stream_create_helper()701 HttpStreamFactory::JobController::websocket_handshake_stream_create_helper() {
702 DCHECK(request_);
703 return request_->websocket_handshake_stream_create_helper();
704 }
705
OnIOComplete(int result)706 void HttpStreamFactory::JobController::OnIOComplete(int result) {
707 RunLoop(result);
708 }
709
RunLoop(int result)710 void HttpStreamFactory::JobController::RunLoop(int result) {
711 int rv = DoLoop(result);
712 if (rv == ERR_IO_PENDING) {
713 return;
714 }
715 if (rv != OK) {
716 // DoLoop can only fail during proxy resolution step which happens before
717 // any jobs are created. Notify |request_| of the failure one message loop
718 // iteration later to avoid re-entrancy.
719 DCHECK(!main_job_);
720 DCHECK(!alternative_job_);
721 DCHECK(!dns_alpn_h3_job_);
722 base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
723 FROM_HERE,
724 base::BindOnce(&HttpStreamFactory::JobController::NotifyRequestFailed,
725 ptr_factory_.GetWeakPtr(), rv));
726 }
727 }
728
DoLoop(int rv)729 int HttpStreamFactory::JobController::DoLoop(int rv) {
730 DCHECK_NE(next_state_, STATE_NONE);
731 do {
732 State state = next_state_;
733 next_state_ = STATE_NONE;
734 switch (state) {
735 case STATE_RESOLVE_PROXY:
736 DCHECK_EQ(OK, rv);
737 rv = DoResolveProxy();
738 break;
739 case STATE_RESOLVE_PROXY_COMPLETE:
740 rv = DoResolveProxyComplete(rv);
741 break;
742 case STATE_CREATE_JOBS:
743 DCHECK_EQ(OK, rv);
744 rv = DoCreateJobs();
745 break;
746 default:
747 NOTREACHED() << "bad state";
748 break;
749 }
750 } while (next_state_ != STATE_NONE && rv != ERR_IO_PENDING);
751 return rv;
752 }
753
DoResolveProxy()754 int HttpStreamFactory::JobController::DoResolveProxy() {
755 DCHECK(!proxy_resolve_request_);
756 DCHECK(session_);
757
758 next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
759
760 if (request_info_.load_flags & LOAD_BYPASS_PROXY) {
761 proxy_info_.UseDirect();
762 return OK;
763 }
764
765 CompletionOnceCallback io_callback =
766 base::BindOnce(&JobController::OnIOComplete, base::Unretained(this));
767 return session_->proxy_resolution_service()->ResolveProxy(
768 origin_url_, request_info_.method,
769 request_info_.network_anonymization_key, &proxy_info_,
770 std::move(io_callback), &proxy_resolve_request_, net_log_);
771 }
772
DoResolveProxyComplete(int rv)773 int HttpStreamFactory::JobController::DoResolveProxyComplete(int rv) {
774 DCHECK_NE(ERR_IO_PENDING, rv);
775
776 proxy_resolve_request_ = nullptr;
777 net_log_.AddEvent(
778 NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_PROXY_SERVER_RESOLVED, [&] {
779 return NetLogHttpStreamJobProxyChainResolved(
780 proxy_info_.is_empty() ? ProxyChain() : proxy_info_.proxy_chain());
781 });
782
783 if (rv != OK) {
784 return rv;
785 }
786 // Remove unsupported proxies from the list.
787 int supported_proxies = ProxyServer::SCHEME_HTTP | ProxyServer::SCHEME_HTTPS |
788 ProxyServer::SCHEME_SOCKS4 |
789 ProxyServer::SCHEME_SOCKS5;
790 // WebSockets is not supported over QUIC.
791 if (session_->IsQuicEnabled() && !is_websocket_) {
792 supported_proxies |= ProxyServer::SCHEME_QUIC;
793 }
794 proxy_info_.RemoveProxiesWithoutScheme(supported_proxies);
795
796 if (proxy_info_.is_empty()) {
797 // No proxies/direct to choose from.
798 return ERR_NO_SUPPORTED_PROXIES;
799 }
800
801 next_state_ = STATE_CREATE_JOBS;
802 return rv;
803 }
804
DoCreateJobs()805 int HttpStreamFactory::JobController::DoCreateJobs() {
806 DCHECK(!main_job_);
807 DCHECK(!alternative_job_);
808 DCHECK(origin_url_.is_valid());
809 DCHECK(origin_url_.IsStandard());
810
811 url::SchemeHostPort destination(origin_url_);
812 DCHECK(destination.IsValid());
813 ConvertWsToHttp(destination);
814
815 // Create an alternative job if alternative service is set up for this domain,
816 // but only if we'll be speaking directly to the server, since QUIC through
817 // proxies is not supported.
818 if (proxy_info_.is_direct()) {
819 alternative_service_info_ = GetAlternativeServiceInfoFor(
820 http_request_info_url_, request_info_, delegate_, stream_type_);
821 }
822 quic::ParsedQuicVersion quic_version = quic::ParsedQuicVersion::Unsupported();
823 if (alternative_service_info_.protocol() == kProtoQUIC) {
824 quic_version =
825 SelectQuicVersion(alternative_service_info_.advertised_versions());
826 DCHECK_NE(quic_version, quic::ParsedQuicVersion::Unsupported());
827 }
828 const bool dns_alpn_h3_job_enabled =
829 !HttpStreamFactory::Job::OriginToForceQuicOn(
830 *session_->context().quic_context->params(), destination) &&
831 enable_alternative_services_ &&
832 session_->params().use_dns_https_svcb_alpn &&
833 base::EqualsCaseInsensitiveASCII(origin_url_.scheme(),
834 url::kHttpsScheme) &&
835 session_->IsQuicEnabled() &&
836 !session_->http_server_properties()->IsAlternativeServiceBroken(
837 GetAlternativeServiceForDnsJob(origin_url_),
838 request_info_.network_anonymization_key);
839
840 if (is_preconnect_) {
841 // Due to how the socket pools handle priorities and idle sockets, only IDLE
842 // priority currently makes sense for preconnects. The priority for
843 // preconnects is currently ignored (see RequestSocketsForPool()), but could
844 // be used at some point for proxy resolution or something.
845 // Note: When `dns_alpn_h3_job_enabled` is true, we create a
846 // PRECONNECT_DNS_ALPN_H3 job. If no matching HTTPS DNS ALPN records are
847 // received, the PRECONNECT_DNS_ALPN_H3 job will fail with
848 // ERR_DNS_NO_MATCHING_SUPPORTED_ALPN, and `preconnect_backup_job_` will
849 // be started in OnPreconnectsComplete().
850 std::unique_ptr<Job> preconnect_job = job_factory_->CreateJob(
851 this, dns_alpn_h3_job_enabled ? PRECONNECT_DNS_ALPN_H3 : PRECONNECT,
852 session_, request_info_, IDLE, proxy_info_, allowed_bad_certs_,
853 destination, origin_url_, is_websocket_, enable_ip_based_pooling_,
854 net_log_.net_log());
855 // When there is an valid alternative service info, and `preconnect_job`
856 // has no existing QUIC session, create a job for the alternative service.
857 if (alternative_service_info_.protocol() != kProtoUnknown &&
858 !preconnect_job->HasAvailableQuicSession()) {
859 GURL alternative_url = CreateAltSvcUrl(
860 origin_url_, alternative_service_info_.host_port_pair());
861 RewriteUrlWithHostMappingRules(alternative_url);
862
863 url::SchemeHostPort alternative_destination =
864 url::SchemeHostPort(alternative_url);
865 ConvertWsToHttp(alternative_destination);
866
867 main_job_ = job_factory_->CreateJob(
868 this, PRECONNECT, session_, request_info_, IDLE, proxy_info_,
869 allowed_bad_certs_, std::move(alternative_destination), origin_url_,
870 is_websocket_, enable_ip_based_pooling_, session_->net_log(),
871 alternative_service_info_.protocol(), quic_version);
872 } else {
873 main_job_ = std::move(preconnect_job);
874
875 if (dns_alpn_h3_job_enabled) {
876 preconnect_backup_job_ = job_factory_->CreateJob(
877 this, PRECONNECT, session_, request_info_, IDLE, proxy_info_,
878 allowed_bad_certs_, std::move(destination), origin_url_,
879 is_websocket_, enable_ip_based_pooling_, net_log_.net_log());
880 }
881 }
882 main_job_->Preconnect(num_streams_);
883 return OK;
884 }
885 main_job_ = job_factory_->CreateJob(
886 this, MAIN, session_, request_info_, priority_, proxy_info_,
887 allowed_bad_certs_, std::move(destination), origin_url_, is_websocket_,
888 enable_ip_based_pooling_, net_log_.net_log());
889
890 // Alternative Service can only be set for HTTPS requests while Alternative
891 // Proxy is set for HTTP requests.
892 // The main job may use HTTP/3 if the origin is specified in
893 // `--origin-to-force-quic-on` switch. In that case, do not create
894 // `alternative_job_` and `dns_alpn_h3_job_`.
895 if ((alternative_service_info_.protocol() != kProtoUnknown) &&
896 !main_job_->using_quic()) {
897 DCHECK(origin_url_.SchemeIs(url::kHttpsScheme));
898 DCHECK(!is_websocket_);
899 DVLOG(1) << "Selected alternative service (host: "
900 << alternative_service_info_.host_port_pair().host()
901 << " port: " << alternative_service_info_.host_port_pair().port()
902 << " version: " << quic_version << ")";
903
904 GURL alternative_url = CreateAltSvcUrl(
905 origin_url_, alternative_service_info_.host_port_pair());
906 RewriteUrlWithHostMappingRules(alternative_url);
907
908 url::SchemeHostPort alternative_destination =
909 url::SchemeHostPort(alternative_url);
910 ConvertWsToHttp(alternative_destination);
911
912 alternative_job_ = job_factory_->CreateJob(
913 this, ALTERNATIVE, session_, request_info_, priority_, proxy_info_,
914 allowed_bad_certs_, std::move(alternative_destination), origin_url_,
915 is_websocket_, enable_ip_based_pooling_, net_log_.net_log(),
916 alternative_service_info_.protocol(), quic_version);
917 }
918
919 if (dns_alpn_h3_job_enabled && !main_job_->using_quic()) {
920 DCHECK(!is_websocket_);
921 url::SchemeHostPort dns_alpn_h3_destination =
922 url::SchemeHostPort(origin_url_);
923 dns_alpn_h3_job_ = job_factory_->CreateJob(
924 this, DNS_ALPN_H3, session_, request_info_, priority_, proxy_info_,
925 allowed_bad_certs_, std::move(dns_alpn_h3_destination), origin_url_,
926 is_websocket_, enable_ip_based_pooling_, net_log_.net_log());
927 }
928
929 ClearInappropriateJobs();
930
931 if (main_job_ && (alternative_job_ ||
932 (dns_alpn_h3_job_ &&
933 (!main_job_->TargettedSocketGroupHasActiveSocket() &&
934 !main_job_->HasAvailableSpdySession())))) {
935 // We don't block |main_job_| when |alternative_job_| doesn't exists and
936 // |dns_alpn_h3_job_| exists and an active socket is available for
937 // |main_job_|. This is intended to make the fallback logic faster.
938 main_job_is_blocked_ = true;
939 }
940
941 if (alternative_job_) {
942 alternative_job_->Start(request_->stream_type());
943 }
944
945 if (dns_alpn_h3_job_) {
946 dns_alpn_h3_job_->Start(request_->stream_type());
947 }
948
949 if (main_job_) {
950 main_job_->Start(request_->stream_type());
951 }
952 return OK;
953 }
954
ClearInappropriateJobs()955 void HttpStreamFactory::JobController::ClearInappropriateJobs() {
956 if (dns_alpn_h3_job_ && dns_alpn_h3_job_->HasAvailableQuicSession()) {
957 // Clear |main_job_| and |alternative_job_| here not to start them when
958 // there is an active session available for |dns_alpn_h3_job_|.
959 main_job_.reset();
960 alternative_job_.reset();
961 }
962
963 if (alternative_job_ && dns_alpn_h3_job_ &&
964 (alternative_job_->HasAvailableQuicSession() ||
965 (alternative_service_info_.alternative_service() ==
966 GetAlternativeServiceForDnsJob(http_request_info_url_)))) {
967 // Clear |dns_alpn_h3_job_|, when there is an active session available for
968 // |alternative_job_| or |alternative_job_| was created for the same
969 // destination.
970 dns_alpn_h3_job_.reset();
971 }
972 }
973
BindJob(Job * job)974 void HttpStreamFactory::JobController::BindJob(Job* job) {
975 DCHECK(request_);
976 DCHECK(job);
977 DCHECK(job == alternative_job_.get() || job == main_job_.get() ||
978 job == dns_alpn_h3_job_.get());
979 DCHECK(!job_bound_);
980 DCHECK(!bound_job_);
981
982 job_bound_ = true;
983 bound_job_ = job;
984
985 request_->net_log().AddEventReferencingSource(
986 NetLogEventType::HTTP_STREAM_REQUEST_BOUND_TO_JOB,
987 job->net_log().source());
988 job->net_log().AddEventReferencingSource(
989 NetLogEventType::HTTP_STREAM_JOB_BOUND_TO_REQUEST,
990 request_->net_log().source());
991
992 OrphanUnboundJob();
993 }
994
OrphanUnboundJob()995 void HttpStreamFactory::JobController::OrphanUnboundJob() {
996 DCHECK(request_);
997 DCHECK(bound_job_);
998
999 if (bound_job_->job_type() == MAIN) {
1000 // Allow |alternative_job_| and |dns_alpn_h3_job_| to run to completion,
1001 // rather than resetting them to check if there is any broken alternative
1002 // service to report. OnOrphanedJobComplete() will clean up |this| when the
1003 // jobs complete.
1004 if (alternative_job_) {
1005 DCHECK(!is_websocket_);
1006 alternative_job_->Orphan();
1007 }
1008 if (dns_alpn_h3_job_) {
1009 DCHECK(!is_websocket_);
1010 dns_alpn_h3_job_->Orphan();
1011 }
1012 return;
1013 }
1014
1015 if (bound_job_->job_type() == ALTERNATIVE) {
1016 if (!alternative_job_failed_on_default_network_ && !dns_alpn_h3_job_) {
1017 // |request_| is bound to the alternative job and the alternative job
1018 // succeeds on the default network, and there is no DNS alt job. This
1019 // means that the main job is no longer needed, so cancel it now. Pending
1020 // ConnectJobs will return established sockets to socket pools if
1021 // applicable.
1022 // https://crbug.com/757548.
1023 // The main job still needs to run if the alternative job succeeds on the
1024 // alternate network in order to figure out whether QUIC should be marked
1025 // as broken until the default network changes. And also the main job
1026 // still needs to run if the DNS alt job exists to figure out whether
1027 // the DNS alpn service is broken.
1028 DCHECK(!main_job_ || (alternative_job_net_error_ == OK));
1029 main_job_.reset();
1030 }
1031 // Allow |dns_alpn_h3_job_| to run to completion, rather than resetting
1032 // it to check if there is any broken alternative service to report.
1033 // OnOrphanedJobComplete() will clean up |this| when the job completes.
1034 if (dns_alpn_h3_job_) {
1035 DCHECK(!is_websocket_);
1036 dns_alpn_h3_job_->Orphan();
1037 }
1038 }
1039 if (bound_job_->job_type() == DNS_ALPN_H3) {
1040 if (!dns_alpn_h3_job_failed_on_default_network_ && !alternative_job_) {
1041 DCHECK(!main_job_ || (dns_alpn_h3_job_net_error_ == OK));
1042 main_job_.reset();
1043 }
1044 // Allow |alternative_job_| to run to completion, rather than resetting
1045 // it to check if there is any broken alternative service to report.
1046 // OnOrphanedJobComplete() will clean up |this| when the job completes.
1047 if (alternative_job_) {
1048 DCHECK(!is_websocket_);
1049 alternative_job_->Orphan();
1050 }
1051 }
1052 }
1053
OnJobSucceeded(Job * job)1054 void HttpStreamFactory::JobController::OnJobSucceeded(Job* job) {
1055 DCHECK(job);
1056 if (!bound_job_) {
1057 BindJob(job);
1058 return;
1059 }
1060 }
1061
MarkRequestComplete(Job * job)1062 void HttpStreamFactory::JobController::MarkRequestComplete(Job* job) {
1063 if (request_) {
1064 AlternateProtocolUsage alternate_protocol_usage =
1065 CalculateAlternateProtocolUsage(job);
1066 request_->Complete(job->negotiated_protocol(), alternate_protocol_usage);
1067 ReportAlternateProtocolUsage(alternate_protocol_usage,
1068 HasGoogleHost(job->origin_url()));
1069 }
1070 }
1071
MaybeReportBrokenAlternativeService(const AlternativeService & alt_service,int alt_job_net_error,bool alt_job_failed_on_default_network,const std::string & histogram_name_for_failure)1072 void HttpStreamFactory::JobController::MaybeReportBrokenAlternativeService(
1073 const AlternativeService& alt_service,
1074 int alt_job_net_error,
1075 bool alt_job_failed_on_default_network,
1076 const std::string& histogram_name_for_failure) {
1077 // If alternative job succeeds on the default network, no brokenness to
1078 // report.
1079 if (alt_job_net_error == OK && !alt_job_failed_on_default_network) {
1080 return;
1081 }
1082
1083 // No brokenness to report if the main job fails.
1084 if (main_job_net_error_ != OK) {
1085 return;
1086 }
1087
1088 // No need to record DNS_NO_MATCHING_SUPPORTED_ALPN error.
1089 if (alt_job_net_error == ERR_DNS_NO_MATCHING_SUPPORTED_ALPN) {
1090 return;
1091 }
1092
1093 if (alt_job_failed_on_default_network && alt_job_net_error == OK) {
1094 // Alternative job failed on the default network but succeeds on the
1095 // non-default network, mark alternative service broken until the default
1096 // network changes.
1097 session_->http_server_properties()
1098 ->MarkAlternativeServiceBrokenUntilDefaultNetworkChanges(
1099 alt_service, request_info_.network_anonymization_key);
1100 return;
1101 }
1102
1103 if (alt_job_net_error == ERR_NETWORK_CHANGED ||
1104 alt_job_net_error == ERR_INTERNET_DISCONNECTED ||
1105 (alt_job_net_error == ERR_NAME_NOT_RESOLVED &&
1106 http_request_info_url_.host() == alt_service.host)) {
1107 // No need to mark alternative service as broken.
1108 return;
1109 }
1110
1111 // Report brokenness if alternative job failed.
1112 base::UmaHistogramSparse(histogram_name_for_failure, -alt_job_net_error);
1113
1114 HistogramBrokenAlternateProtocolLocation(
1115 BROKEN_ALTERNATE_PROTOCOL_LOCATION_HTTP_STREAM_FACTORY_JOB_ALT);
1116 session_->http_server_properties()->MarkAlternativeServiceBroken(
1117 alt_service, request_info_.network_anonymization_key);
1118 }
1119
MaybeNotifyFactoryOfCompletion()1120 void HttpStreamFactory::JobController::MaybeNotifyFactoryOfCompletion() {
1121 if (main_job_ || alternative_job_ || dns_alpn_h3_job_) {
1122 return;
1123 }
1124
1125 // All jobs are gone.
1126 // Report brokenness for the alternate jobs if apply.
1127 MaybeReportBrokenAlternativeService(
1128 alternative_service_info_.alternative_service(),
1129 alternative_job_net_error_, alternative_job_failed_on_default_network_,
1130 "Net.AlternateServiceFailed");
1131 // Report for the DNS alt job if apply.
1132 MaybeReportBrokenAlternativeService(
1133 GetAlternativeServiceForDnsJob(http_request_info_url_),
1134 dns_alpn_h3_job_net_error_, dns_alpn_h3_job_failed_on_default_network_,
1135 "Net.AlternateServiceForDnsAlpnH3Failed");
1136
1137 // Reset error status for Jobs after reporting brokenness to avoid redundant
1138 // reporting.
1139 ResetErrorStatusForJobs();
1140
1141 if (request_) {
1142 return;
1143 }
1144 DCHECK(!bound_job_);
1145 factory_->OnJobControllerComplete(this);
1146 }
1147
NotifyRequestFailed(int rv)1148 void HttpStreamFactory::JobController::NotifyRequestFailed(int rv) {
1149 if (!request_) {
1150 return;
1151 }
1152 delegate_->OnStreamFailed(rv, NetErrorDetails(), ProxyInfo(),
1153 ResolveErrorInfo());
1154 }
1155
RewriteUrlWithHostMappingRules(GURL & url) const1156 void HttpStreamFactory::JobController::RewriteUrlWithHostMappingRules(
1157 GURL& url) const {
1158 session_->params().host_mapping_rules.RewriteUrl(url);
1159 }
1160
DuplicateUrlWithHostMappingRules(const GURL & url) const1161 GURL HttpStreamFactory::JobController::DuplicateUrlWithHostMappingRules(
1162 const GURL& url) const {
1163 GURL copy = url;
1164 RewriteUrlWithHostMappingRules(copy);
1165 return copy;
1166 }
1167
1168 AlternativeServiceInfo
GetAlternativeServiceInfoFor(const GURL & http_request_info_url,const StreamRequestInfo & request_info,HttpStreamRequest::Delegate * delegate,HttpStreamRequest::StreamType stream_type)1169 HttpStreamFactory::JobController::GetAlternativeServiceInfoFor(
1170 const GURL& http_request_info_url,
1171 const StreamRequestInfo& request_info,
1172 HttpStreamRequest::Delegate* delegate,
1173 HttpStreamRequest::StreamType stream_type) {
1174 if (!enable_alternative_services_) {
1175 return AlternativeServiceInfo();
1176 }
1177
1178 AlternativeServiceInfo alternative_service_info =
1179 GetAlternativeServiceInfoInternal(http_request_info_url, request_info,
1180 delegate, stream_type);
1181 AlternativeServiceType type;
1182 if (alternative_service_info.protocol() == kProtoUnknown) {
1183 type = NO_ALTERNATIVE_SERVICE;
1184 } else if (alternative_service_info.protocol() == kProtoQUIC) {
1185 if (http_request_info_url.host_piece() ==
1186 alternative_service_info.alternative_service().host) {
1187 type = QUIC_SAME_DESTINATION;
1188 } else {
1189 type = QUIC_DIFFERENT_DESTINATION;
1190 }
1191 } else {
1192 if (http_request_info_url.host_piece() ==
1193 alternative_service_info.alternative_service().host) {
1194 type = NOT_QUIC_SAME_DESTINATION;
1195 } else {
1196 type = NOT_QUIC_DIFFERENT_DESTINATION;
1197 }
1198 }
1199 UMA_HISTOGRAM_ENUMERATION("Net.AlternativeServiceTypeForRequest", type,
1200 MAX_ALTERNATIVE_SERVICE_TYPE);
1201 return alternative_service_info;
1202 }
1203
1204 AlternativeServiceInfo
GetAlternativeServiceInfoInternal(const GURL & http_request_info_url,const StreamRequestInfo & request_info,HttpStreamRequest::Delegate * delegate,HttpStreamRequest::StreamType stream_type)1205 HttpStreamFactory::JobController::GetAlternativeServiceInfoInternal(
1206 const GURL& http_request_info_url,
1207 const StreamRequestInfo& request_info,
1208 HttpStreamRequest::Delegate* delegate,
1209 HttpStreamRequest::StreamType stream_type) {
1210 GURL original_url = http_request_info_url;
1211
1212 if (!original_url.SchemeIs(url::kHttpsScheme)) {
1213 return AlternativeServiceInfo();
1214 }
1215
1216 HttpServerProperties& http_server_properties =
1217 *session_->http_server_properties();
1218 const AlternativeServiceInfoVector alternative_service_info_vector =
1219 http_server_properties.GetAlternativeServiceInfos(
1220 url::SchemeHostPort(original_url),
1221 request_info.network_anonymization_key);
1222 if (alternative_service_info_vector.empty()) {
1223 return AlternativeServiceInfo();
1224 }
1225
1226 bool quic_advertised = false;
1227 bool quic_all_broken = true;
1228
1229 // First alternative service that is not marked as broken.
1230 AlternativeServiceInfo first_alternative_service_info;
1231
1232 bool is_any_broken = false;
1233 for (const AlternativeServiceInfo& alternative_service_info :
1234 alternative_service_info_vector) {
1235 DCHECK(IsAlternateProtocolValid(alternative_service_info.protocol()));
1236 if (!quic_advertised && alternative_service_info.protocol() == kProtoQUIC) {
1237 quic_advertised = true;
1238 }
1239 const bool is_broken = http_server_properties.IsAlternativeServiceBroken(
1240 alternative_service_info.alternative_service(),
1241 request_info.network_anonymization_key);
1242 net_log_.AddEvent(
1243 NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_ALT_SVC_FOUND, [&] {
1244 return NetLogAltSvcParams(&alternative_service_info, is_broken);
1245 });
1246 if (is_broken) {
1247 if (!is_any_broken) {
1248 // Only log the broken alternative service once per request.
1249 is_any_broken = true;
1250 HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN,
1251 HasGoogleHost(original_url));
1252 }
1253 continue;
1254 }
1255
1256 // Some shared unix systems may have user home directories (like
1257 // http://foo.com/~mike) which allow users to emit headers. This is a bad
1258 // idea already, but with Alternate-Protocol, it provides the ability for a
1259 // single user on a multi-user system to hijack the alternate protocol.
1260 // These systems also enforce ports <1024 as restricted ports. So don't
1261 // allow protocol upgrades to user-controllable ports.
1262 const int kUnrestrictedPort = 1024;
1263 if (!session_->params().enable_user_alternate_protocol_ports &&
1264 (alternative_service_info.alternative_service().port >=
1265 kUnrestrictedPort &&
1266 original_url.EffectiveIntPort() < kUnrestrictedPort)) {
1267 continue;
1268 }
1269
1270 if (alternative_service_info.protocol() == kProtoHTTP2) {
1271 if (!session_->params().enable_http2_alternative_service) {
1272 continue;
1273 }
1274
1275 // Cache this entry if we don't have a non-broken Alt-Svc yet.
1276 if (first_alternative_service_info.protocol() == kProtoUnknown) {
1277 first_alternative_service_info = alternative_service_info;
1278 }
1279 continue;
1280 }
1281
1282 DCHECK_EQ(kProtoQUIC, alternative_service_info.protocol());
1283 quic_all_broken = false;
1284 if (!session_->IsQuicEnabled()) {
1285 continue;
1286 }
1287
1288 if (!original_url.SchemeIs(url::kHttpsScheme)) {
1289 continue;
1290 }
1291
1292 // If there is no QUIC version in the advertised versions that is
1293 // supported, ignore this entry.
1294 if (SelectQuicVersion(alternative_service_info.advertised_versions()) ==
1295 quic::ParsedQuicVersion::Unsupported()) {
1296 continue;
1297 }
1298
1299 // Check whether there is an existing QUIC session to use for this origin.
1300 GURL mapped_origin = original_url;
1301 RewriteUrlWithHostMappingRules(mapped_origin);
1302 QuicSessionKey session_key(
1303 HostPortPair::FromURL(mapped_origin), request_info.privacy_mode,
1304 proxy_info_.proxy_chain(), SessionUsage::kDestination,
1305 request_info.socket_tag, request_info.network_anonymization_key,
1306 request_info.secure_dns_policy, /*require_dns_https_alpn=*/false);
1307
1308 GURL destination = CreateAltSvcUrl(
1309 original_url, alternative_service_info.host_port_pair());
1310 if (session_key.host() != destination.host_piece() &&
1311 !session_->context().quic_context->params()->allow_remote_alt_svc) {
1312 continue;
1313 }
1314 RewriteUrlWithHostMappingRules(destination);
1315
1316 if (session_->quic_session_pool()->CanUseExistingSession(
1317 session_key, url::SchemeHostPort(destination))) {
1318 return alternative_service_info;
1319 }
1320
1321 if (!IsQuicAllowedForHost(destination.host())) {
1322 continue;
1323 }
1324
1325 // Cache this entry if we don't have a non-broken Alt-Svc yet.
1326 if (first_alternative_service_info.protocol() == kProtoUnknown) {
1327 first_alternative_service_info = alternative_service_info;
1328 }
1329 }
1330
1331 // Ask delegate to mark QUIC as broken for the origin.
1332 if (quic_advertised && quic_all_broken && delegate != nullptr) {
1333 delegate->OnQuicBroken();
1334 }
1335
1336 return first_alternative_service_info;
1337 }
1338
SelectQuicVersion(const quic::ParsedQuicVersionVector & advertised_versions)1339 quic::ParsedQuicVersion HttpStreamFactory::JobController::SelectQuicVersion(
1340 const quic::ParsedQuicVersionVector& advertised_versions) {
1341 const quic::ParsedQuicVersionVector& supported_versions =
1342 session_->context().quic_context->params()->supported_versions;
1343 if (advertised_versions.empty()) {
1344 return supported_versions[0];
1345 }
1346
1347 for (const quic::ParsedQuicVersion& advertised : advertised_versions) {
1348 for (const quic::ParsedQuicVersion& supported : supported_versions) {
1349 if (supported == advertised) {
1350 DCHECK_NE(quic::ParsedQuicVersion::Unsupported(), supported);
1351 return supported;
1352 }
1353 }
1354 }
1355
1356 return quic::ParsedQuicVersion::Unsupported();
1357 }
1358
ReportAlternateProtocolUsage(AlternateProtocolUsage alternate_protocol_usage,bool is_google_host) const1359 void HttpStreamFactory::JobController::ReportAlternateProtocolUsage(
1360 AlternateProtocolUsage alternate_protocol_usage,
1361 bool is_google_host) const {
1362 DCHECK_LT(alternate_protocol_usage, ALTERNATE_PROTOCOL_USAGE_MAX);
1363 HistogramAlternateProtocolUsage(alternate_protocol_usage, is_google_host);
1364 }
1365
IsJobOrphaned(Job * job) const1366 bool HttpStreamFactory::JobController::IsJobOrphaned(Job* job) const {
1367 return !request_ || (job_bound_ && bound_job_ != job);
1368 }
1369
1370 AlternateProtocolUsage
CalculateAlternateProtocolUsage(Job * job) const1371 HttpStreamFactory::JobController::CalculateAlternateProtocolUsage(
1372 Job* job) const {
1373 if ((main_job_ && alternative_job_) || dns_alpn_h3_job_) {
1374 if (job == main_job_.get()) {
1375 return ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE;
1376 }
1377 if (job == alternative_job_.get()) {
1378 if (job->using_existing_quic_session()) {
1379 return ALTERNATE_PROTOCOL_USAGE_NO_RACE;
1380 }
1381 return ALTERNATE_PROTOCOL_USAGE_WON_RACE;
1382 }
1383 if (job == dns_alpn_h3_job_.get()) {
1384 if (job->using_existing_quic_session()) {
1385 return ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_WITHOUT_RACE;
1386 }
1387 return ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE;
1388 }
1389 }
1390 // TODO(crbug.com/1345536): Implement better logic to support uncovered cases.
1391 return ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON;
1392 }
1393
ReconsiderProxyAfterError(Job * job,int error)1394 int HttpStreamFactory::JobController::ReconsiderProxyAfterError(Job* job,
1395 int error) {
1396 // ReconsiderProxyAfterError() should only be called when the last job fails.
1397 DCHECK_EQ(1, GetJobCount());
1398 DCHECK(!proxy_resolve_request_);
1399 DCHECK(session_);
1400
1401 if (!job->should_reconsider_proxy()) {
1402 return error;
1403 }
1404
1405 if (request_info_.load_flags & LOAD_BYPASS_PROXY) {
1406 return error;
1407 }
1408
1409 // Clear client certificates for all proxies in the chain.
1410 // TODO(https://crbug.com/1491092): client certificates for multi-proxy
1411 // chains are not yet supported, and this is only tested with single-proxy
1412 // chains.
1413 for (auto& proxy_server : proxy_info_.proxy_chain().proxy_servers()) {
1414 if (proxy_server.is_secure_http_like()) {
1415 session_->ssl_client_context()->ClearClientCertificate(
1416 proxy_server.host_port_pair());
1417 }
1418 }
1419
1420 if (!proxy_info_.Fallback(error, net_log_)) {
1421 // If there is no more proxy to fallback to, fail the transaction
1422 // with the last connection error we got.
1423 return error;
1424 }
1425
1426 // Abandon all Jobs and start over.
1427 job_bound_ = false;
1428 bound_job_ = nullptr;
1429 dns_alpn_h3_job_.reset();
1430 alternative_job_.reset();
1431 main_job_.reset();
1432 ResetErrorStatusForJobs();
1433 // Also resets states that related to the old main job. In particular,
1434 // cancels |resume_main_job_callback_| so there won't be any delayed
1435 // ResumeMainJob() left in the task queue.
1436 resume_main_job_callback_.Cancel();
1437 main_job_is_resumed_ = false;
1438 main_job_is_blocked_ = false;
1439
1440 next_state_ = STATE_RESOLVE_PROXY_COMPLETE;
1441 return OK;
1442 }
1443
IsQuicAllowedForHost(const std::string & host)1444 bool HttpStreamFactory::JobController::IsQuicAllowedForHost(
1445 const std::string& host) {
1446 const base::flat_set<std::string>& host_allowlist =
1447 session_->params().quic_host_allowlist;
1448 if (host_allowlist.empty()) {
1449 return true;
1450 }
1451
1452 std::string lowered_host = base::ToLowerASCII(host);
1453 return base::Contains(host_allowlist, lowered_host);
1454 }
1455
1456 } // namespace net
1457