blob: 517fdd33268cd849d2c68025048d48ac12d0dedf [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/quic/quic_session_attempt_manager.h"
#include <memory>
#include <optional>
#include <set>
#include "base/functional/callback_forward.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "net/base/connection_endpoint_metadata.h"
#include "net/base/features.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_error_details.h"
#include "net/base/net_errors.h"
#include "net/base/network_anonymization_key.h"
#include "net/base/privacy_mode.h"
#include "net/base/proxy_chain.h"
#include "net/base/reconnect_notifier.h"
#include "net/base/session_usage.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/log/net_log_source_type.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/mock_quic_data.h"
#include "net/quic/quic_chromium_client_session.h"
#include "net/quic/quic_endpoint.h"
#include "net/quic/quic_session_alias_key.h"
#include "net/quic/quic_session_attempt_request.h"
#include "net/quic/quic_session_pool_test_base.h"
#include "net/socket/socket_tag.h"
#include "net/socket/socket_test_util.h"
#include "net/spdy/multiplexed_session_creation_initiator.h"
#include "net/test/gtest_util.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace net::test {
namespace {
IPEndPoint MakeIPEndPoint(std::string_view addr, uint16_t port = 443) {
return IPEndPoint(*IPAddress::FromIPLiteral(addr), port);
}
class SessionRequester {
public:
explicit SessionRequester(QuicSessionAttemptManager* manager,
quic::ParsedQuicVersion version)
: manager_(manager),
quic_version_(version),
net_log_(NetLogWithSource::Make(NetLog::Get(),
NetLogSourceType::URL_REQUEST)) {}
SessionRequester(SessionRequester&&) = default;
SessionRequester& operator=(SessionRequester&&) = default;
SessionRequester(const SessionRequester&) = delete;
SessionRequester& operator=(const SessionRequester&) = delete;
~SessionRequester() = default;
SessionRequester& SetDestination(url::SchemeHostPort destination) {
destination_ = std::move(destination);
return *this;
}
SessionRequester& SetIPEndPoint(IPEndPoint ip_endpoint) {
endpoint_ = QuicEndpoint(quic_version_, std::move(ip_endpoint),
ConnectionEndpointMetadata());
return *this;
}
int Request() {
QuicSessionAliasKey key(
destination_,
QuicSessionKey(destination_.host(), destination_.port(), privacy_mode_,
proxy_chain_, session_usage_, socket_tag_,
network_anonymization_key_, secure_dns_policy_,
require_dns_https_alpn_,
disable_cert_verification_network_fetches_));
request_ = manager_->CreateRequest(key);
int rv = request_->RequestSession(
endpoint_, cert_verify_flags_, dns_resolution_start_time_,
dns_resolution_end_time_, /*use_dns_aliases=*/true, dns_aliases_,
session_creation_initiator_, connection_management_config_, net_log_,
base::BindOnce(&SessionRequester::OnComplete, base::Unretained(this)));
if (rv != ERR_IO_PENDING) {
OnComplete(rv);
}
return rv;
}
int WaitForResult() {
if (result_.has_value()) {
return *result_;
}
CHECK(!wait_for_result_closure_);
base::RunLoop run_loop;
wait_for_result_closure_ = run_loop.QuitClosure();
run_loop.Run();
CHECK(result_.has_value());
return *result_;
}
void ResetRequest() { request_.reset(); }
const QuicSessionAliasKey& key() const {
CHECK(request_);
return request_->key();
}
std::optional<int> result() const { return result_; }
QuicChromiumClientSession* session() { return session_; }
const QuicChromiumClientSession* session() const { return session_; }
const NetErrorDetails& error_details() const { return error_details_; }
const url::SchemeHostPort& destination() const { return destination_; }
private:
void OnComplete(int result) {
CHECK(!result_.has_value());
CHECK(request_);
result_ = result;
if (result_ == OK) {
session_ = request_->session();
} else {
error_details_ = request_->error_details();
}
request_.reset();
// Clear the request to avoid dangling pointer.
manager_ = nullptr;
if (wait_for_result_closure_) {
std::move(wait_for_result_closure_).Run();
}
}
raw_ptr<QuicSessionAttemptManager> manager_;
quic::ParsedQuicVersion quic_version_;
// For calculating the session key.
url::SchemeHostPort destination_{
url::kHttpsScheme, QuicSessionPoolTestBase::kDefaultServerHostName,
QuicSessionPoolTestBase::kDefaultServerPort};
PrivacyMode privacy_mode_ = PRIVACY_MODE_DISABLED;
ProxyChain proxy_chain_ = ProxyChain::Direct();
SessionUsage session_usage_ = SessionUsage::kDestination;
SocketTag socket_tag_;
NetworkAnonymizationKey network_anonymization_key_;
SecureDnsPolicy secure_dns_policy_ = SecureDnsPolicy::kAllow;
bool require_dns_https_alpn_ = false;
bool disable_cert_verification_network_fetches_ = false;
// For calling RequestSession().
QuicEndpoint endpoint_{quic_version_,
IPEndPoint(IPAddress::IPv4Localhost(), 443),
ConnectionEndpointMetadata()};
int cert_verify_flags_ = 0;
base::TimeTicks dns_resolution_start_time_ = base::TimeTicks::Now();
base::TimeTicks dns_resolution_end_time_ = base::TimeTicks::Now();
std::set<std::string> dns_aliases_;
MultiplexedSessionCreationInitiator session_creation_initiator_ =
MultiplexedSessionCreationInitiator::kUnknown;
std::optional<ConnectionManagementConfig> connection_management_config_;
NetLogWithSource net_log_;
std::unique_ptr<QuicSessionAttemptRequest> request_;
base::OnceClosure wait_for_result_closure_;
std::optional<int> result_;
raw_ptr<QuicChromiumClientSession> session_ = nullptr;
NetErrorDetails error_details_;
};
} // namespace
class QuicSessionAttemptManagerTest
: public QuicSessionPoolTestBase,
public ::testing::TestWithParam<quic::ParsedQuicVersion> {
protected:
QuicSessionAttemptManagerTest() : QuicSessionPoolTestBase(GetParam()) {}
void InitializeWithDefaultProofVerifyDetails() {
Initialize();
crypto_client_stream_factory_.AddProofVerifyDetails(
&default_verify_details_);
}
SessionRequester CreateRequester() {
return SessionRequester(pool_->session_attempt_manager(), GetParam());
}
QuicSessionAttemptManager* session_attempt_manager() {
return pool_->session_attempt_manager();
}
private:
ProofVerifyDetailsChromium default_verify_details_ =
DefaultProofVerifyDetails();
};
INSTANTIATE_TEST_SUITE_P(/**/,
QuicSessionAttemptManagerTest,
::testing::ValuesIn(AllSupportedQuicVersions()));
TEST_P(QuicSessionAttemptManagerTest, RequestSessionSync) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(net::features::kAsyncQuicSession);
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
socket_data.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester = CreateRequester();
int result = requester.Request();
EXPECT_THAT(result, IsOk());
EXPECT_TRUE(requester.session());
}
TEST_P(QuicSessionAttemptManagerTest, RequestSessionSyncFailure) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndDisableFeature(net::features::kAsyncQuicSession);
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddConnect(ASYNC, ERR_CONNECTION_REFUSED);
socket_data.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester = CreateRequester();
int result = requester.Request();
EXPECT_THAT(result, IsError(ERR_CONNECTION_REFUSED));
}
TEST_P(QuicSessionAttemptManagerTest, RequestSessionAsync) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
socket_data.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester = CreateRequester();
int result = requester.Request();
EXPECT_THAT(result, IsError(ERR_IO_PENDING));
result = requester.WaitForResult();
EXPECT_THAT(result, IsOk());
}
TEST_P(QuicSessionAttemptManagerTest, MultipleRequestsSameSessionSuccess) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
socket_data.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data.AddSocketDataToFactory(socket_factory_.get());
// Create three requesters for the same session.
SessionRequester requester1 = CreateRequester();
SessionRequester requester2 = CreateRequester();
SessionRequester requester3 = CreateRequester();
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
// All requesters should succeed and get the same session.
EXPECT_THAT(requester1.WaitForResult(), IsOk());
EXPECT_THAT(requester2.WaitForResult(), IsOk());
EXPECT_THAT(requester3.WaitForResult(), IsOk());
EXPECT_TRUE(requester1.session());
EXPECT_EQ(requester1.session(), requester2.session());
EXPECT_EQ(requester1.session(), requester3.session());
}
TEST_P(QuicSessionAttemptManagerTest, MultipleRequestsSameSessionFailure) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddConnect(ASYNC, ERR_ADDRESS_IN_USE);
socket_data.AddSocketDataToFactory(socket_factory_.get());
// Create three requesters for the same session.
SessionRequester requester1 = CreateRequester();
SessionRequester requester2 = CreateRequester();
SessionRequester requester3 = CreateRequester();
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
// All requesters should fail with the same error.
EXPECT_THAT(requester1.WaitForResult(), IsError(ERR_ADDRESS_IN_USE));
EXPECT_THAT(requester2.WaitForResult(), IsError(ERR_ADDRESS_IN_USE));
EXPECT_THAT(requester3.WaitForResult(), IsError(ERR_ADDRESS_IN_USE));
EXPECT_FALSE(requester1.session());
EXPECT_FALSE(requester2.session());
EXPECT_FALSE(requester3.session());
}
// Test multiple endpoints for the same session: One succeeds, one fails.
TEST_P(QuicSessionAttemptManagerTest, MultipleEndpointsSuccessAndFailure) {
InitializeWithDefaultProofVerifyDetails();
// Create two mock sockets for two different endpoints.
MockQuicData socket_data1(version_);
socket_data1.AddConnect(ASYNC, ERR_CONNECTION_FAILED);
socket_data1.AddSocketDataToFactory(socket_factory_.get());
MockQuicData socket_data2(version_);
socket_data2.AddReadPauseForever();
socket_data2.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data2.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester1 = CreateRequester();
requester1.SetIPEndPoint(MakeIPEndPoint("192.0.2.1"));
SessionRequester requester2 = CreateRequester();
requester2.SetIPEndPoint(MakeIPEndPoint("192.0.2.2"));
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
// First endpoint fails but second succeeds, so both requesters should
// receive the successful session.
EXPECT_THAT(requester1.WaitForResult(), IsOk());
EXPECT_THAT(requester2.WaitForResult(), IsOk());
EXPECT_TRUE(requester1.session());
EXPECT_TRUE(requester2.session());
EXPECT_EQ(requester1.session(), requester2.session());
}
// Test multiple endpoints for the same session: All fail.
TEST_P(QuicSessionAttemptManagerTest, MultipleEndpointsAllFail) {
InitializeWithDefaultProofVerifyDetails();
// Create three mock sockets for three different endpoints, all fail
MockQuicData socket_data1(version_);
socket_data1.AddConnect(ASYNC, ERR_CONNECTION_FAILED);
socket_data1.AddSocketDataToFactory(socket_factory_.get());
MockQuicData socket_data2(version_);
socket_data2.AddConnect(ASYNC, ERR_ADDRESS_UNREACHABLE);
socket_data2.AddSocketDataToFactory(socket_factory_.get());
MockQuicData socket_data3(version_);
socket_data3.AddConnect(ASYNC, ERR_CONNECTION_REFUSED);
socket_data3.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester1 = CreateRequester();
requester1.SetIPEndPoint(MakeIPEndPoint("2001:db8::1"));
SessionRequester requester2 = CreateRequester();
requester2.SetIPEndPoint(MakeIPEndPoint("2001:db8::2"));
SessionRequester requester3 = CreateRequester();
requester3.SetIPEndPoint(MakeIPEndPoint("2001:db8::3"));
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
// All should fail with the last error.
EXPECT_THAT(requester1.WaitForResult(), IsError(ERR_CONNECTION_REFUSED));
EXPECT_THAT(requester2.WaitForResult(), IsError(ERR_CONNECTION_REFUSED));
EXPECT_THAT(requester3.WaitForResult(), IsError(ERR_CONNECTION_REFUSED));
// No sessions should be created.
EXPECT_FALSE(requester1.session());
EXPECT_FALSE(requester2.session());
EXPECT_FALSE(requester3.session());
}
// Test multiple endpoints for the same session: All sessions succeed,
// but only the first one is used.
TEST_P(QuicSessionAttemptManagerTest, MultipleEndpointsAllSuccess) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data1(version_);
socket_data1.AddReadPauseForever();
socket_data1.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data1.AddSocketDataToFactory(socket_factory_.get());
// This would succeed but should be cancelled.
MockQuicData socket_data2(version_);
socket_data2.AddReadPauseForever();
socket_data2.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data2.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester1 = CreateRequester();
requester1.SetIPEndPoint(MakeIPEndPoint("2001:db8::1"));
SessionRequester requester2 = CreateRequester();
requester2.SetIPEndPoint(MakeIPEndPoint("192.0.2.1"));
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
// Both should succeed and have the same session.
EXPECT_THAT(requester1.WaitForResult(), IsOk());
EXPECT_THAT(requester2.WaitForResult(), IsOk());
EXPECT_TRUE(requester1.session());
EXPECT_TRUE(requester2.session());
EXPECT_EQ(requester1.session(), requester2.session());
}
TEST_P(QuicSessionAttemptManagerTest, JobCompletesWhenAllRequestsCancelled) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddConnect(ASYNC, ERR_IO_PENDING);
socket_data.AddSocketDataToFactory(socket_factory_.get());
// Create multiple pending requests.
SessionRequester requester1 = CreateRequester();
SessionRequester requester2 = CreateRequester();
SessionRequester requester3 = CreateRequester();
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
const QuicSessionAliasKey key = requester1.key();
// Cancel first two requests.
requester1.ResetRequest();
requester2.ResetRequest();
// At this point, the Job should still exist because requester3 is active.
EXPECT_TRUE(session_attempt_manager()->HasActiveJobForTesting(key));
// Cancel the last request. This should destroy the Job.
requester3.ResetRequest();
EXPECT_FALSE(session_attempt_manager()->HasActiveJobForTesting(key));
}
TEST_P(QuicSessionAttemptManagerTest, CancelSomeRequestsWhileOthersComplete) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
socket_data.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data.AddSocketDataToFactory(socket_factory_.get());
// Create multiple pending requests.
SessionRequester requester1 = CreateRequester();
SessionRequester requester2 = CreateRequester();
SessionRequester requester3 = CreateRequester();
SessionRequester requester4 = CreateRequester();
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester4.Request(), IsError(ERR_IO_PENDING));
// Cancel two requests before connection completes.
requester1.ResetRequest();
requester2.ResetRequest();
// Complete connection attempt. Remaining requests should succeed.
EXPECT_THAT(requester3.WaitForResult(), IsOk());
EXPECT_THAT(requester4.WaitForResult(), IsOk());
EXPECT_TRUE(requester3.session());
EXPECT_TRUE(requester4.session());
EXPECT_EQ(requester3.session(), requester4.session());
}
TEST_P(QuicSessionAttemptManagerTest, DestroyManagerWithPendingRequests) {
InitializeWithDefaultProofVerifyDetails();
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
socket_data.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data.AddSocketDataToFactory(socket_factory_.get());
// Create multiple pending requests.
SessionRequester requester1 = CreateRequester();
SessionRequester requester2 = CreateRequester();
SessionRequester requester3 = CreateRequester();
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester3.Request(), IsError(ERR_IO_PENDING));
// Destroy the pool (which contains the manager) while requests are
// pending.
pool_.reset();
EXPECT_THAT(requester1.WaitForResult(), IsError(ERR_ABORTED));
EXPECT_THAT(requester2.WaitForResult(), IsError(ERR_ABORTED));
EXPECT_THAT(requester3.WaitForResult(), IsError(ERR_ABORTED));
}
TEST_P(QuicSessionAttemptManagerTest, OnOriginFrame) {
InitializeWithDefaultProofVerifyDetails();
// 1. Make a request for `kDefaultServerHostName` that will be pending.
MockQuicData socket_data1(version_);
socket_data1.AddConnect(ASYNC, ERR_IO_PENDING);
socket_data1.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester1 = CreateRequester();
requester1.SetIPEndPoint(MakeIPEndPoint("192.0.2.1"));
EXPECT_THAT(requester1.Request(), IsError(ERR_IO_PENDING));
EXPECT_TRUE(
session_attempt_manager()->HasActiveJobForTesting(requester1.key()));
const QuicSessionAliasKey key1 = requester1.key();
// 2. Create a new session for `other.example.org`.
const char* kOtherHostname = "other.example.org";
const url::SchemeHostPort kOtherDestination(url::kHttpsScheme, kOtherHostname,
443);
MockQuicData socket_data2(version_);
socket_data2.AddReadPauseForever();
socket_data2.AddWrite(ASYNC, ConstructInitialSettingsPacket());
socket_data2.AddSocketDataToFactory(socket_factory_.get());
SessionRequester requester2 = CreateRequester();
requester2.SetDestination(kOtherDestination);
requester2.SetIPEndPoint(MakeIPEndPoint("192.0.2.2"));
EXPECT_THAT(requester2.Request(), IsError(ERR_IO_PENDING));
EXPECT_THAT(requester2.WaitForResult(), IsOk());
QuicChromiumClientSession* session2 = requester2.session();
EXPECT_TRUE(session2);
// 3. Simulate an Origin Frame reception.
quic::OriginFrame origin_frame;
origin_frame.origins.emplace_back(requester1.destination().Serialize());
session2->OnOriginFrame(origin_frame);
// 4. The first request should complete with `session2`.
EXPECT_THAT(requester1.WaitForResult(), IsOk());
EXPECT_EQ(requester1.session(), session2);
// The job for `requester1` should be gone.
EXPECT_FALSE(session_attempt_manager()->HasActiveJobForTesting(key1));
}
} // namespace net::test