| // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| // 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 <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/test/bind_test_util.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 "base/trace_event/traced_value.h" |
| #include "build/build_config.h" |
| #include "net/dns/host_cache.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/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 "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() : spdy_session_pool_(nullptr) {} |
| |
| 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(); |
| } |
| |
| SpdySessionDependencies session_deps_; |
| std::unique_ptr<HttpNetworkSession> http_session_; |
| SpdySessionPool* spdy_session_pool_; |
| std::vector<std::unique_ptr<SSLSocketDataProvider>> ssl_data_vector_; |
| }; |
| |
| class SpdySessionRequestDelegate |
| : public SpdySessionPool::SpdySessionRequest::Delegate { |
| public: |
| SpdySessionRequestDelegate() = default; |
| ~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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SpdySessionRequestDelegate); |
| }; |
| |
| // 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); |
| |
| AddressList address_list; |
| EXPECT_THAT( |
| ParseAddressList(ip_address_list, /* canonical_name = */ std::string(), |
| &address_list), |
| IsOk()); |
| address_list = AddressList::CopyWithPort(address_list, 443); |
| |
| // Simulate a host resolution completing. |
| OnHostResolutionCallbackResult result = |
| pool->OnHostResolutionComplete(key, is_websocket, address_list); |
| |
| // 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 OnHeadersReceived( |
| const spdy::SpdyHeaderBlock& response_headers, |
| const spdy::SpdyHeaderBlock* pushed_request_headers) override {} |
| |
| void OnDataReceived(std::unique_ptr<SpdyBuffer> buffer) override {} |
| |
| void OnDataSent() override {} |
| |
| void OnTrailers(const spdy::SpdyHeaderBlock& trailers) override {} |
| |
| void OnClose(int status) override { |
| ignore_result(CreateFakeSpdySession(spdy_session_pool_, key_)); |
| } |
| |
| bool CanGreaseFrameType() const override { return false; } |
| |
| NetLogSource source_dependency() const override { return NetLogSource(); } |
| |
| private: |
| SpdySessionPool* const 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| |
| 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) { |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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(); |
| 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(); |
| 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(); |
| 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(); |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| |
| 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)); |
| } |
| |
| // 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) { |
| const 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 (size_t i = 0; i < base::size(test_hosts); i++) { |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| test_hosts[i].name, test_hosts[i].iplist, std::string()); |
| |
| test_hosts[i].key = SpdySessionKey( |
| HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| } |
| |
| 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(), |
| ProxyServer::FromPacString("HTTP http://proxy.foo.com/"), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| EXPECT_FALSE(TryCreateAliasedSpdySession(spdy_session_pool_, proxy_key, |
| test_hosts[1].iplist)); |
| |
| // Verify that the second host, with a different disable_secure_dns value, |
| // 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(), NetworkIsolationKey(), true /* disable_secure_dns */); |
| 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(); |
| 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) { |
| const 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 (size_t i = 0; i < base::size(test_hosts); i++) { |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| test_hosts[i].name, test_hosts[i].iplist, std::string()); |
| |
| // Setup a SpdySessionKey |
| test_hosts[i].key = SpdySessionKey( |
| HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| } |
| |
| 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. |
| 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 (size_t i = 0; i < base::size(test_hosts); i++) { |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| test_hosts[i].name, test_hosts[i].iplist, std::string()); |
| |
| test_hosts[i].key = SpdySessionKey( |
| HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| } |
| |
| 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. |
| RecordingBoundTestNetLog net_log; |
| 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, net_log.bound()); |
| EXPECT_EQ(session0.get(), session1.get()); |
| |
| ASSERT_EQ(1u, net_log.GetSize()); |
| histogram_tester.ExpectTotalCount("Net.SpdySessionGet", 2); |
| |
| // FindAvailableSession() should have logged a netlog event indicating IP |
| // pooling. |
| auto entry_list = net_log.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. |
| 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 (size_t i = 0; i < base::size(test_hosts); i++) { |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| test_hosts[i].name, test_hosts[i].iplist, std::string()); |
| |
| test_hosts[i].key = SpdySessionKey( |
| HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| } |
| |
| 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( |
| reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)); |
| ASSERT_TRUE(ssl.ssl_info.cert); |
| ssl.ssl_info.client_cert_sent = true; |
| ssl.next_proto = kProtoHTTP2; |
| RunIPPoolingDisabledTest(&ssl); |
| } |
| |
| // 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, IPAddressChanged) { |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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::SpdyHeaderBlock 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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(); |
| |
| #if defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) |
| 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(ERR_NETWORK_CHANGED)); |
| |
| sessionA->CloseSessionOnError(ERR_ABORTED, "Closing"); |
| sessionB->CloseSessionOnError(ERR_ABORTED, "Closing"); |
| |
| EXPECT_TRUE(delegateA.StreamIsClosed()); |
| EXPECT_THAT(delegateA.WaitForClose(), IsError(ERR_ABORTED)); |
| #else |
| 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)); |
| #endif // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_IOS) |
| } |
| |
| // 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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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::SpdyHeaderBlock 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 defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_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 // defined(OS_ANDROID) || defined(OS_WIN) || defined(OS_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(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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::SpdyHeaderBlock 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(); |
| } |
| |
| class SpdySessionMemoryDumpTest |
| : public SpdySessionPoolTest, |
| public testing::WithParamInterface< |
| base::trace_event::MemoryDumpLevelOfDetail> {}; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SpdySessionMemoryDumpTest, |
| ::testing::Values(base::trace_event::MemoryDumpLevelOfDetail::DETAILED, |
| base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND)); |
| |
| TEST_P(SpdySessionMemoryDumpTest, DumpMemoryStats) { |
| SpdySessionKey key(HostPortPair("www.example.org", 443), |
| ProxyServer::Direct(), PRIVACY_MODE_DISABLED, |
| SpdySessionKey::IsProxySession::kFalse, SocketTag(), |
| NetworkIsolationKey(), false /* disable_secure_dns */); |
| |
| MockRead reads[] = {MockRead(SYNCHRONOUS, ERR_IO_PENDING)}; |
| StaticSocketDataProvider data(reads, base::span<MockWrite>()); |
| data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
| session_deps_.socket_factory->AddSocketDataProvider(&data); |
| |
| SSLSocketDataProvider ssl(SYNCHRONOUS, OK); |
| session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl); |
| |
| CreateNetworkSession(); |
| |
| base::WeakPtr<SpdySession> session = |
| CreateSpdySession(http_session_.get(), key, NetLogWithSource()); |
| |
| // Flush the SpdySession::OnReadComplete() task. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(HasSpdySession(spdy_session_pool_, key)); |
| base::trace_event::MemoryDumpArgs dump_args = {GetParam()}; |
| auto process_memory_dump = |
| std::make_unique<base::trace_event::ProcessMemoryDump>(dump_args); |
| base::trace_event::MemoryAllocatorDump* parent_dump = |
| process_memory_dump->CreateAllocatorDump( |
| "net/http_network_session_0x123"); |
| spdy_session_pool_->DumpMemoryStats(process_memory_dump.get(), |
| parent_dump->absolute_name()); |
| |
| // Whether SpdySession::DumpMemoryStats() is invoked. |
| bool did_dump = false; |
| const base::trace_event::ProcessMemoryDump::AllocatorDumpsMap& |
| allocator_dumps = process_memory_dump->allocator_dumps(); |
| for (const auto& pair : allocator_dumps) { |
| const std::string& dump_name = pair.first; |
| if (dump_name.find("spdy_session_pool") == std::string::npos) |
| continue; |
| MemoryAllocatorDump::Entry expected("active_session_count", |
| MemoryAllocatorDump::kUnitsObjects, 0); |
| ASSERT_THAT(pair.second->entries(), Contains(Eq(ByRef(expected)))); |
| did_dump = true; |
| } |
| EXPECT_TRUE(did_dump); |
| spdy_session_pool_->CloseCurrentSessions(ERR_ABORTED); |
| } |
| |
| 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 (size_t i = 0; i < base::size(test_hosts); i++) { |
| session_deps_.host_resolver->rules()->AddIPLiteralRule( |
| test_hosts[i].name, test_hosts[i].iplist, std::string()); |
| |
| test_hosts[i].key = SpdySessionKey( |
| HostPortPair(test_hosts[i].name, kTestPort), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| } |
| |
| 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()); |
| RecordingBoundTestNetLog net_log; |
| // 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::SpdyHeaderBlock 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.bound()); |
| 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.bound()); |
| 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.bound()); |
| 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.bound()); |
| 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.bound()); |
| EXPECT_FALSE(result); |
| |
| // Read EOF. |
| data.Resume(); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(data.AllReadDataConsumed()); |
| EXPECT_TRUE(data.AllWriteDataConsumed()); |
| } |
| |
| class TestOnRequestDeletedCallback { |
| public: |
| TestOnRequestDeletedCallback() = default; |
| ~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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestOnRequestDeletedCallback); |
| }; |
| |
| class TestRequestDelegate |
| : public SpdySessionPool::SpdySessionRequest::Delegate { |
| public: |
| TestRequestDelegate() = default; |
| ~TestRequestDelegate() override = default; |
| |
| // SpdySessionPool::SpdySessionRequest::Delegate implementation: |
| void OnSpdySessionAvailable( |
| base::WeakPtr<SpdySession> spdy_session) override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestRequestDelegate); |
| }; |
| |
| TEST_F(SpdySessionPoolTest, RequestSessionWithNoSessions) { |
| const SpdySessionKey kSessionKey( |
| HostPortPair("foo.test", 443), ProxyServer::Direct(), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| |
| 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(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| |
| 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 when there are no streams |
| // active. |
| 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 = base::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)), |
| ProxyServer::FromPacString(kSSLServerTests[i].proxy_pac_string), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| 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 active. |
| TEST_F(SpdySessionPoolTest, SSLConfigForServerChangedWithStreams) { |
| 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 = base::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; |
| std::vector<base::WeakPtr<SpdyStream>> streams; |
| for (size_t i = 0; i < num_tests; i++) { |
| SCOPED_TRACE(i); |
| SpdySessionKey key( |
| HostPortPair::FromURL(GURL(kSSLServerTests[i].url)), |
| ProxyServer::FromPacString(kSSLServerTests[i].proxy_pac_string), |
| PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kFalse, |
| SocketTag(), NetworkIsolationKey(), false /* disable_secure_dns */); |
| sessions.push_back( |
| CreateSpdySession(http_session_.get(), key, NetLogWithSource())); |
| streams.push_back(CreateStreamSynchronously( |
| SPDY_BIDIRECTIONAL_STREAM, sessions.back(), |
| GURL(kSSLServerTests[i].url), MEDIUM, NetLogWithSource())); |
| ASSERT_TRUE(streams.back()); |
| } |
| |
| // All sessions are active and available. |
| for (size_t i = 0; i < num_tests; i++) { |
| SCOPED_TRACE(i); |
| EXPECT_TRUE(sessions[i]->is_active()); |
| EXPECT_TRUE(sessions[i]->IsAvailable()); |
| } |
| |
| spdy_session_pool_->OnSSLConfigForServerChanged( |
| HostPortPair(kSSLServerTestHost, 443)); |
| |
| // The sessions should continue to be active, but now some are unavailable. |
| for (size_t i = 0; i < num_tests; i++) { |
| SCOPED_TRACE(i); |
| ASSERT_TRUE(sessions[i]); |
| EXPECT_TRUE(sessions[i]->is_active()); |
| if (kSSLServerTests[i].expect_invalidated) { |
| EXPECT_FALSE(sessions[i]->IsAvailable()); |
| EXPECT_TRUE(sessions[i]->IsGoingAway()); |
| } else { |
| EXPECT_TRUE(sessions[i]->IsAvailable()); |
| EXPECT_FALSE(sessions[i]->IsGoingAway()); |
| } |
| } |
| |
| // Each stream is still around. Close them. |
| for (size_t i = 0; i < num_tests; i++) { |
| SCOPED_TRACE(i); |
| ASSERT_TRUE(streams[i]); |
| streams[i]->Close(); |
| } |
| |
| // TODO(https://crbug.com/982499): The invalidated sessions should be closed |
| // after a RunUntilIdle(), but they are not. |
| } |
| |
| } // namespace net |