blob: 5ccf370209806b808cb8747eb19954871aaa175c [file] [log] [blame]
// Copyright (c) 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/io/quic_default_event_loop.h"
#include "quiche/quic/core/io/quic_event_loop.h"
#include "quiche/quic/core/proto/crypto_server_config_proto.h"
#include "quiche/quic/core/quic_alarm_factory.h"
#include "quiche/quic/core/quic_default_clock.h"
#include "quiche/quic/platform/api/quic_expect_bug.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/platform/api/quic_test_loopback.h"
#include "quiche/quic/qbone/platform/icmp_packet.h"
#include "quiche/quic/qbone/qbone_client_session.h"
#include "quiche/quic/qbone/qbone_constants.h"
#include "quiche/quic/qbone/qbone_control_placeholder.pb.h"
#include "quiche/quic/qbone/qbone_packet_processor_test_tools.h"
#include "quiche/quic/qbone/qbone_server_session.h"
#include "quiche/quic/test_tools/crypto_test_utils.h"
#include "quiche/quic/test_tools/mock_clock.h"
#include "quiche/quic/test_tools/mock_connection_id_generator.h"
#include "quiche/quic/test_tools/quic_connection_peer.h"
#include "quiche/quic/test_tools/quic_session_peer.h"
#include "quiche/quic/test_tools/quic_test_utils.h"
namespace quic {
namespace test {
namespace {
using ::testing::_;
using ::testing::Contains;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Not;
std::string TestPacketIn(const std::string& body) {
return PrependIPv6HeaderForTest(body, 5);
}
std::string TestPacketOut(const std::string& body) {
return PrependIPv6HeaderForTest(body, 4);
}
ParsedQuicVersionVector GetTestParams() {
ParsedQuicVersionVector test_versions;
// TODO(b/113130636): Make QBONE work with TLS.
for (const auto& version : CurrentSupportedVersionsWithQuicCrypto()) {
// QBONE requires MESSAGE frames
if (!version.SupportsMessageFrames()) {
continue;
}
test_versions.push_back(version);
}
return test_versions;
}
// Used by QuicCryptoServerConfig to provide server credentials, passes
// everything through to ProofSourceForTesting if success is true,
// and fails otherwise.
class IndirectionProofSource : public ProofSource {
public:
explicit IndirectionProofSource(bool success) {
if (success) {
proof_source_ = crypto_test_utils::ProofSourceForTesting();
}
}
// ProofSource override.
void GetProof(const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address,
const std::string& hostname, const std::string& server_config,
QuicTransportVersion transport_version,
absl::string_view chlo_hash,
std::unique_ptr<Callback> callback) override {
if (!proof_source_) {
QuicCryptoProof proof;
quiche::QuicheReferenceCountedPointer<ProofSource::Chain> chain =
GetCertChain(server_address, client_address, hostname,
&proof.cert_matched_sni);
callback->Run(/*ok=*/false, chain, proof, /*details=*/nullptr);
return;
}
proof_source_->GetProof(server_address, client_address, hostname,
server_config, transport_version, chlo_hash,
std::move(callback));
}
quiche::QuicheReferenceCountedPointer<Chain> GetCertChain(
const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address, const std::string& hostname,
bool* cert_matched_sni) override {
if (!proof_source_) {
return quiche::QuicheReferenceCountedPointer<Chain>();
}
return proof_source_->GetCertChain(server_address, client_address, hostname,
cert_matched_sni);
}
void ComputeTlsSignature(
const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address, const std::string& hostname,
uint16_t signature_algorithm, absl::string_view in,
std::unique_ptr<SignatureCallback> callback) override {
if (!proof_source_) {
callback->Run(/*ok=*/true, "Signature", /*details=*/nullptr);
return;
}
proof_source_->ComputeTlsSignature(server_address, client_address, hostname,
signature_algorithm, in,
std::move(callback));
}
absl::InlinedVector<uint16_t, 8> SupportedTlsSignatureAlgorithms()
const override {
if (!proof_source_) {
return {};
}
return proof_source_->SupportedTlsSignatureAlgorithms();
}
TicketCrypter* GetTicketCrypter() override { return nullptr; }
private:
std::unique_ptr<ProofSource> proof_source_;
};
// Used by QuicCryptoClientConfig to verify server credentials, passes
// everything through to ProofVerifierForTesting is success is true,
// otherwise returns a canned response of QUIC_FAILURE.
class IndirectionProofVerifier : public ProofVerifier {
public:
explicit IndirectionProofVerifier(bool success) {
if (success) {
proof_verifier_ = crypto_test_utils::ProofVerifierForTesting();
}
}
// ProofVerifier override
QuicAsyncStatus VerifyProof(
const std::string& hostname, const uint16_t port,
const std::string& server_config, QuicTransportVersion transport_version,
absl::string_view chlo_hash, const std::vector<std::string>& certs,
const std::string& cert_sct, const std::string& signature,
const ProofVerifyContext* context, std::string* error_details,
std::unique_ptr<ProofVerifyDetails>* verify_details,
std::unique_ptr<ProofVerifierCallback> callback) override {
if (!proof_verifier_) {
return QUIC_FAILURE;
}
return proof_verifier_->VerifyProof(
hostname, port, server_config, transport_version, chlo_hash, certs,
cert_sct, signature, context, error_details, verify_details,
std::move(callback));
}
QuicAsyncStatus VerifyCertChain(
const std::string& hostname, const uint16_t port,
const std::vector<std::string>& certs, const std::string& ocsp_response,
const std::string& cert_sct, const ProofVerifyContext* context,
std::string* error_details, std::unique_ptr<ProofVerifyDetails>* details,
uint8_t* out_alert,
std::unique_ptr<ProofVerifierCallback> callback) override {
if (!proof_verifier_) {
return QUIC_FAILURE;
}
return proof_verifier_->VerifyCertChain(
hostname, port, certs, ocsp_response, cert_sct, context, error_details,
details, out_alert, std::move(callback));
}
std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
if (!proof_verifier_) {
return nullptr;
}
return proof_verifier_->CreateDefaultContext();
}
private:
std::unique_ptr<ProofVerifier> proof_verifier_;
};
class DataSavingQbonePacketWriter : public QbonePacketWriter {
public:
void WritePacketToNetwork(const char* packet, size_t size) override {
data_.push_back(std::string(packet, size));
}
const std::vector<std::string>& data() { return data_; }
private:
std::vector<std::string> data_;
};
template <class T>
class DataSavingQboneControlHandler : public QboneControlHandler<T> {
public:
void OnControlRequest(const T& request) override { data_.push_back(request); }
void OnControlError() override { error_ = true; }
const std::vector<T>& data() { return data_; }
bool error() { return error_; }
private:
std::vector<T> data_;
bool error_ = false;
};
// Single-threaded scheduled task runner based on a MockClock.
//
// Simulates asynchronous execution on a single thread by holding scheduled
// tasks until Run() is called. Performs no synchronization, assumes that
// Schedule() and Run() are called on the same thread.
class FakeTaskRunner {
public:
explicit FakeTaskRunner(MockQuicConnectionHelper* helper)
: tasks_([](const TaskType& l, const TaskType& r) {
// Items at a later time should run after items at an earlier time.
// Priority queue comparisons should return true if l appears after r.
return l->time() > r->time();
}),
helper_(helper) {}
// Runs all tasks in time order. Executes tasks scheduled at
// the same in an arbitrary order.
void Run() {
while (!tasks_.empty()) {
tasks_.top()->Run();
tasks_.pop();
}
}
private:
class InnerTask {
public:
InnerTask(std::function<void()> task, QuicTime time)
: task_(std::move(task)), time_(time) {}
void Cancel() { cancelled_ = true; }
void Run() {
if (!cancelled_) {
task_();
}
}
QuicTime time() const { return time_; }
private:
bool cancelled_ = false;
std::function<void()> task_;
QuicTime time_;
};
public:
// Schedules a function to run immediately and advances the time.
void Schedule(std::function<void()> task) {
tasks_.push(std::shared_ptr<InnerTask>(
new InnerTask(std::move(task), helper_->GetClock()->Now())));
helper_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
}
private:
using TaskType = std::shared_ptr<InnerTask>;
std::priority_queue<TaskType, std::vector<TaskType>,
std::function<bool(const TaskType&, const TaskType&)>>
tasks_;
MockQuicConnectionHelper* helper_;
};
class QboneSessionTest : public QuicTestWithParam<ParsedQuicVersion> {
public:
QboneSessionTest()
: supported_versions_({GetParam()}),
runner_(&helper_),
compressed_certs_cache_(100) {}
~QboneSessionTest() override {
delete client_connection_;
delete server_connection_;
}
const MockClock* GetClock() const {
return static_cast<const MockClock*>(helper_.GetClock());
}
// The parameters are used to control whether the handshake will success or
// not.
void CreateClientAndServerSessions(bool client_handshake_success = true,
bool server_handshake_success = true,
bool send_qbone_alpn = true) {
// Quic crashes if packets are sent at time 0, and the clock defaults to 0.
helper_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000));
event_loop_ = GetDefaultEventLoop()->Create(QuicDefaultClock::Get());
alarm_factory_ = event_loop_->CreateAlarmFactory();
client_writer_ = std::make_unique<DataSavingQbonePacketWriter>();
server_writer_ = std::make_unique<DataSavingQbonePacketWriter>();
client_handler_ =
std::make_unique<DataSavingQboneControlHandler<QboneClientRequest>>();
server_handler_ =
std::make_unique<DataSavingQboneControlHandler<QboneServerRequest>>();
QuicSocketAddress server_address(TestLoopback(), 0);
QuicSocketAddress client_address;
if (server_address.host().address_family() == IpAddressFamily::IP_V4) {
client_address = QuicSocketAddress(QuicIpAddress::Any4(), 0);
} else {
client_address = QuicSocketAddress(QuicIpAddress::Any6(), 0);
}
{
client_connection_ = new QuicConnection(
TestConnectionId(), client_address, server_address, &helper_,
alarm_factory_.get(), new NiceMock<MockPacketWriter>(), true,
Perspective::IS_CLIENT, supported_versions_,
connection_id_generator_);
client_connection_->SetSelfAddress(client_address);
QuicConfig config;
client_crypto_config_ = std::make_unique<QuicCryptoClientConfig>(
std::make_unique<IndirectionProofVerifier>(client_handshake_success));
if (send_qbone_alpn) {
client_crypto_config_->set_alpn("qbone");
}
client_peer_ = std::make_unique<QboneClientSession>(
client_connection_, client_crypto_config_.get(),
/*owner=*/nullptr, config, supported_versions_,
QuicServerId("test.example.com", 1234, false), client_writer_.get(),
client_handler_.get());
}
{
server_connection_ = new QuicConnection(
TestConnectionId(), server_address, client_address, &helper_,
alarm_factory_.get(), new NiceMock<MockPacketWriter>(), true,
Perspective::IS_SERVER, supported_versions_,
connection_id_generator_);
server_connection_->SetSelfAddress(server_address);
QuicConfig config;
server_crypto_config_ = std::make_unique<QuicCryptoServerConfig>(
QuicCryptoServerConfig::TESTING, QuicRandom::GetInstance(),
std::make_unique<IndirectionProofSource>(server_handshake_success),
KeyExchangeSource::Default());
QuicCryptoServerConfig::ConfigOptions options;
QuicServerConfigProtobuf primary_config =
server_crypto_config_->GenerateConfig(QuicRandom::GetInstance(),
GetClock(), options);
std::unique_ptr<CryptoHandshakeMessage> message(
server_crypto_config_->AddConfig(primary_config,
GetClock()->WallNow()));
server_peer_ = std::make_unique<QboneServerSession>(
supported_versions_, server_connection_, nullptr, config,
server_crypto_config_.get(), &compressed_certs_cache_,
server_writer_.get(), TestLoopback6(), TestLoopback6(), 64,
server_handler_.get());
}
// Hook everything up!
MockPacketWriter* client_writer = static_cast<MockPacketWriter*>(
QuicConnectionPeer::GetWriter(client_peer_->connection()));
ON_CALL(*client_writer, WritePacket(_, _, _, _, _))
.WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
const QuicIpAddress& self_address,
const QuicSocketAddress& peer_address,
PerPacketOptions* options) {
char* copy = new char[1024 * 1024];
memcpy(copy, buffer, buf_len);
runner_.Schedule([this, copy, buf_len] {
QuicReceivedPacket packet(copy, buf_len, GetClock()->Now());
server_peer_->ProcessUdpPacket(server_connection_->self_address(),
client_connection_->self_address(),
packet);
delete[] copy;
});
return WriteResult(WRITE_STATUS_OK, buf_len);
}));
MockPacketWriter* server_writer = static_cast<MockPacketWriter*>(
QuicConnectionPeer::GetWriter(server_peer_->connection()));
ON_CALL(*server_writer, WritePacket(_, _, _, _, _))
.WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
const QuicIpAddress& self_address,
const QuicSocketAddress& peer_address,
PerPacketOptions* options) {
char* copy = new char[1024 * 1024];
memcpy(copy, buffer, buf_len);
runner_.Schedule([this, copy, buf_len] {
QuicReceivedPacket packet(copy, buf_len, GetClock()->Now());
client_peer_->ProcessUdpPacket(client_connection_->self_address(),
server_connection_->self_address(),
packet);
delete[] copy;
});
return WriteResult(WRITE_STATUS_OK, buf_len);
}));
}
void StartHandshake() {
server_peer_->Initialize();
client_peer_->Initialize();
runner_.Run();
}
void ExpectICMPTooBigResponse(const std::vector<std::string>& written_packets,
const int mtu, const std::string& packet) {
auto* header = reinterpret_cast<const ip6_hdr*>(packet.data());
icmp6_hdr icmp_header{};
icmp_header.icmp6_type = ICMP6_PACKET_TOO_BIG;
icmp_header.icmp6_mtu = mtu;
std::string expected;
CreateIcmpPacket(header->ip6_dst, header->ip6_src, icmp_header, packet,
[&expected](absl::string_view icmp_packet) {
expected = std::string(icmp_packet);
});
EXPECT_THAT(written_packets, Contains(expected));
}
// Test handshake establishment and sending/receiving of data for two
// directions.
void TestStreamConnection(bool use_messages) {
ASSERT_TRUE(server_peer_->OneRttKeysAvailable());
ASSERT_TRUE(client_peer_->OneRttKeysAvailable());
ASSERT_TRUE(server_peer_->IsEncryptionEstablished());
ASSERT_TRUE(client_peer_->IsEncryptionEstablished());
// Create an outgoing stream from the client and say hello.
QUIC_LOG(INFO) << "Sending client -> server";
client_peer_->ProcessPacketFromNetwork(TestPacketIn("hello"));
client_peer_->ProcessPacketFromNetwork(TestPacketIn("world"));
runner_.Run();
// The server should see the data, the client hasn't received
// anything yet.
EXPECT_THAT(server_writer_->data(),
ElementsAre(TestPacketOut("hello"), TestPacketOut("world")));
EXPECT_TRUE(client_writer_->data().empty());
EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
// Let's pretend some service responds.
QUIC_LOG(INFO) << "Sending server -> client";
server_peer_->ProcessPacketFromNetwork(TestPacketIn("Hello Again"));
server_peer_->ProcessPacketFromNetwork(TestPacketIn("Again"));
runner_.Run();
EXPECT_THAT(server_writer_->data(),
ElementsAre(TestPacketOut("hello"), TestPacketOut("world")));
EXPECT_THAT(
client_writer_->data(),
ElementsAre(TestPacketOut("Hello Again"), TestPacketOut("Again")));
EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
// Try to send long payloads that are larger than the QUIC MTU but
// smaller than the QBONE max size.
// This should trigger the non-ephemeral stream code path.
std::string long_data(
QboneConstants::kMaxQbonePacketBytes - sizeof(ip6_hdr) - 1, 'A');
QUIC_LOG(INFO) << "Sending server -> client long data";
server_peer_->ProcessPacketFromNetwork(TestPacketIn(long_data));
runner_.Run();
if (use_messages) {
ExpectICMPTooBigResponse(
server_writer_->data(),
server_peer_->connection()->GetGuaranteedLargestMessagePayload(),
TestPacketOut(long_data));
} else {
EXPECT_THAT(client_writer_->data(), Contains(TestPacketOut(long_data)));
}
EXPECT_THAT(server_writer_->data(),
Not(Contains(TestPacketOut(long_data))));
EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
QUIC_LOG(INFO) << "Sending client -> server long data";
client_peer_->ProcessPacketFromNetwork(TestPacketIn(long_data));
runner_.Run();
if (use_messages) {
ExpectICMPTooBigResponse(
client_writer_->data(),
client_peer_->connection()->GetGuaranteedLargestMessagePayload(),
TestPacketIn(long_data));
} else {
EXPECT_THAT(server_writer_->data(), Contains(TestPacketOut(long_data)));
}
EXPECT_FALSE(client_peer_->EarlyDataAccepted());
EXPECT_FALSE(client_peer_->ReceivedInchoateReject());
EXPECT_THAT(client_peer_->GetNumReceivedServerConfigUpdates(), Eq(0));
if (!use_messages) {
EXPECT_THAT(client_peer_->GetNumStreamedPackets(), Eq(1));
EXPECT_THAT(server_peer_->GetNumStreamedPackets(), Eq(1));
}
if (use_messages) {
EXPECT_THAT(client_peer_->GetNumEphemeralPackets(), Eq(0));
EXPECT_THAT(server_peer_->GetNumEphemeralPackets(), Eq(0));
EXPECT_THAT(client_peer_->GetNumMessagePackets(), Eq(2));
EXPECT_THAT(server_peer_->GetNumMessagePackets(), Eq(2));
} else {
EXPECT_THAT(client_peer_->GetNumEphemeralPackets(), Eq(2));
EXPECT_THAT(server_peer_->GetNumEphemeralPackets(), Eq(2));
EXPECT_THAT(client_peer_->GetNumMessagePackets(), Eq(0));
EXPECT_THAT(server_peer_->GetNumMessagePackets(), Eq(0));
}
// All streams are ephemeral and should be gone.
EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
}
// Test that client and server are not connected after handshake failure.
void TestDisconnectAfterFailedHandshake() {
EXPECT_FALSE(client_peer_->IsEncryptionEstablished());
EXPECT_FALSE(client_peer_->OneRttKeysAvailable());
EXPECT_FALSE(server_peer_->IsEncryptionEstablished());
EXPECT_FALSE(server_peer_->OneRttKeysAvailable());
}
protected:
const ParsedQuicVersionVector supported_versions_;
std::unique_ptr<QuicEventLoop> event_loop_;
std::unique_ptr<QuicAlarmFactory> alarm_factory_;
FakeTaskRunner runner_;
MockQuicConnectionHelper helper_;
QuicConnection* client_connection_;
QuicConnection* server_connection_;
QuicCompressedCertsCache compressed_certs_cache_;
std::unique_ptr<QuicCryptoClientConfig> client_crypto_config_;
std::unique_ptr<QuicCryptoServerConfig> server_crypto_config_;
std::unique_ptr<DataSavingQbonePacketWriter> client_writer_;
std::unique_ptr<DataSavingQbonePacketWriter> server_writer_;
std::unique_ptr<DataSavingQboneControlHandler<QboneClientRequest>>
client_handler_;
std::unique_ptr<DataSavingQboneControlHandler<QboneServerRequest>>
server_handler_;
std::unique_ptr<QboneServerSession> server_peer_;
std::unique_ptr<QboneClientSession> client_peer_;
MockConnectionIdGenerator connection_id_generator_;
};
INSTANTIATE_TEST_SUITE_P(Tests, QboneSessionTest,
::testing::ValuesIn(GetTestParams()),
::testing::PrintToStringParamName());
TEST_P(QboneSessionTest, StreamConnection) {
CreateClientAndServerSessions();
client_peer_->set_send_packets_as_messages(false);
server_peer_->set_send_packets_as_messages(false);
StartHandshake();
TestStreamConnection(false);
}
TEST_P(QboneSessionTest, Messages) {
CreateClientAndServerSessions();
client_peer_->set_send_packets_as_messages(true);
server_peer_->set_send_packets_as_messages(true);
StartHandshake();
TestStreamConnection(true);
}
TEST_P(QboneSessionTest, ClientRejection) {
CreateClientAndServerSessions(false /*client_handshake_success*/,
true /*server_handshake_success*/,
true /*send_qbone_alpn*/);
StartHandshake();
TestDisconnectAfterFailedHandshake();
}
TEST_P(QboneSessionTest, BadAlpn) {
CreateClientAndServerSessions(true /*client_handshake_success*/,
true /*server_handshake_success*/,
false /*send_qbone_alpn*/);
StartHandshake();
TestDisconnectAfterFailedHandshake();
}
TEST_P(QboneSessionTest, ServerRejection) {
CreateClientAndServerSessions(true /*client_handshake_success*/,
false /*server_handshake_success*/,
true /*send_qbone_alpn*/);
StartHandshake();
TestDisconnectAfterFailedHandshake();
}
// Test that data streams are not created before handshake.
TEST_P(QboneSessionTest, CannotCreateDataStreamBeforeHandshake) {
CreateClientAndServerSessions();
EXPECT_QUIC_BUG(client_peer_->ProcessPacketFromNetwork(TestPacketIn("hello")),
"Attempting to send packet before encryption established");
EXPECT_QUIC_BUG(server_peer_->ProcessPacketFromNetwork(TestPacketIn("hello")),
"Attempting to send packet before encryption established");
EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
}
TEST_P(QboneSessionTest, ControlRequests) {
CreateClientAndServerSessions();
StartHandshake();
EXPECT_TRUE(client_handler_->data().empty());
EXPECT_FALSE(client_handler_->error());
EXPECT_TRUE(server_handler_->data().empty());
EXPECT_FALSE(server_handler_->error());
QboneClientRequest client_request;
client_request.SetExtension(client_placeholder, "hello from the server");
EXPECT_TRUE(server_peer_->SendClientRequest(client_request));
runner_.Run();
ASSERT_FALSE(client_handler_->data().empty());
EXPECT_THAT(client_handler_->data()[0].GetExtension(client_placeholder),
Eq("hello from the server"));
EXPECT_FALSE(client_handler_->error());
QboneServerRequest server_request;
server_request.SetExtension(server_placeholder, "hello from the client");
EXPECT_TRUE(client_peer_->SendServerRequest(server_request));
runner_.Run();
ASSERT_FALSE(server_handler_->data().empty());
EXPECT_THAT(server_handler_->data()[0].GetExtension(server_placeholder),
Eq("hello from the client"));
EXPECT_FALSE(server_handler_->error());
}
} // namespace
} // namespace test
} // namespace quic