blob: c5d51d76b1892acab3b9b5650a09a0501045572f [file] [log] [blame]
// 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.
#include <memory>
#include "net/base/network_anonymization_key.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_server.h"
#include "net/base/session_usage.h"
#include "net/cert/x509_certificate.h"
#include "net/quic/crypto/proof_verifier_chromium.h"
#include "net/quic/mock_quic_data.h"
#include "net/quic/quic_context.h"
#include "net/quic/quic_http_stream.h"
#include "net/quic/quic_session_pool.h"
#include "net/quic/quic_session_pool_test_base.h"
#include "net/quic/quic_test_packet_maker.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
#include "net/third_party/quiche/src/quiche/quic/test_tools/quic_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace net::test {
class QuicSessionPoolProxyJobTest
: public QuicSessionPoolTestBase,
public ::testing::TestWithParam<quic::ParsedQuicVersion> {
protected:
QuicSessionPoolProxyJobTest() : QuicSessionPoolTestBase(GetParam()) {}
};
INSTANTIATE_TEST_SUITE_P(All,
QuicSessionPoolProxyJobTest,
::testing::ValuesIn(AllSupportedQuicVersions()));
TEST_P(QuicSessionPoolProxyJobTest, CreateProxiedQuicSession) {
Initialize();
GURL url("https://www.example.org/");
GURL proxy(kProxy1Url);
auto origin = url::SchemeHostPort(url);
auto proxy_origin = url::SchemeHostPort(proxy);
auto nak = NetworkAnonymizationKey();
scoped_refptr<X509Certificate> cert(
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
ASSERT_TRUE(cert->VerifyNameMatch(origin.host()));
ASSERT_TRUE(cert->VerifyNameMatch(proxy_origin.host()));
ASSERT_FALSE(cert->VerifyNameMatch(kDifferentHostname));
ProofVerifyDetailsChromium verify_details;
verify_details.cert_verify_result.verified_cert = cert;
verify_details.cert_verify_result.is_issued_by_known_root = true;
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
// QUIC proxies do not use priority header.
client_maker_.set_use_priority_header(false);
// Use a separate packet maker for the connection to the endpoint.
QuicTestPacketMaker endpoint_maker(
version_,
quic::QuicUtils::CreateRandomConnectionId(context_.random_generator()),
context_.clock(), kDefaultServerHostName, quic::Perspective::IS_CLIENT,
/*client_priority_uses_incremental=*/true,
/*use_priority_header=*/true);
const uint64_t stream_id = GetNthClientInitiatedBidirectionalStreamId(0);
MockQuicData socket_data(version_);
socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket(1));
socket_data.AddWrite(
SYNCHRONOUS, ConstructConnectUdpRequestPacket(
2, stream_id, proxy.host(),
"/.well-known/masque/udp/www.example.org/443/", false));
socket_data.AddRead(ASYNC, ConstructServerSettingsPacket(3));
socket_data.AddRead(ASYNC, ConstructOkResponsePacket(4, stream_id, true));
socket_data.AddReadPauseForever();
socket_data.AddWrite(ASYNC, client_maker_.MakeAckPacket(3, 3, 4, 3));
socket_data.AddWrite(ASYNC, ConstructClientH3DatagramPacket(
4, stream_id, kConnectUdpContextId,
endpoint_maker.MakeInitialSettingsPacket(1)));
socket_data.AddSocketDataToFactory(socket_factory_.get());
auto proxy_chain = ProxyChain::ForIpProtection({
ProxyServer::FromSchemeHostAndPort(ProxyServer::SCHEME_QUIC,
proxy_origin.host(), 443),
});
EXPECT_TRUE(proxy_chain.IsValid());
RequestBuilder builder(this);
builder.destination = origin;
builder.proxy_chain = proxy_chain;
builder.http_user_agent_settings = &http_user_agent_settings_;
builder.url = url;
// Note: `builder` defaults to using the parameterized `version_` member,
// which we will assert here as a pre-condition for checking that the proxy
// session ignores this and uses RFCv1 instead.
ASSERT_EQ(builder.quic_version, version_);
EXPECT_EQ(ERR_IO_PENDING, builder.CallRequest());
ASSERT_EQ(OK, callback_.WaitForResult());
std::unique_ptr<HttpStream> stream = CreateStream(&builder.request);
EXPECT_TRUE(stream.get());
QuicChromiumClientSession* session =
GetActiveSession(origin, nak, proxy_chain);
ASSERT_TRUE(session);
// The direct connection to the proxy has a max packet size 1350. The
// connection to the endpoint could use up to 1350 - (packet header = 38) -
// (quarter-stream-id = 1) - (context-id = 1), but this value is greater than
// the default maximum of 1250. We can only observe the largest datagram that
// could be sent to the endpoint, which would be 1250 - (packet header = 38) =
// 1212 bytes.
EXPECT_EQ(session->GetGuaranteedLargestMessagePayload(), 1212);
// Check that the session through the proxy uses the version from the request.
EXPECT_EQ(session->GetQuicVersion(), version_);
// Check that the session to the proxy always uses RFCv1.
QuicChromiumClientSession* proxy_session = GetActiveSession(
proxy_origin, nak, ProxyChain::ForIpProtection({}), SessionUsage::kProxy);
ASSERT_TRUE(proxy_session);
EXPECT_EQ(proxy_session->GetQuicVersion(), quic::ParsedQuicVersion::RFCv1());
stream.reset();
// Ensure the session finishes creating before proceeding.
RunUntilIdle();
socket_data.ExpectAllReadDataConsumed();
socket_data.ExpectAllWriteDataConsumed();
}
TEST_P(QuicSessionPoolProxyJobTest, CreateProxySessionFails) {
Initialize();
GURL url("https://www.example.org/");
GURL proxy(kProxy1Url);
auto origin = url::SchemeHostPort(url);
auto proxy_origin = url::SchemeHostPort(proxy);
scoped_refptr<X509Certificate> cert(
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
ASSERT_TRUE(cert->VerifyNameMatch(origin.host()));
ASSERT_TRUE(cert->VerifyNameMatch(proxy_origin.host()));
ASSERT_FALSE(cert->VerifyNameMatch(kDifferentHostname));
ProofVerifyDetailsChromium verify_details;
verify_details.cert_verify_result.verified_cert = cert;
verify_details.cert_verify_result.is_issued_by_known_root = true;
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever();
// Creation of underlying session fails immediately.
socket_data.AddWrite(SYNCHRONOUS, ERR_SOCKET_NOT_CONNECTED);
socket_data.AddSocketDataToFactory(socket_factory_.get());
auto proxy_chain = ProxyChain::ForIpProtection({
ProxyServer::FromSchemeHostAndPort(ProxyServer::SCHEME_QUIC,
proxy_origin.host(), 443),
});
EXPECT_TRUE(proxy_chain.IsValid());
RequestBuilder builder(this);
builder.destination = origin;
builder.proxy_chain = proxy_chain;
builder.http_user_agent_settings = &http_user_agent_settings_;
builder.url = url;
EXPECT_EQ(ERR_IO_PENDING, builder.CallRequest());
ASSERT_EQ(ERR_QUIC_HANDSHAKE_FAILED, callback_.WaitForResult());
socket_data.ExpectAllReadDataConsumed();
socket_data.ExpectAllWriteDataConsumed();
}
TEST_P(QuicSessionPoolProxyJobTest, CreateSessionFails) {
Initialize();
GURL url("https://www.example.org/");
GURL proxy(kProxy1Url);
auto origin = url::SchemeHostPort(url);
auto proxy_origin = url::SchemeHostPort(proxy);
scoped_refptr<X509Certificate> cert(
ImportCertFromFile(GetTestCertsDirectory(), "wildcard.pem"));
ASSERT_TRUE(cert->VerifyNameMatch(origin.host()));
ASSERT_TRUE(cert->VerifyNameMatch(proxy_origin.host()));
ASSERT_FALSE(cert->VerifyNameMatch(kDifferentHostname));
ProofVerifyDetailsChromium verify_details;
verify_details.cert_verify_result.verified_cert = cert;
verify_details.cert_verify_result.is_issued_by_known_root = true;
crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
// QUIC proxies do not use priority header.
client_maker_.set_use_priority_header(false);
MockQuicData socket_data(version_);
socket_data.AddReadPauseForever(); // SYNC/ERR_IO_PENDING
socket_data.AddWritePause();
socket_data.AddWrite(ASYNC, ConstructInitialSettingsPacket(1));
socket_data.AddSocketDataToFactory(socket_factory_.get());
auto proxy_chain = ProxyChain::ForIpProtection({
ProxyServer::FromSchemeHostAndPort(ProxyServer::SCHEME_QUIC,
proxy_origin.host(), 443),
});
EXPECT_TRUE(proxy_chain.IsValid());
RequestBuilder builder(this);
builder.destination = origin;
builder.proxy_chain = proxy_chain;
builder.http_user_agent_settings = &http_user_agent_settings_;
builder.url = url;
EXPECT_EQ(ERR_IO_PENDING, builder.CallRequest());
RunUntilIdle();
// Oops, the session went away. This generates an error
// from `QuicSessionPool::CreateSessionOnProxyStream`.
factory_->CloseAllSessions(ERR_QUIC_HANDSHAKE_FAILED,
quic::QuicErrorCode::QUIC_INTERNAL_ERROR);
socket_data.Resume();
ASSERT_EQ(ERR_QUIC_HANDSHAKE_FAILED, callback_.WaitForResult());
}
} // namespace net::test