QuicDispatcher asks the ConnectionIdGenerator how long short-header connection IDs should be.
Tested against all 4 permutations of quic_connection_uses_abstract_connection_id_generator, and quic_ask_for_short_header_connection_id_length.
(The test passes by expecting connection failure when these flags are not in alignment -- the point of the last flag is to fix the bug.)
Protected by FLAGS_quic_reloadable_flag_quic_ask_for_short_header_connection_id_length.
PiperOrigin-RevId: 481258788
diff --git a/quiche/quic/core/quic_buffered_packet_store.cc b/quiche/quic/core/quic_buffered_packet_store.cc
index a84a293..4c729b6 100644
--- a/quiche/quic/core/quic_buffered_packet_store.cc
+++ b/quiche/quic/core/quic_buffered_packet_store.cc
@@ -194,12 +194,19 @@
absl::optional<absl::string_view> unused_retry_token;
std::string unused_detailed_error;
+ // We don't need to pass |generator| because we already got the correct
+ // connection ID length when we buffered the packet and indexed by
+ // connection ID.
QuicErrorCode error_code = QuicFramer::ParsePublicHeaderDispatcher(
- *packet.packet, kQuicDefaultConnectionIdLength, &unused_format,
- &long_packet_type, &unused_version_flag, &unused_use_length_prefix,
- &unused_version_label, &unused_parsed_version,
- &unused_destination_connection_id, &unused_source_connection_id,
- &unused_retry_token, &unused_detailed_error);
+ *packet.packet,
+ GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length)
+ ? connection_id.length()
+ : kQuicDefaultConnectionIdLength,
+ &unused_format, &long_packet_type, &unused_version_flag,
+ &unused_use_length_prefix, &unused_version_label,
+ &unused_parsed_version, &unused_destination_connection_id,
+ &unused_source_connection_id, &unused_retry_token,
+ &unused_detailed_error);
if (error_code == QUIC_NO_ERROR && long_packet_type == INITIAL) {
initial_packets.push_back(std::move(packet));
@@ -208,8 +215,8 @@
}
}
- initial_packets.splice(initial_packets.end(), other_packets);
- packets_to_deliver.buffered_packets = std::move(initial_packets);
+ initial_packets.splice(initial_packets.end(), other_packets);
+ packets_to_deliver.buffered_packets = std::move(initial_packets);
}
return packets_to_deliver;
}
diff --git a/quiche/quic/core/quic_dispatcher.cc b/quiche/quic/core/quic_dispatcher.cc
index da0af71..0ae3a64 100644
--- a/quiche/quic/core/quic_dispatcher.cc
+++ b/quiche/quic/core/quic_dispatcher.cc
@@ -41,6 +41,13 @@
// Minimal INITIAL packet length sent by clients is 1200.
const QuicPacketLength kMinClientInitialPacketLength = 1200;
+bool VariableShortHeaderConnectionIdLengths() {
+ return GetQuicReloadableFlag(
+ quic_ask_for_short_header_connection_id_length) &&
+ GetQuicReloadableFlag(
+ quic_connection_uses_abstract_connection_id_generator);
+}
+
// An alarm that informs the QuicDispatcher to delete old sessions.
class DeleteSessionsAlarm : public QuicAlarm::DelegateWithoutContext {
public:
@@ -305,13 +312,24 @@
absl::string_view(packet.data(), packet.length()));
ReceivedPacketInfo packet_info(self_address, peer_address, packet);
std::string detailed_error;
- const QuicErrorCode error = QuicFramer::ParsePublicHeaderDispatcher(
- packet, expected_server_connection_id_length_, &packet_info.form,
- &packet_info.long_packet_type, &packet_info.version_flag,
- &packet_info.use_length_prefix, &packet_info.version_label,
- &packet_info.version, &packet_info.destination_connection_id,
- &packet_info.source_connection_id, &packet_info.retry_token,
- &detailed_error);
+ QuicErrorCode error;
+ if (GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length)) {
+ error = QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
+ packet, &packet_info.form, &packet_info.long_packet_type,
+ &packet_info.version_flag, &packet_info.use_length_prefix,
+ &packet_info.version_label, &packet_info.version,
+ &packet_info.destination_connection_id,
+ &packet_info.source_connection_id, &packet_info.retry_token,
+ &detailed_error, connection_id_generator_);
+ } else {
+ error = QuicFramer::ParsePublicHeaderDispatcher(
+ packet, expected_server_connection_id_length_, &packet_info.form,
+ &packet_info.long_packet_type, &packet_info.version_flag,
+ &packet_info.use_length_prefix, &packet_info.version_label,
+ &packet_info.version, &packet_info.destination_connection_id,
+ &packet_info.source_connection_id, &packet_info.retry_token,
+ &detailed_error);
+ }
if (error != QUIC_NO_ERROR) {
// Packet has framing error.
SetLastError(error);
@@ -347,7 +365,16 @@
}
}
- if (should_update_expected_server_connection_id_length_) {
+ // Before introducing the flag, it was impossible for a short header to
+ // update |expected_server_connection_id_length_|.
+ QUIC_BUG_IF(
+ quic_bug_480483284_01,
+ !GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length) &&
+ !packet_info.version_flag &&
+ packet_info.destination_connection_id.length() !=
+ expected_server_connection_id_length_);
+ if (should_update_expected_server_connection_id_length_ &&
+ packet_info.version_flag) {
expected_server_connection_id_length_ =
packet_info.destination_connection_id.length();
}
@@ -356,6 +383,38 @@
// Packet has been dropped or successfully dispatched, stop processing.
return;
}
+ // The framer might have extracted the incorrect Connection ID length from a
+ // short header. |packet| could be gQUIC; if Q043, the connection ID has been
+ // parsed correctly thanks to the fixed bit. If a Q046 or Q050 short header,
+ // the dispatcher might have assumed it was a long connection ID when (because
+ // it was gQUIC) it actually issued or kept an 8-byte ID. The other case is
+ // where NEW_CONNECTION_IDs are not using the generator, and the dispatcher
+ // is, due to flag misconfiguration.
+ if (!packet_info.version_flag &&
+ GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length) &&
+ (IsSupportedVersion(ParsedQuicVersion::Q046()) ||
+ IsSupportedVersion(ParsedQuicVersion::Q050()) ||
+ !GetQuicReloadableFlag(
+ quic_connection_uses_abstract_connection_id_generator))) {
+ ReceivedPacketInfo gquic_packet_info(self_address, peer_address, packet);
+ // Try again without asking |connection_id_generator_| for the length.
+ const QuicErrorCode gquic_error = QuicFramer::ParsePublicHeaderDispatcher(
+ packet, expected_server_connection_id_length_, &gquic_packet_info.form,
+ &gquic_packet_info.long_packet_type, &gquic_packet_info.version_flag,
+ &gquic_packet_info.use_length_prefix, &gquic_packet_info.version_label,
+ &gquic_packet_info.version,
+ &gquic_packet_info.destination_connection_id,
+ &gquic_packet_info.source_connection_id, &gquic_packet_info.retry_token,
+ &detailed_error);
+ if (gquic_error == QUIC_NO_ERROR) {
+ if (MaybeDispatchPacket(gquic_packet_info)) {
+ return;
+ }
+ } else {
+ QUICHE_VLOG(1) << "Tried to parse short header as gQUIC packet: "
+ << detailed_error;
+ }
+ }
ProcessHeader(&packet_info);
}
@@ -416,15 +475,20 @@
// than 64 bits and smaller than what we expect. Unless the version is
// unknown, in which case we allow short connection IDs for version
// negotiation because that version could allow those.
+ uint8_t expected_connection_id_length =
+ VariableShortHeaderConnectionIdLengths()
+ ? connection_id_generator_.ConnectionIdLength(
+ static_cast<uint8_t>(*server_connection_id.data()))
+ : expected_server_connection_id_length_;
if (packet_info.version_flag && packet_info.version.IsKnown() &&
server_connection_id.length() < kQuicMinimumInitialConnectionIdLength &&
- server_connection_id.length() < expected_server_connection_id_length_ &&
+ server_connection_id.length() < expected_connection_id_length &&
!allow_short_initial_server_connection_ids_) {
QUICHE_DCHECK(packet_info.version_flag);
QUICHE_DCHECK(packet_info.version.AllowsVariableLengthConnectionIds());
QUIC_DLOG(INFO) << "Packet with short destination connection ID "
<< server_connection_id << " expected "
- << static_cast<int>(expected_server_connection_id_length_);
+ << static_cast<int>(expected_connection_id_length);
// Drop the packet silently.
QUIC_CODE_COUNT(quic_dropped_invalid_small_initial_connection_id);
return true;
@@ -1294,8 +1358,13 @@
return;
}
} else {
+ uint8_t min_connection_id_length =
+ VariableShortHeaderConnectionIdLengths()
+ ? connection_id_generator_.ConnectionIdLength(static_cast<uint8_t>(
+ *packet_info.destination_connection_id.data()))
+ : expected_server_connection_id_length_;
const size_t MinValidPacketLength =
- kPacketHeaderTypeSize + expected_server_connection_id_length_ +
+ kPacketHeaderTypeSize + min_connection_id_length +
PACKET_1BYTE_PACKET_NUMBER + /*payload size=*/1 + /*tag size=*/12;
if (packet_info.packet.length() < MinValidPacketLength) {
// The packet size is too small.
diff --git a/quiche/quic/core/quic_dispatcher_test.cc b/quiche/quic/core/quic_dispatcher_test.cc
index ffa0b22..ce5e23f 100644
--- a/quiche/quic/core/quic_dispatcher_test.cc
+++ b/quiche/quic/core/quic_dispatcher_test.cc
@@ -584,6 +584,58 @@
ProcessFirstFlight(client_address, TestConnectionId(1));
}
+TEST_P(QuicDispatcherTestAllVersions, VariableServerConnectionIdLength) {
+ QuicConnectionId old_id = TestConnectionId(1);
+ // Return a connection ID that is not expected_server_connection_id_length_
+ // bytes long.
+ generated_connection_id_ = QuicConnectionId(
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b});
+ expect_generator_is_called_ = version_.HasIetfQuicFrames();
+ QuicConnectionId new_id =
+ expect_generator_is_called_ ? *generated_connection_id_ : old_id;
+ // This will not return the correct value for long headers that might use a
+ // first byte other than 0x00, but long header replies don't matter.
+ if (GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length)) {
+ EXPECT_CALL(connection_id_generator_, ConnectionIdLength(0x00))
+ .WillRepeatedly(Return(generated_connection_id_->length()));
+ } else {
+ EXPECT_CALL(connection_id_generator_, ConnectionIdLength(_)).Times(0);
+ }
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+ EXPECT_CALL(*dispatcher_,
+ CreateQuicSession(new_id, _, client_address, Eq(ExpectedAlpn()),
+ _, Eq(ParsedClientHelloForTest())))
+ .WillOnce(Return(ByMove(CreateSession(
+ dispatcher_.get(), config_, new_id, client_address, &mock_helper_,
+ &mock_alarm_factory_, &crypto_config_,
+ QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_))));
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .WillOnce(WithArg<2>(Invoke([this](const QuicEncryptedPacket& packet) {
+ ValidatePacket(TestConnectionId(1), packet);
+ })));
+ ProcessFirstFlight(client_address, old_id);
+
+ // Send short header packets with the new length and verify they are parsed
+ // correctly. If flag is set, all versions should succeed. If not, it should
+ // fail (this is the bugfix!). gQUIC never gets a new connection ID, so it's
+ // not affected by asking.
+ if (version_.HasIetfQuicFrames() &&
+ !GetQuicReloadableFlag(quic_ask_for_short_header_connection_id_length)) {
+ // Dispatcher issued a longer connection ID if IETF QUIC, but won't ask for
+ // that length when processing a short header. Thus dispatch will fail.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(0);
+ } else {
+ // Dispatch succeeds, because it's gQUIC or all the flags are aligned.
+ EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()),
+ ProcessUdpPacket(_, _, _))
+ .Times(1);
+ }
+ ProcessPacket(client_address, new_id, false, "foo");
+}
+
void QuicDispatcherTestBase::TestTlsMultiPacketClientHello(
bool add_reordering, bool long_connection_id) {
if (!version_.UsesTls()) {
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 6078d31..d54304f 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -83,6 +83,8 @@
QUIC_FLAG(quic_reloadable_flag_quic_connection_migration_use_new_cid_v2, true)
// If true, uses conservative cwnd gain and pacing gain when cwnd gets bootstrapped.
QUIC_FLAG(quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
+// Instead of assuming an incoming connection ID length for short headers, ask each time, if both quic_abstract_connection_id_generator and quic_connection_uses_abstract_connection_id_generator are true.
+QUIC_FLAG(quic_reloadable_flag_quic_ask_for_short_header_connection_id_length, false)
// QuicConnection uses a library to generate connection IDs
QUIC_FLAG(quic_reloadable_flag_quic_connection_uses_abstract_connection_id_generator, true)
// When true, defaults to BBR congestion control instead of Cubic.
diff --git a/quiche/quic/core/quic_framer.cc b/quiche/quic/core/quic_framer.cc
index 2c7860f..29a0789 100644
--- a/quiche/quic/core/quic_framer.cc
+++ b/quiche/quic/core/quic_framer.cc
@@ -4,6 +4,8 @@
#include "quiche/quic/core/quic_framer.h"
+#include <sys/types.h>
+
#include <cstddef>
#include <cstdint>
#include <limits>
@@ -6863,6 +6865,35 @@
}
// static
+QuicErrorCode QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
+ const QuicEncryptedPacket& packet, PacketHeaderFormat* format,
+ QuicLongHeaderType* long_packet_type, bool* version_present,
+ bool* has_length_prefix, QuicVersionLabel* version_label,
+ ParsedQuicVersion* parsed_version,
+ QuicConnectionId* destination_connection_id,
+ QuicConnectionId* source_connection_id,
+ absl::optional<absl::string_view>* retry_token, std::string* detailed_error,
+ ConnectionIdGeneratorInterface& generator) {
+ QuicDataReader reader(packet.data(), packet.length());
+ // Get the first two bytes.
+ if (reader.BytesRemaining() < 2) {
+ *detailed_error = "Unable to read first two bytes.";
+ return QUIC_INVALID_PACKET_HEADER;
+ }
+ uint8_t two_bytes[2];
+ reader.ReadBytes(two_bytes, 2);
+ uint8_t expected_destination_connection_id_length =
+ (two_bytes[0] & FLAGS_LONG_HEADER)
+ ? 0
+ : generator.ConnectionIdLength(two_bytes[1]);
+ return ParsePublicHeaderDispatcher(
+ packet, expected_destination_connection_id_length, format,
+ long_packet_type, version_present, has_length_prefix, version_label,
+ parsed_version, destination_connection_id, source_connection_id,
+ retry_token, detailed_error);
+}
+
+// static
QuicErrorCode QuicFramer::ParsePublicHeaderGoogleQuic(
QuicDataReader* reader, uint8_t* first_byte, PacketHeaderFormat* format,
bool* version_present, QuicVersionLabel* version_label,
@@ -7021,8 +7052,6 @@
*format = GetIetfPacketHeaderFormat(*first_byte);
if (*format == IETF_QUIC_SHORT_HEADER_PACKET) {
- // Read destination connection ID using
- // expected_destination_connection_id_length to determine its length.
if (!reader->ReadConnectionId(destination_connection_id,
expected_destination_connection_id_length)) {
*detailed_error = "Unable to read destination connection ID.";
diff --git a/quiche/quic/core/quic_framer.h b/quiche/quic/core/quic_framer.h
index 4552693..484d7c1 100644
--- a/quiche/quic/core/quic_framer.h
+++ b/quiche/quic/core/quic_framer.h
@@ -11,6 +11,7 @@
#include <string>
#include "absl/strings/string_view.h"
+#include "quiche/quic/core/connection_id_generator.h"
#include "quiche/quic/core/crypto/quic_decrypter.h"
#include "quiche/quic/core/crypto/quic_encrypter.h"
#include "quiche/quic/core/crypto/quic_random.h"
@@ -432,6 +433,10 @@
// Parses the unencrypted fields in a QUIC header using |reader| as input,
// stores the result in the other parameters.
// |expected_destination_connection_id_length| is only used for short headers.
+ // When server connection IDs are generated by a
+ // ConnectionIdGeneartor interface, and callers need an accurate
+ // Destination Connection ID for short header packets, call
+ // ParsePublicHeaderDispatcherShortHeaderLengthUnknown() instead.
static QuicErrorCode ParsePublicHeader(
QuicDataReader* reader, uint8_t expected_destination_connection_id_length,
bool ietf_format, uint8_t* first_byte, PacketHeaderFormat* format,
@@ -445,7 +450,10 @@
// Parses the unencrypted fields in |packet| and stores them in the other
// parameters. This can only be called on the server.
- // |expected_destination_connection_id_length| is only used for short headers.
+ // |expected_destination_connection_id_length| is only used
+ // for short headers. When callers need an accurate Destination Connection ID
+ // specifically for short header packets, call
+ // ParsePublicHeaderDispatcherShortHeaderLengthUnknown() instead.
static QuicErrorCode ParsePublicHeaderDispatcher(
const QuicEncryptedPacket& packet,
uint8_t expected_destination_connection_id_length,
@@ -457,6 +465,24 @@
absl::optional<absl::string_view>* retry_token,
std::string* detailed_error);
+ // Parses the unencrypted fields in |packet| and stores them in the other
+ // parameters. The only callers that should use this method are ones where
+ // (1) the short-header connection ID length is only known by looking at the
+ // connection ID itself (and |generator| can provide the answer), and (2)
+ // the caller is interested in the parsed contents even if the packet has a
+ // short header. Some callers are only interested in parsing long header
+ // packets to peer into the handshake, and should use
+ // ParsePublicHeaderDispatcher instead.
+ static QuicErrorCode ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
+ const QuicEncryptedPacket& packet, PacketHeaderFormat* format,
+ QuicLongHeaderType* long_packet_type, bool* version_present,
+ bool* has_length_prefix, QuicVersionLabel* version_label,
+ ParsedQuicVersion* parsed_version,
+ QuicConnectionId* destination_connection_id,
+ QuicConnectionId* source_connection_id,
+ absl::optional<absl::string_view>* retry_token,
+ std::string* detailed_error, ConnectionIdGeneratorInterface& generator);
+
// Serializes a packet containing |frames| into |buffer|.
// Returns the length of the packet, which must not be longer than
// |packet_length|. Returns 0 if it fails to serialize.
diff --git a/quiche/quic/core/quic_framer_test.cc b/quiche/quic/core/quic_framer_test.cc
index 16f44a5..1293834 100644
--- a/quiche/quic/core/quic_framer_test.cc
+++ b/quiche/quic/core/quic_framer_test.cc
@@ -16416,6 +16416,65 @@
framer_.detailed_error());
}
+TEST_P(QuicFramerTest, ShortHeaderWithNonDefaultConnectionIdLength) {
+ SetDecrypterLevel(ENCRYPTION_FORWARD_SECURE);
+ // clang-format off
+ unsigned char packet[kMaxIncomingPacketSize + 1] = {
+ // type (short header, 4 byte packet number)
+ 0x43,
+ // connection_id
+ 0x28, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10, 0x48,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+
+ // frame type (padding frame)
+ 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+ unsigned char* p = packet;
+ size_t p_size = ABSL_ARRAYSIZE(packet);
+
+ const size_t header_size = GetPacketHeaderSize(
+ framer_.transport_version(), kPacket8ByteConnectionId + 1,
+ kPacket0ByteConnectionId, !kIncludeVersion,
+ !kIncludeDiversificationNonce, PACKET_4BYTE_PACKET_NUMBER,
+ quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0, 0,
+ quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0) + 1;
+ // Add one because it's a 9 byte connection ID.
+
+ memset(p + header_size, 0, kMaxIncomingPacketSize - header_size);
+
+ QuicEncryptedPacket encrypted(AsChars(p), p_size, false);
+ MockConnectionIdGenerator generator;
+ PacketHeaderFormat format;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
+ bool version_flag;
+ QuicConnectionId destination_connection_id, source_connection_id;
+ QuicVersionLabel version_label;
+ std::string detailed_error;
+ bool use_length_prefix;
+ absl::optional<absl::string_view> retry_token;
+ ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
+ EXPECT_CALL(generator, ConnectionIdLength(0x28))
+ .WillOnce(Return(9));
+ EXPECT_EQ(QUIC_NO_ERROR,
+ QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
+ encrypted, &format, &long_packet_type, &version_flag,
+ &use_length_prefix, &version_label, &parsed_version,
+ &destination_connection_id, &source_connection_id, &retry_token,
+ &detailed_error, generator));
+ EXPECT_EQ(format, IETF_QUIC_SHORT_HEADER_PACKET);
+ EXPECT_EQ(long_packet_type, INVALID_PACKET_TYPE);
+ EXPECT_FALSE(version_flag);
+ EXPECT_FALSE(use_length_prefix);
+ EXPECT_EQ(version_label, 0);
+ EXPECT_EQ(parsed_version, UnsupportedQuicVersion());
+ EXPECT_EQ(destination_connection_id.length(), 9);
+ EXPECT_EQ(source_connection_id.length(), 0);
+ EXPECT_FALSE(retry_token.has_value());
+ EXPECT_EQ(detailed_error, "");
+}
+
} // namespace
} // namespace test
} // namespace quic