Interop of draft-ietf-quic-version-negotiation-05 for IETF 112
diff --git a/quic/core/crypto/crypto_utils.cc b/quic/core/crypto/crypto_utils.cc
index 2f1fa34..5a94b03 100644
--- a/quic/core/crypto/crypto_utils.cc
+++ b/quic/core/crypto/crypto_utils.cc
@@ -641,6 +641,69 @@
   return QUIC_NO_ERROR;
 }
 
+// static
+bool CryptoUtils::ValidateChosenVersion(
+    const QuicVersionLabel& version_information_chosen_version,
+    const ParsedQuicVersion& session_version, std::string* error_details) {
+  if (version_information_chosen_version !=
+      CreateQuicVersionLabel(session_version)) {
+    *error_details = absl::StrCat(
+        "Detected version mismatch: version_information contained ",
+        QuicVersionLabelToString(version_information_chosen_version),
+        " instead of ", ParsedQuicVersionToString(session_version));
+    return false;
+  }
+  return true;
+}
+
+// static
+bool CryptoUtils::ValidateServerVersions(
+    const QuicVersionLabelVector& version_information_other_versions,
+    const ParsedQuicVersion& session_version,
+    const ParsedQuicVersionVector& client_original_supported_versions,
+    std::string* error_details) {
+  if (client_original_supported_versions.empty()) {
+    // We did not receive a version negotiation packet.
+    return true;
+  }
+  // Parse the server's other versions.
+  ParsedQuicVersionVector parsed_other_versions;
+  for (const QuicVersionLabel& other_version :
+       version_information_other_versions) {
+    ParsedQuicVersion parsed_other_version =
+        ParseQuicVersionLabel(other_version);
+    if (parsed_other_version.IsKnown()) {
+      parsed_other_versions.push_back(parsed_other_version);
+    }
+  }
+  // Find the first version that we originally supported that is listed in the
+  // server's other versions.
+  ParsedQuicVersion expected_version = ParsedQuicVersion::Unsupported();
+  for (const ParsedQuicVersion& client_version :
+       client_original_supported_versions) {
+    if (std::find(parsed_other_versions.begin(), parsed_other_versions.end(),
+                  client_version) != parsed_other_versions.end()) {
+      expected_version = client_version;
+      break;
+    }
+  }
+  if (expected_version != session_version) {
+    *error_details = absl::StrCat(
+        "Downgrade attack detected: used ",
+        ParsedQuicVersionToString(session_version), " but ServerVersions(",
+        version_information_other_versions.size(), ")[",
+        QuicVersionLabelVectorToString(version_information_other_versions, ",",
+                                       30),
+        "] ClientOriginalVersions(", client_original_supported_versions.size(),
+        ")[",
+        ParsedQuicVersionVectorToString(client_original_supported_versions, ",",
+                                        30),
+        "]");
+    return false;
+  }
+  return true;
+}
+
 #define RETURN_STRING_LITERAL(x) \
   case x:                        \
     return #x
diff --git a/quic/core/crypto/crypto_utils.h b/quic/core/crypto/crypto_utils.h
index 15ffe06..2c61a90 100644
--- a/quic/core/crypto/crypto_utils.h
+++ b/quic/core/crypto/crypto_utils.h
@@ -217,6 +217,27 @@
       const ParsedQuicVersionVector& supported_versions,
       std::string* error_details);
 
+  // Validates that the chosen version from the version_information matches the
+  // version from the session. Returns true if they match, otherwise returns
+  // false and fills in |error_details|.
+  static bool ValidateChosenVersion(
+      const QuicVersionLabel& version_information_chosen_version,
+      const ParsedQuicVersion& session_version, std::string* error_details);
+
+  // Validates that there was no downgrade attack involving a version
+  // negotiation packet. This verifies that if the client was initially
+  // configured with |client_original_supported_versions| and it had received a
+  // version negotiation packet with |version_information_other_versions|, then
+  // it would have selected |session_version|. Returns true if they match (or if
+  // |version_information_other_versions| is empty indicating no version
+  // negotiation packet was received), otherwise returns
+  // false and fills in |error_details|.
+  static bool ValidateServerVersions(
+      const QuicVersionLabelVector& version_information_other_versions,
+      const ParsedQuicVersion& session_version,
+      const ParsedQuicVersionVector& client_original_supported_versions,
+      std::string* error_details);
+
   // Returns the name of the HandshakeFailureReason as a char*
   static const char* HandshakeFailureReasonToString(
       HandshakeFailureReason reason);
diff --git a/quic/core/crypto/crypto_utils_test.cc b/quic/core/crypto/crypto_utils_test.cc
index 6c3d384..251f136 100644
--- a/quic/core/crypto/crypto_utils_test.cc
+++ b/quic/core/crypto/crypto_utils_test.cc
@@ -108,6 +108,63 @@
   }
 }
 
