| // 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_ |