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