1180 lines
54 KiB
C++
1180 lines
54 KiB
C++
// Copyright 2018 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 <string>
|
|
#include <vector>
|
|
|
|
#include "base/memory/ref_counted.h"
|
|
#include "base/test/scoped_feature_list.h"
|
|
#include "base/test/task_environment.h"
|
|
#include "net/base/address_family.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/ip_address.h"
|
|
#include "net/base/ip_endpoint.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/cert/ct_policy_enforcer.h"
|
|
#include "net/cert/mock_cert_verifier.h"
|
|
#include "net/dns/mock_host_resolver.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/http/transport_security_state.h"
|
|
#include "net/log/net_log.h"
|
|
#include "net/socket/connect_job_test_util.h"
|
|
#include "net/socket/connection_attempts.h"
|
|
#include "net/socket/ssl_client_socket.h"
|
|
#include "net/socket/stream_socket.h"
|
|
#include "net/socket/transport_client_socket_pool_test_util.h"
|
|
#include "net/ssl/ssl_config_service.h"
|
|
#include "net/ssl/test_ssl_config_service.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
#include "url/scheme_host_port.h"
|
|
#include "url/url_constants.h"
|
|
|
|
namespace net {
|
|
namespace {
|
|
|
|
const char kHostName[] = "unresolvable.host.name";
|
|
|
|
IPAddress ParseIP(const std::string& ip) {
|
|
IPAddress address;
|
|
CHECK(address.AssignFromIPLiteral(ip));
|
|
return address;
|
|
}
|
|
|
|
class TransportConnectJobTest : public WithTaskEnvironment,
|
|
public testing::Test {
|
|
public:
|
|
TransportConnectJobTest()
|
|
: WithTaskEnvironment(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
|
|
client_socket_factory_(NetLog::Get()),
|
|
common_connect_job_params_(
|
|
&client_socket_factory_,
|
|
&host_resolver_,
|
|
/*http_auth_cache=*/nullptr,
|
|
/*http_auth_handler_factory=*/nullptr,
|
|
/*spdy_session_pool=*/nullptr,
|
|
/*quic_supported_versions=*/nullptr,
|
|
/*quic_stream_factory=*/nullptr,
|
|
/*proxy_delegate=*/nullptr,
|
|
/*http_user_agent_settings=*/nullptr,
|
|
&ssl_client_context_,
|
|
/*socket_performance_watcher_factory=*/nullptr,
|
|
/*network_quality_estimator=*/nullptr,
|
|
NetLog::Get(),
|
|
/*websocket_endpoint_lock_manager=*/nullptr) {}
|
|
|
|
~TransportConnectJobTest() override = default;
|
|
|
|
static scoped_refptr<TransportSocketParams> DefaultParams() {
|
|
return base::MakeRefCounted<TransportSocketParams>(
|
|
url::SchemeHostPort(url::kHttpScheme, kHostName, 80),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
|
|
OnHostResolutionCallback(),
|
|
/*supported_alpns=*/base::flat_set<std::string>());
|
|
}
|
|
|
|
static scoped_refptr<TransportSocketParams> DefaultHttpsParams() {
|
|
return base::MakeRefCounted<TransportSocketParams>(
|
|
url::SchemeHostPort(url::kHttpsScheme, kHostName, 443),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
|
|
OnHostResolutionCallback(),
|
|
/*supported_alpns=*/base::flat_set<std::string>{"h2", "http/1.1"});
|
|
}
|
|
|
|
protected:
|
|
MockHostResolver host_resolver_{/*default_result=*/MockHostResolverBase::
|
|
RuleResolver::GetLocalhostResult()};
|
|
MockTransportClientSocketFactory client_socket_factory_;
|
|
TestSSLConfigService ssl_config_service_{SSLContextConfig{}};
|
|
MockCertVerifier cert_verifier_;
|
|
TransportSecurityState transport_security_state_;
|
|
DefaultCTPolicyEnforcer ct_policy_enforcer_;
|
|
SSLClientContext ssl_client_context_{&ssl_config_service_,
|
|
&cert_verifier_,
|
|
&transport_security_state_,
|
|
&ct_policy_enforcer_,
|
|
/*ssl_client_session_cache=*/nullptr,
|
|
/*sct_auditing_delegate=*/nullptr};
|
|
const CommonConnectJobParams common_connect_job_params_;
|
|
};
|
|
|
|
TEST_F(TransportConnectJobTest, HostResolutionFailure) {
|
|
host_resolver_.rules()->AddSimulatedTimeoutFailure(kHostName);
|
|
|
|
// Check sync and async failures.
|
|
for (bool host_resolution_synchronous : {false, true}) {
|
|
host_resolver_.set_synchronous_mode(host_resolution_synchronous);
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job,
|
|
ERR_NAME_NOT_RESOLVED,
|
|
host_resolution_synchronous);
|
|
EXPECT_THAT(transport_connect_job.GetResolveErrorInfo().error,
|
|
test::IsError(ERR_DNS_TIMED_OUT));
|
|
}
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, ConnectionFailure) {
|
|
for (bool host_resolution_synchronous : {false, true}) {
|
|
for (bool connection_synchronous : {false, true}) {
|
|
host_resolver_.set_synchronous_mode(host_resolution_synchronous);
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
connection_synchronous
|
|
? MockTransportClientSocketFactory::Type::kFailing
|
|
: MockTransportClientSocketFactory::Type::kPendingFailing);
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(
|
|
&transport_connect_job, ERR_CONNECTION_FAILED,
|
|
host_resolution_synchronous && connection_synchronous);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, HostResolutionTimeout) {
|
|
const base::TimeDelta kTinyTime = base::Microseconds(1);
|
|
|
|
// Make request hang.
|
|
host_resolver_.set_ondemand_mode(true);
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
ASSERT_THAT(transport_connect_job.Connect(), test::IsError(ERR_IO_PENDING));
|
|
|
|
// Right up until just before expiration, the job does not time out.
|
|
FastForwardBy(TransportConnectJob::ConnectionTimeout() - kTinyTime);
|
|
EXPECT_FALSE(test_delegate.has_result());
|
|
|
|
// But at the exact time of expiration, the job fails.
|
|
FastForwardBy(kTinyTime);
|
|
EXPECT_TRUE(test_delegate.has_result());
|
|
EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, ConnectionTimeout) {
|
|
const base::TimeDelta kTinyTime = base::Microseconds(1);
|
|
|
|
// Half the timeout time. In the async case, spend half the time waiting on
|
|
// host resolution, half on connecting.
|
|
const base::TimeDelta kFirstHalfOfTimeout =
|
|
TransportConnectJob::ConnectionTimeout() / 2;
|
|
|
|
const base::TimeDelta kSecondHalfOfTimeout =
|
|
TransportConnectJob::ConnectionTimeout() - kFirstHalfOfTimeout;
|
|
ASSERT_LE(kTinyTime, kSecondHalfOfTimeout);
|
|
|
|
// Make connection attempts hang.
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kStalled);
|
|
|
|
for (bool host_resolution_synchronous : {false, true}) {
|
|
host_resolver_.set_ondemand_mode(!host_resolution_synchronous);
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
EXPECT_THAT(transport_connect_job.Connect(), test::IsError(ERR_IO_PENDING));
|
|
|
|
// After half the timeout, connection does not timeout.
|
|
FastForwardBy(kFirstHalfOfTimeout);
|
|
EXPECT_FALSE(test_delegate.has_result());
|
|
|
|
// In the async case, the host resolution completes now.
|
|
if (!host_resolution_synchronous)
|
|
host_resolver_.ResolveOnlyRequestNow();
|
|
|
|
// After (almost) the second half of timeout, just before the full timeout
|
|
// period, the ConnectJob is still live.
|
|
FastForwardBy(kSecondHalfOfTimeout - kTinyTime);
|
|
EXPECT_FALSE(test_delegate.has_result());
|
|
|
|
// But at the exact timeout time, the job fails.
|
|
FastForwardBy(kTinyTime);
|
|
EXPECT_TRUE(test_delegate.has_result());
|
|
EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT));
|
|
}
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, ConnectionSuccess) {
|
|
for (bool host_resolution_synchronous : {false, true}) {
|
|
for (bool connection_synchronous : {false, true}) {
|
|
host_resolver_.set_synchronous_mode(host_resolution_synchronous);
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
connection_synchronous
|
|
? MockTransportClientSocketFactory::Type::kSynchronous
|
|
: MockTransportClientSocketFactory::Type::kPending);
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(
|
|
&transport_connect_job, OK,
|
|
host_resolution_synchronous && connection_synchronous);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, LoadState) {
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kStalled);
|
|
host_resolver_.set_ondemand_mode(true);
|
|
host_resolver_.rules()->AddIPLiteralRule(kHostName, "1:abcd::3:4:ff,1.1.1.1",
|
|
std::string());
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, /*net_log=*/nullptr);
|
|
EXPECT_THAT(transport_connect_job.Connect(), test::IsError(ERR_IO_PENDING));
|
|
|
|
// The job is initially waiting on DNS.
|
|
EXPECT_EQ(transport_connect_job.GetLoadState(), LOAD_STATE_RESOLVING_HOST);
|
|
|
|
// Complete DNS. It is now waiting on a TCP connection.
|
|
host_resolver_.ResolveOnlyRequestNow();
|
|
RunUntilIdle();
|
|
EXPECT_EQ(transport_connect_job.GetLoadState(), LOAD_STATE_CONNECTING);
|
|
|
|
// Wait for the IPv4 job to start. The job is still waiting on a TCP
|
|
// connection.
|
|
FastForwardBy(TransportConnectJob::kIPv6FallbackTime +
|
|
base::Milliseconds(50));
|
|
EXPECT_EQ(transport_connect_job.GetLoadState(), LOAD_STATE_CONNECTING);
|
|
}
|
|
|
|
// TODO(crbug.com/1206799): Set up `host_resolver_` to require the expected
|
|
// scheme.
|
|
TEST_F(TransportConnectJobTest, HandlesHttpsEndpoint) {
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
base::MakeRefCounted<TransportSocketParams>(
|
|
url::SchemeHostPort(url::kHttpsScheme, kHostName, 80),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
|
|
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 */);
|
|
}
|
|
|
|
// TODO(crbug.com/1206799): Set up `host_resolver_` to require the expected
|
|
// lack of scheme.
|
|
TEST_F(TransportConnectJobTest, HandlesNonStandardEndpoint) {
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
base::MakeRefCounted<TransportSocketParams>(
|
|
HostPortPair(kHostName, 80), NetworkAnonymizationKey(),
|
|
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 */);
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, SecureDnsPolicy) {
|
|
for (auto secure_dns_policy :
|
|
{SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}) {
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
base::MakeRefCounted<TransportSocketParams>(
|
|
url::SchemeHostPort(url::kHttpScheme, kHostName, 80),
|
|
NetworkAnonymizationKey(), secure_dns_policy,
|
|
OnHostResolutionCallback(),
|
|
/*supported_alpns=*/base::flat_set<std::string>{}),
|
|
&test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
false /* expect_sync_result */);
|
|
EXPECT_EQ(secure_dns_policy, host_resolver_.last_secure_dns_policy());
|
|
}
|
|
}
|
|
|
|
// Test the case of the IPv6 address stalling, and falling back to the IPv4
|
|
// socket which finishes first.
|
|
TEST_F(TransportConnectJobTest, IPv6FallbackSocketIPv4FinishesFirst) {
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// The first IPv6 attempt fails.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1:abcd::3:4:ff"), 80)}),
|
|
// The second IPv6 attempt stalls.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kStalled,
|
|
std::vector{IPEndPoint(ParseIP("2:abcd::3:4:ff"), 80)}),
|
|
// After a timeout, we try the IPv4 address.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kPending,
|
|
std::vector{IPEndPoint(ParseIP("2.2.2.2"), 80)})};
|
|
|
|
client_socket_factory_.SetRules(rules);
|
|
|
|
// Resolve an AddressList with two IPv6 addresses and then a IPv4 address.
|
|
host_resolver_.rules()->AddIPLiteralRule(
|
|
kHostName, "1:abcd::3:4:ff,2:abcd::3:4:ff,2.2.2.2", std::string());
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
false /* expect_sync_result */);
|
|
|
|
IPEndPoint endpoint;
|
|
test_delegate.socket()->GetLocalAddress(&endpoint);
|
|
EXPECT_TRUE(endpoint.address().IsIPv4());
|
|
|
|
// Check that the failed connection attempt is collected.
|
|
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
|
|
ASSERT_EQ(1u, attempts.size());
|
|
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1:abcd::3:4:ff"), 80));
|
|
|
|
EXPECT_EQ(3, client_socket_factory_.allocation_count());
|
|
}
|
|
|
|
// Test the case of the IPv6 address being slow, thus falling back to trying to
|
|
// connect to the IPv4 address, but having the connect to the IPv6 address
|
|
// finish first.
|
|
TEST_F(TransportConnectJobTest, IPv6FallbackSocketIPv6FinishesFirst) {
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// The first IPv6 attempt ultimately succeeds, but is delayed.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kDelayed,
|
|
std::vector{IPEndPoint(ParseIP("2:abcd::3:4:ff"), 80)}),
|
|
// The first IPv4 attempt fails.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2.2.2.2"), 80)}),
|
|
// The second IPv4 attempt stalls.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kStalled,
|
|
std::vector{IPEndPoint(ParseIP("3.3.3.3"), 80)})};
|
|
|
|
client_socket_factory_.SetRules(rules);
|
|
client_socket_factory_.set_delay(TransportConnectJob::kIPv6FallbackTime +
|
|
base::Milliseconds(50));
|
|
|
|
// Resolve an AddressList with a IPv6 address first and then a IPv4 address.
|
|
host_resolver_.rules()->AddIPLiteralRule(
|
|
kHostName, "2:abcd::3:4:ff,2.2.2.2,3.3.3.3", std::string());
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
false /* expect_sync_result */);
|
|
|
|
IPEndPoint endpoint;
|
|
test_delegate.socket()->GetLocalAddress(&endpoint);
|
|
EXPECT_TRUE(endpoint.address().IsIPv6());
|
|
|
|
// Check that the failed connection attempt on the fallback socket is
|
|
// collected.
|
|
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
|
|
ASSERT_EQ(1u, attempts.size());
|
|
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("2.2.2.2"), 80));
|
|
|
|
EXPECT_EQ(3, client_socket_factory_.allocation_count());
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, IPv6NoIPv4AddressesToFallbackTo) {
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kDelayed);
|
|
|
|
// Resolve an AddressList with only IPv6 addresses.
|
|
host_resolver_.rules()->AddIPLiteralRule(
|
|
kHostName, "2:abcd::3:4:ff,3:abcd::3:4:ff", std::string());
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
false /* expect_sync_result */);
|
|
|
|
IPEndPoint endpoint;
|
|
test_delegate.socket()->GetLocalAddress(&endpoint);
|
|
EXPECT_TRUE(endpoint.address().IsIPv6());
|
|
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
|
|
EXPECT_EQ(0u, attempts.size());
|
|
EXPECT_EQ(1, client_socket_factory_.allocation_count());
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, IPv4HasNoFallback) {
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kDelayed);
|
|
|
|
// Resolve an AddressList with only IPv4 addresses.
|
|
host_resolver_.rules()->AddIPLiteralRule(kHostName, "1.1.1.1", std::string());
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
false /* expect_sync_result */);
|
|
|
|
IPEndPoint endpoint;
|
|
test_delegate.socket()->GetLocalAddress(&endpoint);
|
|
EXPECT_TRUE(endpoint.address().IsIPv4());
|
|
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
|
|
EXPECT_EQ(0u, attempts.size());
|
|
EXPECT_EQ(1, client_socket_factory_.allocation_count());
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, DnsAliases) {
|
|
host_resolver_.set_synchronous_mode(true);
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kSynchronous);
|
|
|
|
// Resolve an AddressList with DNS aliases.
|
|
std::vector<std::string> aliases({"alias1", "alias2", kHostName});
|
|
host_resolver_.rules()->AddIPLiteralRuleWithDnsAliases(kHostName, "2.2.2.2",
|
|
std::move(aliases));
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
true /* expect_sync_result */);
|
|
|
|
// Verify that the elements of the alias list are those from the
|
|
// parameter vector.
|
|
EXPECT_THAT(test_delegate.socket()->GetDnsAliases(),
|
|
testing::ElementsAre("alias1", "alias2", kHostName));
|
|
}
|
|
|
|
TEST_F(TransportConnectJobTest, NoAdditionalDnsAliases) {
|
|
host_resolver_.set_synchronous_mode(true);
|
|
client_socket_factory_.set_default_client_socket_type(
|
|
MockTransportClientSocketFactory::Type::kSynchronous);
|
|
|
|
// Resolve an AddressList without additional DNS aliases. (The parameter
|
|
// is an empty vector.)
|
|
std::vector<std::string> aliases;
|
|
host_resolver_.rules()->AddIPLiteralRuleWithDnsAliases(kHostName, "2.2.2.2",
|
|
std::move(aliases));
|
|
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultParams(), &test_delegate, nullptr /* net_log */);
|
|
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
true /* expect_sync_result */);
|
|
|
|
// Verify that the alias list only contains kHostName.
|
|
EXPECT_THAT(test_delegate.socket()->GetDnsAliases(),
|
|
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,
|
|
MockHostResolverBase::RuleResolver::RuleResult(std::vector{endpoint}));
|
|
|
|
// The first access succeeds.
|
|
MockTransportClientSocketFactory::Rule rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8443)});
|
|
client_socket_factory_.SetRules(base::make_span(&rule, 1u));
|
|
|
|
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 = transport_connect_job.GetConnectionAttempts();
|
|
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, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// `endpoints[0]`'s addresses each fail.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[0]}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[1]}),
|
|
// `endpoints[1]` is skipped because the ALPN is not compatible.
|
|
// `endpoints[2]`'s first address succeeds.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{endpoints[2].ip_endpoints[0]}),
|
|
};
|
|
|
|
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 = 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));
|
|
}
|
|
|
|
// 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, MockHostResolverBase::RuleResolver::RuleResult(
|
|
std::vector{endpoint1, endpoint2, endpoint3}));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// `endpoint1`'s IPv6 address fails, but takes long enough that the IPv4
|
|
// fallback runs.
|
|
//
|
|
// TODO(davidben): If the network is such that IPv6 connection attempts
|
|
// always stall, we will never try `endpoint2`. Should Happy Eyeballs
|
|
// logic happen before HTTPS RR. Or perhaps we should implement a more
|
|
// Happy-Eyeballs-v2-like strategy.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kDelayedFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8441)}),
|
|
|
|
// `endpoint1`'s IPv4 address fails immediately.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1.1.1.1"), 8441)}),
|
|
|
|
// `endpoint2` is skipped because the ALPN is not compatible.
|
|
|
|
// `endpoint3`'s IPv6 address never completes.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kStalled,
|
|
std::vector{IPEndPoint(ParseIP("3::"), 443)}),
|
|
// `endpoint3`'s IPv4 address succeeds.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{IPEndPoint(ParseIP("3.3.3.3"), 443)}),
|
|
};
|
|
client_socket_factory_.SetRules(rules);
|
|
client_socket_factory_.set_delay(TransportConnectJob::kIPv6FallbackTime +
|
|
base::Milliseconds(50));
|
|
|
|
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 = 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.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 `TransportConnectJob` will not continue trying routes given
|
|
// ERR_NETWORK_IO_SUSPENDED.
|
|
TEST_F(TransportConnectJobTest, MultipleRoutesSuspended) {
|
|
std::vector<HostResolverEndpointResult> endpoints(2);
|
|
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8443)};
|
|
endpoints[0].metadata.supported_protocol_alpns = {"h3", "h2", "http/1.1"};
|
|
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 443)};
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
// The first connect attempt will fail with `ERR_NETWORK_IO_SUSPENDED`.
|
|
// `TransportConnectJob` should not attempt routes after receiving this error.
|
|
MockTransportClientSocketFactory::Rule rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
endpoints[0].ip_endpoints, ERR_NETWORK_IO_SUSPENDED);
|
|
client_socket_factory_.SetRules(base::make_span(&rule, 1u));
|
|
|
|
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_NETWORK_IO_SUSPENDED,
|
|
/*expect_sync_result=*/false);
|
|
|
|
// Check that failed connection attempts are reported.
|
|
ConnectionAttempts attempts = transport_connect_job.GetConnectionAttempts();
|
|
ASSERT_EQ(1u, attempts.size());
|
|
EXPECT_THAT(attempts[0].result, test::IsError(ERR_NETWORK_IO_SUSPENDED));
|
|
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1::"), 8443));
|
|
}
|
|
|
|
// 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, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
// `endpoints[2]`'s first address succeeds.
|
|
MockTransportClientSocketFactory::Rule rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{endpoints[2].ip_endpoints[0]});
|
|
client_socket_factory_.SetRules(base::make_span(&rule, 1u));
|
|
|
|
// 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, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// `endpoints[0]`'s addresses each fail.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[0]}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[1]}),
|
|
// `endpoints[1]` is skipped because the ALPN is not compatible.
|
|
// `endpoints[2]`'s addresses each fail.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[2].ip_endpoints[0]}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[2].ip_endpoints[1]}),
|
|
};
|
|
|
|
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, MockHostResolverBase::RuleResolver::RuleResult(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, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// `endpoints[0]`'s addresses each fail.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[0]}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{endpoints[0].ip_endpoints[1]}),
|
|
// `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));
|
|
}
|
|
|
|
// `GetHostResolverEndpointResult` should surface information about the endpoint
|
|
// that was actually used.
|
|
TEST_F(TransportConnectJobTest, GetHostResolverEndpointResult) {
|
|
std::vector<HostResolverEndpointResult> endpoints(4);
|
|
// `endpoints[0]` will be skipped due to ALPN mismatch.
|
|
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoints[0].metadata.supported_protocol_alpns = {"h3"};
|
|
endpoints[0].metadata.ech_config_list = {1, 2, 3, 4};
|
|
// `endpoints[1]` will be skipped due to connection failure.
|
|
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)};
|
|
endpoints[1].metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoints[1].metadata.ech_config_list = {5, 6, 7, 8};
|
|
// `endpoints[2]` will succeed.
|
|
endpoints[2].ip_endpoints = {IPEndPoint(ParseIP("3::"), 8443)};
|
|
endpoints[2].metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoints[2].metadata.ech_config_list = {9, 10, 11, 12};
|
|
// `endpoints[3]` will be not be tried because `endpoints[2]` will already
|
|
// have succeeded.
|
|
endpoints[3].ip_endpoints = {IPEndPoint(ParseIP("4::"), 8444)};
|
|
endpoints[3].metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoints[3].metadata.ech_config_list = {13, 14, 15, 16};
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 8442)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{IPEndPoint(ParseIP("3::"), 8443)}),
|
|
};
|
|
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);
|
|
|
|
EXPECT_EQ(transport_connect_job.GetHostResolverEndpointResult(),
|
|
endpoints[2]);
|
|
}
|
|
|
|
// If the client and server both support ECH, TransportConnectJob should switch
|
|
// to SVCB-reliant mode and disable the A/AAAA fallback.
|
|
TEST_F(TransportConnectJobTest, SvcbReliantIfEch) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
|
|
|
|
HostResolverEndpointResult endpoint1, endpoint2, endpoint3;
|
|
endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoint1.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint1.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)};
|
|
endpoint2.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint2.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint3.ip_endpoints = {IPEndPoint(ParseIP("3::"), 443)};
|
|
// `endpoint3` has no `supported_protocol_alpns` and is thus a fallback route.
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(
|
|
std::vector{endpoint1, endpoint2, endpoint3}));
|
|
|
|
// `TransportConnectJob` should not try `endpoint3`.
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8441)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 8442)}),
|
|
};
|
|
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);
|
|
|
|
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("2::"), 8442));
|
|
}
|
|
|
|
// SVCB-reliant mode should be disabled for ECH servers when ECH is disabled via
|
|
// `base::Feature`.
|
|
TEST_F(TransportConnectJobTest, SvcbOptionalIfEchDisabledFeature) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndDisableFeature(features::kEncryptedClientHello);
|
|
|
|
HostResolverEndpointResult endpoint1, endpoint2, endpoint3;
|
|
endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoint1.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint1.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)};
|
|
endpoint2.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint2.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint3.ip_endpoints = {IPEndPoint(ParseIP("3::"), 443)};
|
|
// `endpoint3` has no `supported_protocol_alpns` and is thus a fallback route.
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(
|
|
std::vector{endpoint1, endpoint2, endpoint3}));
|
|
|
|
// `TransportConnectJob` should try `endpoint3`.
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8441)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 8442)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{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);
|
|
}
|
|
|
|
// SVCB-reliant mode should be disabled for ECH servers when ECH is disabled via
|
|
// config.
|
|
TEST_F(TransportConnectJobTest, SvcbOptionalIfEchDisabledConfig) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
|
|
|
|
SSLContextConfig config;
|
|
config.ech_enabled = false;
|
|
ssl_config_service_.UpdateSSLConfigAndNotify(config);
|
|
|
|
HostResolverEndpointResult endpoint1, endpoint2, endpoint3;
|
|
endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoint1.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint1.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)};
|
|
endpoint2.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint2.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint3.ip_endpoints = {IPEndPoint(ParseIP("3::"), 443)};
|
|
// `endpoint3` has no `supported_protocol_alpns` and is thus a fallback route.
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(
|
|
std::vector{endpoint1, endpoint2, endpoint3}));
|
|
|
|
// `TransportConnectJob` should try `endpoint3`.
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8441)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 8442)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{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);
|
|
}
|
|
|
|
// SVCB-reliant mode should be disabled if not all SVCB/HTTPS records include
|
|
// ECH.
|
|
TEST_F(TransportConnectJobTest, SvcbOptionalIfEchInconsistent) {
|
|
base::test::ScopedFeatureList feature_list;
|
|
feature_list.InitAndEnableFeature(features::kEncryptedClientHello);
|
|
|
|
HostResolverEndpointResult endpoint1, endpoint2, endpoint3;
|
|
endpoint1.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoint1.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint1.metadata.ech_config_list = {1, 2, 3, 4};
|
|
endpoint2.ip_endpoints = {IPEndPoint(ParseIP("2::"), 8442)};
|
|
endpoint2.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
endpoint2.metadata.ech_config_list = {};
|
|
endpoint3.ip_endpoints = {IPEndPoint(ParseIP("3::"), 443)};
|
|
// `endpoint3` has no `supported_protocol_alpns` and is thus a fallback route.
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(
|
|
std::vector{endpoint1, endpoint2, endpoint3}));
|
|
|
|
// `TransportConnectJob` should try `endpoint3`.
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 8441)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 8442)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
std::vector{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);
|
|
}
|
|
|
|
// Overriding the endpoint results should skip DNS resolution.
|
|
TEST_F(TransportConnectJobTest, EndpointResultOverride) {
|
|
// Make DNS resolution fail, to confirm we don't use the result.
|
|
host_resolver_.rules()->AddRule(kHostName, ERR_FAILED);
|
|
|
|
// `TransportConnectJob` should try `endpoint`.
|
|
HostResolverEndpointResult endpoint;
|
|
endpoint.ip_endpoints = {IPEndPoint(ParseIP("1::"), 8441)};
|
|
endpoint.metadata.supported_protocol_alpns = {"http/1.1"};
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kSynchronous,
|
|
endpoint.ip_endpoints),
|
|
};
|
|
client_socket_factory_.SetRules(rules);
|
|
|
|
TransportConnectJob::EndpointResultOverride override(
|
|
endpoint, {"alias.example", kHostName});
|
|
TestConnectJobDelegate test_delegate;
|
|
TransportConnectJob transport_connect_job(
|
|
DEFAULT_PRIORITY, SocketTag(), &common_connect_job_params_,
|
|
DefaultHttpsParams(), &test_delegate, /*net_log=*/nullptr, override);
|
|
test_delegate.StartJobExpectingResult(&transport_connect_job, OK,
|
|
/*expect_sync_result=*/true);
|
|
|
|
// Verify information is reported from the override.
|
|
EXPECT_EQ(transport_connect_job.GetHostResolverEndpointResult(), endpoint);
|
|
EXPECT_THAT(test_delegate.socket()->GetDnsAliases(),
|
|
testing::ElementsAre("alias.example", kHostName));
|
|
}
|
|
|
|
// If two `HostResolverEndpointResult`s share an IP endpoint,
|
|
// `TransportConnectJob` should not try to connect a second time.
|
|
TEST_F(TransportConnectJobTest, DedupIPEndPoints) {
|
|
std::vector<HostResolverEndpointResult> endpoints(4);
|
|
// Some initial IPEndPoints.
|
|
endpoints[0].ip_endpoints = {IPEndPoint(ParseIP("1::"), 443),
|
|
IPEndPoint(ParseIP("1.1.1.1"), 443)};
|
|
endpoints[0].metadata.supported_protocol_alpns = {"h2", "http/1.1"};
|
|
// Contains a new IPEndPoint, but no common protocols.
|
|
endpoints[1].ip_endpoints = {IPEndPoint(ParseIP("2::"), 443)};
|
|
endpoints[1].metadata.supported_protocol_alpns = {"h3"};
|
|
// Contains mixture of previously seen and new IPEndPoints, so we should only
|
|
// try a subset of them.
|
|
endpoints[2].ip_endpoints = {
|
|
// Duplicate from `endpoints[0]`, should be filtered out.
|
|
IPEndPoint(ParseIP("1::"), 443),
|
|
// Same IP but new port. Should be used.
|
|
IPEndPoint(ParseIP("1::"), 444),
|
|
// Duplicate from `endpoints[1]`, but `endpoints[1]` was dropped, so this
|
|
// should be used.
|
|
IPEndPoint(ParseIP("2::"), 443),
|
|
// Duplicate from `endpoints[0]`, should be filtered out.
|
|
IPEndPoint(ParseIP("1.1.1.1"), 443),
|
|
// New endpoint. Should be used.
|
|
IPEndPoint(ParseIP("2.2.2.2"), 443)};
|
|
endpoints[2].metadata.supported_protocol_alpns = {"h2", "http/1.1"};
|
|
// Contains only previously seen IPEndPoints, so should be filtered out
|
|
// entirely.
|
|
endpoints[3].ip_endpoints = {IPEndPoint(ParseIP("1::"), 443),
|
|
IPEndPoint(ParseIP("1::"), 444),
|
|
IPEndPoint(ParseIP("2.2.2.2"), 443)};
|
|
endpoints[3].metadata.supported_protocol_alpns = {"h2", "http/1.1"};
|
|
host_resolver_.rules()->AddRule(
|
|
kHostName, MockHostResolverBase::RuleResolver::RuleResult(endpoints));
|
|
|
|
MockTransportClientSocketFactory::Rule rules[] = {
|
|
// First, try `endpoints[0]`'s addresses.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 443)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1.1.1.1"), 443)}),
|
|
|
|
// `endpoints[1]` is unusable, so it is ignored, including for purposes of
|
|
// duplicate endpoints.
|
|
|
|
// Only new IP endpoints from `endpoints[2]` should be considered. Note
|
|
// different ports count as different endpoints.
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("1::"), 444)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2::"), 443)}),
|
|
MockTransportClientSocketFactory::Rule(
|
|
MockTransportClientSocketFactory::Type::kFailing,
|
|
std::vector{IPEndPoint(ParseIP("2.2.2.2"), 443)}),
|
|
|
|
// `endpoints[3]` only contains duplicate IP endpoints and should be
|
|
// skipped.
|
|
};
|
|
|
|
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(5u, attempts.size());
|
|
EXPECT_THAT(attempts[0].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[0].endpoint, IPEndPoint(ParseIP("1::"), 443));
|
|
EXPECT_THAT(attempts[1].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[1].endpoint, IPEndPoint(ParseIP("1.1.1.1"), 443));
|
|
EXPECT_THAT(attempts[2].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[2].endpoint, IPEndPoint(ParseIP("1::"), 444));
|
|
EXPECT_THAT(attempts[3].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[3].endpoint, IPEndPoint(ParseIP("2::"), 443));
|
|
EXPECT_THAT(attempts[4].result, test::IsError(ERR_CONNECTION_FAILED));
|
|
EXPECT_EQ(attempts[4].endpoint, IPEndPoint(ParseIP("2.2.2.2"), 443));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace net
|