Add QuicFramer Probe methods
These will allow the Youtube probe to rely on QUIC code to write and parse version negotiation packets instead of writing their own QUIC packets.
gfe-relnote: n/a, adds new unused API.
PiperOrigin-RevId: 248425256
Change-Id: Ibf204b1488897d7d81dc33f08ffd10147524b823
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index 9633053..f0954d1 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -6104,5 +6104,162 @@
return QUIC_NO_ERROR;
}
+// static
+bool QuicFramer::WriteClientVersionNegotiationProbePacket(
+ char* packet_bytes,
+ QuicByteCount packet_length,
+ const char* destination_connection_id_bytes,
+ uint8_t destination_connection_id_length) {
+ if (packet_bytes == nullptr) {
+ QUIC_BUG << "Invalid packet_bytes";
+ return false;
+ }
+ if (packet_length < kMinPacketSizeForVersionNegotiation ||
+ packet_length > 65535) {
+ QUIC_BUG << "Invalid packet_length";
+ return false;
+ }
+ if (destination_connection_id_length > kQuicMaxConnectionIdLength ||
+ (destination_connection_id_length > 0 &&
+ destination_connection_id_length < 4)) {
+ QUIC_BUG << "Invalid connection_id_length";
+ return false;
+ }
+ // clang-format off
+ static const unsigned char packet_start_bytes[] = {
+ // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+ 0xc0,
+ // Version, part of the IETF space reserved for negotiation.
+ // This intentionally differs from QuicVersionReservedForNegotiation()
+ // to allow differentiating them over the wire.
+ 0xca, 0xba, 0xda, 0xba,
+ };
+ // clang-format on
+ static_assert(sizeof(packet_start_bytes) == 5, "bad packet_start_bytes size");
+ QuicDataWriter writer(packet_length, packet_bytes);
+ if (!writer.WriteBytes(packet_start_bytes, sizeof(packet_start_bytes))) {
+ QUIC_BUG << "Failed to write packet start";
+ return false;
+ }
+
+ QuicConnectionId destination_connection_id(destination_connection_id_bytes,
+ destination_connection_id_length);
+ if (!AppendIetfConnectionIds(/*version_flag=*/true, destination_connection_id,
+ EmptyQuicConnectionId(), &writer)) {
+ QUIC_BUG << "Failed to write connection IDs";
+ return false;
+ }
+ // Add 8 bytes of zeroes followed by 8 bytes of ones to ensure that this does
+ // not parse with any known version. The zeroes make sure that packet numbers,
+ // retry token lengths and payload lengths are parsed as zero, and if the
+ // zeroes are treated as padding frames, 0xff is known to not parse as a
+ // valid frame type.
+ if (!writer.WriteUInt64(0) ||
+ !writer.WriteUInt64(std::numeric_limits<uint64_t>::max())) {
+ QUIC_BUG << "Failed to write 18 bytes";
+ return false;
+ }
+ // Make sure the polite greeting below is padded to a 16-byte boundary to
+ // make it easier to read in tcpdump.
+ while (writer.length() % 16 != 0) {
+ if (!writer.WriteUInt8(0)) {
+ QUIC_BUG << "Failed to write padding byte";
+ return false;
+ }
+ }
+ // Add a polite greeting in case a human sees this in tcpdump.
+ static const char polite_greeting[] =
+ "This packet only exists to trigger IETF QUIC version negotiation. "
+ "Please respond with a Version Negotiation packet indicating what "
+ "versions you support. Thank you and have a nice day.";
+ if (!writer.WriteBytes(polite_greeting, sizeof(polite_greeting))) {
+ QUIC_BUG << "Failed to write polite greeting";
+ return false;
+ }
+ // Fill the rest of the packet with zeroes.
+ writer.WritePadding();
+ DCHECK_EQ(0u, writer.remaining());
+ return true;
+}
+
+// static
+bool QuicFramer::ParseServerVersionNegotiationProbeResponse(
+ const char* packet_bytes,
+ QuicByteCount packet_length,
+ char* source_connection_id_bytes,
+ uint8_t* source_connection_id_length_out,
+ std::string* detailed_error) {
+ if (detailed_error == nullptr) {
+ QUIC_BUG << "Invalid error_details";
+ return false;
+ }
+ *detailed_error = "";
+ if (packet_bytes == nullptr) {
+ *detailed_error = "Invalid packet_bytes";
+ return false;
+ }
+ if (packet_length < 6) {
+ *detailed_error = "Invalid packet_length";
+ return false;
+ }
+ if (source_connection_id_bytes == nullptr) {
+ *detailed_error = "Invalid source_connection_id_bytes";
+ return false;
+ }
+ if (source_connection_id_length_out == nullptr) {
+ *detailed_error = "Invalid source_connection_id_length_out";
+ return false;
+ }
+ QuicDataReader reader(packet_bytes, packet_length);
+ uint8_t type_byte = 0;
+ if (!reader.ReadUInt8(&type_byte)) {
+ *detailed_error = "Failed to read type byte";
+ return false;
+ }
+ if ((type_byte & 0x80) == 0) {
+ *detailed_error = "Packet does not have long header";
+ return false;
+ }
+ uint32_t version = 0;
+ if (!reader.ReadUInt32(&version)) {
+ *detailed_error = "Failed to read version";
+ return false;
+ }
+ if (version != 0) {
+ *detailed_error = "Packet is not a version negotiation packet";
+ return false;
+ }
+ uint8_t expected_connection_id_length = 0,
+ destination_connection_id_length = 0, source_connection_id_length = 0;
+ if (!ProcessAndValidateIetfConnectionIdLength(
+ &reader, UnsupportedQuicVersion(),
+ /*should_update_expected_connection_id_length=*/true,
+ &expected_connection_id_length, &destination_connection_id_length,
+ &source_connection_id_length, detailed_error)) {
+ return false;
+ }
+ if (destination_connection_id_length != 0) {
+ *detailed_error = "Received unexpected destination connection ID length";
+ return false;
+ }
+ QuicConnectionId destination_connection_id, source_connection_id;
+ if (!reader.ReadConnectionId(&destination_connection_id,
+ destination_connection_id_length)) {
+ *detailed_error = "Failed to read destination connection ID";
+ return false;
+ }
+ if (!reader.ReadConnectionId(&source_connection_id,
+ source_connection_id_length)) {
+ *detailed_error = "Failed to read source connection ID";
+ return false;
+ }
+
+ memcpy(source_connection_id_bytes, source_connection_id.data(),
+ source_connection_id_length);
+ *source_connection_id_length_out = source_connection_id_length;
+
+ return true;
+}
+
#undef ENDPOINT // undef for jumbo builds
} // namespace quic
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index bded585..ab0e8d7 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -588,6 +588,38 @@
void EnableMultiplePacketNumberSpacesSupport();
+ // Writes an array of bytes that, if sent as a UDP datagram, will trigger
+ // IETF QUIC Version Negotiation on servers. The bytes will be written to
+ // |packet_bytes|, which must point to |packet_length| bytes of memory.
+ // |packet_length| must be in the range [1200, 65535].
+ // |destination_connection_id_bytes| will be sent as the destination
+ // connection ID, and must point to |destination_connection_id_length| bytes
+ // of memory. |destination_connection_id_length| must be either 0 or in the
+ // range [4,18]. When targeting Google servers, it is recommended to use a
+ // |destination_connection_id_length| of 8.
+ static bool WriteClientVersionNegotiationProbePacket(
+ char* packet_bytes,
+ QuicByteCount packet_length,
+ const char* destination_connection_id_bytes,
+ uint8_t destination_connection_id_length);
+
+ // Parses a packet which a QUIC server sent in response to a packet sent by
+ // WriteClientVersionNegotiationProbePacket. |packet_bytes| must point to
+ // |packet_length| bytes in memory which represent the response.
+ // |packet_length| must be greater or equal to 6. This method will fill in
+ // |source_connection_id_bytes| which must point to at least 18 bytes in
+ // memory. |source_connection_id_length_out| will contain the length of the
+ // received source connection ID, which on success will match the contents of
+ // the destination connection ID passed in to
+ // WriteClientVersionNegotiationProbePacket. In the case of a failure,
+ // |detailed_error| will be filled in with an explanation of what failed.
+ static bool ParseServerVersionNegotiationProbeResponse(
+ const char* packet_bytes,
+ QuicByteCount packet_length,
+ char* source_connection_id_bytes,
+ uint8_t* source_connection_id_length_out,
+ std::string* detailed_error);
+
private:
friend class test::QuicFramerPeer;
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index e83fd1b..f196e5d 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -13603,6 +13603,104 @@
CheckFramingBoundaries(packet, QUIC_INVALID_PACKET_HEADER);
}
+TEST_P(QuicFramerTest, WriteClientVersionNegotiationProbePacket) {
+ // clang-format off
+ static const char expected_packet[1200] = {
+ // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+ 0xc0,
+ // Version, part of the IETF space reserved for negotiation.
+ 0xca, 0xba, 0xda, 0xba,
+ // Destination connection ID length 8, source connection ID length 0.
+ 0x50,
+ // 8-byte destination connection ID.
+ 0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21,
+ // 8 bytes of zeroes followed by 8 bytes of ones to ensure that this does
+ // not parse with any known version.
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ // 2 bytes of zeroes to pad to 16 byte boundary.
+ 0x00, 0x00,
+ // A polite greeting in case a human sees this in tcpdump.
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x63,
+ 0x6b, 0x65, 0x74, 0x20, 0x6f, 0x6e, 0x6c, 0x79,
+ 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x20,
+ 0x74, 0x6f, 0x20, 0x74, 0x72, 0x69, 0x67, 0x67,
+ 0x65, 0x72, 0x20, 0x49, 0x45, 0x54, 0x46, 0x20,
+ 0x51, 0x55, 0x49, 0x43, 0x20, 0x76, 0x65, 0x72,
+ 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x65, 0x67,
+ 0x6f, 0x74, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+ 0x2e, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65,
+ 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64,
+ 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x20,
+ 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
+ 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74,
+ 0x69, 0x6f, 0x6e, 0x20, 0x70, 0x61, 0x63, 0x6b,
+ 0x65, 0x74, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63,
+ 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x77, 0x68,
+ 0x61, 0x74, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69,
+ 0x6f, 0x6e, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20,
+ 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+ 0x20, 0x54, 0x68, 0x61, 0x6e, 0x6b, 0x20, 0x79,
+ 0x6f, 0x75, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68,
+ 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x69,
+ 0x63, 0x65, 0x20, 0x64, 0x61, 0x79, 0x2e, 0x00,
+ };
+ // clang-format on
+ char packet[1200];
+ char destination_connection_id_bytes[] = {0x56, 0x4e, 0x20, 0x70,
+ 0x6c, 0x7a, 0x20, 0x21};
+ EXPECT_TRUE(QuicFramer::WriteClientVersionNegotiationProbePacket(
+ packet, sizeof(packet), destination_connection_id_bytes,
+ sizeof(destination_connection_id_bytes)));
+ test::CompareCharArraysWithHexError("constructed packet", expected_packet,
+ sizeof(expected_packet), packet,
+ sizeof(packet));
+ QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet),
+ sizeof(packet), false);
+ // Make sure we fail to parse this packet for the version under test.
+ EXPECT_FALSE(framer_.ProcessPacket(encrypted));
+ if (framer_.transport_version() <= QUIC_VERSION_43) {
+ // We can only parse the connection ID with an IETF parser.
+ return;
+ }
+ ASSERT_TRUE(visitor_.header_.get());
+ QuicConnectionId probe_payload_connection_id(
+ reinterpret_cast<const char*>(destination_connection_id_bytes),
+ sizeof(destination_connection_id_bytes));
+ EXPECT_EQ(probe_payload_connection_id,
+ visitor_.header_.get()->destination_connection_id);
+}
+
+TEST_P(QuicFramerTest, ParseServerVersionNegotiationProbeResponse) {
+ // clang-format off
+ const char packet[] = {
+ // IETF long header with fixed bit set, type initial, all-0 encrypted bits.
+ 0xc0,
+ // Version of 0, indicating version negotiation.
+ 0x00, 0x00, 0x00, 0x00,
+ // Destination connection ID length 0, source connection ID length 8.
+ 0x05,
+ // 8-byte source connection ID.
+ 0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21,
+ // A few supported versions.
+ 0xaa, 0xaa, 0xaa, 0xaa,
+ QUIC_VERSION_BYTES,
+ };
+ // clang-format on
+ char probe_payload_bytes[] = {0x56, 0x4e, 0x20, 0x70, 0x6c, 0x7a, 0x20, 0x21};
+ char parsed_probe_payload_bytes[kQuicMaxConnectionIdLength] = {};
+ uint8_t parsed_probe_payload_length = 0;
+ std::string parse_detailed_error = "";
+ EXPECT_TRUE(QuicFramer::ParseServerVersionNegotiationProbeResponse(
+ reinterpret_cast<const char*>(packet), sizeof(packet),
+ reinterpret_cast<char*>(parsed_probe_payload_bytes),
+ &parsed_probe_payload_length, &parse_detailed_error));
+ EXPECT_EQ("", parse_detailed_error);
+ test::CompareCharArraysWithHexError(
+ "parsed probe", probe_payload_bytes, sizeof(probe_payload_bytes),
+ parsed_probe_payload_bytes, parsed_probe_payload_length);
+}
+
} // namespace
} // namespace test
} // namespace quic