blob: 0d0bf4a5685441b149df7403fad186b4b6456c9c [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_SPDY_SPDY_SESSION_POOL_H_
#define NET_SPDY_SPDY_SESSION_POOL_H_
#include <stddef.h>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_timing_info.h"
#include "net/base/net_errors.h"
#include "net/base/net_export.h"
#include "net/base/network_change_notifier.h"
#include "net/base/proxy_server.h"
#include "net/dns/public/host_resolver_results.h"
#include "net/log/net_log_source.h"
#include "net/proxy_resolution/proxy_config.h"
#include "net/socket/connect_job.h"
#include "net/socket/ssl_client_socket.h"
#include "net/spdy/http2_push_promise_index.h"
#include "net/spdy/server_push_delegate.h"
#include "net/spdy/spdy_session_key.h"
#include "net/ssl/ssl_config_service.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace net {
class ClientSocketHandle;
class HostResolver;
class HttpServerProperties;
class NetLogWithSource;
class NetworkQualityEstimator;
class SpdySession;
class StreamSocket;
class TransportSecurityState;
// This is a very simple pool for open SpdySessions.
class NET_EXPORT SpdySessionPool
: public NetworkChangeNotifier::IPAddressObserver,
public SSLClientContext::Observer {
public:
typedef base::TimeTicks (*TimeFunc)();
// Struct to hold randomly generated frame parameters to be used for sending
// frames on the wire to "grease" frame type. Frame type has to be one of
// the reserved values defined in
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
struct GreasedHttp2Frame {
uint8_t type;
uint8_t flags;
std::string payload;
};
// A request for a SpdySession with a particular SpdySessionKey. The
// SpdySessionPool's RequestSession() creates these. The Delegate's
// OnSpdySessionAvailable() method will be invoked when a matching SpdySession
// is added to the pool. The Delegate's OnSpdySessionAvailable() method will
// be invoked at most once for a single SpdySessionRequest.
//
// Destroying the request will stop watching the pool for such a session. The
// request must be destroyed before the SpdySessionPool is.
class NET_EXPORT_PRIVATE SpdySessionRequest {
public:
// Interface for watching for when a SpdySession with a provided key is
// created.
class NET_EXPORT_PRIVATE Delegate {
public:
Delegate();
Delegate(const Delegate&) = delete;
Delegate& operator=(const Delegate&) = delete;
virtual ~Delegate();
// |spdy_session| will not be null.
virtual void OnSpdySessionAvailable(
base::WeakPtr<SpdySession> spdy_session) = 0;
};
// Constructor - this is called by the SpdySessionPool.
SpdySessionRequest(const SpdySessionKey& key,
bool enable_ip_based_pooling,
bool is_websocket,
bool is_blocking_request_for_session,
Delegate* delegate,
SpdySessionPool* spdy_session_pool);
SpdySessionRequest(const SpdySessionRequest&) = delete;
SpdySessionRequest& operator=(const SpdySessionRequest&) = delete;
~SpdySessionRequest();
// Called by SpdySessionPool to signal that the request has been removed
// from the SpdySessionPool.
void OnRemovedFromPool();
const SpdySessionKey& key() const { return key_; }
bool enable_ip_based_pooling() const { return enable_ip_based_pooling_; }
bool is_websocket() const { return is_websocket_; }
bool is_blocking_request_for_session() const {
return is_blocking_request_for_session_;
}
Delegate* delegate() { return delegate_; }
// The associated SpdySessionPool, or nullptr if OnRemovedFromPool() has
// been called.
SpdySessionPool* spdy_session_pool() { return spdy_session_pool_; }
private:
const SpdySessionKey key_;
const bool enable_ip_based_pooling_;
const bool is_websocket_;
const bool is_blocking_request_for_session_;
const raw_ptr<Delegate> delegate_;
raw_ptr<SpdySessionPool> spdy_session_pool_;
};
SpdySessionPool(HostResolver* host_resolver,
SSLClientContext* ssl_client_context,
HttpServerProperties* http_server_properties,
TransportSecurityState* transport_security_state,
const quic::ParsedQuicVersionVector& quic_supported_versions,
bool enable_ping_based_connection_checking,
bool is_http_enabled,
bool is_quic_enabled,
size_t session_max_recv_window_size,
int session_max_queued_capped_frames,
const spdy::SettingsMap& initial_settings,
bool enable_http2_settings_grease,
const absl::optional<GreasedHttp2Frame>& greased_http2_frame,
bool http2_end_stream_with_data_frame,
bool enable_priority_update,
bool go_away_on_ip_change,
SpdySessionPool::TimeFunc time_func,
NetworkQualityEstimator* network_quality_estimator,
bool cleanup_sessions_on_ip_address_changed);
SpdySessionPool(const SpdySessionPool&) = delete;
SpdySessionPool& operator=(const SpdySessionPool&) = delete;
~SpdySessionPool() override;
// In the functions below, a session is "available" if this pool has
// a reference to it and there is some SpdySessionKey for which
// FindAvailableSession() will return it. A session is "unavailable"
// if this pool has a reference to it but it won't be returned by
// FindAvailableSession() for any SpdySessionKey; for example, this
// can happen when a session receives a GOAWAY frame and is still
// processing existing streams.
// Create a new SPDY session from an existing socket. There must
// not already be a session for the given key.
//
// Returns OK on success and sets |*session| to point to the new SpdySession.
// Returns a net error code on failure, in which case the value of |*session|
// is undefined.
//
// Note that the SpdySession begins reading from |client_socket_handle| on a
// subsequent event loop iteration, so it may be closed immediately afterwards
// if the first read of |client_socket_handle| fails.
int CreateAvailableSessionFromSocketHandle(
const SpdySessionKey& key,
std::unique_ptr<ClientSocketHandle> client_socket_handle,
const NetLogWithSource& net_log,
base::WeakPtr<SpdySession>* session);
// Just like the above method, except it takes a SocketStream instead of a
// ClientSocketHandle, and separate connect timing information. When this
// constructor is used, there is no socket pool beneath the SpdySession.
// Instead, the session takes exclusive ownership of the underting socket, and
// destroying the session will directly destroy the socket, as opposed to
// disconnected it and then returning it to the socket pool. This is intended
// for use with H2 proxies, which are layered beneath the socket pools and
// can have sockets above them for tunnels, which are put in a socket pool.
base::WeakPtr<SpdySession> CreateAvailableSessionFromSocket(
const SpdySessionKey& key,
std::unique_ptr<StreamSocket> socket_stream,
const LoadTimingInfo::ConnectTiming& connect_timing,
const NetLogWithSource& net_log);
// If there is an available session for |key|, return it.
// Otherwise if there is a session to pool to based on IP address:
// * if |enable_ip_based_pooling == true|,
// then mark it as available for |key| and return it;
// * if |enable_ip_based_pooling == false|,
// then remove it from the available sessions, and return nullptr.
// Otherwise return nullptr.
base::WeakPtr<SpdySession> FindAvailableSession(
const SpdySessionKey& key,
bool enable_ip_based_pooling,
bool is_websocket,
const NetLogWithSource& net_log);
// Returns true if there is an available session for |key|.
bool HasAvailableSession(const SpdySessionKey& key, bool is_websocket) const;
// Just like FindAvailableSession.
//
// Additionally, if it returns nullptr, populates |spdy_session_request| with
// a request that will invoke |delegate| once a matching SPDY session becomes
// available through the creation of a new SpdySession (as opposed to by
// creating an alias for an existing session with a new host).
//
// |is_blocking_request_for_session| will be set to |true| if there is not
// another "blocking" request already pending. For example, the first request
// created will be considered "blocking", but subsequent requests will not as
// long as the "blocking" request is not destroyed. Once the "blocking"
// request is destroyed, the next created request will be marked "blocking".
//
// If a request is created, that request is not the "blocking" request, and
// |on_blocking_request_destroyed_callback| is non-null, then
// |on_blocking_request_destroyed_callback| will be invoked asynchronously
// when the "blocking" request is destroyed. The callback associated with the
// "blocking" request is never invoked.
//
// |delegate|, |spdy_session_request|, and |is_blocking_request_for_session|
// must all be non-null.
//
// TODO(mmenke): Merge this into FindAvailableSession().
// TODO(mmenke): Don't invoke |on_blocking_request_destroyed_callback| when
// all requests for a session have been successfully responded to.
base::WeakPtr<SpdySession> RequestSession(
const SpdySessionKey& key,
bool enable_ip_based_pooling,
bool is_websocket,
const NetLogWithSource& net_log,
base::RepeatingClosure on_blocking_request_destroyed_callback,
SpdySessionRequest::Delegate* delegate,
std::unique_ptr<SpdySessionRequest>* spdy_session_request,
bool* is_blocking_request_for_session);
// Invoked when a host resolution completes. Returns
// OnHostResolutionCallbackResult::kMayBeDeletedAsync if there's a SPDY
// session that's a suitable alias for |key|, setting up the alias if needed.
OnHostResolutionCallbackResult OnHostResolutionComplete(
const SpdySessionKey& key,
bool is_websocket,
const std::vector<HostResolverEndpointResult>& endpoint_results,
const std::set<std::string>& aliases);
// Remove all mappings and aliases for the given session, which must
// still be available. Except for in tests, this must be called by
// the given session itself.
void MakeSessionUnavailable(
const base::WeakPtr<SpdySession>& available_session);
// Removes an unavailable session from the pool. Except for in
// tests, this must be called by the given session itself.
void RemoveUnavailableSession(
const base::WeakPtr<SpdySession>& unavailable_session);
// Note that the next three methods close sessions, potentially notifing
// delegates of error or synchronously invoking callbacks, which might trigger
// retries, thus opening new sessions.
// Close only the currently existing SpdySessions with |error|.
// Let any new ones created while this method is running continue to
// live.
void CloseCurrentSessions(Error error);
// Close only the currently existing SpdySessions that are idle.
// Let any new ones created while this method is running continue to
// live.
void CloseCurrentIdleSessions(const std::string& description);
// Repeatedly close all SpdySessions until all of them (including new ones
// created in the process of closing the current ones, and new ones created in
// the process of closing those new ones, etc.) are unavailable.
void CloseAllSessions();
// Mark all current sessions as going away.
void MakeCurrentSessionsGoingAway(Error error);
// Creates a Value summary of the state of the spdy session pool.
std::unique_ptr<base::Value> SpdySessionPoolInfoToValue() const;
HttpServerProperties* http_server_properties() {
return http_server_properties_;
}
Http2PushPromiseIndex* push_promise_index() { return &push_promise_index_; }
void set_server_push_delegate(ServerPushDelegate* push_delegate) {
push_delegate_ = push_delegate;
}
// NetworkChangeNotifier::IPAddressObserver methods:
// We flush all idle sessions and release references to the active ones so
// they won't get re-used. The active ones will either complete successfully
// or error out due to the IP address change.
void OnIPAddressChanged() override;
// SSLClientContext::Observer methods:
// We perform the same flushing as described above when SSL settings change.
void OnSSLConfigChanged(
SSLClientContext::SSLConfigChangeType change_type) override;
// Makes all sessions using |server|'s SSL configuration unavailable, meaning
// they will not be used to service new streams. Does not close any existing
// streams.
void OnSSLConfigForServerChanged(const HostPortPair& server) override;
void set_network_quality_estimator(
NetworkQualityEstimator* network_quality_estimator) {
network_quality_estimator_ = network_quality_estimator;
}
// Returns the stored DNS aliases for the session key.
std::set<std::string> GetDnsAliasesForSessionKey(
const SpdySessionKey& key) const;
private:
friend class SpdySessionPoolPeer; // For testing.
using SessionSet = std::set<SpdySession*>;
using WeakSessionList = std::vector<base::WeakPtr<SpdySession>>;
using AvailableSessionMap =
std::map<SpdySessionKey, base::WeakPtr<SpdySession>>;
using AliasMap = std::multimap<IPEndPoint, SpdySessionKey>;
using DnsAliasesBySessionKeyMap =
std::map<SpdySessionKey, std::set<std::string>>;
using RequestSet = std::set<SpdySessionRequest*>;
struct RequestInfoForKey {
RequestInfoForKey();
~RequestInfoForKey();
// Whether one of the requests in |RequestSet| has its
// is_blocking_request_for_session() bit set.
bool has_blocking_request = false;
RequestSet request_set;
// Set of callbacks watching for the blocking request to be destroyed.
std::list<base::RepeatingClosure> deferred_callbacks;
};
using SpdySessionRequestMap = std::map<SpdySessionKey, RequestInfoForKey>;
// Removes |request| from |spdy_session_request_map_|.
void RemoveRequestForSpdySession(SpdySessionRequest* request);
// Returns true iff |session| is in |available_sessions_|.
bool IsSessionAvailable(const base::WeakPtr<SpdySession>& session) const;
// Map the given key to the given session. There must not already be a
// mapping for `key`. Also adds an entry for `key` and `dns_aliases` in
// `dns_aliases_by_session_key_`. If there are already DNS aliases for the
// given key, replaces them.
void MapKeyToAvailableSession(const SpdySessionKey& key,
const base::WeakPtr<SpdySession>& session,
std::set<std::string> dns_aliases);
// Returns an iterator into |available_sessions_| for the given key,
// which may be equal to |available_sessions_.end()|.
AvailableSessionMap::iterator LookupAvailableSessionByKey(
const SpdySessionKey& key);
// Remove the mapping of the given key, which must exist. Also erases the
// key-value pair of SpdySessionKey and DNS aliases from the
// `dns_aliases_by_session_key_` map.
void UnmapKey(const SpdySessionKey& key);
// Remove all aliases for |key| from the aliases table.
void RemoveAliases(const SpdySessionKey& key);
// Get a copy of the current sessions as a list of WeakPtrs. Used by
// CloseCurrentSessionsHelper() below.
WeakSessionList GetCurrentSessions() const;
// Close only the currently existing SpdySessions with |error|. Let
// any new ones created while this method is running continue to
// live. If |idle_only| is true only idle sessions are closed.
void CloseCurrentSessionsHelper(Error error,
const std::string& description,
bool idle_only);
// Creates a new session. The session must be initialized before
// InsertSession() is invoked.
std::unique_ptr<SpdySession> CreateSession(const SpdySessionKey& key,
NetLog* net_log);
// Adds a new session previously created with CreateSession to the pool.
// |source_net_log| is the NetLog for the object that created the session.
base::WeakPtr<SpdySession> InsertSession(
const SpdySessionKey& key,
std::unique_ptr<SpdySession> new_session,
const NetLogWithSource& source_net_log,
std::set<std::string> dns_aliases);
// If a session with the specified |key| exists, invokes
// OnSpdySessionAvailable on all matching members of
// |spdy_session_request_map_|, removing them from the map. Regardless of
// whether or not such key exists, invokes all corresponding callbacks
// currently in |spdy_session_pending_request_map_|.
void UpdatePendingRequests(const SpdySessionKey& key);
// Removes the SpdySessionRequest at |request_set_iterator| from the
// RequestSet at |request_map_iterator| and calls OnRemovedFromPool() on the
// request. If the RequestSet becomes empty, also removes it from
// |spdy_session_request_map_|.
void RemoveRequestInternal(
SpdySessionRequestMap::iterator request_map_iterator,
RequestSet::iterator request_set_iterator);
raw_ptr<HttpServerProperties> http_server_properties_;
raw_ptr<TransportSecurityState> transport_security_state_;
// The set of all sessions. This is a superset of the sessions in
// |available_sessions_|.
//
// |sessions_| owns all its SpdySession objects.
SessionSet sessions_;
// This is a map of available sessions by key. A session may appear
// more than once in this map if it has aliases.
AvailableSessionMap available_sessions_;
// A map of IPEndPoint aliases for sessions.
AliasMap aliases_;
// A map of DNS alias vectors by session keys.
DnsAliasesBySessionKeyMap dns_aliases_by_session_key_;
// The index of all unclaimed pushed streams of all SpdySessions in this pool.
Http2PushPromiseIndex push_promise_index_;
const raw_ptr<SSLClientContext> ssl_client_context_;
const raw_ptr<HostResolver> resolver_;
// Versions of QUIC which may be used.
const quic::ParsedQuicVersionVector quic_supported_versions_;
// Defaults to true. May be controlled via SpdySessionPoolPeer for tests.
bool enable_sending_initial_data_ = true;
bool enable_ping_based_connection_checking_;
const bool is_http2_enabled_;
const bool is_quic_enabled_;
size_t session_max_recv_window_size_;
// Maximum number of capped frames that can be queued at any time.
int session_max_queued_capped_frames_;
// Settings that are sent in the initial SETTINGS frame
// (if |enable_sending_initial_data_| is true),
// and also control SpdySession parameters like initial receive window size
// and maximum HPACK dynamic table size.
const spdy::SettingsMap initial_settings_;
// If true, a setting parameter with reserved identifier will be sent in every
// initial SETTINGS frame, see
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
// The setting identifier and value will be drawn independently for each
// connection to prevent tracking of the client.
const bool enable_http2_settings_grease_;
// If set, an HTTP/2 frame with a reserved frame type will be sent after
// every HTTP/2 SETTINGS frame and before every HTTP/2 DATA frame. See
// https://tools.ietf.org/html/draft-bishop-httpbis-grease-00.
const absl::optional<GreasedHttp2Frame> greased_http2_frame_;
// If set, the HEADERS frame carrying a request without body will not have the
// END_STREAM flag set. The stream will be closed by a subsequent empty DATA
// frame with END_STREAM. Does not affect bidirectional or proxy streams.
// If unset, the HEADERS frame will have the END_STREAM flag set on.
// This is useful in conjuction with |greased_http2_frame_| so that a frame
// of reserved type can be sent out even on requests without a body.
const bool http2_end_stream_with_data_frame_;
// If true, enable sending PRIORITY_UPDATE frames until SETTINGS frame
// arrives. After SETTINGS frame arrives, do not send PRIORITY_UPDATE frames
// any longer if SETTINGS_DEPRECATE_HTTP2_PRIORITIES is missing or has zero 0,
// but continue and also stop sending HTTP/2-style priority information in
// HEADERS frames and PRIORITY frames if it has value 1.
const bool enable_priority_update_;
// If set, sessions will be marked as going away upon relevant network changes
// (instead of being closed).
const bool go_away_on_ip_change_;
SpdySessionRequestMap spdy_session_request_map_;
TimeFunc time_func_;
raw_ptr<ServerPushDelegate> push_delegate_ = nullptr;
raw_ptr<NetworkQualityEstimator> network_quality_estimator_;
const bool cleanup_sessions_on_ip_address_changed_;
base::WeakPtrFactory<SpdySessionPool> weak_ptr_factory_{this};
};
} // namespace net
#endif // NET_SPDY_SPDY_SESSION_POOL_H_