+TEST_F(CryptoUtilsTest, ValidateChosenVersion) {
+  for (const ParsedQuicVersion& v1 : AllSupportedVersions()) {
+    for (const ParsedQuicVersion& v2 : AllSupportedVersions()) {
+      std::string error_details;
+      bool success = CryptoUtils::ValidateChosenVersion(
+          CreateQuicVersionLabel(v1), v2, &error_details);
+      EXPECT_EQ(success, v1 == v2);
+      EXPECT_EQ(success, error_details.empty());
+    }
+  }
+}
+
+TEST_F(CryptoUtilsTest, ValidateServerVersionsNoVersionNegotiation) {
+  QuicVersionLabelVector version_information_other_versions;
+  ParsedQuicVersionVector client_original_supported_versions;
+  for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+    std::string error_details;
+    EXPECT_TRUE(CryptoUtils::ValidateServerVersions(
+        version_information_other_versions, version,
+        client_original_supported_versions, &error_details));
+    EXPECT_TRUE(error_details.empty());
+  }
+}
+
+TEST_F(CryptoUtilsTest, ValidateServerVersionsWithVersionNegotiation) {
+  for (const ParsedQuicVersion& version : AllSupportedVersions()) {
+    QuicVersionLabelVector version_information_other_versions{
+        CreateQuicVersionLabel(version)};
+    ParsedQuicVersionVector client_original_supported_versions{
+        ParsedQuicVersion::ReservedForNegotiation(), version};
+    std::string error_details;
+    EXPECT_TRUE(CryptoUtils::ValidateServerVersions(
+        version_information_other_versions, version,
+        client_original_supported_versions, &error_details));
+    EXPECT_TRUE(error_details.empty());
+  }
+}
+
+TEST_F(CryptoUtilsTest, ValidateServerVersionsWithDowngrade) {
+  if (AllSupportedVersions().size() <= 1) {
+    // We are not vulnerable to downgrade if we only support one version.
+    return;
+  }
+  ParsedQuicVersion client_version = AllSupportedVersions().front();
+  ParsedQuicVersion server_version = AllSupportedVersions().back();
+  ASSERT_NE(client_version, server_version);
+  QuicVersionLabelVector version_information_other_versions{
+      CreateQuicVersionLabel(client_version)};
+  ParsedQuicVersionVector client_original_supported_versions{
+      ParsedQuicVersion::ReservedForNegotiation(), server_version};
+  std::string error_details;
+  EXPECT_FALSE(CryptoUtils::ValidateServerVersions(
+      version_information_other_versions, server_version,
+      client_original_supported_versions, &error_details));
+  EXPECT_FALSE(error_details.empty());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc
index fec6cc5..8449372 100644
--- a/quic/core/crypto/transport_parameters.cc
+++ b/quic/core/crypto/transport_parameters.cc
@@ -62,7 +62,8 @@
   kGoogleQuicVersion =
       0x4752,  // Used to transmit version and supported_versions.
 
-  kMinAckDelay = 0xDE1A,  // draft-iyengar-quic-delayed-ack.
+  kMinAckDelay = 0xDE1A,           // draft-iyengar-quic-delayed-ack.
+  kVersionInformation = 0xFF73DB,  // draft-ietf-quic-version-negotiation.
 };
 
 namespace {
@@ -129,6 +130,8 @@
       return "google-version";
     case TransportParameters::kMinAckDelay:
       return "min_ack_delay_us";
+    case TransportParameters::kVersionInformation:
+      return "version_information";
   }
   return absl::StrCat("Unknown(", param_id, ")");
 }
@@ -158,6 +161,7 @@
     case TransportParameters::kGoogleConnectionOptions:
     case TransportParameters::kGoogleQuicVersion:
     case TransportParameters::kMinAckDelay:
+    case TransportParameters::kVersionInformation:
       return true;
     case TransportParameters::kGoogleUserAgentId:
       return !GetQuicReloadableFlag(quic_ignore_user_agent_transport_parameter);
@@ -309,6 +313,70 @@
          "]";
 }
 
+TransportParameters::LegacyVersionInformation::LegacyVersionInformation()
+    : version(0) {}
+
+bool TransportParameters::LegacyVersionInformation::operator==(
+    const LegacyVersionInformation& rhs) const {
+  return version == rhs.version && supported_versions == rhs.supported_versions;
+}
+
+bool TransportParameters::LegacyVersionInformation::operator!=(
+    const LegacyVersionInformation& rhs) const {
+  return !(*this == rhs);
+}
+
+std::string TransportParameters::LegacyVersionInformation::ToString() const {
+  std::string rv =
+      absl::StrCat("legacy[version ", QuicVersionLabelToString(version));
+  if (!supported_versions.empty()) {
+    absl::StrAppend(&rv,
+                    " supported_versions " +
+                        QuicVersionLabelVectorToString(supported_versions));
+  }
+  absl::StrAppend(&rv, "]");
+  return rv;
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         const TransportParameters::LegacyVersionInformation&
+                             legacy_version_information) {
+  os << legacy_version_information.ToString();
+  return os;
+}
+
+TransportParameters::VersionInformation::VersionInformation()
+    : chosen_version(0) {}
+
+bool TransportParameters::VersionInformation::operator==(
+    const VersionInformation& rhs) const {
+  return chosen_version == rhs.chosen_version &&
+         other_versions == rhs.other_versions;
+}
+
+bool TransportParameters::VersionInformation::operator!=(
+    const VersionInformation& rhs) const {
+  return !(*this == rhs);
+}
+
+std::string TransportParameters::VersionInformation::ToString() const {
+  std::string rv = absl::StrCat("[chosen_version ",
+                                QuicVersionLabelToString(chosen_version));
+  if (!other_versions.empty()) {
+    absl::StrAppend(&rv, " other_versions " +
+                             QuicVersionLabelVectorToString(other_versions));
+  }
+  absl::StrAppend(&rv, "]");
+  return rv;
+}
+
+std::ostream& operator<<(
+    std::ostream& os,
+    const TransportParameters::VersionInformation& version_information) {
+  os << version_information.ToString();
+  return os;
+}
+
 std::ostream& operator<<(std::ostream& os, const TransportParameters& params) {
   os << params.ToString();
   return os;
@@ -321,12 +389,11 @@
   } else {
     rv += "Client";
   }
