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);