0

Use HostResolverEndpointResults in TransportConnectJob

This switches TransportConnectJob to HostResolverEndpointResults. It is
currently a no-op because HostResolverEndpointResults contains the same
data as AddressList, but in a different order. But once HostResolver
starts returning data from the HTTPS DNS record (issue , and
gated by the appropriate feature flag), this CL will cause
TransportConnectJob to pick it up.

That will allow two things. First, we'll pick up additional routes
advertised in the HTTPS DNS record. Second, we can pass this data up to
SSLConnectJob (will be done in a subsequent CL) for ECH to pick up. In
principle, we can also use the ALPN information to guide
ShouldThrottleConnectForSpdy() and perhaps reduce the number of
connections made in an initial connection to the server, but this CL
does not implement that. (We'd probably need to revise the
HttpStreamFactory/TransportClientSocketPool/ConnectJob split for that.)

As part of this CL, TransportConnectJob needs ALPN information to select
usable routes. I've passed that down from the corresponding SSLConfig
when creating a ConnectJob. To make sure we do it consistently, I've
added a DCHECK and fixed the various tests to fill this in.

Bug: 1287240
Change-Id: I3b9f6a5135a59cc88f1955d0afe204f3f2ede932
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3418520
Reviewed-by: Matt Menke <mmenke@chromium.org>
Reviewed-by: Eric Orth <ericorth@chromium.org>
Commit-Queue: David Benjamin <davidben@chromium.org>
Cr-Commit-Position: refs/heads/main@{#969665}
This commit is contained in:
David Benjamin
2022-02-10 22:23:21 +00:00
committed by Chromium LUCI CQ
parent 7f01af5621
commit 13072a47ac
17 changed files with 587 additions and 63 deletions

@ -38,6 +38,9 @@ AddressList::AddressList(const IPEndPoint& endpoint,
push_back(endpoint);
}
AddressList::AddressList(std::vector<IPEndPoint> endpoints)
: endpoints_(std::move(endpoints)) {}
// static
AddressList AddressList::CreateFromIPAddress(const IPAddress& address,
uint16_t port) {

@ -34,12 +34,15 @@ class NET_EXPORT AddressList {
AddressList& operator=(AddressList&&);
~AddressList();
// Creates an address list for a single IP literal.
// Creates an address list for a single IP endpoint.
explicit AddressList(const IPEndPoint& endpoint);
// Creates an address list for a single IP literal and a list of DNS aliases.
// Creates an address list for a single IP endpoint and a list of DNS aliases.
AddressList(const IPEndPoint& endpoint, std::vector<std::string> aliases);
// Creates an address list for a list of IP endpoints.
explicit AddressList(std::vector<IPEndPoint> endpoints);
static AddressList CreateFromIPAddress(const IPAddress& address,
uint16_t port);

@ -282,6 +282,18 @@ class MockHostResolverBase::RequestImpl
// TODO(crbug.com/1264933): Perform fixups on `endpoint_results`?
endpoint_results_ = std::move(endpoint_results);
// For now, we do not support configuring DNS aliases with endpoint results,
// but the value is expected to always be present.
//
// TODO(crbug.com/1264933): Add some way to configure this, to support code
// migrating to `HostResolverEndpointResult`.
fixed_up_dns_alias_results_.emplace();
// `HostResolver` implementations are expected to provide an `AddressList`
// result whenever `HostResolverEndpointResult` is also available.
address_results_ = EndpointResultToAddressList(
*endpoint_results_, *fixed_up_dns_alias_results_);
}
void OnAsyncCompleted(size_t id, int error) {

@ -118,7 +118,8 @@ class HttpProxyConnectJobTest : public ::testing::TestWithParam<HttpProxyType>,
return nullptr;
return base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHttpProxyHost, 80), NetworkIsolationKey(),
secure_dns_policy, OnHostResolutionCallback());
secure_dns_policy, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>());
}
scoped_refptr<SSLSocketParams> CreateHttpsProxyParams(
@ -128,7 +129,8 @@ class HttpProxyConnectJobTest : public ::testing::TestWithParam<HttpProxyType>,
return base::MakeRefCounted<SSLSocketParams>(
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHttpsProxyHost, 443), NetworkIsolationKey(),
secure_dns_policy, OnHostResolutionCallback()),
secure_dns_policy, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>()),
nullptr, nullptr, HostPortPair(kHttpsProxyHost, 443), SSLConfig(),
PRIVACY_MODE_DISABLED, NetworkIsolationKey());
}
@ -935,7 +937,8 @@ TEST_P(HttpProxyConnectJobTest, SpdySessionKeyDisableSecureDns) {
auto ssl_params = base::MakeRefCounted<SSLSocketParams>(
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHttpsProxyHost, 443), NetworkIsolationKey(),
SecureDnsPolicy::kDisable, OnHostResolutionCallback()),
SecureDnsPolicy::kDisable, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>()),
nullptr, nullptr, HostPortPair(kHttpsProxyHost, 443), SSLConfig(),
PRIVACY_MODE_DISABLED, NetworkIsolationKey());
auto http_proxy_params = base::MakeRefCounted<HttpProxySocketParams>(

@ -1779,10 +1779,12 @@ TEST_F(HttpStreamFactoryTest, NewSpdySessionCloseIdleH2Sockets) {
auto connection = std::make_unique<ClientSocketHandle>();
TestCompletionCallback callback;
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = session->GetAlpnProtos();
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
ClientSocketPool::GroupId group_id(
destination, PrivacyMode::PRIVACY_MODE_DISABLED, NetworkIsolationKey(),
SecureDnsPolicy::kAllow);

@ -8,6 +8,7 @@
#include <utility>
#include "base/check.h"
#include "base/containers/flat_set.h"
#include "base/memory/scoped_refptr.h"
#include "net/base/host_port_pair.h"
#include "net/base/network_isolation_key.h"
@ -17,6 +18,7 @@
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/http_proxy_connect_job.h"
#include "net/socket/connect_job.h"
#include "net/socket/next_proto.h"
#include "net/socket/socket_tag.h"
#include "net/socket/socks_connect_job.h"
#include "net/socket/ssl_connect_job.h"
@ -74,6 +76,15 @@ TransportSocketParams::Endpoint ToTransportEndpoint(
.host_port_pair;
}
base::flat_set<std::string> SupportedProtocolsFromSSLConfig(
const SSLConfig& config) {
// We convert because `SSLConfig` uses `NextProto` for ALPN protocols while
// `TransportConnectJob` and DNS logic needs `std::string`. See
// https://crbug.com/1286835.
return base::MakeFlatSet<std::string>(config.alpn_protos, /*comp=*/{},
NextProtoToString);
}
} // namespace
ConnectJobFactory::ConnectJobFactory(
@ -160,11 +171,18 @@ std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
ConnectJob::Delegate* delegate) const {
scoped_refptr<HttpProxySocketParams> http_proxy_params;
scoped_refptr<SOCKSSocketParams> socks_params;
base::flat_set<std::string> no_alpn_protocols;
if (!proxy_server.is_direct()) {
// TODO(crbug.com/1206799): For an http-like proxy, should this pass a
// `SchemeHostPort`, so proxies can participate in ECH? Note doing so with
// `SCHEME_HTTP` requires handling the HTTPS record upgrade.
auto proxy_tcp_params = base::MakeRefCounted<TransportSocketParams>(
proxy_server.host_port_pair(), proxy_dns_network_isolation_key_,
secure_dns_policy, resolution_callback);
secure_dns_policy, resolution_callback,
proxy_server.is_secure_http_like()
? SupportedProtocolsFromSSLConfig(*ssl_config_for_proxy)
: no_alpn_protocols);
if (proxy_server.is_http_like()) {
scoped_refptr<SSLSocketParams> ssl_params;
@ -204,7 +222,8 @@ std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
if (proxy_server.is_direct()) {
ssl_tcp_params = base::MakeRefCounted<TransportSocketParams>(
ToTransportEndpoint(endpoint), network_isolation_key,
secure_dns_policy, resolution_callback);
secure_dns_policy, resolution_callback,
SupportedProtocolsFromSSLConfig(*ssl_config_for_origin));
}
// TODO(crbug.com/1206799): Pass `endpoint` directly (preserving scheme
// when available)?
@ -229,10 +248,11 @@ std::unique_ptr<ConnectJob> ConnectJobFactory::CreateConnectJob(
std::move(socks_params), delegate, /*net_log=*/nullptr);
}
// Only SSL/TLS-based endpoints have ALPN protocols.
DCHECK(proxy_server.is_direct());
auto tcp_params = base::MakeRefCounted<TransportSocketParams>(
ToTransportEndpoint(endpoint), network_isolation_key, secure_dns_policy,
resolution_callback);
resolution_callback, no_alpn_protocols);
if (!common_connect_job_params->websocket_endpoint_lock_manager) {
return transport_connect_job_factory_->Create(
request_priority, socket_tag, common_connect_job_params, tcp_params,

@ -19,6 +19,7 @@
#include "net/log/net_log_with_source.h"
#include "net/socket/connect_job.h"
#include "net/socket/connect_job_test_util.h"
#include "net/socket/next_proto.h"
#include "net/socket/socket_tag.h"
#include "net/socket/socks_connect_job.h"
#include "net/socket/ssl_connect_job.h"
@ -271,6 +272,7 @@ TEST_F(ConnectJobFactoryTest, CreateConnectJobWithoutScheme) {
TEST_F(ConnectJobFactoryTest, CreateHttpsConnectJob) {
const url::SchemeHostPort kEndpoint(url::kHttpsScheme, "test", 84);
SSLConfig ssl_config;
ssl_config.alpn_protos = {kProtoHTTP2, kProtoHTTP11};
std::unique_ptr<ConnectJob> job = factory_->CreateConnectJob(
kEndpoint, ProxyServer::Direct(),
@ -293,6 +295,8 @@ TEST_F(ConnectJobFactoryTest, CreateHttpsConnectJob) {
*params.GetDirectConnectionParams();
EXPECT_THAT(transport_params.destination(),
testing::VariantWith<url::SchemeHostPort>(kEndpoint));
EXPECT_THAT(transport_params.supported_alpns(),
testing::UnorderedElementsAre("h2", "http/1.1"));
}
TEST_F(ConnectJobFactoryTest, CreateHttpsConnectJobWithoutScheme) {

@ -5,6 +5,7 @@
#include "net/socket/socks_connect_job.h"
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/span.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
@ -73,7 +74,8 @@ class SOCKSConnectJobTest : public testing::Test, public WithTaskEnvironment {
return base::MakeRefCounted<SOCKSSocketParams>(
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kProxyHostName, kProxyPort), NetworkIsolationKey(),
secure_dns_policy, OnHostResolutionCallback()),
secure_dns_policy, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>()),
socks_version == SOCKSVersion::V5,
socks_version == SOCKSVersion::V4
? HostPortPair(kSOCKS4TestHost, kSOCKS4TestPort)
@ -121,7 +123,8 @@ TEST_F(SOCKSConnectJobTest, HostResolutionFailureSOCKS4Endpoint) {
base::MakeRefCounted<SOCKSSocketParams>(
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kProxyHostName, kProxyPort), NetworkIsolationKey(),
SecureDnsPolicy::kAllow, OnHostResolutionCallback()),
SecureDnsPolicy::kAllow, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>()),
false /* socks_v5 */, HostPortPair(hostname, kSOCKS4TestPort),
NetworkIsolationKey(), TRAFFIC_ANNOTATION_FOR_TESTS);

@ -98,12 +98,14 @@ class SSLConnectJobTest : public WithTaskEnvironment, public testing::Test {
url::SchemeHostPort(url::kHttpsScheme, "host", 443),
NetworkIsolationKey(),
SecureDnsPolicy::kAllow,
OnHostResolutionCallback())),
OnHostResolutionCallback(),
/*supported_alpns=*/{"h2", "http/1.1"})),
proxy_transport_socket_params_(
new TransportSocketParams(HostPortPair("proxy", 443),
NetworkIsolationKey(),
SecureDnsPolicy::kAllow,
OnHostResolutionCallback())),
OnHostResolutionCallback(),
/*supported_alpns=*/{})),
socks_socket_params_(
new SOCKSSocketParams(proxy_transport_socket_params_,
true,
@ -435,7 +437,8 @@ TEST_F(SSLConnectJobTest, SecureDnsPolicy) {
base::MakeRefCounted<TransportSocketParams>(
url::SchemeHostPort(url::kHttpsScheme, "host", 443),
NetworkIsolationKey(), secure_dns_policy,
OnHostResolutionCallback());
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>{"h2", "http/1.1"});
auto common_connect_job_params = session_->CreateCommonConnectJobParams();
std::unique_ptr<ConnectJob> ssl_connect_job =
std::make_unique<SSLConnectJob>(DEFAULT_PRIORITY, SocketTag(),

@ -1100,11 +1100,13 @@ TEST_F(TransportClientSocketPoolTest, SSLCertError) {
const url::SchemeHostPort kEndpoint(url::kHttpsScheme, "ssl.server.test",
443);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
ClientSocketHandle handle;
TestCompletionCallback callback;
@ -1734,13 +1736,15 @@ TEST_F(TransportClientSocketPoolTest, NetworkIsolationKeySsl) {
url::SchemeHostPort(url::kHttpsScheme, kHost, 443),
PrivacyMode::PRIVACY_MODE_DISABLED, kNetworkIsolationKey,
SecureDnsPolicy::kAllow);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
ClientSocketHandle handle;
TestCompletionCallback callback;
EXPECT_THAT(
handle.Init(group_id,
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */),
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr),
TRAFFIC_ANNOTATION_FOR_TESTS, LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED, callback.callback(),
ClientSocketPool::ProxyAuthCallback(), pool_.get(),
@ -2308,10 +2312,12 @@ TEST_F(TransportClientSocketPoolTest, TagSSLDirect) {
PrivacyMode::PRIVACY_MODE_DISABLED, NetworkIsolationKey(),
SecureDnsPolicy::kAllow);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
// Test socket is tagged before connected.
uint64_t old_traffic = GetTaggedBytes(tag_val1);
@ -2378,10 +2384,12 @@ TEST_F(TransportClientSocketPoolTest, TagSSLDirectTwoSockets) {
url::SchemeHostPort(test_server.base_url()),
PrivacyMode::PRIVACY_MODE_DISABLED, NetworkIsolationKey(),
SecureDnsPolicy::kAllow);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
// Test connect jobs that are orphaned and then adopted, appropriately apply
// new tag. Request socket with |tag1|.
@ -2442,10 +2450,12 @@ TEST_F(TransportClientSocketPoolTest, TagSSLDirectTwoSocketsFullPool) {
url::SchemeHostPort(test_server.base_url()),
PrivacyMode::PRIVACY_MODE_DISABLED, NetworkIsolationKey(),
SecureDnsPolicy::kAllow);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
// Test that sockets paused by a full underlying socket pool are properly
// connected and tagged when underlying pool is freed up.
@ -2596,10 +2606,12 @@ TEST_F(TransportClientSocketPoolTest, TagHttpProxyTunnel) {
kDestination, PrivacyMode::PRIVACY_MODE_DISABLED, NetworkIsolationKey(),
SecureDnsPolicy::kAllow);
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP2, kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
// Verify requested socket is tagged properly.
ClientSocketHandle handle;

@ -22,6 +22,7 @@
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
#include "net/base/trace_constants.h"
#include "net/dns/host_resolver_results.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/log/net_log.h"
#include "net/log/net_log_event_type.h"
@ -35,6 +36,7 @@
#include "net/socket/websocket_transport_connect_job.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace net {
@ -69,11 +71,38 @@ TransportSocketParams::TransportSocketParams(
Endpoint destination,
NetworkIsolationKey network_isolation_key,
SecureDnsPolicy secure_dns_policy,
OnHostResolutionCallback host_resolution_callback)
OnHostResolutionCallback host_resolution_callback,
base::flat_set<std::string> supported_alpns)
: destination_(std::move(destination)),
network_isolation_key_(std::move(network_isolation_key)),
secure_dns_policy_(secure_dns_policy),
host_resolution_callback_(std::move(host_resolution_callback)) {}
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;
@ -152,6 +181,7 @@ LoadState TransportConnectJob::GetLoadState() const {
case STATE_RESOLVE_HOST:
case STATE_RESOLVE_HOST_COMPLETE:
return LOAD_STATE_RESOLVING_HOST;
case STATE_RESOLVE_HOST_CALLBACK_COMPLETE:
case STATE_TRANSPORT_CONNECT:
case STATE_TRANSPORT_CONNECT_COMPLETE:
case STATE_FALLBACK_CONNECT_COMPLETE:
@ -171,10 +201,10 @@ bool TransportConnectJob::HasEstablishedConnection() const {
ConnectionAttempts TransportConnectJob::GetConnectionAttempts() const {
// If hostname resolution failed, record an empty endpoint and the result.
// Also record any attempts made on either of the sockets.
// Also record any attempts made on any failed sockets.
ConnectionAttempts attempts = connection_attempts_;
if (resolve_result_ != OK) {
DCHECK(!request_->GetAddressResults());
DCHECK(endpoint_results_.empty());
attempts.push_back(ConnectionAttempt(IPEndPoint(), resolve_result_));
}
return attempts;
@ -267,6 +297,10 @@ int TransportConnectJob::DoLoop(int result) {
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();
@ -321,12 +355,15 @@ int TransportConnectJob::DoResolveHostComplete(int result) {
if (result != OK)
return result;
DCHECK(request_->GetAddressResults());
next_state_ = STATE_TRANSPORT_CONNECT;
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()) {
// TODO(https://crbug.com/1287240): Switch `OnHostResolutionCallbackResult`
// to `request_->GetEndpointResults()` and `request_->GetDnsAliasResults()`.
OnHostResolutionCallbackResult callback_result =
params_->host_resolution_callback().Run(
ToLegacyDestinationEndpoint(params_->destination()),
@ -342,27 +379,50 @@ int TransportConnectJob::DoResolveHostComplete(int result) {
return result;
}
int TransportConnectJob::DoResolveHostCallbackComplete() {
for (const auto& result : *request_->GetEndpointResults()) {
if (IsEndpointResultUsable(result)) {
endpoint_results_.push_back(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;
AddressList addresses = GetCurrentAddressList();
// Create a |SocketPerformanceWatcher|, and pass the ownership.
std::unique_ptr<SocketPerformanceWatcher> socket_performance_watcher;
if (socket_performance_watcher_factory()) {
socket_performance_watcher =
socket_performance_watcher_factory()->CreateSocketPerformanceWatcher(
SocketPerformanceWatcherFactory::PROTOCOL_TCP,
*request_->GetAddressResults());
SocketPerformanceWatcherFactory::PROTOCOL_TCP, addresses);
}
transport_socket_ = client_socket_factory()->CreateTransportClientSocket(
*request_->GetAddressResults(), std::move(socket_performance_watcher),
addresses, std::move(socket_performance_watcher),
network_quality_estimator(), net_log().net_log(), net_log().source());
// If the list contains IPv6 and IPv4 addresses, and the first address
// is IPv6, the IPv4 addresses will be tried as fallback addresses, per
// "Happy Eyeballs" (RFC 6555).
bool try_ipv6_connect_with_ipv4_fallback =
request_->GetAddressResults()->front().GetFamily() ==
ADDRESS_FAMILY_IPV6 &&
!AddressListOnlyContainsIPv6(*request_->GetAddressResults());
addresses.front().GetFamily() == ADDRESS_FAMILY_IPV6 &&
!AddressListOnlyContainsIPv6(addresses);
transport_socket_->ApplySocketTag(socket_tag());
@ -388,9 +448,7 @@ int TransportConnectJob::DoTransportConnectComplete(bool is_fallback,
// Save the connection attempts from the other socket. (Unfortunately, the
// only simple way to return information in the success case is through the
// successfully-connected socket.)
ConnectionAttempts attempts;
other_socket->GetConnectionAttempts(&attempts);
completed_socket->AddConnectionAttempts(attempts);
SaveConnectionAttempts(*other_socket);
}
if (is_fallback) {
connect_timing_.connect_start = fallback_connect_start_time_;
@ -401,8 +459,7 @@ int TransportConnectJob::DoTransportConnectComplete(bool is_fallback,
other_socket.reset();
if (result == OK) {
DCHECK(request_);
const AddressList& addresses = *request_->GetAddressResults();
AddressList addresses = GetCurrentAddressList();
bool is_ipv4 = addresses.front().GetFamily() == ADDRESS_FAMILY_IPV4;
RaceResult race_result = RACE_UNKNOWN;
if (is_fallback) {
@ -416,13 +473,21 @@ int TransportConnectJob::DoTransportConnectComplete(bool is_fallback,
}
HistogramDuration(connect_timing_, race_result);
SetSocket(std::move(completed_socket),
base::OptionalFromPtr(request_->GetDnsAliasResults()));
// Add connection attempts from previous routes.
completed_socket->AddConnectionAttempts(connection_attempts_);
SetSocket(std::move(completed_socket), dns_aliases_);
} else {
// Failure will be returned via |GetAdditionalErrorState|, so save
// connection attempts from the socket for use there.
completed_socket->GetConnectionAttempts(&connection_attempts_);
SaveConnectionAttempts(*completed_socket);
completed_socket.reset();
// 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;
@ -438,7 +503,7 @@ void TransportConnectJob::OnIPv6FallbackTimerComplete() {
DCHECK(!fallback_transport_socket_.get());
AddressList fallback_addresses = *request_->GetAddressResults();
AddressList fallback_addresses = GetCurrentAddressList();
MakeAddressListStartWithIPv4(&fallback_addresses);
// Create a |SocketPerformanceWatcher|, and pass the ownership.
@ -485,4 +550,50 @@ void TransportConnectJob::ChangePriorityInternal(RequestPriority priority) {
}
}
bool TransportConnectJob::IsEndpointResultUsable(
const HostResolverEndpointResult& result) 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()) {
// TODO(https://crbug.com/1091403): Reject fallback routes when ECH triggers
// SVCB-reliant mode.
return true;
}
// 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;
}
AddressList TransportConnectJob::GetCurrentAddressList() const {
CHECK_LT(current_endpoint_result_, endpoint_results_.size());
const HostResolverEndpointResult& endpoint_result =
endpoint_results_[current_endpoint_result_];
DCHECK(IsEndpointResultUsable(endpoint_result));
// TODO(crbug.com/126134): `HostResolverEndpointResult` has a
// `vector<IPEndPoint>`, while all these classes expect an `AddressList`.
// Align these after DNS aliases are fully moved out of `AddressList`.
// https://crbug.com/1291352 will also likely move the `AddressList` iteration
// out of `TCPClientSocket`, which will also avoid the conversion.
return AddressList(endpoint_result.ip_endpoints);
}
void TransportConnectJob::SaveConnectionAttempts(const StreamSocket& socket) {
ConnectionAttempts attempts;
socket.GetConnectionAttempts(&attempts);
connection_attempts_.insert(connection_attempts_.end(), attempts.begin(),
attempts.end());
}
} // namespace net

@ -6,8 +6,12 @@
#define NET_SOCKET_TRANSPORT_CONNECT_JOB_H_
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
@ -16,11 +20,13 @@
#include "net/base/net_export.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/host_resolver.h"
#include "net/dns/host_resolver_results.h"
#include "net/dns/public/resolve_error_info.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/socket/connect_job.h"
#include "net/socket/connection_attempts.h"
#include "net/socket/socket_tag.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "url/scheme_host_port.h"
@ -41,11 +47,14 @@ class NET_EXPORT_PRIVATE TransportSocketParams
// |host_resolution_callback| will be invoked after the the hostname is
// resolved. |network_isolation_key| is passed to the HostResolver to prevent
// cross-NIK leaks. If |host_resolution_callback| does not return OK, then the
// connection will be aborted with that value.
// connection will be aborted with that value. |supported_alpns| specifies
// ALPN protocols for selecting HTTPS/SVCB records. If empty, addresses from
// HTTPS/SVCB records will be ignored and only A/AAAA will be used.
TransportSocketParams(Endpoint destination,
NetworkIsolationKey network_isolation_key,
SecureDnsPolicy secure_dns_policy,
OnHostResolutionCallback host_resolution_callback);
OnHostResolutionCallback host_resolution_callback,
base::flat_set<std::string> supported_alpns);
TransportSocketParams(const TransportSocketParams&) = delete;
TransportSocketParams& operator=(const TransportSocketParams&) = delete;
@ -58,6 +67,9 @@ class NET_EXPORT_PRIVATE TransportSocketParams
const OnHostResolutionCallback& host_resolution_callback() const {
return host_resolution_callback_;
}
const base::flat_set<std::string>& supported_alpns() const {
return supported_alpns_;
}
private:
friend class base::RefCounted<TransportSocketParams>;
@ -67,6 +79,7 @@ class NET_EXPORT_PRIVATE TransportSocketParams
const NetworkIsolationKey network_isolation_key_;
const SecureDnsPolicy secure_dns_policy_;
const OnHostResolutionCallback host_resolution_callback_;
const base::flat_set<std::string> supported_alpns_;
};
// TransportConnectJob handles the host resolution necessary for socket creation
@ -157,6 +170,7 @@ class NET_EXPORT_PRIVATE TransportConnectJob : public ConnectJob {
enum State {
STATE_RESOLVE_HOST,
STATE_RESOLVE_HOST_COMPLETE,
STATE_RESOLVE_HOST_CALLBACK_COMPLETE,
STATE_TRANSPORT_CONNECT,
STATE_TRANSPORT_CONNECT_COMPLETE,
STATE_FALLBACK_CONNECT_COMPLETE,
@ -168,6 +182,7 @@ class NET_EXPORT_PRIVATE TransportConnectJob : public ConnectJob {
int DoResolveHost();
int DoResolveHostComplete(int result);
int DoResolveHostCallbackComplete();
int DoTransportConnect();
int DoTransportConnectComplete(bool is_fallback, int result);
@ -184,8 +199,22 @@ class NET_EXPORT_PRIVATE TransportConnectJob : public ConnectJob {
// resolver request.
void ChangePriorityInternal(RequestPriority priority) override;
// Returns whether `result` is usable for this connection.
bool IsEndpointResultUsable(const HostResolverEndpointResult& result) const;
// Returns an `AddressList` containing the IP endpoints for the current route.
// May only be called if the current route is usable for this connection.
AddressList GetCurrentAddressList() const;
// Appends connection attempts from `socket` to `connection_attempts_`. Should
// be called when discarding a failed socket.
void SaveConnectionAttempts(const StreamSocket& socket);
scoped_refptr<TransportSocketParams> params_;
std::unique_ptr<HostResolver::ResolveHostRequest> request_;
std::vector<HostResolverEndpointResult> endpoint_results_;
size_t current_endpoint_result_ = 0;
std::set<std::string> dns_aliases_;
State next_state_;

@ -69,7 +69,16 @@ class TransportConnectJobTest : public WithTaskEnvironment,
return base::MakeRefCounted<TransportSocketParams>(
url::SchemeHostPort(url::kHttpScheme, kHostName, 80),
NetworkIsolationKey(), SecureDnsPolicy::kAllow,
OnHostResolutionCallback());
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>());
}
static scoped_refptr<TransportSocketParams> DefaultHttpsParams() {
return base::MakeRefCounted<TransportSocketParams>(
url::SchemeHostPort(url::kHttpsScheme, kHostName, 443),
NetworkIsolationKey(), SecureDnsPolicy::kAllow,
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>{"h2", "http/1.1"});
}
protected:
@ -279,7 +288,8 @@ TEST_F(TransportConnectJobTest, HandlesHttpsEndpoint) {
base::MakeRefCounted<TransportSocketParams>(
url::SchemeHostPort(url::kHttpsScheme, kHostName, 80),
NetworkIsolationKey(), SecureDnsPolicy::kAllow,
OnHostResolutionCallback()),
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>{"h2", "http/1.1"}),
&test_delegate, nullptr /* net_log */);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
false /* expect_sync_result */);
@ -293,7 +303,8 @@ TEST_F(TransportConnectJobTest, HandlesNonStandardEndpoint) {
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHostName, 80), NetworkIsolationKey(),
SecureDnsPolicy::kAllow, OnHostResolutionCallback()),
SecureDnsPolicy::kAllow, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>()),
&test_delegate, nullptr /* net_log */);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
false /* expect_sync_result */);
@ -308,7 +319,8 @@ TEST_F(TransportConnectJobTest, SecureDnsPolicy) {
base::MakeRefCounted<TransportSocketParams>(
url::SchemeHostPort(url::kHttpScheme, kHostName, 80),
NetworkIsolationKey(), secure_dns_policy,
OnHostResolutionCallback()),
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>{}),
&test_delegate, nullptr /* net_log */);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
false /* expect_sync_result */);
@ -501,5 +513,305 @@ TEST_F(TransportConnectJobTest, NoAdditionalDnsAliases) {
testing::ElementsAre(kHostName));
}
// Test that `TransportConnectJob` will pick up options from
// `HostResolverEndpointResult`.
TEST_F(TransportConnectJobTest, EndpointResult) {
HostResolverEndpointResult endpoint;
endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8443),
IPEndPoint(ParseIP("1.1.1.1"), 8443)};
endpoint.metadata.supported_protocol_alpns = {"h2"};
host_resolver_.rules()->AddRule(kHostName, std::vector{endpoint});
MockTransportClientSocketFactory::Rule rule(
MockTransportClientSocketFactory::Type::kSynchronous,
std::vector{IPEndPoint(ParseIP("1::"), 8443),
IPEndPoint(ParseIP("1.1.1.1"), 8443)});
client_socket_factory_.SetRules(base::make_span(&rule, 1));
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
/*expect_sync_result=*/false);
IPEndPoint peer_address;
test_delegate.socket()->GetPeerAddress(&peer_address);
EXPECT_EQ(peer_address, IPEndPoint(ParseIP("1::"), 8443));
EXPECT_EQ(1, client_socket_factory_.allocation_count());
// There were no failed connection attempts to report.
ConnectionAttempts attempts;
test_delegate.socket()->GetConnectionAttempts(&attempts);
EXPECT_EQ(0u, attempts.size());
}
// Test that, given multiple `HostResolverEndpointResult` results,
// `TransportConnectJob` tries each in succession.
TEST_F(TransportConnectJobTest, MultipleRoutesFallback) {
std::vector<HostResolverEndpointResult> endpoints(3);
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)};
endpoints[0].metadata.supported_protocol_alpns = {"h3", "h2", "http/1.1"};
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442),
IPEndPoint(ParseIP("2.2.2.2"), 8442)};
endpoints[1].metadata.supported_protocol_alpns = {"h3"};
endpoints[2].ip_endpoints = {IPEndPoint(ParseIP("4::"), 443),
IPEndPoint(ParseIP("4.4.4.4"), 443)};
host_resolver_.rules()->AddRule(kHostName, endpoints);
MockTransportClientSocketFactory::Rule rules[] = {
// `endpoints[0]`'s IPv6-prefering socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kFailing,
endpoints[0].ip_endpoints),
// `endpoints[1]` is skipped because the ALPN is not compatible.
// `endpoints[2]`'s IPv6-preferring socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kSynchronous,
endpoints[2].ip_endpoints),
};
client_socket_factory_.SetRules(rules);
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
/*expect_sync_result=*/false);
IPEndPoint peer_address;
test_delegate.socket()->GetPeerAddress(&peer_address);
EXPECT_EQ(peer_address, IPEndPoint(ParseIP("4::"), 443));
// Check that failed connection attempts are reported.
ConnectionAttempts attempts;
test_delegate.socket()->GetConnectionAttempts(&attempts);
ASSERT_EQ(2u, attempts.size());
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1::"), 8441));
EXPECT_THAT(attempts[1].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[1].endpoint, IPEndPoint(ParseIP("1.1.1.1"), 8441));
}
// Test that the `HostResolverEndpointResult` fallback works in combination with
// the IPv4 fallback.
TEST_F(TransportConnectJobTest, MultipleRoutesIPV4Fallback) {
HostResolverEndpointResult endpoint1, endpoint2, endpoint3;
endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)};
endpoint1.metadata.supported_protocol_alpns = {"h3", "h2", "http/1.1"};
endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442),
IPEndPoint(ParseIP("2.2.2.2"), 8442)};
endpoint2.metadata.supported_protocol_alpns = {"h3"};
endpoint3.ip_endpoints = {IPEndPoint(ParseIP("3::"), 443),
IPEndPoint(ParseIP("3.3.3.3"), 443)};
host_resolver_.rules()->AddRule(kHostName,
std::vector{endpoint1, endpoint2, endpoint3});
MockTransportClientSocketFactory::Rule rules[] = {
// `endpoint1`'s IPv6-prefering socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kStalled,
std::vector{IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)}),
// `endpoint1`'s IPv4-prefering socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kFailing,
std::vector{IPEndPoint(ParseIP("1.1.1.1"), 8441),
IPEndPoint(ParseIP("1::"), 8441)}),
// `endpoint2` is skipped because the ALPN is not compatible.
// `endpoint3`'s IPv6-preferring socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kStalled,
std::vector{IPEndPoint(ParseIP("3::"), 443),
IPEndPoint(ParseIP("3.3.3.3"), 443)}),
// `endpoint3`'s IPv4-preferring socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kSynchronous,
std::vector{IPEndPoint(ParseIP("3.3.3.3"), 443),
IPEndPoint(ParseIP("3::"), 443)}),
};
client_socket_factory_.SetRules(rules);
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
/*expect_sync_result=*/false);
IPEndPoint peer_address;
test_delegate.socket()->GetPeerAddress(&peer_address);
EXPECT_EQ(peer_address, IPEndPoint(ParseIP("3.3.3.3"), 443));
// Check that failed connection attempts are reported.
ConnectionAttempts attempts;
test_delegate.socket()->GetConnectionAttempts(&attempts);
ASSERT_EQ(2u, attempts.size());
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1.1.1.1"), 8441));
EXPECT_THAT(attempts[1].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[1].endpoint, IPEndPoint(ParseIP("1::"), 8441));
}
// Test that, if `HostResolver` supports SVCB for a scheme but the caller didn't
// pass in any ALPN protocols, `TransportConnectJob` ignores all protocol
// endpoints.
TEST_F(TransportConnectJobTest, NoAlpnProtocols) {
std::vector<HostResolverEndpointResult> endpoints(3);
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8081),
IPEndPoint(ParseIP("1.1.1.1"), 8081)};
endpoints[0].metadata.supported_protocol_alpns = {"foo", "bar"};
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8082),
IPEndPoint(ParseIP("2.2.2.2"), 8082)};
endpoints[1].metadata.supported_protocol_alpns = {"baz"};
endpoints[2].ip_endpoints = {IPEndPoint(ParseIP("3::"), 80),
IPEndPoint(ParseIP("3.3.3.3"), 80)};
host_resolver_.rules()->AddRule(kHostName, endpoints);
// `endpoints[2]`'s IPv6-preferring socket.
MockTransportClientSocketFactory::Rule rule(
MockTransportClientSocketFactory::Type::kSynchronous,
endpoints[2].ip_endpoints);
client_socket_factory_.SetRules(base::make_span(&rule, 1));
// Use `DefaultParams()`, an http scheme. That it is http is not very
// important, but `url::SchemeHostPort` is difficult to use with unknown
// schemes. See https://crbug.com/869291.
scoped_refptr<TransportSocketParams> params = DefaultParams();
ASSERT_TRUE(params->supported_alpns().empty());
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
std::move(params), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
/*expect_sync_result=*/false);
IPEndPoint peer_address;
test_delegate.socket()->GetPeerAddress(&peer_address);
EXPECT_EQ(peer_address, IPEndPoint(ParseIP("3::"), 80));
}
// Test that, given multiple `HostResolverEndpointResult` results,
// `TransportConnectJob` reports failure if each one fails.
TEST_F(TransportConnectJobTest, MultipleRoutesAllFailed) {
std::vector<HostResolverEndpointResult> endpoints(3);
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)};
endpoints[0].metadata.supported_protocol_alpns = {"h3", "h2", "http/1.1"};
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442),
IPEndPoint(ParseIP("2.2.2.2"), 8442)};
endpoints[1].metadata.supported_protocol_alpns = {"h3"};
endpoints[2].ip_endpoints = {IPEndPoint(ParseIP("3::"), 443),
IPEndPoint(ParseIP("3.3.3.3"), 443)};
host_resolver_.rules()->AddRule(kHostName, endpoints);
MockTransportClientSocketFactory::Rule rules[] = {
// `endpoints[0]`'s IPv6-prefering socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kFailing,
endpoints[0].ip_endpoints),
// `endpoints[1]` is skipped because the ALPN is not compatible.
// `endpoints[2]`'s IPv6-preferring socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kFailing,
endpoints[2].ip_endpoints),
};
client_socket_factory_.SetRules(rules);
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job,
ERR_CONNECTION_FAILED,
/*expect_sync_result=*/false);
// Check that failed connection attempts are reported.
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
ASSERT_EQ(4u, attempts.size());
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1::"), 8441));
EXPECT_THAT(attempts[1].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[1].endpoint, IPEndPoint(ParseIP("1.1.1.1"), 8441));
EXPECT_THAT(attempts[2].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[2].endpoint, IPEndPoint(ParseIP("3::"), 443));
EXPECT_THAT(attempts[3].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[3].endpoint, IPEndPoint(ParseIP("3.3.3.3"), 443));
}
// Test that `TransportConnectJob` reports failure if all provided routes were
// unusable.
TEST_F(TransportConnectJobTest, NoUsableRoutes) {
std::vector<HostResolverEndpointResult> endpoints(2);
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)};
endpoints[0].metadata.supported_protocol_alpns = {"h3"};
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442),
IPEndPoint(ParseIP("2.2.2.2"), 8442)};
endpoints[1].metadata.supported_protocol_alpns = {"unrecognized-protocol"};
host_resolver_.rules()->AddRule(kHostName, endpoints);
// `TransportConnectJob` should not create any sockets.
client_socket_factory_.set_default_client_socket_type(
MockTransportClientSocketFactory::Type::kUnexpected);
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job,
ERR_NAME_NOT_RESOLVED,
/*expect_sync_result=*/false);
}
// Test that, if the last route is unusable, the error from the
// previously-attempted route is preserved.
TEST_F(TransportConnectJobTest, LastRouteUnusable) {
std::vector<HostResolverEndpointResult> endpoints(2);
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441),
IPEndPoint(ParseIP("1.1.1.1"), 8441)};
endpoints[0].metadata.supported_protocol_alpns = {"h3", "h2", "http/1.1"};
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442),
IPEndPoint(ParseIP("2.2.2.2"), 8442)};
endpoints[1].metadata.supported_protocol_alpns = {"h3"};
host_resolver_.rules()->AddRule(kHostName, endpoints);
MockTransportClientSocketFactory::Rule rules[] = {
// `endpoints[0]`'s IPv6-prefering socket.
MockTransportClientSocketFactory::Rule(
MockTransportClientSocketFactory::Type::kFailing,
endpoints[0].ip_endpoints),
// `endpoints[1]` is skipped because the ALPN is not compatible.
};
client_socket_factory_.SetRules(rules);
TestConnectJobDelegate test_delegate;
TransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr);
test_delegate.StartJobExpectingResult(&transport_connect_job,
ERR_CONNECTION_FAILED,
/*expect_sync_result=*/false);
// Check that failed connection attempts are reported.
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
ASSERT_EQ(2u, attempts.size());
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1::"), 8441));
EXPECT_THAT(attempts[1].result, test::IsError(ERR_CONNECTION_FAILED));
EXPECT_EQ(attempts[1].endpoint, IPEndPoint(ParseIP("1.1.1.1"), 8441));
}
} // namespace
} // namespace net