-  if (version != 0) {
-    rv += " version " + QuicVersionLabelToString(version);
+  if (legacy_version_information.has_value()) {
+    rv += " " + legacy_version_information.value().ToString();
   }
-  if (!supported_versions.empty()) {
-    rv += " supported_versions " +
-          QuicVersionLabelVectorToString(supported_versions);
+  if (version_information.has_value()) {
+    rv += " " + version_information.value().ToString();
   }
   if (original_destination_connection_id.has_value()) {
     rv += " " + TransportParameterIdToString(kOriginalDestinationConnectionId) +
@@ -403,12 +470,9 @@
 }
 
 TransportParameters::TransportParameters()
-    : version(0),
-      max_idle_timeout_ms(kMaxIdleTimeout),
-      max_udp_payload_size(kMaxPacketSize,
-                           kDefaultMaxPacketSizeTransportParam,
-                           kMinMaxPacketSizeTransportParam,
-                           kVarInt62MaxValue),
+    : max_idle_timeout_ms(kMaxIdleTimeout),
+      max_udp_payload_size(kMaxPacketSize, kDefaultMaxPacketSizeTransportParam,
+                           kMinMaxPacketSizeTransportParam, kVarInt62MaxValue),
       initial_max_data(kInitialMaxData),
       initial_max_stream_data_bidi_local(kInitialMaxStreamDataBidiLocal),
       initial_max_stream_data_bidi_remote(kInitialMaxStreamDataBidiRemote),
@@ -416,16 +480,11 @@
       initial_max_streams_bidi(kInitialMaxStreamsBidi),
       initial_max_streams_uni(kInitialMaxStreamsUni),
       ack_delay_exponent(kAckDelayExponent,
-                         kDefaultAckDelayExponentTransportParam,
-                         0,
+                         kDefaultAckDelayExponentTransportParam, 0,
                          kMaxAckDelayExponentTransportParam),
-      max_ack_delay(kMaxAckDelay,
-                    kDefaultMaxAckDelayTransportParam,
-                    0,
+      max_ack_delay(kMaxAckDelay, kDefaultMaxAckDelayTransportParam, 0,
                     kMaxMaxAckDelayTransportParam),
-      min_ack_delay_us(kMinAckDelay,
-                       0,
-                       0,
+      min_ack_delay_us(kMinAckDelay, 0, 0,
                        kMaxMaxAckDelayTransportParam * kNumMicrosPerMilli),
       disable_active_migration(false),
       active_connection_id_limit(kActiveConnectionIdLimit,
@@ -443,8 +502,8 @@
 
 TransportParameters::TransportParameters(const TransportParameters& other)
     : perspective(other.perspective),
-      version(other.version),
-      supported_versions(other.supported_versions),
+      legacy_version_information(other.legacy_version_information),
+      version_information(other.version_information),
       original_destination_connection_id(
           other.original_destination_connection_id),
       max_idle_timeout_ms(other.max_idle_timeout_ms),
@@ -478,8 +537,9 @@
 }
 
 bool TransportParameters::operator==(const TransportParameters& rhs) const {
-  if (!(perspective == rhs.perspective && version == rhs.version &&
-        supported_versions == rhs.supported_versions &&
+  if (!(perspective == rhs.perspective &&
+        legacy_version_information == rhs.legacy_version_information &&
+        version_information == rhs.version_information &&
         original_destination_connection_id ==
             rhs.original_destination_connection_id &&
         max_idle_timeout_ms.value() == rhs.max_idle_timeout_ms.value() &&
@@ -589,6 +649,22 @@
     *error_details = "Server cannot send user agent ID";
     return false;
   }
+  if (version_information.has_value()) {
+    const QuicVersionLabel& chosen_version =
+        version_information.value().chosen_version;
+    const QuicVersionLabelVector& other_versions =
+        version_information.value().other_versions;
+    if (chosen_version == 0) {
+      *error_details = "Invalid chosen version";
+      return false;
+    }
+    if (perspective == Perspective::IS_CLIENT &&
+        std::find(other_versions.begin(), other_versions.end(),
+                  chosen_version) == other_versions.end()) {
+      *error_details = "Client chosen version not in other versions";
+      return false;
+    }
+  }
   const bool ok =
       max_idle_timeout_ms.IsValid() && max_udp_payload_size.IsValid() &&
       initial_max_data.IsValid() &&
@@ -616,8 +692,10 @@
         << "Not serializing invalid transport parameters: " << error_details;
     return false;
   }
-  if (in.version == 0 || (in.perspective == Perspective::IS_SERVER &&
-                          in.supported_versions.empty())) {
+  if (!in.legacy_version_information.has_value() ||
+      in.legacy_version_information.value().version == 0 ||
+      (in.perspective == Perspective::IS_SERVER &&
+       in.legacy_version_information.value().supported_versions.empty())) {
     QUIC_BUG(missing versions) << "Refusing to serialize without versions";
     return false;
   }
@@ -701,6 +779,7 @@
       TransportParameters::kGoogleUserAgentId,
       TransportParameters::kGoogleKeyUpdateNotYetSupported,
       TransportParameters::kGoogleQuicVersion,
+      TransportParameters::kVersionInformation,
   };
 
   size_t max_transport_param_length = kKnownTransportParamLength;
@@ -714,9 +793,21 @@
     max_transport_param_length += in.user_agent_id.value().length();
   }
   // Google-specific version extension.
-  max_transport_param_length +=
-      sizeof(in.version) + 1 /* versions length */ +
-      in.supported_versions.size() * sizeof(QuicVersionLabel);
+  if (in.legacy_version_information.has_value()) {
+    max_transport_param_length +=
+        sizeof(in.legacy_version_information.value().version) +
+        1 /* versions length */ +
+        in.legacy_version_information.value().supported_versions.size() *
+            sizeof(QuicVersionLabel);
+  }
+  // version_information.
+  if (in.version_information.has_value()) {
+    max_transport_param_length +=
+        sizeof(in.version_information.value().chosen_version) +
+        // Add one for the added GREASE version.
+        (in.version_information.value().other_versions.size() + 1) *
+            sizeof(QuicVersionLabel);
+  }
 
   // Add a random GREASE transport parameter, as defined in the
   // "Reserved Transport Parameters" section of RFC 9000.
@@ -1051,30 +1142,38 @@
       } break;
       // Google-specific version extension.
       case TransportParameters::kGoogleQuicVersion: {
+        if (!in.legacy_version_information.has_value()) {
+          break;
+        }
         static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t),
                       "bad length");
-        uint64_t google_version_length = sizeof(in.version);
+        uint64_t google_version_length =
+            sizeof(in.legacy_version_information.value().version);
         if (in.perspective == Perspective::IS_SERVER) {
           google_version_length +=
               /* versions length */ sizeof(uint8_t) +
-              sizeof(QuicVersionLabel) * in.supported_versions.size();
+              sizeof(QuicVersionLabel) * in.legacy_version_information.value()
+                                             .supported_versions.size();
         }
         if (!writer.WriteVarInt62(TransportParameters::kGoogleQuicVersion) ||
             !writer.WriteVarInt62(
                 /* transport parameter length */ google_version_length) ||
-            !writer.WriteUInt32(in.version)) {
+            !writer.WriteUInt32(
+                in.legacy_version_information.value().version)) {
           QUIC_BUG(Failed to write Google version extension)
               << "Failed to write Google version extension for " << in;
           return false;
         }
         if (in.perspective == Perspective::IS_SERVER) {
           if (!writer.WriteUInt8(sizeof(QuicVersionLabel) *
-                                 in.supported_versions.size())) {
+                                 in.legacy_version_information.value()
+                                     .supported_versions.size())) {
             QUIC_BUG(Failed to write versions length)
                 << "Failed to write versions length for " << in;
             return false;
           }
-          for (QuicVersionLabel version_label : in.supported_versions) {
+          for (QuicVersionLabel version_label :
+               in.legacy_version_information.value().supported_versions) {
             if (!writer.WriteUInt32(version_label)) {
               QUIC_BUG(Failed to write supported version)
                   << "Failed to write supported version for " << in;
@@ -1083,6 +1182,41 @@
           }
         }
       } break;
+      // version_information.
+      case TransportParameters::kVersionInformation: {
+        if (!in.version_information.has_value()) {
+          break;
+        }
+        static_assert(sizeof(QuicVersionLabel) == sizeof(uint32_t),
+                      "bad length");
+        QuicVersionLabelVector other_versions =
+            in.version_information.value().other_versions;
+        // Insert one GREASE version at a random index.
+        const size_t grease_index =
+            random->InsecureRandUint64() % (other_versions.size() + 1);
+        other_versions.insert(
+            other_versions.begin() + grease_index,
+            CreateQuicVersionLabel(QuicVersionReservedForNegotiation()));
+        const uint64_t version_information_length =
+            sizeof(in.version_information.value().chosen_version) +
+            sizeof(QuicVersionLabel) * other_versions.size();
+        if (!writer.WriteVarInt62(TransportParameters::kVersionInformation) ||
+            !writer.WriteVarInt62(
+                /* transport parameter length */ version_information_length) ||
+            !writer.WriteUInt32(
+                in.version_information.value().chosen_version)) {
+          QUIC_BUG(Failed to write chosen version)
+              << "Failed to write chosen version for " << in;
+          return false;
+        }
+        for (QuicVersionLabel version_label : other_versions) {
+          if (!writer.WriteUInt32(version_label)) {
+            QUIC_BUG(Failed to write other version)
+                << "Failed to write other version for " << in;
+            return false;
+          }
+        }
+      } break;
       // Custom parameters and GREASE.
       default: {
         auto it = custom_parameters.find(parameter_id);
@@ -1377,7 +1511,12 @@
         out->key_update_not_yet_supported = true;
         break;
       case TransportParameters::kGoogleQuicVersion: {
-        if (!value_reader.ReadUInt32(&out->version)) {
+        if (!out->legacy_version_information.has_value()) {
+          out->legacy_version_information =
+              TransportParameters::LegacyVersionInformation();
+        }
+        if (!value_reader.ReadUInt32(
+                &out->legacy_version_information.value().version)) {
           *error_details = "Failed to read Google version extension version";
           return false;
         }
@@ -1394,10 +1533,32 @@
               *error_details = "Failed to parse Google supported version";
               return false;
             }
-            out->supported_versions.push_back(version);
+            out->legacy_version_information.value()
+                .supported_versions.push_back(version);
           }
         }
       } break;
+      case TransportParameters::kVersionInformation: {
+        if (out->version_information.has_value()) {
+          *error_details = "Received a second version_information";
+          return false;
+        }
+        out->version_information = TransportParameters::VersionInformation();
+        if (!value_reader.ReadUInt32(
+                &out->version_information.value().chosen_version)) {
+          *error_details = "Failed to read chosen version";
+          return false;
+        }
+        while (!value_reader.IsDoneReading()) {
+          QuicVersionLabel other_version;
+          if (!value_reader.ReadUInt32(&other_version)) {
+            *error_details = "Failed to parse other version";
+            return false;
+          }
+          out->version_information.value().other_versions.push_back(
+              other_version);
+        }
+      } break;
       case TransportParameters::kMinAckDelay:
         parse_success =
             out->min_ack_delay_us.Read(&value_reader, error_details);
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h
index b6ecb69..fa34935 100644
--- a/quic/core/crypto/transport_parameters.h
+++ b/quic/core/crypto/transport_parameters.h
@@ -110,6 +110,64 @@
         const TransportParameters& params);
   };
 
+  // LegacyVersionInformation represents the Google QUIC downgrade prevention
+  // mechanism ported to QUIC+TLS. It is exchanged using transport parameter ID
+  // 0x4752 and will eventually be deprecated in favor of
+  // draft-ietf-quic-version-negotiation.
+  struct QUIC_EXPORT_PRIVATE LegacyVersionInformation {
+    LegacyVersionInformation();
+    LegacyVersionInformation(const LegacyVersionInformation& other) = default;
+    LegacyVersionInformation& operator=(const LegacyVersionInformation& other) =
+        default;
+    LegacyVersionInformation& operator=(LegacyVersionInformation&& other) =
+        default;
+    LegacyVersionInformation(LegacyVersionInformation&& other) = default;
+    ~LegacyVersionInformation() = default;
+    bool operator==(const LegacyVersionInformation& rhs) const;
+    bool operator!=(const LegacyVersionInformation& rhs) const;
+    // When sent by the client, |version| is the initial version offered by the
+    // client (before any version negotiation packets) for this connection. When
+    // sent by the server, |version| is the version that is in use.
+    QuicVersionLabel version;
+
+    // When sent by the server, |supported_versions| contains a list of all
+    // versions that the server would send in a version negotiation packet. When
+    // sent by the client, this is empty.
+    QuicVersionLabelVector supported_versions;
+
+    // Allows easily logging.
+    std::string ToString() const;
+    friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+        std::ostream& os,
+        const LegacyVersionInformation& legacy_version_information);
+  };
+
+  // Version information used for version downgrade prevention and compatible
+  // version negotiation. See draft-ietf-quic-version-negotiation-05.
+  struct QUIC_EXPORT_PRIVATE VersionInformation {
+    VersionInformation();
+    VersionInformation(const VersionInformation& other) = default;
+    VersionInformation& operator=(const VersionInformation& other) = default;
+    VersionInformation& operator=(VersionInformation&& other) = default;
+    VersionInformation(VersionInformation&& other) = default;
+    ~VersionInformation() = default;
+    bool operator==(const VersionInformation& rhs) const;
+    bool operator!=(const VersionInformation& rhs) const;
+
+    // Version that the sender has chosen to use on this connection.
+    QuicVersionLabel chosen_version;
+
+    // When sent by the client, |other_versions| contains all the versions that
+    // this first flight is compatible with. When sent by the server,
+    // |other_versions| contains all of the versions supported by the server.
+    QuicVersionLabelVector other_versions;
+
+    // Allows easily logging.
+    std::string ToString() const;
+    friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
+        std::ostream& os, const VersionInformation& version_information);
+  };
+
   TransportParameters();
   TransportParameters(const TransportParameters& other);
   ~TransportParameters();
