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