// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/http/http_proxy_connect_job.h" #include #include #include #include #include "base/metrics/field_trial.h" #include "base/metrics/field_trial_param_associator.h" #include "base/metrics/field_trial_params.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 "base/test/task_environment.h" #include "base/time/time.h" #include "build/build_config.h" #include "net/base/host_port_pair.h" #include "net/base/network_anonymization_key.h" #include "net/base/proxy_string_util.h" #include "net/base/test_proxy_delegate.h" #include "net/dns/mock_host_resolver.h" #include "net/dns/public/secure_dns_policy.h" #include "net/http/http_network_session.h" #include "net/http/http_response_headers.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_connect_job.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_data_directory.h" #include "net/test/test_with_task_environment.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/scheme_host_port.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, public WithTaskEnvironment { protected: HttpProxyConnectJobTest() : WithTaskEnvironment( base::test::TaskEnvironment::TimeSource::MOCK_TIME) { // Used a mock HostResolver that does not have a cache. session_deps_.host_resolver = std::make_unique( /*default_result=*/MockHostResolverBase::RuleResolver:: GetLocalhostResult()); network_quality_estimator_ = std::make_unique(); session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); InitCommonConnectJobParams(); } ~HttpProxyConnectJobTest() override { // 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 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 CreateHttpProxyParams( SecureDnsPolicy secure_dns_policy) const { if (GetParam() != HTTP) return nullptr; return base::MakeRefCounted( HostPortPair(kHttpProxyHost, 80), NetworkAnonymizationKey(), secure_dns_policy, OnHostResolutionCallback(), /*supported_alpns=*/base::flat_set()); } scoped_refptr CreateHttpsProxyParams( SecureDnsPolicy secure_dns_policy) const { if (GetParam() == HTTP) return nullptr; return base::MakeRefCounted( base::MakeRefCounted( HostPortPair(kHttpsProxyHost, 443), NetworkAnonymizationKey(), secure_dns_policy, OnHostResolutionCallback(), /*supported_alpns=*/base::flat_set()), nullptr, nullptr, HostPortPair(kHttpsProxyHost, 443), SSLConfig(), PRIVACY_MODE_DISABLED, NetworkAnonymizationKey()); } // Returns a correctly constructed HttpProxyParams for the HTTP or HTTPS // proxy. scoped_refptr CreateParams( bool tunnel, SecureDnsPolicy secure_dns_policy) { return base::MakeRefCounted( CreateHttpProxyParams(secure_dns_policy), CreateHttpsProxyParams(secure_dns_policy), false /* is_quic */, HostPortPair(kEndpointHost, tunnel ? 443 : 80), tunnel, TRAFFIC_ANNOTATION_FOR_TESTS, NetworkAnonymizationKey()); } std::unique_ptr CreateConnectJobForHttpRequest( ConnectJob::Delegate* delegate, RequestPriority priority = DEFAULT_PRIORITY, SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) { return CreateConnectJob(CreateParams(false /* tunnel */, secure_dns_policy), delegate, priority); } std::unique_ptr CreateConnectJobForTunnel( ConnectJob::Delegate* delegate, RequestPriority priority = DEFAULT_PRIORITY, SecureDnsPolicy secure_dns_policy = SecureDnsPolicy::kAllow) { return CreateConnectJob(CreateParams(true /* tunnel */, secure_dns_policy), delegate, priority); } std::unique_ptr CreateConnectJob( scoped_refptr http_proxy_socket_params, ConnectJob::Delegate* delegate, RequestPriority priority) { return std::make_unique( priority, SocketTag(), common_connect_job_params_.get(), std::move(http_proxy_socket_params), delegate, nullptr /* net_log */); } // This may only be called at the start of the test, before any ConnectJobs // have been created. void InitProxyDelegate() { proxy_delegate_ = std::make_unique(); InitCommonConnectJobParams(); } // This may only be called at the start of the test, before any ConnectJobs // have been created. void InitCommonConnectJobParams() { common_connect_job_params_ = std::make_unique( session_->CreateCommonConnectJobParams()); // TODO(mmenke): Consider reworking this so it can be done through // |session_deps_|. common_connect_job_params_->proxy_delegate = proxy_delegate_.get(); common_connect_job_params_->network_quality_estimator = network_quality_estimator_.get(); } void Initialize(base::span reads, base::span writes, base::span spdy_reads, base::span spdy_writes, IoMode connect_and_ssl_io_mode) { if (GetParam() == SPDY) { data_ = std::make_unique(spdy_reads, spdy_writes); } else { data_ = std::make_unique(reads, writes); } data_->set_connect_data(MockConnect(connect_and_ssl_io_mode, OK)); session_deps_.socket_factory->AddSocketDataProvider(data_.get()); if (GetParam() != HTTP) { // Keep the old ssl_data in case there is a draining socket. old_ssl_data_.swap(ssl_data_); ssl_data_ = std::make_unique(connect_and_ssl_io_mode, OK); if (GetParam() == SPDY) { InitializeSpdySsl(ssl_data_.get()); } session_deps_.socket_factory->AddSSLSocketDataProvider(ssl_data_.get()); } } void InitializeSpdySsl(SSLSocketDataProvider* ssl_data) { ssl_data->next_proto = kProtoHTTP2; } // Return the timeout for establishing the lower layer connection. i.e., for // an HTTP proxy, the TCP connection timeout, and for an HTTPS proxy, the // TCP+SSL connection timeout. In many cases, this will return the return // value of the "AlternateNestedConnectionTimeout()". base::TimeDelta GetNestedConnectionTimeout() { base::TimeDelta normal_nested_connection_timeout = TransportConnectJob::ConnectionTimeout(); if (GetParam() != HTTP) normal_nested_connection_timeout += SSLConnectJob::HandshakeTimeoutForTesting(); // 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. base::TimeDelta alternate_connection_timeout = HttpProxyConnectJob::AlternateNestedConnectionTimeout( *CreateParams(true /* tunnel */, SecureDnsPolicy::kAllow), network_quality_estimator_.get()); // If there's an alternate connection timeout, and it's less than the // standard TCP+SSL timeout (Which is also applied by the nested connect // jobs), return the alternate connection timeout. Otherwise, return the // normal timeout. if (!alternate_connection_timeout.is_zero() && alternate_connection_timeout < normal_nested_connection_timeout) { return alternate_connection_timeout; } return normal_nested_connection_timeout; } protected: std::unique_ptr proxy_delegate_; std::unique_ptr ssl_data_; std::unique_ptr old_ssl_data_; std::unique_ptr data_; SpdySessionDependencies session_deps_; std::unique_ptr network_quality_estimator_; std::unique_ptr session_; SpdyTestUtil spdy_util_; TestCompletionCallback callback_; std::unique_ptr common_connect_job_params_; }; // 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(); for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { SCOPED_TRACE(io_mode); session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); base::HistogramTester histogram_tester; Initialize(base::span(), base::span(), base::span(), base::span(), io_mode); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForHttpRequest(&test_delegate); test_delegate.StartJobExpectingResult(connect_job.get(), OK, io_mode == SYNCHRONOUS); EXPECT_FALSE(proxy_delegate_->on_before_tunnel_request_called()); // Proxies should not set any DNS aliases. EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty()); bool is_secure_proxy = GetParam() == HTTPS || GetParam() == SPDY; histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Insecure.Success", is_secure_proxy ? 0 : 1); histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Secure.Success", is_secure_proxy ? 1 : 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)); session_deps_.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); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); break; case SPDY: InitializeSpdySsl(&ssl_data); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); break; } TestConnectJobDelegate test_delegate; std::unique_ptr 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, HttpProxyConnectJob::kH2QuicTunnelPriority, 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); ssl_data.ssl_info.cert = ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"); ASSERT_TRUE(ssl_data.ssl_info.cert); switch (GetParam()) { case HTTP: sequenced_data = &http1_data; break; case HTTPS: sequenced_data = &http1_data; ssl_data.next_proto = NextProto::kProtoHTTP11; session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); break; case SPDY: sequenced_data = &spdy_data; InitializeSpdySsl(&ssl_data); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); break; } session_deps_.socket_factory->AddSocketDataProvider(sequenced_data); TestConnectJobDelegate test_delegate; std::unique_ptr 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()); // Proxies should not set any DNS aliases. EXPECT_TRUE(test_delegate.socket()->GetDnsAliases().empty()); // Although the underlying proxy connection may use TLS or negotiate ALPN, the // tunnel itself is a TCP connection to the origin and should not report these // values. SSLInfo ssl_info; EXPECT_FALSE(test_delegate.socket()->GetSSLInfo(&ssl_info)); EXPECT_FALSE(test_delegate.socket()->WasAlpnNegotiated()); EXPECT_EQ(test_delegate.socket()->GetNegotiatedProtocol(), NextProto::kProtoUnknown); } TEST_P(HttpProxyConnectJobTest, ProxyDelegateExtraHeaders) { InitProxyDelegate(); ProxyServer proxy_server( GetParam() == HTTP ? ProxyServer::SCHEME_HTTP : ProxyServer::SCHEME_HTTPS, HostPortPair(GetParam() == HTTP ? kHttpProxyHost : kHttpsProxyHost, GetParam() == HTTP ? 80 : 443)); std::string proxy_server_uri = ProxyServerToProxyUri(proxy_server); std::string http1_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_uri + "\r\n\r\n"; MockWrite writes[] = { MockWrite(ASYNC, 0, http1_request.c_str()), }; const char kResponseHeaderName[] = "foo"; const char kResponseHeaderValue[] = "Response"; std::string http1_response = base::StringPrintf( "HTTP/1.1 200 Connection Established\r\n" "%s: %s\r\n\r\n", kResponseHeaderName, kResponseHeaderValue); MockRead reads[] = { MockRead(ASYNC, 1, http1_response.c_str()), }; const char* const kExtraRequestHeaders[] = { "foo", proxy_server_uri.c_str(), }; const char* const kExtraResponseHeaders[] = { kResponseHeaderName, kResponseHeaderValue, }; spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect( kExtraRequestHeaders, std::size(kExtraRequestHeaders) / 2, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); MockWrite spdy_writes[] = {CreateMockWrite(req, 0)}; spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyGetReply( kExtraResponseHeaders, std::size(kExtraResponseHeaders) / 2, 1)); MockRead spdy_reads[] = { CreateMockRead(resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), }; Initialize(reads, writes, spdy_reads, spdy_writes, ASYNC); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); test_delegate.StartJobExpectingResult(connect_job.get(), OK, false /* expect_sync_result */); proxy_delegate_->VerifyOnTunnelHeadersReceived( proxy_server, kResponseHeaderName, kResponseHeaderValue); } // Test the case where auth credentials are not cached. TEST_P(HttpProxyConnectJobTest, NeedAuth) { 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"), MockWrite(io_mode, 5, "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" "Host: www.endpoint.test:443\r\n" "Proxy-Connection: keep-alive\r\n" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { // No credentials. MockRead(io_mode, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"), MockRead(io_mode, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"), MockRead(io_mode, 3, "Content-Length: 10\r\n\r\n"), MockRead(io_mode, 4, "0123456789"), MockRead(io_mode, 6, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; SpdyTestUtil spdy_util; spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect( nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); spdy::SpdySerializedFrame rst( spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); spdy_util.UpdateWithStreamDestruction(1); // After calling trans.RestartWithAuth(), this is the request we should // be issuing -- the final header line contains the credentials. const char* const kSpdyAuthCredentials[] = { "proxy-authorization", "Basic Zm9vOmJhcg==", }; spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect( kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); MockWrite spdy_writes[] = { CreateMockWrite(connect, 0, io_mode), CreateMockWrite(rst, 2, io_mode), CreateMockWrite(connect2, 3, io_mode), }; // The proxy responds to the connect with a 407, using a persistent // connection. const char kAuthStatus[] = "407"; const char* const kAuthChallenge[] = { "proxy-authenticate", "Basic realm=\"MyRealm1\"", }; spdy::SpdySerializedFrame connect_auth_resp( spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge, std::size(kAuthChallenge) / 2, 1)); spdy::SpdySerializedFrame connect2_resp( spdy_util.ConstructSpdyGetReply(nullptr, 0, 3)); MockRead spdy_reads[] = { CreateMockRead(connect_auth_resp, 1, ASYNC), CreateMockRead(connect2_resp, 4, ASYNC), MockRead(ASYNC, OK, 5), }; Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); ASSERT_EQ(ERR_IO_PENDING, connect_job->Connect()); // Auth callback is always invoked asynchronously when a challenge is // observed. EXPECT_EQ(0, test_delegate.num_auth_challenges()); test_delegate.WaitForAuthChallenge(1); ASSERT_TRUE(test_delegate.auth_response_info().headers); EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code()); std::string proxy_authenticate; ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader( nullptr, "Proxy-Authenticate", &proxy_authenticate)); EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\""); ASSERT_TRUE(test_delegate.auth_controller()); EXPECT_FALSE(test_delegate.has_result()); test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar")); test_delegate.RunAuthCallback(); // Per API contract, the request can not complete synchronously. EXPECT_FALSE(test_delegate.has_result()); EXPECT_EQ(net::OK, test_delegate.WaitForResult()); EXPECT_EQ(1, test_delegate.num_auth_challenges()); // Close the H2 session to prevent reuse. if (GetParam() == SPDY) session_->CloseAllConnections(ERR_FAILED, "Very good reason"); // Also need to clear the auth cache before re-running the test. session_->http_auth_cache()->ClearAllEntries(); } } // Test the case where auth credentials are not cached and the first time // credentials are sent, they are rejected. TEST_P(HttpProxyConnectJobTest, NeedAuthTwice) { 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"), MockWrite(io_mode, 2, "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" "Host: www.endpoint.test:443\r\n" "Proxy-Connection: keep-alive\r\n" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), MockWrite(io_mode, 4, "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" "Host: www.endpoint.test:443\r\n" "Proxy-Connection: keep-alive\r\n" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { // No credentials. MockRead(io_mode, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n" "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n" "Content-Length: 0\r\n\r\n"), MockRead(io_mode, 3, "HTTP/1.1 407 Proxy Authentication Required\r\n" "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n" "Content-Length: 0\r\n\r\n"), MockRead(io_mode, 5, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; SpdyTestUtil spdy_util; spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect( nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); spdy::SpdySerializedFrame rst( spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); spdy_util.UpdateWithStreamDestruction(1); // After calling trans.RestartWithAuth(), this is the request we should // be issuing -- the final header line contains the credentials. const char* const kSpdyAuthCredentials[] = { "proxy-authorization", "Basic Zm9vOmJhcg==", }; spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect( kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); spdy::SpdySerializedFrame rst2( spdy_util.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL)); spdy_util.UpdateWithStreamDestruction(3); spdy::SpdySerializedFrame connect3(spdy_util.ConstructSpdyConnect( kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 5, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); MockWrite spdy_writes[] = { CreateMockWrite(connect, 0, io_mode), CreateMockWrite(rst, 2, io_mode), CreateMockWrite(connect2, 3, io_mode), CreateMockWrite(rst2, 5, io_mode), CreateMockWrite(connect3, 6, io_mode), }; // The proxy responds to the connect with a 407, using a persistent // connection. const char kAuthStatus[] = "407"; const char* const kAuthChallenge[] = { "proxy-authenticate", "Basic realm=\"MyRealm1\"", }; spdy::SpdySerializedFrame connect_auth_resp( spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge, std::size(kAuthChallenge) / 2, 1)); spdy::SpdySerializedFrame connect2_auth_resp( spdy_util.ConstructSpdyReplyError(kAuthStatus, kAuthChallenge, std::size(kAuthChallenge) / 2, 3)); spdy::SpdySerializedFrame connect3_resp( spdy_util.ConstructSpdyGetReply(nullptr, 0, 5)); MockRead spdy_reads[] = { CreateMockRead(connect_auth_resp, 1, ASYNC), CreateMockRead(connect2_auth_resp, 4, ASYNC), CreateMockRead(connect3_resp, 7, ASYNC), MockRead(ASYNC, OK, 8), }; Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); ASSERT_EQ(ERR_IO_PENDING, connect_job->Connect()); // Auth callback is always invoked asynchronously when a challenge is // observed. EXPECT_EQ(0, test_delegate.num_auth_challenges()); test_delegate.WaitForAuthChallenge(1); ASSERT_TRUE(test_delegate.auth_response_info().headers); EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code()); std::string proxy_authenticate; ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader( nullptr, "Proxy-Authenticate", &proxy_authenticate)); EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\""); EXPECT_FALSE(test_delegate.has_result()); test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar")); test_delegate.RunAuthCallback(); // Per API contract, the auth callback can't be invoked synchronously. EXPECT_FALSE(test_delegate.auth_controller()); EXPECT_FALSE(test_delegate.has_result()); test_delegate.WaitForAuthChallenge(2); ASSERT_TRUE(test_delegate.auth_response_info().headers); EXPECT_EQ(407, test_delegate.auth_response_info().headers->response_code()); ASSERT_TRUE(test_delegate.auth_response_info().headers->EnumerateHeader( nullptr, "Proxy-Authenticate", &proxy_authenticate)); EXPECT_EQ(proxy_authenticate, "Basic realm=\"MyRealm1\""); EXPECT_FALSE(test_delegate.has_result()); test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar")); test_delegate.RunAuthCallback(); // Per API contract, the request can't complete synchronously. EXPECT_FALSE(test_delegate.has_result()); EXPECT_EQ(net::OK, test_delegate.WaitForResult()); EXPECT_EQ(2, test_delegate.num_auth_challenges()); // Close the H2 session to prevent reuse. if (GetParam() == SPDY) session_->CloseAllConnections(ERR_FAILED, "Very good reason"); // Also need to clear the auth cache before re-running the test. session_->http_auth_cache()->ClearAllEntries(); } } // Test the case where auth credentials are cached. TEST_P(HttpProxyConnectJobTest, HaveAuth) { // Prepopulate auth cache. const std::u16string kFoo(u"foo"); const std::u16string kBar(u"bar"); url::SchemeHostPort proxy_scheme_host_port( GetParam() == HTTP ? GURL(std::string("http://") + kHttpProxyHost) : GURL(std::string("https://") + kHttpsProxyHost)); session_->http_auth_cache()->Add( proxy_scheme_host_port, HttpAuth::AUTH_PROXY, "MyRealm1", HttpAuth::AUTH_SCHEME_BASIC, NetworkAnonymizationKey(), "Basic realm=MyRealm1", AuthCredentials(kFoo, kBar), "/"); 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" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { MockRead(io_mode, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; const char* const kSpdyAuthCredentials[] = { "proxy-authorization", "Basic Zm9vOmJhcg==", }; SpdyTestUtil spdy_util; spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect( kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); MockWrite spdy_writes[] = { CreateMockWrite(connect, 0, ASYNC), }; spdy::SpdySerializedFrame connect_resp( spdy_util.ConstructSpdyGetReply(nullptr, 0, 1)); MockRead spdy_reads[] = { // SpdySession starts trying to read from the socket as soon as it's // created, so this cannot be SYNCHRONOUS. CreateMockRead(connect_resp, 1, ASYNC), MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2), }; Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); // SPDY operations always complete asynchronously. test_delegate.StartJobExpectingResult( connect_job.get(), OK, io_mode == SYNCHRONOUS && GetParam() != SPDY); // Close the H2 session to prevent reuse. if (GetParam() == SPDY) session_->CloseAllConnections(ERR_FAILED, "Very good reason"); } } TEST_P(HttpProxyConnectJobTest, HostResolutionFailure) { session_deps_.host_resolver->rules()->AddSimulatedTimeoutFailure( kHttpProxyHost); session_deps_.host_resolver->rules()->AddSimulatedTimeoutFailure( kHttpsProxyHost); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForHttpRequest(&test_delegate, DEFAULT_PRIORITY); test_delegate.StartJobExpectingResult(connect_job.get(), ERR_PROXY_CONNECTION_FAILED, false /* expect_sync_result */); EXPECT_THAT(connect_job->GetResolveErrorInfo().error, test::IsError(ERR_DNS_TIMED_OUT)); } TEST_P(HttpProxyConnectJobTest, RequestPriority) { // Make request hang during host resolution, so can observe priority there. session_deps_.host_resolver->set_ondemand_mode(true); 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 connect_job = CreateConnectJobForHttpRequest( &test_delegate, static_cast(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(); size_t request_id = host_resolver->last_id(); EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id)); connect_job->ChangePriority(static_cast(new_priority)); EXPECT_EQ(new_priority, host_resolver->request_priority(request_id)); connect_job->ChangePriority( static_cast(initial_priority)); EXPECT_EQ(initial_priority, host_resolver->request_priority(request_id)); } } } TEST_P(HttpProxyConnectJobTest, SecureDnsPolicy) { for (auto secure_dns_policy : {SecureDnsPolicy::kAllow, SecureDnsPolicy::kDisable}) { TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForHttpRequest( &test_delegate, DEFAULT_PRIORITY, secure_dns_policy); EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); EXPECT_EQ(secure_dns_policy, session_deps_.host_resolver->last_secure_dns_policy()); } } TEST_P(HttpProxyConnectJobTest, SpdySessionKeyDisableSecureDns) { if (GetParam() != SPDY) return; SSLSocketDataProvider ssl_data(ASYNC, OK); InitializeSpdySsl(&ssl_data); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); // SPDY proxy CONNECT request / response, with a pause during the read. spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect( nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); MockWrite spdy_writes[] = {CreateMockWrite(req, 0)}; spdy::SpdySerializedFrame resp( spdy_util_.ConstructSpdyGetReply(nullptr, 0, 1)); MockRead spdy_reads[] = {CreateMockRead(resp, 1), MockRead(ASYNC, 0, 2)}; SequencedSocketData spdy_data(spdy_reads, spdy_writes); spdy_data.set_connect_data(MockConnect(ASYNC, OK)); SequencedSocketData* sequenced_data = &spdy_data; session_deps_.socket_factory->AddSocketDataProvider(sequenced_data); TestConnectJobDelegate test_delegate; auto ssl_params = base::MakeRefCounted( base::MakeRefCounted( HostPortPair(kHttpsProxyHost, 443), NetworkAnonymizationKey(), SecureDnsPolicy::kDisable, OnHostResolutionCallback(), /*supported_alpns=*/base::flat_set()), nullptr, nullptr, HostPortPair(kHttpsProxyHost, 443), SSLConfig(), PRIVACY_MODE_DISABLED, NetworkAnonymizationKey()); auto http_proxy_params = base::MakeRefCounted( nullptr /* tcp_params */, std::move(ssl_params), false /* is_quic */, HostPortPair(kEndpointHost, 443), /*tunnel=*/true, TRAFFIC_ANNOTATION_FOR_TESTS, NetworkAnonymizationKey()); std::unique_ptr connect_job = CreateConnectJob( std::move(http_proxy_params), &test_delegate, DEFAULT_PRIORITY); EXPECT_THAT(connect_job->Connect(), test::IsError(ERR_IO_PENDING)); EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); EXPECT_TRUE( common_connect_job_params_->spdy_session_pool->FindAvailableSession( SpdySessionKey(HostPortPair(kHttpsProxyHost, 443), ProxyServer::Direct(), PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kTrue, SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kDisable), /* enable_ip_based_pooling = */ false, /* is_websocket = */ false, NetLogWithSource())); EXPECT_FALSE( common_connect_job_params_->spdy_session_pool->FindAvailableSession( SpdySessionKey(HostPortPair(kHttpsProxyHost, 443), ProxyServer::Direct(), PRIVACY_MODE_DISABLED, SpdySessionKey::IsProxySession::kTrue, SocketTag(), NetworkAnonymizationKey(), SecureDnsPolicy::kAllow), /* enable_ip_based_pooling = */ false, /* is_websocket = */ false, NetLogWithSource())); } // Make sure that HttpProxyConnectJob does not pass on its priority to its // SPDY session's socket request on Init, or 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 kH2QuicTunnelPriority, // even though the ConnectJob's priority is set to HIGHEST after connection // establishment. spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect( nullptr /* extra_headers */, 0 /* extra_header_count */, 1 /* stream_id */, HttpProxyConnectJob::kH2QuicTunnelPriority, 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(), base::span(), spdy_reads, spdy_writes, SYNCHRONOUS); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate, IDLE); 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; for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { SCOPED_TRACE(io_mode); session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); base::HistogramTester histogram_tester; SequencedSocketData data; data.set_connect_data(MockConnect(io_mode, ERR_CONNECTION_CLOSED)); session_deps_.socket_factory->AddSocketDataProvider(&data); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForHttpRequest(&test_delegate); test_delegate.StartJobExpectingResult( connect_job.get(), ERR_PROXY_CONNECTION_FAILED, io_mode == SYNCHRONOUS); bool is_secure_proxy = GetParam() == HTTPS; histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Insecure.Error", is_secure_proxy ? 0 : 1); histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Secure.Error", is_secure_proxy ? 1 : 0); } } TEST_P(HttpProxyConnectJobTest, SSLError) { if (GetParam() == HTTP) return; for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { SCOPED_TRACE(io_mode); session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); base::HistogramTester histogram_tester; SequencedSocketData data; data.set_connect_data(MockConnect(io_mode, OK)); session_deps_.socket_factory->AddSocketDataProvider(&data); SSLSocketDataProvider ssl_data(io_mode, ERR_CERT_AUTHORITY_INVALID); if (GetParam() == SPDY) { InitializeSpdySsl(&ssl_data); } session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); test_delegate.StartJobExpectingResult(connect_job.get(), ERR_PROXY_CERTIFICATE_INVALID, io_mode == SYNCHRONOUS); histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Secure.Error", 1); 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 */, HttpProxyConnectJob::kH2QuicTunnelPriority, 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 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(), base::span(), io_mode); TestConnectJobDelegate test_delegate; std::unique_ptr 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 */, HttpProxyConnectJob::kH2QuicTunnelPriority, 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 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, SslClientAuth) { if (GetParam() == HTTP) return; for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { SCOPED_TRACE(io_mode); session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); base::HistogramTester histogram_tester; SequencedSocketData socket_data(MockConnect(io_mode, OK), base::span(), base::span()); session_deps_.socket_factory->AddSocketDataProvider(&socket_data); SSLSocketDataProvider ssl_data(io_mode, ERR_SSL_CLIENT_AUTH_CERT_NEEDED); if (GetParam() == SPDY) InitializeSpdySsl(&ssl_data); session_deps_.socket_factory->AddSSLSocketDataProvider(&ssl_data); // Redirects in the HTTPS case return errors, but also return sockets. TestConnectJobDelegate test_delegate; std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); test_delegate.StartJobExpectingResult(connect_job.get(), ERR_SSL_CLIENT_AUTH_CERT_NEEDED, io_mode == SYNCHRONOUS); histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Secure.Error", 1); histogram_tester.ExpectTotalCount( "Net.HttpProxy.ConnectLatency.Insecure.Error", 0); } } TEST_P(HttpProxyConnectJobTest, TunnelSetupRedirect) { const std::string kRedirectTarget = "https://foo.google.com/"; for (IoMode io_mode : {SYNCHRONOUS, ASYNC}) { SCOPED_TRACE(io_mode); session_deps_.host_resolver->set_synchronous_mode(io_mode == SYNCHRONOUS); const std::string kResponseText = "HTTP/1.1 302 Found\r\n" "Location: " + kRedirectTarget + "\r\n" "Set-Cookie: foo=bar\r\n" "\r\n"; 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, kResponseText.c_str()), }; SpdyTestUtil spdy_util; spdy::SpdySerializedFrame req(spdy_util.ConstructSpdyConnect( nullptr /* extra_headers */, 0 /* extra_header_count */, 1, DEFAULT_PRIORITY, HostPortPair(kEndpointHost, 443))); spdy::SpdySerializedFrame rst( spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); MockWrite spdy_writes[] = { CreateMockWrite(req, 0, io_mode), CreateMockWrite(rst, 3, io_mode), }; const char* const responseHeaders[] = { "location", kRedirectTarget.c_str(), "set-cookie", "foo=bar", }; const int responseHeadersSize = std::size(responseHeaders) / 2; spdy::SpdySerializedFrame resp(spdy_util.ConstructSpdyReplyError( "302", responseHeaders, responseHeadersSize, 1)); MockRead spdy_reads[] = { CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, 0, 2), }; Initialize(reads, writes, spdy_reads, spdy_writes, io_mode); // Redirects during CONNECT returns an error. TestConnectJobDelegate test_delegate( TestConnectJobDelegate::SocketExpected::ON_SUCCESS_ONLY); std::unique_ptr connect_job = CreateConnectJobForTunnel(&test_delegate); // H2 never completes synchronously. bool expect_sync_result = (io_mode == SYNCHRONOUS && GetParam() != SPDY); // We don't trust 302 responses to CONNECT from proxies. test_delegate.StartJobExpectingResult( connect_job.get(), ERR_TUNNEL_CONNECTION_FAILED, expect_sync_result); EXPECT_FALSE(test_delegate.socket()); // Need to close the session to prevent reuse in the next loop iteration. session_->spdy_session_pool()->CloseAllSessions(); } } // Test timeouts in the case of an auth challenge and response. TEST_P(HttpProxyConnectJobTest, TestTimeoutsAuthChallenge) { // Wait until this amount of time before something times out. const base::TimeDelta kTinyTime = base::Microseconds(1); enum class TimeoutPhase { CONNECT, PROXY_HANDSHAKE, SECOND_PROXY_HANDSHAKE, NONE, }; const TimeoutPhase kTimeoutPhases[] = { TimeoutPhase::CONNECT, TimeoutPhase::PROXY_HANDSHAKE, TimeoutPhase::SECOND_PROXY_HANDSHAKE, TimeoutPhase::NONE, }; session_deps_.host_resolver->set_ondemand_mode(true); MockWrite 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"), MockWrite(ASYNC, 3, "CONNECT www.endpoint.test:443 HTTP/1.1\r\n" "Host: www.endpoint.test:443\r\n" "Proxy-Connection: keep-alive\r\n" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads[] = { // Pause before first response is read. MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 2, "HTTP/1.1 407 Proxy Authentication Required\r\n" "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n" "Content-Length: 0\r\n\r\n"), // Pause again before second response is read. MockRead(ASYNC, ERR_IO_PENDING, 4), MockRead(ASYNC, 5, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; SpdyTestUtil spdy_util; spdy::SpdySerializedFrame connect(spdy_util.ConstructSpdyConnect( nullptr, 0, 1, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); spdy::SpdySerializedFrame rst( spdy_util.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL)); spdy_util.UpdateWithStreamDestruction(1); // After calling trans.RestartWithAuth(), this is the request we should // be issuing -- the final header line contains the credentials. const char* const kSpdyAuthCredentials[] = { "proxy-authorization", "Basic Zm9vOmJhcg==", }; spdy::SpdySerializedFrame connect2(spdy_util.ConstructSpdyConnect( kSpdyAuthCredentials, std::size(kSpdyAuthCredentials) / 2, 3, HttpProxyConnectJob::kH2QuicTunnelPriority, HostPortPair(kEndpointHost, 443))); // This may be sent in some tests, either when tearing down a successful // connection, or on timeout. spdy::SpdySerializedFrame rst2( spdy_util.ConstructSpdyRstStream(3, spdy::ERROR_CODE_CANCEL)); MockWrite spdy_writes[] = { CreateMockWrite(connect, 0, ASYNC), CreateMockWrite(rst, 3, ASYNC), CreateMockWrite(connect2, 4, ASYNC), CreateMockWrite(rst2, 8, ASYNC), }; // The proxy responds to the connect with a 407, using a persistent // connection. const char kAuthStatus[] = "407"; const char* const kAuthChallenge[] = { "proxy-authenticate", "Basic realm=\"MyRealm1\"", }; spdy::SpdySerializedFrame connect_auth_resp(spdy_util.ConstructSpdyReplyError( kAuthStatus, kAuthChallenge, std::size(kAuthChallenge) / 2, 1)); spdy::SpdySerializedFrame connect2_resp( spdy_util.ConstructSpdyGetReply(nullptr, 0, 3)); MockRead spdy_reads[] = { // Pause before first response is read. MockRead(ASYNC, ERR_IO_PENDING, 1), CreateMockRead(connect_auth_resp, 2, ASYNC), // Pause again before second response is read. MockRead(ASYNC, ERR_IO_PENDING, 5), CreateMockRead(connect2_resp, 6, ASYNC), MockRead(ASYNC, OK, 7), }; for (TimeoutPhase timeout_phase : kTimeoutPhases) { SCOPED_TRACE(static_cast(timeout_phase)); // Need to close the session to prevent reuse of a session from the last // loop iteration. session_->spdy_session_pool()->CloseAllSessions(); // And clear the auth cache to prevent reusing cache entries. session_->http_auth_cache()->ClearAllEntries(); TestConnectJobDelegate test_delegate; std::unique_ptr 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()); // Run until just before timeout. FastForwardBy(GetNestedConnectionTimeout() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); // Wait until timeout, if appropriate. if (timeout_phase == TimeoutPhase::CONNECT) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } // Add mock reads for socket needed in next step. Connect phase is timed out // before establishing a connection, so don't need them for // TimeoutPhase::CONNECT. Initialize(reads, writes, spdy_reads, spdy_writes, SYNCHRONOUS); // Finish resolution. session_deps_.host_resolver->ResolveOnlyRequestNow(); EXPECT_FALSE(test_delegate.has_result()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, connect_job->GetLoadState()); // Wait until just before negotiation with the tunnel should time out. FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); if (timeout_phase == TimeoutPhase::PROXY_HANDSHAKE) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } data_->Resume(); test_delegate.WaitForAuthChallenge(1); EXPECT_FALSE(test_delegate.has_result()); // ConnectJobs cannot timeout while showing an auth dialog. FastForwardBy(base::Days(1)); EXPECT_FALSE(test_delegate.has_result()); // Send credentials test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar")); test_delegate.RunAuthCallback(); EXPECT_FALSE(test_delegate.has_result()); FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); if (timeout_phase == TimeoutPhase::SECOND_PROXY_HANDSHAKE) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } data_->Resume(); EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); } } // Same as above, except test the case the first connection cannot be reused // once credentials are received. TEST_P(HttpProxyConnectJobTest, TestTimeoutsAuthChallengeNewConnection) { // Proxy-Connection: Close doesn't make sense with H2. if (GetParam() == SPDY) return; enum class TimeoutPhase { CONNECT, PROXY_HANDSHAKE, SECOND_CONNECT, SECOND_PROXY_HANDSHAKE, // This has to be last for the H2 proxy case, since success will populate // the H2 session pool. NONE, }; const TimeoutPhase kTimeoutPhases[] = { TimeoutPhase::CONNECT, TimeoutPhase::PROXY_HANDSHAKE, TimeoutPhase::SECOND_CONNECT, TimeoutPhase::SECOND_PROXY_HANDSHAKE, TimeoutPhase::NONE, }; // Wait until this amount of time before something times out. const base::TimeDelta kTinyTime = base::Microseconds(1); session_deps_.host_resolver->set_ondemand_mode(true); MockWrite 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 reads[] = { // Pause at read. MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 2, "HTTP/1.1 407 Proxy Authentication Required\r\n" "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n" "Proxy-Connection: Close\r\n" "Content-Length: 0\r\n\r\n"), }; MockWrite writes2[] = { 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" "Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"), }; MockRead reads2[] = { // Pause at read. MockRead(ASYNC, ERR_IO_PENDING, 1), MockRead(ASYNC, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"), }; for (TimeoutPhase timeout_phase : kTimeoutPhases) { SCOPED_TRACE(static_cast(timeout_phase)); // Need to clear the auth cache to prevent reusing cache entries. session_->http_auth_cache()->ClearAllEntries(); TestConnectJobDelegate test_delegate; std::unique_ptr 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()); // Run until just before timeout. FastForwardBy(GetNestedConnectionTimeout() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); // Wait until timeout, if appropriate. if (timeout_phase == TimeoutPhase::CONNECT) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } // Add mock reads for socket needed in next step. Connect phase is timed out // before establishing a connection, so don't need them for // TimeoutPhase::CONNECT. Initialize(reads, writes, base::span(), base::span(), SYNCHRONOUS); // Finish resolution. session_deps_.host_resolver->ResolveOnlyRequestNow(); EXPECT_FALSE(test_delegate.has_result()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, connect_job->GetLoadState()); // Wait until just before negotiation with the tunnel should time out. FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); if (timeout_phase == TimeoutPhase::PROXY_HANDSHAKE) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } data_->Resume(); test_delegate.WaitForAuthChallenge(1); EXPECT_FALSE(test_delegate.has_result()); // ConnectJobs cannot timeout while showing an auth dialog. FastForwardBy(base::Days(1)); EXPECT_FALSE(test_delegate.has_result()); // Send credentials test_delegate.auth_controller()->ResetAuth(AuthCredentials(u"foo", u"bar")); test_delegate.RunAuthCallback(); EXPECT_FALSE(test_delegate.has_result()); // Since the connection was not reusable, a new connection needs to be // established. base::RunLoop().RunUntilIdle(); EXPECT_FALSE(test_delegate.has_result()); EXPECT_TRUE(session_deps_.host_resolver->has_pending_requests()); EXPECT_EQ(LOAD_STATE_RESOLVING_HOST, connect_job->GetLoadState()); // Run until just before timeout. FastForwardBy(GetNestedConnectionTimeout() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); // Wait until timeout, if appropriate. if (timeout_phase == TimeoutPhase::SECOND_CONNECT) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } // Add mock reads for socket needed in next step. Connect phase is timed out // before establishing a connection, so don't need them for // TimeoutPhase::SECOND_CONNECT. Initialize(reads2, writes2, base::span(), base::span(), SYNCHRONOUS); // Finish resolution. session_deps_.host_resolver->ResolveOnlyRequestNow(); EXPECT_FALSE(test_delegate.has_result()); EXPECT_EQ(LOAD_STATE_ESTABLISHING_PROXY_TUNNEL, connect_job->GetLoadState()); // Wait until just before negotiation with the tunnel should time out. FastForwardBy(HttpProxyConnectJob::TunnelTimeoutForTesting() - kTinyTime); EXPECT_FALSE(test_delegate.has_result()); if (timeout_phase == TimeoutPhase::SECOND_PROXY_HANDSHAKE) { FastForwardBy(kTinyTime); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsError(ERR_TIMED_OUT)); continue; } data_->Resume(); ASSERT_TRUE(test_delegate.has_result()); EXPECT_THAT(test_delegate.WaitForResult(), test::IsOk()); } } TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutNoNQE) { // 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. base::TimeDelta alternate_connection_timeout = HttpProxyConnectJob::AlternateNestedConnectionTimeout( *CreateParams(true /* tunnel */, SecureDnsPolicy::kAllow), nullptr /* network_quality_estimator */); #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS) // On Android and iOS, when there's no NQE, there's a hard-coded alternate // proxy timeout. EXPECT_EQ(base::Seconds(10), alternate_connection_timeout); #else // On other platforms, there is not. EXPECT_EQ(base::TimeDelta(), alternate_connection_timeout); #endif } TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMin) { // Set RTT estimate to a low value. base::TimeDelta rtt_estimate = base::Milliseconds(1); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout()); // Test against a large value. EXPECT_GE(base::Minutes(10), GetNestedConnectionTimeout()); EXPECT_EQ(base::Seconds(8), GetNestedConnectionTimeout()); } TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutMax) { // Set RTT estimate to a high value. base::TimeDelta rtt_estimate = base::Seconds(100); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout()); // Test against a large value. EXPECT_GE(base::Minutes(10), GetNestedConnectionTimeout()); EXPECT_EQ(base::Seconds(30), GetNestedConnectionTimeout()); } // 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::Seconds(8); const base::TimeDelta kMaxTimeout = base::Seconds(20); InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier, kMinTimeout, kMaxTimeout); EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout()); base::TimeDelta rtt_estimate = base::Seconds(4); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); base::TimeDelta expected_connection_timeout = kMultiplier * rtt_estimate; EXPECT_EQ(expected_connection_timeout, GetNestedConnectionTimeout()); // Connection timeout should not exceed kMaxTimeout. rtt_estimate = base::Seconds(25); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMaxTimeout, GetNestedConnectionTimeout()); // Connection timeout should not be less than kMinTimeout. rtt_estimate = base::Seconds(0); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMinTimeout, GetNestedConnectionTimeout()); } // 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::Seconds(2); const base::TimeDelta kMaxTimeout = base::Seconds(30); InitAdaptiveTimeoutFieldTrialWithParams(false, kMultiplier, kMultiplier, kMinTimeout, kMaxTimeout); EXPECT_LE(base::TimeDelta(), GetNestedConnectionTimeout()); base::TimeDelta rtt_estimate = base::Seconds(2); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMultiplier * rtt_estimate, GetNestedConnectionTimeout()); // A change in RTT estimate should also change the connection timeout. rtt_estimate = base::Seconds(7); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMultiplier * rtt_estimate, GetNestedConnectionTimeout()); // Connection timeout should not exceed kMaxTimeout. rtt_estimate = base::Seconds(35); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMaxTimeout, GetNestedConnectionTimeout()); // Connection timeout should not be less than kMinTimeout. rtt_estimate = base::Seconds(0); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_EQ(kMinTimeout, GetNestedConnectionTimeout()); } TEST_P(HttpProxyConnectJobTest, ConnectionTimeoutWithConnectionProperty) { const int kSecureMultiplier = 3; const int kNonSecureMultiplier = 5; const base::TimeDelta kMinTimeout = base::Seconds(2); const base::TimeDelta kMaxTimeout = base::Seconds(30); InitAdaptiveTimeoutFieldTrialWithParams( false, kSecureMultiplier, kNonSecureMultiplier, kMinTimeout, kMaxTimeout); const base::TimeDelta kRttEstimate = base::Seconds(2); network_quality_estimator_->SetStartTimeNullHttpRtt(kRttEstimate); // By default, connection timeout should return the timeout for secure // proxies. if (GetParam() != HTTP) { EXPECT_EQ(kSecureMultiplier * kRttEstimate, GetNestedConnectionTimeout()); } else { EXPECT_EQ(kNonSecureMultiplier * kRttEstimate, GetNestedConnectionTimeout()); } } // 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(), GetNestedConnectionTimeout()); // Timeout should be |http_rtt_multiplier| times the HTTP RTT // estimate. base::TimeDelta rtt_estimate = base::Milliseconds(10); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); // Connection timeout should not be less than the HTTP RTT estimate. EXPECT_LE(rtt_estimate, GetNestedConnectionTimeout()); // A change in RTT estimate should also change the connection timeout. rtt_estimate = base::Seconds(10); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); // Connection timeout should not be less than the HTTP RTT estimate. EXPECT_LE(rtt_estimate, GetNestedConnectionTimeout()); // Set RTT to a very large value. rtt_estimate = base::Minutes(60); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_GT(rtt_estimate, GetNestedConnectionTimeout()); // Set RTT to a very small value. rtt_estimate = base::Seconds(0); network_quality_estimator_->SetStartTimeNullHttpRtt(rtt_estimate); EXPECT_LT(rtt_estimate, GetNestedConnectionTimeout()); } } // namespace net