blob: 577210bc1cb95bd951cc0b11a92dd6c97316fc9d [file] [log] [blame] [edit]
// Copyright 2024 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_HTTP_HTTP_STREAM_POOL_ATTEMPT_MANAGER_H_
#define NET_HTTP_HTTP_STREAM_POOL_ATTEMPT_MANAGER_H_
#include <memory>
#include <optional>
#include <set>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected.h"
#include "net/base/completion_once_callback.h"
#include "net/base/ip_endpoint.h"
#include "net/base/load_states.h"
#include "net/base/load_timing_info.h"
#include "net/base/load_timing_internal_info.h"
#include "net/base/net_error_details.h"
#include "net/base/net_export.h"
#include "net/base/priority_queue.h"
#include "net/base/request_priority.h"
#include "net/dns/host_resolver.h"
#include "net/dns/public/resolve_error_info.h"
#include "net/http/http_stream_pool.h"
#include "net/http/http_stream_pool_ip_endpoint_state_tracker.h"
#include "net/http/http_stream_pool_job.h"
#include "net/http/http_stream_request.h"
#include "net/log/net_log_with_source.h"
#include "net/quic/quic_session_pool.h"
#include "net/socket/connection_attempts.h"
#include "net/socket/next_proto.h"
#include "net/socket/stream_attempt.h"
#include "net/socket/stream_socket_close_reason.h"
#include "net/socket/stream_socket_handle.h"
#include "net/socket/tls_stream_attempt.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
namespace net {
class HttpNetworkSession;
class NetLog;
class HttpStreamKey;
// Drives connection attempts for a single destination.
//
// Maintains multiple in-flight Jobs for a single destination keyed by
// HttpStreamKey. Peforms DNS resolution and manages connection attempts.
// Delegates QUIC connection attempts to QuicAttempt. Upon successful HttpStream
// creations or fatal error occurrence, notify jobs of success or failure.
//
// Created by an HttpStreamPool::Group when new connection attempts are needed
// and destroyed when all jobs, in-flight attempts, and the QuicAttempt are
// completed.
class HttpStreamPool::AttemptManager
: public HostResolver::ServiceEndpointRequest::Delegate,
public IPEndPointStateTracker::Delegate {
public:
// Represents the initial attempt state of this manager.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// LINT.IfChange(InitialAttemptState)
enum class InitialAttemptState {
kOther = 0,
// CanUseQuic() && quic_version_.IsKnown() && !SupportsSpdy()
kCanUseQuicWithKnownVersion = 1,
// CanUseQuic() && quic_version_.IsKnown() && SupportsSpdy()
kCanUseQuicWithKnownVersionAndSupportsSpdy = 2,
// CanUseQuic() && !quic_version_.IsKnown() && !SupportsSpdy()
kCanUseQuicWithUnknownVersion = 3,
// CanUseQuic() && !quic_version_.IsKnown() && SupportsSpdy()
kCanUseQuicWithUnknownVersionAndSupportsSpdy = 4,
// !CanUseQuic() && quic_version_.IsKnown() && !SupportsSpdy()
kCannotUseQuicWithKnownVersion = 5,
// !CanUseQuic() && quic_version_.IsKnown() && SupportsSpdy()
kCannotUseQuicWithKnownVersionAndSupportsSpdy = 6,
// !CanUseQuic() && !quic_version_.IsKnown() && !SupportsSpdy()
kCannotUseQuicWithUnknownVersion = 7,
// !CanUseQuic() && !quic_version_.IsKnown() && SupportsSpdy()
kCannotUseQuicWithUnknownVersionAndSupportsSpdy = 8,
kMaxValue = kCannotUseQuicWithUnknownVersionAndSupportsSpdy,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/net/enums.xml:HttpStreamPoolInitialAttemptState)
// Time to delay connection attempts more than one when the destination is
// known to support HTTP/2, to avoid unnecessary socket connection
// establishments. See https://crbug.com/718576
static constexpr base::TimeDelta kSpdyThrottleDelay = base::Milliseconds(300);
// `group` must outlive `this`.
AttemptManager(Group* group, NetLog* net_log);
AttemptManager(const AttemptManager&) = delete;
AttemptManager& operator=(const AttemptManager&) = delete;
~AttemptManager() override;
const HttpStreamKey& stream_key() const;
const SpdySessionKey& spdy_session_key() const;
const QuicSessionAliasKey& quic_session_alias_key() const;
HttpNetworkSession* http_network_session() const;
SpdySessionPool* spdy_session_pool() const;
QuicSessionPool* quic_session_pool() const;
HttpStreamPool* pool();
const HttpStreamPool* pool() const;
Group* group() { return group_; }
HostResolver::ServiceEndpointRequest* service_endpoint_request() {
return service_endpoint_request_.get();
}
const perfetto::Track& track() const { return track_; }
std::optional<InitialAttemptState> initial_attempt_state() const {
return initial_attempt_state_;
}
bool is_shutting_down() const {
return availability_state_ != AvailabilityState::kAvailable;
}
int final_error_to_notify_jobs() const;
base::TimeTicks dns_resolution_start_time() const {
return dns_resolution_start_time_;
}
base::TimeTicks dns_resolution_end_time() const {
return dns_resolution_end_time_;
}
NextProtoSet allowed_alpns() const { return allowed_alpns_; }
const NetLogWithSource& net_log();
// Starts `job` for a stream request. Will call one of Job::Delegate methods
// to notify results.
void RequestStream(Job* job);
// Creates idle streams or sessions for `num_streams` be opened.
// Note that `job` will be notified once `this` has enough streams/sessions
// for `num_streams` be opened. This means that when there are two preconnect
// requests with `num_streams = 1`, all jobs are notified when one
// stream/session is established (not two).
void Preconnect(Job* job);
// HostResolver::ServiceEndpointRequest::Delegate implementation:
void OnServiceEndpointsUpdated() override;
void OnServiceEndpointRequestFinished(int rv) override;
// IPEndPointStateTracker::Delegate implementation:
HostResolver::ServiceEndpointRequest* GetServiceEndpointRequest() override;
bool IsSvcbOptional() override;
bool HasEnoughTcpBasedAttemptsForSlowIPEndPoint(
const IPEndPoint& ip_endpoint) override;
bool IsEndpointUsableForTcpBasedAttempt(const ServiceEndpoint& endpoint,
bool svcb_optional) override;
// Tries to process a single pending request/preconnect.
void ProcessPendingJob();
// Returns the number of request jobs that haven't yet been notified success
// or failure.
size_t RequestJobCount() const { return request_jobs_.size(); }
// Returns the number of request jobs that have already been notified success
// or failure.
size_t NotifiedRequestJobCount() const { return notified_jobs_.size(); }
// Returns the number of in-flight TCP based attempts.
size_t TcpBasedAttemptCount() const { return tcp_based_attempts_.size(); }
// Cancels all in-flight TCP based attempts.
void CancelTcpBasedAttempts(StreamSocketCloseReason reason);
// Called when `job` is going to be destroyed.
void OnJobComplete(Job* job);
// Cancels all jobs.
void CancelJobs(int error);
// Cancels the QuicAttempt if it exists.
void CancelQuicAttempt(int error);
// Returns the current load state.
LoadState GetLoadState() const;
// Called when the priority of `job` is set.
void SetJobPriority(Job* job, RequestPriority priority);
// Returns the highest priority in `jobs_` when there is at least one job.
// Otherwise, returns IDLE assuming this manager is doing preconnects.
RequestPriority GetPriority() const;
// Returns true when `this` is blocked by the pool's stream limit.
bool IsStalledByPoolLimit();
// Returns the SSLConfig to use for TLS connections, not incorporating any
// configuration based on the service endpoint.
SSLConfig GetBaseSSLConfig();
base::expected<ServiceEndpoint, TlsStreamAttempt::GetServiceEndpointError>
GetServiceEndpoint(const IPEndPoint& endpoint);
void OnTcpBasedAttemptComplete(TcpBasedAttempt* raw_attempt, int rv);
void OnTcpBasedAttemptSlow(TcpBasedAttempt* raw_attempt);
bool CanUseExistingQuicSession();
// Runs the TCP based attempt delay timer if TCP based attempts are blocked
// and the timer is not running. TcpBasedAttemptDelayBehavior specifies when
// this method is called.
void MaybeRunTcpBasedAttemptDelayTimer();
// Called when the QuicAttempt owned by `this` is completed.
void OnQuicAttemptComplete(QuicAttemptOutcome result);
// Called when the QuicAttempt owned by `this` is slow.
void OnQuicAttemptSlow();
// Retrieves information on the current state of `this` as a base::Value.
base::Value::Dict GetInfoAsValue() const;
base::Value::Dict GetStatesAsNetLogParams() const;
MultiplexedSessionCreationInitiator
CalculateMultiplexedSessionCreationInitiator();
std::optional<int> GetQuicAttemptResultForTesting() {
return quic_attempt_result_;
}
base::WeakPtr<AttemptManager> GetWeakPtrForTesting() {
return weak_ptr_factory_.GetWeakPtr();
}
QuicAttempt* quic_attempt_for_testing() const { return quic_attempt_.get(); }
void SetOnCompleteCallbackForTesting(base::OnceClosure callback);
private:
// Represents the availability of this instance. If not kAvailable, `this`
// can't handle new Jobs and this should not have in-flight attempts.
enum class AvailabilityState {
// Can handle new Jobs and make connection attempts.
kAvailable = 0,
// Is in preparation of a successful completion.
kDraining = 1,
// Is handling a fatal error.
kFailing = 2,
};
// Represents failure of connection attempts. Used to notify job of completion
// for failure cases.
enum class FailureKind {
kStreamFailed,
kCertifcateError,
kNeedsClientAuth,
};
// Represents reasons if future connection attempts could be blocked or not.
enum class CanAttemptResult {
kAttempt,
kNoPendingJob,
kBlockedTcpBasedAttempt,
kThrottledForSpdy,
kReachedGroupLimit,
kReachedPoolLimit,
};
// The state of TCP/TLS connection attempts.
enum class TcpBasedAttemptState {
kNotStarted,
kAttempting,
kSucceededAtLeastOnce,
kAllEndpointsFailed,
};
std::string_view InitialAttemptStateToString(InitialAttemptState state);
using JobQueue = PriorityQueue<raw_ptr<Job>>;
static std::string_view CanAttemptResultToString(CanAttemptResult result);
static std::string_view TcpBasedAttemptStateToString(
TcpBasedAttemptState state);
bool is_service_endpoint_request_finished() const {
return service_endpoint_request_finished_;
}
void SetInitialAttemptState();
InitialAttemptState CalculateInitialAttemptState();
bool UsingTls() const;
void StartInternal(Job* job);
void ResolveServiceEndpoint(RequestPriority initial_priority);
// Helper methods to reset ServiceEndpointRequest later.
// TODO(crbug.com/421299722, crbug.com/397597592): Remove these helper
// methods and reset ServiceEndpointRequest without PostTask(). We need to
// update the HostResolver's object management first. See comment #8 of
// crbug.com/397597592.
void ResetServiceEndpointRequestLater();
void ResetServiceEndpointRequest();
void RestrictAllowedProtocols(NextProtoSet allowed_alpns);
void MaybeChangeServiceEndpointRequestPriority();
// Called when service endpoint results have changed or finished.
void ProcessServiceEndpointChanges();
// Returns an active QUIC session when there is an active QUIC session that
// can be used for on-going jobs after service endpoint results have changed.
QuicChromiumClientSession* CanUseExistingQuicSessionAfterEndpointChanges();
// Returns an active SPDY session when there is an active SPDY session that
// can be used for on-going jobs after service endpoint results have changed.
base::WeakPtr<SpdySession> CanUseExistingSpdySessionAfterEndpointChanges();
// If `this` is ready to start cryptographic handshakes, notifies TCP based
// attempts that SSLConfigs are ready.
void MaybeNotifySSLConfigReady();
// Attempts QUIC sessions if QUIC can be used and `this` is ready to start
// cryptographic connection handshakes.
void MaybeAttemptQuic();
// Attempts connections if there are pending jobs and IPEndPoints that
// haven't failed. If `exclude_ip_endpoint` is given, exclude the IPEndPoint
// from attempts. If `max_attempts` is given, attempts connections up to
// `max_attempts`.
void MaybeAttemptTcpBased(
std::optional<IPEndPoint> exclude_ip_endpoint = std::nullopt,
std::optional<size_t> max_attempts = std::nullopt);
// Returns true if there are pending jobs and the pool and the group
// haven't reached stream limits. If the pool reached the stream limit, may
// close idle sockets in other groups. Also may cancel preconnects or trigger
// `spdy_throttle_timer_`.
bool IsTcpBasedAttemptReady();
// Actual implementation of IsConnectionAttemptReady(), without having side
// effects.
CanAttemptResult CanAttemptConnection() const;
// Returns true only when there are no jobs that ignore the pool and group
// limits.
bool ShouldRespectLimits() const;
// Returns true only when there are no jobs that disable IP based pooling for
// HTTP/2. Note that this does nothing with QUIC.
bool IsIpBasedPoolingEnabledForH2() const;
// Returns true only when there are no jobs that disable alternative services.
bool IsAlternativeServiceEnabled() const;
// Returns true when the destination is known to support HTTP/2. Note that
// this could return false while initializing HttpServerProperties.
bool SupportsSpdy() const;
// Returns true when connection attempts should be throttled because there is
// an in-flight TCP based attempt and the destination is known to support
// HTTP/2.
bool ShouldThrottleAttemptForSpdy() const;
// Calculates the maximum streams counts requested by preconnects.
size_t CalculateMaxPreconnectCount() const;
// Calculates the number of TCP based attempts required to satisfy
// preconnects.
size_t CalculateRequiredTcpBasedAttemptForPreconnect() const;
// Returns the number of TCP based attempts that are not considered as slow.
size_t NonSlowTcpBasedAttemptCount() const;
// Returns a QUIC endpoint to make a connection attempt. See the comments in
// QuicSessionPool::SelectQuicVersion() for the criteria to select a QUIC
// endpoint.
std::optional<QuicEndpoint> GetQuicEndpointToAttempt();
// Called when this gets a fatal error. Notifies all jobs of the failure and
// cancels in-flight TCP based attempts and QuicAttempt's, if they exist.
void HandleFinalError(int error);
// Calculate the failure kind to notify jobs of failure. Used to call one of
// the job's methods.
FailureKind DetermineFailureKind();
// Notifies a failure to a single request job. Used by NotifyFailure().
void NotifyJobOfFailure();
// Notifies all preconnects of completion.
void NotifyPreconnectsComplete(int rv);
// Called after completion of a connection attempt to decrement stream
// counts in preconnect entries. Invokes the callback of an entry when the
// entry's stream counts is less than or equal to `active_stream_count`
// (i.e., `this` has enough streams).
void ProcessPreconnectsAfterAttemptComplete(int rv,
size_t active_stream_count);
// Notifies a job of preconnect completion.
void NotifyJobOfPreconnectComplete(raw_ptr<Job> job, int rv);
// Creates a text based stream and Notifies the highest priority job.
void CreateTextBasedStreamAndNotify(
std::unique_ptr<StreamSocket> stream_socket,
StreamSocketHandle::SocketReuseType reuse_type,
LoadTimingInfo::ConnectTiming connect_timing);
bool HasAvailableSpdySession() const;
void MaybeStartDraining();
void MaybeCreateSpdyStreamAndNotify(base::WeakPtr<SpdySession> spdy_session,
SessionSource session_source);
void MaybeCreateQuicStreamAndNotify(QuicChromiumClientSession* quic_session,
SessionSource session_source);
void NotifyStreamReady(std::unique_ptr<HttpStream> stream,
NextProto negotiated_protocol,
std::optional<SessionSource> session_source);
// Called when a SPDY session is ready to use. Cancels in-flight attempts.
// Closes idle streams. Completes request/preconnect jobs.
void HandleSpdySessionReady(base::WeakPtr<SpdySession> spdy_session,
StreamSocketCloseReason refresh_group_reason);
// Called when a QUIC session is ready to use. Cancels in-flight attempts.
// Closes idle streams. Completes request/preconnect jobs.
void HandleQuicSessionReady(QuicChromiumClientSession* quic_session,
StreamSocketCloseReason refresh_group_reason);
// Extracts an entry from `request_jobs_` of which priority is highest. The
// ownership of the entry is moved to `notified_jobs_`.
Job* ExtractFirstJobToNotify();
// Remove the pointeee of `job_pointer` from `request_jobs_`. May cancel
// in-flight TCP based attempts when there are no limit ignoring jobs after
// removing the job and in-flight TCP based attempts count is larger than the
// limit.
raw_ptr<Job> RemoveJobFromQueue(JobQueue::Pointer job_pointer);
// Transfers the ownership of `raw_attempt` to the caller.
std::unique_ptr<TcpBasedAttempt> ExtractTcpBasedAttempt(
TcpBasedAttempt* raw_attempt);
void HandleTcpBasedAttemptFailure(
std::unique_ptr<TcpBasedAttempt> tcp_based_attempt,
int rv);
void OnSpdyThrottleDelayPassed();
// Returns the delay for TCP based attempts in favor of QUIC.
base::TimeDelta GetTcpBasedAttemptDelay();
// Updates whether TCP based attempts should be blocked or not. May cancel
// `tcp_based_attempt_delay_timer_`.
void UpdateTcpBasedAttemptState();
// Cancels `tcp_based_attempt_delay_timer_`.
void CancelTcpBasedAttemptDelayTimer();
// Called when `tcp_based_attempt_delay_timer_` is fired.
void OnTcpBasedAttemptDelayPassed();
bool CanUseTcpBasedProtocols();
bool CanUseQuic();
bool IsEchEnabled() const;
// Mark QUIC brokenness if QUIC attempts failed but TCP/TLS attempts succeeded
// or not attempted.
void MaybeMarkQuicBroken();
// Returns true when this can complete.
bool CanComplete() const;
// Notifies `group_` that `this` has completed and can be destroyed.
void MaybeComplete();
// If `this` is ready to complete, posts a task to call MaybeComplete().
void MaybeCompleteLater();
const raw_ptr<Group> group_;
const NetLogWithSource net_log_;
// For trace events.
const perfetto::Track track_;
const base::TimeTicks created_time_;
// Keeps the initial attempt state. Set when `this` attempts a TCP based
// attempt for the first time.
std::optional<InitialAttemptState> initial_attempt_state_;
NextProtoSet allowed_alpns_ = NextProtoSet::All();
// Holds request jobs that are waiting for notifications.
JobQueue request_jobs_;
// Holds preconnect jobs that are waiting for notifications.
std::set<raw_ptr<Job>> preconnect_jobs_;
// Holds jobs that are already notified results. We need to keep them to avoid
// dangling pointers.
std::set<raw_ptr<Job>> notified_jobs_;
base::flat_set<raw_ptr<Job>> limit_ignoring_jobs_;
base::flat_set<raw_ptr<Job>> ip_based_pooling_disabling_jobs_;
base::flat_set<raw_ptr<Job>> alternative_service_disabling_jobs_;
std::unique_ptr<HostResolver::ServiceEndpointRequest>
service_endpoint_request_;
bool service_endpoint_request_finished_ = false;
base::TimeTicks dns_resolution_start_time_;
base::TimeTicks dns_resolution_end_time_;
AvailabilityState availability_state_ = AvailabilityState::kAvailable;
NetErrorDetails net_error_details_;
ResolveErrorInfo resolve_error_info_;
ConnectionAttempts connection_attempts_;
// TODO(crbug.com/406936736): Remove this once we identify the cause of the
// bug.
bool ip_matching_spdy_session_found_ = false;
// An error code to notify jobs when `this` cannot make any further progress.
// Set to an error from service endpoint resolution failure, the last stream
// attempt failure, network change events, or QUIC task failure.
std::optional<int> final_error_to_notify_jobs_;
// Set to the most recent TCP based attempt failure, if any.
std::optional<int> most_recent_tcp_error_;
// Set to a SSLInfo when an attempt has failed with a certificate error. Used
// to notify jobs.
std::optional<SSLInfo> cert_error_ssl_info_;
// Set to a SSLCertRequestInfo when an attempt has requested a client cert.
// Used to notify jobs.
scoped_refptr<SSLCertRequestInfo> client_auth_cert_info_;
// Base SSLConfig for TCP based attempts, Allowed bad certificates are set
// from the newest job.
std::optional<SSLConfig> base_ssl_config_;
std::set<std::unique_ptr<TcpBasedAttempt>, base::UniquePtrComparator>
tcp_based_attempts_;
// The number of in-flight TCP based attempts that are treated as slow.
size_t slow_tcp_based_attempt_count_ = 0;
base::OneShotTimer spdy_throttle_timer_;
bool spdy_throttle_delay_passed_ = false;
// Tracks the states of IPEndPoints.
IPEndPointStateTracker ip_endpoint_state_tracker_{this};
// The current state of TCP/TLS connection attempts.
TcpBasedAttemptState tcp_based_attempt_state_ =
TcpBasedAttemptState::kNotStarted;
// QUIC version that is known to be used for the destination, usually coming
// from Alt-Svc.
quic::ParsedQuicVersion quic_version_ =
quic::ParsedQuicVersion::Unsupported();
// Created when attempting a QUIC session.
std::unique_ptr<QuicAttempt> quic_attempt_;
// Set when `quic_attempt_` is completed.
std::optional<int> quic_attempt_result_;
// The delay for TCP based stream attempts in favor of QUIC.
base::TimeDelta tcp_based_attempt_delay_;
// Set to true when TCP based attempts should be blocked.
bool should_block_tcp_based_attempt_ = false;
base::OneShotTimer tcp_based_attempt_delay_timer_;
base::OnceClosure on_complete_callback_for_testing_;
base::WeakPtrFactory<AttemptManager> weak_ptr_factory_{this};
};
} // namespace net
#endif // NET_HTTP_HTTP_STREAM_POOL_ATTEMPT_MANAGER_H_