563 lines
20 KiB
C++
563 lines
20 KiB
C++
// Copyright 2012 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/socket/transport_connect_job.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/feature_list.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/location.h"
|
|
#include "base/logging.h"
|
|
#include "base/metrics/histogram_macros.h"
|
|
#include "base/notreached.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/time/time.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/ip_endpoint.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/trace_constants.h"
|
|
#include "net/base/tracing.h"
|
|
#include "net/dns/public/host_resolver_results.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/log/net_log_event_type.h"
|
|
#include "net/socket/socket_tag.h"
|
|
#include "net/socket/transport_connect_sub_job.h"
|
|
#include "third_party/abseil-cpp/absl/types/variant.h"
|
|
#include "url/scheme_host_port.h"
|
|
#include "url/url_constants.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
// TODO(crbug.com/1206799): Delete once endpoint usage is converted to using
|
|
// url::SchemeHostPort when available.
|
|
HostPortPair ToLegacyDestinationEndpoint(
|
|
const TransportSocketParams::Endpoint& endpoint) {
|
|
if (absl::holds_alternative<url::SchemeHostPort>(endpoint)) {
|
|
return HostPortPair::FromSchemeHostPort(
|
|
absl::get<url::SchemeHostPort>(endpoint));
|
|
}
|
|
|
|
DCHECK(absl::holds_alternative<HostPortPair>(endpoint));
|
|
return absl::get<HostPortPair>(endpoint);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TransportSocketParams::TransportSocketParams(
|
|
Endpoint destination,
|
|
NetworkAnonymizationKey network_anonymization_key,
|
|
SecureDnsPolicy secure_dns_policy,
|
|
OnHostResolutionCallback host_resolution_callback,
|
|
base::flat_set<std::string> supported_alpns)
|
|
: destination_(std::move(destination)),
|
|
network_anonymization_key_(std::move(network_anonymization_key)),
|
|
secure_dns_policy_(secure_dns_policy),
|
|
host_resolution_callback_(std::move(host_resolution_callback)),
|
|
supported_alpns_(std::move(supported_alpns)) {
|
|
#if DCHECK_IS_ON()
|
|
auto* scheme_host_port = absl::get_if<url::SchemeHostPort>(&destination_);
|
|
if (scheme_host_port) {
|
|
if (scheme_host_port->scheme() == url::kHttpsScheme) {
|
|
// HTTPS destinations will, when passed to the DNS resolver, return
|
|
// SVCB/HTTPS-based routes. Those routes require ALPN protocols to
|
|
// evaluate. If there are none, `IsEndpointResultUsable` will correctly
|
|
// skip each route, but it doesn't make sense to make a DNS query if we
|
|
// can't handle the result.
|
|
DCHECK(!supported_alpns_.empty());
|
|
} else if (scheme_host_port->scheme() == url::kHttpScheme) {
|
|
// HTTP (not HTTPS) does not currently define ALPN protocols, so the list
|
|
// should be empty. This means `IsEndpointResultUsable` will skip any
|
|
// SVCB-based routes. HTTP also has no SVCB mapping, so `HostResolver`
|
|
// will never return them anyway.
|
|
//
|
|
// `HostResolver` will still query SVCB (rather, HTTPS) records for the
|
|
// corresponding HTTPS URL to implement an upgrade flow (section 9.5 of
|
|
// draft-ietf-dnsop-svcb-https-08), but this will result in DNS resolution
|
|
// failing with `ERR_DNS_NAME_HTTPS_ONLY`, not SVCB-based routes.
|
|
DCHECK(supported_alpns_.empty());
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
TransportSocketParams::~TransportSocketParams() = default;
|
|
|
|
std::unique_ptr<TransportConnectJob> TransportConnectJob::Factory::Create(
|
|
RequestPriority priority,
|
|
const SocketTag& socket_tag,
|
|
const CommonConnectJobParams* common_connect_job_params,
|
|
const scoped_refptr<TransportSocketParams>& params,
|
|
Delegate* delegate,
|
|
const NetLogWithSource* net_log) {
|
|
return std::make_unique<TransportConnectJob>(priority, socket_tag,
|
|
common_connect_job_params,
|
|
params, delegate, net_log);
|
|
}
|
|
|
|
TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
|
|
HostResolverEndpointResult result,
|
|
std::set<std::string> dns_aliases)
|
|
: result(std::move(result)), dns_aliases(std::move(dns_aliases)) {}
|
|
TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
|
|
EndpointResultOverride&&) = default;
|
|
TransportConnectJob::EndpointResultOverride::EndpointResultOverride(
|
|
const EndpointResultOverride&) = default;
|
|
TransportConnectJob::EndpointResultOverride::~EndpointResultOverride() =
|
|
default;
|
|
|
|
TransportConnectJob::TransportConnectJob(
|
|
RequestPriority priority,
|
|
const SocketTag& socket_tag,
|
|
const CommonConnectJobParams* common_connect_job_params,
|
|
const scoped_refptr<TransportSocketParams>& params,
|
|
Delegate* delegate,
|
|
const NetLogWithSource* net_log,
|
|
absl::optional<EndpointResultOverride> endpoint_result_override)
|
|
: ConnectJob(priority,
|
|
socket_tag,
|
|
ConnectionTimeout(),
|
|
common_connect_job_params,
|
|
delegate,
|
|
net_log,
|
|
NetLogSourceType::TRANSPORT_CONNECT_JOB,
|
|
NetLogEventType::TRANSPORT_CONNECT_JOB_CONNECT),
|
|
params_(params) {
|
|
if (endpoint_result_override) {
|
|
has_dns_override_ = true;
|
|
endpoint_results_ = {std::move(endpoint_result_override->result)};
|
|
dns_aliases_ = std::move(endpoint_result_override->dns_aliases);
|
|
DCHECK(!endpoint_results_.front().ip_endpoints.empty());
|
|
DCHECK(IsEndpointResultUsable(endpoint_results_.front(),
|
|
IsSvcbOptional(endpoint_results_)));
|
|
}
|
|
}
|
|
|
|
// We don't worry about cancelling the host resolution and TCP connect, since
|
|
// ~HostResolver::Request and ~TransportConnectSubJob will take care of it.
|
|
TransportConnectJob::~TransportConnectJob() = default;
|
|
|
|
LoadState TransportConnectJob::GetLoadState() const {
|
|
switch (next_state_) {
|
|
case STATE_RESOLVE_HOST:
|
|
case STATE_RESOLVE_HOST_COMPLETE:
|
|
case STATE_RESOLVE_HOST_CALLBACK_COMPLETE:
|
|
return LOAD_STATE_RESOLVING_HOST;
|
|
case STATE_TRANSPORT_CONNECT:
|
|
case STATE_TRANSPORT_CONNECT_COMPLETE: {
|
|
LoadState load_state = LOAD_STATE_IDLE;
|
|
if (ipv6_job_ && ipv6_job_->started()) {
|
|
load_state = ipv6_job_->GetLoadState();
|
|
}
|
|
// This method should return LOAD_STATE_CONNECTING in preference to
|
|
// LOAD_STATE_WAITING_FOR_AVAILABLE_SOCKET when possible because "waiting
|
|
// for available socket" implies that nothing is happening.
|
|
if (ipv4_job_ && ipv4_job_->started() &&
|
|
load_state != LOAD_STATE_CONNECTING) {
|
|
load_state = ipv4_job_->GetLoadState();
|
|
}
|
|
return load_state;
|
|
}
|
|
case STATE_NONE:
|
|
return LOAD_STATE_IDLE;
|
|
}
|
|
}
|
|
|
|
bool TransportConnectJob::HasEstablishedConnection() const {
|
|
// No need to ever return true, since NotifyComplete() is called as soon as a
|
|
// connection is established.
|
|
return false;
|
|
}
|
|
|
|
ConnectionAttempts TransportConnectJob::GetConnectionAttempts() const {
|
|
return connection_attempts_;
|
|
}
|
|
|
|
ResolveErrorInfo TransportConnectJob::GetResolveErrorInfo() const {
|
|
return resolve_error_info_;
|
|
}
|
|
|
|
absl::optional<HostResolverEndpointResult>
|
|
TransportConnectJob::GetHostResolverEndpointResult() const {
|
|
CHECK_LT(current_endpoint_result_, endpoint_results_.size());
|
|
return endpoint_results_[current_endpoint_result_];
|
|
}
|
|
|
|
base::TimeDelta TransportConnectJob::ConnectionTimeout() {
|
|
// TODO(eroman): The use of this constant needs to be re-evaluated. The time
|
|
// needed for TCPClientSocketXXX::Connect() can be arbitrarily long, since
|
|
// the address list may contain many alternatives, and most of those may
|
|
// timeout. Even worse, the per-connect timeout threshold varies greatly
|
|
// between systems (anywhere from 20 seconds to 190 seconds).
|
|
// See comment #12 at http://crbug.com/23364 for specifics.
|
|
return base::Minutes(4);
|
|
}
|
|
|
|
void TransportConnectJob::OnIOComplete(int result) {
|
|
result = DoLoop(result);
|
|
if (result != ERR_IO_PENDING)
|
|
NotifyDelegateOfCompletion(result); // Deletes |this|
|
|
}
|
|
|
|
int TransportConnectJob::DoLoop(int result) {
|
|
DCHECK_NE(next_state_, STATE_NONE);
|
|
|
|
int rv = result;
|
|
do {
|
|
State state = next_state_;
|
|
next_state_ = STATE_NONE;
|
|
switch (state) {
|
|
case STATE_RESOLVE_HOST:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoResolveHost();
|
|
break;
|
|
case STATE_RESOLVE_HOST_COMPLETE:
|
|
rv = DoResolveHostComplete(rv);
|
|
break;
|
|
case STATE_RESOLVE_HOST_CALLBACK_COMPLETE:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoResolveHostCallbackComplete();
|
|
break;
|
|
case STATE_TRANSPORT_CONNECT:
|
|
DCHECK_EQ(OK, rv);
|
|
rv = DoTransportConnect();
|
|
break;
|
|
case STATE_TRANSPORT_CONNECT_COMPLETE:
|
|
rv = DoTransportConnectComplete(rv);
|
|
break;
|
|
default:
|
|
NOTREACHED();
|
|
rv = ERR_FAILED;
|
|
break;
|
|
}
|
|
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
|
|
|
|
return rv;
|
|
}
|
|
|
|
int TransportConnectJob::DoResolveHost() {
|
|
connect_timing_.domain_lookup_start = base::TimeTicks::Now();
|
|
|
|
if (has_dns_override_) {
|
|
DCHECK_EQ(1u, endpoint_results_.size());
|
|
connect_timing_.domain_lookup_end = connect_timing_.domain_lookup_start;
|
|
next_state_ = STATE_TRANSPORT_CONNECT;
|
|
return OK;
|
|
}
|
|
|
|
next_state_ = STATE_RESOLVE_HOST_COMPLETE;
|
|
|
|
HostResolver::ResolveHostParameters parameters;
|
|
parameters.initial_priority = priority();
|
|
parameters.secure_dns_policy = params_->secure_dns_policy();
|
|
if (absl::holds_alternative<url::SchemeHostPort>(params_->destination())) {
|
|
request_ = host_resolver()->CreateRequest(
|
|
absl::get<url::SchemeHostPort>(params_->destination()),
|
|
params_->network_anonymization_key(), net_log(), parameters);
|
|
} else {
|
|
request_ = host_resolver()->CreateRequest(
|
|
absl::get<HostPortPair>(params_->destination()),
|
|
params_->network_anonymization_key(), net_log(), parameters);
|
|
}
|
|
|
|
return request_->Start(base::BindOnce(&TransportConnectJob::OnIOComplete,
|
|
base::Unretained(this)));
|
|
}
|
|
|
|
int TransportConnectJob::DoResolveHostComplete(int result) {
|
|
TRACE_EVENT0(NetTracingCategory(),
|
|
"TransportConnectJob::DoResolveHostComplete");
|
|
connect_timing_.domain_lookup_end = base::TimeTicks::Now();
|
|
// Overwrite connection start time, since for connections that do not go
|
|
// through proxies, |connect_start| should not include dns lookup time.
|
|
connect_timing_.connect_start = connect_timing_.domain_lookup_end;
|
|
resolve_error_info_ = request_->GetResolveErrorInfo();
|
|
|
|
if (result != OK) {
|
|
// If hostname resolution failed, record an empty endpoint and the result.
|
|
connection_attempts_.push_back(ConnectionAttempt(IPEndPoint(), result));
|
|
return result;
|
|
}
|
|
|
|
DCHECK(request_->GetAddressResults());
|
|
DCHECK(request_->GetDnsAliasResults());
|
|
DCHECK(request_->GetEndpointResults());
|
|
|
|
// Invoke callback. If it indicates |this| may be slated for deletion, then
|
|
// only continue after a PostTask.
|
|
next_state_ = STATE_RESOLVE_HOST_CALLBACK_COMPLETE;
|
|
if (!params_->host_resolution_callback().is_null()) {
|
|
OnHostResolutionCallbackResult callback_result =
|
|
params_->host_resolution_callback().Run(
|
|
ToLegacyDestinationEndpoint(params_->destination()),
|
|
*request_->GetEndpointResults(), *request_->GetDnsAliasResults());
|
|
if (callback_result == OnHostResolutionCallbackResult::kMayBeDeletedAsync) {
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE, base::BindOnce(&TransportConnectJob::OnIOComplete,
|
|
weak_ptr_factory_.GetWeakPtr(), OK));
|
|
return ERR_IO_PENDING;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int TransportConnectJob::DoResolveHostCallbackComplete() {
|
|
const auto& unfiltered_results = *request_->GetEndpointResults();
|
|
bool svcb_optional = IsSvcbOptional(unfiltered_results);
|
|
std::set<IPEndPoint> ip_endpoints_seen;
|
|
for (const auto& result : unfiltered_results) {
|
|
if (!IsEndpointResultUsable(result, svcb_optional)) {
|
|
continue;
|
|
}
|
|
// The TCP connect itself does not depend on any metadata, so we can dedup
|
|
// by IP endpoint. In particular, the fallback A/AAAA route will often use
|
|
// the same IP endpoints as the HTTPS route. If they do not work for one
|
|
// route, there is no use in trying a second time.
|
|
std::vector<IPEndPoint> ip_endpoints;
|
|
for (const auto& ip_endpoint : result.ip_endpoints) {
|
|
auto [iter, inserted] = ip_endpoints_seen.insert(ip_endpoint);
|
|
if (inserted) {
|
|
ip_endpoints.push_back(ip_endpoint);
|
|
}
|
|
}
|
|
if (!ip_endpoints.empty()) {
|
|
HostResolverEndpointResult new_result;
|
|
new_result.ip_endpoints = std::move(ip_endpoints);
|
|
new_result.metadata = result.metadata;
|
|
endpoint_results_.push_back(std::move(new_result));
|
|
}
|
|
}
|
|
dns_aliases_ = *request_->GetDnsAliasResults();
|
|
|
|
// No need to retain `request_` beyond this point.
|
|
request_.reset();
|
|
|
|
if (endpoint_results_.empty()) {
|
|
// In the general case, DNS may successfully return routes, but none are
|
|
// compatible with this `ConnectJob`. This should not happen for HTTPS
|
|
// because `HostResolver` will reject SVCB/HTTPS sets that do not cover the
|
|
// default "http/1.1" ALPN.
|
|
return ERR_NAME_NOT_RESOLVED;
|
|
}
|
|
|
|
next_state_ = STATE_TRANSPORT_CONNECT;
|
|
return OK;
|
|
}
|
|
|
|
int TransportConnectJob::DoTransportConnect() {
|
|
next_state_ = STATE_TRANSPORT_CONNECT_COMPLETE;
|
|
|
|
const HostResolverEndpointResult& endpoint =
|
|
GetEndpointResultForCurrentSubJobs();
|
|
std::vector<IPEndPoint> ipv4_addresses, ipv6_addresses;
|
|
for (const auto& ip_endpoint : endpoint.ip_endpoints) {
|
|
switch (ip_endpoint.GetFamily()) {
|
|
case ADDRESS_FAMILY_IPV4:
|
|
ipv4_addresses.push_back(ip_endpoint);
|
|
break;
|
|
|
|
case ADDRESS_FAMILY_IPV6:
|
|
ipv6_addresses.push_back(ip_endpoint);
|
|
break;
|
|
|
|
default:
|
|
DVLOG(1) << "Unexpected ADDRESS_FAMILY: " << ip_endpoint.GetFamily();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ipv4_addresses.empty()) {
|
|
ipv4_job_ = std::make_unique<TransportConnectSubJob>(
|
|
std::move(ipv4_addresses), this, SUB_JOB_IPV4);
|
|
}
|
|
|
|
if (!ipv6_addresses.empty()) {
|
|
ipv6_job_ = std::make_unique<TransportConnectSubJob>(
|
|
std::move(ipv6_addresses), this, SUB_JOB_IPV6);
|
|
int result = ipv6_job_->Start();
|
|
if (result != ERR_IO_PENDING)
|
|
return HandleSubJobComplete(result, ipv6_job_.get());
|
|
if (ipv4_job_) {
|
|
// This use of base::Unretained is safe because |fallback_timer_| is
|
|
// owned by this object.
|
|
fallback_timer_.Start(
|
|
FROM_HERE, kIPv6FallbackTime,
|
|
base::BindOnce(&TransportConnectJob::StartIPv4JobAsync,
|
|
base::Unretained(this)));
|
|
}
|
|
return ERR_IO_PENDING;
|
|
}
|
|
|
|
DCHECK(!ipv6_job_);
|
|
DCHECK(ipv4_job_);
|
|
int result = ipv4_job_->Start();
|
|
if (result != ERR_IO_PENDING)
|
|
return HandleSubJobComplete(result, ipv4_job_.get());
|
|
return ERR_IO_PENDING;
|
|
}
|
|
|
|
int TransportConnectJob::DoTransportConnectComplete(int result) {
|
|
// Make sure nothing else calls back into this object.
|
|
ipv4_job_.reset();
|
|
ipv6_job_.reset();
|
|
fallback_timer_.Stop();
|
|
|
|
if (result == OK) {
|
|
DCHECK(!connect_timing_.connect_start.is_null());
|
|
DCHECK(!connect_timing_.domain_lookup_start.is_null());
|
|
// `HandleSubJobComplete` should have called `SetSocket`.
|
|
DCHECK(socket());
|
|
base::TimeTicks now = base::TimeTicks::Now();
|
|
base::TimeDelta total_duration = now - connect_timing_.domain_lookup_start;
|
|
UMA_HISTOGRAM_CUSTOM_TIMES("Net.DNS_Resolution_And_TCP_Connection_Latency2",
|
|
total_duration, base::Milliseconds(1),
|
|
base::Minutes(10), 100);
|
|
|
|
base::TimeDelta connect_duration = now - connect_timing_.connect_start;
|
|
UMA_HISTOGRAM_CUSTOM_TIMES("Net.TCP_Connection_Latency", connect_duration,
|
|
base::Milliseconds(1), base::Minutes(10), 100);
|
|
} else {
|
|
// Don't try the next route if entering suspend mode.
|
|
if (result != ERR_NETWORK_IO_SUSPENDED) {
|
|
// If there is another endpoint available, try it.
|
|
current_endpoint_result_++;
|
|
if (current_endpoint_result_ < endpoint_results_.size()) {
|
|
next_state_ = STATE_TRANSPORT_CONNECT;
|
|
result = OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int TransportConnectJob::HandleSubJobComplete(int result,
|
|
TransportConnectSubJob* job) {
|
|
DCHECK_NE(result, ERR_IO_PENDING);
|
|
if (result == OK) {
|
|
SetSocket(job->PassSocket(), dns_aliases_);
|
|
return result;
|
|
}
|
|
|
|
if (result == ERR_NETWORK_IO_SUSPENDED) {
|
|
// Don't try other jobs if entering suspend mode.
|
|
return result;
|
|
}
|
|
|
|
switch (job->type()) {
|
|
case SUB_JOB_IPV4:
|
|
ipv4_job_.reset();
|
|
break;
|
|
|
|
case SUB_JOB_IPV6:
|
|
ipv6_job_.reset();
|
|
// Start the other job, rather than wait for the fallback timer.
|
|
if (ipv4_job_ && !ipv4_job_->started()) {
|
|
fallback_timer_.Stop();
|
|
result = ipv4_job_->Start();
|
|
if (result != ERR_IO_PENDING) {
|
|
return HandleSubJobComplete(result, ipv4_job_.get());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ipv4_job_ || ipv6_job_) {
|
|
// Wait for the other job to complete, rather than reporting |result|.
|
|
return ERR_IO_PENDING;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void TransportConnectJob::OnSubJobComplete(int result,
|
|
TransportConnectSubJob* job) {
|
|
result = HandleSubJobComplete(result, job);
|
|
if (result != ERR_IO_PENDING) {
|
|
OnIOComplete(result);
|
|
}
|
|
}
|
|
|
|
void TransportConnectJob::StartIPv4JobAsync() {
|
|
DCHECK(ipv4_job_);
|
|
net_log().AddEvent(NetLogEventType::TRANSPORT_CONNECT_JOB_IPV6_FALLBACK);
|
|
int result = ipv4_job_->Start();
|
|
if (result != ERR_IO_PENDING)
|
|
OnSubJobComplete(result, ipv4_job_.get());
|
|
}
|
|
|
|
int TransportConnectJob::ConnectInternal() {
|
|
next_state_ = STATE_RESOLVE_HOST;
|
|
return DoLoop(OK);
|
|
}
|
|
|
|
void TransportConnectJob::ChangePriorityInternal(RequestPriority priority) {
|
|
if (next_state_ == STATE_RESOLVE_HOST_COMPLETE) {
|
|
DCHECK(request_);
|
|
// Change the request priority in the host resolver.
|
|
request_->ChangeRequestPriority(priority);
|
|
}
|
|
}
|
|
|
|
bool TransportConnectJob::IsSvcbOptional(
|
|
base::span<const HostResolverEndpointResult> results) const {
|
|
// If SVCB/HTTPS resolution succeeded, the client supports ECH, and all routes
|
|
// support ECH, disable the A/AAAA fallback. See Section 10.1 of
|
|
// draft-ietf-dnsop-svcb-https-08.
|
|
|
|
auto* scheme_host_port =
|
|
absl::get_if<url::SchemeHostPort>(¶ms_->destination());
|
|
if (!scheme_host_port || scheme_host_port->scheme() != url::kHttpsScheme) {
|
|
return true; // This is not a SVCB-capable request at all.
|
|
}
|
|
|
|
if (!common_connect_job_params()->ssl_client_context ||
|
|
!common_connect_job_params()
|
|
->ssl_client_context->config()
|
|
.EncryptedClientHelloEnabled()) {
|
|
return true; // ECH is not supported for this request.
|
|
}
|
|
|
|
return !HostResolver::AllProtocolEndpointsHaveEch(results);
|
|
}
|
|
|
|
bool TransportConnectJob::IsEndpointResultUsable(
|
|
const HostResolverEndpointResult& result,
|
|
bool svcb_optional) const {
|
|
// A `HostResolverEndpointResult` with no ALPN protocols is the fallback
|
|
// A/AAAA route. This is always compatible. We assume the ALPN-less option is
|
|
// TCP-based.
|
|
if (result.metadata.supported_protocol_alpns.empty()) {
|
|
// See draft-ietf-dnsop-svcb-https-08, Section 3.
|
|
return svcb_optional;
|
|
}
|
|
|
|
// See draft-ietf-dnsop-svcb-https-08, Section 7.1.2. Routes are usable if
|
|
// there is an overlap between the route's ALPN protocols and the configured
|
|
// ones. This ensures we do not, e.g., connect to a QUIC-only route with TCP.
|
|
// Note that, if `params_` did not specify any ALPN protocols, no
|
|
// SVCB/HTTPS-based routes will match and we will effectively ignore all but
|
|
// plain A/AAAA routes.
|
|
for (const auto& alpn : result.metadata.supported_protocol_alpns) {
|
|
if (params_->supported_alpns().contains(alpn)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const HostResolverEndpointResult&
|
|
TransportConnectJob::GetEndpointResultForCurrentSubJobs() const {
|
|
CHECK_LT(current_endpoint_result_, endpoint_results_.size());
|
|
return endpoint_results_[current_endpoint_result_];
|
|
}
|
|
|
|
} // namespace net
|