@ -1197,7 +1197,8 @@ TEST_F(WebSocketTransportClientSocketPoolTest,
scoped_refptr<TransportSocketParams> params =
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHostName, 80), NetworkIsolationKey(),
SecureDnsPolicy::kAllow, OnHostResolutionCallback());
SecureDnsPolicy::kAllow, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>());
WebSocketTransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_, params,
@ -1229,7 +1230,8 @@ TEST_F(WebSocketTransportClientSocketPoolTest,
scoped_refptr<TransportSocketParams> params =
base::MakeRefCounted<TransportSocketParams>(
HostPortPair(kHostName, 80), NetworkIsolationKey(),
SecureDnsPolicy::kAllow, OnHostResolutionCallback());
SecureDnsPolicy::kAllow, OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>());
WebSocketTransportConnectJob transport_connect_job(
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_, params,

@ -100,7 +100,8 @@ base::WeakPtr<SpdySession> CreateSpdyProxySession(
auto transport_params = base::MakeRefCounted<TransportSocketParams>(
destination, NetworkIsolationKey(), SecureDnsPolicy::kAllow,
OnHostResolutionCallback());
OnHostResolutionCallback(),
/*supported_alpns=*/base::flat_set<std::string>{"h2", "http/1.1"});
SSLConfig ssl_config;
auto ssl_params = base::MakeRefCounted<SSLSocketParams>(

@ -483,22 +483,24 @@ base::WeakPtr<SpdySession> CreateSpdySessionHelper(
bool enable_ip_based_pooling) {
EXPECT_FALSE(http_session->spdy_session_pool()->FindAvailableSession(
key, enable_ip_based_pooling,
/* is_websocket = */ false, NetLogWithSource()));
/*is_websocket=*/false, NetLogWithSource()));
auto connection = std::make_unique<ClientSocketHandle>();
TestCompletionCallback callback;
auto ssl_config = std::make_unique<SSLConfig>();
ssl_config->alpn_protos = http_session->GetAlpnProtos();
scoped_refptr<ClientSocketPool::SocketParams> socket_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
/*ssl_config_for_origin=*/std::move(ssl_config),
/*ssl_config_for_proxy=*/nullptr);
int rv = connection->Init(
ClientSocketPool::GroupId(
url::SchemeHostPort(url::kHttpsScheme,
key.host_port_pair().HostForURL(),
key.host_port_pair().port()),
key.privacy_mode(), NetworkIsolationKey(), SecureDnsPolicy::kAllow),
socket_params, absl::nullopt /* proxy_annotation_tag */, MEDIUM,
socket_params, /*proxy_annotation_tag=*/absl::nullopt, MEDIUM,
key.socket_tag(), ClientSocketPool::RespectLimits::ENABLED,
callback.callback(), ClientSocketPool::ProxyAuthCallback(),
http_session->GetSocketPool(HttpNetworkSession::NORMAL_SOCKET_POOL,

@ -65,10 +65,12 @@ class WebSocketClientSocketHandleAdapterTest : public TestWithTaskEnvironment {
~WebSocketClientSocketHandleAdapterTest() override = default;
bool InitClientSocketHandle(ClientSocketHandle* connection) {
auto ssl_config_for_origin = std::make_unique<SSLConfig>();
ssl_config_for_origin->alpn_protos = {kProtoHTTP11};
scoped_refptr<ClientSocketPool::SocketParams> socks_params =
base::MakeRefCounted<ClientSocketPool::SocketParams>(
std::make_unique<SSLConfig>() /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
std::move(ssl_config_for_origin),
/*ssl_config_for_proxy=*/nullptr);
TestCompletionCallback callback;
int rv = connection->Init(
ClientSocketPool::GroupId(