| // Copyright 2019 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/http/http_proxy_connect_job.h" |
| |
| #include <map> |
| #include <string> |
| #include <utility> |
| |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_param_associator.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "build/build_config.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/base/test_proxy_delegate.h" |
| #include "net/http/http_network_session.h" |
| #include "net/nqe/network_quality_estimator_test_util.h" |
| #include "net/socket/client_socket_handle.h" |
| #include "net/socket/connect_job_test_util.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/socket/socks_connect_job.h" |
| #include "net/socket/ssl_client_socket.h" |
| #include "net/socket/ssl_connect_job.h" |
| #include "net/socket/transport_client_socket_pool.h" |
| #include "net/socket/transport_connect_job.h" |
| #include "net/spdy/spdy_test_util_common.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/test_with_scoped_task_environment.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const char kEndpointHost[] = "www.endpoint.test"; |
| |
| enum HttpProxyType { HTTP, HTTPS, SPDY }; |
| |
| const char kHttpProxyHost[] = "httpproxy.example.test"; |
| const char kHttpsProxyHost[] = "httpsproxy.example.test"; |
| |
| } // namespace |
| |
| class HttpProxyConnectJobTest : public ::testing::TestWithParam<HttpProxyType>, |
| public WithScopedTaskEnvironment { |
| protected: |
| HttpProxyConnectJobTest() |
| : transport_socket_pool_(32 /* max_sockets */, |
| 6 /* max_sockets_pre_group */, |
| &socket_factory_, |
| session_deps_.host_resolver.get(), |
| nullptr /* proxy_delegate */, |
| session_deps_.cert_verifier.get(), |
| session_deps_.channel_id_service.get(), |
| session_deps_.transport_security_state.get(), |
| session_deps_.cert_transparency_verifier.get(), |
| session_deps_.ct_policy_enforcer.get(), |
| nullptr /* ssl_client_session_cache */, |
| std::string() /* ssl_session_cache_shard */, |
| session_deps_.ssl_config_service.get(), |
| nullptr /* socket_performance_watcher_factory */, |
| nullptr /* network_quality_estimator */, |
| nullptr /* net_log */), |
| ssl_socket_pool_(32 /* max_sockets */, |
| 6 /* max_sockets_pre_group */, |
| &socket_factory_, |
| session_deps_.host_resolver.get(), |
| nullptr /* proxy_delegate */, |
| session_deps_.cert_verifier.get(), |
| session_deps_.channel_id_service.get(), |
| session_deps_.transport_security_state.get(), |
| session_deps_.cert_transparency_verifier.get(), |
| session_deps_.ct_policy_enforcer.get(), |
| nullptr /* ssl_client_session_cache */, |
| std::string() /* ssl_session_cache_shard */, |
| session_deps_.ssl_config_service.get(), |
| nullptr /* socket_performance_watcher_factory */, |
| nullptr /* network_quality_estimator */, |
| nullptr /* net_log */), |
| field_trial_list_(nullptr) { |
| session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); |
| } |
| |
| virtual ~HttpProxyConnectJobTest() { |
| // Reset global field trial parameters to defaults values. |
| base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); |
| HttpProxyConnectJob::UpdateFieldTrialParametersForTesting(); |
| } |
| |
| // Initializes the field trial parameters for the field trial that determines |
| // connection timeout based on the network quality. |
| void InitAdaptiveTimeoutFieldTrialWithParams( |
| bool use_default_params, |
| int ssl_http_rtt_multiplier, |
| int non_ssl_http_rtt_multiplier, |
| base::TimeDelta min_proxy_connection_timeout, |
| base::TimeDelta max_proxy_connection_timeout) { |
| std::string trial_name = "NetAdaptiveProxyConnectionTimeout"; |
| std::string group_name = "GroupName"; |
| |
| std::map<std::string, std::string> params; |
| if (!use_default_params) { |
| params["ssl_http_rtt_multiplier"] = |
| base::NumberToString(ssl_http_rtt_multiplier); |
| params["non_ssl_http_rtt_multiplier"] = |
| base::NumberToString(non_ssl_http_rtt_multiplier); |
| params["min_proxy_connection_timeout_seconds"] = |
| base::NumberToString(min_proxy_connection_timeout.InSeconds()); |
| params["max_proxy_connection_timeout_seconds"] = |
| base::NumberToString(max_proxy_connection_timeout.InSeconds()); |
| } |
| base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); |
| EXPECT_TRUE( |
| base::AssociateFieldTrialParams(trial_name, group_name, params)); |
| EXPECT_TRUE(base::FieldTrialList::CreateFieldTrial(trial_name, group_name)); |
| |
| // Force static global that reads the field trials to update. |
| HttpProxyConnectJob::UpdateFieldTrialParametersForTesting(); |
| } |
| |
| scoped_refptr<TransportSocketParams> CreateHttpProxyParams() const { |
| if (GetParam() != HTTP) |
| return nullptr; |
| return base::MakeRefCounted<TransportSocketParams>( |
| HostPortPair(kHttpProxyHost, 80), false, OnHostResolutionCallback()); |
| } |
| |
| scoped_refptr<SSLSocketParams> CreateHttpsProxyParams() const { |
| if (GetParam() == HTTP) |
| return nullptr; |
| return base::MakeRefCounted<SSLSocketParams>( |
| base::MakeRefCounted<TransportSocketParams>( |
| HostPortPair(kHttpsProxyHost, 443), false, |
| OnHostResolutionCallback()), |
| nullptr, nullptr, HostPortPair(kHttpsProxyHost, 443), SSLConfig(), |
| PRIVACY_MODE_DISABLED); |
| } |
| |
| // Returns a correctly constructed HttpProxyParams for the HTTP or HTTPS |
| // proxy. |
| scoped_refptr<HttpProxySocketParams> CreateParams(bool tunnel) { |
| return base::MakeRefCounted<HttpProxySocketParams>( |
| CreateHttpProxyParams(), CreateHttpsProxyParams(), |
| quic::QUIC_VERSION_UNSUPPORTED, std::string(), |
| HostPortPair(kEndpointHost, tunnel ? 443 : 80), |
| session_->http_auth_cache(), session_->http_auth_handler_factory(), |
| session_->spdy_session_pool(), session_->quic_stream_factory(), |
| /*is_trusted_proxy=*/false, tunnel, TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForHttpRequest( |
| ConnectJob::Delegate* delegate, |
| RequestPriority priority = DEFAULT_PRIORITY) { |
| return CreateConnectJob(CreateParams(false /* tunnel */), delegate, |
| priority); |
| } |
| |
| std::unique_ptr<HttpProxyConnectJob> CreateConnectJobForTunnel( |
| ConnectJob::Delegate* delegate, |
| RequestPriority priority = DEFAULT_PRIORITY) { |
| return CreateConnectJob(CreateParams(true /* tunnel */), delegate, |
| priority); |
| } |
| |
| std::unique_ptr<HttpProxyConnectJob> CreateConnectJob( |
| scoped_refptr<HttpProxySocketParams> http_proxy_socket_params, |
| ConnectJob::Delegate* delegate, |
| RequestPriority priority) { |
| return std::make_unique<HttpProxyConnectJob>( |
| priority, |
| CommonConnectJobParams( |
| "group_name", SocketTag(), true /* respect_limits */, |
| &socket_factory_, session_deps_.host_resolver.get(), |
| proxy_delegate_.get(), |
| SSLClientSocketContext( |
| session_deps_.cert_verifier.get(), |
| session_deps_.channel_id_service.get(), |
| session_deps_.transport_security_state.get(), |
| session_deps_.cert_transparency_verifier.get(), |
| session_deps_.ct_policy_enforcer.get(), |
| nullptr /* ssl_session_cache_arg */, |
| std::string() /* ssl_session_cache_shart_arg */), |
| nullptr /* socket_performance_watcher_factory */, |
| &network_quality_estimator_, nullptr /* net_log */, |
| nullptr /* websocket_endpoint_lock_manager */), |
| std::move(http_proxy_socket_params), delegate); |
| } |
| |
| void InitProxyDelegate() { |
| proxy_delegate_ = std::make_unique<TestProxyDelegate>(); |
| } |
| |
| void Initialize(base::span<const MockRead> reads, |
| base::span<const MockWrite> writes, |
| base::span<const MockRead> spdy_reads, |
| base::span<const MockWrite> spdy_writes, |
| IoMode connect_and_ssl_io_mode) { |
| if (GetParam() == SPDY) { |
| data_ = std::make_unique<SequencedSocketData>(spdy_reads, spdy_writes); |
| } else { |
| data_ = std::make_unique<SequencedSocketData>(reads, writes); |
| } |
| |
| data_->set_connect_data(MockConnect(connect_and_ssl_io_mode, OK)); |
| |
| socket_factory_.AddSocketDataProvider(data_.get()); |
| |
| if (GetParam() != HTTP) { |
| ssl_data_ = |
| std::make_unique<SSLSocketDataProvider>(connect_and_ssl_io_mode, OK); |
| if (GetParam() == SPDY) { |
| InitializeSpdySsl(ssl_data_.get()); |
| } |
| socket_factory_.AddSSLSocketDataProvider(ssl_data_.get()); |
| } |
| } |
| |
| void InitializeSpdySsl(SSLSocketDataProvider* ssl_data) { |
| ssl_data->next_proto = kProtoHTTP2; |
| } |
| |
| TransportClientSocketPool* transport_socket_pool() { |
| return &transport_socket_pool_; |
| } |
| TransportClientSocketPool* ssl_socket_pool() { return &ssl_socket_pool_; } |
| |
| base::TimeDelta GetProxyConnectionTimeout() { |
| // Doesn't actually matter whether or not this is for a tunnel - the |
| // connection timeout is the same, though it probably shouldn't be the same, |
| // since tunnels need an extra round trip. |
| return HttpProxyConnectJob::ConnectionTimeout( |
| *CreateParams(true /* tunnel */), &network_quality_estimator_); |
| } |
| |
| protected: |
| std::unique_ptr<TestProxyDelegate> proxy_delegate_; |
| |
| std::unique_ptr<SSLSocketDataProvider> ssl_data_; |
| std::unique_ptr<SequencedSocketData> data_; |
| MockClientSocketFactory socket_factory_; |
| SpdySessionDependencies session_deps_; |
| |
| TestNetworkQualityEstimator network_quality_estimator_; |
| |
| TransportClientSocketPool transport_socket_pool_; |
| TransportClientSocketPool ssl_socket_pool_; |
| |
| std::unique_ptr<HttpNetworkSession> session_; |
| |
| base::HistogramTester histogram_tester_; |
| |
| base::FieldTrialList field_trial_list_; |
| |
| SpdyTestUtil spdy_util_; |
| |
| TestCompletionCallback callback_; |
| }; |
| |
| // All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY) |
| // and SPDY. |
| INSTANTIATE_TEST_SUITE_P(HttpProxyType, |
| HttpProxyConnectJobTest, |
| ::testing::Values(HTTP, HTTPS, SPDY)); |
| |
| TEST_P(HttpProxyConnectJobTest, NoTunnel) { |
| InitProxyDelegate(); |
| int loop_iterations = 0; |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| Initialize(base::span<MockRead>(), base::span<MockWrite>(), |
| base::span<MockRead>(), base::span<MockWrite>(), io_mode); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForHttpRequest(&test_delegate); |
| test_delegate.StartJobExpectingResult(connect_job.get(), OK, |
| io_mode == SYNCHRONOUS); |
| EXPECT_FALSE(proxy_delegate_->on_before_tunnel_request_called()); |
| |
| ++loop_iterations; |
| bool is_secure_proxy = GetParam() == HTTPS || GetParam() == SPDY; |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Insecure.Success", |
| is_secure_proxy ? 0 : loop_iterations); |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Secure.Success", |
| is_secure_proxy ? loop_iterations : 0); |
| } |
| } |
| |
| // Pauses an HttpProxyConnectJob at various states, and check the value of |
| // HasEstablishedConnection(). |
| TEST_P(HttpProxyConnectJobTest, HasEstablishedConnectionNoTunnel) { |
| session_deps_.host_resolver->set_ondemand_mode(true); |
| |
| SequencedSocketData data; |
| data.set_connect_data(MockConnect(ASYNC, OK)); |
| socket_factory_.AddSocketDataProvider(&data); |
| |
| // Set up SSL, if needed. |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| switch (GetParam()) { |
| case HTTP: |
| // No SSL needed. |
| break; |
| case HTTPS: |
| // SSL negotiation is the last step in non-tunnel connections over HTTPS |
| // proxies, so pause there, to check the final state before completion. |
| ssl_data = SSLSocketDataProvider(SYNCHRONOUS, ERR_IO_PENDING); |
| socket_factory_.AddSSLSocketDataProvider(&ssl_data); |
| break; |
| case SPDY: |
| InitializeSpdySsl(&ssl_data); |
| socket_factory_.AddSSLSocketDataProvider(&ssl_data); |
| break; |
| } |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForHttpRequest(&test_delegate); |
| |
| // Connecting should run until the request hits the HostResolver. |
| EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests()); |
| EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState()); |
| EXPECT_FALSE(connect_job->HasEstablishedConnection()); |
| |
| // Once the HostResolver completes, the job should start establishing a |
| // connection, which will complete asynchronously. |
| session_deps_.host_resolver->ResolveOnlyRequestNow(); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_EQ(LOAD_STATE_CONNECTING, connect_job->GetLoadState()); |
| EXPECT_FALSE(connect_job->HasEstablishedConnection()); |
| |
| switch (GetParam()) { |
| case HTTP: |
| case SPDY: |
| // Connection completes. Since no tunnel is established, the socket is |
| // returned immediately, and HasEstablishedConnection() is only specified |
| // to work before the ConnectJob completes. |
| EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); |
| break; |
| case HTTPS: |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_EQ(LOAD_STATE_SSL_HANDSHAKE, connect_job->GetLoadState()); |
| EXPECT_TRUE(connect_job->HasEstablishedConnection()); |
| |
| // Unfortunately, there's no API to advance the paused SSL negotiation, |
| // so just end the test here. |
| } |
| } |
| |
| // Pauses an HttpProxyConnectJob at various states, and check the value of |
| // HasEstablishedConnection(). |
| TEST_P(HttpProxyConnectJobTest, HasEstablishedConnectionTunnel) { |
| session_deps_.host_resolver->set_ondemand_mode(true); |
| |
| // HTTP proxy CONNECT request / response, with a pause during the read. |
| MockWrite http1_writes[] = { |
| MockWrite(ASYNC, 0, |
| "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" |
| "Host: www.endpoint.test:443\r\n" |
| "Proxy-Connection: keep-alive\r\n\r\n"), |
| }; |
| MockRead http1_reads[] = { |
| // Pause at first read. |
| MockRead(ASYNC, ERR_IO_PENDING, 1), |
| MockRead(ASYNC, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"), |
| }; |
| SequencedSocketData http1_data(http1_reads, http1_writes); |
| http1_data.set_connect_data(MockConnect(ASYNC, OK)); |
| |
| // SPDY proxy CONNECT request / response, with a pause during the read. |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect( |
| nullptr, 0, 1, DEFAULT_PRIORITY, HostPortPair(kEndpointHost, 443))); |
| MockWrite spdy_writes[] = {CreateMockWrite(req, 0)}; |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead spdy_reads[] = { |
| // Pause at first read. |
| MockRead(ASYNC, ERR_IO_PENDING, 1), |
| CreateMockRead(resp, 2, ASYNC), |
| MockRead(ASYNC, 0, 3), |
| }; |
| SequencedSocketData spdy_data(spdy_reads, spdy_writes); |
| spdy_data.set_connect_data(MockConnect(ASYNC, OK)); |
| |
| // Will point to either the HTTP/1.x or SPDY data, depending on GetParam(). |
| SequencedSocketData* sequenced_data = nullptr; |
| |
| SSLSocketDataProvider ssl_data(ASYNC, OK); |
| |
| switch (GetParam()) { |
| case HTTP: |
| sequenced_data = &http1_data; |
| break; |
| case HTTPS: |
| sequenced_data = &http1_data; |
| socket_factory_.AddSSLSocketDataProvider(&ssl_data); |
| break; |
| case SPDY: |
| sequenced_data = &spdy_data; |
| InitializeSpdySsl(&ssl_data); |
| socket_factory_.AddSSLSocketDataProvider(&ssl_data); |
| break; |
| } |
| |
| socket_factory_.AddSocketDataProvider(sequenced_data); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate); |
| |
| // Connecting should run until the request hits the HostResolver. |
| EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests()); |
| EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState()); |
| EXPECT_FALSE(connect_job->HasEstablishedConnection()); |
| |
| // Once the HostResolver completes, the job should start establishing a |
| // connection, which will complete asynchronously. |
| session_deps_.host_resolver->ResolveOnlyRequestNow(); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_EQ(LOAD_STATE_CONNECTING, connect_job->GetLoadState()); |
| EXPECT_FALSE(connect_job->HasEstablishedConnection()); |
| |
| // Run until the socket starts reading the proxy's handshake response. |
| sequenced_data->RunUntilPaused(); |
| EXPECT_FALSE(test_delegate.has_result()); |
| EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, connect_job->GetLoadState()); |
| EXPECT_TRUE(connect_job->HasEstablishedConnection()); |
| |
| // Finish the read, and run the job until it's complete. |
| sequenced_data->Resume(); |
| EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, ProxyDelegateExtraHeaders) { |
| // TODO(https://crbug.com/926427): The ProxyDelegate API is currently broken |
| // in the SPDY case. |
| if (GetParam() == SPDY) |
| return; |
| |
| InitProxyDelegate(); |
| |
| ProxyServer proxy_server( |
| GetParam() == HTTP ? ProxyServer::SCHEME_HTTP : ProxyServer::SCHEME_HTTPS, |
| HostPortPair(GetParam() == HTTP ? kHttpProxyHost : kHttpsProxyHost, |
| GetParam() == HTTP ? 80 : 443)); |
| std::string request = |
| "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" |
| "Host: www.endpoint.test:443\r\n" |
| "Proxy-Connection: keep-alive\r\n" |
| "Foo: " + |
| proxy_server.ToURI() + "\r\n\r\n"; |
| MockWrite writes[] = { |
| MockWrite(ASYNC, 0, request.c_str()), |
| }; |
| |
| const char kResponseHeaderName[] = "Foo"; |
| const char kResponseHeaderValue[] = "Response"; |
| std::string response = base::StringPrintf( |
| "HTTP/1.1 200 Connection Established\r\n" |
| "%s: %s\r\n\r\n", |
| kResponseHeaderName, kResponseHeaderValue); |
| MockRead reads[] = { |
| MockRead(ASYNC, 1, response.c_str()), |
| }; |
| |
| Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>(), |
| ASYNC); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate); |
| test_delegate.StartJobExpectingResult(connect_job.get(), OK, |
| false /* expect_sync_result */); |
| proxy_delegate_->VerifyOnHttp1TunnelHeadersReceived( |
| proxy_server, kResponseHeaderName, kResponseHeaderValue); |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, RequestPriority) { |
| // Make request hang during host resolution, so can observe priority there. |
| session_deps_.host_resolver->set_ondemand_mode(true); |
| |
| // Needed to destroy the ConnectJob in the nested socket pools. |
| // TODO(https://crbug.com/927088): Remove this once there are no nested socket |
| // pools. |
| session_deps_.host_resolver->rules()->AddSimulatedFailure(kHttpProxyHost); |
| session_deps_.host_resolver->rules()->AddSimulatedFailure(kHttpsProxyHost); |
| |
| for (int initial_priority = MINIMUM_PRIORITY; |
| initial_priority <= MAXIMUM_PRIORITY; ++initial_priority) { |
| SCOPED_TRACE(initial_priority); |
| for (int new_priority = MINIMUM_PRIORITY; new_priority <= MAXIMUM_PRIORITY; |
| ++new_priority) { |
| SCOPED_TRACE(new_priority); |
| if (initial_priority == new_priority) |
| continue; |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = CreateConnectJobForHttpRequest( |
| &test_delegate, static_cast<RequestPriority>(initial_priority)); |
| EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); |
| EXPECT_FALSE(test_delegate.has_result()); |
| |
| MockHostResolverBase* host_resolver = session_deps_.host_resolver.get(); |
| int request_id = host_resolver->num_resolve(); |
| EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id)); |
| |
| connect_job->ChangePriority(static_cast<RequestPriority>(new_priority)); |
| EXPECT_EQ(new_priority, host_resolver->request_priority(request_id)); |
| |
| connect_job->ChangePriority( |
| static_cast<RequestPriority>(initial_priority)); |
| EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id)); |
| |
| // Complete the resolution, which should result in destroying the |
| // connecting socket. Can't just delete the ConnectJob, since that won't |
| // destroy the ConnectJobs in the underlying pools. |
| host_resolver->ResolveAllPending(); |
| EXPECT_THAT(test_delegate.WaitForResult(), |
| test::IsError(ERR_PROXY_CONNECTION_FAILED)); |
| } |
| } |
| } |
| |
| // Make sure that HttpProxyConnectJob passes on its priority to its |
| // SPDY session's socket request on Init, and on SetPriority. |
| TEST_P(HttpProxyConnectJobTest, SetSpdySessionSocketRequestPriority) { |
| if (GetParam() != SPDY) |
| return; |
| session_deps_.host_resolver->set_synchronous_mode(true); |
| |
| // The SPDY CONNECT request should have a priority of HIGHEST. |
| spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect( |
| nullptr /* extra_headers */, 0 /* extra_header_count */, |
| 1 /* stream_id */, HIGHEST, HostPortPair(kEndpointHost, 443))); |
| MockWrite spdy_writes[] = {CreateMockWrite(req, 0, ASYNC)}; |
| spdy::SpdySerializedFrame resp( |
| spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); |
| MockRead spdy_reads[] = {CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, 0, 2)}; |
| |
| Initialize(base::span<MockRead>(), base::span<MockWrite>(), spdy_reads, |
| spdy_writes, SYNCHRONOUS); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate, MEDIUM); |
| EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); |
| EXPECT_FALSE(test_delegate.has_result()); |
| |
| connect_job->ChangePriority(HIGHEST); |
| |
| // Wait for tunnel to be established. If the frame has a MEDIUM priority |
| // instead of highest, the written data will not match what is expected, and |
| // the test will fail. |
| EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, TCPError) { |
| // SPDY and HTTPS are identical, as they only differ once a connection is |
| // established. |
| if (GetParam() == SPDY) |
| return; |
| int loop_iterations = 0; |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| SequencedSocketData data; |
| data.set_connect_data(MockConnect(io_mode, ERR_CONNECTION_CLOSED)); |
| socket_factory_.AddSocketDataProvider(&data); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForHttpRequest(&test_delegate); |
| test_delegate.StartJobExpectingResult( |
| connect_job.get(), ERR_PROXY_CONNECTION_FAILED, io_mode == SYNCHRONOUS); |
| |
| bool is_secure_proxy = GetParam() == HTTPS; |
| ++loop_iterations; |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Insecure.Error", |
| is_secure_proxy ? 0 : loop_iterations); |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Secure.Error", |
| is_secure_proxy ? loop_iterations : 0); |
| } |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, SSLError) { |
| if (GetParam() == HTTP) |
| return; |
| int loop_iterations = 0; |
| |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| SequencedSocketData data; |
| data.set_connect_data(MockConnect(io_mode, OK)); |
| socket_factory_.AddSocketDataProvider(&data); |
| |
| SSLSocketDataProvider ssl_data(io_mode, ERR_CERT_AUTHORITY_INVALID); |
| if (GetParam() == SPDY) { |
| InitializeSpdySsl(&ssl_data); |
| } |
| socket_factory_.AddSSLSocketDataProvider(&ssl_data); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate); |
| test_delegate.StartJobExpectingResult(connect_job.get(), |
| ERR_PROXY_CERTIFICATE_INVALID, |
| io_mode == SYNCHRONOUS); |
| |
| ++loop_iterations; |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Secure.Error", loop_iterations); |
| histogram_tester_.ExpectTotalCount( |
| "Net.HttpProxy.ConnectLatency.Insecure.Error", 0); |
| } |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, TunnelUnexpectedClose) { |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| MockWrite writes[] = { |
| MockWrite(io_mode, 0, |
| "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" |
| "Host: www.endpoint.test:443\r\n" |
| "Proxy-Connection: keep-alive\r\n\r\n"), |
| }; |
| MockRead reads[] = { |
| MockRead(io_mode, 1, "HTTP/1.1 200 Conn"), |
| MockRead(io_mode, ERR_CONNECTION_CLOSED, 2), |
| }; |
| spdy::SpdySerializedFrame req(SpdyTestUtil().ConstructSpdyConnect( |
| nullptr /*extra_headers */, 0 /*extra_header_count */, |
| 1 /* stream_id */, DEFAULT_PRIORITY, HostPortPair(kEndpointHost, 443))); |
| MockWrite spdy_writes[] = {CreateMockWrite(req, 0, io_mode)}; |
| // Sync reads don't really work with SPDY, since it constantly reads from |
| // the socket. |
| MockRead spdy_reads[] = { |
| MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1), |
| }; |
| |
| Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate); |
| |
| if (GetParam() == SPDY) { |
| // SPDY cannot process a headers block unless it's complete and so it |
| // returns ERR_CONNECTION_CLOSED in this case. SPDY also doesn't return |
| // this failure synchronously. |
| test_delegate.StartJobExpectingResult(connect_job.get(), |
| ERR_CONNECTION_CLOSED, |
| false /* expect_sync_result */); |
| } else { |
| test_delegate.StartJobExpectingResult(connect_job.get(), |
| ERR_RESPONSE_HEADERS_TRUNCATED, |
| io_mode == SYNCHRONOUS); |
| } |
| } |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, Tunnel1xxResponse) { |
| // Tests that 1xx responses are rejected for a CONNECT request. |
| if (GetParam() == SPDY) { |
| // SPDY doesn't have 1xx responses. |
| return; |
| } |
| |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| MockWrite writes[] = { |
| MockWrite(io_mode, 0, |
| "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" |
| "Host: www.endpoint.test:443\r\n" |
| "Proxy-Connection: keep-alive\r\n\r\n"), |
| }; |
| MockRead reads[] = { |
| MockRead(io_mode, 1, "HTTP/1.1 100 Continue\r\n\r\n"), |
| MockRead(io_mode, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"), |
| }; |
| |
| Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>(), |
| io_mode); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate); |
| test_delegate.StartJobExpectingResult(connect_job.get(), |
| ERR_TUNNEL_CONNECTION_FAILED, |
| io_mode == SYNCHRONOUS); |
| } |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, TunnelSetupError) { |
| for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { |
| SCOPED_TRACE(io_mode); |
| session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); |
| |
| MockWrite writes[] = { |
| MockWrite(io_mode, 0, |
| "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" |
| "Host: www.endpoint.test:443\r\n" |
| "Proxy-Connection: keep-alive\r\n\r\n"), |
| }; |
| MockRead reads[] = { |
| MockRead(io_mode, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"), |
| }; |
| SpdyTestUtil spdy_util; |
| spdy::SpdySerializedFrame req(spdy_util.ConstructSpdyConnect( |
| nullptr /* extra_headers */, 0 /* extra_header_count */, |
| 1 /* stream_id */, LOW, HostPortPair("www.endpoint.test", 443))); |
| spdy::SpdySerializedFrame rst( |
| spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); |
| MockWrite spdy_writes[] = { |
| CreateMockWrite(req, 0, io_mode), |
| CreateMockWrite(rst, 2, io_mode), |
| }; |
| spdy::SpdySerializedFrame resp(spdy_util.ConstructSpdyReplyError(1)); |
| // Sync reads don't really work with SPDY, since it constantly reads from |
| // the socket. |
| MockRead spdy_reads[] = { |
| CreateMockRead(resp, 1, ASYNC), |
| MockRead(ASYNC, OK, 3), |
| }; |
| |
| Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); |
| |
| TestConnectJobDelegate test_delegate; |
| std::unique_ptr<ConnectJob> connect_job = |
| CreateConnectJobForTunnel(&test_delegate, LOW); |
| test_delegate.StartJobExpectingResult( |
| connect_job.get(), ERR_TUNNEL_CONNECTION_FAILED, |
| io_mode == SYNCHRONOUS && GetParam() != SPDY); |
| // Need to close the session to prevent reuse in the next loop iteration. |
| session_->spdy_session_pool()->CloseAllSessions(); |
| } |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMin) { |
| // Set RTT estimate to a low value. |
| base::TimeDelta rtt_estimate = base::TimeDelta::FromMilliseconds(1); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| |
| EXPECT_LE(base::TimeDelta(), GetProxyConnectionTimeout()); |
| |
| // Test against a large value. |
| EXPECT_GE(base::TimeDelta::FromMinutes(10), GetProxyConnectionTimeout()); |
| |
| #if (defined(OS_ANDROID) || defined(OS_IOS)) |
| EXPECT_EQ(base::TimeDelta::FromSeconds(8), GetProxyConnectionTimeout()); |
| #else |
| EXPECT_EQ(base::TimeDelta::FromSeconds(30), GetProxyConnectionTimeout()); |
| #endif |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMax) { |
| // Set RTT estimate to a high value. |
| base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(100); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| |
| EXPECT_LE(base::TimeDelta(), GetProxyConnectionTimeout()); |
| |
| // Test against a large value. |
| EXPECT_GE(base::TimeDelta::FromMinutes(10), GetProxyConnectionTimeout()); |
| |
| #if (defined(OS_ANDROID) || defined(OS_IOS)) |
| EXPECT_EQ(base::TimeDelta::FromSeconds(30), GetProxyConnectionTimeout()); |
| #else |
| EXPECT_EQ(base::TimeDelta::FromSeconds(60), GetProxyConnectionTimeout()); |
| #endif |
| } |
| |
| // Tests the connection timeout values when the field trial parameters are |
| // specified. |
| TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutWithExperiment) { |
| // Timeout should be kMultiplier times the HTTP RTT estimate. |
| const int kMultiplier = 4; |
| const base::TimeDelta kMinTimeout = base::TimeDelta::FromSeconds(8); |
| const base::TimeDelta kMaxTimeout = base::TimeDelta::FromSeconds(20); |
| |
| InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier, |
| kMinTimeout, kMaxTimeout); |
| EXPECT_LE(base::TimeDelta(), GetProxyConnectionTimeout()); |
| |
| base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(4); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| base::TimeDelta expected_connection_timeout = kMultiplier * rtt_estimate; |
| EXPECT_EQ(expected_connection_timeout, GetProxyConnectionTimeout()); |
| |
| // Connection timeout should not exceed kMaxTimeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(25); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMaxTimeout, GetProxyConnectionTimeout()); |
| |
| // Connection timeout should not be less than kMinTimeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(0); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMinTimeout, GetProxyConnectionTimeout()); |
| } |
| |
| // Tests the connection timeout values when the field trial parameters are |
| // specified. |
| TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutExperimentDifferentParams) { |
| // Timeout should be kMultiplier times the HTTP RTT estimate. |
| const int kMultiplier = 3; |
| const base::TimeDelta kMinTimeout = base::TimeDelta::FromSeconds(2); |
| const base::TimeDelta kMaxTimeout = base::TimeDelta::FromSeconds(30); |
| |
| InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier, |
| kMinTimeout, kMaxTimeout); |
| EXPECT_LE(base::TimeDelta(), GetProxyConnectionTimeout()); |
| |
| base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(2); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMultiplier * rtt_estimate, GetProxyConnectionTimeout()); |
| |
| // A change in RTT estimate should also change the connection timeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(7); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMultiplier * rtt_estimate, GetProxyConnectionTimeout()); |
| |
| // Connection timeout should not exceed kMaxTimeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(35); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMaxTimeout, GetProxyConnectionTimeout()); |
| |
| // Connection timeout should not be less than kMinTimeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(0); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_EQ(kMinTimeout, GetProxyConnectionTimeout()); |
| } |
| |
| TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutWithConnectionProperty) { |
| const int kSecureMultiplier = 3; |
| const int kNonSecureMultiplier = 5; |
| const base::TimeDelta kMinTimeout = base::TimeDelta::FromSeconds(2); |
| const base::TimeDelta kMaxTimeout = base::TimeDelta::FromSeconds(30); |
| |
| InitAdaptiveTimeoutFieldTrialWithParams( |
| false, kSecureMultiplier, kNonSecureMultiplier, kMinTimeout, kMaxTimeout); |
| |
| const base::TimeDelta kRttEstimate = base::TimeDelta::FromSeconds(2); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(kRttEstimate); |
| // By default, connection timeout should return the timeout for secure |
| // proxies. |
| if (GetParam() != HTTP) { |
| EXPECT_EQ(kSecureMultiplier * kRttEstimate, GetProxyConnectionTimeout()); |
| } else { |
| EXPECT_EQ(kNonSecureMultiplier * kRttEstimate, GetProxyConnectionTimeout()); |
| } |
| } |
| |
| // Tests the connection timeout values when the field trial parameters are not |
| // specified. |
| TEST_P(HttpProxyConnectJobTest, ProxyPoolTimeoutWithExperimentDefaultParams) { |
| InitAdaptiveTimeoutFieldTrialWithParams(true, 0, 0, base::TimeDelta(), |
| base::TimeDelta()); |
| EXPECT_LE(base::TimeDelta(), GetProxyConnectionTimeout()); |
| |
| // Timeout should be |http_rtt_multiplier| times the HTTP RTT |
| // estimate. |
| base::TimeDelta rtt_estimate = base::TimeDelta::FromMilliseconds(10); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| // Connection timeout should not be less than the HTTP RTT estimate. |
| EXPECT_LE(rtt_estimate, GetProxyConnectionTimeout()); |
| |
| // A change in RTT estimate should also change the connection timeout. |
| rtt_estimate = base::TimeDelta::FromSeconds(10); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| // Connection timeout should not be less than the HTTP RTT estimate. |
| EXPECT_LE(rtt_estimate, GetProxyConnectionTimeout()); |
| |
| // Set RTT to a very large value. |
| rtt_estimate = base::TimeDelta::FromMinutes(60); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_GT(rtt_estimate, GetProxyConnectionTimeout()); |
| |
| // Set RTT to a very small value. |
| rtt_estimate = base::TimeDelta::FromSeconds(0); |
| network_quality_estimator_.SetStartTimeNullHttpRtt(rtt_estimate); |
| EXPECT_LT(rtt_estimate, GetProxyConnectionTimeout()); |
| } |
| |
| // TODO(https://crbug.com/927096): It would be nice to also test the timeouts in |
| // HttpProxyClientSocketPool. |
| |
| } // namespace net |