blob: 63bf2f8f439d3186d461f67d9260aa3460ad09f5 [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/socket/client_socket_pool.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/check_op.h"
#include "base/feature_list.h"
#include "net/base/features.h"
#include "net/base/host_port_pair.h"
#include "net/base/proxy_server.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/http_proxy_connect_job.h"
#include "net/log/net_log_event_type.h"
#include "net/log/net_log_with_source.h"
#include "net/socket/connect_job.h"
#include "net/socket/connect_job_factory.h"
#include "net/socket/socks_connect_job.h"
#include "net/socket/ssl_connect_job.h"
#include "net/socket/stream_socket.h"
#include "net/spdy/spdy_session.h"
#include "net/spdy/spdy_session_pool.h"
#include "url/gurl.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace net {
namespace {
// The maximum duration, in seconds, to keep used idle persistent sockets alive.
int64_t g_used_idle_socket_timeout_s = 300; // 5 minutes
// Invoked by the transport socket pool after host resolution is complete
// to allow the connection to be aborted, if a matching SPDY session can
// be found. Returns OnHostResolutionCallbackResult::kMayBeDeletedAsync if such
// a session is found, as it will post a task that may delete the calling
// ConnectJob. Also returns kMayBeDeletedAsync if there may already be such
// a task posted.
OnHostResolutionCallbackResult OnHostResolution(
SpdySessionPool* spdy_session_pool,
const SpdySessionKey& spdy_session_key,
bool is_for_websockets,
const HostPortPair& host_port_pair,
const AddressList& addresses) {
DCHECK(host_port_pair == spdy_session_key.host_port_pair());
// It is OK to dereference spdy_session_pool, because the
// ClientSocketPoolManager will be destroyed in the same callback that
// destroys the SpdySessionPool.
return spdy_session_pool->OnHostResolutionComplete(
spdy_session_key, is_for_websockets, addresses);
}
} // namespace
ClientSocketPool::SocketParams::SocketParams(
std::unique_ptr<SSLConfig> ssl_config_for_origin,
std::unique_ptr<SSLConfig> ssl_config_for_proxy)
: ssl_config_for_origin_(std::move(ssl_config_for_origin)),
ssl_config_for_proxy_(std::move(ssl_config_for_proxy)) {}
ClientSocketPool::SocketParams::~SocketParams() = default;
scoped_refptr<ClientSocketPool::SocketParams>
ClientSocketPool::SocketParams::CreateForHttpForTesting() {
return base::MakeRefCounted<SocketParams>(nullptr /* ssl_config_for_origin */,
nullptr /* ssl_config_for_proxy */);
}
ClientSocketPool::GroupId::GroupId()
: privacy_mode_(PrivacyMode::PRIVACY_MODE_DISABLED) {}
ClientSocketPool::GroupId::GroupId(url::SchemeHostPort destination,
PrivacyMode privacy_mode,
NetworkIsolationKey network_isolation_key,
SecureDnsPolicy secure_dns_policy)
: destination_(std::move(destination)),
privacy_mode_(privacy_mode),
network_isolation_key_(
base::FeatureList::IsEnabled(
features::kPartitionConnectionsByNetworkIsolationKey)
? std::move(network_isolation_key)
: NetworkIsolationKey()),
secure_dns_policy_(secure_dns_policy) {
DCHECK(destination_.IsValid());
// ClientSocketPool only expected to be used for HTTP/HTTPS/WS/WSS cases, and
// "ws"/"wss" schemes should be converted to "http"/"https" equivalent first.
DCHECK(destination_.scheme() == url::kHttpScheme ||
destination_.scheme() == url::kHttpsScheme);
}
ClientSocketPool::GroupId::GroupId(const GroupId& group_id) = default;
ClientSocketPool::GroupId::~GroupId() = default;
ClientSocketPool::GroupId& ClientSocketPool::GroupId::operator=(
const GroupId& group_id) = default;
ClientSocketPool::GroupId& ClientSocketPool::GroupId::operator=(
GroupId&& group_id) = default;
std::string ClientSocketPool::GroupId::ToString() const {
std::string result = destination_.Serialize();
if (privacy_mode_)
result = "pm/" + result;
if (base::FeatureList::IsEnabled(
features::kPartitionConnectionsByNetworkIsolationKey)) {
result += " <";
result += network_isolation_key_.ToDebugString();
result += ">";
}
switch (secure_dns_policy_) {
case SecureDnsPolicy::kAllow:
break;
case SecureDnsPolicy::kDisable:
result = "dsd/" + result;
break;
case SecureDnsPolicy::kBootstrap:
result = "dns_bootstrap/" + result;
break;
}
return result;
}
ClientSocketPool::~ClientSocketPool() = default;
// static
base::TimeDelta ClientSocketPool::used_idle_socket_timeout() {
return base::Seconds(g_used_idle_socket_timeout_s);
}
// static
void ClientSocketPool::set_used_idle_socket_timeout(base::TimeDelta timeout) {
DCHECK_GT(timeout.InSeconds(), 0);
g_used_idle_socket_timeout_s = timeout.InSeconds();
}
ClientSocketPool::ClientSocketPool(
bool is_for_websockets,
const CommonConnectJobParams* common_connect_job_params,
std::unique_ptr<ConnectJobFactory> connect_job_factory)
: is_for_websockets_(is_for_websockets),
common_connect_job_params_(common_connect_job_params),
connect_job_factory_(std::move(connect_job_factory)) {}
void ClientSocketPool::NetLogTcpClientSocketPoolRequestedSocket(
const NetLogWithSource& net_log,
const GroupId& group_id) {
// TODO(eroman): Split out the host and port parameters.
net_log.AddEvent(NetLogEventType::TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKET,
[&] { return NetLogGroupIdParams(group_id); });
}
base::Value ClientSocketPool::NetLogGroupIdParams(const GroupId& group_id) {
base::Value event_params(base::Value::Type::DICTIONARY);
event_params.SetStringKey("group_id", group_id.ToString());
return event_params;
}
std::unique_ptr<ConnectJob> ClientSocketPool::CreateConnectJob(
GroupId group_id,
scoped_refptr<SocketParams> socket_params,
const ProxyServer& proxy_server,
const absl::optional<NetworkTrafficAnnotationTag>& proxy_annotation_tag,
RequestPriority request_priority,
SocketTag socket_tag,
ConnectJob::Delegate* delegate) {
bool using_ssl = GURL::SchemeIsCryptographic(group_id.destination().scheme());
// If applicable, set up a callback to handle checking for H2 IP pooling
// opportunities.
OnHostResolutionCallback resolution_callback;
if (using_ssl && proxy_server.is_direct()) {
resolution_callback = base::BindRepeating(
&OnHostResolution, common_connect_job_params_->spdy_session_pool,
// TODO(crbug.com/1206799): Pass along as SchemeHostPort.
SpdySessionKey(HostPortPair::FromSchemeHostPort(group_id.destination()),
proxy_server, group_id.privacy_mode(),
SpdySessionKey::IsProxySession::kFalse, socket_tag,
group_id.network_isolation_key(),
group_id.secure_dns_policy()),
is_for_websockets_);
} else if (proxy_server.is_https()) {
resolution_callback = base::BindRepeating(
&OnHostResolution, common_connect_job_params_->spdy_session_pool,
SpdySessionKey(proxy_server.host_port_pair(), ProxyServer::Direct(),
group_id.privacy_mode(),
SpdySessionKey::IsProxySession::kTrue, socket_tag,
group_id.network_isolation_key(),
group_id.secure_dns_policy()),
is_for_websockets_);
}
return connect_job_factory_->CreateConnectJob(
group_id.destination(), proxy_server, proxy_annotation_tag,
socket_params->ssl_config_for_origin(),
socket_params->ssl_config_for_proxy(), is_for_websockets_,
group_id.privacy_mode(), resolution_callback, request_priority,
socket_tag, group_id.network_isolation_key(),
group_id.secure_dns_policy(), common_connect_job_params_, delegate);
}
} // namespace net