Introduce Legacy Version Encapsulation
This feature allows modern QUIC versions to encapsulate their first flight in Q043 packets, to support SNI-extraction middleboxes that do not support modern versions. More details in the design document at <go/quic-legacy-encaps>.
Introduce legacy version encapsulation, protected by gfe2_reloadable_flag_quic_dispatcher_legacy_version_encapsulation.
PiperOrigin-RevId: 317921151
Change-Id: I2dd8a3641caf3fd3d283613de3d117c1bdbea632
diff --git a/quic/core/crypto/crypto_protocol.h b/quic/core/crypto/crypto_protocol.h
index c178aa2..c59830b 100644
--- a/quic/core/crypto/crypto_protocol.h
+++ b/quic/core/crypto/crypto_protocol.h
@@ -339,6 +339,8 @@
// flow control receive window.
const QuicTag kUAID = TAG('U', 'A', 'I', 'D'); // Client's User Agent ID.
const QuicTag kXLCT = TAG('X', 'L', 'C', 'T'); // Expected leaf certificate.
+const QuicTag kQLVE = TAG('Q', 'L', 'V', 'E'); // Legacy Version
+ // Encapsulation.
const QuicTag kMAD = TAG('M', 'A', 'D', 0); // Max Ack Delay (IETF QUIC)
diff --git a/quic/core/crypto/quic_crypto_client_config.cc b/quic/core/crypto/quic_crypto_client_config.cc
index 28193ee..9314e99 100644
--- a/quic/core/crypto/quic_crypto_client_config.cc
+++ b/quic/core/crypto/quic_crypto_client_config.cc
@@ -69,7 +69,8 @@
session_cache_(std::move(session_cache)),
enable_zero_rtt_for_tls_(
GetQuicReloadableFlag(quic_enable_zero_rtt_for_tls)),
- ssl_ctx_(TlsClientConnection::CreateSslCtx(enable_zero_rtt_for_tls_)) {
+ ssl_ctx_(TlsClientConnection::CreateSslCtx(enable_zero_rtt_for_tls_)),
+ disable_chlo_padding_(GetQuicReloadableFlag(quic_dont_pad_chlo)) {
DCHECK(proof_verifier_.get());
SetDefaults();
}
@@ -420,7 +421,7 @@
CryptoHandshakeMessage* out) const {
out->set_tag(kCHLO);
// TODO(rch): Remove this when we remove quic_use_chlo_packet_size flag.
- if (pad_inchoate_hello_ && !GetQuicReloadableFlag(quic_dont_pad_chlo)) {
+ if (pad_inchoate_hello_ && !disable_chlo_padding_) {
out->set_minimum_size(kClientHelloMinimumSize);
} else {
out->set_minimum_size(1);
@@ -509,7 +510,7 @@
FillInchoateClientHello(server_id, preferred_version, cached, rand,
/* demand_x509_proof= */ true, out_params, out);
- if (pad_full_hello_ && !GetQuicReloadableFlag(quic_dont_pad_chlo)) {
+ if (pad_full_hello_ && !disable_chlo_padding_) {
out->set_minimum_size(kClientHelloMinimumSize);
} else {
out->set_minimum_size(1);
diff --git a/quic/core/crypto/quic_crypto_client_config.h b/quic/core/crypto/quic_crypto_client_config.h
index e90cc7e..ce0efe5 100644
--- a/quic/core/crypto/quic_crypto_client_config.h
+++ b/quic/core/crypto/quic_crypto_client_config.h
@@ -397,6 +397,10 @@
bool pad_full_hello() const { return pad_full_hello_; }
void set_pad_full_hello(bool new_value) { pad_full_hello_ = new_value; }
+ void set_disable_chlo_padding(bool disabled) {
+ disable_chlo_padding_ = disabled;
+ }
+
private:
// Sets the members to reasonable, default values.
void SetDefaults();
@@ -466,6 +470,7 @@
// other means of verifying the client.
bool pad_inchoate_hello_ = true;
bool pad_full_hello_ = true;
+ bool disable_chlo_padding_;
};
} // namespace quic
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index a729721..5b9419f 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -4285,6 +4285,95 @@
"test");
}
+TEST_P(EndToEndTest, LegacyVersionEncapsulation) {
+ if (!version_.HasLongHeaderLengths()) {
+ // Decapsulating Legacy Version Encapsulation packets from these versions
+ // is not currently supported in QuicDispatcher.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ SetQuicReloadableFlag(quic_dont_pad_chlo, true);
+ SetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation, true);
+ client_config_.SetClientConnectionOptions(QuicTagVector{kQLVE});
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_GT(GetClientConnection()
+ ->GetStats()
+ .sent_legacy_version_encapsulated_packets,
+ 0u);
+}
+
+TEST_P(EndToEndTest, LegacyVersionEncapsulationWithMultiPacketChlo) {
+ if (!version_.HasLongHeaderLengths()) {
+ // Decapsulating Legacy Version Encapsulation packets from these versions
+ // is not currently supported in QuicDispatcher.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ if (!version_.UsesTls()) {
+ // This test uses custom transport parameters to increase the size of the
+ // CHLO, and those are only supported with TLS.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ SetQuicReloadableFlag(quic_dont_pad_chlo, true);
+ SetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation, true);
+ client_config_.SetClientConnectionOptions(QuicTagVector{kQLVE});
+ constexpr auto kCustomParameter =
+ static_cast<TransportParameters::TransportParameterId>(0xff34);
+ client_config_.custom_transport_parameters_to_send()[kCustomParameter] =
+ std::string(2000, '?');
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_GT(GetClientConnection()
+ ->GetStats()
+ .sent_legacy_version_encapsulated_packets,
+ 0u);
+}
+
+TEST_P(EndToEndTest, LegacyVersionEncapsulationWithVersionNegotiation) {
+ if (!version_.HasLongHeaderLengths()) {
+ // Decapsulating Legacy Version Encapsulation packets from these versions
+ // is not currently supported in QuicDispatcher.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ client_supported_versions_.insert(client_supported_versions_.begin(),
+ QuicVersionReservedForNegotiation());
+ SetQuicReloadableFlag(quic_dont_pad_chlo, true);
+ SetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation, true);
+ client_config_.SetClientConnectionOptions(QuicTagVector{kQLVE});
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_GT(GetClientConnection()
+ ->GetStats()
+ .sent_legacy_version_encapsulated_packets,
+ 0u);
+}
+
+TEST_P(EndToEndTest, LegacyVersionEncapsulationWithLoss) {
+ if (!version_.HasLongHeaderLengths()) {
+ // Decapsulating Legacy Version Encapsulation packets from these versions
+ // is not currently supported in QuicDispatcher.
+ ASSERT_TRUE(Initialize());
+ return;
+ }
+ SetPacketLossPercentage(30);
+ SetQuicReloadableFlag(quic_dont_pad_chlo, true);
+ SetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation, true);
+ client_config_.SetClientConnectionOptions(QuicTagVector{kQLVE});
+ ASSERT_TRUE(Initialize());
+ EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+ EXPECT_EQ("200", client_->response_headers()->find(":status")->second);
+ EXPECT_GT(GetClientConnection()
+ ->GetStats()
+ .sent_legacy_version_encapsulated_packets,
+ 0u);
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/http/quic_spdy_client_session.cc b/quic/core/http/quic_spdy_client_session.cc
index 78b6a56..066adb9 100644
--- a/quic/core/http/quic_spdy_client_session.cc
+++ b/quic/core/http/quic_spdy_client_session.cc
@@ -39,6 +39,13 @@
void QuicSpdyClientSession::Initialize() {
crypto_stream_ = CreateQuicCryptoStream();
+ if (config()->HasClientRequestedIndependentOption(kQLVE,
+ Perspective::IS_CLIENT)) {
+ connection()->EnableLegacyVersionEncapsulation(server_id_.host());
+ // Legacy Version Encapsulation needs CHLO padding to be disabled.
+ // TODO(dschinazi) remove this line once we deprecate quic_dont_pad_chlo.
+ crypto_config_->set_disable_chlo_padding(true);
+ }
QuicSpdyClientSessionBase::Initialize();
}
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index b5c496f..b4c454f 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -25,6 +25,7 @@
#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
#include "net/third_party/quiche/src/quic/core/quic_constants.h"
#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_legacy_version_encapsulator.h"
#include "net/third_party/quiche/src/quic/core/quic_types.h"
#include "net/third_party/quiche/src/quic/core/quic_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
@@ -33,6 +34,7 @@
#include "net/third_party/quiche/src/quic/platform/api/quic_exported_stats.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_hostname_utils.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_map_util.h"
#include "net/third_party/quiche/src/quic/platform/api/quic_string_utils.h"
@@ -616,8 +618,7 @@
}
if (config.HasReceivedMaxPacketSize()) {
peer_max_packet_size_ = config.ReceivedMaxPacketSize();
- packet_creator_.SetMaxPacketLength(
- GetLimitedMaxPacketSize(packet_creator_.max_packet_length()));
+ MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
}
if (config.HasReceivedMaxDatagramFrameSize()) {
packet_creator_.SetMaxDatagramFrameSize(
@@ -633,6 +634,29 @@
}
}
+void QuicConnection::EnableLegacyVersionEncapsulation(
+ const std::string& server_name) {
+ if (perspective_ != Perspective::IS_CLIENT) {
+ QUIC_BUG << "Cannot enable Legacy Version Encapsulation on the server";
+ return;
+ }
+ if (legacy_version_encapsulation_enabled_) {
+ QUIC_BUG << "Do not call EnableLegacyVersionEncapsulation twice";
+ return;
+ }
+ if (!QuicHostnameUtils::IsValidSNI(server_name)) {
+ // Legacy Version Encapsulation is only used when SNI is transmitted.
+ QUIC_DLOG(INFO)
+ << "Refusing to use Legacy Version Encapsulation with invalid SNI \""
+ << server_name << "\"";
+ return;
+ }
+ QUIC_DLOG(INFO) << "Enabling Legacy Version Encapsulation with SNI \""
+ << server_name << "\"";
+ legacy_version_encapsulation_enabled_ = true;
+ legacy_version_encapsulation_sni_ = server_name;
+}
+
void QuicConnection::ApplyConnectionOptions(
const QuicTagVector& connection_options) {
sent_packet_manager_.ApplyConnectionOptions(connection_options);
@@ -1869,6 +1893,29 @@
pending_version_negotiation_packet_ = false;
}
+void QuicConnection::MaybeActivateLegacyVersionEncapsulation() {
+ if (!legacy_version_encapsulation_enabled_) {
+ return;
+ }
+ DCHECK(!legacy_version_encapsulation_in_progress_);
+ QUIC_BUG_IF(!packet_creator_.CanSetMaxPacketLength())
+ << "Cannot activate Legacy Version Encapsulation mid-packet";
+ QUIC_BUG_IF(coalesced_packet_.length() != 0u)
+ << "Cannot activate Legacy Version Encapsulation mid-coalesced-packet";
+ legacy_version_encapsulation_in_progress_ = true;
+ MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
+}
+void QuicConnection::MaybeDisactivateLegacyVersionEncapsulation() {
+ if (!legacy_version_encapsulation_in_progress_) {
+ return;
+ }
+ // Flush any remaining packet before disactivating encapsulation.
+ packet_creator_.FlushCurrentPacket();
+ DCHECK(legacy_version_encapsulation_enabled_);
+ legacy_version_encapsulation_in_progress_ = false;
+ MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
+}
+
size_t QuicConnection::SendCryptoData(EncryptionLevel level,
size_t write_length,
QuicStreamOffset offset) {
@@ -1880,8 +1927,17 @@
!ShouldGeneratePacket(HAS_RETRANSMITTABLE_DATA, IS_HANDSHAKE)) {
return 0;
}
- ScopedPacketFlusher flusher(this);
- return packet_creator_.ConsumeCryptoData(level, write_length, offset);
+ if (level == ENCRYPTION_INITIAL) {
+ MaybeActivateLegacyVersionEncapsulation();
+ }
+ size_t consumed_length;
+ {
+ ScopedPacketFlusher flusher(this);
+ consumed_length =
+ packet_creator_.ConsumeCryptoData(level, write_length, offset);
+ } // Added scope ensures packets are flushed before continuing.
+ MaybeDisactivateLegacyVersionEncapsulation();
+ return consumed_length;
}
QuicConsumedData QuicConnection::SendStreamData(QuicStreamId id,
@@ -1893,13 +1949,24 @@
return QuicConsumedData(0, false);
}
- // Opportunistically bundle an ack with every outgoing packet.
- // Particularly, we want to bundle with handshake packets since we don't know
- // which decrypter will be used on an ack packet following a handshake
- // packet (a handshake packet from client to server could result in a REJ or a
- // SHLO from the server, leading to two different decrypters at the server.)
- ScopedPacketFlusher flusher(this);
- return packet_creator_.ConsumeData(id, write_length, offset, state);
+ if (packet_creator_.encryption_level() == ENCRYPTION_INITIAL &&
+ QuicUtils::IsCryptoStreamId(transport_version(), id)) {
+ MaybeActivateLegacyVersionEncapsulation();
+ }
+ QuicConsumedData consumed_data(0, false);
+ {
+ // Opportunistically bundle an ack with every outgoing packet.
+ // Particularly, we want to bundle with handshake packets since we don't
+ // know which decrypter will be used on an ack packet following a handshake
+ // packet (a handshake packet from client to server could result in a REJ or
+ // a SHLO from the server, leading to two different decrypters at the
+ // server.)
+ ScopedPacketFlusher flusher(this);
+ consumed_data =
+ packet_creator_.ConsumeData(id, write_length, offset, state);
+ } // Added scope ensures packets are flushed before continuing.
+ MaybeDisactivateLegacyVersionEncapsulation();
+ return consumed_data;
}
bool QuicConnection::SendControlFrame(const QuicFrame& frame) {
@@ -2038,6 +2105,31 @@
return info;
}
+void QuicConnection::MaybeUpdatePacketCreatorMaxPacketLengthAndPadding() {
+ QuicByteCount max_packet_length = GetLimitedMaxPacketSize(long_term_mtu_);
+ if (legacy_version_encapsulation_in_progress_) {
+ DCHECK(legacy_version_encapsulation_enabled_);
+ const QuicByteCount minimum_overhead =
+ QuicLegacyVersionEncapsulator::GetMinimumOverhead(
+ legacy_version_encapsulation_sni_);
+ if (max_packet_length < minimum_overhead) {
+ QUIC_BUG << "Cannot apply Legacy Version Encapsulation overhead because "
+ << "max_packet_length " << max_packet_length
+ << " < minimum_overhead " << minimum_overhead;
+ legacy_version_encapsulation_in_progress_ = false;
+ legacy_version_encapsulation_enabled_ = false;
+ MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
+ return;
+ }
+ max_packet_length -= minimum_overhead;
+ }
+ packet_creator_.SetMaxPacketLength(max_packet_length);
+ if (legacy_version_encapsulation_enabled_) {
+ packet_creator_.set_disable_padding_override(
+ legacy_version_encapsulation_in_progress_);
+ }
+}
+
void QuicConnection::ProcessUdpPacket(const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address,
const QuicReceivedPacket& packet) {
@@ -2516,7 +2608,7 @@
// Termination packets are encrypted and saved, so don't exit early.
const bool is_termination_packet = IsTerminationPacket(*packet);
QuicPacketNumber packet_number = packet->packet_number;
- const QuicPacketLength encrypted_length = packet->encrypted_length;
+ QuicPacketLength encrypted_length = packet->encrypted_length;
// Termination packets are eventually owned by TimeWaitListManager.
// Others are deleted at the end of this call.
if (is_termination_packet) {
@@ -2531,17 +2623,21 @@
}
DCHECK_LE(encrypted_length, kMaxOutgoingPacketSize);
- if (!is_mtu_discovery) {
- DCHECK_LE(encrypted_length, packet_creator_.max_packet_length());
- }
+ DCHECK(is_mtu_discovery ||
+ encrypted_length <= packet_creator_.max_packet_length())
+ << " encrypted_length=" << encrypted_length
+ << " > packet_creator max_packet_length="
+ << packet_creator_.max_packet_length();
QUIC_DVLOG(1) << ENDPOINT << "Sending packet " << packet_number << " : "
<< (IsRetransmittable(*packet) == HAS_RETRANSMITTABLE_DATA
? "data bearing "
: " ack only ")
<< ", encryption level: " << packet->encryption_level
<< ", encrypted length:" << encrypted_length
- << ", fate: " << SerializedPacketFateToString(fate);
- QUIC_DVLOG(2) << ENDPOINT << "packet(" << packet_number << "): " << std::endl
+ << ", fate: " << fate;
+ QUIC_DVLOG(2) << ENDPOINT << packet->encryption_level << " packet number "
+ << packet_number << " of length " << encrypted_length << ": "
+ << std::endl
<< quiche::QuicheTextUtils::HexDump(quiche::QuicheStringPiece(
packet->encrypted_buffer, encrypted_length));
@@ -2614,6 +2710,48 @@
// write error has been handled.
QUIC_BUG_IF(!version().CanSendCoalescedPackets());
return false;
+ case LEGACY_VERSION_ENCAPSULATE: {
+ DCHECK(!is_mtu_discovery);
+ DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+ DCHECK_EQ(packet->encryption_level, ENCRYPTION_INITIAL);
+ DCHECK(legacy_version_encapsulation_enabled_);
+ DCHECK(legacy_version_encapsulation_in_progress_);
+ QuicPacketLength encapsulated_length =
+ QuicLegacyVersionEncapsulator::Encapsulate(
+ legacy_version_encapsulation_sni_,
+ quiche::QuicheStringPiece(packet->encrypted_buffer,
+ packet->encrypted_length),
+ server_connection_id_, framer_.creation_time(),
+ GetLimitedMaxPacketSize(long_term_mtu_),
+ const_cast<char*>(packet->encrypted_buffer));
+ if (encapsulated_length != 0) {
+ stats_.sent_legacy_version_encapsulated_packets++;
+ packet->encrypted_length = encapsulated_length;
+ encrypted_length = encapsulated_length;
+ QUIC_DVLOG(2)
+ << ENDPOINT
+ << "Successfully performed Legacy Version Encapsulation on "
+ << packet->encryption_level << " packet number " << packet_number
+ << " of length " << encrypted_length << ": " << std::endl
+ << quiche::QuicheTextUtils::HexDump(quiche::QuicheStringPiece(
+ packet->encrypted_buffer, encrypted_length));
+ } else {
+ QUIC_BUG << ENDPOINT
+ << "Failed to perform Legacy Version Encapsulation on "
+ << packet->encryption_level << " packet number "
+ << packet_number << " of length " << encrypted_length;
+ }
+ if (!buffered_packets_.empty() || HandleWriteBlocked()) {
+ // Buffer the packet.
+ buffered_packets_.emplace_back(*packet, self_address(), peer_address());
+ } else { // Send the packet to the writer.
+ // writer_->WritePacket transfers buffer ownership back to the writer.
+ packet->release_encrypted_buffer = nullptr;
+ result = writer_->WritePacket(packet->encrypted_buffer,
+ encrypted_length, self_address().host(),
+ peer_address(), per_packet_options_);
+ }
+ } break;
default:
DCHECK(false);
break;
@@ -3438,7 +3576,7 @@
void QuicConnection::SetMaxPacketLength(QuicByteCount length) {
long_term_mtu_ = length;
- packet_creator_.SetMaxPacketLength(GetLimitedMaxPacketSize(length));
+ MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
}
bool QuicConnection::HasQueuedData() const {
@@ -4526,15 +4664,22 @@
SerializedPacketFate QuicConnection::DeterminePacketFate(
bool is_mtu_discovery) {
- if (version().CanSendCoalescedPackets() && !IsHandshakeConfirmed() &&
- !is_mtu_discovery) {
- // Before receiving ACK for any 1-RTT packets, always try to coalesce
- // packet (except MTU discovery packet).
- return COALESCE;
+ if (legacy_version_encapsulation_in_progress_) {
+ DCHECK(!is_mtu_discovery);
+ return LEGACY_VERSION_ENCAPSULATE;
}
- // Packet cannot be coalesced, flush existing coalesced packet.
- if (version().CanSendCoalescedPackets() && !FlushCoalescedPacket()) {
- return FAILED_TO_WRITE_COALESCED_PACKET;
+ if (version().CanSendCoalescedPackets()) {
+ // Disable coalescing when Legacy Version Encapsulation is in use to avoid
+ // having to reframe encapsulated packets.
+ if (!IsHandshakeConfirmed() && !is_mtu_discovery) {
+ // Before receiving ACK for any 1-RTT packets, always try to coalesce
+ // packet (except MTU discovery packet).
+ return COALESCE;
+ }
+ // Packet cannot be coalesced, flush existing coalesced packet.
+ if (!FlushCoalescedPacket()) {
+ return FAILED_TO_WRITE_COALESCED_PACKET;
+ }
}
if (!buffered_packets_.empty() || HandleWriteBlocked()) {
return BUFFER;
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index bc30a70..3b602ef 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -985,6 +985,10 @@
void OnUserAgentIdKnown() { sent_packet_manager_.OnUserAgentIdKnown(); }
+ // Enables Legacy Version Encapsulation using |server_name| as SNI.
+ // Can only be set if this is a client connection.
+ void EnableLegacyVersionEncapsulation(const std::string& server_name);
+
protected:
// Calls cancel() on all the alarms owned by this connection.
void CancelAllAlarms();
@@ -1330,6 +1334,13 @@
// Returns string which contains undecryptable packets information.
std::string UndecryptablePacketsInfo() const;
+ // Sets the max packet length on the packet creator if needed.
+ void MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
+
+ // Sets internal state to enable or disable Legacy Version Encapsulation.
+ void MaybeActivateLegacyVersionEncapsulation();
+ void MaybeDisactivateLegacyVersionEncapsulation();
+
QuicFramer framer_;
// Contents received in the current packet, especially used to identify
@@ -1721,6 +1732,14 @@
const bool default_enable_5rto_blackhole_detection_ =
GetQuicReloadableFlag(quic_default_enable_5rto_blackhole_detection2);
+
+ // Whether the Legacy Version Encapsulation feature is enabled.
+ bool legacy_version_encapsulation_enabled_ = false;
+ // Whether we are in the middle of sending a packet using Legacy Version
+ // Encapsulation.
+ bool legacy_version_encapsulation_in_progress_ = false;
+ // SNI to send when using Legacy Version Encapsulation.
+ std::string legacy_version_encapsulation_sni_;
};
} // namespace quic
diff --git a/quic/core/quic_connection_stats.cc b/quic/core/quic_connection_stats.cc
index 89f7e54..191918c 100644
--- a/quic/core/quic_connection_stats.cc
+++ b/quic/core/quic_connection_stats.cc
@@ -53,6 +53,8 @@
os << " num_coalesced_packets_processed: "
<< s.num_coalesced_packets_processed;
os << " num_ack_aggregation_epochs: " << s.num_ack_aggregation_epochs;
+ os << " sent_legacy_version_encapsulated_packets: "
+ << s.sent_legacy_version_encapsulated_packets;
os << " }";
return os;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index d0211da..3579be0 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -158,6 +158,10 @@
// Max consecutive retransmission timeout before making forward progress.
size_t max_consecutive_rto_with_forward_progress = 0;
+
+ // Number of sent packets that were encapsulated using Legacy Version
+ // Encapsulation.
+ QuicPacketCount sent_legacy_version_encapsulated_packets = 0;
};
} // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index e5ab141..864a824 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -10371,6 +10371,49 @@
EXPECT_NE(nullptr, writer_->coalesced_packet());
}
+TEST_P(QuicConnectionTest, LegacyVersionEncapsulation) {
+ connection_.EnableLegacyVersionEncapsulation("test.example.org");
+
+ MockQuicConnectionDebugVisitor debug_visitor;
+ connection_.set_debug_visitor(&debug_visitor);
+ EXPECT_CALL(debug_visitor, OnPacketSent(_, _, _)).Times(1);
+
+ // Our TestPacketWriter normally parses the sent packet using the version
+ // from the connection, so here we need to tell it to use the encapsulation
+ // version, and reset the initial decrypter for that version.
+ writer_->framer()->SetSupportedVersions(
+ SupportedVersions(LegacyVersionForEncapsulation()));
+ writer_->framer()->framer()->SetInitialObfuscators(
+ connection_.connection_id());
+
+ {
+ QuicConnection::ScopedPacketFlusher flusher(&connection_);
+ connection_.SendCryptoDataWithString("TEST_CRYPTO_DATA", /*offset=*/0);
+ }
+
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+ // Verify that the packet is fully padded.
+ EXPECT_EQ(connection_.max_packet_length(), writer_->last_packet_size());
+
+ // Check that the connection stats show Legacy Version Encapsulation was used.
+ EXPECT_GT(connection_.GetStats().sent_legacy_version_encapsulated_packets,
+ 0u);
+
+ // Verify that the sent packet was in fact encapsulated, and check header.
+ const QuicPacketHeader& encapsulated_header = writer_->last_packet_header();
+ EXPECT_TRUE(encapsulated_header.version_flag);
+ EXPECT_EQ(encapsulated_header.version, LegacyVersionForEncapsulation());
+ EXPECT_EQ(encapsulated_header.destination_connection_id,
+ connection_.connection_id());
+
+ // Encapsulated packet should contain a stream frame for the crypto stream,
+ // optionally padding, and nothing else.
+ EXPECT_EQ(0u, writer_->crypto_frames().size());
+ EXPECT_EQ(1u, writer_->stream_frames().size());
+ EXPECT_EQ(writer_->frame_count(), writer_->framer()->padding_frames().size() +
+ writer_->stream_frames().size());
+}
+
TEST_P(QuicConnectionTest, ClientReceivedHandshakeDone) {
if (!connection_.version().HasHandshakeDone()) {
return;
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index b261690..afdec63 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -181,21 +181,113 @@
// Class which extracts the ALPN from a QUIC_CRYPTO CHLO packet.
class ChloAlpnExtractor : public ChloExtractor::Delegate {
public:
- void OnChlo(QuicTransportVersion /*version*/,
+ void OnChlo(QuicTransportVersion version,
QuicConnectionId /*server_connection_id*/,
const CryptoHandshakeMessage& chlo) override {
quiche::QuicheStringPiece alpn_value;
if (chlo.GetStringPiece(kALPN, &alpn_value)) {
alpn_ = std::string(alpn_value);
}
+ if (GetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_dispatcher_legacy_version_encapsulation,
+ 1, 3);
+ if (version == LegacyVersionForEncapsulation().transport_version) {
+ quiche::QuicheStringPiece qlve_value;
+ if (chlo.GetStringPiece(kQLVE, &qlve_value)) {
+ legacy_version_encapsulation_inner_packet_ = std::string(qlve_value);
+ }
+ }
+ }
}
std::string&& ConsumeAlpn() { return std::move(alpn_); }
+ std::string&& ConsumeLegacyVersionEncapsulationInnerPacket() {
+ DCHECK(GetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation));
+ return std::move(legacy_version_encapsulation_inner_packet_);
+ }
+
private:
std::string alpn_;
+ std::string legacy_version_encapsulation_inner_packet_;
};
+bool MaybeHandleLegacyVersionEncapsulation(
+ QuicDispatcher* dispatcher,
+ ChloAlpnExtractor* alpn_extractor,
+ const ReceivedPacketInfo& packet_info) {
+ DCHECK(GetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation));
+ std::string legacy_version_encapsulation_inner_packet =
+ alpn_extractor->ConsumeLegacyVersionEncapsulationInnerPacket();
+ if (legacy_version_encapsulation_inner_packet.empty()) {
+ // This CHLO did not contain the Legacy Version Encapsulation tag.
+ return false;
+ }
+ PacketHeaderFormat format;
+ QuicLongHeaderType long_packet_type;
+ bool version_present;
+ bool has_length_prefix;
+ QuicVersionLabel version_label;
+ ParsedQuicVersion parsed_version = ParsedQuicVersion::Unsupported();
+ QuicConnectionId destination_connection_id, source_connection_id;
+ bool retry_token_present;
+ quiche::QuicheStringPiece retry_token;
+ std::string detailed_error;
+ const QuicErrorCode error = QuicFramer::ParsePublicHeaderDispatcher(
+ QuicEncryptedPacket(legacy_version_encapsulation_inner_packet.data(),
+ legacy_version_encapsulation_inner_packet.length()),
+ kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
+ &destination_connection_id, &source_connection_id, &retry_token_present,
+ &retry_token, &detailed_error);
+ if (error != QUIC_NO_ERROR) {
+ QUIC_DLOG(ERROR)
+ << "Failed to parse Legacy Version Encapsulation inner packet:"
+ << detailed_error;
+ return false;
+ }
+ if (destination_connection_id != packet_info.destination_connection_id) {
+ // We enforce that the inner and outer connection IDs match to make sure
+ // this never impacts routing of packets.
+ QUIC_DLOG(ERROR) << "Ignoring Legacy Version Encapsulation packet "
+ "with mismatched connection ID "
+ << destination_connection_id << " vs "
+ << packet_info.destination_connection_id;
+ return false;
+ }
+ if (legacy_version_encapsulation_inner_packet.length() >=
+ packet_info.packet.length()) {
+ QUIC_BUG << "Inner packet cannot be larger than outer "
+ << legacy_version_encapsulation_inner_packet.length() << " vs "
+ << packet_info.packet.length();
+ return false;
+ }
+
+ QUIC_DVLOG(1) << "Extracted a Legacy Version Encapsulation "
+ << legacy_version_encapsulation_inner_packet.length()
+ << " byte packet of version " << parsed_version;
+
+ // Append zeroes to the end of the packet. This will ensure that
+ // we use the right number of bytes for calculating anti-amplification
+ // limits. Note that this only works for long headers of versions that carry
+ // long header lengths, since they'll ignore any trailing zeroes. We still
+ // do this for all packets to ensure version negotiation works.
+ legacy_version_encapsulation_inner_packet.append(
+ packet_info.packet.length() -
+ legacy_version_encapsulation_inner_packet.length(),
+ 0x00);
+
+ // Process the inner packet as if it had been received by itself.
+ QuicReceivedPacket received_encapsulated_packet(
+ legacy_version_encapsulation_inner_packet.data(),
+ legacy_version_encapsulation_inner_packet.length(),
+ packet_info.packet.receipt_time());
+ dispatcher->ProcessPacket(packet_info.self_address, packet_info.peer_address,
+ received_encapsulated_packet);
+ QUIC_CODE_COUNT(quic_legacy_version_encapsulation_decapsulated);
+ return true;
+}
+
} // namespace
QuicDispatcher::QuicDispatcher(
@@ -391,6 +483,26 @@
auto it = session_map_.find(server_connection_id);
if (it != session_map_.end()) {
DCHECK(!buffered_packets_.HasBufferedPackets(server_connection_id));
+ if (GetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_dispatcher_legacy_version_encapsulation,
+ 2, 3);
+ if (packet_info.version_flag &&
+ packet_info.version != it->second->version() &&
+ packet_info.version == LegacyVersionForEncapsulation()) {
+ // This packet is using the Legacy Version Encapsulation version but the
+ // corresponding session isn't, attempt extraction of inner packet.
+ ChloAlpnExtractor alpn_extractor;
+ if (ChloExtractor::Extract(packet_info.packet, packet_info.version,
+ config_->create_session_tag_indicators(),
+ &alpn_extractor,
+ server_connection_id.length())) {
+ if (MaybeHandleLegacyVersionEncapsulation(this, &alpn_extractor,
+ packet_info)) {
+ return true;
+ }
+ }
+ }
+ }
it->second->ProcessUdpPacket(packet_info.self_address,
packet_info.peer_address, packet_info.packet);
return true;
@@ -560,6 +672,14 @@
break;
}
}
+ if (GetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation)) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(
+ quic_dispatcher_legacy_version_encapsulation, 3, 3);
+ if (MaybeHandleLegacyVersionEncapsulation(this, &alpn_extractor,
+ *packet_info)) {
+ break;
+ }
+ }
ProcessChlo({alpn_extractor.ConsumeAlpn()}, packet_info);
} break;
case kFateTimeWait:
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index ae084ec..ef4190d 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -580,6 +580,73 @@
TestTlsMultiPacketClientHello(/*add_reordering=*/true);
}
+TEST_P(QuicDispatcherTestAllVersions, LegacyVersionEncapsulation) {
+ if (!version_.HasLongHeaderLengths()) {
+ // Decapsulating Legacy Version Encapsulation packets from these versions
+ // is not currently supported in QuicDispatcher.
+ return;
+ }
+ SetQuicReloadableFlag(quic_dont_pad_chlo, true);
+ SetQuicReloadableFlag(quic_dispatcher_legacy_version_encapsulation, true);
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ QuicConnectionId server_connection_id = TestConnectionId();
+ QuicConfig client_config = DefaultQuicConfig();
+ client_config.SetClientConnectionOptions(QuicTagVector{kQLVE});
+ std::vector<std::unique_ptr<QuicReceivedPacket>> packets =
+ GetFirstFlightOfPackets(version_, client_config, server_connection_id);
+ ASSERT_EQ(packets.size(), 1u);
+
+ // Validate that Legacy Version Encapsulation is actually being used by
+ // checking the version of the packet before processing it.
+ PacketHeaderFormat format = IETF_QUIC_LONG_HEADER_PACKET;
+ QuicLongHeaderType long_packet_type;
+ bool version_present;
+ bool has_length_prefix;
+ QuicVersionLabel version_label;
+ ParsedQuicVersion parsed_version = ParsedQuicVersion::Unsupported();
+ QuicConnectionId destination_connection_id, source_connection_id;
+ bool retry_token_present;
+ quiche::QuicheStringPiece retry_token;
+ std::string detailed_error;
+ const QuicErrorCode error = QuicFramer::ParsePublicHeaderDispatcher(
+ QuicEncryptedPacket(packets[0]->data(), packets[0]->length()),
+ kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
+ &destination_connection_id, &source_connection_id, &retry_token_present,
+ &retry_token, &detailed_error);
+ ASSERT_THAT(error, IsQuicNoError()) << detailed_error;
+ EXPECT_EQ(format, GOOGLE_QUIC_PACKET);
+ EXPECT_TRUE(version_present);
+ EXPECT_FALSE(has_length_prefix);
+ EXPECT_EQ(parsed_version, LegacyVersionForEncapsulation());
+ EXPECT_EQ(destination_connection_id, server_connection_id);
+ EXPECT_EQ(source_connection_id, EmptyQuicConnectionId());
+ EXPECT_FALSE(retry_token_present);
+ EXPECT_TRUE(detailed_error.empty());
+
+ // Processing the packet should create a new session.
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(server_connection_id, client_address,
+ Eq(ExpectedAlpn()), _))
+ .WillOnce(Return(ByMove(CreateSession(
+ dispatcher_.get(), config_, server_connection_id, client_address,
+ &mock_helper_, &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_))));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(2);
+
+ ProcessReceivedPacket(packets[0]->Clone(), client_address, version_,
+ server_connection_id);
+ EXPECT_EQ(dispatcher_->session_map().size(), 1u);
+
+ // Processing the same packet a second time should also be routed by the
+ // dispatcher to the right connection (we expect ProcessUdpPacket to be
+ // called twice, see the EXPECT_CALL above).
+ ProcessReceivedPacket(std::move(packets[0]), client_address, version_,
+ server_connection_id);
+}
+
TEST_P(QuicDispatcherTestAllVersions, ProcessPackets) {
QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
diff --git a/quic/core/quic_legacy_version_encapsulator.cc b/quic/core/quic_legacy_version_encapsulator.cc
new file mode 100644
index 0000000..816b8c7
--- /dev/null
+++ b/quic/core/quic_legacy_version_encapsulator.cc
@@ -0,0 +1,145 @@
+// Copyright (c) 2020 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 "net/third_party/quiche/src/quic/core/quic_legacy_version_encapsulator.h"
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_handshake_message.h"
+#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
+
+namespace quic {
+
+QuicLegacyVersionEncapsulator::QuicLegacyVersionEncapsulator(
+ QuicPacketBuffer packet_buffer)
+ : packet_buffer_(packet_buffer) {}
+
+QuicLegacyVersionEncapsulator::~QuicLegacyVersionEncapsulator() {}
+
+// static
+QuicByteCount QuicLegacyVersionEncapsulator::GetMinimumOverhead(
+ quiche::QuicheStringPiece sni) {
+ // The number 52 is the sum of:
+ // - Flags (1 byte)
+ // - Server Connection ID (8 bytes)
+ // - Version (4 bytes)
+ // - Packet Number (1 byte)
+ // - Message Authentication Hash (12 bytes)
+ // - Frame Type (1 byte)
+ // - Stream ID (1 byte)
+ // - ClientHello tag (4 bytes)
+ // - ClientHello num tags (2 bytes)
+ // - Padding (2 bytes)
+ // - SNI tag (4 bytes)
+ // - SNI end offset (4 bytes)
+ // - QLVE tag (4 bytes)
+ // - QLVE end offset (4 bytes)
+ return 52 + sni.length();
+}
+
+QuicPacketBuffer QuicLegacyVersionEncapsulator::GetPacketBuffer() {
+ return packet_buffer_;
+}
+
+void QuicLegacyVersionEncapsulator::OnSerializedPacket(
+ SerializedPacket serialized_packet) {
+ if (encrypted_length_ != 0) {
+ unrecoverable_failure_encountered_ = true;
+ QUIC_BUG << "OnSerializedPacket called twice";
+ return;
+ }
+ if (serialized_packet.encrypted_length == 0) {
+ unrecoverable_failure_encountered_ = true;
+ QUIC_BUG << "OnSerializedPacket called with empty packet";
+ return;
+ }
+ encrypted_length_ = serialized_packet.encrypted_length;
+}
+
+void QuicLegacyVersionEncapsulator::OnUnrecoverableError(
+ QuicErrorCode error,
+ const std::string& error_details) {
+ unrecoverable_failure_encountered_ = true;
+ QUIC_BUG << "QuicLegacyVersionEncapsulator received error " << error << ": "
+ << error_details;
+}
+
+bool QuicLegacyVersionEncapsulator::ShouldGeneratePacket(
+ HasRetransmittableData /*retransmittable*/,
+ IsHandshake /*handshake*/) {
+ return true;
+}
+
+const QuicFrames
+QuicLegacyVersionEncapsulator::MaybeBundleAckOpportunistically() {
+ // We do not want to ever include any ACKs here, return an empty array.
+ return QuicFrames();
+}
+
+// static
+QuicPacketLength QuicLegacyVersionEncapsulator::Encapsulate(
+ quiche::QuicheStringPiece sni,
+ quiche::QuicheStringPiece inner_packet,
+ const QuicConnectionId& server_connection_id,
+ QuicTime creation_time,
+ QuicByteCount outer_max_packet_length,
+ char* out) {
+ if (outer_max_packet_length > kMaxOutgoingPacketSize) {
+ outer_max_packet_length = kMaxOutgoingPacketSize;
+ }
+ CryptoHandshakeMessage outer_chlo;
+ outer_chlo.set_tag(kCHLO);
+ outer_chlo.SetStringPiece(kSNI, sni);
+ outer_chlo.SetStringPiece(kQLVE, inner_packet);
+ const QuicData& serialized_outer_chlo = outer_chlo.GetSerialized();
+ DCHECK(!LegacyVersionForEncapsulation().UsesCryptoFrames());
+ DCHECK(LegacyVersionForEncapsulation().UsesQuicCrypto());
+ QuicStreamFrame outer_stream_frame(
+ QuicUtils::GetCryptoStreamId(
+ LegacyVersionForEncapsulation().transport_version),
+ /*fin=*/false,
+ /*offset=*/0, serialized_outer_chlo.AsStringPiece());
+ QuicFramer outer_framer(
+ ParsedQuicVersionVector{LegacyVersionForEncapsulation()}, creation_time,
+ Perspective::IS_CLIENT, kQuicDefaultConnectionIdLength);
+ outer_framer.SetInitialObfuscators(server_connection_id);
+ char outer_encrypted_packet[kMaxOutgoingPacketSize];
+ QuicPacketBuffer outer_packet_buffer(outer_encrypted_packet, nullptr);
+ QuicLegacyVersionEncapsulator creator_delegate(outer_packet_buffer);
+ QuicPacketCreator outer_creator(server_connection_id, &outer_framer,
+ &creator_delegate);
+ outer_creator.SetMaxPacketLength(outer_max_packet_length);
+ outer_creator.set_encryption_level(ENCRYPTION_INITIAL);
+ outer_creator.SetTransmissionType(NOT_RETRANSMISSION);
+ if (!outer_creator.AddPaddedSavedFrame(QuicFrame(outer_stream_frame),
+ NOT_RETRANSMISSION)) {
+ QUIC_BUG << "Failed to add Legacy Version Encapsulation stream frame "
+ "(max packet length is "
+ << outer_creator.max_packet_length() << ") " << outer_stream_frame;
+ return 0;
+ }
+ outer_creator.FlushCurrentPacket();
+ const QuicPacketLength encrypted_length = creator_delegate.encrypted_length_;
+ if (creator_delegate.unrecoverable_failure_encountered_ ||
+ encrypted_length == 0) {
+ QUIC_BUG << "Failed to perform Legacy Version Encapsulation of "
+ << inner_packet.length() << " bytes";
+ return 0;
+ }
+ if (encrypted_length > kMaxOutgoingPacketSize) {
+ QUIC_BUG << "Legacy Version Encapsulation outer creator generated a "
+ "packet with unexpected length "
+ << encrypted_length;
+ return 0;
+ }
+
+ QUIC_DLOG(INFO) << "Successfully performed Legacy Version Encapsulation from "
+ << inner_packet.length() << " bytes to " << encrypted_length;
+
+ // Replace our current packet with the encapsulated one.
+ memcpy(out, outer_encrypted_packet, encrypted_length);
+ return encrypted_length;
+}
+
+} // namespace quic
diff --git a/quic/core/quic_legacy_version_encapsulator.h b/quic/core/quic_legacy_version_encapsulator.h
new file mode 100644
index 0000000..3d9c156
--- /dev/null
+++ b/quic/core/quic_legacy_version_encapsulator.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2020 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.
+
+#ifndef QUICHE_QUIC_CORE_QUIC_LEGACY_VERSION_ENCAPSULATOR_H_
+#define QUICHE_QUIC_CORE_QUIC_LEGACY_VERSION_ENCAPSULATOR_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_packet_creator.h"
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
+
+namespace quic {
+
+// QuicLegacyVersionEncapsulator is responsible for encapsulation of packets
+// using Legacy Version Encapsulation.
+
+class QUIC_EXPORT_PRIVATE QuicLegacyVersionEncapsulator
+ : public QuicPacketCreator::DelegateInterface {
+ public:
+ // Encapsulates |inner_packet| into a new encapsulated packet that uses a
+ // CHLO of version LegacyVersionForEncapsulation() with server name |sni|
+ // exposed and using |server_connection_id|. The packet will be padded up to
+ // |outer_max_packet_length| bytes if necessary. On failure, returns 0. On
+ // success, returns the length of the outer encapsulated packet, and copies
+ // the contents of the encapsulated packet to |out|. |out| must point to a
+ // valid memory buffer capable of holding kMaxOutgoingPacketSize bytes.
+ static QuicPacketLength Encapsulate(
+ quiche::QuicheStringPiece sni,
+ quiche::QuicheStringPiece inner_packet,
+ const QuicConnectionId& server_connection_id,
+ QuicTime creation_time,
+ QuicByteCount outer_max_packet_length,
+ char* out);
+
+ // Returns the number of bytes of minimum overhead caused by Legacy Version
+ // Encapsulation, based on the length of the provided server name |sni|.
+ // The overhead may be higher due to extra padding added.
+ static QuicByteCount GetMinimumOverhead(quiche::QuicheStringPiece sni);
+
+ // Overrides for QuicPacketCreator::DelegateInterface.
+ QuicPacketBuffer GetPacketBuffer() override;
+ void OnSerializedPacket(SerializedPacket serialized_packet) override;
+ void OnUnrecoverableError(QuicErrorCode error,
+ const std::string& error_details) override;
+ bool ShouldGeneratePacket(HasRetransmittableData retransmittable,
+ IsHandshake handshake) override;
+ const QuicFrames MaybeBundleAckOpportunistically() override;
+
+ ~QuicLegacyVersionEncapsulator() override;
+
+ private:
+ explicit QuicLegacyVersionEncapsulator(QuicPacketBuffer packet_buffer);
+
+ // Disallow copy, move and assignment.
+ QuicLegacyVersionEncapsulator(const QuicLegacyVersionEncapsulator&) = delete;
+ QuicLegacyVersionEncapsulator(QuicLegacyVersionEncapsulator&&) = delete;
+ QuicLegacyVersionEncapsulator& operator=(
+ const QuicLegacyVersionEncapsulator&) = delete;
+ QuicLegacyVersionEncapsulator& operator=(QuicLegacyVersionEncapsulator&&) =
+ delete;
+
+ QuicPacketBuffer packet_buffer_;
+ QuicPacketLength encrypted_length_ = 0;
+ bool unrecoverable_failure_encountered_ = false;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE_QUIC_LEGACY_VERSION_ENCAPSULATOR_H_
diff --git a/quic/core/quic_legacy_version_encapsulator_test.cc b/quic/core/quic_legacy_version_encapsulator_test.cc
new file mode 100644
index 0000000..a71b2f4
--- /dev/null
+++ b/quic/core/quic_legacy_version_encapsulator_test.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 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 "net/third_party/quiche/src/quic/core/quic_legacy_version_encapsulator.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_string_piece.h"
+#include "net/third_party/quiche/src/common/platform/api/quiche_text_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+class QuicLegacyVersionEncapsulatorTest
+ : public QuicTestWithParam<ParsedQuicVersion> {
+ protected:
+ QuicLegacyVersionEncapsulatorTest()
+ : version_(GetParam()),
+ sni_("test.example.org"),
+ server_connection_id_(TestConnectionId()),
+ outer_max_packet_length_(kMaxOutgoingPacketSize),
+ encapsulated_length_(0) {}
+
+ void Encapsulate(const std::string& inner_packet) {
+ encapsulated_length_ = QuicLegacyVersionEncapsulator::Encapsulate(
+ sni_, inner_packet, server_connection_id_, QuicTime::Zero(),
+ outer_max_packet_length_, outer_buffer_);
+ }
+
+ void CheckEncapsulation() {
+ ASSERT_NE(encapsulated_length_, 0u);
+ ASSERT_EQ(encapsulated_length_, outer_max_packet_length_);
+ // Verify that the encapsulated packet parses as encapsulated.
+ PacketHeaderFormat format = IETF_QUIC_LONG_HEADER_PACKET;
+ QuicLongHeaderType long_packet_type;
+ bool version_present;
+ bool has_length_prefix;
+ QuicVersionLabel version_label;
+ ParsedQuicVersion parsed_version = ParsedQuicVersion::Unsupported();
+ QuicConnectionId destination_connection_id, source_connection_id;
+ bool retry_token_present;
+ quiche::QuicheStringPiece retry_token;
+ std::string detailed_error;
+ const QuicErrorCode error = QuicFramer::ParsePublicHeaderDispatcher(
+ QuicEncryptedPacket(outer_buffer_, encapsulated_length_),
+ kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
+ &destination_connection_id, &source_connection_id, &retry_token_present,
+ &retry_token, &detailed_error);
+ ASSERT_THAT(error, IsQuicNoError()) << detailed_error;
+ EXPECT_EQ(format, GOOGLE_QUIC_PACKET);
+ EXPECT_TRUE(version_present);
+ EXPECT_FALSE(has_length_prefix);
+ EXPECT_EQ(parsed_version, LegacyVersionForEncapsulation());
+ EXPECT_EQ(destination_connection_id, server_connection_id_);
+ EXPECT_EQ(source_connection_id, EmptyQuicConnectionId());
+ EXPECT_FALSE(retry_token_present);
+ EXPECT_TRUE(detailed_error.empty());
+ }
+
+ QuicByteCount Overhead() {
+ return QuicLegacyVersionEncapsulator::GetMinimumOverhead(sni_);
+ }
+
+ ParsedQuicVersion version_;
+ std::string sni_;
+ QuicConnectionId server_connection_id_;
+ QuicByteCount outer_max_packet_length_;
+ char outer_buffer_[kMaxOutgoingPacketSize];
+ QuicPacketLength encapsulated_length_;
+};
+
+INSTANTIATE_TEST_SUITE_P(QuicLegacyVersionEncapsulatorTests,
+ QuicLegacyVersionEncapsulatorTest,
+ ::testing::ValuesIn(AllSupportedVersions()),
+ ::testing::PrintToStringParamName());
+
+TEST_P(QuicLegacyVersionEncapsulatorTest, Simple) {
+ Encapsulate("TEST_INNER_PACKET");
+ CheckEncapsulation();
+}
+
+TEST_P(QuicLegacyVersionEncapsulatorTest, TooBig) {
+ std::string inner_packet(kMaxOutgoingPacketSize, '?');
+ EXPECT_QUIC_BUG(Encapsulate(inner_packet), "Legacy Version Encapsulation");
+ ASSERT_EQ(encapsulated_length_, 0u);
+}
+
+TEST_P(QuicLegacyVersionEncapsulatorTest, BarelyFits) {
+ QuicByteCount inner_size = kMaxOutgoingPacketSize - Overhead();
+ std::string inner_packet(inner_size, '?');
+ Encapsulate(inner_packet);
+ CheckEncapsulation();
+}
+
+TEST_P(QuicLegacyVersionEncapsulatorTest, DoesNotQuiteFit) {
+ QuicByteCount inner_size = 1 + kMaxOutgoingPacketSize - Overhead();
+ std::string inner_packet(inner_size, '?');
+ EXPECT_QUIC_BUG(Encapsulate(inner_packet), "Legacy Version Encapsulation");
+ ASSERT_EQ(encapsulated_length_, 0u);
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc
index 7672a37..f933a5a 100644
--- a/quic/core/quic_packet_creator.cc
+++ b/quic/core/quic_packet_creator.cc
@@ -165,6 +165,8 @@
if (length == max_packet_length_) {
return;
}
+ QUIC_DVLOG(1) << "Updating packet creator max packet length from "
+ << max_packet_length_ << " to " << length;
max_packet_length_ = length;
max_plaintext_size_ = framer_->GetMaxPlaintextSize(max_packet_length_);
@@ -283,6 +285,10 @@
bool needs_full_padding,
TransmissionType transmission_type,
QuicFrame* frame) {
+ QUIC_DVLOG(2) << "ConsumeCryptoDataToFillCurrentPacket " << level
+ << " write_length " << write_length << " offset " << offset
+ << (needs_full_padding ? " needs_full_padding" : "") << " "
+ << transmission_type;
if (!CreateCryptoFrame(level, write_length, offset, frame)) {
return false;
}
@@ -1339,6 +1345,8 @@
size_t QuicPacketCreator::ConsumeCryptoData(EncryptionLevel level,
size_t write_length,
QuicStreamOffset offset) {
+ QUIC_DVLOG(2) << "ConsumeCryptoData " << level << " write_length "
+ << write_length << " offset " << offset;
QUIC_BUG_IF(!flusher_attached_) << "Packet flusher is not attached when "
"generator tries to write crypto data.";
MaybeBundleAckOpportunistically();
@@ -1595,7 +1603,8 @@
/* last_frame_in_packet= */ true, GetPacketNumberLength());
}
if (frame_len == 0) {
- // Current open packet is full.
+ QUIC_DVLOG(1) << "Flushing because current open packet is full when adding "
+ << frame;
FlushCurrentPacket();
return false;
}
@@ -1718,6 +1727,10 @@
}
}
+ if (disable_padding_override_) {
+ needs_full_padding_ = false;
+ }
+
// Header protection requires a minimum plaintext packet size.
size_t extra_padding_bytes = 0;
if (framer_->version().HasHeaderProtection()) {
diff --git a/quic/core/quic_packet_creator.h b/quic/core/quic_packet_creator.h
index 743e46e..d71a1af 100644
--- a/quic/core/quic_packet_creator.h
+++ b/quic/core/quic_packet_creator.h
@@ -255,6 +255,7 @@
void set_encryption_level(EncryptionLevel level) {
packet_.encryption_level = level;
}
+ EncryptionLevel encryption_level() { return packet_.encryption_level; }
// packet number of the last created packet, or 0 if no packets have been
// created.
@@ -429,6 +430,10 @@
char* buffer,
size_t buffer_len);
+ void set_disable_padding_override(bool should_disable_padding) {
+ disable_padding_override_ = should_disable_padding;
+ }
+
private:
friend class test::QuicPacketCreatorPeer;
@@ -604,6 +609,9 @@
const bool fix_min_crypto_frame_size_ =
GetQuicReloadableFlag(quic_fix_min_crypto_frame_size);
+
+ // When true, this will override the padding generation code to disable it.
+ bool disable_padding_override_ = false;
};
} // namespace quic
diff --git a/quic/core/quic_types.cc b/quic/core/quic_types.cc
index 49ccdf0..a52571e 100644
--- a/quic/core/quic_types.cc
+++ b/quic/core/quic_types.cc
@@ -265,11 +265,17 @@
RETURN_STRING_LITERAL(BUFFER);
RETURN_STRING_LITERAL(SEND_TO_WRITER);
RETURN_STRING_LITERAL(FAILED_TO_WRITE_COALESCED_PACKET);
+ RETURN_STRING_LITERAL(LEGACY_VERSION_ENCAPSULATE);
default:
return quiche::QuicheStrCat("Unknown(", static_cast<int>(fate), ")");
}
}
+std::ostream& operator<<(std::ostream& os, SerializedPacketFate fate) {
+ os << SerializedPacketFateToString(fate);
+ return os;
+}
+
std::string EncryptionLevelToString(EncryptionLevel level) {
switch (level) {
RETURN_STRING_LITERAL(ENCRYPTION_INITIAL);
diff --git a/quic/core/quic_types.h b/quic/core/quic_types.h
index 39e1a87..8a37475 100644
--- a/quic/core/quic_types.h
+++ b/quic/core/quic_types.h
@@ -694,11 +694,16 @@
SEND_TO_WRITER, // Send packet to writer.
FAILED_TO_WRITE_COALESCED_PACKET, // Packet cannot be coalesced, error occurs
// when sending existing coalesced packet.
+ LEGACY_VERSION_ENCAPSULATE, // Perform Legacy Version Encapsulation on this
+ // packet.
};
QUIC_EXPORT_PRIVATE std::string SerializedPacketFateToString(
SerializedPacketFate fate);
+QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
+ const SerializedPacketFate fate);
+
// There are three different forms of CONNECTION_CLOSE.
enum QuicConnectionCloseType {
GOOGLE_QUIC_CONNECTION_CLOSE = 0,
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 925993f..461a488 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -674,6 +674,10 @@
return ParsedQuicVersion::ReservedForNegotiation();
}
+ParsedQuicVersion LegacyVersionForEncapsulation() {
+ return ParsedQuicVersion::Q043();
+}
+
std::string AlpnForVersion(ParsedQuicVersion parsed_version) {
if (parsed_version.handshake_protocol == PROTOCOL_TLS1_3) {
if (parsed_version.transport_version == QUIC_VERSION_IETF_DRAFT_29) {
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 9c7288a..341d398 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -400,6 +400,10 @@
QUIC_EXPORT_PRIVATE ParsedQuicVersion QuicVersionReservedForNegotiation();
+// Outer version used when encapsulating other packets using the Legacy Version
+// Encapsulation feature.
+QUIC_EXPORT_PRIVATE ParsedQuicVersion LegacyVersionForEncapsulation();
+
QUIC_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
const ParsedQuicVersion& version);