@@ -122,15 +180,12 @@
   // the encrypted_extensions handshake message.
   Perspective perspective;
 
-  // When Perspective::IS_CLIENT, |version| is the initial version offered by
-  // the client (before any version negotiation packets) for this connection.
-  // When Perspective::IS_SERVER, |version| is the version that is in use.
-  QuicVersionLabel version;
+  // Google QUIC downgrade prevention mechanism sent over QUIC+TLS.
+  absl::optional<LegacyVersionInformation> legacy_version_information;
 
-  // |supported_versions| contains a list of all versions that the server would
-  // send in a version negotiation packet. It is not used if |perspective ==
-  // Perspective::IS_CLIENT|.
-  QuicVersionLabelVector supported_versions;
+  // IETF downgrade prevention and compatible version negotiation, see
+  // draft-ietf-quic-version-negotiation.
+  absl::optional<VersionInformation> version_information;
 
   // The value of the Destination Connection ID field from the first
   // Initial packet sent by the client.
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc
index 05ada68..c4070dd 100644
--- a/quic/core/crypto/transport_parameters_test.cc
+++ b/quic/core/crypto/transport_parameters_test.cc
@@ -99,6 +99,30 @@
       preferred_address);
 }
 
+TransportParameters::LegacyVersionInformation
+CreateFakeLegacyVersionInformationClient() {
+  TransportParameters::LegacyVersionInformation legacy_version_information;
+  legacy_version_information.version = kFakeVersionLabel;
+  return legacy_version_information;
+}
+
+TransportParameters::LegacyVersionInformation
+CreateFakeLegacyVersionInformationServer() {
+  TransportParameters::LegacyVersionInformation legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
+  legacy_version_information.supported_versions.push_back(kFakeVersionLabel);
+  legacy_version_information.supported_versions.push_back(kFakeVersionLabel2);
+  return legacy_version_information;
+}
+
+TransportParameters::VersionInformation CreateFakeVersionInformation() {
+  TransportParameters::VersionInformation version_information;
+  version_information.chosen_version = kFakeVersionLabel;
+  version_information.other_versions.push_back(kFakeVersionLabel);
+  version_information.other_versions.push_back(kFakeVersionLabel2);
+  return version_information;
+}
+
 QuicTagVector CreateFakeGoogleConnectionOptions() {
   return {kALPN, MakeQuicTag('E', 'F', 'G', 0x00),
           MakeQuicTag('H', 'I', 'J', 0xff)};
@@ -119,6 +143,18 @@
   for (TransportParameters::TransportParameterId param_id : grease_params) {
     params->custom_parameters.erase(param_id);
   }
+  // Remove all GREASE versions from version_information.other_versions.
+  if (params->version_information.has_value()) {
+    QuicVersionLabelVector& other_versions =
+        params->version_information.value().other_versions;
+    for (auto it = other_versions.begin(); it != other_versions.end();) {
+      if ((*it & 0x0f0f0f0f) == 0x0a0a0a0a) {
+        it = other_versions.erase(it);
+      } else {
+        ++it;
+      }
+    }
+  }
 }
 
 }  // namespace
