Apply GREASE to QUIC Version Negotiation packets

This prevents client implementations from breaking when they observe unknown versions.

This also ensures that the produced packet will always be long enough to allow QUIC proxy munging.

gfe-relnote: minor change to packet format of Version Negotiation packets, protected by --gfe2_reloadable_flag_quic_disable_version_negotiation_grease_randomness
PiperOrigin-RevId: 255015606
Change-Id: I8070d543a765e914a2cdc07fd941fa5e1b359c14
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index 8aaec09..0789567 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -670,7 +670,7 @@
   termination_packets.push_back(QuicFramer::BuildVersionNegotiationPacket(
       server_connection_id, EmptyQuicConnectionId(),
       /*ietf_quic=*/format != GOOGLE_QUIC_PACKET,
-      ParsedQuicVersionVector{QuicVersionReservedForNegotiation()}));
+      /*versions=*/{}));
   time_wait_list_manager()->AddConnectionIdToTimeWait(
       server_connection_id, /*ietf_quic=*/format != GOOGLE_QUIC_PACKET,
       QuicTimeWaitListManager::SEND_TERMINATION_PACKETS, ENCRYPTION_INITIAL,
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 5b061d7..6a6046c 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -1392,17 +1392,46 @@
     QuicConnectionId client_connection_id,
     bool ietf_quic,
     const ParsedQuicVersionVector& versions) {
+  ParsedQuicVersionVector wire_versions = versions;
+  if (!GetQuicReloadableFlag(quic_version_negotiation_grease)) {
+    if (wire_versions.empty()) {
+      wire_versions = {QuicVersionReservedForNegotiation()};
+    }
+  } else {
+    // Add a version reserved for negotiation as suggested by the
+    // "Using Reserved Versions" section of draft-ietf-quic-transport.
+    QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_negotiation_grease, 1, 2);
+    if (wire_versions.empty()) {
+      // Ensure that version negotiation packets we send have at least two
+      // versions. This guarantees that, under all circumstances, all QUIC
+      // packets we send are at least 14 bytes long.
+      wire_versions = {QuicVersionReservedForNegotiation(),
+                       QuicVersionReservedForNegotiation()};
+    } else {
+      // This is not uniformely distributed but is acceptable since no security
+      // depends on this randomness.
+      size_t version_index = 0;
+      const bool disable_randomness =
+          GetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness);
+      if (!disable_randomness) {
+        version_index = QuicRandom::GetInstance()->RandUint64() %
+                        (wire_versions.size() + 1);
+      }
+      wire_versions.insert(wire_versions.begin() + version_index,
+                           QuicVersionReservedForNegotiation());
+    }
+  }
   if (ietf_quic) {
-    return BuildIetfVersionNegotiationPacket(server_connection_id,
-                                             client_connection_id, versions);
+    return BuildIetfVersionNegotiationPacket(
+        server_connection_id, client_connection_id, wire_versions);
   }
 
   // The GQUIC encoding does not support encoding client connection IDs.
   DCHECK(client_connection_id.IsEmpty());
 
-  DCHECK(!versions.empty());
+  DCHECK(!wire_versions.empty());
   size_t len = kPublicFlagsSize + server_connection_id.length() +
-               versions.size() * kQuicVersionSize;
+               wire_versions.size() * kQuicVersionSize;
   std::unique_ptr<char[]> buffer(new char[len]);
   QuicDataWriter writer(len, buffer.get());
 
@@ -1418,7 +1447,7 @@
     return nullptr;
   }
 
-  for (const ParsedQuicVersion& version : versions) {
+  for (const ParsedQuicVersion& version : wire_versions) {
     if (!writer.WriteUInt32(CreateQuicVersionLabel(version))) {
       return nullptr;
     }
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 26e756f..486023a 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -6705,13 +6705,16 @@
 }
 
 TEST_P(QuicFramerTest, BuildVersionNegotiationPacket) {
+  SetQuicReloadableFlag(quic_version_negotiation_grease, true);
+  SetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness, true);
   // clang-format off
   unsigned char packet[] = {
       // public flags (version, 8 byte connection_id)
       0x0D,
       // connection_id
       0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-      // version tag
+      // supported versions
+      0xDA, 0x5A, 0x3A, 0x3A,
       QUIC_VERSION_BYTES,
   };
   unsigned char packet44[] = {
@@ -6723,7 +6726,8 @@
       0x05,
       // connection_id
       0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-      // version tag
+      // supported versions
+      0xDA, 0x5A, 0x3A, 0x3A,
       QUIC_VERSION_BYTES,
   };
   // clang-format on
@@ -6752,6 +6756,8 @@
 
   // Client connection IDs cannot be used unless this flag is true.
   SetQuicRestartFlag(quic_do_not_override_connection_id, true);
+  SetQuicReloadableFlag(quic_version_negotiation_grease, true);
+  SetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness, true);
 
   // clang-format off
   unsigned char packet[] = {
@@ -6765,7 +6771,8 @@
       0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11,
       // server/source connection ID
       0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
-      // version tag
+      // supported versions
+      0xDA, 0x5A, 0x3A, 0x3A,
       QUIC_VERSION_BYTES,
   };
   // clang-format on
