blob: 089f32d3251ace7099588469732cdbcdc21e0ec3 [file] [log] [blame]
// Copyright (c) 2012 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_client_socket_pool.h"
#include <map>
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/compiler_specific.h"
#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/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/http/http_network_session.h"
#include "net/http/http_proxy_client_socket.h"
#include "net/http/http_response_headers.h"
#include "net/log/net_log_with_source.h"
#include "net/nqe/network_quality_estimator_test_util.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/next_proto.h"
#include "net/socket/socket_tag.h"
#include "net/socket/socket_test_util.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/third_party/quiche/src/spdy/core/spdy_protocol.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::test::IsError;
using net::test::IsOk;
namespace net {
namespace {
const int kMaxSockets = 32;
const int kMaxSocketsPerGroup = 6;
const char * const kAuthHeaders[] = {
"proxy-authorization", "Basic Zm9vOmJhcg=="
};
const int kAuthHeadersSize = arraysize(kAuthHeaders) / 2;
enum HttpProxyType {
HTTP,
HTTPS,
SPDY
};
const char kHttpProxyHost[] = "httpproxy.example.com";
const char kHttpsProxyHost[] = "httpsproxy.example.com";
} // namespace
class HttpProxyClientSocketPoolTest
: public ::testing::TestWithParam<HttpProxyType>,
public WithScopedTaskEnvironment {
protected:
HttpProxyClientSocketPoolTest()
: transport_socket_pool_(kMaxSockets,
kMaxSocketsPerGroup,
&socket_factory_),
ssl_socket_pool_(kMaxSockets,
kMaxSocketsPerGroup,
session_deps_.cert_verifier.get(),
NULL /* channel_id_store */,
NULL /* transport_security_state */,
NULL /* cert_transparency_verifier */,
NULL /* ct_policy_enforcer */,
std::string() /* ssl_session_cache_shard */,
&socket_factory_,
&transport_socket_pool_,
NULL,
NULL,
session_deps_.ssl_config_service.get(),
NetLogWithSource().net_log()),
#if !defined(STARBOARD)
field_trial_list_(nullptr),
#endif
pool_(
std::make_unique<HttpProxyClientSocketPool>(kMaxSockets,
kMaxSocketsPerGroup,
&transport_socket_pool_,
&ssl_socket_pool_,
&estimator_,
nullptr)) {
session_ = CreateNetworkSession();
}
virtual ~HttpProxyClientSocketPoolTest() = default;
// Initializes the field trial paramters 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::IntToString(ssl_http_rtt_multiplier);
params["non_ssl_http_rtt_multiplier"] =
base::IntToString(non_ssl_http_rtt_multiplier);
params["min_proxy_connection_timeout_seconds"] =
base::IntToString(min_proxy_connection_timeout.InSeconds());
params["max_proxy_connection_timeout_seconds"] =
base::IntToString(max_proxy_connection_timeout.InSeconds());
}
#if !defined(STARBOARD)
base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting();
EXPECT_TRUE(
base::AssociateFieldTrialParams(trial_name, group_name, params));
EXPECT_TRUE(base::FieldTrialList::CreateFieldTrial(trial_name, group_name));
#endif
// Reset |pool_| so that the field trial parameters are read by the
// |pool_|.
pool_ = std::make_unique<HttpProxyClientSocketPool>(
kMaxSockets, kMaxSocketsPerGroup, &transport_socket_pool_,
&ssl_socket_pool_, &estimator_, NetLogWithSource().net_log());
}
void AddAuthToCache() {
const base::string16 kFoo(base::ASCIIToUTF16("foo"));
const base::string16 kBar(base::ASCIIToUTF16("bar"));
GURL proxy_url(GetParam() == HTTP
? (std::string("http://") + kHttpProxyHost)
: (std::string("https://") + kHttpsProxyHost));
session_->http_auth_cache()->Add(proxy_url,
"MyRealm1",
HttpAuth::AUTH_SCHEME_BASIC,
"Basic realm=MyRealm1",
AuthCredentials(kFoo, kBar),
"/");
}
scoped_refptr<TransportSocketParams> CreateHttpProxyParams() const {
if (GetParam() != HTTP)
return NULL;
return new TransportSocketParams(
HostPortPair(kHttpProxyHost, 80),
false,
OnHostResolutionCallback(),
TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT);
}
scoped_refptr<SSLSocketParams> CreateHttpsProxyParams() const {
if (GetParam() == HTTP)
return NULL;
return new SSLSocketParams(
new TransportSocketParams(
HostPortPair(kHttpsProxyHost, 443), false,
OnHostResolutionCallback(),
TransportSocketParams::COMBINE_CONNECT_AND_WRITE_DEFAULT),
NULL, NULL, HostPortPair(kHttpsProxyHost, 443), SSLConfig(),
PRIVACY_MODE_DISABLED, 0);
}
// Returns the a correctly constructed HttpProxyParms
// 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("www.google.com", 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);
}
scoped_refptr<HttpProxySocketParams> CreateTunnelParams() {
return CreateParams(true);
}
scoped_refptr<HttpProxySocketParams> CreateNoTunnelParams() {
return CreateParams(false);
}
MockTaggingClientSocketFactory* socket_factory() { return &socket_factory_; }
void Initialize(base::span<const MockRead> reads,
base::span<const MockWrite> writes,
base::span<const MockRead> spdy_reads,
base::span<const MockWrite> spdy_writes) {
if (GetParam() == SPDY) {
data_.reset(new SequencedSocketData(spdy_reads, spdy_writes));
} else {
data_.reset(new SequencedSocketData(reads, writes));
}
data_->set_connect_data(MockConnect(SYNCHRONOUS, OK));
socket_factory()->AddSocketDataProvider(data_.get());
if (GetParam() != HTTP) {
ssl_data_.reset(new SSLSocketDataProvider(SYNCHRONOUS, OK));
if (GetParam() == SPDY) {
InitializeSpdySsl();
}
socket_factory()->AddSSLSocketDataProvider(ssl_data_.get());
}
}
void InitializeSpdySsl() { ssl_data_->next_proto = kProtoHTTP2; }
std::unique_ptr<HttpNetworkSession> CreateNetworkSession() {
return SpdySessionDependencies::SpdyCreateSession(&session_deps_);
}
RequestPriority GetLastTransportRequestPriority() const {
return transport_socket_pool_.last_request_priority();
}
const base::HistogramTester& histogram_tester() { return histogram_tester_; }
TestNetworkQualityEstimator* estimator() { return &estimator_; }
MockTransportClientSocketPool* transport_socket_pool() {
return &transport_socket_pool_;
}
SSLClientSocketPool* ssl_socket_pool() { return &ssl_socket_pool_; }
private:
MockTaggingClientSocketFactory socket_factory_;
SpdySessionDependencies session_deps_;
TestNetworkQualityEstimator estimator_;
MockTransportClientSocketPool transport_socket_pool_;
MockHostResolver host_resolver_;
std::unique_ptr<CertVerifier> cert_verifier_;
SSLClientSocketPool ssl_socket_pool_;
std::unique_ptr<HttpNetworkSession> session_;
base::HistogramTester histogram_tester_;
#if !defined(STARBOARD)
// Starboard does not support FieldTrial.
base::FieldTrialList field_trial_list_;
#endif
protected:
SpdyTestUtil spdy_util_;
std::unique_ptr<SSLSocketDataProvider> ssl_data_;
std::unique_ptr<SequencedSocketData> data_;
std::unique_ptr<HttpProxyClientSocketPool> pool_;
ClientSocketHandle handle_;
TestCompletionCallback callback_;
};
// All tests are run with three different proxy types: HTTP, HTTPS (non-SPDY)
// and SPDY.
INSTANTIATE_TEST_CASE_P(HttpProxyType,
HttpProxyClientSocketPoolTest,
::testing::Values(HTTP, HTTPS, SPDY));
TEST_P(HttpProxyClientSocketPoolTest, NoTunnel) {
Initialize(base::span<MockRead>(), base::span<MockWrite>(),
base::span<MockRead>(), base::span<MockWrite>());
int rv =
handle_.Init("a", CreateNoTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
CompletionOnceCallback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsOk());
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
EXPECT_TRUE(handle_.socket()->IsConnected());
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);
}
// Make sure that HttpProxyConnectJob passes on its priority to its
// (non-SSL) socket request on Init.
TEST_P(HttpProxyClientSocketPoolTest, SetSocketRequestPriorityOnInit) {
Initialize(base::span<MockRead>(), base::span<MockWrite>(),
base::span<MockRead>(), base::span<MockWrite>());
EXPECT_EQ(OK, handle_.Init("a", CreateNoTunnelParams(), HIGHEST, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
CompletionOnceCallback(), pool_.get(),
NetLogWithSource()));
EXPECT_EQ(HIGHEST, GetLastTransportRequestPriority());
}
TEST_P(HttpProxyClientSocketPoolTest, NeedAuth) {
MockWrite writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead reads[] = {
// No credentials.
MockRead(ASYNC, 1, "HTTP/1.1 407 Proxy Authentication Required\r\n"),
MockRead(ASYNC, 2, "Proxy-Authenticate: Basic realm=\"MyRealm1\"\r\n"),
MockRead(ASYNC, 3, "Content-Length: 10\r\n\r\n"),
MockRead(ASYNC, 4, "0123456789"),
};
spdy::SpdySerializedFrame req(spdy_util_.ConstructSpdyConnect(
NULL, 0, 1, LOW, HostPortPair("www.google.com", 443)));
spdy::SpdySerializedFrame rst(
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
MockWrite spdy_writes[] = {
CreateMockWrite(req, 0, ASYNC), CreateMockWrite(rst, 2, ASYNC),
};
spdy::SpdyHeaderBlock resp_block;
resp_block[spdy::kHttp2StatusHeader] = "407";
resp_block["proxy-authenticate"] = "Basic realm=\"MyRealm1\"";
spdy::SpdySerializedFrame resp(
spdy_util_.ConstructSpdyReply(1, std::move(resp_block)));
MockRead spdy_reads[] = {CreateMockRead(resp, 1, ASYNC),
MockRead(ASYNC, 0, 3)};
Initialize(reads, writes, spdy_reads, spdy_writes);
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
rv = callback_.WaitForResult();
EXPECT_THAT(rv, IsError(ERR_PROXY_AUTH_REQUESTED));
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
ProxyClientSocket* tunnel_socket =
static_cast<ProxyClientSocket*>(handle_.socket());
if (GetParam() == SPDY) {
EXPECT_TRUE(tunnel_socket->IsConnected());
EXPECT_TRUE(tunnel_socket->IsUsingSpdy());
} else {
EXPECT_FALSE(tunnel_socket->IsConnected());
EXPECT_FALSE(tunnel_socket->IsUsingSpdy());
}
}
TEST_P(HttpProxyClientSocketPoolTest, HaveAuth) {
// It's pretty much impossible to make the SPDY case behave synchronously
// so we skip this test for SPDY
if (GetParam() == SPDY)
return;
std::string proxy_host_port = GetParam() == HTTP
? (kHttpProxyHost + std::string(":80"))
: (kHttpsProxyHost + std::string(":443"));
std::string request =
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n";
MockWrite writes[] = {
MockWrite(SYNCHRONOUS, 0, request.c_str()),
};
MockRead reads[] = {
MockRead(SYNCHRONOUS, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
};
Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>());
AddAuthToCache();
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsOk());
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
EXPECT_TRUE(handle_.socket()->IsConnected());
}
TEST_P(HttpProxyClientSocketPoolTest, AsyncHaveAuth) {
std::string proxy_host_port = GetParam() == HTTP
? (kHttpProxyHost + std::string(":80"))
: (kHttpsProxyHost + std::string(":443"));
std::string request =
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n";
MockWrite writes[] = {
MockWrite(ASYNC, 0, request.c_str()),
};
MockRead reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 200 Connection Established\r\n\r\n"),
};
spdy::SpdySerializedFrame req(
spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1, LOW,
HostPortPair("www.google.com", 443)));
MockWrite spdy_writes[] = {CreateMockWrite(req, 0, ASYNC)};
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyGetReply(NULL, 0, 1));
MockRead spdy_reads[] = {
CreateMockRead(resp, 1, ASYNC),
// Connection stays open.
MockRead(SYNCHRONOUS, ERR_IO_PENDING, 2),
};
Initialize(reads, writes, spdy_reads, spdy_writes);
AddAuthToCache();
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
EXPECT_THAT(callback_.WaitForResult(), IsOk());
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
EXPECT_TRUE(handle_.socket()->IsConnected());
}
// Make sure that HttpProxyConnectJob passes on its priority to its
// SPDY session's socket request on Init (if applicable).
TEST_P(HttpProxyClientSocketPoolTest,
SetSpdySessionSocketRequestPriorityOnInit) {
if (GetParam() != SPDY)
return;
spdy::SpdySerializedFrame req(
spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1, MEDIUM,
HostPortPair("www.google.com", 443)));
MockWrite spdy_writes[] = {CreateMockWrite(req, 0, ASYNC)};
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyGetReply(NULL, 0, 1));
MockRead spdy_reads[] = {CreateMockRead(resp, 1, ASYNC),
MockRead(ASYNC, 0, 2)};
Initialize(base::span<MockRead>(), base::span<MockWrite>(), spdy_reads,
spdy_writes);
AddAuthToCache();
EXPECT_EQ(
ERR_IO_PENDING,
handle_.Init("a", CreateTunnelParams(), MEDIUM, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource()));
EXPECT_EQ(MEDIUM, GetLastTransportRequestPriority());
EXPECT_THAT(callback_.WaitForResult(), IsOk());
}
TEST_P(HttpProxyClientSocketPoolTest, TCPError) {
if (GetParam() == SPDY)
return;
data_.reset(new SequencedSocketData());
data_->set_connect_data(MockConnect(ASYNC, ERR_CONNECTION_CLOSED));
socket_factory()->AddSocketDataProvider(data_.get());
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_PROXY_CONNECTION_FAILED));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
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(HttpProxyClientSocketPoolTest, SSLError) {
if (GetParam() == HTTP)
return;
data_.reset(new SequencedSocketData());
data_->set_connect_data(MockConnect(ASYNC, OK));
socket_factory()->AddSocketDataProvider(data_.get());
ssl_data_.reset(new SSLSocketDataProvider(ASYNC,
ERR_CERT_AUTHORITY_INVALID));
if (GetParam() == SPDY) {
InitializeSpdySsl();
}
socket_factory()->AddSSLSocketDataProvider(ssl_data_.get());
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
EXPECT_THAT(callback_.WaitForResult(),
IsError(ERR_PROXY_CERTIFICATE_INVALID));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
histogram_tester().ExpectTotalCount(
"Net.HttpProxy.ConnectLatency.Secure.Error", 1);
histogram_tester().ExpectTotalCount(
"Net.HttpProxy.ConnectLatency.Insecure.Error", 0);
}
TEST_P(HttpProxyClientSocketPoolTest, SslClientAuth) {
if (GetParam() == HTTP)
return;
data_.reset(new SequencedSocketData());
data_->set_connect_data(MockConnect(ASYNC, OK));
socket_factory()->AddSocketDataProvider(data_.get());
ssl_data_.reset(new SSLSocketDataProvider(ASYNC,
ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
if (GetParam() == SPDY) {
InitializeSpdySsl();
}
socket_factory()->AddSSLSocketDataProvider(ssl_data_.get());
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
EXPECT_THAT(callback_.WaitForResult(),
IsError(ERR_SSL_CLIENT_AUTH_CERT_NEEDED));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
histogram_tester().ExpectTotalCount(
"Net.HttpProxy.ConnectLatency.Secure.Error", 1);
histogram_tester().ExpectTotalCount(
"Net.HttpProxy.ConnectLatency.Insecure.Error", 0);
}
TEST_P(HttpProxyClientSocketPoolTest, TunnelUnexpectedClose) {
MockWrite writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 200 Conn"),
MockRead(ASYNC, ERR_CONNECTION_CLOSED, 2),
};
spdy::SpdySerializedFrame req(
spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1, LOW,
HostPortPair("www.google.com", 443)));
MockWrite spdy_writes[] = {CreateMockWrite(req, 0, ASYNC)};
MockRead spdy_reads[] = {
MockRead(ASYNC, ERR_CONNECTION_CLOSED, 1),
};
Initialize(reads, writes, spdy_reads, spdy_writes);
AddAuthToCache();
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
if (GetParam() == SPDY) {
// SPDY cannot process a headers block unless it's complete and so it
// returns ERR_CONNECTION_CLOSED in this case.
EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_CONNECTION_CLOSED));
} else {
EXPECT_THAT(callback_.WaitForResult(),
IsError(ERR_RESPONSE_HEADERS_TRUNCATED));
}
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
}
TEST_P(HttpProxyClientSocketPoolTest, Tunnel1xxResponse) {
// Tests that 1xx responses are rejected for a CONNECT request.
if (GetParam() == SPDY) {
// SPDY doesn't have 1xx responses.
return;
}
MockWrite writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n\r\n"),
};
MockRead reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 100 Continue\r\n\r\n"),
MockRead(ASYNC, 2, "HTTP/1.1 200 Connection Established\r\n\r\n"),
};
Initialize(reads, writes, base::span<MockRead>(), base::span<MockWrite>());
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_TUNNEL_CONNECTION_FAILED));
}
TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupError) {
MockWrite writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead reads[] = {
MockRead(ASYNC, 1, "HTTP/1.1 304 Not Modified\r\n\r\n"),
};
spdy::SpdySerializedFrame req(
spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1, LOW,
HostPortPair("www.google.com", 443)));
spdy::SpdySerializedFrame rst(
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
MockWrite spdy_writes[] = {
CreateMockWrite(req, 0, ASYNC), CreateMockWrite(rst, 2, ASYNC),
};
spdy::SpdySerializedFrame resp(spdy_util_.ConstructSpdyReplyError(1));
MockRead spdy_reads[] = {
CreateMockRead(resp, 1, ASYNC), MockRead(ASYNC, 0, 3),
};
Initialize(reads, writes, spdy_reads, spdy_writes);
AddAuthToCache();
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
rv = callback_.WaitForResult();
// All Proxy CONNECT responses are not trustworthy
EXPECT_THAT(rv, IsError(ERR_TUNNEL_CONNECTION_FAILED));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
}
TEST_P(HttpProxyClientSocketPoolTest, TunnelSetupRedirect) {
const std::string redirectTarget = "https://foo.google.com/";
const std::string responseText = "HTTP/1.1 302 Found\r\n"
"Location: " + redirectTarget + "\r\n"
"Set-Cookie: foo=bar\r\n"
"\r\n";
MockWrite writes[] = {
MockWrite(ASYNC, 0,
"CONNECT www.google.com:443 HTTP/1.1\r\n"
"Host: www.google.com:443\r\n"
"Proxy-Connection: keep-alive\r\n"
"Proxy-Authorization: Basic Zm9vOmJhcg==\r\n\r\n"),
};
MockRead reads[] = {
MockRead(ASYNC, 1, responseText.c_str()),
};
spdy::SpdySerializedFrame req(
spdy_util_.ConstructSpdyConnect(kAuthHeaders, kAuthHeadersSize, 1, LOW,
HostPortPair("www.google.com", 443)));
spdy::SpdySerializedFrame rst(
spdy_util_.ConstructSpdyRstStream(1, spdy::ERROR_CODE_CANCEL));
MockWrite spdy_writes[] = {
CreateMockWrite(req, 0, ASYNC), CreateMockWrite(rst, 3, ASYNC),
};
const char* const responseHeaders[] = {
"location", redirectTarget.c_str(),
"set-cookie", "foo=bar",
};
const int responseHeadersSize = arraysize(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);
AddAuthToCache();
int rv = handle_.Init("a", CreateTunnelParams(), LOW, SocketTag(),
ClientSocketPool::RespectLimits::ENABLED,
callback_.callback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsError(ERR_IO_PENDING));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
rv = callback_.WaitForResult();
if (GetParam() == HTTP) {
// We don't trust 302 responses to CONNECT from HTTP proxies.
EXPECT_THAT(rv, IsError(ERR_TUNNEL_CONNECTION_FAILED));
EXPECT_FALSE(handle_.is_initialized());
EXPECT_FALSE(handle_.socket());
} else {
// Expect ProxyClientSocket to return the proxy's response, sanitized.
EXPECT_THAT(rv, IsError(ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT));
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
const ProxyClientSocket* tunnel_socket =
static_cast<ProxyClientSocket*>(handle_.socket());
const HttpResponseInfo* response = tunnel_socket->GetConnectResponseInfo();
const HttpResponseHeaders* headers = response->headers.get();
// Make sure Set-Cookie header was stripped.
EXPECT_FALSE(headers->HasHeader("set-cookie"));
// Make sure Content-Length: 0 header was added.
EXPECT_TRUE(headers->HasHeaderValue("content-length", "0"));
// Make sure Location header was included and correct.
std::string location;
EXPECT_TRUE(headers->IsRedirect(&location));
EXPECT_EQ(location, redirectTarget);
}
}
TEST_P(HttpProxyClientSocketPoolTest, ProxyPoolMinTimeout) {
// Set RTT estimate to a low value.
base::TimeDelta rtt_estimate = base::TimeDelta::FromMilliseconds(1);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_LE(base::TimeDelta(), pool_->ConnectionTimeout());
// Test against a large value.
EXPECT_GE(base::TimeDelta::FromMinutes(10), pool_->ConnectionTimeout());
#if (defined(OS_ANDROID) || defined(OS_IOS))
EXPECT_EQ(base::TimeDelta::FromSeconds(8), pool_->ConnectionTimeout());
#else
EXPECT_EQ(base::TimeDelta::FromSeconds(30), pool_->ConnectionTimeout());
#endif
}
TEST_P(HttpProxyClientSocketPoolTest, ProxyPoolMaxTimeout) {
// Set RTT estimate to a high value.
base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(100);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_LE(base::TimeDelta(), pool_->ConnectionTimeout());
// Test against a large value.
EXPECT_GE(base::TimeDelta::FromMinutes(10), pool_->ConnectionTimeout());
#if (defined(OS_ANDROID) || defined(OS_IOS))
EXPECT_EQ(base::TimeDelta::FromSeconds(30), pool_->ConnectionTimeout());
#else
EXPECT_EQ(base::TimeDelta::FromSeconds(60), pool_->ConnectionTimeout());
#endif
}
// Cobalt does not need FieldTrial yet.
#if !defined(STARBOARD)
// Tests the connection timeout values when the field trial parameters are
// specified.
TEST_P(HttpProxyClientSocketPoolTest, ProxyPoolTimeoutWithExperiment) {
// 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(), pool_->ConnectionTimeout());
base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(4);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
base::TimeDelta expected_connection_timeout = kMultiplier * rtt_estimate;
EXPECT_EQ(expected_connection_timeout, pool_->ConnectionTimeout());
// Connection timeout should not exceed kMaxTimeout.
rtt_estimate = base::TimeDelta::FromSeconds(25);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMaxTimeout, pool_->ConnectionTimeout());
// Connection timeout should not be less than kMinTimeout.
rtt_estimate = base::TimeDelta::FromSeconds(0);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMinTimeout, pool_->ConnectionTimeout());
}
// Tests the connection timeout values when the field trial parameters are
// specified.
TEST_P(HttpProxyClientSocketPoolTest,
ProxyPoolTimeoutWithExperimentDifferentParams) {
// 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(), pool_->ConnectionTimeout());
base::TimeDelta rtt_estimate = base::TimeDelta::FromSeconds(2);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMultiplier * rtt_estimate, pool_->ConnectionTimeout());
// A change in RTT estimate should also change the connection timeout.
rtt_estimate = base::TimeDelta::FromSeconds(7);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMultiplier * rtt_estimate, pool_->ConnectionTimeout());
// Connection timeout should not exceed kMaxTimeout.
rtt_estimate = base::TimeDelta::FromSeconds(35);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMaxTimeout, pool_->ConnectionTimeout());
// Connection timeout should not be less than kMinTimeout.
rtt_estimate = base::TimeDelta::FromSeconds(0);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_EQ(kMinTimeout, pool_->ConnectionTimeout());
}
TEST_P(HttpProxyClientSocketPoolTest, ProxyPoolTimeoutWithConnectionProperty) {
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);
HttpProxyClientSocketPool::HttpProxyConnectJobFactory job_factory(
transport_socket_pool(), ssl_socket_pool(), estimator(), nullptr);
const base::TimeDelta kRttEstimate = base::TimeDelta::FromSeconds(2);
estimator()->SetStartTimeNullHttpRtt(kRttEstimate);
// By default, connection timeout should return the timeout for secure
// proxies.
EXPECT_EQ(kSecureMultiplier * kRttEstimate, job_factory.ConnectionTimeout());
EXPECT_EQ(kSecureMultiplier * kRttEstimate,
job_factory.ConnectionTimeoutWithConnectionProperty(true));
EXPECT_EQ(kNonSecureMultiplier * kRttEstimate,
job_factory.ConnectionTimeoutWithConnectionProperty(false));
}
#endif
// Tests the connection timeout values when the field trial parameters are not
// specified.
TEST_P(HttpProxyClientSocketPoolTest,
ProxyPoolTimeoutWithExperimentDefaultParams) {
InitAdaptiveTimeoutFieldTrialWithParams(true, 0, 0, base::TimeDelta(),
base::TimeDelta());
EXPECT_LE(base::TimeDelta(), pool_->ConnectionTimeout());
// Timeout should be |http_rtt_multiplier| times the HTTP RTT
// estimate.
base::TimeDelta rtt_estimate = base::TimeDelta::FromMilliseconds(10);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
// Connection timeout should not be less than the HTTP RTT estimate.
EXPECT_LE(rtt_estimate, pool_->ConnectionTimeout());
// A change in RTT estimate should also change the connection timeout.
rtt_estimate = base::TimeDelta::FromSeconds(10);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
// Connection timeout should not be less than the HTTP RTT estimate.
EXPECT_LE(rtt_estimate, pool_->ConnectionTimeout());
// Set RTT to a very large value.
rtt_estimate = base::TimeDelta::FromMinutes(60);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_GT(rtt_estimate, pool_->ConnectionTimeout());
// Set RTT to a very small value.
rtt_estimate = base::TimeDelta::FromSeconds(0);
estimator()->SetStartTimeNullHttpRtt(rtt_estimate);
EXPECT_LT(rtt_estimate, pool_->ConnectionTimeout());
}
// It would be nice to also test the timeouts in HttpProxyClientSocketPool.
// Test that SocketTag passed into HttpProxyClientSocketPool is applied to
// returned underlying TCP sockets.
#if defined(OS_ANDROID)
TEST_P(HttpProxyClientSocketPoolTest, Tag) {
Initialize(base::span<MockRead>(), base::span<MockWrite>(),
base::span<MockRead>(), base::span<MockWrite>());
SocketTag tag1(SocketTag::UNSET_UID, 0x12345678);
SocketTag tag2(getuid(), 0x87654321);
// Verify requested socket is tagged properly.
int rv =
handle_.Init("a", CreateNoTunnelParams(), LOW, tag1,
ClientSocketPool::RespectLimits::ENABLED,
CompletionOnceCallback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsOk());
EXPECT_TRUE(handle_.is_initialized());
ASSERT_TRUE(handle_.socket());
EXPECT_TRUE(handle_.socket()->IsConnected());
EXPECT_EQ(socket_factory()->GetLastProducedTCPSocket()->tag(), tag1);
EXPECT_TRUE(
socket_factory()->GetLastProducedTCPSocket()->tagged_before_connected());
// Verify reused socket is retagged properly.
StreamSocket* socket = handle_.socket();
handle_.Reset();
rv = handle_.Init("a", CreateNoTunnelParams(), LOW, tag2,
ClientSocketPool::RespectLimits::ENABLED,
CompletionOnceCallback(), pool_.get(), NetLogWithSource());
EXPECT_THAT(rv, IsOk());
EXPECT_TRUE(handle_.socket());
EXPECT_TRUE(handle_.socket()->IsConnected());
EXPECT_EQ(handle_.socket(), socket);
EXPECT_EQ(socket_factory()->GetLastProducedTCPSocket()->tag(), tag2);
handle_.socket()->Disconnect();
handle_.Reset();
}
#endif
} // namespace net