@@ -145,8 +181,12 @@
   EXPECT_FALSE(orig_params == new_params);
   EXPECT_TRUE(orig_params != new_params);
   new_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
-  new_params.version = kFakeVersionLabel;
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
+  new_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
+  orig_params.version_information = CreateFakeVersionInformation();
+  new_params.version_information = CreateFakeVersionInformation();
   orig_params.disable_active_migration = true;
   new_params.disable_active_migration = true;
   EXPECT_EQ(orig_params, new_params);
@@ -154,13 +194,16 @@
   EXPECT_FALSE(orig_params != new_params);
 
   // Test comparison on vectors.
-  orig_params.supported_versions.push_back(kFakeVersionLabel);
-  new_params.supported_versions.push_back(kFakeVersionLabel2);
+  orig_params.legacy_version_information.value().supported_versions.push_back(
+      kFakeVersionLabel);
+  new_params.legacy_version_information.value().supported_versions.push_back(
+      kFakeVersionLabel2);
   EXPECT_NE(orig_params, new_params);
   EXPECT_FALSE(orig_params == new_params);
   EXPECT_TRUE(orig_params != new_params);
-  new_params.supported_versions.pop_back();
-  new_params.supported_versions.push_back(kFakeVersionLabel);
+  new_params.legacy_version_information.value().supported_versions.pop_back();
+  new_params.legacy_version_information.value().supported_versions.push_back(
+      kFakeVersionLabel);
   orig_params.stateless_reset_token = CreateStatelessResetTokenForTest();
   new_params.stateless_reset_token = CreateStatelessResetTokenForTest();
   EXPECT_EQ(orig_params, new_params);
