1876 lines
73 KiB
C++
1876 lines
73 KiB
C++
// Copyright 2013 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/spdy/spdy_session_pool.h"
|
|
|
|
#include <cstddef>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/memory/ref_counted.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/trace_event/memory_allocator_dump.h"
|
|
#include "base/trace_event/process_memory_dump.h"
|
|
#include "build/build_config.h"
|
|
#include "net/base/proxy_string_util.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/base/tracing.h"
|
|
#include "net/dns/host_cache.h"
|
|
#include "net/dns/public/host_resolver_results.h"
|
|
#include "net/dns/public/secure_dns_policy.h"
|
|
#include "net/http/http_network_session.h"
|
|
#include "net/log/net_log_with_source.h"
|
|
#include "net/log/test_net_log.h"
|
|
#include "net/socket/client_socket_handle.h"
|
|
#include "net/socket/socket_tag.h"
|
|
#include "net/socket/socket_test_util.h"
|
|
#include "net/socket/transport_client_socket_pool.h"
|
|
#include "net/spdy/spdy_session.h"
|
|
#include "net/spdy/spdy_stream_test_util.h"
|
|
#include "net/spdy/spdy_test_util_common.h"
|
|
#include "net/test/cert_test_util.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_certificate_data.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
|
|
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
using base::trace_event::MemoryAllocatorDump;
|
|
using net::test::IsError;
|
|
using net::test::IsOk;
|
|
using testing::Contains;
|
|
using testing::Eq;
|
|
using testing::Contains;
|
|
using testing::ByRef;
|
|
|
|
namespace net {
|
|
|
|
class SpdySessionPoolTest : public TestWithTaskEnvironment {
|
|
protected:
|
|
// Used by RunIPPoolingTest().
|
|
enum SpdyPoolCloseSessionsType {
|
|
SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
|
|
SPDY_POOL_CLOSE_CURRENT_SESSIONS,
|
|
SPDY_POOL_CLOSE_IDLE_SESSIONS,
|
|
};
|
|
|
|
SpdySessionPoolTest() = default;
|
|
|
|
void CreateNetworkSession() {
|
|
http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
|
|
spdy_session_pool_ = http_session_->spdy_session_pool();
|
|
}
|
|
|
|
void AddSSLSocketData() {
|
|
auto ssl = std::make_unique<SSLSocketDataProvider>(SYNCHRONOUS, OK);
|
|
ssl->ssl_info.cert =
|
|
ImportCertFromFile(GetTestCertsDirectory(), "spdy_pooling.pem");
|
|
ASSERT_TRUE(ssl->ssl_info.cert);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(ssl.get());
|
|
ssl_data_vector_.push_back(std::move(ssl));
|
|
}
|
|
|
|
void RunIPPoolingTest(SpdyPoolCloseSessionsType close_sessions_type);
|
|
void RunIPPoolingDisabledTest(SSLSocketDataProvider* ssl);
|
|
|
|
size_t num_active_streams(base::WeakPtr<SpdySession> session) {
|
|
return session->active_streams_.size();
|
|
}
|
|
|
|
size_t max_concurrent_streams(base::WeakPtr<SpdySession> session) {
|
|
return session->max_concurrent_streams_;
|
|
}
|
|
|
|
SpdySessionDependencies session_deps_;
|
|
std::unique_ptr<HttpNetworkSession> http_session_;
|
|
raw_ptr<SpdySessionPool> spdy_session_pool_ = nullptr;
|
|
std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_data_vector_;
|
|
};
|
|
|
|
class SpdySessionRequestDelegate
|
|
: public SpdySessionPool::SpdySessionRequest::Delegate {
|
|
public:
|
|
SpdySessionRequestDelegate() = default;
|
|
|
|
SpdySessionRequestDelegate(const SpdySessionRequestDelegate&) = delete;
|
|
SpdySessionRequestDelegate& operator=(const SpdySessionRequestDelegate&) =
|
|
delete;
|
|
|
|
~SpdySessionRequestDelegate() override = default;
|
|
|
|
void OnSpdySessionAvailable(
|
|
base::WeakPtr<SpdySession> spdy_session) override {
|
|
EXPECT_FALSE(callback_invoked_);
|
|
callback_invoked_ = true;
|
|
spdy_session_ = spdy_session;
|
|
}
|
|
|
|
bool callback_invoked() const { return callback_invoked_; }
|
|
|
|
SpdySession* spdy_session() { return spdy_session_.get(); }
|
|
|
|
private:
|
|
bool callback_invoked_ = false;
|
|
base::WeakPtr<SpdySession> spdy_session_;
|
|
};
|
|
|
|
// Attempts to set up an alias for |key| using an already existing session in
|
|
// |pool|. To do this, simulates a host resolution that returns
|
|
// |ip_address_list|.
|
|
bool TryCreateAliasedSpdySession(SpdySessionPool* pool,
|
|
const SpdySessionKey& key,
|
|
const std::string& ip_address_list,
|
|
bool enable_ip_based_pooling = true,
|
|
bool is_websocket = false) {
|
|
// The requested session must not already exist.
|
|
EXPECT_FALSE(pool->FindAvailableSession(key, enable_ip_based_pooling,
|
|
is_websocket, NetLogWithSource()));
|
|
|
|
// Create a request for the session. There should be no matching session
|
|
// (aliased or otherwise) yet. A pending request is necessary for the session
|
|
// to create an alias on host resolution completion.
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> request;
|
|
bool is_blocking_request_for_session = false;
|
|
SpdySessionRequestDelegate request_delegate;
|
|
EXPECT_FALSE(pool->RequestSession(
|
|
key, enable_ip_based_pooling, is_websocket, NetLogWithSource(),
|
|
/* on_blocking_request_destroyed_callback = */ base::RepeatingClosure(),
|
|
&request_delegate, &request, &is_blocking_request_for_session));
|
|
EXPECT_TRUE(request);
|
|
EXPECT_TRUE(is_blocking_request_for_session);
|
|
|
|
std::vector<IPEndPoint> ip_endpoints;
|
|
EXPECT_THAT(ParseAddressList(ip_address_list, &ip_endpoints), IsOk());
|
|
HostResolverEndpointResult endpoint;
|
|
for (auto& ip_endpoint : ip_endpoints) {
|
|
endpoint.ip_endpoints.emplace_back(ip_endpoint.address(), 443);
|
|
}
|
|
|
|
// Simulate a host resolution completing.
|
|
OnHostResolutionCallbackResult result = pool->OnHostResolutionComplete(
|
|
key, is_websocket, {endpoint}, /*aliases=*/{});
|
|
|
|
// Spin the message loop and see if it creates an H2 session.
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_EQ(request_delegate.callback_invoked(),
|
|
result == OnHostResolutionCallbackResult::kMayBeDeletedAsync);
|
|
EXPECT_EQ(request_delegate.callback_invoked(),
|
|
request_delegate.spdy_session() != nullptr);
|
|
request.reset();
|
|
|
|
// Calling RequestSession again should return request_delegate.spdy_session()
|
|
// (i.e. the newly created session, if a session was created, or nullptr, if
|
|
// one was not.)
|
|
EXPECT_EQ(request_delegate.spdy_session(),
|
|
pool->RequestSession(key, enable_ip_based_pooling, is_websocket,
|
|
NetLogWithSource(),
|
|
/* on_blocking_request_destroyed_callback = */
|
|
base::RepeatingClosure(), &request_delegate,
|
|
&request, &is_blocking_request_for_session)
|
|
.get());
|
|
|
|
return request_delegate.spdy_session() != nullptr;
|
|
}
|
|
|
|
// A delegate that opens a new session when it is closed.
|
|
class SessionOpeningDelegate : public SpdyStream::Delegate {
|
|
public:
|
|
SessionOpeningDelegate(SpdySessionPool* spdy_session_pool,
|
|
const SpdySessionKey& key)
|
|
: spdy_session_pool_(spdy_session_pool),
|
|
key_(key) {}
|
|
|
|
~SessionOpeningDelegate() override = default;
|
|
|
|
void OnHeadersSent() override {}
|
|
|
|
void OnEarlyHintsReceived(const spdy::Http2HeaderBlock& headers) override {}
|
|
|
|
void OnHeadersReceived(
|
|
const spdy::Http2HeaderBlock& response_headers,
|
|
const spdy::Http2HeaderBlock* pushed_request_headers) override {}
|
|
|
|
void OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) override {}
|
|
|
|
void OnDataSent() override {}
|
|
|
|
void OnTrailers(const spdy::Http2HeaderBlock& trailers) override {}
|
|
|
|
void OnClose(int status) override {
|
|
std::ignore = CreateFakeSpdySession(spdy_session_pool_, key_);
|
|
}
|
|
|
|
bool CanGreaseFrameType() const override { return false; }
|
|
|
|
NetLogSource source_dependency() const override { return NetLogSource(); }
|
|
|
|
private:
|
|
const raw_ptr<SpdySessionPool> spdy_session_pool_;
|
|
const SpdySessionKey key_;
|
|
};
|
|
|
|
// Set up a SpdyStream to create a new session when it is closed.
|
|
// CloseCurrentSessions should not close the newly-created session.
|
|
TEST_F(SpdySessionPoolTest, CloseCurrentSessions) {
|
|
const char kTestHost[] = "www.foo.com";
|
|
const int kTestPort = 80;
|
|
|
|
HostPortPair test_host_port_pair(kTestHost, kTestPort);
|
|
SpdySessionKey test_key = SpdySessionKey(
|
|
test_host_port_pair, ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
data.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
|
|
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Setup the first session to the first host.
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), test_key, NetLogWithSource());
|
|
|
|
// Flush the SpdySession::OnReadComplete() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Verify that we have sessions for everything.
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
|
|
|
|
// Set the stream to create a new session when it is closed.
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, GURL("http://www.foo.com"), MEDIUM,
|
|
NetLogWithSource());
|
|
SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
|
|
spdy_stream->SetDelegate(&delegate);
|
|
|
|
// Close the current session.
|
|
spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
|
|
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, CloseCurrentIdleSessions) {
|
|
const std::string close_session_description = "Closing idle sessions.";
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
|
|
StaticSocketDataProvider data1(reads, base::span<MockWrite>());
|
|
data1.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data1);
|
|
|
|
AddSSLSocketData();
|
|
AddSSLSocketData();
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Set up session 1
|
|
const GURL url1("https://www.example.org");
|
|
HostPortPair test_host_port_pair1(HostPortPair::FromURL(url1));
|
|
SpdySessionKey key1(test_host_port_pair1, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session1 =
|
|
CreateSpdySession(http_session_.get(), key1, NetLogWithSource());
|
|
base::WeakPtr<SpdyStream> spdy_stream1 = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session1, url1, MEDIUM, NetLogWithSource());
|
|
ASSERT_TRUE(spdy_stream1);
|
|
|
|
// Set up session 2
|
|
StaticSocketDataProvider data2(reads, base::span<MockWrite>());
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data2);
|
|
const GURL url2("https://mail.example.org");
|
|
HostPortPair test_host_port_pair2(HostPortPair::FromURL(url2));
|
|
SpdySessionKey key2(test_host_port_pair2, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session2 =
|
|
CreateSpdySession(http_session_.get(), key2, NetLogWithSource());
|
|
base::WeakPtr<SpdyStream> spdy_stream2 = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session2, url2, MEDIUM, NetLogWithSource());
|
|
ASSERT_TRUE(spdy_stream2);
|
|
|
|
// Set up session 3
|
|
StaticSocketDataProvider data3(reads, base::span<MockWrite>());
|
|
data3.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data3);
|
|
const GURL url3("https://mail.example.com");
|
|
HostPortPair test_host_port_pair3(HostPortPair::FromURL(url3));
|
|
SpdySessionKey key3(test_host_port_pair3, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session3 =
|
|
CreateSpdySession(http_session_.get(), key3, NetLogWithSource());
|
|
base::WeakPtr<SpdyStream> spdy_stream3 = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session3, url3, MEDIUM, NetLogWithSource());
|
|
ASSERT_TRUE(spdy_stream3);
|
|
|
|
// All sessions are active and not closed
|
|
EXPECT_TRUE(session1->is_active());
|
|
EXPECT_TRUE(session1->IsAvailable());
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
EXPECT_TRUE(session3->is_active());
|
|
EXPECT_TRUE(session3->IsAvailable());
|
|
|
|
// Should not do anything, all are active
|
|
spdy_session_pool_->CloseCurrentIdleSessions(close_session_description);
|
|
EXPECT_TRUE(session1->is_active());
|
|
EXPECT_TRUE(session1->IsAvailable());
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
EXPECT_TRUE(session3->is_active());
|
|
EXPECT_TRUE(session3->IsAvailable());
|
|
|
|
// Make sessions 1 and 3 inactive, but keep them open.
|
|
// Session 2 still open and active
|
|
session1->CloseCreatedStream(spdy_stream1, OK);
|
|
EXPECT_FALSE(spdy_stream1);
|
|
session3->CloseCreatedStream(spdy_stream3, OK);
|
|
EXPECT_FALSE(spdy_stream3);
|
|
EXPECT_FALSE(session1->is_active());
|
|
EXPECT_TRUE(session1->IsAvailable());
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
EXPECT_FALSE(session3->is_active());
|
|
EXPECT_TRUE(session3->IsAvailable());
|
|
|
|
// Should close session 1 and 3, 2 should be left open
|
|
spdy_session_pool_->CloseCurrentIdleSessions(close_session_description);
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_FALSE(session1);
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
EXPECT_FALSE(session3);
|
|
|
|
// Should not do anything
|
|
spdy_session_pool_->CloseCurrentIdleSessions(close_session_description);
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
|
|
// Make 2 not active
|
|
session2->CloseCreatedStream(spdy_stream2, OK);
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_FALSE(spdy_stream2);
|
|
EXPECT_FALSE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
|
|
// This should close session 2
|
|
spdy_session_pool_->CloseCurrentIdleSessions(close_session_description);
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_FALSE(session2);
|
|
}
|
|
|
|
// Set up a SpdyStream to create a new session when it is closed.
|
|
// CloseAllSessions should close the newly-created session.
|
|
TEST_F(SpdySessionPoolTest, CloseAllSessions) {
|
|
const char kTestHost[] = "www.foo.com";
|
|
const int kTestPort = 80;
|
|
|
|
HostPortPair test_host_port_pair(kTestHost, kTestPort);
|
|
SpdySessionKey test_key = SpdySessionKey(
|
|
test_host_port_pair, ProxyServer::Direct(), PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
data.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
|
|
SSLSocketDataProvider ssl(SYNCHRONOUS, OK);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl);
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Setup the first session to the first host.
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), test_key, NetLogWithSource());
|
|
|
|
// Flush the SpdySession::OnReadComplete() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Verify that we have sessions for everything.
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key));
|
|
|
|
// Set the stream to create a new session when it is closed.
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, GURL("http://www.foo.com"), MEDIUM,
|
|
NetLogWithSource());
|
|
SessionOpeningDelegate delegate(spdy_session_pool_, test_key);
|
|
spdy_stream->SetDelegate(&delegate);
|
|
|
|
// Close the current session.
|
|
spdy_session_pool_->CloseAllSessions();
|
|
|
|
EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_key));
|
|
}
|
|
|
|
// Code testing SpdySessionPool::OnIPAddressChange requires a SpdySessionPool
|
|
// with some active sessions. This fixture takes care of setting most things up
|
|
// but doesn't create the pool yet, allowing tests to possibly further
|
|
// configure sessions_deps_.
|
|
class SpdySessionPoolOnIPAddressChangeTest : public SpdySessionPoolTest {
|
|
protected:
|
|
SpdySessionPoolOnIPAddressChangeTest()
|
|
: test_host_port_pair_(kTestHost, kTestPort),
|
|
reads_({
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
}),
|
|
test_key_(SpdySessionKey(test_host_port_pair_,
|
|
ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(),
|
|
NetworkAnonymizationKey(),
|
|
SecureDnsPolicy::kAllow)),
|
|
connect_data_(SYNCHRONOUS, OK),
|
|
data_(reads_, base::span<MockWrite>()),
|
|
ssl_(SYNCHRONOUS, OK) {
|
|
data_.set_connect_data(connect_data_);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data_);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_);
|
|
}
|
|
|
|
static constexpr char kTestHost[] = "www.foo.com";
|
|
static constexpr int kTestPort = 80;
|
|
static constexpr int kReadSize = 1;
|
|
|
|
const HostPortPair test_host_port_pair_;
|
|
const std::array<MockRead, kReadSize> reads_;
|
|
const SpdySessionKey test_key_;
|
|
const MockConnect connect_data_;
|
|
StaticSocketDataProvider data_;
|
|
SSLSocketDataProvider ssl_;
|
|
};
|
|
|
|
TEST_F(SpdySessionPoolOnIPAddressChangeTest, DoNotIgnoreIPAddressChanges) {
|
|
// Default behavior should be ignore_ip_address_changes = false;
|
|
CreateNetworkSession();
|
|
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), test_key_, NetLogWithSource());
|
|
|
|
// Flush the SpdySession::OnReadComplete() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
// Verify that we have a session.
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key_));
|
|
|
|
// Without setting session_deps_.ignore_ip_address_changes = true the pool
|
|
// should close (or make unavailable) all sessions after an IP address change.
|
|
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_key_));
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolOnIPAddressChangeTest, IgnoreIPAddressChanges) {
|
|
session_deps_.ignore_ip_address_changes = true;
|
|
CreateNetworkSession();
|
|
|
|
// Setup the first session to the first host.
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), test_key_, NetLogWithSource());
|
|
// Flush the SpdySession::OnReadComplete() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
// Verify that we have a session.
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key_));
|
|
|
|
// Since we set ignore_ip_address_changes = true, the session should still be
|
|
// there after an IP address change.
|
|
NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_key_));
|
|
}
|
|
|
|
// This test has three variants, one for each style of closing the connection.
|
|
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_SESSIONS_MANUALLY,
|
|
// the sessions are closed manually, calling SpdySessionPool::Remove() directly.
|
|
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_CURRENT_SESSIONS,
|
|
// sessions are closed with SpdySessionPool::CloseCurrentSessions().
|
|
// If |clean_via_close_current_sessions| is SPDY_POOL_CLOSE_IDLE_SESSIONS,
|
|
// sessions are closed with SpdySessionPool::CloseIdleSessions().
|
|
void SpdySessionPoolTest::RunIPPoolingTest(
|
|
SpdyPoolCloseSessionsType close_sessions_type) {
|
|
constexpr int kTestPort = 443;
|
|
struct TestHosts {
|
|
std::string url;
|
|
std::string name;
|
|
std::string iplist;
|
|
SpdySessionKey key;
|
|
} test_hosts[] = {
|
|
{"http://www.example.org", "www.example.org",
|
|
"192.0.2.33,192.168.0.1,192.168.0.5"},
|
|
{"http://mail.example.org", "mail.example.org",
|
|
"192.168.0.2,192.168.0.3,192.168.0.5,192.0.2.33"},
|
|
{"http://mail.example.com", "mail.example.com",
|
|
"192.168.0.4,192.168.0.3"},
|
|
};
|
|
|
|
for (auto& test_host : test_hosts) {
|
|
session_deps_.host_resolver->rules()->AddIPLiteralRule(
|
|
test_host.name, test_host.iplist, std::string());
|
|
|
|
test_host.key = SpdySessionKey(
|
|
HostPortPair(test_host.name, kTestPort), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
}
|
|
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
|
|
StaticSocketDataProvider data1(reads, base::span<MockWrite>());
|
|
data1.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data1);
|
|
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Setup the first session to the first host.
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession(
|
|
http_session_.get(), test_hosts[0].key, NetLogWithSource());
|
|
|
|
// Flush the SpdySession::OnReadComplete() task.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// The third host has no overlap with the first, so it can't pool IPs.
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
|
|
|
|
// The second host overlaps with the first, and should IP pool.
|
|
EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
|
|
test_hosts[1].iplist));
|
|
|
|
// However, if IP pooling is disabled, FindAvailableSession() should not find
|
|
// |session| for the second host.
|
|
base::WeakPtr<SpdySession> session1 =
|
|
spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource());
|
|
EXPECT_FALSE(session1);
|
|
|
|
// Verify that the second host, through a proxy, won't share the IP, even if
|
|
// the IP list matches.
|
|
SpdySessionKey proxy_key(
|
|
test_hosts[1].key.host_port_pair(),
|
|
PacResultElementToProxyServer("HTTP http://proxy.foo.com/"),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(spdy_session_pool_, proxy_key,
|
|
test_hosts[1].iplist));
|
|
|
|
// Verify that the second host, with a different SecureDnsPolicy,
|
|
// won't share the IP, even if the IP list matches.
|
|
SpdySessionKey disable_secure_dns_key(
|
|
test_hosts[1].key.host_port_pair(), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kDisable);
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, disable_secure_dns_key, test_hosts[1].iplist));
|
|
|
|
// Overlap between 2 and 3 is not transitive to 1.
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
|
|
|
|
// Create a new session to host 2.
|
|
StaticSocketDataProvider data2(reads, base::span<MockWrite>());
|
|
data2.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data2);
|
|
|
|
AddSSLSocketData();
|
|
|
|
base::WeakPtr<SpdySession> session2 = CreateSpdySession(
|
|
http_session_.get(), test_hosts[2].key, NetLogWithSource());
|
|
|
|
// Verify that we have sessions for everything.
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
|
|
|
|
// Grab the session to host 1 and verify that it is the same session
|
|
// we got with host 0, and that is a different from host 2's session.
|
|
session1 = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ false, NetLogWithSource());
|
|
EXPECT_EQ(session.get(), session1.get());
|
|
EXPECT_NE(session2.get(), session1.get());
|
|
|
|
// Remove the aliases and observe that we still have a session for host1.
|
|
SpdySessionPoolPeer pool_peer(spdy_session_pool_);
|
|
pool_peer.RemoveAliases(test_hosts[0].key);
|
|
pool_peer.RemoveAliases(test_hosts[1].key);
|
|
EXPECT_TRUE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
|
|
|
|
// Cleanup the sessions.
|
|
switch (close_sessions_type) {
|
|
case SPDY_POOL_CLOSE_SESSIONS_MANUALLY:
|
|
session->CloseSessionOnError(ERR_ABORTED, std::string());
|
|
session2->CloseSessionOnError(ERR_ABORTED, std::string());
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(session2);
|
|
break;
|
|
case SPDY_POOL_CLOSE_CURRENT_SESSIONS:
|
|
spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED);
|
|
break;
|
|
case SPDY_POOL_CLOSE_IDLE_SESSIONS:
|
|
GURL url(test_hosts[0].url);
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
|
|
GURL url1(test_hosts[1].url);
|
|
base::WeakPtr<SpdyStream> spdy_stream1 =
|
|
CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session1, url1,
|
|
MEDIUM, NetLogWithSource());
|
|
GURL url2(test_hosts[2].url);
|
|
base::WeakPtr<SpdyStream> spdy_stream2 =
|
|
CreateStreamSynchronously(SPDY_BIDIRECTIONAL_STREAM, session2, url2,
|
|
MEDIUM, NetLogWithSource());
|
|
|
|
// Close streams to make spdy_session and spdy_session1 inactive.
|
|
session->CloseCreatedStream(spdy_stream, OK);
|
|
EXPECT_FALSE(spdy_stream);
|
|
session1->CloseCreatedStream(spdy_stream1, OK);
|
|
EXPECT_FALSE(spdy_stream1);
|
|
|
|
// Check spdy_session and spdy_session1 are not closed.
|
|
EXPECT_FALSE(session->is_active());
|
|
EXPECT_TRUE(session->IsAvailable());
|
|
EXPECT_FALSE(session1->is_active());
|
|
EXPECT_TRUE(session1->IsAvailable());
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
|
|
// Test that calling CloseIdleSessions, does not cause a crash.
|
|
// http://crbug.com/181400
|
|
spdy_session_pool_->CloseCurrentIdleSessions("Closing idle sessions.");
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Verify spdy_session and spdy_session1 are closed.
|
|
EXPECT_FALSE(session);
|
|
EXPECT_FALSE(session1);
|
|
EXPECT_TRUE(session2->is_active());
|
|
EXPECT_TRUE(session2->IsAvailable());
|
|
|
|
spdy_stream2->Cancel(ERR_ABORTED);
|
|
EXPECT_FALSE(spdy_stream);
|
|
EXPECT_FALSE(spdy_stream1);
|
|
EXPECT_FALSE(spdy_stream2);
|
|
|
|
session2->CloseSessionOnError(ERR_ABORTED, std::string());
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(session2);
|
|
break;
|
|
}
|
|
|
|
// Verify that the map is all cleaned up.
|
|
EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[0].key));
|
|
EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[1].key));
|
|
EXPECT_FALSE(HasSpdySession(spdy_session_pool_, test_hosts[2].key));
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[0].key, test_hosts[0].iplist));
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist));
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[2].key, test_hosts[2].iplist));
|
|
}
|
|
|
|
void SpdySessionPoolTest::RunIPPoolingDisabledTest(SSLSocketDataProvider* ssl) {
|
|
constexpr int kTestPort = 443;
|
|
struct TestHosts {
|
|
std::string name;
|
|
std::string iplist;
|
|
SpdySessionKey key;
|
|
} test_hosts[] = {
|
|
{"www.webkit.org", "192.0.2.33,192.168.0.1,192.168.0.5"},
|
|
{"js.webkit.com", "192.168.0.4,192.168.0.1,192.0.2.33"},
|
|
};
|
|
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
for (auto& test_host : test_hosts) {
|
|
session_deps_.host_resolver->rules()->AddIPLiteralRule(
|
|
test_host.name, test_host.iplist, std::string());
|
|
|
|
// Setup a SpdySessionKey
|
|
test_host.key = SpdySessionKey(
|
|
HostPortPair(test_host.name, kTestPort), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
}
|
|
|
|
MockRead reads[] = {
|
|
MockRead(ASYNC, ERR_IO_PENDING),
|
|
};
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
session_deps_.socket_factory->AddSSLSocketDataProvider(ssl);
|
|
|
|
CreateNetworkSession();
|
|
|
|
base::WeakPtr<SpdySession> spdy_session = CreateSpdySession(
|
|
http_session_.get(), test_hosts[0].key, NetLogWithSource());
|
|
EXPECT_TRUE(
|
|
HasSpdySession(http_session_->spdy_session_pool(), test_hosts[0].key));
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist,
|
|
/* enable_ip_based_pooling = */ false));
|
|
|
|
http_session_->spdy_session_pool()->CloseAllSessions();
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, IPPooling) {
|
|
RunIPPoolingTest(SPDY_POOL_CLOSE_SESSIONS_MANUALLY);
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, IPPoolingCloseCurrentSessions) {
|
|
RunIPPoolingTest(SPDY_POOL_CLOSE_CURRENT_SESSIONS);
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, IPPoolingCloseIdleSessions) {
|
|
RunIPPoolingTest(SPDY_POOL_CLOSE_IDLE_SESSIONS);
|
|
}
|
|
|
|
// Regression test for https://crbug.com/643025.
|
|
TEST_F(SpdySessionPoolTest, IPPoolingNetLog) {
|
|
// Define two hosts with identical IP address.
|
|
constexpr int kTestPort = 443;
|
|
struct TestHosts {
|
|
std::string name;
|
|
std::string iplist;
|
|
SpdySessionKey key;
|
|
} test_hosts[] = {
|
|
{"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
|
|
};
|
|
|
|
// Populate the HostResolver cache.
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
for (auto& test_host : test_hosts) {
|
|
session_deps_.host_resolver->rules()->AddIPLiteralRule(
|
|
test_host.name, test_host.iplist, std::string());
|
|
|
|
test_host.key = SpdySessionKey(
|
|
HostPortPair(test_host.name, kTestPort), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
}
|
|
|
|
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
data.set_connect_data(connect_data);
|
|
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Open SpdySession to the first host.
|
|
base::WeakPtr<SpdySession> session0 = CreateSpdySession(
|
|
http_session_.get(), test_hosts[0].key, NetLogWithSource());
|
|
|
|
// The second host should pool to the existing connection.
|
|
RecordingNetLogObserver net_log_observer;
|
|
base::HistogramTester histogram_tester;
|
|
EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
|
|
test_hosts[1].iplist));
|
|
histogram_tester.ExpectTotalCount("Net.SpdySessionGet", 1);
|
|
|
|
base::WeakPtr<SpdySession> session1 =
|
|
spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ false,
|
|
NetLogWithSource::Make(NetLogSourceType::NONE));
|
|
EXPECT_EQ(session0.get(), session1.get());
|
|
|
|
ASSERT_EQ(1u, net_log_observer.GetSize());
|
|
histogram_tester.ExpectTotalCount("Net.SpdySessionGet", 2);
|
|
|
|
// FindAvailableSession() should have logged a netlog event indicating IP
|
|
// pooling.
|
|
auto entry_list = net_log_observer.GetEntries();
|
|
EXPECT_EQ(
|
|
NetLogEventType::HTTP2_SESSION_POOL_FOUND_EXISTING_SESSION_FROM_IP_POOL,
|
|
entry_list[0].type);
|
|
|
|
// Both FindAvailableSession() calls (including one from
|
|
// TryCreateAliasedSpdySession) should log histogram entries indicating IP
|
|
// pooling.
|
|
histogram_tester.ExpectUniqueSample("Net.SpdySessionGet", 2, 2);
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, IPPoolingDisabled) {
|
|
// Define two hosts with identical IP address.
|
|
constexpr int kTestPort = 443;
|
|
struct TestHosts {
|
|
std::string name;
|
|
std::string iplist;
|
|
SpdySessionKey key;
|
|
} test_hosts[] = {
|
|
{"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
|
|
};
|
|
|
|
// Populate the HostResolver cache.
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
for (auto& test_host : test_hosts) {
|
|
session_deps_.host_resolver->rules()->AddIPLiteralRule(
|
|
test_host.name, test_host.iplist, std::string());
|
|
|
|
test_host.key = SpdySessionKey(
|
|
HostPortPair(test_host.name, kTestPort), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
}
|
|
|
|
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
|
|
StaticSocketDataProvider data(reads, base::span<MockWrite>());
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
data.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
AddSSLSocketData();
|
|
|
|
MockRead reads1[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
|
|
StaticSocketDataProvider data1(reads1, base::span<MockWrite>());
|
|
MockConnect connect_data1(SYNCHRONOUS, OK);
|
|
data1.set_connect_data(connect_data1);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data1);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Open SpdySession to the first host.
|
|
base::WeakPtr<SpdySession> session0 = CreateSpdySession(
|
|
http_session_.get(), test_hosts[0].key, NetLogWithSource());
|
|
|
|
// |test_hosts[1]| should pool to the existing connection.
|
|
EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
|
|
test_hosts[1].iplist));
|
|
base::WeakPtr<SpdySession> session1 =
|
|
spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ false, NetLogWithSource());
|
|
EXPECT_EQ(session0.get(), session1.get());
|
|
|
|
// A request to the second host should not pool to the existing connection if
|
|
// IP based pooling is disabled.
|
|
session1 = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource());
|
|
EXPECT_FALSE(session1);
|
|
|
|
// It should be possible to open a new SpdySession, even if a previous call to
|
|
// FindAvailableSession() linked the second key to the first connection in the
|
|
// IP pooled bucket of SpdySessionPool::available_session_map_.
|
|
session1 = CreateSpdySessionWithIpBasedPoolingDisabled(
|
|
http_session_.get(), test_hosts[1].key, NetLogWithSource());
|
|
EXPECT_TRUE(session1);
|
|
EXPECT_NE(session0.get(), session1.get());
|
|
}
|
|
|
|
// Verifies that an SSL connection with client authentication disables SPDY IP
|
|
// pooling.
|
|
TEST_F(SpdySessionPoolTest, IPPoolingClientCert) {
|
|
SSLSocketDataProvider ssl(ASYNC, OK);
|
|
ssl.ssl_info.cert = X509Certificate::CreateFromBytes(webkit_der);
|
|
ASSERT_TRUE(ssl.ssl_info.cert);
|
|
ssl.ssl_info.client_cert_sent = true;
|
|
ssl.next_proto = kProtoHTTP2;
|
|
RunIPPoolingDisabledTest(&ssl);
|
|
}
|
|
|
|
namespace {
|
|
enum class ChangeType {
|
|
kIpAddress = 0,
|
|
kSSLConfig,
|
|
kCertDatabase,
|
|
kCertVerifier
|
|
};
|
|
|
|
class SpdySessionGoAwayOnChangeTest
|
|
: public SpdySessionPoolTest,
|
|
public ::testing::WithParamInterface<ChangeType> {
|
|
public:
|
|
void SetUp() override {
|
|
SpdySessionPoolTest::SetUp();
|
|
|
|
if (GetParam() == ChangeType::kIpAddress) {
|
|
session_deps_.go_away_on_ip_change = true;
|
|
}
|
|
}
|
|
|
|
void SimulateChange() {
|
|
switch (GetParam()) {
|
|
case ChangeType::kIpAddress:
|
|
spdy_session_pool_->OnIPAddressChanged();
|
|
break;
|
|
case ChangeType::kSSLConfig:
|
|
session_deps_.ssl_config_service->NotifySSLContextConfigChange();
|
|
break;
|
|
case ChangeType::kCertDatabase:
|
|
// TODO(mattm): For more realistic testing this should call
|
|
// `CertDatabase::GetInstance()->NotifyObserversCertDBChanged()`,
|
|
// however that delivers notifications asynchronously, and running
|
|
// the message loop to allow the notification to be delivered allows
|
|
// other parts of the tested code to advance, breaking the test
|
|
// expectations.
|
|
spdy_session_pool_->OnSSLConfigChanged(
|
|
SSLClientContext::SSLConfigChangeType::kCertDatabaseChanged);
|
|
break;
|
|
case ChangeType::kCertVerifier:
|
|
session_deps_.cert_verifier->SimulateOnCertVerifierChanged();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Error ExpectedNetError() const {
|
|
switch (GetParam()) {
|
|
case ChangeType::kIpAddress:
|
|
return ERR_NETWORK_CHANGED;
|
|
case ChangeType::kSSLConfig:
|
|
return ERR_NETWORK_CHANGED;
|
|
case ChangeType::kCertDatabase:
|
|
return ERR_CERT_DATABASE_CHANGED;
|
|
case ChangeType::kCertVerifier:
|
|
return ERR_CERT_VERIFIER_CHANGED;
|
|
}
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
// Construct a Pool with SpdySessions in various availability states. Simulate
|
|
// an IP address change. Ensure sessions gracefully shut down. Regression test
|
|
// for crbug.com/379469.
|
|
TEST_P(SpdySessionGoAwayOnChangeTest, GoAwayOnChange) {
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
|
|
// This isn't testing anything having to do with SPDY frames; we
|
|
// can ignore issues of how dependencies are set. We default to
|
|
// setting them (when doing the appropriate protocol) since that's
|
|
// where we're eventually headed for all HTTP/2 connections.
|
|
SpdyTestUtil spdy_util;
|
|
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet("http://www.example.org", 1, MEDIUM));
|
|
MockWrite writes[] = {CreateMockWrite(req, 1)};
|
|
|
|
StaticSocketDataProvider dataA(reads, writes);
|
|
dataA.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataA);
|
|
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
// Set up session A: Going away, but with an active stream.
|
|
const std::string kTestHostA("www.example.org");
|
|
HostPortPair test_host_port_pairA(kTestHostA, 80);
|
|
SpdySessionKey keyA(test_host_port_pairA, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionA =
|
|
CreateSpdySession(http_session_.get(), keyA, NetLogWithSource());
|
|
|
|
GURL urlA("http://www.example.org");
|
|
base::WeakPtr<SpdyStream> spdy_streamA = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, sessionA, urlA, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegateA(spdy_streamA);
|
|
spdy_streamA->SetDelegate(&delegateA);
|
|
|
|
spdy::Http2HeaderBlock headers(
|
|
spdy_util.ConstructGetHeaderBlock(urlA.spec()));
|
|
spdy_streamA->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
|
|
base::RunLoop().RunUntilIdle(); // Allow headers to write.
|
|
EXPECT_TRUE(delegateA.send_headers_completed());
|
|
|
|
sessionA->MakeUnavailable();
|
|
EXPECT_TRUE(sessionA->IsGoingAway());
|
|
EXPECT_FALSE(delegateA.StreamIsClosed());
|
|
|
|
// Set up session B: Available, with a created stream.
|
|
StaticSocketDataProvider dataB(reads, writes);
|
|
dataB.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataB);
|
|
|
|
AddSSLSocketData();
|
|
|
|
const std::string kTestHostB("mail.example.org");
|
|
HostPortPair test_host_port_pairB(kTestHostB, 80);
|
|
SpdySessionKey keyB(test_host_port_pairB, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionB =
|
|
CreateSpdySession(http_session_.get(), keyB, NetLogWithSource());
|
|
EXPECT_TRUE(sessionB->IsAvailable());
|
|
|
|
GURL urlB("http://mail.example.org");
|
|
base::WeakPtr<SpdyStream> spdy_streamB = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, sessionB, urlB, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegateB(spdy_streamB);
|
|
spdy_streamB->SetDelegate(&delegateB);
|
|
|
|
// Set up session C: Draining.
|
|
StaticSocketDataProvider dataC(reads, writes);
|
|
dataC.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataC);
|
|
|
|
AddSSLSocketData();
|
|
|
|
const std::string kTestHostC("mail.example.com");
|
|
HostPortPair test_host_port_pairC(kTestHostC, 80);
|
|
SpdySessionKey keyC(test_host_port_pairC, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionC =
|
|
CreateSpdySession(http_session_.get(), keyC, NetLogWithSource());
|
|
|
|
sessionC->CloseSessionOnError(ERR_HTTP2_PROTOCOL_ERROR, "Error!");
|
|
EXPECT_TRUE(sessionC->IsDraining());
|
|
|
|
SimulateChange();
|
|
|
|
EXPECT_TRUE(sessionA->IsGoingAway());
|
|
EXPECT_TRUE(sessionB->IsDraining());
|
|
EXPECT_TRUE(sessionC->IsDraining());
|
|
|
|
EXPECT_EQ(1u,
|
|
num_active_streams(sessionA)); // Active stream is still active.
|
|
EXPECT_FALSE(delegateA.StreamIsClosed());
|
|
|
|
EXPECT_TRUE(delegateB.StreamIsClosed()); // Created stream was closed.
|
|
EXPECT_THAT(delegateB.WaitForClose(), IsError(ExpectedNetError()));
|
|
|
|
sessionA->CloseSessionOnError(ERR_ABORTED, "Closing");
|
|
sessionB->CloseSessionOnError(ERR_ABORTED, "Closing");
|
|
|
|
EXPECT_TRUE(delegateA.StreamIsClosed());
|
|
EXPECT_THAT(delegateA.WaitForClose(), IsError(ERR_ABORTED));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(All,
|
|
SpdySessionGoAwayOnChangeTest,
|
|
testing::Values(ChangeType::kIpAddress,
|
|
ChangeType::kSSLConfig,
|
|
ChangeType::kCertDatabase,
|
|
ChangeType::kCertVerifier));
|
|
|
|
// Construct a Pool with SpdySessions in various availability states. Simulate
|
|
// an IP address change. Ensure sessions gracefully shut down. Regression test
|
|
// for crbug.com/379469.
|
|
TEST_F(SpdySessionPoolTest, CloseOnIPAddressChanged) {
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
|
|
// This isn't testing anything having to do with SPDY frames; we
|
|
// can ignore issues of how dependencies are set. We default to
|
|
// setting them (when doing the appropriate protocol) since that's
|
|
// where we're eventually headed for all HTTP/2 connections.
|
|
SpdyTestUtil spdy_util;
|
|
|
|
MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet("http://www.example.org", 1, MEDIUM));
|
|
MockWrite writes[] = {CreateMockWrite(req, 1)};
|
|
|
|
StaticSocketDataProvider dataA(reads, writes);
|
|
dataA.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataA);
|
|
|
|
AddSSLSocketData();
|
|
|
|
session_deps_.go_away_on_ip_change = false;
|
|
CreateNetworkSession();
|
|
|
|
// Set up session A: Going away, but with an active stream.
|
|
const std::string kTestHostA("www.example.org");
|
|
HostPortPair test_host_port_pairA(kTestHostA, 80);
|
|
SpdySessionKey keyA(test_host_port_pairA, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionA =
|
|
CreateSpdySession(http_session_.get(), keyA, NetLogWithSource());
|
|
|
|
GURL urlA("http://www.example.org");
|
|
base::WeakPtr<SpdyStream> spdy_streamA = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, sessionA, urlA, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegateA(spdy_streamA);
|
|
spdy_streamA->SetDelegate(&delegateA);
|
|
|
|
spdy::Http2HeaderBlock headers(
|
|
spdy_util.ConstructGetHeaderBlock(urlA.spec()));
|
|
spdy_streamA->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
|
|
base::RunLoop().RunUntilIdle(); // Allow headers to write.
|
|
EXPECT_TRUE(delegateA.send_headers_completed());
|
|
|
|
sessionA->MakeUnavailable();
|
|
EXPECT_TRUE(sessionA->IsGoingAway());
|
|
EXPECT_FALSE(delegateA.StreamIsClosed());
|
|
|
|
// Set up session B: Available, with a created stream.
|
|
StaticSocketDataProvider dataB(reads, writes);
|
|
dataB.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataB);
|
|
|
|
AddSSLSocketData();
|
|
|
|
const std::string kTestHostB("mail.example.org");
|
|
HostPortPair test_host_port_pairB(kTestHostB, 80);
|
|
SpdySessionKey keyB(test_host_port_pairB, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionB =
|
|
CreateSpdySession(http_session_.get(), keyB, NetLogWithSource());
|
|
EXPECT_TRUE(sessionB->IsAvailable());
|
|
|
|
GURL urlB("http://mail.example.org");
|
|
base::WeakPtr<SpdyStream> spdy_streamB = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, sessionB, urlB, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegateB(spdy_streamB);
|
|
spdy_streamB->SetDelegate(&delegateB);
|
|
|
|
// Set up session C: Draining.
|
|
StaticSocketDataProvider dataC(reads, writes);
|
|
dataC.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&dataC);
|
|
|
|
AddSSLSocketData();
|
|
|
|
const std::string kTestHostC("mail.example.com");
|
|
HostPortPair test_host_port_pairC(kTestHostC, 80);
|
|
SpdySessionKey keyC(test_host_port_pairC, ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> sessionC =
|
|
CreateSpdySession(http_session_.get(), keyC, NetLogWithSource());
|
|
|
|
sessionC->CloseSessionOnError(ERR_HTTP2_PROTOCOL_ERROR, "Error!");
|
|
EXPECT_TRUE(sessionC->IsDraining());
|
|
|
|
spdy_session_pool_->OnIPAddressChanged();
|
|
|
|
EXPECT_TRUE(sessionA->IsDraining());
|
|
EXPECT_TRUE(sessionB->IsDraining());
|
|
EXPECT_TRUE(sessionC->IsDraining());
|
|
|
|
// Both streams were closed with an error.
|
|
EXPECT_TRUE(delegateA.StreamIsClosed());
|
|
EXPECT_THAT(delegateA.WaitForClose(), IsError(ERR_NETWORK_CHANGED));
|
|
EXPECT_TRUE(delegateB.StreamIsClosed());
|
|
EXPECT_THAT(delegateB.WaitForClose(), IsError(ERR_NETWORK_CHANGED));
|
|
}
|
|
|
|
// Regression test for https://crbug.com/789791.
|
|
TEST_F(SpdySessionPoolTest, HandleIPAddressChangeThenShutdown) {
|
|
MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)};
|
|
SpdyTestUtil spdy_util;
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet(kDefaultUrl, 1, MEDIUM));
|
|
MockWrite writes[] = {CreateMockWrite(req, 1)};
|
|
StaticSocketDataProvider data(reads, writes);
|
|
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
data.set_connect_data(connect_data);
|
|
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
const GURL url(kDefaultUrl);
|
|
SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), key, NetLogWithSource());
|
|
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegate(spdy_stream);
|
|
spdy_stream->SetDelegate(&delegate);
|
|
|
|
spdy::Http2HeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
|
|
spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_TRUE(delegate.send_headers_completed());
|
|
|
|
spdy_session_pool_->OnIPAddressChanged();
|
|
|
|
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
|
|
EXPECT_EQ(1u, num_active_streams(session));
|
|
EXPECT_TRUE(session->IsGoingAway());
|
|
EXPECT_FALSE(session->IsDraining());
|
|
#else
|
|
EXPECT_EQ(0u, num_active_streams(session));
|
|
EXPECT_FALSE(session->IsGoingAway());
|
|
EXPECT_TRUE(session->IsDraining());
|
|
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_IOS)
|
|
|
|
http_session_.reset();
|
|
|
|
data.AllReadDataConsumed();
|
|
data.AllWriteDataConsumed();
|
|
}
|
|
|
|
// Regression test for https://crbug.com/789791.
|
|
TEST_F(SpdySessionPoolTest, HandleGracefulGoawayThenShutdown) {
|
|
SpdyTestUtil spdy_util;
|
|
spdy::SpdySerializedFrame goaway(spdy_util.ConstructSpdyGoAway(
|
|
0x7fffffff, spdy::ERROR_CODE_NO_ERROR, "Graceful shutdown."));
|
|
MockRead reads[] = {
|
|
MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(goaway, 2),
|
|
MockRead(ASYNC, ERR_IO_PENDING, 3), MockRead(ASYNC, OK, 4)};
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet(kDefaultUrl, 1, MEDIUM));
|
|
MockWrite writes[] = {CreateMockWrite(req, 0)};
|
|
SequencedSocketData data(reads, writes);
|
|
|
|
MockConnect connect_data(SYNCHRONOUS, OK);
|
|
data.set_connect_data(connect_data);
|
|
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
const GURL url(kDefaultUrl);
|
|
SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), key, NetLogWithSource());
|
|
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, url, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegate(spdy_stream);
|
|
spdy_stream->SetDelegate(&delegate);
|
|
|
|
spdy::Http2HeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
|
|
spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
|
|
// Send headers.
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_TRUE(delegate.send_headers_completed());
|
|
|
|
EXPECT_EQ(1u, num_active_streams(session));
|
|
EXPECT_FALSE(session->IsGoingAway());
|
|
EXPECT_FALSE(session->IsDraining());
|
|
|
|
// Read GOAWAY.
|
|
data.Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_EQ(1u, num_active_streams(session));
|
|
EXPECT_TRUE(session->IsGoingAway());
|
|
EXPECT_FALSE(session->IsDraining());
|
|
|
|
http_session_.reset();
|
|
|
|
data.AllReadDataConsumed();
|
|
data.AllWriteDataConsumed();
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, IPConnectionPoolingWithWebSockets) {
|
|
// Define two hosts with identical IP address.
|
|
const int kTestPort = 443;
|
|
struct TestHosts {
|
|
std::string name;
|
|
std::string iplist;
|
|
SpdySessionKey key;
|
|
} test_hosts[] = {
|
|
{"www.example.org", "192.168.0.1"}, {"mail.example.org", "192.168.0.1"},
|
|
};
|
|
|
|
// Populate the HostResolver cache.
|
|
session_deps_.host_resolver->set_synchronous_mode(true);
|
|
for (auto& test_host : test_hosts) {
|
|
session_deps_.host_resolver->rules()->AddIPLiteralRule(
|
|
test_host.name, test_host.iplist, std::string());
|
|
|
|
test_host.key = SpdySessionKey(
|
|
HostPortPair(test_host.name, kTestPort), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
}
|
|
|
|
SpdyTestUtil spdy_util;
|
|
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet(nullptr, 0, 1, LOWEST));
|
|
spdy::SpdySerializedFrame settings_ack(spdy_util.ConstructSpdySettingsAck());
|
|
MockWrite writes[] = {CreateMockWrite(req, 0),
|
|
CreateMockWrite(settings_ack, 2)};
|
|
|
|
spdy::SettingsMap settings;
|
|
settings[spdy::SETTINGS_ENABLE_CONNECT_PROTOCOL] = 1;
|
|
spdy::SpdySerializedFrame settings_frame(
|
|
spdy_util.ConstructSpdySettings(settings));
|
|
spdy::SpdySerializedFrame resp(
|
|
spdy_util.ConstructSpdyGetReply(nullptr, 0, 1));
|
|
spdy::SpdySerializedFrame body(spdy_util.ConstructSpdyDataFrame(1, true));
|
|
MockRead reads[] = {CreateMockRead(settings_frame, 1),
|
|
CreateMockRead(resp, 3), CreateMockRead(body, 4),
|
|
MockRead(ASYNC, ERR_IO_PENDING, 5),
|
|
MockRead(ASYNC, 0, 6)};
|
|
|
|
SequencedSocketData data(reads, writes);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&data);
|
|
AddSSLSocketData();
|
|
CreateNetworkSession();
|
|
|
|
// Create a connection to the first host.
|
|
base::WeakPtr<SpdySession> session = CreateSpdySession(
|
|
http_session_.get(), test_hosts[0].key, NetLogWithSource());
|
|
|
|
// SpdySession does not support Websocket before SETTINGS frame is read.
|
|
EXPECT_FALSE(session->support_websocket());
|
|
NetLogWithSource net_log_with_source{
|
|
NetLogWithSource::Make(NetLogSourceType::NONE)};
|
|
// TryCreateAliasedSpdySession should not find |session| for either
|
|
// SpdySessionKeys if |is_websocket| argument is set.
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[0].key, test_hosts[0].iplist,
|
|
/* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true));
|
|
EXPECT_FALSE(TryCreateAliasedSpdySession(
|
|
spdy_session_pool_, test_hosts[1].key, test_hosts[1].iplist,
|
|
/* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true));
|
|
|
|
// Start request that triggers reading the SETTINGS frame.
|
|
const GURL url(kDefaultUrl);
|
|
base::WeakPtr<SpdyStream> spdy_stream = CreateStreamSynchronously(
|
|
SPDY_BIDIRECTIONAL_STREAM, session, url, LOWEST, NetLogWithSource());
|
|
test::StreamDelegateDoNothing delegate(spdy_stream);
|
|
spdy_stream->SetDelegate(&delegate);
|
|
|
|
spdy::Http2HeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
|
|
spdy_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Now SpdySession has read the SETTINGS frame and thus supports Websocket.
|
|
EXPECT_TRUE(session->support_websocket());
|
|
|
|
// FindAvailableSession() on the first host should now find the existing
|
|
// session with websockets enabled, and TryCreateAliasedSpdySession() should
|
|
// now set up aliases for |session| for the second one.
|
|
base::WeakPtr<SpdySession> result = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[0].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true, net_log_with_source);
|
|
EXPECT_EQ(session.get(), result.get());
|
|
EXPECT_TRUE(TryCreateAliasedSpdySession(spdy_session_pool_, test_hosts[1].key,
|
|
test_hosts[1].iplist,
|
|
/* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true));
|
|
|
|
// FindAvailableSession() should return |session| for either SpdySessionKeys
|
|
// when IP based pooling is enabled.
|
|
result = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[0].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true, net_log_with_source);
|
|
EXPECT_EQ(session.get(), result.get());
|
|
result = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ true,
|
|
/* is_websocket = */ true, net_log_with_source);
|
|
EXPECT_EQ(session.get(), result.get());
|
|
|
|
// FindAvailableSession() should only return |session| for the first
|
|
// SpdySessionKey when IP based pooling is disabled.
|
|
result = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[0].key, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ true, net_log_with_source);
|
|
EXPECT_EQ(session.get(), result.get());
|
|
result = spdy_session_pool_->FindAvailableSession(
|
|
test_hosts[1].key, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ true, net_log_with_source);
|
|
EXPECT_FALSE(result);
|
|
|
|
// Read EOF.
|
|
data.Resume();
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
EXPECT_TRUE(data.AllReadDataConsumed());
|
|
EXPECT_TRUE(data.AllWriteDataConsumed());
|
|
}
|
|
|
|
class TestOnRequestDeletedCallback {
|
|
public:
|
|
TestOnRequestDeletedCallback() = default;
|
|
|
|
TestOnRequestDeletedCallback(const TestOnRequestDeletedCallback&) = delete;
|
|
TestOnRequestDeletedCallback& operator=(const TestOnRequestDeletedCallback&) =
|
|
delete;
|
|
|
|
~TestOnRequestDeletedCallback() = default;
|
|
|
|
base::RepeatingClosure Callback() {
|
|
return base::BindRepeating(&TestOnRequestDeletedCallback::OnRequestDeleted,
|
|
base::Unretained(this));
|
|
}
|
|
|
|
bool invoked() const { return invoked_; }
|
|
|
|
void WaitUntilInvoked() { run_loop_.Run(); }
|
|
|
|
void SetRequestDeletedCallback(base::OnceClosure request_deleted_callback) {
|
|
DCHECK(!request_deleted_callback_);
|
|
request_deleted_callback_ = std::move(request_deleted_callback);
|
|
}
|
|
|
|
private:
|
|
void OnRequestDeleted() {
|
|
EXPECT_FALSE(invoked_);
|
|
invoked_ = true;
|
|
if (request_deleted_callback_)
|
|
std::move(request_deleted_callback_).Run();
|
|
run_loop_.Quit();
|
|
}
|
|
|
|
bool invoked_ = false;
|
|
base::RunLoop run_loop_;
|
|
|
|
base::OnceClosure request_deleted_callback_;
|
|
};
|
|
|
|
class TestRequestDelegate
|
|
: public SpdySessionPool::SpdySessionRequest::Delegate {
|
|
public:
|
|
TestRequestDelegate() = default;
|
|
|
|
TestRequestDelegate(const TestRequestDelegate&) = delete;
|
|
TestRequestDelegate& operator=(const TestRequestDelegate&) = delete;
|
|
|
|
~TestRequestDelegate() override = default;
|
|
|
|
// SpdySessionPool::SpdySessionRequest::Delegate implementation:
|
|
void OnSpdySessionAvailable(
|
|
base::WeakPtr<SpdySession> spdy_session) override {}
|
|
};
|
|
|
|
TEST_F(SpdySessionPoolTest, RequestSessionWithNoSessions) {
|
|
const SpdySessionKey kSessionKey(
|
|
HostPortPair("foo.test", 443), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
|
|
CreateNetworkSession();
|
|
|
|
// First request. Its request deleted callback should never be invoked.
|
|
TestOnRequestDeletedCallback request_deleted_callback1;
|
|
TestRequestDelegate request_delegate1;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request1;
|
|
bool is_first_request_for_session;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback1.Callback(), &request_delegate1,
|
|
&spdy_session_request1, &is_first_request_for_session));
|
|
EXPECT_TRUE(is_first_request_for_session);
|
|
|
|
// Second request.
|
|
TestOnRequestDeletedCallback request_deleted_callback2;
|
|
TestRequestDelegate request_delegate2;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request2;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback2.Callback(), &request_delegate2,
|
|
&spdy_session_request2, &is_first_request_for_session));
|
|
EXPECT_FALSE(is_first_request_for_session);
|
|
|
|
// Third request.
|
|
TestOnRequestDeletedCallback request_deleted_callback3;
|
|
TestRequestDelegate request_delegate3;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request3;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback3.Callback(), &request_delegate3,
|
|
&spdy_session_request3, &is_first_request_for_session));
|
|
EXPECT_FALSE(is_first_request_for_session);
|
|
|
|
// Destroying the second request shouldn't cause anything to happen.
|
|
spdy_session_request2.reset();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(request_deleted_callback1.invoked());
|
|
EXPECT_FALSE(request_deleted_callback2.invoked());
|
|
EXPECT_FALSE(request_deleted_callback3.invoked());
|
|
|
|
// But destroying the first request should cause the second and third
|
|
// callbacks to be invoked.
|
|
spdy_session_request1.reset();
|
|
request_deleted_callback2.WaitUntilInvoked();
|
|
request_deleted_callback3.WaitUntilInvoked();
|
|
EXPECT_FALSE(request_deleted_callback1.invoked());
|
|
|
|
// Nothing should happen when the third request is destroyed.
|
|
spdy_session_request3.reset();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(request_deleted_callback1.invoked());
|
|
}
|
|
|
|
TEST_F(SpdySessionPoolTest, RequestSessionDuringNotification) {
|
|
const SpdySessionKey kSessionKey(
|
|
HostPortPair("foo.test", 443), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
|
|
CreateNetworkSession();
|
|
|
|
// First request. Its request deleted callback should never be invoked.
|
|
TestOnRequestDeletedCallback request_deleted_callback1;
|
|
TestRequestDelegate request_delegate1;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request1;
|
|
bool is_first_request_for_session;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback1.Callback(), &request_delegate1,
|
|
&spdy_session_request1, &is_first_request_for_session));
|
|
EXPECT_TRUE(is_first_request_for_session);
|
|
|
|
// Second request.
|
|
TestOnRequestDeletedCallback request_deleted_callback2;
|
|
TestRequestDelegate request_delegate2;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request2;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback2.Callback(), &request_delegate2,
|
|
&spdy_session_request2, &is_first_request_for_session));
|
|
EXPECT_FALSE(is_first_request_for_session);
|
|
|
|
TestOnRequestDeletedCallback request_deleted_callback3;
|
|
TestRequestDelegate request_delegate3;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request3;
|
|
TestOnRequestDeletedCallback request_deleted_callback4;
|
|
TestRequestDelegate request_delegate4;
|
|
std::unique_ptr<SpdySessionPool::SpdySessionRequest> spdy_session_request4;
|
|
request_deleted_callback2.SetRequestDeletedCallback(
|
|
base::BindLambdaForTesting([&]() {
|
|
// Third request. It should again be marked as the first request for the
|
|
// session, since it's only created after the original two have been
|
|
// removed.
|
|
bool is_first_request_for_session;
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback3.Callback(), &request_delegate3,
|
|
&spdy_session_request3, &is_first_request_for_session));
|
|
EXPECT_TRUE(is_first_request_for_session);
|
|
|
|
// Fourth request.
|
|
EXPECT_FALSE(spdy_session_pool_->RequestSession(
|
|
kSessionKey, /* enable_ip_based_pooling = */ false,
|
|
/* is_websocket = */ false, NetLogWithSource(),
|
|
request_deleted_callback4.Callback(), &request_delegate4,
|
|
&spdy_session_request4, &is_first_request_for_session));
|
|
EXPECT_FALSE(is_first_request_for_session);
|
|
}));
|
|
|
|
// Destroying the first request should cause the second callback to be
|
|
// invoked, and the third and fourth request to be made.
|
|
spdy_session_request1.reset();
|
|
request_deleted_callback2.WaitUntilInvoked();
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_FALSE(request_deleted_callback1.invoked());
|
|
EXPECT_FALSE(request_deleted_callback3.invoked());
|
|
EXPECT_FALSE(request_deleted_callback4.invoked());
|
|
EXPECT_TRUE(spdy_session_request3);
|
|
EXPECT_TRUE(spdy_session_request4);
|
|
|
|
// Destroying the third request should cause the fourth callback to be
|
|
// invoked.
|
|
spdy_session_request3.reset();
|
|
request_deleted_callback4.WaitUntilInvoked();
|
|
EXPECT_FALSE(request_deleted_callback1.invoked());
|
|
EXPECT_FALSE(request_deleted_callback3.invoked());
|
|
}
|
|
|
|
static const char kSSLServerTestHost[] = "config-changed.test";
|
|
|
|
static const struct {
|
|
const char* url;
|
|
const char* proxy_pac_string;
|
|
bool expect_invalidated;
|
|
} kSSLServerTests[] = {
|
|
// If the host and port match, the session should be invalidated.
|
|
{"https://config-changed.test", "DIRECT", true},
|
|
// If host and port do not match, the session should not be invalidated.
|
|
{"https://mail.config-changed.test", "DIRECT", false},
|
|
{"https://config-changed.test:444", "DIRECT", false},
|
|
// If the proxy matches, the session should be invalidated independent of
|
|
// the host.
|
|
{"https://config-changed.test", "HTTPS config-changed.test:443", true},
|
|
{"https://mail.config-changed.test", "HTTPS config-changed.test:443", true},
|
|
// HTTP and SOCKS proxies do not have client certificates.
|
|
{"https://mail.config-changed.test", "PROXY config-changed.test:443",
|
|
false},
|
|
{"https://mail.config-changed.test", "SOCKS5 config-changed.test:443",
|
|
false},
|
|
// The proxy host and port must match.
|
|
{"https://mail.config-changed.test", "HTTPS mail.config-changed.test:443",
|
|
false},
|
|
{"https://mail.config-changed.test", "HTTPS config-changed.test:444",
|
|
false},
|
|
};
|
|
|
|
// Tests the OnSSLConfigForServerChanged() method matches SpdySessions as
|
|
// expected.
|
|
TEST_F(SpdySessionPoolTest, SSLConfigForServerChanged) {
|
|
const MockConnect connect_data(SYNCHRONOUS, OK);
|
|
const MockRead reads[] = {
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
|
|
std::vector<std::unique_ptr<StaticSocketDataProvider>> socket_data;
|
|
size_t num_tests = std::size(kSSLServerTests);
|
|
for (size_t i = 0; i < num_tests; i++) {
|
|
socket_data.push_back(std::make_unique<StaticSocketDataProvider>(
|
|
reads, base::span<MockWrite>()));
|
|
socket_data.back()->set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(
|
|
socket_data.back().get());
|
|
AddSSLSocketData();
|
|
}
|
|
|
|
CreateNetworkSession();
|
|
|
|
std::vector<base::WeakPtr<SpdySession>> sessions;
|
|
for (size_t i = 0; i < num_tests; i++) {
|
|
SpdySessionKey key(
|
|
HostPortPair::FromURL(GURL(kSSLServerTests[i].url)),
|
|
PacResultElementToProxyServer(kSSLServerTests[i].proxy_pac_string),
|
|
PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse,
|
|
SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
sessions.push_back(
|
|
CreateSpdySession(http_session_.get(), key, NetLogWithSource()));
|
|
}
|
|
|
|
// All sessions are available.
|
|
for (size_t i = 0; i < num_tests; i++) {
|
|
SCOPED_TRACE(i);
|
|
EXPECT_TRUE(sessions[i]->IsAvailable());
|
|
}
|
|
|
|
spdy_session_pool_->OnSSLConfigForServerChanged(
|
|
HostPortPair(kSSLServerTestHost, 443));
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Sessions were inactive, so the unavailable sessions are closed.
|
|
for (size_t i = 0; i < num_tests; i++) {
|
|
SCOPED_TRACE(i);
|
|
if (kSSLServerTests[i].expect_invalidated) {
|
|
EXPECT_FALSE(sessions[i]);
|
|
} else {
|
|
ASSERT_TRUE(sessions[i]);
|
|
EXPECT_TRUE(sessions[i]->IsAvailable());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests the OnSSLConfigForServerChanged() method when there are streams open.
|
|
TEST_F(SpdySessionPoolTest, SSLConfigForServerChangedWithStreams) {
|
|
// Set up a SpdySession with an active, created, and pending stream.
|
|
SpdyTestUtil spdy_util;
|
|
spdy::SettingsMap settings;
|
|
settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 2;
|
|
spdy::SpdySerializedFrame settings_frame =
|
|
spdy_util.ConstructSpdySettings(settings);
|
|
spdy::SpdySerializedFrame settings_ack = spdy_util.ConstructSpdySettingsAck();
|
|
spdy::SpdySerializedFrame req(
|
|
spdy_util.ConstructSpdyGet(nullptr, 0, 1, MEDIUM));
|
|
|
|
const MockConnect connect_data(SYNCHRONOUS, OK);
|
|
const MockRead reads[] = {
|
|
CreateMockRead(settings_frame),
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
const MockWrite writes[] = {
|
|
CreateMockWrite(settings_ack),
|
|
CreateMockWrite(req),
|
|
};
|
|
|
|
StaticSocketDataProvider socket_data(reads, writes);
|
|
socket_data.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&socket_data);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
const GURL url(kDefaultUrl);
|
|
SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), key, NetLogWithSource());
|
|
|
|
// Pick up the SETTINGS frame to update SETTINGS_MAX_CONCURRENT_STREAMS.
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_EQ(2u, max_concurrent_streams(session));
|
|
|
|
// The first two stream requests should succeed.
|
|
base::WeakPtr<SpdyStream> active_stream = CreateStreamSynchronously(
|
|
SPDY_REQUEST_RESPONSE_STREAM, session, url, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing active_stream_delegate(active_stream);
|
|
active_stream->SetDelegate(&active_stream_delegate);
|
|
base::WeakPtr<SpdyStream> created_stream = CreateStreamSynchronously(
|
|
SPDY_REQUEST_RESPONSE_STREAM, session, url, MEDIUM, NetLogWithSource());
|
|
test::StreamDelegateDoNothing created_stream_delegate(created_stream);
|
|
created_stream->SetDelegate(&created_stream_delegate);
|
|
|
|
// The third will block.
|
|
TestCompletionCallback callback;
|
|
SpdyStreamRequest stream_request;
|
|
EXPECT_THAT(
|
|
stream_request.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session, url,
|
|
/*can_send_early=*/false, MEDIUM, SocketTag(),
|
|
NetLogWithSource(), callback.callback(),
|
|
TRAFFIC_ANNOTATION_FOR_TESTS),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
// Activate the first stream by sending data.
|
|
spdy::Http2HeaderBlock headers(spdy_util.ConstructGetHeaderBlock(url.spec()));
|
|
active_stream->SendRequestHeaders(std::move(headers), NO_MORE_DATA_TO_SEND);
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// The active stream should now have a stream ID.
|
|
EXPECT_EQ(1u, active_stream->stream_id());
|
|
EXPECT_EQ(spdy::kInvalidStreamId, created_stream->stream_id());
|
|
EXPECT_TRUE(session->is_active());
|
|
EXPECT_TRUE(session->IsAvailable());
|
|
|
|
spdy_session_pool_->OnSSLConfigForServerChanged(HostPortPair::FromURL(url));
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// The active stream is still alive, so the session is still active.
|
|
ASSERT_TRUE(session);
|
|
EXPECT_TRUE(session->is_active());
|
|
ASSERT_TRUE(active_stream);
|
|
|
|
// The session is no longer available.
|
|
EXPECT_FALSE(session->IsAvailable());
|
|
EXPECT_TRUE(session->IsGoingAway());
|
|
|
|
// The pending and created stream are cancelled.
|
|
// TODO(https://crbug.com/1213609): Ideally, this would be recoverable.
|
|
EXPECT_THAT(callback.WaitForResult(), IsError(ERR_NETWORK_CHANGED));
|
|
EXPECT_THAT(created_stream_delegate.WaitForClose(),
|
|
IsError(ERR_NETWORK_CHANGED));
|
|
|
|
// Close the active stream.
|
|
active_stream->Close();
|
|
// TODO(https://crbug.com/982499): The invalidated session should be closed
|
|
// after a RunUntilIdle(), but it is not.
|
|
}
|
|
|
|
// Tests the OnSSLConfigForServerChanged() method when there only pending
|
|
// streams active.
|
|
TEST_F(SpdySessionPoolTest, SSLConfigForServerChangedWithOnlyPendingStreams) {
|
|
// Set up a SpdySession that accepts no streams.
|
|
SpdyTestUtil spdy_util;
|
|
spdy::SettingsMap settings;
|
|
settings[spdy::SETTINGS_MAX_CONCURRENT_STREAMS] = 0;
|
|
spdy::SpdySerializedFrame settings_frame =
|
|
spdy_util.ConstructSpdySettings(settings);
|
|
spdy::SpdySerializedFrame settings_ack = spdy_util.ConstructSpdySettingsAck();
|
|
|
|
const MockConnect connect_data(SYNCHRONOUS, OK);
|
|
const MockRead reads[] = {
|
|
CreateMockRead(settings_frame),
|
|
MockRead(SYNCHRONOUS, ERR_IO_PENDING) // Stall forever.
|
|
};
|
|
const MockWrite writes[] = {
|
|
CreateMockWrite(settings_ack),
|
|
};
|
|
|
|
StaticSocketDataProvider socket_data(reads, writes);
|
|
socket_data.set_connect_data(connect_data);
|
|
session_deps_.socket_factory->AddSocketDataProvider(&socket_data);
|
|
AddSSLSocketData();
|
|
|
|
CreateNetworkSession();
|
|
|
|
const GURL url(kDefaultUrl);
|
|
SpdySessionKey key(HostPortPair::FromURL(url), ProxyServer::Direct(),
|
|
PRIVACY_MODE_DISABLED,
|
|
SpdySessionKey::IsProxySession::kFalse, SocketTag(),
|
|
NetworkAnonymizationKey(), SecureDnsPolicy::kAllow);
|
|
base::WeakPtr<SpdySession> session =
|
|
CreateSpdySession(http_session_.get(), key, NetLogWithSource());
|
|
|
|
// Pick up the SETTINGS frame to update SETTINGS_MAX_CONCURRENT_STREAMS.
|
|
base::RunLoop().RunUntilIdle();
|
|
EXPECT_EQ(0u, max_concurrent_streams(session));
|
|
|
|
// Create a stream. It should block on the stream limit.
|
|
TestCompletionCallback callback;
|
|
SpdyStreamRequest stream_request;
|
|
ASSERT_THAT(
|
|
stream_request.StartRequest(SPDY_REQUEST_RESPONSE_STREAM, session, url,
|
|
/*can_send_early=*/false, MEDIUM, SocketTag(),
|
|
NetLogWithSource(), callback.callback(),
|
|
TRAFFIC_ANNOTATION_FOR_TESTS),
|
|
IsError(ERR_IO_PENDING));
|
|
|
|
spdy_session_pool_->OnSSLConfigForServerChanged(HostPortPair::FromURL(url));
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// The pending stream is cancelled.
|
|
// TODO(https://crbug.com/1213609): Ideally, this would be recoverable.
|
|
EXPECT_THAT(callback.WaitForResult(), IsError(ERR_NETWORK_CHANGED));
|
|
EXPECT_FALSE(session);
|
|
}
|
|
|
|
} // namespace net
|