blob: ced15c35387412f6bcfdda3212c42e907bb89db2 [file] [log] [blame]
// Copyright 2016 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/http/http_stream_factory_job_controller.h"
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/values.h"
#include "net/base/completion_once_callback.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/base/proxy_string_util.h"
#include "net/base/schemeful_site.h"
#include "net/base/session_usage.h"
#include "net/base/test_proxy_delegate.h"
#include "net/dns/dns_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/alternative_service.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session_peer.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_server_properties.h"
#include "net/http/http_server_properties_manager.h"
#include "net/http/http_stream_factory.h"
#include "net/http/http_stream_factory_job.h"
#include "net/http/http_stream_factory_test_util.h"
#include "net/http/http_stream_key.h"
#include "net/http/http_stream_pool.h"
#include "net/http/http_stream_pool_group.h"
#include "net/http/http_stream_pool_test_util.h"
#include "net/log/net_log.h"
#include "net/log/net_log_with_source.h"
#include "net/log/test_net_log.h"
#include "net/log/test_net_log_util.h"
#include "net/proxy_resolution/configured_proxy_resolution_service.h"
#include "net/proxy_resolution/mock_proxy_resolver.h"
#include "net/proxy_resolution/proxy_config_service_fixed.h"
#include "net/proxy_resolution/proxy_info.h"
#include "net/proxy_resolution/proxy_list.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/quic/address_utils.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/mock_crypto_client_stream_factory.h"
#include "net/quic/mock_quic_context.h"
#include "net/quic/mock_quic_data.h"
#include "net/quic/quic_chromium_connection_helper.h"
#include "net/quic/quic_http_stream.h"
#include "net/quic/quic_server_info.h"
#include "net/quic/quic_session_alias_key.h"
#include "net/quic/quic_session_pool.h"
#include "net/quic/quic_session_pool_peer.h"
#include "net/quic/quic_test_packet_maker.h"
#include "net/quic/test_quic_crypto_client_config_handle.h"
#include "net/socket/socket_test_util.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/spdy/spdy_session_key.h"
#include "net/spdy/spdy_test_util_common.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "net/test/test_with_task_environment.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/crypto_test_utils.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/mock_connection_id_generator.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/static_http_user_agent_settings.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
using ::testing::_;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::IsEmpty;
using ::testing::Key;
using ::testing::SizeIs;
namespace net::test {
namespace {
const char kServerHostname[] = "www.example.com";
const std::string_view kHttpTestUrls[] = {"http://www.example.com",
"https://www.example.com"};
// The default delay for main job defined in QuicSessionPool::
// GetTimeDelayForWaitingJob().
const int kDefaultDelayMilliSecsForWaitingJob = 300;
// Phases in which errors will happen for HTTP, HTTPS and SOCKS5 tests.
enum class TcpErrorPhase {
kHostResolution,
kTcpConnect,
kProxySslHandshake,
kTunnelRead,
};
// Defined so it can be printed by Gtest.
template <typename Sink>
void AbslStringify(Sink& sink, TcpErrorPhase e) {
switch (e) {
case TcpErrorPhase::kHostResolution:
sink.Append("HostResolution");
break;
case TcpErrorPhase::kTcpConnect:
sink.Append("TcpConnect");
break;
case TcpErrorPhase::kProxySslHandshake:
sink.Append("ProxySslHandshake");
break;
case TcpErrorPhase::kTunnelRead:
sink.Append("TunnelRead");
}
}
// Phases in which errors will happen for QUIC tests.
enum class QuicErrorPhase { kHostResolution, kProxySession, kUdpConnect };
// Defined so it can be printed by Gtest.
template <typename Sink>
void AbslStringify(Sink& sink, QuicErrorPhase e) {
switch (e) {
case QuicErrorPhase::kHostResolution:
sink.Append("HostResolution");
break;
case QuicErrorPhase::kProxySession:
sink.Append("ProxySession");
break;
case QuicErrorPhase::kUdpConnect:
sink.Append("UdpConnect");
break;
}
}
class FailingProxyResolverFactory : public ProxyResolverFactory {
public:
FailingProxyResolverFactory() : ProxyResolverFactory(false) {}
// ProxyResolverFactory override.
int CreateProxyResolver(const scoped_refptr<PacFileData>& script_data,
std::unique_ptr<ProxyResolver>* result,
CompletionOnceCallback callback,
std::unique_ptr<Request>* request) override {
return ERR_PAC_SCRIPT_FAILED;
}
};
// A subclass of QuicChromiumClientSession that "goes away" right after
// CreateHandle was called.
class MockQuicChromiumClientSession : public QuicChromiumClientSession {
public:
using QuicChromiumClientSession::QuicChromiumClientSession;
std::unique_ptr<QuicChromiumClientSession::Handle> CreateHandle(
url::SchemeHostPort destination) override {
auto res = QuicChromiumClientSession::CreateHandle(destination);
// Make the session go away right after it was created.
SetGoingAwayForTesting(true);
return res;
}
};
// A mock HttpServerProperties::PrefDelegate that never finishes loading, so
// HttpServerProperties::IsInitialized() always returns false.
class MockPrefDelegate : public HttpServerProperties::PrefDelegate {
public:
MockPrefDelegate() = default;
MockPrefDelegate(const MockPrefDelegate&) = delete;
MockPrefDelegate& operator=(const MockPrefDelegate&) = delete;
~MockPrefDelegate() override = default;
// HttpServerProperties::PrefDelegate implementation:
const base::Value::Dict& GetServerProperties() const override {
return empty_dict_;
}
void SetServerProperties(base::Value::Dict dict,
base::OnceClosure callback) override {}
void WaitForPrefLoad(base::OnceClosure pref_loaded_callback) override {}
base::Value::Dict empty_dict_;
};
// A `TestProxyDelegate` which always sets a `ProxyChain` with
// `is_for_ip_protection` set to true on the `ProxyInfo` it receives in
// `OnResolveProxy()`.
class TestProxyDelegateForIpProtection : public TestProxyDelegate {
public:
TestProxyDelegateForIpProtection() {
set_proxy_chain(
ProxyChain::ForIpProtection({ProxyServer::FromSchemeHostAndPort(
ProxyServer::SCHEME_HTTPS, "ip-pro", 443)}));
set_extra_header_name(HttpRequestHeaders::kAuthorization);
}
void OnResolveProxy(const GURL& url,
const NetworkAnonymizationKey& network_anonymization_key,
const std::string& method,
const ProxyRetryInfoMap& proxy_retry_info,
ProxyInfo* result) override {
ProxyList proxy_list;
CHECK(proxy_chain().is_for_ip_protection());
proxy_list.AddProxyChain(proxy_chain());
proxy_list.AddProxyChain(ProxyChain::ForIpProtection({}));
result->UseProxyList(proxy_list);
}
net::Error OnTunnelHeadersReceived(
const net::ProxyChain& proxy_chain,
size_t chain_index,
const net::HttpResponseHeaders& response_headers,
net::CompletionOnceCallback callback) override {
if (response_headers.response_code() == 502) {
return net::ERR_PROXY_UNABLE_TO_CONNECT_TO_DESTINATION;
}
return net::OK;
}
};
} // anonymous namespace
class HttpStreamFactoryJobPeer {
public:
// Returns `num_streams_` of `job`. It should be 0 for non-preconnect Jobs.
static int GetNumStreams(const HttpStreamFactory::Job* job) {
return job->num_streams_;
}
// Return SpdySessionKey of `job`.
static const SpdySessionKey GetSpdySessionKey(
const HttpStreamFactory::Job* job) {
return job->spdy_session_key_;
}
static void SetShouldReconsiderProxy(HttpStreamFactory::Job* job) {
job->should_reconsider_proxy_ = true;
}
static void SetStream(HttpStreamFactory::Job* job,
std::unique_ptr<HttpStream> http_stream) {
job->stream_ = std::move(http_stream);
}
static void SetQuicConnectionFailedOnDefaultNetwork(
HttpStreamFactory::Job* job) {
job->quic_request_.OnConnectionFailedOnDefaultNetwork();
}
};
class JobControllerPeer {
public:
static bool main_job_is_blocked(
HttpStreamFactory::JobController* job_controller) {
return job_controller->main_job_is_blocked_;
}
static bool main_job_is_resumed(
HttpStreamFactory::JobController* job_controller) {
return job_controller->main_job_is_resumed_;
}
static void InitializeProxyInfo(
HttpStreamFactory::JobController* job_controller) {
job_controller->proxy_info_.UseDirect();
}
static AlternativeServiceInfo GetAlternativeServiceInfoFor(
HttpStreamFactory::JobController* job_controller,
const HttpRequestInfo& request_info,
HttpStreamRequest::Delegate* delegate,
HttpStreamRequest::StreamType stream_type) {
return job_controller->GetAlternativeServiceInfoFor(
HttpStreamFactory::StreamRequestInfo(request_info), delegate,
stream_type);
}
static quic::ParsedQuicVersion SelectQuicVersion(
HttpStreamFactory::JobController* job_controller,
const quic::ParsedQuicVersionVector& advertised_versions) {
return job_controller->SelectQuicVersion(advertised_versions);
}
static void SetAltJobFailedOnDefaultNetwork(
HttpStreamFactory::JobController* job_controller) {
DCHECK(job_controller->alternative_job() != nullptr);
HttpStreamFactoryJobPeer::SetQuicConnectionFailedOnDefaultNetwork(
job_controller->alternative_job_.get());
}
static void SetDnsAlpnH3JobFailedOnDefaultNetwork(
HttpStreamFactory::JobController* job_controller) {
DCHECK(job_controller->dns_alpn_h3_job() != nullptr);
HttpStreamFactoryJobPeer::SetQuicConnectionFailedOnDefaultNetwork(
job_controller->dns_alpn_h3_job_.get());
}
};
class HttpStreamFactoryJobControllerTestBase : public TestWithTaskEnvironment {
public:
explicit HttpStreamFactoryJobControllerTestBase(
bool happy_eyeballs_v3_enabled,
std::vector<base::test::FeatureRef> enabled_features = {})
: TestWithTaskEnvironment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME),
happy_eyeballs_v3_enabled_(happy_eyeballs_v3_enabled) {
std::vector<base::test::FeatureRef> disabled_features;
if (happy_eyeballs_v3_enabled_) {
enabled_features.emplace_back(features::kHappyEyeballsV3);
} else {
disabled_features.emplace_back(features::kHappyEyeballsV3);
}
feature_list_.InitWithFeatures(enabled_features, disabled_features);
FLAGS_quic_enable_http3_grease_randomness = false;
CreateSessionDeps();
}
void DestroySession() {
factory_ = nullptr;
job_controller_ = nullptr;
session_.reset();
}
// Creates / re-creates `session_deps_`, and clears test fixture fields
// referencing it.
void CreateSessionDeps() {
DestroySession();
session_deps_.proxy_resolution_service->SetProxyDelegate(nullptr);
session_deps_ = SpdySessionDependencies(
ConfiguredProxyResolutionService::CreateDirect());
session_deps_.enable_quic = true;
session_deps_.host_resolver->set_synchronous_mode(true);
session_deps_.http_user_agent_settings =
std::make_unique<StaticHttpUserAgentSettings>("*", "test-ua");
if (base::FeatureList::IsEnabled(features::kHappyEyeballsV3)) {
auto host_resolver = std::make_unique<FakeServiceEndpointResolver>();
// This configures the FakeServiceEndpointResolver in much the same way
// SpdySessionDeps configures the default MockHostResolver.
host_resolver->ConfigureDefaultResolution()
.set_start_result(OK)
.add_endpoint(ServiceEndpointBuilder().add_v4("127.0.2.1").endpoint())
.set_crypto_ready(true);
session_deps_.alternate_host_resolver = std::move(host_resolver);
}
}
void SetPreconnect() {
ASSERT_FALSE(session_deps_.proxy_delegate);
is_preconnect_ = true;
}
void DisableIPBasedPoolingForH2() {
ASSERT_FALSE(session_deps_.proxy_delegate);
enable_ip_based_pooling_for_h2_ = false;
}
void SetNotDelayMainJobWithAvailableSpdySession() {
ASSERT_FALSE(session_deps_.proxy_delegate);
delay_main_job_with_available_spdy_session_ = false;
}
void DisableAlternativeServices() {
ASSERT_FALSE(session_deps_.proxy_delegate);
enable_alternative_services_ = false;
}
void SkipCreatingJobController() {
ASSERT_FALSE(job_controller_);
create_job_controller_ = false;
}
void Initialize(const HttpRequestInfo& request_info) {
ASSERT_FALSE(session_deps_.proxy_delegate);
session_deps_.proxy_delegate = std::make_unique<TestProxyDelegate>();
if (quic_data_) {
quic_data_->AddSocketDataToFactory(session_deps_.socket_factory.get());
}
if (quic_data2_) {
quic_data2_->AddSocketDataToFactory(session_deps_.socket_factory.get());
}
if (tcp_data_) {
session_deps_.socket_factory->AddSocketDataProvider(tcp_data_.get());
}
if (tcp_data2_) {
session_deps_.socket_factory->AddSocketDataProvider(tcp_data2_.get());
}
session_deps_.proxy_resolution_service->SetProxyDelegate(
session_deps_.proxy_delegate.get());
session_deps_.net_log = NetLog::Get();
HttpNetworkSessionParams params =
SpdySessionDependencies::CreateSessionParams(&session_deps_);
HttpNetworkSessionContext session_context =
SpdySessionDependencies::CreateSessionContext(&session_deps_);
session_context.quic_crypto_client_stream_factory =
&crypto_client_stream_factory_;
session_context.http_user_agent_settings = &http_user_agent_settings_;
session_context.quic_context = &quic_context_;
session_ = std::make_unique<HttpNetworkSession>(params, session_context);
factory_ = static_cast<HttpStreamFactory*>(session_->http_stream_factory());
if (create_job_controller_) {
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate_, session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
job_controller_ = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
}
}
HttpStreamFactoryJobControllerTestBase(
const HttpStreamFactoryJobControllerTestBase&) = delete;
HttpStreamFactoryJobControllerTestBase& operator=(
const HttpStreamFactoryJobControllerTestBase&) = delete;
~HttpStreamFactoryJobControllerTestBase() override {
if (should_check_data_consumed_) {
if (quic_data_) {
EXPECT_TRUE(quic_data_->AllReadDataConsumed());
EXPECT_TRUE(quic_data_->AllWriteDataConsumed());
}
if (quic_data2_) {
EXPECT_TRUE(quic_data2_->AllReadDataConsumed());
EXPECT_TRUE(quic_data2_->AllWriteDataConsumed());
}
if (tcp_data_) {
EXPECT_TRUE(tcp_data_->AllReadDataConsumed());
EXPECT_TRUE(tcp_data_->AllWriteDataConsumed());
}
if (tcp_data2_) {
EXPECT_TRUE(tcp_data2_->AllReadDataConsumed());
EXPECT_TRUE(tcp_data2_->AllWriteDataConsumed());
}
}
}
void SetAlternativeService(const HttpRequestInfo& request_info,
AlternativeService alternative_service) {
url::SchemeHostPort server(request_info.url);
base::Time expiration = base::Time::Now() + base::Days(1);
if (alternative_service.protocol == NextProto::kProtoQUIC) {
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
quic_context_.params()->supported_versions);
} else {
session_->http_server_properties()->SetHttp2AlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration);
}
}
void VerifyBrokenAlternateProtocolMapping(const HttpRequestInfo& request_info,
bool should_mark_broken) {
const url::SchemeHostPort server(request_info.url);
const AlternativeServiceInfoVector alternative_service_info_vector =
session_->http_server_properties()->GetAlternativeServiceInfos(
server, NetworkAnonymizationKey());
EXPECT_EQ(1u, alternative_service_info_vector.size());
EXPECT_EQ(should_mark_broken,
session_->http_server_properties()->IsAlternativeServiceBroken(
alternative_service_info_vector[0].alternative_service(),
NetworkAnonymizationKey()));
}
void SetAsyncQuicSession(bool async_quic_session) {
std::vector<base::test::FeatureRef> enabled_features = {};
std::vector<base::test::FeatureRef> disabled_features = {};
if (happy_eyeballs_v3_enabled_) {
enabled_features.emplace_back(features::kHappyEyeballsV3);
} else {
disabled_features.emplace_back(features::kHappyEyeballsV3);
}
if (async_quic_session) {
enabled_features.emplace_back(features::kAsyncQuicSession);
} else {
disabled_features.emplace_back(features::kAsyncQuicSession);
}
feature_list_.Reset();
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
void TestAltJobSucceedsAfterMainJobFailed(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestMainJobSucceedsAfterAltJobFailed(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestMainJobSucceedsAfterIgnoredError(int net_error,
bool async_quic_session,
bool expect_broken = false,
std::string alternate_host = "");
void TestAltJobSucceedsAfterMainJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestOnStreamFailedForBothJobs(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestAltJobFailsAfterMainJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestMainJobSucceedsAfterAltJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestMainJobFailsAfterAltJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session);
void TestAltSvcVersionSelection(
const std::string& alt_svc_header,
const quic::ParsedQuicVersion& expected_version,
const quic::ParsedQuicVersionVector& supported_versions);
void TestResumeMainJobWhenAltJobStalls(bool async_quic_session);
void TestAltJobSucceedsMainJobDestroyed(bool async_quic_session);
void TestOrphanedJobCompletesControllerDestroyed(bool async_quic_session);
void TestDoNotDelayMainJobIfQuicWasRecentlyBroken(bool async_quic_session);
void TestDelayMainJobAfterRecentlyBrokenQuicWasConfirmed(
bool async_quic_session);
void TestDoNotDelayMainJobIfHasAvailableSpdySession(bool async_quic_session);
quic::ParsedQuicVersion version_ = DefaultSupportedQuicVersions().front();
RecordingNetLogObserver net_log_observer_;
NetLogWithSource net_log_with_source_{
NetLogWithSource::Make(NetLogSourceType::HTTP_STREAM_JOB_CONTROLLER)};
TestJobFactory job_factory_;
MockHttpStreamRequestDelegate request_delegate_;
MockQuicContext quic_context_;
StaticHttpUserAgentSettings http_user_agent_settings_ = {"*", "test-ua"};
SpdySessionDependencies session_deps_;
std::unique_ptr<HttpNetworkSession> session_;
raw_ptr<HttpStreamFactory> factory_ = nullptr;
raw_ptr<HttpStreamFactory::JobController, AcrossTasksDanglingUntriaged>
job_controller_ = nullptr;
std::unique_ptr<HttpStreamRequest> request_;
std::unique_ptr<SequencedSocketData> tcp_data_;
std::unique_ptr<SequencedSocketData> tcp_data2_;
std::unique_ptr<MockQuicData> quic_data_;
std::unique_ptr<MockQuicData> quic_data2_;
MockCryptoClientStreamFactory crypto_client_stream_factory_;
QuicTestPacketMaker client_maker_{version_,
quic::QuicUtils::CreateRandomConnectionId(
quic_context_.random_generator()),
quic_context_.clock(),
kServerHostname,
quic::Perspective::IS_CLIENT,
false};
protected:
bool is_preconnect_ = false;
bool enable_ip_based_pooling_for_h2_ = true;
bool enable_alternative_services_ = true;
bool delay_main_job_with_available_spdy_session_ = true;
bool should_check_data_consumed_ = true;
private:
const bool happy_eyeballs_v3_enabled_;
bool create_job_controller_ = true;
base::test::ScopedFeatureList feature_list_;
};
class HttpStreamFactoryJobControllerTest
: public HttpStreamFactoryJobControllerTestBase {
protected:
HttpStreamFactoryJobControllerTest()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false) {}
};
// Tests that are run with Happy Eyeballs v3 both enabled and disabled.
class HttpStreamFactoryJobControllerDualPathTest
: public HttpStreamFactoryJobControllerTestBase,
public ::testing::WithParamInterface<bool> {
public:
HttpStreamFactoryJobControllerDualPathTest()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/GetParam()) {}
};
INSTANTIATE_TEST_SUITE_P(All,
HttpStreamFactoryJobControllerDualPathTest,
::testing::Bool());
// Make sure that a socket will not outlive the network session if the session
// is destroyed before the conneciton completes.
TEST_P(HttpStreamFactoryJobControllerDualPathTest,
DestroyingSocketPoolDestroysSocket) {
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.test");
Initialize(request_info);
MockHttpStreamRequestDelegate request_delegate;
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate_, session_.get(), &job_factory_, request_info,
is_preconnect_, /*is_websocket=*/false, enable_ip_based_pooling_for_h2_,
enable_alternative_services_, delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_, std::move(job_controller));
std::unique_ptr<HttpStreamRequest> request = job_controller_ptr->Start(
&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(tcp_data_->socket());
request.reset();
DestroySession();
// Destroying the session (and the request) should destroy the socket.
EXPECT_FALSE(tcp_data_->socket());
}
TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsSync) {
ProxyConfig proxy_config;
proxy_config.set_pac_url(GURL("http://fooproxyurl"));
proxy_config.set_pac_mandatory(true);
session_deps_.proxy_resolution_service =
std::make_unique<ConfiguredProxyResolutionService>(
std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
std::make_unique<FailingProxyResolverFactory>(), nullptr,
/*quick_check_enabled=*/true);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
Initialize(request_info);
EXPECT_CALL(request_delegate_,
OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _, _))
.Times(1);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_FALSE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
// Make sure calling GetLoadState() when before job creation does not crash.
// Regression test for crbug.com/723920.
EXPECT_EQ(LOAD_STATE_IDLE, job_controller_->GetLoadState());
base::RunLoop().RunUntilIdle();
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest, ProxyResolutionFailsAsync) {
ProxyConfig proxy_config;
proxy_config.set_pac_url(GURL("http://fooproxyurl"));
proxy_config.set_pac_mandatory(true);
auto proxy_resolver_factory =
std::make_unique<MockAsyncProxyResolverFactory>(false);
auto* proxy_resolver_factory_ptr = proxy_resolver_factory.get();
MockAsyncProxyResolver resolver;
session_deps_.proxy_resolution_service =
std::make_unique<ConfiguredProxyResolutionService>(
std::make_unique<ProxyConfigServiceFixed>(ProxyConfigWithAnnotation(
proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS)),
std::move(proxy_resolver_factory), nullptr,
/*quick_check_enabled=*/true);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
Initialize(request_info);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_FALSE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(LOAD_STATE_RESOLVING_PROXY_FOR_URL,
job_controller_->GetLoadState());
EXPECT_CALL(request_delegate_,
OnStreamFailed(ERR_MANDATORY_PROXY_CONFIGURATION_FAILED, _, _, _))
.Times(1);
proxy_resolver_factory_ptr->pending_requests()[0]->CompleteNowWithForwarder(
ERR_FAILED, &resolver);
base::RunLoop().RunUntilIdle();
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest, NoSupportedProxies) {
session_deps_.proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromProxyChainsForTest(
{ProxyChain::FromSchemeHostAndPort(ProxyServer::SCHEME_QUIC,
"myproxy.org", 443)},
TRAFFIC_ANNOTATION_FOR_TESTS);
session_deps_.enable_quic = false;
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
Initialize(request_info);
EXPECT_CALL(request_delegate_,
OnStreamFailed(ERR_NO_SUPPORTED_PROXIES, _, _, _))
.Times(1);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_FALSE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
base::RunLoop().RunUntilIdle();
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// TODO(crbug.com/365771838): Add tests for non-ip protection nested proxy
// chains if support is enabled for all builds.
class JobControllerReconsiderProxyAfterErrorTest
: public HttpStreamFactoryJobControllerTestBase {
public:
JobControllerReconsiderProxyAfterErrorTest()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false) {}
void Initialize(
std::unique_ptr<ProxyResolutionService> proxy_resolution_service,
std::unique_ptr<ProxyDelegate> proxy_delegate = nullptr,
bool using_quic = false) {
session_deps_.proxy_delegate = std::move(proxy_delegate);
session_deps_.proxy_resolution_service =
std::move(proxy_resolution_service);
session_deps_.proxy_resolution_service->SetProxyDelegate(
session_deps_.proxy_delegate.get());
session_deps_.http_user_agent_settings =
std::make_unique<StaticHttpUserAgentSettings>("*", "test-ua");
HttpNetworkSessionParams params =
SpdySessionDependencies::CreateSessionParams(&session_deps_);
HttpNetworkSessionContext session_context =
SpdySessionDependencies::CreateSessionContext(&session_deps_);
if (using_quic) {
params.enable_quic = true;
session_context.quic_crypto_client_stream_factory =
&crypto_client_stream_factory_;
session_context.quic_context = &quic_context_;
session_context.quic_context->params()->origins_to_force_quic_on.insert(
HostPortPair::FromURL(GURL("https://www.example.com")));
}
session_ = std::make_unique<HttpNetworkSession>(params, session_context);
factory_ = session_->http_stream_factory();
}
std::unique_ptr<MockQuicChromiumClientSession> CreateMockQUICProxySession(
url::SchemeHostPort server) {
const IPEndPoint kIpEndPoint = IPEndPoint(IPAddress::IPv4AllZeros(), 0);
quic::test::MockRandom random{0};
quic::MockClock clock;
QuicChromiumConnectionHelper helper(&clock, &random);
quic::test::MockAlarmFactory alarm_factory;
quic::test::MockConnectionIdGenerator connection_id_generator;
TransportSecurityState transport_security_state;
SSLConfigServiceDefaults ssl_config_service;
quic::QuicCryptoClientConfig crypto_config(
quic::test::crypto_test_utils::ProofVerifierForTesting());
quic::QuicConfig quic_config(quic::test::DefaultQuicConfig());
std::unique_ptr<DatagramClientSocket> socket =
session_deps_.socket_factory->CreateDatagramClientSocket(
DatagramSocket::DEFAULT_BIND, NetLog::Get(), NetLogSource());
socket->Connect(kIpEndPoint);
quic::test::MockQuicConnection* connection =
new quic::test::MockQuicConnection(&helper, &alarm_factory,
quic::Perspective::IS_CLIENT);
EXPECT_CALL(*connection,
CloseConnection(quic::QUIC_PEER_GOING_AWAY, "session torn down",
quic::ConnectionCloseBehavior::SILENT_CLOSE))
.Times(1);
QuicSessionKey session_key(
server.host(), server.port(), PRIVACY_MODE_DISABLED,
ProxyChain::ForIpProtection({}, 0), SessionUsage::kProxy, SocketTag(),
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
/*require_dns_https_alpn=*/false,
/*disable_cert_verification_network_fetches=*/true);
auto new_session = std::make_unique<MockQuicChromiumClientSession>(
connection, std::move(socket), session_->quic_session_pool(),
&crypto_client_stream_factory_, &clock, &transport_security_state,
&ssl_config_service,
base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
QuicSessionAliasKey(server, session_key),
/*require_confirmation=*/false,
/*migrate_session_early_v2=*/false,
/*migrate_session_on_network_change_v2=*/false, kDefaultNetworkForTests,
quic::QuicTime::Delta::FromMilliseconds(
kDefaultRetransmittableOnWireTimeout.InMilliseconds()),
/*migrate_idle_session=*/false, /*allow_port_migration_=*/false,
kDefaultIdleSessionMigrationPeriod,
/*multi_port_probing_interval=*/0, kMaxTimeOnNonDefaultNetwork,
kMaxMigrationsToNonDefaultNetworkOnWriteError,
kMaxMigrationsToNonDefaultNetworkOnPathDegrading,
kQuicYieldAfterPacketsRead,
quic::QuicTime::Delta::FromMilliseconds(
kQuicYieldAfterDurationMilliseconds),
/*cert_verify_flags=*/0, quic_config,
std::make_unique<TestQuicCryptoClientConfigHandle>(&crypto_config),
"CONNECTION_UNKNOWN", base::TimeTicks::Now(), base::TimeTicks::Now(),
base::DefaultTickClock::GetInstance(),
base::SingleThreadTaskRunner::GetCurrentDefault().get(),
/*socket_performance_watcher=*/nullptr, ConnectionEndpointMetadata(),
/*enable_origin_frame=*/true,
/*allow_server_preferred_address=*/true,
MultiplexedSessionCreationInitiator::kUnknown,
NetLogWithSource::Make(NetLogSourceType::NONE));
mock_proxy_sessions_.emplace_back(new_session.get());
quic::test::NoopQpackStreamSenderDelegate
noop_qpack_stream_sender_delegate_;
mock_proxy_sessions_.back()->Initialize();
mock_proxy_sessions_.back()
->qpack_decoder()
->set_qpack_stream_sender_delegate(&noop_qpack_stream_sender_delegate_);
mock_proxy_sessions_.back()->StartReading();
return new_session;
}
std::unique_ptr<HttpStreamRequest> CreateJobController(
const HttpRequestInfo& request_info) {
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate_, session_.get(), &default_job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
return job_controller_ptr->Start(
&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
}
protected:
std::vector<raw_ptr<MockQuicChromiumClientSession>> mock_proxy_sessions_;
private:
// Use real Jobs so that Job::Resume() is not mocked out. When main job is
// resumed it will use mock socket data.
HttpStreamFactory::JobFactory default_job_factory_;
};
// Test case struct for proxies that use TCP i.e. HTTP, HTTPS, and SOCKS5.
struct TcpProxyTestCase {
template <typename Sink>
friend void AbslStringify(Sink& sink, TcpProxyTestCase test_case) {
sink.Append(testing::PrintToString(test_case.phase));
sink.Append("_");
sink.Append(ErrorToShortString(test_case.error));
}
// Each test case simulates a connection attempt through a proxy that fails
// twice, followed by two connection attempts that succeed. For most cases,
// this is done by having a connection attempt to the first proxy fail,
// triggering fallback to a second proxy, which also fails, and then fallback
// to the final (DIRECT) proxy option. However, SslConnectJobs have their own
// try logic in certain cases. This value is true for those cases, in which
// case there are two connection attempts to the first proxy, and then the
// requests fall back to the second (DIRECT) proxy.
//
// This only applies to HTTPS tests.
bool TriggersSslConnectJobRetryLogic() const {
if (phase == TcpErrorPhase::kProxySslHandshake &&
error == ERR_SSL_PROTOCOL_ERROR) {
return true;
}
return false;
}
TcpErrorPhase phase;
Error error;
// For tests that verify metrics.
struct {
Error error;
size_t size;
} recorded_metric;
};
std::string PrintHttpProxyTestName(
const testing::TestParamInfo<
std::tuple<TcpProxyTestCase, std::string_view>>& info) {
return ::testing::PrintToString(std::get<0>(info.param)) + "_" +
GURL(std::get<1>(info.param)).scheme();
}
constexpr TcpProxyTestCase kHttpProxyTestCases[] = {
// These largely correspond to the list of errors in
// CanFalloverToNextProxy() which can occur with an HTTP proxy.
//
// We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
// HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
// `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
//
// TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
// handling?
{TcpErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
{TcpErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
{TcpErrorPhase::kTunnelRead, ERR_TIMED_OUT},
{TcpErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR},
};
class JobControllerReconsiderProxyAfterErrorHttpProxyTest
: public JobControllerReconsiderProxyAfterErrorTest,
public ::testing::WithParamInterface<
std::tuple<TcpProxyTestCase, std::string_view>> {};
INSTANTIATE_TEST_SUITE_P(
,
JobControllerReconsiderProxyAfterErrorHttpProxyTest,
::testing::Combine(::testing::ValuesIn(kHttpProxyTestCases),
::testing::ValuesIn(kHttpTestUrls)),
&PrintHttpProxyTestName);
// Test proxy fallback logic in the case connecting through an HTTP proxy.
//
// TODO(eroman): The testing should be expanded to test cases where proxy
// fallback is NOT supposed to occur.
TEST_P(JobControllerReconsiderProxyAfterErrorHttpProxyTest, Test) {
const auto [phase, error, unused] = std::get<0>(GetParam());
const GURL dest_url(std::get<1>(GetParam()));
CreateSessionDeps();
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"PROXY badproxy:99; PROXY badfallbackproxy:98; DIRECT",
TRAFFIC_ANNOTATION_FOR_TESTS);
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
test_proxy_delegate->set_extra_header_name("Foo");
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kBadProxyTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: badproxy:99\r\n\r\n";
static constexpr char kBadFallbackProxyTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: badfallbackproxy:98\r\n\r\n";
const MockWrite kBadProxyTunnelWrites[] = {{ASYNC, kBadProxyTunnelRequest}};
const MockWrite kBadFallbackProxyTunnelWrites[] = {
{ASYNC, kBadFallbackProxyTunnelRequest}};
std::vector<MockRead> reads;
// Generate identical errors for both the main proxy and the fallback proxy.
// No alternative job is created for either, so only need one data provider
// for each, when the request makes it to the socket layer.
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
switch (phase) {
case TcpErrorPhase::kProxySslHandshake:
NOTREACHED() << "No SSL handshake for HTTP connections.";
case TcpErrorPhase::kHostResolution:
// Only ERR_NAME_NOT_RESOLVED can be returned by the mock host resolver.
DCHECK_EQ(ERR_NAME_NOT_RESOLVED, error);
session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badfallbackproxy");
break;
case TcpErrorPhase::kTcpConnect:
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job->set_connect_data(MockConnect(ASYNC, error));
socket_data_proxy_main_job2 =
std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job2->set_connect_data(MockConnect(ASYNC, error));
break;
case TcpErrorPhase::kTunnelRead:
if (dest_url.SchemeIs(url::kHttpScheme)) {
GTEST_SKIP() << "Tunnels aren't established for HTTP destinations.";
}
reads.emplace_back(ASYNC, error);
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>(
reads, kBadProxyTunnelWrites);
socket_data_proxy_main_job2 = std::make_unique<StaticSocketDataProvider>(
reads, kBadFallbackProxyTunnelWrites);
break;
}
if (socket_data_proxy_main_job) {
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job2.get());
}
// After both proxies fail, the request should fall back to using DIRECT, and
// succeed.
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`.
// The second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info, Contains(Key(ProxyUriToProxyChain(
"badproxy:99", ProxyServer::SCHEME_HTTP))));
EXPECT_THAT(retry_info,
Contains(Key(ProxyUriToProxyChain("badfallbackproxy:98",
ProxyServer::SCHEME_HTTP))));
// The idle socket should have been added back to the socket pool. Close it,
// so the next loop iteration creates a new socket instead of reusing the
// idle one.
auto* socket_pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyChain::Direct());
EXPECT_EQ(1, socket_pool->IdleSocketCount());
socket_pool->CloseIdleSockets("Close socket reason");
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
using JobControllerReconsiderProxyAfterErrorHttpsProxyTest =
JobControllerReconsiderProxyAfterErrorHttpProxyTest;
constexpr TcpProxyTestCase kHttpsProxyTestCases[] = {
// These largely correspond to the list of errors in
// CanFalloverToNextProxy() which can occur with an HTTPS proxy.
//
// We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
// HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
// `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
//
// TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
// handling?
{TcpErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
{TcpErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
{TcpErrorPhase::kProxySslHandshake, ERR_CERT_COMMON_NAME_INVALID},
{TcpErrorPhase::kProxySslHandshake, ERR_SSL_PROTOCOL_ERROR},
{TcpErrorPhase::kTunnelRead, ERR_TIMED_OUT},
{TcpErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR},
};
INSTANTIATE_TEST_SUITE_P(
,
JobControllerReconsiderProxyAfterErrorHttpsProxyTest,
::testing::Combine(::testing::ValuesIn(kHttpsProxyTestCases),
::testing::ValuesIn(kHttpTestUrls)),
&PrintHttpProxyTestName);
// Test proxy fallback logic in the case connecting through an HTTPS proxy.
TEST_P(JobControllerReconsiderProxyAfterErrorHttpsProxyTest, Test) {
const auto [phase, error, unused] = std::get<0>(GetParam());
bool triggers_ssl_connect_job_retry_logic =
std::get<0>(GetParam()).TriggersSslConnectJobRetryLogic();
const GURL dest_url(std::get<1>(GetParam()));
CreateSessionDeps();
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"HTTPS badproxy:99; HTTPS badfallbackproxy:98; DIRECT",
TRAFFIC_ANNOTATION_FOR_TESTS);
if (triggers_ssl_connect_job_retry_logic) {
proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"HTTPS badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS);
}
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
test_proxy_delegate->set_extra_header_name("Foo");
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kBadProxyTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badproxy:99\r\n\r\n";
static constexpr char kBadFallbackProxyTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badfallbackproxy:98\r\n\r\n";
const MockWrite kBadProxyTunnelWrites[] = {{ASYNC, kBadProxyTunnelRequest}};
const MockWrite kBadFallbackProxyTunnelWrites[] = {
{ASYNC, kBadFallbackProxyTunnelRequest}};
std::vector<MockRead> reads;
// Generate identical errors for both the main proxy and the fallback proxy.
// No alternative job is created for either, so only need one data provider
// for each, when the request makes it to the socket layer.
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job;
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job2;
switch (phase) {
case TcpErrorPhase::kHostResolution:
// Only ERR_NAME_NOT_RESOLVED can be returned by the mock host resolver.
DCHECK_EQ(ERR_NAME_NOT_RESOLVED, error);
session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badfallbackproxy");
break;
case TcpErrorPhase::kTcpConnect:
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job->set_connect_data(MockConnect(ASYNC, error));
socket_data_proxy_main_job2 =
std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job2->set_connect_data(MockConnect(ASYNC, error));
break;
case TcpErrorPhase::kProxySslHandshake:
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>();
ssl_data_proxy_main_job =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
socket_data_proxy_main_job2 =
std::make_unique<StaticSocketDataProvider>();
ssl_data_proxy_main_job2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
break;
case TcpErrorPhase::kTunnelRead:
if (dest_url.SchemeIs(url::kHttpScheme)) {
GTEST_SKIP() << "Tunnels aren't established for HTTP destinations.";
}
reads.emplace_back(ASYNC, error);
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>(
reads, kBadProxyTunnelWrites);
ssl_data_proxy_main_job =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
socket_data_proxy_main_job2 = std::make_unique<StaticSocketDataProvider>(
reads, triggers_ssl_connect_job_retry_logic
? kBadProxyTunnelWrites
: kBadFallbackProxyTunnelWrites);
ssl_data_proxy_main_job2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
break;
}
if (socket_data_proxy_main_job) {
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job2.get());
}
if (ssl_data_proxy_main_job) {
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job2.get());
}
// After both proxies fail, the request should fall back to using DIRECT, and
// succeed.
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`.
// The second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
if (!triggers_ssl_connect_job_retry_logic) {
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info,
Contains(Key(ProxyUriToProxyChain(
"https://badproxy:99", ProxyServer::SCHEME_HTTP))));
EXPECT_THAT(
retry_info,
Contains(Key(ProxyUriToProxyChain("https://badfallbackproxy:98",
ProxyServer::SCHEME_HTTP))));
} else {
ASSERT_THAT(retry_info, SizeIs(1));
EXPECT_THAT(retry_info,
Contains(Key(ProxyUriToProxyChain(
"https://badproxy:99", ProxyServer::SCHEME_HTTP))));
}
// The idle socket should have been added back to the socket pool. Close it,
// so the next loop iteration creates a new socket instead of reusing the
// idle one.
auto* socket_pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyChain::Direct());
EXPECT_EQ(1, socket_pool->IdleSocketCount());
socket_pool->CloseIdleSockets("Close socket reason");
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
constexpr TcpProxyTestCase kFirstNestedHttpsProxyTestCases[] = {
// These largely correspond to the list of errors in
// CanFalloverToNextProxy() which can occur with an HTTPS proxy.
//
// We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
// HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
// `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
//
// TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
// handling?
{TcpErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_RESET,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 2}},
{TcpErrorPhase::kProxySslHandshake, ERR_CERT_COMMON_NAME_INVALID,
/*recorded_metric=*/{ERR_PROXY_CERTIFICATE_INVALID, 2}},
{TcpErrorPhase::kProxySslHandshake, ERR_SSL_PROTOCOL_ERROR,
/*recorded_metric=*/{ERR_PROXY_CONNECTION_FAILED, 1}},
{TcpErrorPhase::kTunnelRead, ERR_TIMED_OUT,
/*recorded_metric=*/{ERR_TIMED_OUT, 2}},
{TcpErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR,
/*recorded_metric=*/{ERR_SSL_PROTOCOL_ERROR, 2}},
};
using JobControllerReconsiderProxyAfterErrorFirstNestedHttpsProxyTest =
JobControllerReconsiderProxyAfterErrorHttpProxyTest;
INSTANTIATE_TEST_SUITE_P(
,
JobControllerReconsiderProxyAfterErrorFirstNestedHttpsProxyTest,
::testing::Combine(::testing::ValuesIn(kFirstNestedHttpsProxyTestCases),
::testing::ValuesIn(kHttpTestUrls)),
&PrintHttpProxyTestName);
// Same as above but using a multi-proxy chain, with errors encountered by the
// first proxy server in the chain.
TEST_P(JobControllerReconsiderProxyAfterErrorFirstNestedHttpsProxyTest, Test) {
const auto [phase, error, recorded_metric] = std::get<0>(GetParam());
const GURL dest_url(std::get<1>(GetParam()));
bool triggers_ssl_connect_job_retry_logic =
std::get<0>(GetParam()).TriggersSslConnectJobRetryLogic();
const ProxyServer kGoodProxyServer{ProxyServer::SCHEME_HTTPS,
HostPortPair("goodproxyserver", 100)};
const ProxyServer kBadProxyServer1{ProxyServer::SCHEME_HTTPS,
HostPortPair("badproxyserver", 99)};
const ProxyServer kBadProxyServer2{
ProxyServer::SCHEME_HTTPS, HostPortPair("badfallbackproxyserver", 98)};
const ProxyChain kNestedProxyChain1 =
ProxyChain::ForIpProtection({{kBadProxyServer1, kGoodProxyServer}});
const ProxyChain kNestedProxyChain2 =
ProxyChain::ForIpProtection({{kBadProxyServer2, kGoodProxyServer}});
const ProxyChain kDirectIpProtectionProxyChain =
ProxyChain::ForIpProtection({});
base::HistogramTester histogram_tester;
CreateSessionDeps();
ProxyList proxy_list;
proxy_list.AddProxyChain(kNestedProxyChain1);
proxy_list.AddProxyChain(kNestedProxyChain2);
proxy_list.AddProxyChain(kDirectIpProtectionProxyChain);
ProxyConfig proxy_config = ProxyConfig::CreateForTesting(proxy_list);
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config,
TRAFFIC_ANNOTATION_FOR_TESTS));
if (triggers_ssl_connect_job_retry_logic) {
proxy_list.Clear();
proxy_list.AddProxyChain(kNestedProxyChain1);
proxy_list.AddProxyChain(kDirectIpProtectionProxyChain);
ProxyConfig proxy_config2 = ProxyConfig::CreateForTesting(proxy_list);
proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config2,
TRAFFIC_ANNOTATION_FOR_TESTS));
}
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
test_proxy_delegate->set_extra_header_name("Foo");
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kBadProxyServer1TunnelRequest[] =
"CONNECT goodproxyserver:100 HTTP/1.1\r\n"
"Host: goodproxyserver:100\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badproxyserver:99\r\n\r\n";
static constexpr char kBadProxyServer2TunnelRequest[] =
"CONNECT goodproxyserver:100 HTTP/1.1\r\n"
"Host: goodproxyserver:100\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badfallbackproxyserver:98\r\n\r\n";
const MockWrite kBadProxyServer1TunnelWrites[] = {
MockWrite(ASYNC, 0, kBadProxyServer1TunnelRequest)};
const MockWrite kBadProxyServer2TunnelWrites[] = {
MockWrite(ASYNC, 0, kBadProxyServer2TunnelRequest)};
std::vector<MockRead> reads;
// Generate identical errors for the first proxy server in both the main proxy
// chain and the fallback proxy chain. No alternative job is created for
// either, so only need one data provider for each, when the request makes it
// to the socket layer.
std::unique_ptr<SequencedSocketData> socket_data_proxy_main_job;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job;
std::unique_ptr<SequencedSocketData> socket_data_proxy_main_job2;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job2;
switch (phase) {
case TcpErrorPhase::kHostResolution:
// Only ERR_NAME_NOT_RESOLVED can be returned by the mock host
// resolver.
DCHECK_EQ(ERR_NAME_NOT_RESOLVED, error);
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badproxyserver");
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badfallbackproxyserver");
break;
case TcpErrorPhase::kTcpConnect:
socket_data_proxy_main_job = std::make_unique<SequencedSocketData>();
socket_data_proxy_main_job->set_connect_data(MockConnect(ASYNC, error));
socket_data_proxy_main_job2 = std::make_unique<SequencedSocketData>();
socket_data_proxy_main_job2->set_connect_data(MockConnect(ASYNC, error));
break;
case TcpErrorPhase::kProxySslHandshake:
socket_data_proxy_main_job = std::make_unique<SequencedSocketData>();
ssl_data_proxy_main_job =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
socket_data_proxy_main_job2 = std::make_unique<SequencedSocketData>();
ssl_data_proxy_main_job2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
break;
case TcpErrorPhase::kTunnelRead:
// Note: Unlike for single-proxy chains, tunnels are established for HTTP
// destinations when multi-proxy chains are in use, so simulate tunnel
// read failures in all cases.
reads.emplace_back(ASYNC, error, 1);
socket_data_proxy_main_job = std::make_unique<SequencedSocketData>(
reads, kBadProxyServer1TunnelWrites);
ssl_data_proxy_main_job =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
socket_data_proxy_main_job2 = std::make_unique<SequencedSocketData>(
reads, kBadProxyServer2TunnelWrites);
ssl_data_proxy_main_job2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
break;
}
if (socket_data_proxy_main_job) {
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job2.get());
}
if (ssl_data_proxy_main_job) {
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job2.get());
}
// After both proxy chains fail, the request should fall back to using DIRECT,
// and succeed.
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`.
// The second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
if (!triggers_ssl_connect_job_retry_logic) {
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain1)));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain2)));
} else {
ASSERT_THAT(retry_info, SizeIs(1));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain1)));
}
// The idle socket should have been added back to the socket pool. Close it,
// so the next loop iteration creates a new socket instead of reusing the
// idle one.
auto* socket_pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, kDirectIpProtectionProxyChain);
EXPECT_EQ(1, socket_pool->IdleSocketCount());
socket_pool->CloseIdleSockets("Close socket reason");
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Check that the errors were logged.
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain0"),
BucketsAre(base::Bucket(recorded_metric.error, recorded_metric.size)));
// Check that no other proxy chains were logged.
const base::HistogramTester::CountsMap counts =
histogram_tester.GetTotalCountsForPrefix(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain");
EXPECT_THAT(counts, SizeIs(1));
}
constexpr TcpProxyTestCase kSecondNestedHttpsProxyTestCases[] = {
// These largely correspond to the list of errors in
// CanFalloverToNextProxy() which can occur with an HTTPS proxy.
//
// We omit `ERR_CONNECTION_CLOSED` because it is largely unreachable. The
// HTTP/1.1 parser maps it to `ERR_EMPTY_RESPONSE` or
// `ERR_RESPONSE_HEADERS_TRUNCATED` in most cases.
//
// TODO(davidben): Is omitting `ERR_EMPTY_RESPONSE` a bug in proxy error
// handling?
{TcpErrorPhase::kProxySslHandshake, ERR_CERT_COMMON_NAME_INVALID,
/*recorded_metric=*/{ERR_PROXY_CERTIFICATE_INVALID, 2}},
{TcpErrorPhase::kProxySslHandshake, ERR_SSL_PROTOCOL_ERROR,
/*recorded_metric=*/{ERR_SSL_PROTOCOL_ERROR, 1}},
{TcpErrorPhase::kTunnelRead, ERR_TIMED_OUT,
/*recorded_metric=*/{ERR_TIMED_OUT, 2}},
{TcpErrorPhase::kTunnelRead, ERR_SSL_PROTOCOL_ERROR,
/*recorded_metric=*/{ERR_SSL_PROTOCOL_ERROR, 2}},
};
using JobControllerReconsiderProxyAfterErrorSecondNestedHttpsProxyTest =
JobControllerReconsiderProxyAfterErrorHttpProxyTest;
INSTANTIATE_TEST_SUITE_P(
,
JobControllerReconsiderProxyAfterErrorSecondNestedHttpsProxyTest,
::testing::Combine(::testing::ValuesIn(kSecondNestedHttpsProxyTestCases),
::testing::ValuesIn(kHttpTestUrls)),
&PrintHttpProxyTestName);
// Same as above but using a multi-proxy chain, with errors encountered by the
// second proxy server in the chain.
TEST_P(JobControllerReconsiderProxyAfterErrorSecondNestedHttpsProxyTest, Test) {
const auto [phase, error, recorded_metric] = std::get<0>(GetParam());
const bool triggers_ssl_connect_job_retry_logic =
std::get<0>(GetParam()).TriggersSslConnectJobRetryLogic();
const GURL dest_url(std::get<1>(GetParam()));
const ProxyServer kGoodProxyServer{ProxyServer::SCHEME_HTTPS,
HostPortPair("goodproxyserver", 100)};
const ProxyServer kBadProxyServer1{ProxyServer::SCHEME_HTTPS,
HostPortPair("badproxyserver", 99)};
const ProxyServer kBadProxyServer2{
ProxyServer::SCHEME_HTTPS, HostPortPair("badfallbackproxyserver", 98)};
const ProxyChain kNestedProxyChain1 =
ProxyChain::ForIpProtection({{kGoodProxyServer, kBadProxyServer1}});
const ProxyChain kNestedProxyChain2 =
ProxyChain::ForIpProtection({{kGoodProxyServer, kBadProxyServer2}});
const ProxyChain kDirectIpProtectionProxyChain =
ProxyChain::ForIpProtection({});
base::HistogramTester histogram_tester;
CreateSessionDeps();
ProxyList proxy_list;
proxy_list.AddProxyChain(kNestedProxyChain1);
proxy_list.AddProxyChain(kNestedProxyChain2);
proxy_list.AddProxyChain(kDirectIpProtectionProxyChain);
ProxyConfig proxy_config = ProxyConfig::CreateForTesting(proxy_list);
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config,
TRAFFIC_ANNOTATION_FOR_TESTS));
if (triggers_ssl_connect_job_retry_logic) {
proxy_list.Clear();
proxy_list.AddProxyChain(kNestedProxyChain1);
proxy_list.AddProxyChain(kDirectIpProtectionProxyChain);
ProxyConfig proxy_config2 = ProxyConfig::CreateForTesting(proxy_list);
proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedForTest(
ProxyConfigWithAnnotation(proxy_config2,
TRAFFIC_ANNOTATION_FOR_TESTS));
}
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
test_proxy_delegate->set_extra_header_name("Foo");
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kBadProxyServer1TunnelRequest[] =
"CONNECT badproxyserver:99 HTTP/1.1\r\n"
"Host: badproxyserver:99\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://goodproxyserver:100\r\n\r\n";
static constexpr char kBadProxyServer2TunnelRequest[] =
"CONNECT badfallbackproxyserver:98 HTTP/1.1\r\n"
"Host: badfallbackproxyserver:98\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://goodproxyserver:100\r\n\r\n";
const std::string kBadProxyServer1EndpointTunnelRequest = base::StringPrintf(
"CONNECT %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badproxyserver:99\r\n\r\n",
HostPortPair::FromURL(dest_url).ToString().c_str(),
HostPortPair::FromURL(dest_url).ToString().c_str());
const std::string kBadProxyServer2EndpointTunnelRequest = base::StringPrintf(
"CONNECT %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Foo: https://badfallbackproxyserver:98\r\n\r\n",
HostPortPair::FromURL(dest_url).ToString().c_str(),
HostPortPair::FromURL(dest_url).ToString().c_str());
const MockWrite kNestedProxyChain1TunnelWrites[] = {
{ASYNC, kBadProxyServer1TunnelRequest},
{ASYNC, kBadProxyServer1EndpointTunnelRequest}};
const MockWrite kNestedProxyChain2TunnelWrites[] = {
{ASYNC, kBadProxyServer2TunnelRequest},
{ASYNC, kBadProxyServer2EndpointTunnelRequest}};
std::vector<MockRead> reads = {
MockRead(ASYNC, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
};
// Generate identical errors for the second proxy server in both the main
// proxy chain and the fallback proxy chain. No alternative job is created for
// either, so only need one data provider for each, when the request makes it
// to the socket layer.
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job_server1;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job_server2;
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job2_server1;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job2_server2;
ssl_data_proxy_main_job_server1 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
ssl_data_proxy_main_job2_server1 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
switch (phase) {
case TcpErrorPhase::kProxySslHandshake:
ssl_data_proxy_main_job_server2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
ssl_data_proxy_main_job2_server2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, error);
break;
case TcpErrorPhase::kTunnelRead:
// Note: Unlike for single-proxy chains, tunnels are established for HTTP
// destinations when multi-proxy chains are in use, so simulate tunnel
// read failures in all cases.
reads.emplace_back(ASYNC, error);
ssl_data_proxy_main_job_server2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
ssl_data_proxy_main_job2_server2 =
std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
break;
case TcpErrorPhase::kHostResolution:
case TcpErrorPhase::kTcpConnect:
NOTREACHED()
<< "Skip the kHostResolution and kTcpConnect cases for this test"
<< "since those only make sense for connections to the first proxy "
"server.";
}
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>(
reads, kNestedProxyChain1TunnelWrites);
socket_data_proxy_main_job2 = std::make_unique<StaticSocketDataProvider>(
reads, triggers_ssl_connect_job_retry_logic
? kNestedProxyChain1TunnelWrites
: kNestedProxyChain2TunnelWrites);
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job_server1.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job_server2.get());
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job2.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job2_server1.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job2_server2.get());
// After both proxy chains fail, the request should fall back to using DIRECT,
// and succeed.
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
// Only used in the HTTPS destination case, but harmless in the HTTP case.
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`.
// The second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
if (!triggers_ssl_connect_job_retry_logic) {
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain1)));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain2)));
} else {
ASSERT_THAT(retry_info, SizeIs(1));
EXPECT_THAT(retry_info, Contains(Key(kNestedProxyChain1)));
}
// The idle socket should have been added back to the socket pool. Close it,
// so the next loop iteration creates a new socket instead of reusing the
// idle one.
auto* socket_pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, kDirectIpProtectionProxyChain);
EXPECT_EQ(1, socket_pool->IdleSocketCount());
socket_pool->CloseIdleSockets("Close socket reason");
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Check that the errors were logged.
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain0"),
BucketsAre(base::Bucket(recorded_metric.error, recorded_metric.size)));
// Check that no other proxy chains were logged.
const base::HistogramTester::CountsMap counts =
histogram_tester.GetTotalCountsForPrefix(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain");
EXPECT_THAT(counts, SizeIs(1));
}
// Test proxy fallback logic for an IP Protection request.
TEST_F(JobControllerReconsiderProxyAfterErrorTest,
FallbackOnTunnelConnectionFailedForIpProtection) {
GURL dest_url = GURL("https://www.example.com");
CreateSessionDeps();
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"https://not-used:70", TRAFFIC_ANNOTATION_FOR_TESTS);
auto test_proxy_delegate =
std::make_unique<TestProxyDelegateForIpProtection>();
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Authorization: https://ip-pro:443\r\n\r\n";
const MockWrite kTunnelWrites[] = {{ASYNC, kTunnelRequest}};
std::vector<MockRead> reads;
// Generate errors for the first proxy server.
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
std::unique_ptr<SSLSocketDataProvider> ssl_data_proxy_main_job;
reads.emplace_back(ASYNC, ERR_TUNNEL_CONNECTION_FAILED);
socket_data_proxy_main_job =
std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
ssl_data_proxy_main_job = std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSSLSocketDataProvider(
ssl_data_proxy_main_job.get());
// After proxying fails, the request should fall back to using DIRECT, and
// succeed.
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
}
// Test that if the proxy delegate returns
// net::ERR_PROXY_UNABLE_TO_CONNECT_TO_DESTINATION in its
// OnTunnelHeadersReceived method, the request fails but the proxy is not marked
// as bad.
TEST_F(JobControllerReconsiderProxyAfterErrorTest,
NoFallbackOnProxyUnableToConnectToDestination) {
GURL dest_url = GURL("https://www.example.com");
CreateSessionDeps();
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"https://not-used:70", TRAFFIC_ANNOTATION_FOR_TESTS);
auto test_proxy_delegate =
std::make_unique<TestProxyDelegateForIpProtection>();
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
static constexpr char kProxyTunnelRequest[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n"
"Authorization: https://ip-pro:443\r\n\r\n";
MockWrite writes[] = {{ASYNC, kProxyTunnelRequest}};
MockRead reads[] = {
{ASYNC, "HTTP/1.1 502 Bad Gateway\r\n\r\n"},
};
StaticSocketDataProvider socket_data(reads, writes);
session_deps_.socket_factory->AddSocketDataProvider(&socket_data);
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
EXPECT_CALL(
request_delegate_,
OnStreamFailed(net::ERR_PROXY_UNABLE_TO_CONNECT_TO_DESTINATION, _, _, _));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that the proxy is not marked as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
EXPECT_TRUE(retry_info.empty());
}
constexpr TcpProxyTestCase kSocks5ProxyTestCases[] = {
// These largely correspond to the list of errors in
// CanFalloverToNextProxy() which can occur with an HTTPS proxy.
//
// Unlike HTTP/HTTPS proxies, SOCKS proxies are retried in response to
// `ERR_CONNECTION_CLOSED`.
{TcpErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
{TcpErrorPhase::kTcpConnect, ERR_ADDRESS_UNREACHABLE},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_TIMED_OUT},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_RESET},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_ABORTED},
{TcpErrorPhase::kTcpConnect, ERR_CONNECTION_REFUSED},
{TcpErrorPhase::kTunnelRead, ERR_TIMED_OUT},
{TcpErrorPhase::kTunnelRead, ERR_CONNECTION_CLOSED},
};
class JobControllerReconsiderProxyAfterErrorSocks5ProxyTest
: public JobControllerReconsiderProxyAfterErrorTest,
public ::testing::WithParamInterface<TcpProxyTestCase> {};
INSTANTIATE_TEST_SUITE_P(,
JobControllerReconsiderProxyAfterErrorSocks5ProxyTest,
::testing::ValuesIn(kSocks5ProxyTestCases),
::testing::PrintToStringParamName());
// Test proxy fallback logic in the case connecting through socks5 proxy.
TEST_P(JobControllerReconsiderProxyAfterErrorSocks5ProxyTest, Test) {
const auto [phase, error, unused] = GetParam();
// "host" on port 80 matches the kSOCK5GreetRequest.
const GURL kDestUrl = GURL("http://host:80/");
CreateSessionDeps();
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"SOCKS5 badproxy:99; SOCKS5 badfallbackproxy:98; DIRECT",
TRAFFIC_ANNOTATION_FOR_TESTS);
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
const MockWrite kTunnelWrites[] = {{ASYNC, kSOCKS5GreetRequest}};
std::vector<MockRead> reads;
// Generate identical errors for both the main proxy and the fallback proxy.
// No alternative job is created for either, so only need one data provider
// for each, when the request makes it to the socket layer.
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job;
std::unique_ptr<StaticSocketDataProvider> socket_data_proxy_main_job2;
switch (phase) {
case TcpErrorPhase::kHostResolution:
// Only ERR_NAME_NOT_RESOLVED can be returned by the mock host resolver.
DCHECK_EQ(ERR_NAME_NOT_RESOLVED, error);
session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badfallbackproxy");
break;
case TcpErrorPhase::kTcpConnect:
socket_data_proxy_main_job = std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job->set_connect_data(MockConnect(ASYNC, error));
socket_data_proxy_main_job2 =
std::make_unique<StaticSocketDataProvider>();
socket_data_proxy_main_job2->set_connect_data(MockConnect(ASYNC, error));
break;
case TcpErrorPhase::kTunnelRead:
reads.emplace_back(ASYNC, error);
socket_data_proxy_main_job =
std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
socket_data_proxy_main_job2 =
std::make_unique<StaticSocketDataProvider>(reads, kTunnelWrites);
break;
case TcpErrorPhase::kProxySslHandshake:
NOTREACHED() << "Not possible using SOCKS5.";
}
if (socket_data_proxy_main_job) {
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job.get());
session_deps_.socket_factory->AddSocketDataProvider(
socket_data_proxy_main_job2.get());
}
// After both proxies fail, the request should fall back to using DIRECT, and
// succeed.
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = kDestUrl;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate));
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`. The
// second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// Verify that request was fetched without proxy.
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info,
Contains(Key(ProxyUriToProxyChain(
"socks5://badproxy:99", ProxyServer::SCHEME_SOCKS5))));
EXPECT_THAT(
retry_info,
Contains(Key(ProxyUriToProxyChain("socks5://badfallbackproxy:98",
ProxyServer::SCHEME_SOCKS5))));
// The idle socket should have been added back to the socket pool. Close it,
// so the next loop iteration creates a new socket instead of reusing the
// idle one.
auto* socket_pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyChain::Direct());
EXPECT_EQ(1, socket_pool->IdleSocketCount());
socket_pool->CloseIdleSockets("Close socket reason");
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Tests that ERR_MSG_TOO_BIG is retryable for QUIC proxy.
TEST_F(JobControllerReconsiderProxyAfterErrorTest, ReconsiderErrMsgTooBig) {
auto quic_proxy_chain =
ProxyChain::ForIpProtection({ProxyServer::FromSchemeHostAndPort(
ProxyServer::SCHEME_QUIC, "bad", 99)});
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromProxyChainsForTest(
{quic_proxy_chain, ProxyChain::ForIpProtection({})},
TRAFFIC_ANNOTATION_FOR_TESTS);
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
// Mock data for the QUIC proxy socket.
StaticSocketDataProvider quic_proxy_socket;
quic_proxy_socket.set_connect_data(MockConnect(ASYNC, ERR_MSG_TOO_BIG));
session_deps_.socket_factory->AddSocketDataProvider(&quic_proxy_socket);
// Mock data for DIRECT.
StaticSocketDataProvider socket_data_direct;
socket_data_direct.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&socket_data_direct);
// Now request a stream. It should fall back to DIRECT on ERR_MSG_TOO_BIG.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.example.com");
Initialize(std::move(proxy_resolution_service));
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(used_proxy_info.is_direct());
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
EXPECT_THAT(retry_info, SizeIs(1));
EXPECT_THAT(retry_info, Contains(Key(quic_proxy_chain)));
request.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
constexpr struct QuicProxyTestCase {
template <typename Sink>
friend void AbslStringify(Sink& sink, const QuicProxyTestCase& test_case) {
sink.Append(testing::PrintToString(test_case.phase));
sink.Append("_");
sink.Append(ErrorToShortString(test_case.error));
}
QuicErrorPhase phase;
Error error;
} kQuicProxyTestCases[] = {
{QuicErrorPhase::kHostResolution, ERR_NAME_NOT_RESOLVED},
// Test that proxy session gets activated but then failed before requesting
// the stream. The error is determined by
// QuicChromiumClientSession::Handle::RequestStream.
{QuicErrorPhase::kProxySession, ERR_CONNECTION_CLOSED},
{QuicErrorPhase::kUdpConnect, ERR_ADDRESS_UNREACHABLE},
{QuicErrorPhase::kUdpConnect, ERR_CONNECTION_TIMED_OUT},
{QuicErrorPhase::kUdpConnect, ERR_CONNECTION_RESET},
{QuicErrorPhase::kUdpConnect, ERR_CONNECTION_ABORTED},
{QuicErrorPhase::kUdpConnect, ERR_CONNECTION_REFUSED},
{QuicErrorPhase::kUdpConnect, ERR_QUIC_PROTOCOL_ERROR},
{QuicErrorPhase::kUdpConnect, ERR_QUIC_HANDSHAKE_FAILED},
{QuicErrorPhase::kUdpConnect, ERR_MSG_TOO_BIG},
};
class JobControllerReconsiderProxyAfterErrorQuicProxyTest
: public JobControllerReconsiderProxyAfterErrorTest,
public ::testing::WithParamInterface<QuicProxyTestCase> {};
INSTANTIATE_TEST_SUITE_P(,
JobControllerReconsiderProxyAfterErrorQuicProxyTest,
::testing::ValuesIn(kQuicProxyTestCases),
::testing::PrintToStringParamName());
// Test proxy fallback logic in the case connecting through a QUIC proxy.
TEST_P(JobControllerReconsiderProxyAfterErrorQuicProxyTest, Test) {
const auto [phase, error] = GetParam();
// To use Quic proxy the destination must be HTTPS.
GURL dest_url("https://www.example.com");
url::SchemeHostPort proxy_server(url::kHttpsScheme, "badproxy", 99);
url::SchemeHostPort proxy_server2(url::kHttpsScheme, "badfallbackproxy", 98);
base::HistogramTester histogram_tester;
CreateSessionDeps();
auto quic_proxy_chain =
ProxyChain::ForIpProtection({ProxyServer::FromSchemeHostAndPort(
ProxyServer::SCHEME_QUIC, proxy_server.host(), proxy_server.port())});
auto quic_proxy_chain2 =
ProxyChain::ForIpProtection({ProxyServer::FromSchemeHostAndPort(
ProxyServer::SCHEME_QUIC, proxy_server2.host(),
proxy_server2.port())});
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromProxyChainsForTest(
{quic_proxy_chain, quic_proxy_chain2, ProxyChain::Direct()},
TRAFFIC_ANNOTATION_FOR_TESTS);
auto test_proxy_delegate = std::make_unique<TestProxyDelegate>();
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
// Generate identical errors for both the main proxy and the fallback proxy.
// No alternative job is created for either, so only need one data provider
// for each, when the request makes it to the socket layer.
std::unique_ptr<StaticSocketDataProvider> quic_proxy_socket_main_job;
std::unique_ptr<StaticSocketDataProvider> quic_proxy_socket_main_job2;
switch (phase) {
case QuicErrorPhase::kHostResolution:
// Only ERR_NAME_NOT_RESOLVED can be returned by the mock host resolver.
DCHECK_EQ(ERR_NAME_NOT_RESOLVED, GetParam().error);
session_deps_.host_resolver->rules()->AddSimulatedFailure("badproxy");
session_deps_.host_resolver->rules()->AddSimulatedFailure(
"badfallbackproxy");
break;
case QuicErrorPhase::kProxySession:
quic_proxy_socket_main_job = std::make_unique<StaticSocketDataProvider>();
quic_proxy_socket_main_job->set_connect_data(MockConnect(ASYNC, OK));
quic_proxy_socket_main_job2 =
std::make_unique<StaticSocketDataProvider>();
quic_proxy_socket_main_job2->set_connect_data(MockConnect(ASYNC, OK));
break;
case QuicErrorPhase::kUdpConnect:
quic_proxy_socket_main_job = std::make_unique<StaticSocketDataProvider>();
quic_proxy_socket_main_job->set_connect_data(MockConnect(ASYNC, error));
quic_proxy_socket_main_job2 =
std::make_unique<StaticSocketDataProvider>();
quic_proxy_socket_main_job2->set_connect_data(MockConnect(ASYNC, error));
break;
}
// Mock data for the QUIC proxy socket.
if (quic_proxy_socket_main_job) {
session_deps_.socket_factory->AddSocketDataProvider(
quic_proxy_socket_main_job.get());
session_deps_.socket_factory->AddSocketDataProvider(
quic_proxy_socket_main_job2.get());
}
SSLSocketDataProvider ssl_data_first_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_first_request;
socket_data_direct_first_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_first_request);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_first_request);
// Second request should use DIRECT, skipping the bad proxies, and succeed.
SSLSocketDataProvider ssl_data_second_request(ASYNC, OK);
StaticSocketDataProvider socket_data_direct_second_request;
socket_data_direct_second_request.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(
&socket_data_direct_second_request);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_data_second_request);
// Now request a stream. It should succeed using the DIRECT fallback proxy
// option.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = dest_url;
Initialize(std::move(proxy_resolution_service),
std::move(test_proxy_delegate),
/*using_quic=*/true);
if (phase == QuicErrorPhase::kProxySession) {
session_->quic_session_pool()->ActivateSessionForTesting(
CreateMockQUICProxySession(proxy_server));
session_->quic_session_pool()->ActivateSessionForTesting(
CreateMockQUICProxySession(proxy_server2));
ASSERT_EQ(mock_proxy_sessions_.size(), 2u);
}
// Start two requests. The first request should consume data from
// `socket_data_proxy_main_job` and `socket_data_direct_first_request`.
// The second request should consume data from
// `socket_data_direct_second_request`.
for (size_t i = 0; i < 2; ++i) {
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce(::testing::SaveArg<0>(&used_proxy_info));
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
RunUntilIdle();
// TODO(crbug.com/336318587): Verify the session key.
crypto_client_stream_factory_.last_stream()
->NotifySessionOneRttKeyAvailable();
RunUntilIdle();
EXPECT_TRUE(used_proxy_info.is_direct());
// The proxies that failed should now be known to the proxy service as bad.
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
ASSERT_THAT(retry_info, SizeIs(2));
EXPECT_THAT(retry_info, Contains(Key(quic_proxy_chain)));
EXPECT_THAT(retry_info, Contains(Key(quic_proxy_chain2)));
// Quic connection does not create socket. So only check the sessions, and
// close them. So that the next loop iteration won't reuse them.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
// Mock sessions must be removed from the vector before the session pool
// destroys them to avoid dangling pointers.
while (!mock_proxy_sessions_.empty()) {
MockQuicChromiumClientSession* session = mock_proxy_sessions_.back();
mock_proxy_sessions_.pop_back();
quic_session_pool->DeactivateSessionForTesting(session);
}
EXPECT_EQ(1, quic_session_pool->CountActiveSessions());
quic_session_pool->CloseAllSessions(OK, quic::QUIC_NO_ERROR);
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Check that the errors were logged.
EXPECT_THAT(histogram_tester.GetAllSamples(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain0"),
BucketsAre(base::Bucket(error, 2)));
// Check that no other proxy chains were logged.
const base::HistogramTester::CountsMap counts =
histogram_tester.GetTotalCountsForPrefix(
"Net.IpProtection.CanFalloverToNextProxy2.Error.Chain");
EXPECT_THAT(counts, SizeIs(1));
}
// Same as test above except that this is testing the retry behavior for
// non-QUIC proxy on ERR_MSG_TOO_BIG.
TEST_F(JobControllerReconsiderProxyAfterErrorTest,
DoNotReconsiderErrMsgTooBig) {
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateFixedFromPacResultForTest(
"HTTPS badproxy:99; DIRECT", TRAFFIC_ANNOTATION_FOR_TESTS);
// Before starting the test, verify that there are no proxies marked as bad.
ASSERT_TRUE(proxy_resolution_service->proxy_retry_info().empty());
// Mock data for the HTTPS proxy socket.
static constexpr char kHttpConnect[] =
"CONNECT www.example.com:443 HTTP/1.1\r\n"
"Host: www.example.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"User-Agent: test-ua\r\n\r\n";
const MockWrite kWrites[] = {{ASYNC, kHttpConnect}};
const MockRead kReads[] = {{ASYNC, ERR_MSG_TOO_BIG}};
SSLSocketDataProvider ssl_data(ASYNC, OK);
StaticSocketDataProvider https_proxy_socket(kReads, kWrites);
https_proxy_socket.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&https_proxy_socket);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
// Now request a stream. It should not fallback to DIRECT on ERR_MSG_TOO_BIG.
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(std::move(proxy_resolution_service));
ProxyInfo used_proxy_info;
EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_MSG_TOO_BIG, _, _, _))
.Times(1);
std::unique_ptr<HttpStreamRequest> request =
CreateJobController(request_info);
base::RunLoop().RunUntilIdle();
const ProxyRetryInfoMap& retry_info =
session_->proxy_resolution_service()->proxy_retry_info();
EXPECT_THAT(retry_info, SizeIs(0));
request.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest, OnStreamFailedWithNoAlternativeJob) {
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
Initialize(request_info);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
// There's no other alternative job. Thus when stream failed, it should
// notify Request of the stream failure.
EXPECT_CALL(request_delegate_, OnStreamFailed(ERR_FAILED, _, _, _)).Times(1);
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryJobControllerTest, OnStreamReadyWithNoAlternativeJob) {
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.google.com");
Initialize(request_info);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
// There's no other alternative job. Thus when a stream is ready, it should
// notify Request.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
}
// Test we cancel Jobs correctly when the Request is explicitly canceled
// before any Job is bound to Request.
TEST_F(HttpStreamFactoryJobControllerTest, CancelJobsBeforeBinding) {
// Use COLD_START to make the alt job pending.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_CONNECTION_CLOSED);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Reset the Request will cancel all the Jobs since there's no Job determined
// to serve Request yet and JobController will notify the factory to delete
// itself upon completion.
request_.reset();
// QuicSessionPool::Job::Request will not complete since the Jobs are
// canceled, so there is no need to check if all read data was consumed.
should_check_data_consumed_ = false;
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Test that the controller does not create alternative job when the advertised
// versions in AlternativeServiceInfo do not contain any version that is
// supported.
TEST_F(HttpStreamFactoryJobControllerTest,
DoNotCreateAltJobIfQuicVersionsUnsupported) {
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
base::Time expiration = base::Time::Now() + base::Days(1);
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
{quic::ParsedQuicVersion::Unsupported()});
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
request_.reset();
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
void HttpStreamFactoryJobControllerTestBase::
TestDoNotDelayMainJobIfQuicWasRecentlyBroken(bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
base::Time expiration = base::Time::Now() + base::Days(1);
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
quic_context_.params()->supported_versions);
// Enable QUIC but mark the alternative service as recently broken.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
session_->http_server_properties()->MarkAlternativeServiceRecentlyBroken(
alternative_service, NetworkAnonymizationKey());
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// The main job shouldn't have any delay since QUIC was recently broken. Main
// job should still be blocked as alt job has not succeeded or failed at least
// once yet.
EXPECT_EQ(job_controller_->get_main_job_wait_time_for_tests(),
base::TimeDelta());
if (async_quic_session) {
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
} else {
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
base::RunLoop().RunUntilIdle();
// Check that alternative job is bound while main job is destroyed.
EXPECT_FALSE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
request_.reset();
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest,
DoNotDelayMainJobIfQuicWasRecentlyBroken) {
TestDoNotDelayMainJobIfQuicWasRecentlyBroken(false);
}
TEST_F(HttpStreamFactoryJobControllerTest,
DoNotDelayMainJobIfQuicWasRecentlyBrokenAsyncQuicSession) {
TestDoNotDelayMainJobIfQuicWasRecentlyBroken(true);
}
void HttpStreamFactoryJobControllerTestBase::
TestDelayMainJobAfterRecentlyBrokenQuicWasConfirmed(
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
base::Time expiration = base::Time::Now() + base::Days(1);
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
quic_context_.params()->supported_versions);
// Enable QUIC but mark the alternative service as recently broken.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
session_->http_server_properties()->MarkAlternativeServiceRecentlyBroken(
alternative_service, NetworkAnonymizationKey());
// Confirm the alt service.
session_->http_server_properties()->ConfirmAlternativeService(
alternative_service, NetworkAnonymizationKey());
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// The main job should wait and it should still be blocked because the new
// QUIC session hasn't been created yet. The wait time should be greater than
// 0.
EXPECT_TRUE(job_controller_->ShouldWait(
const_cast<HttpStreamFactory::Job*>(job_controller_->main_job())));
if (async_quic_session) {
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
} else {
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
}
EXPECT_GE(job_controller_->get_main_job_wait_time_for_tests(),
base::TimeDelta());
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
base::RunLoop().RunUntilIdle();
// Check that alternative job is bound while main job is destroyed.
EXPECT_FALSE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
request_.reset();
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest,
DelayMainJobAfterRecentlyBrokenQuicWasConfirmed) {
TestDelayMainJobAfterRecentlyBrokenQuicWasConfirmed(false);
}
TEST_F(HttpStreamFactoryJobControllerTest,
DelayMainJobAfterRecentlyBrokenQuicWasConfirmedAsyncQuicSession) {
TestDelayMainJobAfterRecentlyBrokenQuicWasConfirmed(true);
}
void HttpStreamFactoryJobControllerTestBase::TestOnStreamFailedForBothJobs(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddConnect(ASYNC, ERR_FAILED);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// The failure of second Job should be reported to Request as there's no more
// pending Job to serve the Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(1);
base::RunLoop().RunUntilIdle();
VerifyBrokenAlternateProtocolMapping(request_info, false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// This test verifies that the alternative service is not marked broken if both
// jobs fail, and the alternative job is not retried on the alternate network.
TEST_F(HttpStreamFactoryJobControllerTest,
OnStreamFailedForBothJobsWithoutQuicRetry) {
TestOnStreamFailedForBothJobs(false, false);
}
// This test verifies that the alternative service is not marked broken if both
// jobs fail, and the alternative job is retried on the alternate network.
TEST_F(HttpStreamFactoryJobControllerTest,
OnStreamFailedForBothJobsWithQuicRetriedOnAlternateNetwork) {
TestOnStreamFailedForBothJobs(true, false);
}
// This test verifies that the alternative service is not marked broken if both
// jobs fail, and the alternative job is not retried on the alternate network.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
OnStreamFailedForBothJobsWithoutQuicRetryAsyncQuicSession) {
TestOnStreamFailedForBothJobs(false, true);
}
// This test verifies that the alternative service is not marked broken if both
// jobs fail, and the alternative job is retried on the alternate network. This
// test uses asynchronous QUIC session creation.
TEST_F(
HttpStreamFactoryJobControllerTest,
OnStreamFailedForBothJobsWithQuicRetriedOnAlternateNetworkAsyncQuicSession) {
TestOnStreamFailedForBothJobs(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestAltJobFailsAfterMainJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(ASYNC, ERR_FAILED);
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(SYNCHRONOUS, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// Main job succeeds, starts serving Request and it should report status
// to Request. The alternative job will mark the main job complete and gets
// orphaned.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
// JobController shouldn't report the status of second job as request
// is already successfully served.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
base::RunLoop().RunUntilIdle();
// Reset the request as it's been successfully served.
request_.reset();
base::RunLoop().RunUntilIdle();
VerifyBrokenAlternateProtocolMapping(request_info, true);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Verify the brokenness is not cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
VerifyBrokenAlternateProtocolMapping(request_info, true);
}
// This test verifies that the alternative service is marked broken when the
// alternative job fails on default after the main job succeeded. The
// brokenness should not be cleared when the default network changes.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobFailsOnDefaultNetworkAfterMainJobSucceeded) {
TestAltJobFailsAfterMainJobSucceeded(false, false);
}
// This test verifies that the alternative service is marked broken when the
// alternative job fails on both networks after the main job succeeded. The
// brokenness should not be cleared when the default network changes.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobFailsOnBothNetworksAfterMainJobSucceeded) {
TestAltJobFailsAfterMainJobSucceeded(true, false);
}
// This test verifies that the alternative service is marked broken when the
// alternative job fails on default after the main job succeeded. The
// brokenness should not be cleared when the default network changes. This test
// uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobFailsOnDefaultNetworkAfterMainJobSucceededAsyncQuicSession) {
TestAltJobFailsAfterMainJobSucceeded(false, true);
}
// This test verifies that the alternative service is marked broken when the
// alternative job fails on both networks after the main job succeeded. The
// brokenness should not be cleared when the default network changes. This test
// uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobFailsOnBothNetworksAfterMainJobSucceededAsyncQuicSession) {
TestAltJobFailsAfterMainJobSucceeded(true, true);
}
void HttpStreamFactoryJobControllerTestBase::TestAltJobSucceedsMainJobDestroyed(
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (async_quic_session) {
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
} else {
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
request_.reset();
VerifyBrokenAlternateProtocolMapping(request_info, false);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Tests that when alt job succeeds, main job is destroyed.
TEST_F(HttpStreamFactoryJobControllerTest, AltJobSucceedsMainJobDestroyed) {
TestAltJobSucceedsMainJobDestroyed(false);
}
// Tests that when alt job succeeds, main job is destroyed.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsMainJobDestroyedAsyncQuicSession) {
TestAltJobSucceedsMainJobDestroyed(true);
}
// Tests that if alt job succeeds and main job is blocked, main job should be
// cancelled immediately. `request_` completion will clean up the JobController.
// Regression test for crbug.com/678768.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsMainJobBlockedControllerDestroyed) {
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(1));
quic_data_->AddRead(ASYNC, ERR_CONNECTION_CLOSED);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
// `alternative_job` succeeds and should report status to `request_delegate_`.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Invoke OnRequestComplete() which should delete `job_controller_` from
// `factory_`.
request_.reset();
// base::RunLoop().RunUntilIdle();
VerifyBrokenAlternateProtocolMapping(request_info, false);
// This fails without the fix for crbug.com/678768.
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerTest,
SpdySessionKeyHasOriginHostPortPair) {
session_deps_.enable_http2_alternative_service = true;
const char origin_host[] = "www.example.org";
const uint16_t origin_port = 443;
const char alternative_host[] = "mail.example.org";
const uint16_t alternative_port = 123;
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url =
GURL(base::StringPrintf("https://%s:%u", origin_host, origin_port));
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoHTTP2,
alternative_host, alternative_port);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
HostPortPair main_host_port_pair =
HttpStreamFactoryJobPeer::GetSpdySessionKey(job_controller_->main_job())
.host_port_pair();
EXPECT_EQ(origin_host, main_host_port_pair.host());
EXPECT_EQ(origin_port, main_host_port_pair.port());
HostPortPair alternative_host_port_pair =
HttpStreamFactoryJobPeer::GetSpdySessionKey(
job_controller_->alternative_job())
.host_port_pair();
EXPECT_EQ(origin_host, alternative_host_port_pair.host());
EXPECT_EQ(origin_port, alternative_host_port_pair.port());
}
// Regression test for crbug.com/395919017.
// Test that a Job calls HttpServerProperties::SetSupportsSpdy() for an IPv6
// host appropriately.
TEST_F(HttpStreamFactoryJobControllerTest, SupportsSpdyIPv6Destination) {
// Make sure there is only one socket connect.
MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)};
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)};
tcp_data_ = std::make_unique<SequencedSocketData>(reads, writes);
// connect needs to be async, so the H2 session isn't created immediately.
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://[2001:db8::1]");
Initialize(request_info);
// Start a request.
std::unique_ptr<HttpStreamRequest> stream_request = job_controller_->Start(
&request_delegate_, nullptr /* websocket_handshake_create_helper */,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
// Wait for an H2 session creation.
FastForwardUntilNoTasksRemain();
stream_request.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Check that the SpdySession was created.
base::WeakPtr<SpdySession> spdy_session =
session_->spdy_session_pool()->FindAvailableSession(
SpdySessionKey(HostPortPair::FromURL(request_info.url),
request_info.privacy_mode, ProxyChain::Direct(),
SessionUsage::kDestination, request_info.socket_tag,
request_info.network_anonymization_key,
request_info.secure_dns_policy,
/*disable_cert_verification_network_fetches=*/false),
/*enable_ip_based_pooling_for_h2=*/false, /*is_websocket=*/false,
NetLogWithSource());
EXPECT_TRUE(spdy_session);
EXPECT_TRUE(session_->http_server_properties()->GetSupportsSpdy(
url::SchemeHostPort(request_info.url),
request_info.network_anonymization_key));
}
void HttpStreamFactoryJobControllerTestBase::
TestOrphanedJobCompletesControllerDestroyed(bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
// Complete main job now.
base::RunLoop().RunUntilIdle();
// Invoke OnRequestComplete() which should not delete `job_controller_` from
// `factory_` because alt job is yet to finish.
request_.reset();
ASSERT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_FALSE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
// This should not call request_delegate_::OnStreamReady.
job_controller_->OnStreamReady(job_factory_.alternative_job());
// Make sure that controller does not leak.
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Tests that if an orphaned job completes after `request_` is gone,
// JobController will be cleaned up.
TEST_F(HttpStreamFactoryJobControllerTest,
OrphanedJobCompletesControllerDestroyed) {
TestOrphanedJobCompletesControllerDestroyed(false);
}
// Tests that if an orphaned job completes after `request_` is gone,
// JobController will be cleaned up.
TEST_F(HttpStreamFactoryJobControllerTest,
OrphanedJobCompletesControllerDestroyedAsyncQuicSession) {
TestOrphanedJobCompletesControllerDestroyed(true);
}
void HttpStreamFactoryJobControllerTestBase::
TestAltJobSucceedsAfterMainJobFailed(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
// One failed TCP connect.
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, ERR_FAILED));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// `main_job` fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retried on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
if (async_quic_session) {
base::RunLoop run_loop;
EXPECT_CALL(*job_factory_.main_job(), Resume())
.Times(1)
.WillOnce([&run_loop, this]() {
run_loop.Quit();
job_factory_.main_job()->DoResume();
});
run_loop.Run();
}
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
base::RunLoop().RunUntilIdle();
// `alternative_job` succeeds and should report status to Request.
VerifyBrokenAlternateProtocolMapping(request_info, false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// This test verifies that the alternative service is not mark broken if the
// alternative job succeeds on the default network after the main job failed.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnDefaultNetworkAfterMainJobFailed) {
TestAltJobSucceedsAfterMainJobFailed(false, false);
}
// This test verifies that the alternative service is not mark broken if the
// alternative job succeeds on the alternate network after the main job failed.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnAlternateNetworkAfterMainJobFailed) {
TestAltJobSucceedsAfterMainJobFailed(true, false);
}
// This test verifies that the alternative service is not mark broken if the
// alternative job succeeds on the default network after the main job failed.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnDefaultNetworkAfterMainJobFailedAsyncQuicSession) {
TestAltJobSucceedsAfterMainJobFailed(false, true);
}
// This test verifies that the alternative service is not mark broken if the
// alternative job succeeds on the alternate network after the main job failed.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnAlternateNetworkAfterMainJobFailedAsyncQuicSession) {
TestAltJobSucceedsAfterMainJobFailed(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestAltJobSucceedsAfterMainJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// `main_job` fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// Run the message loop to make `main_job` succeed and status will be
// reported to Request.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
VerifyBrokenAlternateProtocolMapping(request_info, false);
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
request_.reset();
// If alt job was retried on the alternate network, the alternative service
// should be marked broken until the default network changes.
VerifyBrokenAlternateProtocolMapping(request_info,
alt_job_retried_on_non_default_network);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
if (alt_job_retried_on_non_default_network) {
// Verify the brokenness is cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
VerifyBrokenAlternateProtocolMapping(request_info, false);
}
}
// This test verifies that the alternative service is not marked broken if the
// alternative job succeeds on the default network after the main job succeeded.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnDefaultNetworkAfterMainJobSucceeded) {
TestAltJobSucceedsAfterMainJobSucceeded(false, false);
}
// This test verifies that the alternative service is marked broken until the
// default network changes if the alternative job succeeds on the non-default
// network, which failed on the default network previously, after the main job
// succeeded. The brokenness should be cleared when the default network
// changes.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnAlternateNetworkAfterMainJobSucceeded) {
TestAltJobSucceedsAfterMainJobSucceeded(true, false);
}
// This test verifies that the alternative service is not marked broken if the
// alternative job succeeds on the default network after the main job succeeded.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnDefaultNetworkAfterMainJobSucceededAsyncQuicSession) {
TestAltJobSucceedsAfterMainJobSucceeded(false, true);
}
// This test verifies that the alternative service is marked broken until the
// default network changes if the alternative job succeeds on the non-default
// network, which failed on the default network previously, after the main job
// succeeded. The brokenness should be cleared when the default network
// changes. This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
AltJobSucceedsOnAlternateNetworkAfterMainJobSucceededAsyncQuicSession) {
TestAltJobSucceedsAfterMainJobSucceeded(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestMainJobSucceedsAfterAltJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
if (async_quic_session) {
base::RunLoop run_loop;
EXPECT_CALL(*job_factory_.main_job(), Resume())
.Times(1)
.WillOnce([&run_loop, this]() {
run_loop.Quit();
job_factory_.main_job()->DoResume();
});
run_loop.Run();
}
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
// Run message loop to make the main job succeed.
base::RunLoop().RunUntilIdle();
request_.reset();
// If alt job was retried on the alternate network, the alternative service
// should be marked broken until the default network changes.
VerifyBrokenAlternateProtocolMapping(request_info,
alt_job_retried_on_non_default_network);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
if (alt_job_retried_on_non_default_network) {
// Verify the brokenness is cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
VerifyBrokenAlternateProtocolMapping(request_info, false);
}
}
// This test verifies that the alternative service is not marked broken if the
// main job succeeds after the alternative job succeeded on the default network.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobSucceededOnDefaultNetwork) {
TestMainJobSucceedsAfterAltJobSucceeded(false, false);
}
// This test verifies that the alternative service is marked broken until the
// default network changes if the main job succeeds after the alternative job
// succeeded on the non-default network, i.e., failed on the default network
// previously. The brokenness should be cleared when the default network
// changes.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobSucceededOnAlternateNetwork) {
TestMainJobSucceedsAfterAltJobSucceeded(true, false);
}
// This test verifies that the alternative service is not marked broken if the
// main job succeeds after the alternative job succeeded on the default network.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobSucceededOnDefaultNetworkAsyncQuicSession) {
TestMainJobSucceedsAfterAltJobSucceeded(false, true);
}
// This test verifies that the alternative service is marked broken until the
// default network changes if the main job succeeds after the alternative job
// succeeded on the non-default network, i.e., failed on the default network
// previously. The brokenness should be cleared when the default network
// changes. This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobSucceededOnAlternateNetworkAsyncQuicSession) {
TestMainJobSucceedsAfterAltJobSucceeded(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestMainJobFailsAfterAltJobSucceeded(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
// Use cold start and complete alt job manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
// Make `alternative_job` succeed.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
if (async_quic_session) {
base::RunLoop run_loop;
EXPECT_CALL(*job_factory_.main_job(), Resume())
.Times(1)
.WillOnce([&run_loop, this]() {
run_loop.Quit();
job_factory_.main_job()->DoResume();
});
run_loop.Run();
}
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
// Run message loop to make the main job fail.
base::RunLoop().RunUntilIdle();
VerifyBrokenAlternateProtocolMapping(request_info, false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// This test verifies that the alternative service is not marked broken if the
// main job fails after the alternative job succeeded on the default network.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobFailsAfterAltJobSucceededOnDefaultNetwork) {
TestMainJobFailsAfterAltJobSucceeded(false, false);
}
// This test verifies that the alternative service is not marked broken if the
// main job fails after the alternative job succeeded on the non-default
// network, i.e., failed on the default network previously.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobFailsAfterAltJobSucceededOnAlternateNetwork) {
TestMainJobFailsAfterAltJobSucceeded(true, false);
}
// This test verifies that the alternative service is not marked broken if the
// main job fails after the alternative job succeeded on the default network.
// This test uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobFailsAfterAltJobSucceededOnDefaultNetworkAsyncQuicSession) {
TestMainJobFailsAfterAltJobSucceeded(false, true);
}
// This test verifies that the alternative service is not marked broken if the
// main job fails after the alternative job succeeded on the non-default
// network, i.e., failed on the default network previously. This test uses
// asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobFailsAfterAltJobSucceededOnAlternateNetworkAsyncQuicSession) {
TestMainJobFailsAfterAltJobSucceeded(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestMainJobSucceedsAfterAltJobFailed(
bool alt_job_retried_on_non_default_network,
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddConnect(SYNCHRONOUS, ERR_FAILED);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
base::HistogramTester histogram_tester;
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// `alternative_job` fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// `main_job` succeeds and should report status to Request.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
if (alt_job_retried_on_non_default_network) {
// Set the alt job as if it failed on the default network and is retired on
// the alternate network.
JobControllerPeer::SetAltJobFailedOnDefaultNetwork(job_controller_);
}
base::RunLoop().RunUntilIdle();
request_.reset();
// Verify that the alternate protocol is marked as broken.
VerifyBrokenAlternateProtocolMapping(request_info, true);
histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed", -ERR_FAILED,
1);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Verify the brokenness is not cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
VerifyBrokenAlternateProtocolMapping(request_info, true);
}
// This test verifies that the alternative service will be marked broken when
// the alternative job fails on the default network and main job succeeds later.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobFailedOnDefaultNetwork) {
TestMainJobSucceedsAfterAltJobFailed(false, false);
}
// This test verifies that the alternative service will be marked broken when
// the alternative job fails on both default and alternate networks and main job
// succeeds later.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobFailedOnBothNetworks) {
TestMainJobSucceedsAfterAltJobFailed(true, false);
}
// This test verifies that the alternative service will be marked broken when
// the alternative job fails on the default network and main job succeeds later.
// This test uses asynchronous Quic session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobFailedOnDefaultNetworkAsyncQuicSession) {
TestMainJobSucceedsAfterAltJobFailed(false, true);
}
// This test verifies that the alternative service will be marked broken when
// the alternative job fails on both default and alternate networks and main job
// succeeds later. This test uses asynchronous Quic session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterAltJobFailedOnBothNetworksAsyncQuicSession) {
TestMainJobSucceedsAfterAltJobFailed(true, true);
}
void HttpStreamFactoryJobControllerTestBase::
TestMainJobSucceedsAfterIgnoredError(int net_error,
bool async_quic_session,
bool expect_broken,
std::string alternate_host) {
SetAsyncQuicSession(async_quic_session);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddConnect(SYNCHRONOUS, net_error);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
base::HistogramTester histogram_tester;
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
if (alternate_host.empty()) {
alternate_host = server.host();
}
AlternativeService alternative_service(NextProto::kProtoQUIC, alternate_host,
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// `alternative_job` fails but should not report status to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// `main_job` succeeds and should report status to Request.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
request_.reset();
// Verify that the alternate protocol is not marked as broken.
VerifyBrokenAlternateProtocolMapping(request_info, expect_broken);
if (expect_broken) {
histogram_tester.ExpectUniqueSample("Net.AlternateServiceFailed",
-net_error, 1);
}
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Verifies that if the alternative job fails due to a connection change event,
// then the alternative service is not marked as broken.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterConnectionChanged) {
TestMainJobSucceedsAfterIgnoredError(ERR_NETWORK_CHANGED, false);
}
// Verifies that if the alternative job fails due to a disconnected network,
// then the alternative service is not marked as broken.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterInternetDisconnected) {
TestMainJobSucceedsAfterIgnoredError(ERR_INTERNET_DISCONNECTED, false);
}
// Verifies that if the alternative job fails due to a connection change event,
// then the alternative service is not marked as broken. This test uses
// asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterConnectionChangedAsyncQuicSession) {
TestMainJobSucceedsAfterIgnoredError(ERR_NETWORK_CHANGED, true);
}
// Verifies that if the alternative job fails due to a disconnected network,
// then the alternative service is not marked as broken. This test uses
// asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterInternetDisconnectedAsyncQuicSession) {
TestMainJobSucceedsAfterIgnoredError(ERR_INTERNET_DISCONNECTED, true);
}
// Verifies that if the alternative job fails due to a DNS failure,
// then the alternative service is not marked as broken.
TEST_F(HttpStreamFactoryJobControllerTest, MainJobSucceedsAfterDnsFailure) {
TestMainJobSucceedsAfterIgnoredError(ERR_NAME_NOT_RESOLVED, false);
}
// Verifies that if the alternative job fails due to a DNS failure,
// then the alternative service is not marked as broken. This test uses
// asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterDnsFailureAsyncQuicSession) {
TestMainJobSucceedsAfterIgnoredError(ERR_NAME_NOT_RESOLVED, true);
}
// Verifies that if the alternative job fails due to a DNS failure on a
// different name, then the alternative service is marked as broken.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterDnsFailureWithAlternateName) {
TestMainJobSucceedsAfterIgnoredError(ERR_NAME_NOT_RESOLVED, false, true,
"alternate.google.com");
}
// Verifies that if the alternative job fails due to a DNS failure on a
// different name, then the alternative service is marked as broken. This test
// uses asynchronous QUIC session creation.
TEST_F(HttpStreamFactoryJobControllerTest,
MainJobSucceedsAfterDnsFailureWithAlternateNameAsyncQuicSession) {
TestMainJobSucceedsAfterIgnoredError(ERR_NAME_NOT_RESOLVED, true, true,
"alternate.google.com");
}
// Regression test for crbug/621069.
// Get load state after main job fails and before alternative job succeeds.
TEST_F(HttpStreamFactoryJobControllerTest, GetLoadStateAfterMainJobFailed) {
// Use COLD_START to complete alt job manually.
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, ERR_FAILED));
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// `main_job` fails but should not report status to Request.
// The alternative job will mark the main job complete.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
base::RunLoop().RunUntilIdle();
// Controller should use alternative job to get load state.
job_controller_->GetLoadState();
// `alternative_job` succeeds and should report status to Request.
auto http_stream = std::make_unique<HttpBasicStream>(
std::make_unique<ClientSocketHandle>(), false);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, http_stream.get()));
HttpStreamFactoryJobPeer::SetStream(job_factory_.alternative_job(),
std::move(http_stream));
job_controller_->OnStreamReady(job_factory_.alternative_job());
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
void HttpStreamFactoryJobControllerTestBase::TestResumeMainJobWhenAltJobStalls(
bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
// Use COLD_START to stall alt job.
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
if (async_quic_session) {
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
}
// Alt job is stalled and main job should complete successfully.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
}
TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobWhenAltJobStalls) {
TestResumeMainJobWhenAltJobStalls(false);
}
TEST_F(HttpStreamFactoryJobControllerTest,
ResumeMainJobWhenAltJobStallsAsyncQuicSession) {
TestResumeMainJobWhenAltJobStalls(true);
}
TEST_F(HttpStreamFactoryJobControllerTest, InvalidPortForQuic) {
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
// Using a restricted port 101 for QUIC should fail and the alternative job
// should post OnStreamFailedCall on the controller to resume the main job.
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
101);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_factory_.main_job()->is_waiting());
// Wait until OnStreamFailedCallback is executed on the alternative job.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
base::RunLoop().RunUntilIdle();
}
// Verifies that the main job is not resumed until after the alt job completes
// host resolution.
TEST_F(HttpStreamFactoryJobControllerTest, HostResolutionHang) {
auto hanging_resolver = std::make_unique<MockHostResolver>();
hanging_resolver->set_ondemand_mode(true);
hanging_resolver->rules()->AddRule("www.google.com", "1.2.3.4");
session_deps_.host_resolver = std::move(hanging_resolver);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// handshake will fail asynchronously after mock data is unpaused.
MockQuicData quic_data(version_);
quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause
quic_data.AddRead(ASYNC, ERR_FAILED);
quic_data.AddWrite(ASYNC, ERR_FAILED);
quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get());
// Enable delayed TCP and set time delay for waiting job.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
ServerNetworkStats stats1;
stats1.srtt = base::Microseconds(10);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")),
NetworkAnonymizationKey(), stats1);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// This prevents handshake from immediately succeeding.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
// Since the alt job has not finished host resolution, there should be no
// delayed task posted to resume the main job.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(base::Microseconds(50));
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
// Allow alt job host resolution to complete.
session_deps_.host_resolver->ResolveAllPending();
// Task to resume main job in 15 microseconds should be posted.
EXPECT_NE(0u, GetPendingMainThreadTaskCount());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(base::Microseconds(14));
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
FastForwardBy(base::Microseconds(1));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Unpause mock quic data.
// Will cause `alternative_job` to fail, but its failure should not be
// reported to Request.
EXPECT_CALL(request_delegate_, OnStreamFailed(_, _, _, _)).Times(0);
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_));
// OnStreamFailed will post a task to resume the main job immediately but
// won't call Resume() on the main job since it's been resumed already.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
quic_data.Resume();
FastForwardUntilNoTasksRemain();
// Alt job should be cleaned up
EXPECT_FALSE(job_controller_->alternative_job());
}
// Regression test for crbug.com/789560.
TEST_F(HttpStreamFactoryJobControllerTest, ResumeMainJobLaterCanceled) {
std::unique_ptr<ConfiguredProxyResolutionService> proxy_resolution_service =
ConfiguredProxyResolutionService::CreateDirect();
ConfiguredProxyResolutionService* proxy_resolution_service_raw =
proxy_resolution_service.get();
session_deps_.proxy_resolution_service = std::move(proxy_resolution_service);
// Using hanging resolver will cause the alternative job to hang indefinitely.
session_deps_.alternate_host_resolver =
std::make_unique<HangingHostResolver>();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// Enable delayed TCP and set time delay for waiting job.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
ServerNetworkStats stats1;
stats1.srtt = base::Microseconds(10);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")),
NetworkAnonymizationKey(), stats1);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop run_loop;
// The main job should be resumed without delay when alt job fails.
EXPECT_CALL(*job_factory_.main_job(), Resume())
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
job_controller_->OnStreamFailed(job_factory_.alternative_job(),
ERR_QUIC_PROTOCOL_ERROR);
FastForwardBy(base::Microseconds(0));
run_loop.Run();
EXPECT_FALSE(job_controller_->alternative_job());
// Calling ForceReloadProxyConfig will cause the proxy configuration to
// change. It will still be the direct connection but the configuration
// version will be bumped. That is enough for the job controller to restart
// the jobs.
proxy_resolution_service_raw->ForceReloadProxyConfig();
HttpStreamFactoryJobPeer::SetShouldReconsiderProxy(job_factory_.main_job());
// Now the alt service is marked as broken (e.g. through a different request),
// so only non-alt job is restarted.
session_->http_server_properties()->MarkAlternativeServiceBroken(
alternative_service, NetworkAnonymizationKey());
job_controller_->OnStreamFailed(job_factory_.main_job(), ERR_FAILED);
// Jobs are restarted.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
// There shouldn't be any ResumeMainJobLater() delayed tasks.
// This EXPECT_CALL will fail before crbug.com/789560 fix.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(base::Microseconds(15));
EXPECT_TRUE(job_controller_->main_job());
request_.reset();
}
// Test that main job is blocked for kMaxDelayTimeForMainJob(3s) if
// http_server_properties cached an inappropriate large srtt for the server,
// which would potentially delay the main job for a extremely long time in
// delayed tcp case.
TEST_F(HttpStreamFactoryJobControllerTest, DelayedTCPWithLargeSrtt) {
// The max delay time should be in sync with .cc file.
base::TimeDelta kMaxDelayTimeForMainJob = base::Seconds(3);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// handshake will fail asynchronously after mock data is unpaused.
MockQuicData quic_data(version_);
quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause
quic_data.AddRead(ASYNC, ERR_FAILED);
quic_data.AddWrite(ASYNC, ERR_FAILED);
quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get());
// Enable delayed TCP and set time delay for waiting job.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
ServerNetworkStats stats1;
stats1.srtt = base::Seconds(100);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")),
NetworkAnonymizationKey(), stats1);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// This prevents handshake from immediately succeeding.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
base::RunLoop().RunUntilIdle();
// Main job is not blocked but hasn't resumed yet; it should resume in 3s.
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_));
// Task to resume main job in 3 seconds should be posted.
EXPECT_NE(0u, GetPendingMainThreadTaskCount());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(kMaxDelayTimeForMainJob - base::Microseconds(1));
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
FastForwardBy(base::Microseconds(1));
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_));
// Unpause mock quic data and run all remaining tasks. Alt-job should fail
// and be cleaned up.
quic_data.Resume();
FastForwardUntilNoTasksRemain();
EXPECT_FALSE(job_controller_->alternative_job());
}
// TODO(crbug.com/40649375): Disabled because the pending task count does
// not match expectations.
TEST_F(HttpStreamFactoryJobControllerTest,
DISABLED_ResumeMainJobImmediatelyOnStreamFailed) {
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// handshake will fail asynchronously after mock data is unpaused.
MockQuicData quic_data(version_);
quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause
quic_data.AddRead(ASYNC, ERR_FAILED);
quic_data.AddWrite(ASYNC, ERR_FAILED);
quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get());
// Enable delayed TCP and set time delay for waiting job.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
ServerNetworkStats stats1;
stats1.srtt = base::Microseconds(10);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")),
NetworkAnonymizationKey(), stats1);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// This prevents handshake from immediately succeeding.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// Main job is not blocked but hasn't resumed yet; it's scheduled to resume
// in 15us.
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
EXPECT_FALSE(JobControllerPeer::main_job_is_resumed(job_controller_));
// Task to resume main job in 15us should be posted.
EXPECT_NE(0u, GetPendingMainThreadTaskCount());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(base::Microseconds(1));
// Now unpause the mock quic data to fail the alt job. This should immediately
// resume the main job.
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1);
quic_data.Resume();
FastForwardBy(base::TimeDelta());
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_TRUE(JobControllerPeer::main_job_is_resumed(job_controller_));
// Verify there is another task to resume main job with delay but should
// not call Resume() on the main job as main job has been resumed.
EXPECT_NE(0u, GetPendingMainThreadTaskCount());
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(0);
FastForwardBy(base::Microseconds(15));
FastForwardUntilNoTasksRemain();
}
TEST_F(HttpStreamFactoryJobControllerTest, PreconnectToHostWithValidAltSvc) {
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddWrite(SYNCHRONOUS, client_maker_.MakeInitialSettingsPacket(1));
quic_data_->AddRead(ASYNC, ERR_CONNECTION_CLOSED);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
SetPreconnect();
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
job_controller_->Preconnect(1);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_->main_job()->job_type());
EXPECT_FALSE(job_controller_->alternative_job());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// When preconnect to a H2 supported server, only 1 connection is opened.
TEST_F(HttpStreamFactoryJobControllerTest,
PreconnectMultipleStreamsToH2Server) {
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SetPreconnect();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.example.com");
Initialize(request_info);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
job_controller_->Preconnect(/*num_streams=*/5);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_->main_job()->job_type());
// There is only 1 connect even though multiple streams were requested.
EXPECT_EQ(
1, HttpStreamFactoryJobPeer::GetNumStreams(job_controller_->main_job()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Check that the logic to only preconnect a single socket to servers with H2
// support respects NetworkIsolationKeys.
TEST_F(HttpStreamFactoryJobControllerTest,
PreconnectMultipleStreamsToH2ServerWithNetworkIsolationKey) {
base::test::ScopedFeatureList feature_list;
// It's not strictly necessary to enable
// `kPartitionConnectionsByNetworkIsolationKey`, but the second phase of the
// test would only make 4 connections, reusing the first connection, without
// it.
feature_list.InitAndEnableFeature(
features::kPartitionConnectionsByNetworkIsolationKey);
// Need to re-create HttpServerProperties after enabling the field trial,
// since it caches the field trial value on construction.
session_deps_.http_server_properties =
std::make_unique<HttpServerProperties>();
const SchemefulSite kSite1(GURL("https://foo.test/"));
const NetworkIsolationKey kNetworkIsolationKey1(kSite1, kSite1);
const auto kNetworkAnonymizationKey1 =
NetworkAnonymizationKey::CreateSameSite(kSite1);
const SchemefulSite kSite2(GURL("https://bar.test/"));
const NetworkIsolationKey kNetworkIsolationKey2(kSite2, kSite2);
const auto kNetworkAnonymizationKey2 =
NetworkAnonymizationKey::CreateSameSite(kSite2);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SetPreconnect();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.example.com");
request_info.network_isolation_key = kNetworkIsolationKey1;
request_info.network_anonymization_key = kNetworkAnonymizationKey1;
Initialize(request_info);
// Sets server support HTTP/2, using kNetworkIsolationKey.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, kNetworkAnonymizationKey1, true);
job_controller_->Preconnect(/*num_streams=*/5);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_->main_job()->job_type());
// There is only 1 connect even though multiple streams were requested.
EXPECT_EQ(
1, HttpStreamFactoryJobPeer::GetNumStreams(job_controller_->main_job()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Now try using two different NetworkIsolationKeys, one empty, one not, and
// make sure that 5 sockets are preconnected with each one.
std::vector<std::unique_ptr<SequencedSocketData>> socket_data;
for (auto other_network_isolation_key :
{NetworkIsolationKey(), kNetworkIsolationKey2}) {
for (int i = 0; i < 5; ++i) {
socket_data.emplace_back(std::make_unique<SequencedSocketData>(
MockConnect(ASYNC, OK), base::span<const MockRead>(),
base::span<const MockWrite>()));
session_deps_.socket_factory->AddSocketDataProvider(
socket_data.back().get());
}
request_info.network_isolation_key = other_network_isolation_key;
request_info.network_anonymization_key =
NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
other_network_isolation_key);
MockHttpStreamRequestDelegate request_delegate;
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate, session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
job_controller_ptr->Preconnect(/*num_streams=*/5);
// Five jobs should be started.
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_ptr->main_job()->job_type());
EXPECT_EQ(5, HttpStreamFactoryJobPeer::GetNumStreams(
job_controller_ptr->main_job()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
}
void HttpStreamFactoryJobControllerTestBase::
TestDoNotDelayMainJobIfHasAvailableSpdySession(bool async_quic_session) {
SetAsyncQuicSession(async_quic_session);
SetNotDelayMainJobWithAvailableSpdySession();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// Put a SpdySession in the pool.
HostPortPair host_port_pair("www.google.com", 443);
SpdySessionKey key(host_port_pair, PRIVACY_MODE_DISABLED,
ProxyChain::Direct(), SessionUsage::kDestination,
SocketTag(), NetworkAnonymizationKey(),
SecureDnsPolicy::kAllow,
/*disable_cert_verification_network_fetches=*/false);
std::ignore = CreateFakeSpdySession(session_->spdy_session_pool(), key);
// Handshake will fail asynchronously after mock data is unpaused.
MockQuicData quic_data(version_);
quic_data.AddRead(ASYNC, ERR_IO_PENDING); // Pause
quic_data.AddRead(ASYNC, ERR_FAILED);
quic_data.AddWrite(ASYNC, ERR_FAILED);
quic_data.AddSocketDataToFactory(session_deps_.socket_factory.get());
// Enable delayed TCP and set time delay for waiting job.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
ServerNetworkStats stats1;
stats1.srtt = base::Milliseconds(100);
session_->http_server_properties()->SetServerNetworkStats(
url::SchemeHostPort(GURL("https://www.google.com")),
NetworkAnonymizationKey(), stats1);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// This prevents handshake from immediately succeeding.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
EXPECT_TRUE(job_controller_->alternative_job());
// The main job shouldn't have any delay since request can be sent on
// available SPDY session. When QUIC session creation is async, the main job
// should still be blocked as alt job has not succeeded or failed at least
// once yet. Otherwise the main job should not be blocked
EXPECT_EQ(job_controller_->get_main_job_wait_time_for_tests(),
base::TimeDelta());
if (async_quic_session) {
EXPECT_TRUE(JobControllerPeer::main_job_is_blocked(job_controller_));
// The main job should have a SPDY session available.
EXPECT_TRUE(job_controller_->main_job()->HasAvailableSpdySession());
// Wait for QUIC session creation attempt to resume and unblock the main
// job.
FastForwardBy(base::Milliseconds(1));
// Main job should still have no delay and should be unblocked now.
EXPECT_EQ(job_controller_->get_main_job_wait_time_for_tests(),
base::TimeDelta());
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
} else {
EXPECT_FALSE(JobControllerPeer::main_job_is_blocked(job_controller_));
EXPECT_TRUE(job_controller_->main_job()->HasAvailableSpdySession());
}
}
TEST_F(HttpStreamFactoryJobControllerTest,
DoNotDelayMainJobIfHasAvailableSpdySession) {
TestDoNotDelayMainJobIfHasAvailableSpdySession(false);
}
TEST_F(HttpStreamFactoryJobControllerTest,
DoNotDelayMainJobIfHasAvailableSpdySessionAsyncQuicSession) {
TestDoNotDelayMainJobIfHasAvailableSpdySession(true);
}
// Check the case that while a preconnect is waiting in the H2 request queue,
// and a SPDY session appears, the job completes successfully.
TEST_F(HttpStreamFactoryJobControllerTest, SpdySessionInterruptsPreconnect) {
// Make sure there is only one socket connect.
MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)};
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)};
tcp_data_ = std::make_unique<SequencedSocketData>(reads, writes);
// connect needs to be async, so the H2 session isn't created immediately.
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
// Start a non-preconnect request.
std::unique_ptr<HttpStreamRequest> stream_request = job_controller_->Start(
&request_delegate_, nullptr /* websocket_handshake_create_helper */,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
// Create and start a preconnect request, which should start watching the
// SpdySessionPool.
MockHttpStreamRequestDelegate preconnect_request_delegate;
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &preconnect_request_delegate, session_.get(), &job_factory_,
request_info, /*is_preconnect=*/true, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_, std::move(job_controller));
job_controller_ptr->Preconnect(1);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
// The non-preconnect request should create an H2 session, which the
// preconnect then sees, and the preconnect request should complete and be
// torn down without ever requesting a socket. If it did request a socket, the
// test would fail since the mock socket factory would see an unexpected
// socket request.
base::RunLoop().RunUntilIdle();
stream_request.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
// Sanity check - make sure the SpdySession was created.
base::WeakPtr<SpdySession> spdy_session =
session_->spdy_session_pool()->FindAvailableSession(
SpdySessionKey(HostPortPair::FromURL(request_info.url),
request_info.privacy_mode, ProxyChain::Direct(),
SessionUsage::kDestination, request_info.socket_tag,
request_info.network_anonymization_key,
request_info.secure_dns_policy,
/*disable_cert_verification_network_fetches=*/false),
false /* enable_ip_based_pooling_for_h2 */, /*is_websocket=*/false,
NetLogWithSource());
EXPECT_TRUE(spdy_session);
}
// This test verifies that a preconnect job doesn't block subsequent requests
// which can use an existing IP based pooled SpdySession.
// This test uses "wildcard.pem" to support IpBasedPooling for *.example.org,
// and starts 3 requests:
// [1] Normal non-preconnect request to www.example.org.
// [2] Preconnect request to other.example.org. The connection is paused until
// OnConnectComplete() is called in the end of the test.
// [3] Normal non-preconnect request to other.example.org. This request must
// succeed even while the preconnect request [2] is paused.
TEST_F(HttpStreamFactoryJobControllerTest,
PreconnectJobDoesntBlockIpBasedPooling) {
// Make sure that both "www.example.org" and "other.example.org" are pointing
// to the same IP address.
session_deps_.host_resolver->rules()->AddRule(
"www.example.org", IPAddress::IPv4Localhost().ToString());
session_deps_.host_resolver->rules()->AddRule(
"other.example.org", IPAddress::IPv4Localhost().ToString());
// Make `host_resolver` asynchronous to simulate the issue of
// crbug.com/1320608.
session_deps_.host_resolver->set_synchronous_mode(false);
// This is used for the non-preconnect requests [1] and [3].
MockWrite writes[] = {MockWrite(SYNCHRONOUS, ERR_IO_PENDING, 0)};
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING, 1)};
SequencedSocketData first_socket(reads, writes);
first_socket.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&first_socket);
// This is used for the non-preconnect requests.
SSLSocketDataProvider ssl_data1(ASYNC, OK);
ssl_data1.next_proto = NextProto::kProtoHTTP2;
// "wildcard.pem" supports "*.example.org".
ssl_data1.ssl_info.cert =
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem");
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data1);
// This is used for the preconnect request.
SequencedSocketData second_socket;
// The connection is paused. And it will be completed with
// ERR_CONNECTION_FAILED.
second_socket.set_connect_data(MockConnect(ASYNC, ERR_IO_PENDING));
session_deps_.socket_factory->AddSocketDataProvider(&second_socket);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.org");
Initialize(request_info);
// Start a non-preconnect request [1].
{
std::unique_ptr<HttpStreamRequest> stream_request = job_controller_->Start(
&request_delegate_,
/*websocket_handshake_stream_create_helper=*/nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_CALL(*job_factory_.main_job(), Resume()).Times(1).WillOnce([this]() {
job_factory_.main_job()->DoResume();
});
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
}
// Sanity check - make sure the SpdySession was created.
{
base::WeakPtr<SpdySession> spdy_session =
session_->spdy_session_pool()->FindAvailableSession(
SpdySessionKey(HostPortPair::FromURL(request_info.url),
request_info.privacy_mode, ProxyChain::Direct(),
SessionUsage::kDestination, request_info.socket_tag,
request_info.network_anonymization_key,
request_info.secure_dns_policy,
/*disable_cert_verification_network_fetches=*/false),
/*enable_ip_based_pooling_for_h2=*/false, /*is_websocket=*/false,
NetLogWithSource());
EXPECT_TRUE(spdy_session);
}
HttpRequestInfo other_request_info;
other_request_info.method = "GET";
other_request_info.url = GURL("https://other.example.org");
// Create and start a preconnect request [2].
MockHttpStreamRequestDelegate preconnect_request_delegate;
auto preconnect_job_controller =
std::make_unique<HttpStreamFactory::JobController>(
factory_, &preconnect_request_delegate, session_.get(), &job_factory_,
other_request_info, /*is_preconnect=*/true,
/*is_websocket=*/false, /*enable_ip_based_pooling_for_h2=*/true,
enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* preconnect_job_controller_ptr = preconnect_job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(preconnect_job_controller));
preconnect_job_controller_ptr->Preconnect(1);
base::RunLoop().RunUntilIdle();
// The SpdySession is available for IP based pooling when the host resolution
// has finished.
{
const SpdySessionKey spdy_session_key = SpdySessionKey(
HostPortPair::FromURL(other_request_info.url),
other_request_info.privacy_mode, ProxyChain::Direct(),
SessionUsage::kDestination, other_request_info.socket_tag,
other_request_info.network_anonymization_key,
other_request_info.secure_dns_policy,
/*disable_cert_verification_network_fetches=*/false);
EXPECT_FALSE(session_->spdy_session_pool()->FindAvailableSession(
spdy_session_key, /*enable_ip_based_pooling_for_h2=*/false,
/*is_websocket=*/false, NetLogWithSource()));
EXPECT_TRUE(session_->spdy_session_pool()->FindAvailableSession(
spdy_session_key, /*enable_ip_based_pooling_for_h2=*/true,
/*is_websocket=*/false, NetLogWithSource()));
}
// Create and start a second non-preconnect request [3].
{
MockHttpStreamRequestDelegate request_delegate;
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate, session_.get(), &job_factory_,
other_request_info, /*is_preconnect=*/false,
/*is_websocket=*/false, /*enable_ip_based_pooling_for_h2=*/true,
enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
std::unique_ptr<HttpStreamRequest> second_stream_request =
job_controller_ptr->Start(
&request_delegate,
/*websocket_handshake_stream_create_helper=*/nullptr,
NetLogWithSource(), HttpStreamRequest::HTTP_STREAM,
DEFAULT_PRIORITY);
base::RunLoop run_loop;
EXPECT_CALL(request_delegate, OnStreamReadyImpl(_, _))
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
second_stream_request.reset();
}
second_socket.socket()->OnConnectComplete(
MockConnect(SYNCHRONOUS, ERR_CONNECTION_FAILED));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_TRUE(first_socket.AllReadDataConsumed());
EXPECT_TRUE(first_socket.AllWriteDataConsumed());
}
class JobControllerLimitMultipleH2Requests
: public HttpStreamFactoryJobControllerTestBase {
protected:
JobControllerLimitMultipleH2Requests()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false) {}
const int kNumRequests = 5;
void SetUp() override { SkipCreatingJobController(); }
};
TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequests) {
// Make sure there is only one socket connect.
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
tcp_data_ =
std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>());
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
SpdySessionPoolPeer pool_peer(session_->spdy_session_pool());
pool_peer.SetEnableSendingInitialData(false);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
std::vector<std::unique_ptr<HttpStreamRequest>> requests;
for (int i = 0; i < kNumRequests; ++i) {
request_delegates.emplace_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
auto request = job_controller_ptr->Start(
request_delegates[i].get(), nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
requests.push_back(std::move(request));
}
for (int i = 0; i < kNumRequests; ++i) {
EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _));
}
base::RunLoop().RunUntilIdle();
requests.clear();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
auto entries = net_log_observer_.GetEntries();
size_t log_position = 0;
for (int i = 0; i < kNumRequests - 1; ++i) {
log_position = ExpectLogContainsSomewhereAfter(
entries, log_position, NetLogEventType::HTTP_STREAM_JOB_THROTTLED,
NetLogEventPhase::NONE);
}
}
// Check that throttling simultaneous requests to a single H2 server respects
// NetworkIsolationKeys.
TEST_F(JobControllerLimitMultipleH2Requests,
MultipleRequestsNetworkIsolationKey) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(
features::kPartitionConnectionsByNetworkIsolationKey);
// Need to re-create HttpServerProperties after enabling the field trial,
// since it caches the field trial value on construction.
session_deps_.http_server_properties =
std::make_unique<HttpServerProperties>();
const SchemefulSite kSite1(GURL("https://foo.test/"));
const NetworkIsolationKey kNetworkIsolationKey1(kSite1, kSite1);
const auto kNetworkAnonymizationKey1 =
NetworkAnonymizationKey::CreateSameSite(kSite1);
const SchemefulSite kSite2(GURL("https://bar.test/"));
const NetworkIsolationKey kNetworkIsolationKey2(kSite2, kSite2);
const auto kNetworkAnonymizationKey2 =
NetworkAnonymizationKey::CreateSameSite(kSite2);
tcp_data_ = std::make_unique<SequencedSocketData>(
MockConnect(SYNCHRONOUS, ERR_IO_PENDING), base::span<MockRead>(),
base::span<MockWrite>());
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, kNetworkAnonymizationKey1, true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
std::vector<std::unique_ptr<HttpStreamRequest>> requests;
std::vector<std::unique_ptr<SequencedSocketData>> socket_data;
for (int i = 0; i < kNumRequests; ++i) {
// Shouldn't matter whether requests are interleaved by NetworkIsolationKey
// or not.
for (const auto& network_isolation_key :
{NetworkIsolationKey(), kNetworkIsolationKey1,
kNetworkIsolationKey2}) {
request_info.network_isolation_key = network_isolation_key;
request_info.network_anonymization_key =
NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
network_isolation_key);
// For kNetworkIsolationKey1, all requests but the first will be
// throttled.
if (i == 0 || network_isolation_key != kNetworkIsolationKey1) {
socket_data.emplace_back(std::make_unique<SequencedSocketData>(
MockConnect(ASYNC, OK), base::span<const MockRead>(),
base::span<const MockWrite>()));
session_deps_.socket_factory->AddSocketDataProvider(
socket_data.back().get());
}
request_delegates.emplace_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
auto request = job_controller_ptr->Start(
request_delegates[i].get(), nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
requests.push_back(std::move(request));
}
}
TransportClientSocketPool* socket_pool =
reinterpret_cast<TransportClientSocketPool*>(session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, ProxyChain::Direct()));
ClientSocketPool::GroupId group_id0(
url::SchemeHostPort(request_info.url), request_info.privacy_mode,
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
/*disable_cert_network_fetches=*/false);
ClientSocketPool::GroupId group_id1(
url::SchemeHostPort(request_info.url), request_info.privacy_mode,
kNetworkAnonymizationKey1, SecureDnsPolicy::kAllow,
/*disable_cert_network_fetches=*/false);
ClientSocketPool::GroupId group_id2(
url::SchemeHostPort(request_info.url), request_info.privacy_mode,
kNetworkAnonymizationKey2, SecureDnsPolicy::kAllow,
/*disable_cert_network_fetches=*/false);
EXPECT_EQ(static_cast<uint32_t>(kNumRequests),
socket_pool->NumConnectJobsInGroupForTesting(group_id0));
EXPECT_EQ(1u, socket_pool->NumConnectJobsInGroupForTesting(group_id1));
EXPECT_EQ(static_cast<uint32_t>(kNumRequests),
socket_pool->NumConnectJobsInGroupForTesting(group_id2));
}
TEST_F(JobControllerLimitMultipleH2Requests, MultipleRequestsFirstRequestHang) {
// First socket connect hang.
SequencedSocketData hangdata;
hangdata.set_connect_data(MockConnect(SYNCHRONOUS, ERR_IO_PENDING));
session_deps_.socket_factory->AddSocketDataProvider(&hangdata);
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
std::list<SequencedSocketData> socket_data;
std::list<SSLSocketDataProvider> ssl_socket_data;
// kNumRequests - 1 will resume themselves after a delay. There will be
// kNumRequests - 1 sockets opened.
for (int i = 0; i < kNumRequests - 1; i++) {
// Only the first one needs a MockRead because subsequent sockets are
// not used to establish a SpdySession.
if (i == 0) {
socket_data.emplace_back(reads, base::span<MockWrite>());
} else {
socket_data.emplace_back();
}
socket_data.back().set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back());
ssl_socket_data.emplace_back(ASYNC, OK);
ssl_socket_data.back().next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_socket_data.back());
}
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
SpdySessionPoolPeer pool_peer(session_->spdy_session_pool());
pool_peer.SetEnableSendingInitialData(false);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
std::vector<std::unique_ptr<HttpStreamRequest>> requests;
for (int i = 0; i < kNumRequests; ++i) {
request_delegates.push_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
auto request = job_controller_ptr->Start(
request_delegates[i].get(), nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
requests.push_back(std::move(request));
}
for (int i = 0; i < kNumRequests; ++i) {
EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _));
}
EXPECT_GT(GetPendingMainThreadTaskCount(), 0u);
FastForwardBy(base::Milliseconds(HttpStreamFactory::Job::kHTTP2ThrottleMs));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
requests.clear();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_TRUE(hangdata.AllReadDataConsumed());
for (const auto& data : socket_data) {
EXPECT_TRUE(data.AllReadDataConsumed());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
}
TEST_F(JobControllerLimitMultipleH2Requests,
MultipleRequestsFirstRequestCanceled) {
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
SequencedSocketData first_socket(reads, base::span<MockWrite>());
first_socket.set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider first_ssl_data(ASYNC, OK);
first_ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSocketDataProvider(&first_socket);
session_deps_.socket_factory->AddSSLSocketDataProvider(&first_ssl_data);
std::list<SequencedSocketData> socket_data;
std::list<SSLSocketDataProvider> ssl_socket_data;
// kNumRequests - 1 will be resumed when the first request is canceled.
for (int i = 0; i < kNumRequests - 1; i++) {
socket_data.emplace_back();
socket_data.back().set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&socket_data.back());
ssl_socket_data.emplace_back(ASYNC, OK);
ssl_socket_data.back().next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_socket_data.back());
}
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
SpdySessionPoolPeer pool_peer(session_->spdy_session_pool());
pool_peer.SetEnableSendingInitialData(false);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
std::vector<std::unique_ptr<HttpStreamRequest>> requests;
for (int i = 0; i < kNumRequests; ++i) {
request_delegates.emplace_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
auto request = job_controller_ptr->Start(
request_delegates[i].get(), nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
requests.push_back(std::move(request));
}
// Cancel the first one.
requests[0].reset();
for (int i = 1; i < kNumRequests; ++i) {
EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _));
}
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
requests.clear();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_TRUE(first_socket.AllReadDataConsumed());
for (const auto& data : socket_data) {
EXPECT_TRUE(data.AllReadDataConsumed());
EXPECT_TRUE(data.AllWriteDataConsumed());
}
}
TEST_F(JobControllerLimitMultipleH2Requests, MultiplePreconnects) {
// Make sure there is only one socket connect.
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
SetPreconnect();
Initialize(request_info);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
for (int i = 0; i < kNumRequests; ++i) {
request_delegates.emplace_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
job_controller_ptr->Preconnect(1);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
}
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(JobControllerLimitMultipleH2Requests, H1NegotiatedForFirstRequest) {
// First socket is an HTTP/1.1 socket.
SequencedSocketData first_socket;
first_socket.set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSocketDataProvider(&first_socket);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
// Second socket is an HTTP/2 socket.
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
SequencedSocketData second_socket(reads, base::span<MockWrite>());
second_socket.set_connect_data(MockConnect(ASYNC, OK));
session_deps_.socket_factory->AddSocketDataProvider(&second_socket);
SSLSocketDataProvider second_ssl_data(ASYNC, OK);
second_ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&second_ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.com");
Initialize(request_info);
SpdySessionPoolPeer pool_peer(session_->spdy_session_pool());
pool_peer.SetEnableSendingInitialData(false);
// Sets server support HTTP/2.
url::SchemeHostPort server(request_info.url);
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
std::vector<std::unique_ptr<MockHttpStreamRequestDelegate>> request_delegates;
std::vector<std::unique_ptr<HttpStreamRequest>> requests;
for (int i = 0; i < 2; ++i) {
request_delegates.emplace_back(
std::make_unique<MockHttpStreamRequestDelegate>());
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegates[i].get(), session_.get(), &job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
auto request = job_controller_ptr->Start(
request_delegates[i].get(), nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_FALSE(job_controller_ptr->alternative_job());
requests.push_back(std::move(request));
}
for (int i = 0; i < 2; ++i) {
EXPECT_CALL(*request_delegates[i].get(), OnStreamReadyImpl(_, _));
}
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
requests.clear();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_TRUE(first_socket.AllReadDataConsumed());
EXPECT_FALSE(second_socket.AllReadDataConsumed());
}
// Tests that HTTP/2 throttling logic only applies to non-QUIC jobs.
TEST_F(JobControllerLimitMultipleH2Requests, QuicJobNotThrottled) {
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
tcp_data_ =
std::make_unique<SequencedSocketData>(reads, base::span<MockWrite>());
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
ssl_data.next_proto = NextProto::kProtoHTTP2;
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
SpdySessionPoolPeer pool_peer(session_->spdy_session_pool());
pool_peer.SetEnableSendingInitialData(false);
url::SchemeHostPort server(request_info.url);
// Sets server supports QUIC.
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// Sets server support HTTP/2.
session_->http_server_properties()->SetSupportsSpdy(
server, NetworkAnonymizationKey(), true);
// Use default job factory so that Resume() is not mocked out.
HttpStreamFactory::JobFactory default_job_factory;
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate_, session_.get(), &default_job_factory,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
auto* job_controller_ptr = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_, std::move(job_controller));
request_ = job_controller_ptr->Start(
&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_ptr->main_job());
EXPECT_TRUE(job_controller_ptr->alternative_job());
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
auto entries = net_log_observer_.GetEntries();
for (const auto& entry : entries) {
ASSERT_NE(NetLogEventType::HTTP_STREAM_JOB_THROTTLED, entry.type);
}
}
class HttpStreamFactoryJobControllerMisdirectedRequestRetry
: public HttpStreamFactoryJobControllerTestBase,
public ::testing::WithParamInterface<::testing::tuple<bool, bool>> {
public:
HttpStreamFactoryJobControllerMisdirectedRequestRetry()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false) {}
};
INSTANTIATE_TEST_SUITE_P(All,
HttpStreamFactoryJobControllerMisdirectedRequestRetry,
::testing::Combine(::testing::Bool(),
::testing::Bool()));
TEST_P(HttpStreamFactoryJobControllerMisdirectedRequestRetry,
DisableIPBasedPoolingForH2AndAlternativeServices) {
const bool enable_ip_based_pooling_for_h2 = ::testing::get<0>(GetParam());
const bool enable_alternative_services = ::testing::get<1>(GetParam());
if (enable_alternative_services) {
quic_data_ = std::make_unique<MockQuicData>(version_);
quic_data_->AddConnect(SYNCHRONOUS, OK);
quic_data_->AddWrite(SYNCHRONOUS,
client_maker_.MakeInitialSettingsPacket(1));
quic_data_->AddRead(ASYNC, ERR_CONNECTION_CLOSED);
}
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
SSLSocketDataProvider ssl_data(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data);
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
if (!enable_ip_based_pooling_for_h2) {
DisableIPBasedPoolingForH2();
}
if (!enable_alternative_services) {
DisableAlternativeServices();
}
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
request_ =
job_controller_->Start(&request_delegate_, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
EXPECT_TRUE(job_controller_->main_job());
if (enable_alternative_services) {
EXPECT_TRUE(job_controller_->alternative_job());
} else {
EXPECT_FALSE(job_controller_->alternative_job());
}
// `main_job` succeeds and should report status to Request.
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _));
base::RunLoop().RunUntilIdle();
}
class HttpStreamFactoryJobControllerPreconnectTest
: public HttpStreamFactoryJobControllerTestBase,
public ::testing::WithParamInterface<bool> {
protected:
HttpStreamFactoryJobControllerPreconnectTest()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false) {}
void SetUp() override {
if (!GetParam()) {
scoped_feature_list_.InitFromCommandLine(std::string(),
"LimitEarlyPreconnects");
}
}
void Initialize() {
session_deps_.http_server_properties =
std::make_unique<HttpServerProperties>(
std::make_unique<MockPrefDelegate>(), nullptr /* net_log */);
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
factory_ = session_->http_stream_factory();
request_info_.method = "GET";
request_info_.url = GURL("https://www.example.com");
auto job_controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, &request_delegate_, session_.get(), &job_factory_,
request_info_, /* is_preconnect = */ true,
/* is_websocket = */ false,
/* enable_ip_based_pooling_for_h2 = */ true,
/* enable_alternative_services = */ true,
/* delay_main_job_with_available_spdy_session = */ true,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
job_controller_ = job_controller.get();
HttpStreamFactoryPeer::AddJobController(factory_,
std::move(job_controller));
}
protected:
void Preconnect(int num_streams) {
job_controller_->Preconnect(num_streams);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
HttpRequestInfo request_info_;
};
INSTANTIATE_TEST_SUITE_P(All,
HttpStreamFactoryJobControllerPreconnectTest,
::testing::Bool());
TEST_P(HttpStreamFactoryJobControllerPreconnectTest, LimitEarlyPreconnects) {
std::list<SequencedSocketData> providers;
std::list<SSLSocketDataProvider> ssl_providers;
const int kNumPreconects = 5;
MockRead reads[] = {MockRead(ASYNC, OK)};
// If experiment is not enabled, there are 5 socket connects.
const size_t actual_num_connects = GetParam() ? 1 : kNumPreconects;
for (size_t i = 0; i < actual_num_connects; ++i) {
providers.emplace_back(reads, base::span<MockWrite>());
session_deps_.socket_factory->AddSocketDataProvider(&providers.back());
ssl_providers.emplace_back(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(
&ssl_providers.back());
}
Initialize();
Preconnect(kNumPreconects);
// If experiment is enabled, only 1 stream is requested.
EXPECT_EQ((int)actual_num_connects, HttpStreamFactoryJobPeer::GetNumStreams(
job_controller_->main_job()));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Test that GetAlternativeServiceInfoFor will include a list of advertised
// versions, which contains a version that is supported. Returns an empty list
// if advertised versions are missing in HttpServerProperties.
TEST_F(HttpStreamFactoryJobControllerTest, GetAlternativeServiceInfoFor) {
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
base::Time expiration = base::Time::Now() + base::Days(1);
// Set alternative service with no advertised version.
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
quic::ParsedQuicVersionVector());
// Simulate proxy resolution succeeding, after which
// GetAlternativeServiceInfoFor can be called.
JobControllerPeer::InitializeProxyInfo(job_controller_);
AlternativeServiceInfo alt_svc_info =
JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
// Verify that JobController get an empty list of supported QUIC versions.
EXPECT_TRUE(alt_svc_info.advertised_versions().empty());
// Set alternative service for the same server with the same list of versions
// that is supported.
quic::ParsedQuicVersionVector supported_versions =
quic_context_.params()->supported_versions;
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
supported_versions);
alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
std::sort(
supported_versions.begin(), supported_versions.end(),
[](const quic::ParsedQuicVersion& a, const quic::ParsedQuicVersion& b) {
return a.transport_version < b.transport_version;
});
quic::ParsedQuicVersionVector advertised_versions =
alt_svc_info.advertised_versions();
std::sort(
advertised_versions.begin(), advertised_versions.end(),
[](const quic::ParsedQuicVersion& a, const quic::ParsedQuicVersion& b) {
return a.transport_version < b.transport_version;
});
EXPECT_EQ(supported_versions, advertised_versions);
quic::ParsedQuicVersion unsupported_version_1 =
quic::ParsedQuicVersion::Unsupported();
quic::ParsedQuicVersion unsupported_version_2 =
quic::ParsedQuicVersion::Unsupported();
for (const quic::ParsedQuicVersion& version : quic::AllSupportedVersions()) {
if (base::Contains(supported_versions, version)) {
continue;
}
if (unsupported_version_1 == quic::ParsedQuicVersion::Unsupported()) {
unsupported_version_1 = version;
continue;
}
unsupported_version_2 = version;
break;
}
// Set alternative service for the same server with two QUIC versions:
// - one unsupported version: `unsupported_version_1`,
// - one supported version:
// quic_context_.params()->supported_versions[0].
quic::ParsedQuicVersionVector mixed_quic_versions = {
unsupported_version_1, quic_context_.params()->supported_versions[0]};
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
mixed_quic_versions);
alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
EXPECT_EQ(2u, alt_svc_info.advertised_versions().size());
// Verify that JobController returns the list of versions specified in set.
EXPECT_EQ(mixed_quic_versions, alt_svc_info.advertised_versions());
// Set alternative service for the same server with two unsupported QUIC
// versions: `unsupported_version_1`, `unsupported_version_2`.
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(), alternative_service, expiration,
{unsupported_version_1, unsupported_version_2});
alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
// Verify that JobController returns no valid alternative service.
EXPECT_EQ(NextProto::kProtoUnknown,
alt_svc_info.alternative_service().protocol);
EXPECT_EQ(0u, alt_svc_info.advertised_versions().size());
}
void HttpStreamFactoryJobControllerTestBase::TestAltSvcVersionSelection(
const std::string& alt_svc_header,
const quic::ParsedQuicVersion& expected_version,
const quic::ParsedQuicVersionVector& supported_versions) {
quic_context_.params()->supported_versions = supported_versions;
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://example.com");
NetworkIsolationKey network_isolation_key(
SchemefulSite(GURL("https://example.com")),
SchemefulSite(GURL("https://example.com")));
auto network_anonymization_key = NetworkAnonymizationKey::CreateSameSite(
SchemefulSite(GURL("https://example.com")));
request_info.network_isolation_key = network_isolation_key;
request_info.network_anonymization_key = network_anonymization_key;
Initialize(request_info);
url::SchemeHostPort origin(request_info.url);
auto headers = base::MakeRefCounted<HttpResponseHeaders>("");
headers->AddHeader("alt-svc", alt_svc_header);
session_->http_stream_factory()->ProcessAlternativeServices(
session_.get(), network_anonymization_key, headers.get(), origin);
// Simulate proxy resolution succeeding, after which
// GetAlternativeServiceInfoFor can be called.
JobControllerPeer::InitializeProxyInfo(job_controller_);
AlternativeServiceInfo alt_svc_info =
JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
quic::ParsedQuicVersionVector advertised_versions =
alt_svc_info.advertised_versions();
quic::ParsedQuicVersion selected_version =
JobControllerPeer::SelectQuicVersion(job_controller_,
advertised_versions);
EXPECT_EQ(expected_version, selected_version)
<< alt_svc_info.ToString() << " "
<< quic::ParsedQuicVersionVectorToString(advertised_versions);
}
TEST_F(HttpStreamFactoryJobControllerTest,
AltSvcVersionSelectionFindsFirstMatch) {
TestAltSvcVersionSelection(
"h3-Q050=\":443\"; ma=2592000,"
"h3-Q049=\":443\"; ma=2592000,"
"h3-Q048=\":443\"; ma=2592000,"
"h3-Q046=\":443\"; ma=2592000,",
quic::ParsedQuicVersion::Q046(), quic::AllSupportedVersions());
}
TEST_F(HttpStreamFactoryJobControllerTest,
AltSvcVersionSelectionFindsFirstMatchInverse) {
TestAltSvcVersionSelection(
"h3-Q046=\":443\"; ma=2592000,"
"h3-Q048=\":443\"; ma=2592000,"
"h3-Q049=\":443\"; ma=2592000,",
quic::ParsedQuicVersion::Q046(), quic::AllSupportedVersions());
}
TEST_F(HttpStreamFactoryJobControllerTest,
AltSvcVersionSelectionWithInverseOrderingNewFormat) {
// Server prefers Q046 but client prefers Q050.
TestAltSvcVersionSelection(
"h3-Q046=\":443\"; ma=2592000,"
"h3-Q050=\":443\"; ma=2592000",
quic::ParsedQuicVersion::Q046(),
quic::ParsedQuicVersionVector{quic::ParsedQuicVersion::Q046()});
}
// Tests that if HttpNetworkSession has a non-empty QUIC host allowlist,
// then GetAlternativeServiceFor() will not return any QUIC alternative service
// that's not on the allowlist.
TEST_F(HttpStreamFactoryJobControllerTest, QuicHostAllowlist) {
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.google.com");
Initialize(request_info);
// Set HttpNetworkSession's QUIC host allowlist to only have www.example.com
HttpNetworkSessionPeer session_peer(session_.get());
session_peer.params()->quic_host_allowlist.insert("www.example.com");
quic_context_.params()->allow_remote_alt_svc = true;
// Set alternative service for www.google.com to be www.example.com over QUIC.
url::SchemeHostPort server(request_info.url);
base::Time expiration = base::Time::Now() + base::Days(1);
quic::ParsedQuicVersionVector supported_versions =
quic_context_.params()->supported_versions;
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(),
AlternativeService(NextProto::kProtoQUIC, "www.example.com", 443),
expiration, supported_versions);
// Simulate proxy resolution succeeding, after which
// GetAlternativeServiceInfoFor can be called.
JobControllerPeer::InitializeProxyInfo(job_controller_);
AlternativeServiceInfo alt_svc_info =
JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
std::sort(
supported_versions.begin(), supported_versions.end(),
[](const quic::ParsedQuicVersion& a, const quic::ParsedQuicVersion& b) {
return a.transport_version < b.transport_version;
});
quic::ParsedQuicVersionVector advertised_versions =
alt_svc_info.advertised_versions();
std::sort(
advertised_versions.begin(), advertised_versions.end(),
[](const quic::ParsedQuicVersion& a, const quic::ParsedQuicVersion& b) {
return a.transport_version < b.transport_version;
});
EXPECT_EQ(NextProto::kProtoQUIC, alt_svc_info.alternative_service().protocol);
EXPECT_EQ(supported_versions, advertised_versions);
session_->http_server_properties()->SetQuicAlternativeService(
server, NetworkAnonymizationKey(),
AlternativeService(NextProto::kProtoQUIC, "www.example.org", 443),
expiration, supported_versions);
alt_svc_info = JobControllerPeer::GetAlternativeServiceInfoFor(
job_controller_, request_info, &request_delegate_,
HttpStreamRequest::HTTP_STREAM);
EXPECT_EQ(NextProto::kProtoUnknown,
alt_svc_info.alternative_service().protocol);
EXPECT_EQ(0u, alt_svc_info.advertised_versions().size());
}
// Tests specific to UseDnsHttpsAlpn feature.
class HttpStreamFactoryJobControllerDnsHttpsAlpnTest
: public HttpStreamFactoryJobControllerTestBase {
protected:
explicit HttpStreamFactoryJobControllerDnsHttpsAlpnTest(
std::vector<base::test::FeatureRef> enabled_features = {})
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/false,
std::move(enabled_features)) {}
void SetUp() override { SkipCreatingJobController(); }
void EnableOndemandHostResolver() {
session_deps_.host_resolver->set_synchronous_mode(false);
session_deps_.host_resolver->set_ondemand_mode(true);
}
HttpRequestInfo CreateTestHttpRequestInfo() {
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("https://www.example.org");
return request_info;
}
void RegisterMockHttpsRecord() {
HostResolverEndpointResult endpoint_result1;
endpoint_result1.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
endpoint_result1.metadata.supported_protocol_alpns = {
quic::AlpnForVersion(version_)};
HostResolverEndpointResult endpoint_result2;
endpoint_result2.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
std::vector<HostResolverEndpointResult> endpoints;
endpoints.push_back(endpoint_result1);
endpoints.push_back(endpoint_result2);
session_deps_.host_resolver->rules()->AddRule(
"www.example.org",
MockHostResolverBase::RuleResolver::RuleResult(
std::move(endpoints),
/*aliases=*/std::set<std::string>{"www.example.org"}));
}
void CreateJobController(const HttpRequestInfo& request_info) {
CreateJobControllerImpl(&job_controller_, &request_delegate_, request_info);
}
std::unique_ptr<HttpStreamRequest> CreateJobControllerAndStart(
const HttpRequestInfo& request_info) {
return CreateJobControllerAndStartImpl(&job_controller_, &request_delegate_,
request_info);
}
std::unique_ptr<HttpStreamRequest> CreateSecondJobControllerAndStart(
const HttpRequestInfo& request_info) {
return CreateJobControllerAndStartImpl(&job_controller2_,
&request_delegate2_, request_info);
}
void PrepareForMainJob() { PrepareForMainJobImpl(&tcp_data_, &ssl_data_); }
void PrepareForSecondMainJob() {
PrepareForMainJobImpl(&tcp_data2_, &ssl_data2_);
}
void PrepareForFirstQuicJob() { PrepareForQuicJobImpl(&quic_data_); }
void PrepareForSecondQuicJob() { PrepareForQuicJobImpl(&quic_data2_); }
void PrepareForFirstQuicJobFailure() {
PrepareForQuicJobFailureImpl(&quic_data_);
}
void PrepareForSecondQuicJobFailure() {
PrepareForQuicJobFailureImpl(&quic_data2_);
}
void MakeMainJobSucceed(bool expect_stream_ready) {
MakeMainJobSucceedImpl(request_delegate_, tcp_data_.get(),
expect_stream_ready);
}
void MakeSecondMainJobSucceed(bool expect_stream_ready) {
MakeMainJobSucceedImpl(request_delegate2_, tcp_data2_.get(),
expect_stream_ready);
}
void MakeQuicJobSucceed(size_t index, bool expect_stream_ready) {
base::RunLoop().RunUntilIdle();
ASSERT_GT(crypto_client_stream_factory_.streams().size(), index);
MockCryptoClientStream* stream =
crypto_client_stream_factory_.streams()[index].get();
ASSERT_TRUE(stream);
if (expect_stream_ready) {
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
stream->NotifySessionOneRttKeyAvailable();
run_loop.Run();
} else {
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _)).Times(0);
stream->NotifySessionOneRttKeyAvailable();
base::RunLoop().RunUntilIdle();
}
}
void CheckJobsStatus(bool main_job_exists,
bool alternative_job_exists,
bool dns_alpn_h3_job_exists,
const std::string& scoped_trace_message = "") {
CheckJobsStatusImpl(job_controller_.get(), main_job_exists,
alternative_job_exists, dns_alpn_h3_job_exists,
scoped_trace_message);
}
void CheckSecondJobsStatus(bool main_job_exists,
bool alternative_job_exists,
bool dns_alpn_h3_job_exists,
const std::string& scoped_trace_message = "") {
CheckJobsStatusImpl(job_controller2_.get(), main_job_exists,
alternative_job_exists, dns_alpn_h3_job_exists,
scoped_trace_message);
}
std::unique_ptr<QuicHttpStream> ConnectQuicHttpStream(
bool alt_destination,
bool require_dns_https_alpn) {
NetErrorDetails net_error_details;
QuicSessionRequest quic_request(session_->quic_session_pool());
url::SchemeHostPort scheme_host_port(
url::kHttpsScheme,
alt_destination ? "alt.example.org" : "www.example.org", 443);
std::optional<int> quic_request_result;
CHECK_EQ(ERR_IO_PENDING,
quic_request.Request(
scheme_host_port,
require_dns_https_alpn ? quic::ParsedQuicVersion::Unsupported()
: version_,
ProxyChain::Direct(), TRAFFIC_ANNOTATION_FOR_TESTS,
/*http_user_agent_settings=*/nullptr,
SessionUsage::kDestination, PRIVACY_MODE_DISABLED,
DEFAULT_PRIORITY, SocketTag(), NetworkAnonymizationKey(),
SecureDnsPolicy::kAllow, require_dns_https_alpn,
/*cert_verify_flags=*/0, GURL("https://www.example.org/"),
net_log_with_source_, &net_error_details,
MultiplexedSessionCreationInitiator::kUnknown, std::nullopt,
base::BindLambdaForTesting([&](int result) {}),
base::BindLambdaForTesting([&quic_request_result](int result) {
quic_request_result = result;
})));
base::RunLoop().RunUntilIdle();
CHECK_EQ(1u, crypto_client_stream_factory_.streams().size());
CHECK(crypto_client_stream_factory_.streams()[0]);
crypto_client_stream_factory_.streams()[0]
->NotifySessionOneRttKeyAvailable();
base::RunLoop().RunUntilIdle();
CHECK(quic_request_result);
CHECK_EQ(OK, *quic_request_result);
std::unique_ptr<QuicChromiumClientSession::Handle> session =
quic_request.ReleaseSessionHandle();
std::set<std::string> dns_aliases =
session->GetDnsAliasesForSessionKey(quic_request.session_key());
auto stream = std::make_unique<QuicHttpStream>(std::move(session),
std::move(dns_aliases));
return stream;
}
bool IsAlternativeServiceBroken(GURL& url) {
return session_->http_server_properties()->IsAlternativeServiceBroken(
AlternativeService(NextProto::kProtoQUIC, HostPortPair::FromURL(url)),
NetworkAnonymizationKey());
}
raw_ptr<HttpStreamFactory::JobController, AcrossTasksDanglingUntriaged>
job_controller2_ = nullptr;
MockHttpStreamRequestDelegate request_delegate2_;
private:
QuicTestPacketMaker CreateQuicTestPacketMakerForClient() {
return QuicTestPacketMaker(version_,
quic::QuicUtils::CreateRandomConnectionId(
quic_context_.random_generator()),
quic_context_.clock(), "www.example.org",
quic::Perspective::IS_CLIENT, false);
}
void CreateJobControllerImpl(
raw_ptr<HttpStreamFactory::JobController, AcrossTasksDanglingUntriaged>*
job_controller,
MockHttpStreamRequestDelegate* request_delegate,
const HttpRequestInfo& request_info) {
auto controller = std::make_unique<HttpStreamFactory::JobController>(
factory_, request_delegate, session_.get(), &default_job_factory_,
request_info, is_preconnect_, /*is_websocket=*/false,
enable_ip_based_pooling_for_h2_, enable_alternative_services_,
delay_main_job_with_available_spdy_session_,
/*allowed_bad_certs=*/std::vector<SSLConfig::CertAndStatus>());
*job_controller = controller.get();
HttpStreamFactoryPeer::AddJobController(factory_, std::move(controller));
}
std::unique_ptr<HttpStreamRequest> CreateJobControllerAndStartImpl(
raw_ptr<HttpStreamFactory::JobController, AcrossTasksDanglingUntriaged>*
job_controller,
MockHttpStreamRequestDelegate* request_delegate,
const HttpRequestInfo& request_info) {
CreateJobControllerImpl(job_controller, request_delegate, request_info);
return (*job_controller)
->Start(request_delegate, nullptr, net_log_with_source_,
HttpStreamRequest::HTTP_STREAM, DEFAULT_PRIORITY);
}
void PrepareForMainJobImpl(std::unique_ptr<SequencedSocketData>* tcp_data,
std::unique_ptr<SSLSocketDataProvider>* ssl_data) {
*tcp_data = std::make_unique<SequencedSocketData>();
(*tcp_data)->set_connect_data(
MockConnect(ASYNC, ERR_IO_PENDING)); /* pause */
(*ssl_data) = std::make_unique<SSLSocketDataProvider>(ASYNC, OK);
session_deps_.socket_factory->AddSSLSocketDataProvider(ssl_data->get());
}
void PrepareForQuicJobImpl(std::unique_ptr<MockQuicData>* quic_data) {
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
*quic_data = std::make_unique<MockQuicData>(version_);
(*quic_data)->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
(*quic_data)
->AddWrite(
SYNCHRONOUS,
CreateQuicTestPacketMakerForClient().MakeInitialSettingsPacket(1));
}
void PrepareForQuicJobFailureImpl(std::unique_ptr<MockQuicData>* quic_data) {
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
*quic_data = std::make_unique<MockQuicData>(version_);
(*quic_data)->AddRead(ASYNC, ERR_IO_PENDING); // Pause
(*quic_data)->AddRead(ASYNC, ERR_FAILED);
}
void MakeMainJobSucceedImpl(MockHttpStreamRequestDelegate& request_delegate,
SequencedSocketData* tcp_data,
bool expect_stream_ready) {
if (expect_stream_ready) {
base::RunLoop run_loop;
EXPECT_CALL(request_delegate, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
tcp_data->socket()->OnConnectComplete(MockConnect());
run_loop.Run();
} else {
EXPECT_CALL(request_delegate, OnStreamReadyImpl(_, _)).Times(0);
tcp_data->socket()->OnConnectComplete(MockConnect());
base::RunLoop().RunUntilIdle();
}
}
static void CheckJobsStatusImpl(
HttpStreamFactory::JobController* job_controller,
bool main_job_exists,
bool alternative_job_exists,
bool dns_alpn_h3_job_exists,
const std::string& scoped_trace_message) {
SCOPED_TRACE(scoped_trace_message);
EXPECT_EQ(main_job_exists, !!job_controller->main_job());
EXPECT_EQ(alternative_job_exists, !!job_controller->alternative_job());
EXPECT_EQ(dns_alpn_h3_job_exists, !!job_controller->dns_alpn_h3_job());
}
// Use real Jobs so that Job::Resume() is not mocked out. When main job is
// resumed it will use mock socket data.
HttpStreamFactory::JobFactory default_job_factory_;
// Used for man job connection.
std::unique_ptr<SSLSocketDataProvider> ssl_data_;
std::unique_ptr<SSLSocketDataProvider> ssl_data2_;
};
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
NoHttpsRecordSyncHostResolve) {
PrepareForMainJob();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// The main job should be synchronously resumed, as host is resolved
// synchronously.
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
// `dns_alpn_h3_job` must fail when there is no valid supported alpn. And
// must be deleted.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS ALPN job must be deleted.");
base::HistogramTester histogram_tester;
MakeMainJobSucceed(/*expect_stream_ready=*/true);
// Net.AlternateProtocolUsage records
// ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON, when only main job exists.
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON,
1);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
NoHttpsRecordAsyncHostResolveResumeMainWithoutDelay) {
EnableOndemandHostResolver();
PrepareForMainJob();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// The main job should be resumed quickly after resolving the host.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Resolve the host resolve request from `dns_alpn_h3_job`.
session_deps_.host_resolver->ResolveAllPending();
base::RunLoop().RunUntilIdle();
// `dns_alpn_h3_job` must fail when there is no valid supported alpn. And
// must be deleted.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS ALPN job must be deleted.");
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// The host resolve request from the main job must be resolved using the
// cached result.
EXPECT_TRUE(tcp_data_->socket());
base::HistogramTester histogram_tester;
MakeMainJobSucceed(/*expect_stream_ready=*/true);
// Net.AlternateProtocolUsage records
// ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON, when only main job exists.
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON,
1);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
NoHttpsRecordAsyncHostResolveResumeMainWithoutDelayQuicWorkedNetwork) {
EnableOndemandHostResolver();
PrepareForMainJob();
Initialize(HttpRequestInfo());
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// Main job must be waiting.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Resolve the host resolve request from `dns_alpn_h3_job`.
session_deps_.host_resolver->ResolveAllPending();
base::RunLoop().RunUntilIdle();
// `dns_alpn_h3_job` must fail when there is no valid supported alpn. And
// must be deleted.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS ALPN job must be deleted.");
// The main job should be resumed quickly after resolving the host.
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// The host resolve request from the main job must be resolved using the
// cached result.
EXPECT_TRUE(tcp_data_->socket());
base::HistogramTester histogram_tester;
MakeMainJobSucceed(/*expect_stream_ready=*/true);
// Net.AlternateProtocolUsage records
// ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON, when only main job exists.
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON,
1);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
MainJobNoDelayOnQuicNotWorkedNetworkSyncHostResolve) {
PrepareForMainJob();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// `dns_alpn_h3_job` should not be waiting for dns host
// resolution as that was resolved synchronously.
EXPECT_FALSE(job_controller_->dns_alpn_h3_job()
->expect_on_quic_host_resolution_for_tests());
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// The success of `dns_alpn_h3_job` deletes `main_job`.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Main job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
MainJobNoDelayOnQuicNotWorkedNetworkAsyncHostResolve) {
EnableOndemandHostResolver();
PrepareForMainJob();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// `main_job` is blocked until host resolves.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
// Resolve the host resolve request from `dns_alpn_h3_job`.
session_deps_.host_resolver->ResolveAllPending();
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
// `main_job` should have been resumed quickly because
// `is_quic_known_to_work_on_current_network` is false for this test.
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// `dns_alpn_h3_job` must not fail when there is a valid supported alpn.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Both main job and DNS ALPN job must be alive");
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// The success of `dns_alpn_h3_job` deletes `main_job`.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Main job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
MainJobDelayOnQuicWorkedNetwork) {
PrepareForMainJob();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
base::RunLoop().RunUntilIdle();
// `dns_alpn_h3_job` must not fail when there is a valid supported alpn.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Both main job and DNS ALPN job must be alive");
// The main job should be waiting until kDefaultDelayMilliSecsForWaitingJob
// amount of time has passed.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(kDefaultDelayMilliSecsForWaitingJob - 1));
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(1));
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// The success of `dns_alpn_h3_job` deletes `main_job`.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Main job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Test that for a proxied session no DNS APLN H3 job is created (since we don't
// want to perform DNS resolution corresponding to requests that will be
// proxied).
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
NoDnsAlpnH3JobForProxiedSession) {
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
quic_data_ = std::make_unique<MockQuicData>(quic::ParsedQuicVersion::RFCv1());
quic_data_->AddRead(SYNCHRONOUS, ERR_IO_PENDING);
Initialize(HttpRequestInfo());
auto proxy_chain =
ProxyChain::ForIpProtection({ProxyServer::FromSchemeHostAndPort(
ProxyServer::SCHEME_QUIC, "proxy", 99)});
auto* test_proxy_delegate =
static_cast<TestProxyDelegate*>(session_deps_.proxy_delegate.get());
test_proxy_delegate->set_proxy_chain(proxy_chain);
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS ALPN H3 job must not have been created.");
base::RunLoop().RunUntilIdle();
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
MainJobSucceedsDnsAlpnH3JobSucceeds) {
PrepareForMainJob();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
base::RunLoop().RunUntilIdle();
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// `main_job` is not blocked, because the hostname is resolved synchronously
// and `is_quic_known_to_work_on_current_network` is false for this test.
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
base::HistogramTester histogram_tester;
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE,
1);
// The success of `main_job` doesn't delete `dns_alpn_h3_job`.
EXPECT_TRUE(job_controller_->dns_alpn_h3_job());
// Make `dns_alpn_h3_job` complete.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
ActiveSessionAvailableForMainJob) {
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
// Set `is_quic_known_to_work_on_current_network` flag so that
// the delaying logic of main job would work when the main job is blocked.
// Note: In this test, we don't need this because the main job is not blocked.
// But we set here because we want to check that the main job is not blocked.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
// Put a SpdySession in the pool.
SpdySessionKey key(HostPortPair::FromURL(request_info.url),
PRIVACY_MODE_DISABLED, ProxyChain::Direct(),
SessionUsage::kDestination, SocketTag(),
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
/*disable_cert_verification_network_fetches=*/false);
std::ignore = CreateFakeSpdySession(session_->spdy_session_pool(), key);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must be created even when an active session is
// available for `main_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// Main job must not be waiting because an active session is available.
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
base::HistogramTester histogram_tester;
// Run the message loop to make `main_job` succeed and status will be
// reported to Request.
{
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
}
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE,
1);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"DNS ALPN job must be alive");
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS ALPN job must be deleted");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest, MainJobHasActiveSocket) {
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
PrepareForMainJob();
PrepareForSecondMainJob();
PrepareForFirstQuicJobFailure();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
// Set `is_quic_known_to_work_on_current_network` flag so that
// the delaying logic of main job would work when the main job is blocked.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(kDefaultDelayMilliSecsForWaitingJob - 1));
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(1));
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
auto request2 = CreateSecondJobControllerAndStart(request_info);
CheckSecondJobsStatus(
/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created for the second request.");
// When an active socket is available for the main job, the main job should
// not be blocked.
EXPECT_FALSE(job_controller2_->main_job()->is_waiting());
quic_data_->Resume();
base::RunLoop().RunUntilIdle();
MakeMainJobSucceed(/*expect_stream_ready=*/true);
MakeSecondMainJobSucceed(/*expect_stream_ready=*/true);
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
MainJobHasActiveSocketAltSvcRegistered) {
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
PrepareForMainJob();
PrepareForSecondMainJob();
PrepareForFirstQuicJobFailure();
PrepareForSecondQuicJobFailure();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
// Set `is_quic_known_to_work_on_current_network` flag so that
// the delaying logic of main job would work when the main job is blocked.
QuicSessionPool* quic_session_pool = session_->quic_session_pool();
quic_session_pool->set_has_quic_ever_worked_on_current_network(true);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs are created");
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(kDefaultDelayMilliSecsForWaitingJob - 1));
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(1));
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
auto request2 = CreateSecondJobControllerAndStart(request_info);
CheckSecondJobsStatus(
/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs must be created for the second request.");
// The main job should be waiting until kDefaultDelayMilliSecsForWaitingJob
// amount of time has passed, when an alternative service was registered,
// even when an active socket is available for the main job.
// This is intended to switch to QUIC from TCP for the first connection
// when the server supports Alt-Svc but doesn't support HTTP DNS records with
// alpn.
// Note: When QuicParams.delay_main_job_with_available_spdy_session is false,
// main job is not blocked.
EXPECT_TRUE(job_controller2_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(kDefaultDelayMilliSecsForWaitingJob - 1));
EXPECT_TRUE(job_controller2_->main_job()->is_waiting());
FastForwardBy(base::Milliseconds(1));
EXPECT_FALSE(job_controller2_->main_job()->is_waiting());
quic_data_->Resume();
quic_data2_->Resume();
base::RunLoop().RunUntilIdle();
MakeMainJobSucceed(/*expect_stream_ready=*/true);
MakeSecondMainJobSucceed(/*expect_stream_ready=*/true);
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
ActiveSessionAvailableForAltSvcJob) {
PrepareForMainJob();
RegisterMockHttpsRecord();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
PrepareForFirstQuicJob();
Initialize(HttpRequestInfo());
std::unique_ptr<QuicHttpStream> stream =
ConnectQuicHttpStream(/*alt_destination=*/true,
/*require_dns_https_alpn=*/false);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must not be created when an active session is
// available for `alternative_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"Main job and alternative job must be created.");
base::HistogramTester histogram_tester;
// Run the message loop to make `alternative_job` succeed and status will be
// reported to Request.
{
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
}
histogram_tester.ExpectUniqueSample("Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_NO_RACE, 1);
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"Main job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
ActiveSessionAvailableForDnsAlpnH3Job) {
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
std::unique_ptr<QuicHttpStream> stream =
ConnectQuicHttpStream(/*alt_destination=*/false,
/*require_dns_https_alpn=*/true);
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and alternative job must not be available.");
base::HistogramTester histogram_tester;
// Run the message loop to make `dns_alpn_h3_job` succeed and status will be
// reported to Request.
{
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
}
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_WITHOUT_RACE, 1);
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"DNS alpn H3 job must exist.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
ActiveSessionAvailableForMainJobAndDnsAlpnH3Job) {
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
PrepareForFirstQuicJob();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
// Put a SpdySession in the pool.
SpdySessionKey key(HostPortPair::FromURL(request_info.url),
PRIVACY_MODE_DISABLED, ProxyChain::Direct(),
SessionUsage::kDestination, SocketTag(),
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
/*disable_cert_verification_network_fetches=*/false);
std::ignore = CreateFakeSpdySession(session_->spdy_session_pool(), key);
std::unique_ptr<QuicHttpStream> stream =
ConnectQuicHttpStream(/*alt_destination=*/false,
/*require_dns_https_alpn=*/true);
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job must not be available.");
base::HistogramTester histogram_tester;
// Run the message loop to make `dns_alpn_h3_job` succeed and status will be
// reported to Request.
{
base::RunLoop run_loop;
EXPECT_CALL(request_delegate_, OnStreamReadyImpl(_, _))
.Times(1)
.WillOnce([&run_loop]() { run_loop.Quit(); });
run_loop.Run();
}
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_WITHOUT_RACE, 1);
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"DNS alpn H3 job must exist.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DoNotStartDnsAlpnH3JobWhenSameHostDefaultPortAltJobCreated) {
PrepareForMainJob();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"www.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must be deleted when a same origin alt service
// was registered.
CheckJobsStatus(
true, true, false,
"All types of jobs are created, but DNS alpn job must be deleted");
base::RunLoop().RunUntilIdle();
base::HistogramTester histogram_tester;
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE,
1);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"Alternate job must not be deleted");
// Make `alternative_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
AllJobsCreatedMainJobSucceedAltJobSucceedDnsJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJob();
PrepareForSecondQuicJob();
// Use cold start and complete `alternative_job` and `dns_alpn_h3_job`
// manually.
crypto_client_stream_factory_.set_handshake_mode(
MockCryptoClientStream::COLD_START);
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must be created when a different origin alt service
// was registered.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs are created");
base::HistogramTester histogram_tester;
base::RunLoop().RunUntilIdle();
MakeMainJobSucceed(/*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE,
1);
// The success of `main_job` doesn't delete `alternative_job` and
// `dns_alpn_h3_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
// Make `alternative_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Alternate job must be deleted.");
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(1, /*expect_stream_ready=*/false);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS alpn job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
AllJobsCreatedAltJobSucceedDnsJobSucceedMainJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJob();
PrepareForSecondQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must be created when a different origin alt service
// was registered.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs are created");
base::HistogramTester histogram_tester;
// Make `alternative_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample("Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_WON_RACE, 1);
// The success of `alternative_job` doesn't delete `main_job` and
// `dns_alpn_h3_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(1, /*expect_stream_ready=*/false);
// The success of `dns_alpn_h3_job` doesn't delete `main_job` and
// `alternative_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"DNS alpn job must be deleted.");
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/false);
// `main_job` should be cleared.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"Alternate job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
AllJobsCreatedDnsJobSucceedAltJobSucceedMainJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJob();
PrepareForSecondQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
// `dns_alpn_h3_job` must be created when a different origin alt service
// was registered.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs are created");
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(1, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// The success of `dns_alpn_h3_job` doesn't delete `main_job` and
// `alternative_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
// Make `alternative_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
// The success of `alternative_job` doesn't delete `main_job` and
// `dns_alpn_h3_job`.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Alternate job must be deleted.");
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/false);
// `main_job` should be cleared.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Main job must be deleted.");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsJobFailOnDefaultNetworkDnsJobFailMainJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJobFailure();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
JobControllerPeer::SetDnsAlpnH3JobFailedOnDefaultNetwork(job_controller_);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
base::RunLoop().RunUntilIdle();
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` fail.
quic_data_->Resume();
base::RunLoop().RunUntilIdle();
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false, "DNS alpn job be deleted.");
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/true);
// Net.AlternateProtocolUsage records
// ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON, when only main job exists.
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_UNSPECIFIED_REASON,
1);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"DNS alpn job must be deleted.");
request_.reset();
EXPECT_TRUE(IsAlternativeServiceBroken(request_info.url));
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
histogram_tester.ExpectUniqueSample("Net.AlternateServiceForDnsAlpnH3Failed",
-ERR_QUIC_PROTOCOL_ERROR, 1);
// Verify the brokenness is not cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
EXPECT_TRUE(IsAlternativeServiceBroken(request_info.url));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsJobFailOnDefaultNetworkMainJobSucceedDnsJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
base::HistogramTester histogram_tester;
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
JobControllerPeer::SetDnsAlpnH3JobFailedOnDefaultNetwork(job_controller_);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
base::RunLoop().RunUntilIdle();
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage", ALTERNATE_PROTOCOL_USAGE_MAIN_JOB_WON_RACE,
1);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"DNS alpn job must not be deleted.");
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
request_.reset();
histogram_tester.ExpectTotalCount("Net.AlternateServiceForDnsAlpnH3Failed",
0);
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
EXPECT_TRUE(IsAlternativeServiceBroken(request_info.url));
// Verify the brokenness is cleared when the default network changes.
session_->http_server_properties()->OnDefaultNetworkChanged();
EXPECT_FALSE(IsAlternativeServiceBroken(request_info.url));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsJobSucceedMainJobCanceled) {
PrepareForMainJob();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// Main job is canceled.
CheckJobsStatus(/*main_job_exists=*/false, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Main job must be deleted");
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsJobFailOnDefaultNetworkDnsJobSucceedMainJobSucceed) {
PrepareForMainJob();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
JobControllerPeer::SetDnsAlpnH3JobFailedOnDefaultNetwork(job_controller_);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true, "Jobs must not be deleted.");
base::HistogramTester histogram_tester;
// Make `dns_alpn_h3_job` succeed.
MakeQuicJobSucceed(0, /*expect_stream_ready=*/true);
histogram_tester.ExpectUniqueSample(
"Net.AlternateProtocolUsage",
ALTERNATE_PROTOCOL_USAGE_DNS_ALPN_H3_JOB_WON_RACE, 1);
// Main job is not canceled, because `dns_alpn_h3_job` has failed on the
// default network.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job must not be deleted.");
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/false);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest, PreconnectDnsAlpnH3) {
SetPreconnect();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(HttpRequestInfo());
CreateJobController(request_info);
job_controller_->Preconnect(/*num_streams=*/5);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT_DNS_ALPN_H3,
job_controller_->main_job()->job_type());
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
PreconnectAltSvcAvailableActiveSessionAvailable) {
SetPreconnect();
PrepareForFirstQuicJob();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
RegisterMockHttpsRecord();
Initialize(request_info);
// Register Alt-Svc info.
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, server.host(),
443);
SetAlternativeService(request_info, alternative_service);
// Create an active session of require_dns_https_alpn = true.
std::unique_ptr<QuicHttpStream> stream =
ConnectQuicHttpStream(/*alt_destination=*/false,
/*require_dns_https_alpn=*/true);
CreateJobController(request_info);
// Preconnect must succeed using the existing session.
job_controller_->Preconnect(/*num_streams=*/1);
ASSERT_TRUE(job_controller_->main_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT_DNS_ALPN_H3,
job_controller_->main_job()->job_type());
MakeQuicJobSucceed(0, /*expect_stream_ready=*/false);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest, PreconnectNoDnsAlpnH3) {
EnableOndemandHostResolver();
PrepareForMainJob();
SetPreconnect();
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
Initialize(HttpRequestInfo());
CreateJobController(request_info);
job_controller_->Preconnect(/*num_streams=*/1);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT_DNS_ALPN_H3,
job_controller_->main_job()->job_type());
// Resolve the host resolve request from `dns_alpn_h3_job`.
session_deps_.host_resolver->ResolveAllPending();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_->main_job()->job_type());
base::RunLoop().RunUntilIdle();
// Make `main_job` succeed.
MakeMainJobSucceed(/*expect_stream_ready=*/false);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Test that, when an Alt-Svc-based preconnect fails with
// `ERR_DNS_NO_MATCHING_SUPPORTED_ALPN`, the job controller handles it
// correctly. This is a regression test for https://crbug.com/1420202.
//
// In a general HTTPS-RR implementation, this may happen simply because there
// was no A/AAAA route. However, we do not implement HTTPS-RR in full yet (see
// https://crbug.com/1417033), so instead this is only possible in a corner case
// with ECH.
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
PreconnectAlternateNoDnsAlpn) {
const char kAlternateHost[] = "alt.example.com";
EnableOndemandHostResolver();
PrepareForMainJob();
SetPreconnect();
// Register a mock HTTPS record where the HTTPS-RR route is only good for h2,
// which is incompatible with Alt-Svc. The A/AAAA route would be compatible,
// but the server supports ECH, so we enable SVCB-reliant mode and reject it.
// As a result, the alternate job will fail.
HostResolverEndpointResult endpoint_result1;
endpoint_result1.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
endpoint_result1.metadata.ech_config_list = {1, 2, 3, 4};
endpoint_result1.metadata.supported_protocol_alpns = {"h2"};
HostResolverEndpointResult endpoint_result2;
endpoint_result2.ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
session_deps_.host_resolver->rules()->AddRule(
kAlternateHost,
MockHostResolverBase::RuleResolver::RuleResult(
{endpoint_result1, endpoint_result2}, {kAlternateHost}));
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
Initialize(request_info);
CreateJobController(request_info);
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC, kAlternateHost,
443);
SetAlternativeService(request_info, alternative_service);
job_controller_->Preconnect(/*num_streams=*/1);
// Only one job is started.
EXPECT_TRUE(job_controller_->main_job());
EXPECT_FALSE(job_controller_->alternative_job());
EXPECT_EQ(HttpStreamFactory::PRECONNECT,
job_controller_->main_job()->job_type());
// Resolve the DNS request.
session_deps_.host_resolver->ResolveAllPending();
base::RunLoop().RunUntilIdle();
// The jobs should have failed. We currently do not try the non-Alt-Svc route
// in preconnects if Alt-Svc failed.
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsResolutionTimeOverridesFromAlpnH3Job) {
const base::TimeDelta kDnsDelay = base::Milliseconds(10);
EnableOndemandHostResolver();
PrepareForMainJob();
session_deps_.host_resolver->rules()->AddRule(
"www.example.org", IPAddress::IPv4Localhost().ToString());
Initialize(HttpRequestInfo());
request_ = CreateJobControllerAndStart(CreateTestHttpRequestInfo());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Main job and DNS ALPN job must be created.");
// Simulate the delay of DNS resolution.
FastForwardBy(kDnsDelay);
// Resolve the host resolve request from `dns_alpn_h3_job`.
session_deps_.host_resolver->ResolveAllPending();
// `main_job` must be resumed.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// DnsResolutionTimeOverrides must be set.
EXPECT_EQ(kDnsDelay, request_->dns_resolution_end_time_override() -
request_->dns_resolution_start_time_override());
MakeMainJobSucceed(/*expect_stream_ready=*/true);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsResolutionTimeOverridesFromAlternativeJob) {
const base::TimeDelta kDnsDelay = base::Milliseconds(10);
EnableOndemandHostResolver();
PrepareForMainJob();
PrepareForFirstQuicJobFailure();
session_deps_.host_resolver->rules()->AddRule(
"www.example.org", IPAddress::IPv4Localhost().ToString());
Initialize(HttpRequestInfo());
// Register the same destination alternative service.
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"www.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/false,
"Main job and alternative job are created.");
// Simulate the delay of DNS resolution.
FastForwardBy(kDnsDelay);
// Resolve the host resolve request from `alternative_job`.
session_deps_.host_resolver->ResolveAllPending();
// `main_job` must be resumed.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// DnsResolutionTimeOverrides must be set by the `alternative_job`.
EXPECT_EQ(kDnsDelay, request_->dns_resolution_end_time_override() -
request_->dns_resolution_start_time_override());
// Make `dns_alpn_h3_job` fail.
quic_data_->Resume();
MakeMainJobSucceed(/*expect_stream_ready=*/true);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
TEST_F(HttpStreamFactoryJobControllerDnsHttpsAlpnTest,
DnsResolutionTimeOverridesNotFromDifferentAlternativeJob) {
const base::TimeDelta kDnsDelay1 = base::Milliseconds(10);
const base::TimeDelta kDnsDelay2 = base::Milliseconds(20);
HttpRequestInfo request_info = CreateTestHttpRequestInfo();
EnableOndemandHostResolver();
PrepareForMainJob();
PrepareForFirstQuicJobFailure();
session_deps_.host_resolver->rules()->AddRule(
"www.example.org", IPAddress::IPv4Localhost().ToString());
session_deps_.host_resolver->rules()->AddRule(
"alt.example.org", IPAddress::IPv4Localhost().ToString());
Initialize(HttpRequestInfo());
// Register a different destination alternative service.
url::SchemeHostPort server(request_info.url);
AlternativeService alternative_service(NextProto::kProtoQUIC,
"alt.example.org", 443);
SetAlternativeService(request_info, alternative_service);
request_ = CreateJobControllerAndStart(request_info);
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs are created.");
// `main_job` is blocked until host resolves.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
EXPECT_EQ(2u, session_deps_.host_resolver->last_id());
EXPECT_EQ("alt.example.org", session_deps_.host_resolver->request_host(1));
EXPECT_EQ("www.example.org", session_deps_.host_resolver->request_host(2));
// Simulate the delay of DNS resolution.
FastForwardBy(kDnsDelay1);
// Resolves the DNS request for "alt.example.org".
session_deps_.host_resolver->ResolveNow(1);
// `main_job` must be resumed.
EXPECT_TRUE(job_controller_->main_job()->is_waiting());
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// DnsResolutionTimeOverrides must not be set for the different destination's
// alternative job's DNS resolution time.
EXPECT_TRUE(request_->dns_resolution_end_time_override().is_null());
EXPECT_TRUE(request_->dns_resolution_start_time_override().is_null());
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/true,
/*dns_alpn_h3_job_exists=*/true,
"All types of jobs must alive.");
// Make `alternative_job` fail.
quic_data_->Resume();
base::RunLoop().RunUntilIdle();
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/true,
"Alternative job be deleted.");
// Simulate the delay of DNS resolution.
FastForwardBy(kDnsDelay2);
// Resolves the DNS request for "www.example.org".
session_deps_.host_resolver->ResolveAllPending();
base::RunLoop().RunUntilIdle();
// DnsResolutionTimeOverrides must be set.
EXPECT_EQ(kDnsDelay1 + kDnsDelay2,
request_->dns_resolution_end_time_override() -
request_->dns_resolution_start_time_override());
EXPECT_FALSE(job_controller_->main_job()->is_waiting());
// `dns_alpn_h3_job` must not fail when there is a valid supported alpn.
CheckJobsStatus(/*main_job_exists=*/true, /*alternative_job_exists=*/false,
/*dns_alpn_h3_job_exists=*/false,
"Only main job must be alive");
MakeMainJobSucceed(/*expect_stream_ready=*/true);
request_.reset();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
// Tests specific to the HappyEyeballsV3 feature.
// TODO(crbug.com/346835898): Find ways to run more tests with the
// HappyEyeballsV3 feature enabled.
class HttpStreamFactoryJobControllerPoolTest
: public HttpStreamFactoryJobControllerTestBase {
public:
HttpStreamFactoryJobControllerPoolTest()
: HttpStreamFactoryJobControllerTestBase(
/*happy_eyeballs_v3_enabled=*/true) {}
~HttpStreamFactoryJobControllerPoolTest() override = default;
protected:
HttpStreamPool* pool() { return session_->http_stream_pool(); }
FakeServiceEndpointResolver* resolver() {
return static_cast<FakeServiceEndpointResolver*>(
session_deps_.alternate_host_resolver.get());
}
};
TEST_F(HttpStreamFactoryJobControllerPoolTest, Preconnect) {
base::WeakPtr<FakeServiceEndpointRequest> endpoint_request =
resolver()->AddFakeRequest();
endpoint_request
->add_endpoint(ServiceEndpointBuilder().add_v4("127.0.0.1").endpoint())
.CompleteStartSynchronously(OK);
tcp_data_ = std::make_unique<SequencedSocketData>();
tcp_data_->set_connect_data(MockConnect(ASYNC, OK));
SetPreconnect();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.example.com");
Initialize(request_info);
job_controller_->Preconnect(/*num_streams=*/1);
// No jobs should be created.
ASSERT_FALSE(job_controller_->main_job());
ASSERT_FALSE(job_controller_->alternative_job());
RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
ASSERT_EQ(pool()->TotalIdleStreamCount(), 1u);
}
TEST_F(HttpStreamFactoryJobControllerPoolTest, PreconnectSync) {
SetPreconnect();
HttpRequestInfo request_info;
request_info.method = "GET";
request_info.url = GURL("http://www.example.com");
Initialize(request_info);
// Add an idle stream to the pool.
const HttpStreamKey stream_key(
url::SchemeHostPort("http", "www.example.com", 80), PRIVACY_MODE_DISABLED,
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow,
/*disable_cert_network_fetches=*/false);
HttpStreamPool::Group& group = pool()->GetOrCreateGroupForTesting(stream_key);
group.AddIdleStreamSocket(std::make_unique<FakeStreamSocket>());
// Preconnect should complete immediately as we already have an idle stream.
job_controller_->Preconnect(/*num_streams=*/1);
// No jobs should be created.
ASSERT_FALSE(job_controller_->main_job());
ASSERT_FALSE(job_controller_->alternative_job());
ASSERT_EQ(pool()->TotalIdleStreamCount(), 1u);
// Need RunUntilIdle() because the completion notification is delayed.
RunUntilIdle();
EXPECT_TRUE(HttpStreamFactoryPeer::IsJobControllerDeleted(factory_));
}
} // namespace net::test