@@ -219,9 +262,9 @@
 TEST_P(TransportParametersTest, CopyConstructor) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
-  orig_params.supported_versions.push_back(kFakeVersionLabel);
-  orig_params.supported_versions.push_back(kFakeVersionLabel2);
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
+  orig_params.version_information = CreateFakeVersionInformation();
   orig_params.original_destination_connection_id =
       CreateFakeOriginalDestinationConnectionId();
   orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
@@ -260,7 +303,9 @@
 TEST_P(TransportParametersTest, RoundTripClient) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
+  orig_params.version_information = CreateFakeVersionInformation();
   orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
   orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest);
   orig_params.initial_max_data.set_value(kFakeInitialMaxData);
@@ -308,9 +353,9 @@
 TEST_P(TransportParametersTest, RoundTripServer) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_SERVER;
-  orig_params.version = kFakeVersionLabel;
-  orig_params.supported_versions.push_back(kFakeVersionLabel);
-  orig_params.supported_versions.push_back(kFakeVersionLabel2);
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationServer();
+  orig_params.version_information = CreateFakeVersionInformation();
   orig_params.original_destination_connection_id =
       CreateFakeOriginalDestinationConnectionId();
   orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
@@ -446,7 +491,8 @@
 TEST_P(TransportParametersTest, NoClientParamsWithStatelessResetToken) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
   orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
   orig_params.stateless_reset_token = CreateStatelessResetTokenForTest();
   orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest);
@@ -539,6 +585,12 @@
       0x80, 0x00, 0x47, 0x52,  // parameter id
       0x04,  // length
       0x01, 0x23, 0x45, 0x67,  // initial version
+      // version_information
+      0x80, 0xFF, 0x73, 0xDB,  // parameter id
+      0x0C,  // length
+      0x01, 0x23, 0x45, 0x67,  // chosen version
+      0x01, 0x23, 0x45, 0x67,  // other version 1
+      0x89, 0xab, 0xcd, 0xef,  // other version 2
   };
   // clang-format on
   const uint8_t* client_params =
@@ -552,8 +604,14 @@
       << error_details;
   EXPECT_TRUE(error_details.empty());
   EXPECT_EQ(Perspective::IS_CLIENT, new_params.perspective);
-  EXPECT_EQ(kFakeVersionLabel, new_params.version);
-  EXPECT_TRUE(new_params.supported_versions.empty());
+  ASSERT_TRUE(new_params.legacy_version_information.has_value());
+  EXPECT_EQ(kFakeVersionLabel,
+            new_params.legacy_version_information.value().version);
+  EXPECT_TRUE(
+      new_params.legacy_version_information.value().supported_versions.empty());
+  ASSERT_TRUE(new_params.version_information.has_value());
+  EXPECT_EQ(new_params.version_information.value(),
+            CreateFakeVersionInformation());
   EXPECT_FALSE(new_params.original_destination_connection_id.has_value());
   EXPECT_EQ(kFakeIdleTimeoutMilliseconds,
             new_params.max_idle_timeout_ms.value());
@@ -790,6 +848,12 @@
       0x08,  // length of supported versions array
       0x01, 0x23, 0x45, 0x67,
       0x89, 0xab, 0xcd, 0xef,
+      // version_information
+      0x80, 0xFF, 0x73, 0xDB,  // parameter id
+      0x0C,  // length
+      0x01, 0x23, 0x45, 0x67,  // chosen version
+      0x01, 0x23, 0x45, 0x67,  // other version 1
+      0x89, 0xab, 0xcd, 0xef,  // other version 2
   };
   // clang-format on
   const uint8_t* server_params =
@@ -803,10 +867,21 @@
       << error_details;
   EXPECT_TRUE(error_details.empty());
   EXPECT_EQ(Perspective::IS_SERVER, new_params.perspective);
