xref: /aosp_15_r20/external/cronet/net/socket/transport_connect_job.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 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/socket/transport_connect_job.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/check_op.h"
11 #include "base/feature_list.h"
12 #include "base/functional/bind.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/notreached.h"
17 #include "base/task/single_thread_task_runner.h"
18 #include "base/time/time.h"
19 #include "net/base/features.h"
20 #include "net/base/host_port_pair.h"
21 #include "net/base/ip_endpoint.h"
22 #include "net/base/net_errors.h"
23 #include "net/base/trace_constants.h"
24 #include "net/base/tracing.h"
25 #include "net/dns/public/host_resolver_results.h"
26 #include "net/dns/public/secure_dns_policy.h"
27 #include "net/log/net_log_event_type.h"
28 #include "net/socket/socket_tag.h"
29 #include "net/socket/transport_connect_sub_job.h"
30 #include "third_party/abseil-cpp/absl/types/variant.h"
31 #include "url/scheme_host_port.h"
32 #include "url/url_constants.h"
33 
34 namespace net {
35 
36 namespace {
37 
38 // TODO(crbug.com/1206799): Delete once endpoint usage is converted to using
39 // url::SchemeHostPort when available.
ToLegacyDestinationEndpoint(const TransportSocketParams::Endpoint & endpoint)40 HostPortPair ToLegacyDestinationEndpoint(
41     const TransportSocketParams::Endpoint& endpoint) {
42   if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
43     return HostPortPair::FromSchemeHostPort(
44         absl::get<url::SchemeHostPort>(endpoint));
45   }
46 
47   DCHECK(absl::holds_alternative<HostPortPair>(endpoint));
48   return absl::get<HostPortPair>(endpoint);
49 }
50 
51 }  // namespace
52 
TransportSocketParams(Endpoint destination,NetworkAnonymizationKey network_anonymization_key,SecureDnsPolicy secure_dns_policy,OnHostResolutionCallback host_resolution_callback,base::flat_set<std::string> supported_alpns)53 TransportSocketParams::TransportSocketParams(
54     Endpoint destination,
55     NetworkAnonymizationKey network_anonymization_key,
56     SecureDnsPolicy secure_dns_policy,
57     OnHostResolutionCallback host_resolution_callback,
58     base::flat_set<std::string> supported_alpns)
59     : destination_(std::move(destination)),
60       network_anonymization_key_(std::move(network_anonymization_key)),
61       secure_dns_policy_(secure_dns_policy),
62       host_resolution_callback_(std::move(host_resolution_callback)),
63       supported_alpns_(std::move(supported_alpns)) {
64 #if DCHECK_IS_ON()
65   auto* scheme_host_port = absl::get_if<url::SchemeHostPort>(&destination_);
66   if (scheme_host_port) {
67     if (scheme_host_port->scheme() == url::kHttpsScheme) {
68       // HTTPS destinations will, when passed to the DNS resolver, return
69       // SVCB/HTTPS-based routes. Those routes require ALPN protocols to
70       // evaluate. If there are none, `IsEndpointResultUsable` will correctly
71       // skip each route, but it doesn't make sense to make a DNS query if we
72       // can't handle the result.
73       DCHECK(!supported_alpns_.empty());
74     } else if (scheme_host_port->scheme() == url::kHttpScheme) {
75       // HTTP (not HTTPS) does not currently define ALPN protocols, so the list
76       // should be empty. This means `IsEndpointResultUsable` will skip any
77       // SVCB-based routes. HTTP also has no SVCB mapping, so `HostResolver`
78       // will never return them anyway.
79       //
80       // `HostResolver` will still query SVCB (rather, HTTPS) records for the
81       // corresponding HTTPS URL to implement an upgrade flow (section 9.5 of
82       // draft-ietf-dnsop-svcb-https-08), but this will result in DNS resolution
83       // failing with `ERR_DNS_NAME_HTTPS_ONLY`, not SVCB-based routes.
84       DCHECK(supported_alpns_.empty());
85     }
86   }
87 #endif
88 }
89 
90 TransportSocketParams::~TransportSocketParams() = default;
91 
Create(RequestPriority priority,const SocketTag & socket_tag,const CommonConnectJobParams * common_connect_job_params,const scoped_refptr<TransportSocketParams> & params,Delegate * delegate,const NetLogWithSource * net_log)92 std::unique_ptr<TransportConnectJob> TransportConnectJob::Factory::Create(
93     RequestPriority priority,
94     const SocketTag& socket_tag,
95     const CommonConnectJobParams* common_connect_job_params,
96     const scoped_refptr<TransportSocketParams>& params,
97     Delegate* delegate,
98     const NetLogWithSource* net_log) {
99   return std::make_unique<TransportConnectJob>(priority, socket_tag,
100                                                common_connect_job_params,
101                                                params, delegate, net_log);
102 }
103 
EndpointResultOverride(HostResolverEndpointResult result,std::set<std::string> dns_aliases)104 TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
105     HostResolverEndpointResult result,
106     std::set<std::string> dns_aliases)
107     : result(std::move(result)), dns_aliases(std::move(dns_aliases)) {}
108 TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
109     EndpointResultOverride&&) = default;
110 TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
111     const EndpointResultOverride&) = default;
112 TransportConnectJob::EndpointResultOverride::~EndpointResultOverride() =
113     default;
114 
TransportConnectJob(RequestPriority priority,const SocketTag & socket_tag,const CommonConnectJobParams * common_connect_job_params,const scoped_refptr<TransportSocketParams> & params,Delegate * delegate,const NetLogWithSource * net_log,std::optional<EndpointResultOverride> endpoint_result_override)115 TransportConnectJob::TransportConnectJob(
116     RequestPriority priority,
117     const SocketTag& socket_tag,
118     const CommonConnectJobParams* common_connect_job_params,
119     const scoped_refptr<TransportSocketParams>& params,
120     Delegate* delegate,
121     const NetLogWithSource* net_log,
122     std::optional<EndpointResultOverride> endpoint_result_override)
123     : ConnectJob(priority,
124                  socket_tag,
125                  ConnectionTimeout(),
126                  common_connect_job_params,
127                  delegate,
128                  net_log,
129                  NetLogSourceType::TRANSPORT_CONNECT_JOB,
130                  NetLogEventType::TRANSPORT_CONNECT_JOB_CONNECT),
131       params_(params) {
132   if (endpoint_result_override) {
133     has_dns_override_ = true;
134     endpoint_results_ = {std::move(endpoint_result_override->result)};
135     dns_aliases_ = std::move(endpoint_result_override->dns_aliases);
136     DCHECK(!endpoint_results_.front().ip_endpoints.empty());
137     DCHECK(IsEndpointResultUsable(endpoint_results_.front(),
138                                   IsSvcbOptional(endpoint_results_)));
139   }
140 }
141 
142 // We don't worry about cancelling the host resolution and TCP connect, since
143 // ~HostResolver::Request and ~TransportConnectSubJob will take care of it.
144 TransportConnectJob::~TransportConnectJob() = default;
145 
GetLoadState() const146 LoadState TransportConnectJob::GetLoadState() const {
147   switch (next_state_) {
148     case STATE_RESOLVE_HOST:
149     case STATE_RESOLVE_HOST_COMPLETE:
150     case STATE_RESOLVE_HOST_CALLBACK_COMPLETE:
151       return LOAD_STATE_RESOLVING_HOST;
152     case STATE_TRANSPORT_CONNECT:
153     case STATE_TRANSPORT_CONNECT_COMPLETE: {
154       LoadState load_state = LOAD_STATE_IDLE;
155       if (ipv6_job_ && ipv6_job_->started()) {
156         load_state = ipv6_job_->GetLoadState();
157       }
158       // This method should return LOAD_STATE_CONNECTING in preference to
159       // LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET when possible because "waiting
160       // for available socket" implies that nothing is happening.
161       if (ipv4_job_ && ipv4_job_->started() &&
162           load_state != LOAD_STATE_CONNECTING) {
163         load_state = ipv4_job_->GetLoadState();
164       }
165       return load_state;
166     }
167     case STATE_NONE:
168       return LOAD_STATE_IDLE;
169   }
170 }
171 
HasEstablishedConnection() const172 bool TransportConnectJob::HasEstablishedConnection() const {
173   // No need to ever return true, since NotifyComplete() is called as soon as a
174   // connection is established.
175   return false;
176 }
177 
GetConnectionAttempts() const178 ConnectionAttempts TransportConnectJob::GetConnectionAttempts() const {
179   return connection_attempts_;
180 }
181 
GetResolveErrorInfo() const182 ResolveErrorInfo TransportConnectJob::GetResolveErrorInfo() const {
183   return resolve_error_info_;
184 }
185 
186 std::optional<HostResolverEndpointResult>
GetHostResolverEndpointResult() const187 TransportConnectJob::GetHostResolverEndpointResult() const {
188   CHECK_LT(current_endpoint_result_, endpoint_results_.size());
189   return endpoint_results_[current_endpoint_result_];
190 }
191 
ConnectionTimeout()192 base::TimeDelta TransportConnectJob::ConnectionTimeout() {
193   // TODO(eroman): The use of this constant needs to be re-evaluated. The time
194   // needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since
195   // the address list may contain many alternatives, and most of those may
196   // timeout. Even worse, the per-connect timeout threshold varies greatly
197   // between systems (anywhere from 20 seconds to 190 seconds).
198   // See comment #12 at http://crbug.com/23364 for specifics.
199   return base::Minutes(4);
200 }
201 
OnIOComplete(int result)202 void TransportConnectJob::OnIOComplete(int result) {
203   result = DoLoop(result);
204   if (result != ERR_IO_PENDING)
205     NotifyDelegateOfCompletion(result);  // Deletes |this|
206 }
207 
DoLoop(int result)208 int TransportConnectJob::DoLoop(int result) {
209   DCHECK_NE(next_state_, STATE_NONE);
210 
211   int rv = result;
212   do {
213     State state = next_state_;
214     next_state_ = STATE_NONE;
215     switch (state) {
216       case STATE_RESOLVE_HOST:
217         DCHECK_EQ(OK, rv);
218         rv = DoResolveHost();
219         break;
220       case STATE_RESOLVE_HOST_COMPLETE:
221         rv = DoResolveHostComplete(rv);
222         break;
223       case STATE_RESOLVE_HOST_CALLBACK_COMPLETE:
224         DCHECK_EQ(OK, rv);
225         rv = DoResolveHostCallbackComplete();
226         break;
227       case STATE_TRANSPORT_CONNECT:
228         DCHECK_EQ(OK, rv);
229         rv = DoTransportConnect();
230         break;
231       case STATE_TRANSPORT_CONNECT_COMPLETE:
232         rv = DoTransportConnectComplete(rv);
233         break;
234       default:
235         NOTREACHED();
236         rv = ERR_FAILED;
237         break;
238     }
239   } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
240 
241   return rv;
242 }
243 
DoResolveHost()244 int TransportConnectJob::DoResolveHost() {
245   connect_timing_.domain_lookup_start = base::TimeTicks::Now();
246 
247   if (has_dns_override_) {
248     DCHECK_EQ(1u, endpoint_results_.size());
249     connect_timing_.domain_lookup_end = connect_timing_.domain_lookup_start;
250     next_state_ = STATE_TRANSPORT_CONNECT;
251     return OK;
252   }
253 
254   next_state_ = STATE_RESOLVE_HOST_COMPLETE;
255 
256   HostResolver::ResolveHostParameters parameters;
257   parameters.initial_priority = priority();
258   parameters.secure_dns_policy = params_->secure_dns_policy();
259   if (absl::holds_alternative<url::SchemeHostPort>(params_->destination())) {
260     request_ = host_resolver()->CreateRequest(
261         absl::get<url::SchemeHostPort>(params_->destination()),
262         params_->network_anonymization_key(), net_log(), parameters);
263   } else {
264     request_ = host_resolver()->CreateRequest(
265         absl::get<HostPortPair>(params_->destination()),
266         params_->network_anonymization_key(), net_log(), parameters);
267   }
268 
269   return request_->Start(base::BindOnce(&TransportConnectJob::OnIOComplete,
270                                         base::Unretained(this)));
271 }
272 
DoResolveHostComplete(int result)273 int TransportConnectJob::DoResolveHostComplete(int result) {
274   TRACE_EVENT0(NetTracingCategory(),
275                "TransportConnectJob::DoResolveHostComplete");
276   connect_timing_.domain_lookup_end = base::TimeTicks::Now();
277   // Overwrite connection start time, since for connections that do not go
278   // through proxies, |connect_start| should not include dns lookup time.
279   connect_timing_.connect_start = connect_timing_.domain_lookup_end;
280   resolve_error_info_ = request_->GetResolveErrorInfo();
281 
282   if (result != OK) {
283     // If hostname resolution failed, record an empty endpoint and the result.
284     connection_attempts_.push_back(ConnectionAttempt(IPEndPoint(), result));
285     return result;
286   }
287 
288   DCHECK(request_->GetAddressResults());
289   DCHECK(request_->GetDnsAliasResults());
290   DCHECK(request_->GetEndpointResults());
291 
292   // Invoke callback.  If it indicates |this| may be slated for deletion, then
293   // only continue after a PostTask.
294   next_state_ = STATE_RESOLVE_HOST_CALLBACK_COMPLETE;
295   if (!params_->host_resolution_callback().is_null()) {
296     OnHostResolutionCallbackResult callback_result =
297         params_->host_resolution_callback().Run(
298             ToLegacyDestinationEndpoint(params_->destination()),
299             *request_->GetEndpointResults(), *request_->GetDnsAliasResults());
300     if (callback_result == OnHostResolutionCallbackResult::kMayBeDeletedAsync) {
301       base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
302           FROM_HERE, base::BindOnce(&TransportConnectJob::OnIOComplete,
303                                     weak_ptr_factory_.GetWeakPtr(), OK));
304       return ERR_IO_PENDING;
305     }
306   }
307 
308   return result;
309 }
310 
DoResolveHostCallbackComplete()311 int TransportConnectJob::DoResolveHostCallbackComplete() {
312   const auto& unfiltered_results = *request_->GetEndpointResults();
313   bool svcb_optional = IsSvcbOptional(unfiltered_results);
314   std::set<IPEndPoint> ip_endpoints_seen;
315   for (const auto& result : unfiltered_results) {
316     if (!IsEndpointResultUsable(result, svcb_optional)) {
317       continue;
318     }
319     // The TCP connect itself does not depend on any metadata, so we can dedup
320     // by IP endpoint. In particular, the fallback A/AAAA route will often use
321     // the same IP endpoints as the HTTPS route. If they do not work for one
322     // route, there is no use in trying a second time.
323     std::vector<IPEndPoint> ip_endpoints;
324     for (const auto& ip_endpoint : result.ip_endpoints) {
325       auto [iter, inserted] = ip_endpoints_seen.insert(ip_endpoint);
326       if (inserted) {
327         ip_endpoints.push_back(ip_endpoint);
328       }
329     }
330     if (!ip_endpoints.empty()) {
331       HostResolverEndpointResult new_result;
332       new_result.ip_endpoints = std::move(ip_endpoints);
333       new_result.metadata = result.metadata;
334       endpoint_results_.push_back(std::move(new_result));
335     }
336   }
337   dns_aliases_ = *request_->GetDnsAliasResults();
338 
339   // No need to retain `request_` beyond this point.
340   request_.reset();
341 
342   if (endpoint_results_.empty()) {
343     // In the general case, DNS may successfully return routes, but none are
344     // compatible with this `ConnectJob`. This should not happen for HTTPS
345     // because `HostResolver` will reject SVCB/HTTPS sets that do not cover the
346     // default "http/1.1" ALPN.
347     return ERR_NAME_NOT_RESOLVED;
348   }
349 
350   next_state_ = STATE_TRANSPORT_CONNECT;
351   return OK;
352 }
353 
DoTransportConnect()354 int TransportConnectJob::DoTransportConnect() {
355   next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
356 
357   const HostResolverEndpointResult& endpoint =
358       GetEndpointResultForCurrentSubJobs();
359   std::vector<IPEndPoint> ipv4_addresses, ipv6_addresses;
360   for (const auto& ip_endpoint : endpoint.ip_endpoints) {
361     switch (ip_endpoint.GetFamily()) {
362       case ADDRESS_FAMILY_IPV4:
363         ipv4_addresses.push_back(ip_endpoint);
364         break;
365 
366       case ADDRESS_FAMILY_IPV6:
367         ipv6_addresses.push_back(ip_endpoint);
368         break;
369 
370       default:
371         DVLOG(1) << "Unexpected ADDRESS_FAMILY: " << ip_endpoint.GetFamily();
372         break;
373     }
374   }
375 
376   if (!ipv4_addresses.empty()) {
377     ipv4_job_ = std::make_unique<TransportConnectSubJob>(
378         std::move(ipv4_addresses), this, SUB_JOB_IPV4);
379   }
380 
381   if (!ipv6_addresses.empty()) {
382     ipv6_job_ = std::make_unique<TransportConnectSubJob>(
383         std::move(ipv6_addresses), this, SUB_JOB_IPV6);
384     int result = ipv6_job_->Start();
385     if (result != ERR_IO_PENDING)
386       return HandleSubJobComplete(result, ipv6_job_.get());
387     if (ipv4_job_) {
388       // This use of base::Unretained is safe because |fallback_timer_| is
389       // owned by this object.
390       fallback_timer_.Start(
391           FROM_HERE, kIPv6FallbackTime,
392           base::BindOnce(&TransportConnectJob::StartIPv4JobAsync,
393                          base::Unretained(this)));
394     }
395     return ERR_IO_PENDING;
396   }
397 
398   DCHECK(!ipv6_job_);
399   DCHECK(ipv4_job_);
400   int result = ipv4_job_->Start();
401   if (result != ERR_IO_PENDING)
402     return HandleSubJobComplete(result, ipv4_job_.get());
403   return ERR_IO_PENDING;
404 }
405 
DoTransportConnectComplete(int result)406 int TransportConnectJob::DoTransportConnectComplete(int result) {
407   // Make sure nothing else calls back into this object.
408   ipv4_job_.reset();
409   ipv6_job_.reset();
410   fallback_timer_.Stop();
411 
412   if (result == OK) {
413     DCHECK(!connect_timing_.connect_start.is_null());
414     DCHECK(!connect_timing_.domain_lookup_start.is_null());
415     // `HandleSubJobComplete` should have called `SetSocket`.
416     DCHECK(socket());
417     base::TimeTicks now = base::TimeTicks::Now();
418     base::TimeDelta total_duration = now - connect_timing_.domain_lookup_start;
419     UMA_HISTOGRAM_CUSTOM_TIMES("Net.DNS_Resolution_And_TCP_Connection_Latency2",
420                                total_duration, base::Milliseconds(1),
421                                base::Minutes(10), 100);
422 
423     base::TimeDelta connect_duration = now - connect_timing_.connect_start;
424     UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", connect_duration,
425                                base::Milliseconds(1), base::Minutes(10), 100);
426   } else {
427     // Don't try the next route if entering suspend mode.
428     if (result != ERR_NETWORK_IO_SUSPENDED) {
429       // If there is another endpoint available, try it.
430       current_endpoint_result_++;
431       if (current_endpoint_result_ < endpoint_results_.size()) {
432         next_state_ = STATE_TRANSPORT_CONNECT;
433         result = OK;
434       }
435     }
436   }
437 
438   return result;
439 }
440 
HandleSubJobComplete(int result,TransportConnectSubJob * job)441 int TransportConnectJob::HandleSubJobComplete(int result,
442                                               TransportConnectSubJob* job) {
443   DCHECK_NE(result, ERR_IO_PENDING);
444   if (result == OK) {
445     SetSocket(job->PassSocket(), dns_aliases_);
446     return result;
447   }
448 
449   if (result == ERR_NETWORK_IO_SUSPENDED) {
450     // Don't try other jobs if entering suspend mode.
451     return result;
452   }
453 
454   switch (job->type()) {
455     case SUB_JOB_IPV4:
456       ipv4_job_.reset();
457       break;
458 
459     case SUB_JOB_IPV6:
460       ipv6_job_.reset();
461       // Start the other job, rather than wait for the fallback timer.
462       if (ipv4_job_ && !ipv4_job_->started()) {
463         fallback_timer_.Stop();
464         result = ipv4_job_->Start();
465         if (result != ERR_IO_PENDING) {
466           return HandleSubJobComplete(result, ipv4_job_.get());
467         }
468       }
469       break;
470   }
471 
472   if (ipv4_job_ || ipv6_job_) {
473     // Wait for the other job to complete, rather than reporting |result|.
474     return ERR_IO_PENDING;
475   }
476 
477   return result;
478 }
479 
OnSubJobComplete(int result,TransportConnectSubJob * job)480 void TransportConnectJob::OnSubJobComplete(int result,
481                                            TransportConnectSubJob* job) {
482   result = HandleSubJobComplete(result, job);
483   if (result != ERR_IO_PENDING) {
484     OnIOComplete(result);
485   }
486 }
487 
StartIPv4JobAsync()488 void TransportConnectJob::StartIPv4JobAsync() {
489   DCHECK(ipv4_job_);
490   net_log().AddEvent(NetLogEventType::TRANSPORT_CONNECT_JOB_IPV6_FALLBACK);
491   int result = ipv4_job_->Start();
492   if (result != ERR_IO_PENDING)
493     OnSubJobComplete(result, ipv4_job_.get());
494 }
495 
ConnectInternal()496 int TransportConnectJob::ConnectInternal() {
497   next_state_ = STATE_RESOLVE_HOST;
498   return DoLoop(OK);
499 }
500 
ChangePriorityInternal(RequestPriority priority)501 void TransportConnectJob::ChangePriorityInternal(RequestPriority priority) {
502   if (next_state_ == STATE_RESOLVE_HOST_COMPLETE) {
503     DCHECK(request_);
504     // Change the request priority in the host resolver.
505     request_->ChangeRequestPriority(priority);
506   }
507 }
508 
IsSvcbOptional(base::span<const HostResolverEndpointResult> results) const509 bool TransportConnectJob::IsSvcbOptional(
510     base::span<const HostResolverEndpointResult> results) const {
511   // If SVCB/HTTPS resolution succeeded, the client supports ECH, and all routes
512   // support ECH, disable the A/AAAA fallback. See Section 10.1 of
513   // draft-ietf-dnsop-svcb-https-08.
514 
515   auto* scheme_host_port =
516       absl::get_if<url::SchemeHostPort>(&params_->destination());
517   if (!scheme_host_port || scheme_host_port->scheme() != url::kHttpsScheme) {
518     return true;  // This is not a SVCB-capable request at all.
519   }
520 
521   if (!common_connect_job_params()->ssl_client_context ||
522       !common_connect_job_params()->ssl_client_context->config().ech_enabled) {
523     return true;  // ECH is not supported for this request.
524   }
525 
526   return !HostResolver::AllProtocolEndpointsHaveEch(results);
527 }
528 
IsEndpointResultUsable(const HostResolverEndpointResult & result,bool svcb_optional) const529 bool TransportConnectJob::IsEndpointResultUsable(
530     const HostResolverEndpointResult& result,
531     bool svcb_optional) const {
532   // A `HostResolverEndpointResult` with no ALPN protocols is the fallback
533   // A/AAAA route. This is always compatible. We assume the ALPN-less option is
534   // TCP-based.
535   if (result.metadata.supported_protocol_alpns.empty()) {
536     // See draft-ietf-dnsop-svcb-https-08, Section 3.
537     return svcb_optional;
538   }
539 
540   // See draft-ietf-dnsop-svcb-https-08, Section 7.1.2. Routes are usable if
541   // there is an overlap between the route's ALPN protocols and the configured
542   // ones. This ensures we do not, e.g., connect to a QUIC-only route with TCP.
543   // Note that, if `params_` did not specify any ALPN protocols, no
544   // SVCB/HTTPS-based routes will match and we will effectively ignore all but
545   // plain A/AAAA routes.
546   for (const auto& alpn : result.metadata.supported_protocol_alpns) {
547     if (params_->supported_alpns().contains(alpn)) {
548       return true;
549     }
550   }
551   return false;
552 }
553 
554 const HostResolverEndpointResult&
GetEndpointResultForCurrentSubJobs() const555 TransportConnectJob::GetEndpointResultForCurrentSubJobs() const {
556   CHECK_LT(current_endpoint_result_, endpoint_results_.size());
557   return endpoint_results_[current_endpoint_result_];
558 }
559 
560 }  // namespace net
561