diff --git a/quic/core/quic_versions.cc b/quic/core/quic_versions.cc
index 7e828a7..afaae66 100644
--- a/quic/core/quic_versions.cc
+++ b/quic/core/quic_versions.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
 #include "net/third_party/quiche/src/quic/core/quic_tag.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
@@ -25,6 +26,22 @@
   return MakeQuicTag(d, c, b, a);
 }
 
+QuicVersionLabel CreateRandomVersionLabelForNegotiation() {
+  if (!GetQuicReloadableFlag(quic_version_negotiation_grease)) {
+    return MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a);
+  }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_version_negotiation_grease, 2, 2);
+  QuicVersionLabel result;
+  if (!GetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness)) {
+    QuicRandom::GetInstance()->RandBytes(&result, sizeof(result));
+  } else {
+    result = MakeVersionLabel(0xd1, 0x57, 0x38, 0x3f);
+  }
+  result &= 0xf0f0f0f0;
+  result |= 0x0a0a0a0a;
+  return result;
+}
+
 }  // namespace
 
 ParsedQuicVersion::ParsedQuicVersion(HandshakeProtocol handshake_protocol,
@@ -112,7 +129,7 @@
       }
       return MakeVersionLabel(proto, '0', '9', '9');
     case QUIC_VERSION_RESERVED_FOR_NEGOTIATION:
-      return MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a);
+      return CreateRandomVersionLabelForNegotiation();
     default:
       // This is a bug because we should never attempt to convert an invalid
       // QuicTransportVersion to be written to the wire.
diff --git a/quic/core/quic_versions.h b/quic/core/quic_versions.h
index 678e067..c8af447 100644
--- a/quic/core/quic_versions.h
+++ b/quic/core/quic_versions.h
@@ -106,10 +106,12 @@
   QUIC_VERSION_47 = 47,  // Allow variable-length QUIC connection IDs.
   QUIC_VERSION_99 = 99,  // Dumping ground for IETF QUIC changes which are not
                          // yet ready for production.
-  // QUIC_VERSION_RESERVED_FOR_NEGOTIATION is sent over the wire as da5a3a3a
+  // QUIC_VERSION_RESERVED_FOR_NEGOTIATION is sent over the wire as ?a?a?a?a
   // which is part of a range reserved by the IETF for version negotiation
-  // testing. It is intentionally meant to never be supported by servers to
-  // trigger version negotiation when proposed by clients.
+  // testing (see the "Versions" section of draft-ietf-quic-transport).
+  // This version is intentionally meant to never be supported to trigger
+  // version negotiation when proposed by clients and to prevent client
+  // ossification when sent by servers.
   QUIC_VERSION_RESERVED_FOR_NEGOTIATION = 999,
 };
 
diff --git a/quic/core/quic_versions_test.cc b/quic/core/quic_versions_test.cc
index 8d3d7dd..0828860 100644
--- a/quic/core/quic_versions_test.cc
+++ b/quic/core/quic_versions_test.cc
@@ -199,6 +199,18 @@
   EXPECT_EQ(MakeVersionLabel('T', '0', '4', '7'),
             CreateQuicVersionLabel(
                 ParsedQuicVersion(PROTOCOL_TLS1_3, QUIC_VERSION_47)));
+
+  // Make sure the negotiation reserved version is in the IETF reserved space.
+  EXPECT_EQ(MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a) & 0x0f0f0f0f,
+            CreateQuicVersionLabel(ParsedQuicVersion(
+                PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_RESERVED_FOR_NEGOTIATION)) &
+                0x0f0f0f0f);
+
+  // Make sure that disabling randomness works.
+  SetQuicFlag(FLAGS_quic_disable_version_negotiation_grease_randomness, true);
+  EXPECT_EQ(MakeVersionLabel(0xda, 0x5a, 0x3a, 0x3a),
+            CreateQuicVersionLabel(ParsedQuicVersion(
+                PROTOCOL_QUIC_CRYPTO, QUIC_VERSION_RESERVED_FOR_NEGOTIATION)));
 }
 
 TEST_F(QuicVersionsTest, QuicVersionLabelToString) {