-  EXPECT_EQ(kFakeVersionLabel, new_params.version);
-  EXPECT_EQ(2u, new_params.supported_versions.size());
-  EXPECT_EQ(kFakeVersionLabel, new_params.supported_versions[0]);
-  EXPECT_EQ(kFakeVersionLabel2, new_params.supported_versions[1]);
+  ASSERT_TRUE(new_params.legacy_version_information.has_value());
+  EXPECT_EQ(kFakeVersionLabel,
+            new_params.legacy_version_information.value().version);
+  ASSERT_EQ(
+      2u,
+      new_params.legacy_version_information.value().supported_versions.size());
+  EXPECT_EQ(
+      kFakeVersionLabel,
+      new_params.legacy_version_information.value().supported_versions[0]);
+  EXPECT_EQ(
+      kFakeVersionLabel2,
+      new_params.legacy_version_information.value().supported_versions[1]);
+  ASSERT_TRUE(new_params.version_information.has_value());
+  EXPECT_EQ(new_params.version_information.value(),
+            CreateFakeVersionInformation());
   ASSERT_TRUE(new_params.original_destination_connection_id.has_value());
   EXPECT_EQ(CreateFakeOriginalDestinationConnectionId(),
             new_params.original_destination_connection_id.value());
@@ -927,7 +1002,8 @@
   std::string custom_value(70000, '?');
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
   orig_params.custom_parameters[kCustomParameter1] = custom_value;
 
   std::vector<uint8_t> serialized;
@@ -947,7 +1023,8 @@
 TEST_P(TransportParametersTest, SerializationOrderIsRandom) {
   TransportParameters orig_params;
   orig_params.perspective = Perspective::IS_CLIENT;
-  orig_params.version = kFakeVersionLabel;
+  orig_params.legacy_version_information =
+      CreateFakeLegacyVersionInformationClient();
   orig_params.max_idle_timeout_ms.set_value(kFakeIdleTimeoutMilliseconds);
   orig_params.max_udp_payload_size.set_value(kMaxPacketSizeForTest);
   orig_params.initial_max_data.set_value(kFakeInitialMaxData);
@@ -994,9 +1071,8 @@
  protected:
   void SetUp() override {
     original_params_.perspective = Perspective::IS_SERVER;
-    original_params_.version = kFakeVersionLabel;
-    original_params_.supported_versions.push_back(kFakeVersionLabel);
-    original_params_.supported_versions.push_back(kFakeVersionLabel2);
+    original_params_.legacy_version_information =
+        CreateFakeLegacyVersionInformationServer();
     original_params_.original_destination_connection_id =
         CreateFakeOriginalDestinationConnectionId();
     original_params_.max_idle_timeout_ms.set_value(
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index d625fd0..0b5e591 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -133,6 +133,8 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_key_update_not_yet_supported, true)
 // When true, QUIC server will ignore received user agent transport parameter and rely on getting that information from HTTP headers.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_ignore_user_agent_transport_parameter, true)
+// When true, QUIC will both send and validate the version_information transport parameter.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_version_information, false)
 // When true, QuicDispatcher will silently drop incoming packets whose UDP source port is on the blocklist.
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_blocked_ports, true)
 // When true, defaults to BBR congestion control instead of Cubic.
diff --git a/quic/core/quic_session.h b/quic/core/quic_session.h
index cb06d87..db898d2 100644
--- a/quic/core/quic_session.h
+++ b/quic/core/quic_session.h
@@ -630,6 +630,16 @@
   // Try converting all pending streams to normal streams.
   void ProcessAllPendingStreams();
 
+  const ParsedQuicVersionVector& client_original_supported_versions() const {
+    QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+    return client_original_supported_versions_;
+  }
+  void set_client_original_supported_versions(
+      const ParsedQuicVersionVector& client_original_supported_versions) {
+    QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+    client_original_supported_versions_ = client_original_supported_versions;
+  }
+
  protected:
   using StreamMap =
       absl::flat_hash_map<QuicStreamId, std::unique_ptr<QuicStream>>;
@@ -983,6 +993,11 @@
   // list may be a superset of the connection framer's supported versions.
   ParsedQuicVersionVector supported_versions_;
 
+  // Only non-empty on the client after receiving a version negotiation packet,
+  // contains the configured versions from the original session before version
+  // negotiation was received.
+  ParsedQuicVersionVector client_original_supported_versions_;
+
   absl::optional<std::string> user_agent_id_;
 
   // Initialized to false. Set to true when the session has been properly
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 717932f..ca2713f 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -611,6 +611,7 @@
 
 void QuicVersionInitializeSupportForIetfDraft() {
   // Enable necessary flags.
+  SetQuicReloadableFlag(quic_version_information, true);
 }
 
 void QuicEnableVersion(const ParsedQuicVersion& version) {
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc
index 2717db0..7f8fca6 100644
--- a/quic/core/tls_client_handshaker.cc
+++ b/quic/core/tls_client_handshaker.cc
@@ -221,8 +221,14 @@
 bool TlsClientHandshaker::SetTransportParameters() {
   TransportParameters params;
   params.perspective = Perspective::IS_CLIENT;
-  params.version =
+  params.legacy_version_information =
+      TransportParameters::LegacyVersionInformation();
+  params.legacy_version_information.value().version =
       CreateQuicVersionLabel(session()->supported_versions().front());
+  params.version_information = TransportParameters::VersionInformation();
+  const QuicVersionLabel version = CreateQuicVersionLabel(session()->version());
+  params.version_information.value().chosen_version = version;
+  params.version_information.value().other_versions.push_back(version);
 
   if (!handshaker_delegate()->FillTransportParameters(&params)) {
     return false;
@@ -266,27 +272,41 @@
   session()->connection()->OnTransportParametersReceived(
       *received_transport_params_);
 
-  // When interoperating with non-Google implementations that do not send
-  // the version extension, set it to what we expect.
-  if (received_transport_params_->version == 0) {
-    received_transport_params_->version =
-        CreateQuicVersionLabel(session()->connection()->version());
+  if (received_transport_params_->legacy_version_information.has_value()) {
+    if (received_transport_params_->legacy_version_information.value()
+            .version !=
+        CreateQuicVersionLabel(session()->connection()->version())) {
+      *error_details = "Version mismatch detected";
+      return false;
+    }
+    if (CryptoUtils::ValidateServerHelloVersions(
+            received_transport_params_->legacy_version_information.value()
+                .supported_versions,
+            session()->connection()->server_supported_versions(),
+            error_details) != QUIC_NO_ERROR) {
+      QUICHE_DCHECK(!error_details->empty());
+      return false;
+    }
   }
-  if (received_transport_params_->supported_versions.empty()) {
-    received_transport_params_->supported_versions.push_back(
-        received_transport_params_->version);
+  if (received_transport_params_->version_information.has_value()) {
+    if (!CryptoUtils::ValidateChosenVersion(
+            received_transport_params_->version_information.value()
+                .chosen_version,
+            session()->version(), error_details)) {
+      QUICHE_DCHECK(!error_details->empty());
+      return false;
+    }
+    if (!CryptoUtils::CryptoUtils::ValidateServerVersions(
+            received_transport_params_->version_information.value()
+                .other_versions,
+            session()->version(),
+            session()->client_original_supported_versions(), error_details)) {
+      QUICHE_DCHECK(!error_details->empty());
+      return false;
+    }
   }
 
-  if (received_transport_params_->version !=
-      CreateQuicVersionLabel(session()->connection()->version())) {
-    *error_details = "Version mismatch detected";
-    return false;
-  }
-  if (CryptoUtils::ValidateServerHelloVersions(
-          received_transport_params_->supported_versions,
-          session()->connection()->server_supported_versions(),
-          error_details) != QUIC_NO_ERROR ||
-      handshaker_delegate()->ProcessTransportParameters(
+  if (handshaker_delegate()->ProcessTransportParameters(
           *received_transport_params_, /* is_resumption = */ false,
           error_details) != QUIC_NO_ERROR) {
     QUICHE_DCHECK(!error_details->empty());
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 49bd5a0..c6b3d7d 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -500,21 +500,31 @@
     }
   }
 
-  // When interoperating with non-Google implementations that do not send
-  // the version extension, set it to what we expect.
-  if (client_params.version == 0) {
-    client_params.version =
-        CreateQuicVersionLabel(session()->connection()->version());
-  }
-
-  if (CryptoUtils::ValidateClientHelloVersion(
-          client_params.version, session()->connection()->version(),
-          session()->supported_versions(), error_details) != QUIC_NO_ERROR ||
-      handshaker_delegate()->ProcessTransportParameters(
-          client_params, /* is_resumption = */ false, error_details) !=
-          QUIC_NO_ERROR) {
+  if (client_params.legacy_version_information.has_value() &&
+      CryptoUtils::ValidateClientHelloVersion(
+          client_params.legacy_version_information.value().version,
+          session()->connection()->version(), session()->supported_versions(),
+          error_details) != QUIC_NO_ERROR) {
     return false;
   }
+
+  if (GetQuicReloadableFlag(quic_version_information) &&
+      client_params.version_information.has_value()) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_information, 2, 2);
+    if (!CryptoUtils::ValidateChosenVersion(
+            client_params.version_information.value().chosen_version,
+            session()->version(), error_details)) {
+      QUICHE_DCHECK(!error_details->empty());
+      return false;
+    }
+  }
+
+  if (handshaker_delegate()->ProcessTransportParameters(
+          client_params, /* is_resumption = */ false, error_details) !=
+      QUIC_NO_ERROR) {
+    return false;
+  }
+
   ProcessAdditionalTransportParameters(client_params);
   if (!session()->user_agent_id().has_value() &&
       client_params.user_agent_id.has_value()) {
@@ -531,10 +541,21 @@
 
   TransportParameters server_params;
   server_params.perspective = Perspective::IS_SERVER;
-  server_params.supported_versions =
+  server_params.legacy_version_information =
+      TransportParameters::LegacyVersionInformation();
+  server_params.legacy_version_information.value().supported_versions =
       CreateQuicVersionLabelVector(session()->supported_versions());
-  server_params.version =
+  server_params.legacy_version_information.value().version =
       CreateQuicVersionLabel(session()->connection()->version());
+  if (GetQuicReloadableFlag(quic_version_information)) {
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_information, 1, 2);
+    server_params.version_information =
+        TransportParameters::VersionInformation();
+    server_params.version_information.value().chosen_version =
+        CreateQuicVersionLabel(session()->version());
+    server_params.version_information.value().other_versions =
+        CreateQuicVersionLabelVector(session()->supported_versions());
+  }
 
   if (!handshaker_delegate()->FillTransportParameters(&server_params)) {
     return result;
diff --git a/quic/tools/quic_client_base.cc b/quic/tools/quic_client_base.cc
index 6d10de2..e11e25b 100644
--- a/quic/tools/quic_client_base.cc
+++ b/quic/tools/quic_client_base.cc
@@ -185,6 +185,9 @@
                          server_address(), helper(), alarm_factory(), writer,
                          /* owns_writer= */ false, Perspective::IS_CLIENT,
                          client_supported_versions));
+  if (can_reconnect_with_different_version) {
+    session()->set_client_original_supported_versions(supported_versions());
+  }
   if (connection_debug_visitor_ != nullptr) {
     session()->connection()->set_debug_visitor(connection_debug_visitor_);
   }