Plumb max_datagram_frame_size In QUIC_CRYPTO, the maximum allowed size of a DATAGRAM/MESSAGE frame is unlimited. However, in QUIC+TLS, it's negotiated during the handshake using the max_datagram_frame_size IETF QUIC transport parameter. This CL adds this limit to QuicPacketCreator and plumbs it through during the handshake. Since all of our uses of the DATAGRAM/MESSAGE frame today only support QUIC_CRYPTO, this CL does not change any existing behavior. Plumb max_datagram_frame_size, no behavior change, not flag protected PiperOrigin-RevId: 310456458 Change-Id: Ib40d6e7e950865e496809ba5c880dc47cd243be9
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc index d0f42b7..865aef8 100644 --- a/quic/core/quic_config.cc +++ b/quic/core/quic_config.cc
@@ -1188,7 +1188,6 @@ if (params.max_datagram_frame_size.IsValid()) { max_datagram_frame_size_.SetReceivedValue( params.max_datagram_frame_size.value()); - // TODO(dschinazi) act on this. } initial_session_flow_control_window_bytes_.SetReceivedValue(
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 6445fce..7922fdd 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -506,6 +506,10 @@ packet_creator_.SetMaxPacketLength( GetLimitedMaxPacketSize(packet_creator_.max_packet_length())); } + if (config.HasReceivedMaxDatagramFrameSize()) { + packet_creator_.SetMaxDatagramFrameSize( + config.ReceivedMaxDatagramFrameSize()); + } supports_release_time_ = writer_ != nullptr && writer_->SupportsReleaseTime() &&
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 1822d0c..f3bb4e1 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -9150,6 +9150,13 @@ if (!VersionSupportsMessageFrames(connection_.transport_version())) { return; } + if (connection_.version().UsesTls()) { + QuicConfig config; + QuicConfigPeer::SetReceivedMaxDatagramFrameSize( + &config, kMaxAcceptedDatagramFrameSize); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + } std::string message(connection_.GetCurrentLargestMessagePayload() * 2, 'a'); quiche::QuicheStringPiece message_data(message); QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); @@ -9192,6 +9199,94 @@ false)); } +TEST_P(QuicConnectionTest, GetCurrentLargestMessagePayload) { + if (!connection_.version().SupportsMessageFrames()) { + return; + } + // Force use of this encrypter to simplify test expectations by making sure + // that the encryption overhead is constant across versions. + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x00)); + QuicPacketLength expected_largest_payload = 1319; + if (connection_.version().SendsVariableLengthPacketNumberInLongHeader()) { + expected_largest_payload += 3; + } + if (connection_.version().HasLongHeaderLengths()) { + expected_largest_payload -= 2; + } + if (connection_.version().HasLengthPrefixedConnectionIds()) { + expected_largest_payload -= 1; + } + if (connection_.version().UsesTls()) { + // QUIC+TLS disallows DATAGRAM/MESSAGE frames before the handshake. + EXPECT_EQ(connection_.GetCurrentLargestMessagePayload(), 0); + QuicConfig config; + QuicConfigPeer::SetReceivedMaxDatagramFrameSize( + &config, kMaxAcceptedDatagramFrameSize); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + // Verify the value post-handshake. + EXPECT_EQ(connection_.GetCurrentLargestMessagePayload(), + expected_largest_payload); + } else { + EXPECT_EQ(connection_.GetCurrentLargestMessagePayload(), + expected_largest_payload); + } +} + +TEST_P(QuicConnectionTest, GetGuaranteedLargestMessagePayload) { + if (!connection_.version().SupportsMessageFrames()) { + return; + } + // Force use of this encrypter to simplify test expectations by making sure + // that the encryption overhead is constant across versions. + connection_.SetEncrypter(ENCRYPTION_INITIAL, + std::make_unique<TaggingEncrypter>(0x00)); + QuicPacketLength expected_largest_payload = 1319; + if (connection_.version().HasLongHeaderLengths()) { + expected_largest_payload -= 2; + } + if (connection_.version().HasLengthPrefixedConnectionIds()) { + expected_largest_payload -= 1; + } + if (connection_.version().UsesTls()) { + // QUIC+TLS disallows DATAGRAM/MESSAGE frames before the handshake. + EXPECT_EQ(connection_.GetGuaranteedLargestMessagePayload(), 0); + QuicConfig config; + QuicConfigPeer::SetReceivedMaxDatagramFrameSize( + &config, kMaxAcceptedDatagramFrameSize); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + // Verify the value post-handshake. + EXPECT_EQ(connection_.GetGuaranteedLargestMessagePayload(), + expected_largest_payload); + } else { + EXPECT_EQ(connection_.GetGuaranteedLargestMessagePayload(), + expected_largest_payload); + } +} + +TEST_P(QuicConnectionTest, LimitedLargestMessagePayload) { + if (!connection_.version().SupportsMessageFrames() || + !connection_.version().UsesTls()) { + return; + } + constexpr QuicPacketLength kFrameSizeLimit = 1000; + constexpr QuicPacketLength kPayloadSizeLimit = + kFrameSizeLimit - kQuicFrameTypeSize; + // QUIC+TLS disallows DATAGRAM/MESSAGE frames before the handshake. + EXPECT_EQ(connection_.GetCurrentLargestMessagePayload(), 0); + EXPECT_EQ(connection_.GetGuaranteedLargestMessagePayload(), 0); + QuicConfig config; + QuicConfigPeer::SetReceivedMaxDatagramFrameSize(&config, kFrameSizeLimit); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + connection_.SetFromConfig(config); + // Verify the value post-handshake. + EXPECT_EQ(connection_.GetCurrentLargestMessagePayload(), kPayloadSizeLimit); + EXPECT_EQ(connection_.GetGuaranteedLargestMessagePayload(), + kPayloadSizeLimit); +} + // Test to check that the path challenge/path response logic works // correctly. This test is only for version-99 TEST_P(QuicConnectionTest, PathChallengeResponse) { @@ -10575,6 +10670,10 @@ connection_options.push_back(kPTOS); connection_options.push_back(k1PTO); config.SetConnectionOptionsToSend(connection_options); + if (connection_.version().UsesTls()) { + QuicConfigPeer::SetReceivedMaxDatagramFrameSize( + &config, kMaxAcceptedDatagramFrameSize); + } EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); connection_.SetFromConfig(config); EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
diff --git a/quic/core/quic_packet_creator.cc b/quic/core/quic_packet_creator.cc index cea1a1b..a9d7076 100644 --- a/quic/core/quic_packet_creator.cc +++ b/quic/core/quic_packet_creator.cc
@@ -7,6 +7,7 @@ #include <algorithm> #include <cstddef> #include <cstdint> +#include <limits> #include <string> #include <utility> @@ -130,8 +131,15 @@ next_transmission_type_(NOT_RETRANSMISSION), flusher_attached_(false), fully_pad_crypto_handshake_packets_(true), - latched_hard_max_packet_length_(0) { + latched_hard_max_packet_length_(0), + max_datagram_frame_size_(0) { SetMaxPacketLength(kDefaultMaxPacketSize); + if (!framer_->version().UsesTls()) { + // QUIC+TLS negotiates the maximum datagram frame size via the + // IETF QUIC max_datagram_frame_size transport parameter. + // QUIC_CRYPTO however does not negotiate this so we set its value here. + SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } } QuicPacketCreator::~QuicPacketCreator() { @@ -165,6 +173,21 @@ << "Attempted to set max packet length too small"; } +void QuicPacketCreator::SetMaxDatagramFrameSize( + QuicByteCount max_datagram_frame_size) { + constexpr QuicByteCount upper_bound = + std::min<QuicByteCount>(std::numeric_limits<QuicPacketLength>::max(), + std::numeric_limits<size_t>::max()); + if (max_datagram_frame_size > upper_bound) { + // A value of |max_datagram_frame_size| that is equal or greater than + // 2^16-1 is effectively infinite because QUIC packets cannot be that large. + // We therefore clamp the value here to allow us to safely cast + // |max_datagram_frame_size_| to QuicPacketLength or size_t. + max_datagram_frame_size = upper_bound; + } + max_datagram_frame_size_ = max_datagram_frame_size; +} + void QuicPacketCreator::SetSoftMaxPacketLength(QuicByteCount length) { DCHECK(CanSetMaxPacketLength()); if (length > max_packet_length_) { @@ -326,6 +349,10 @@ bool QuicPacketCreator::HasRoomForMessageFrame(QuicByteCount length) { const size_t message_frame_size = QuicFramer::GetMessageFrameSize( framer_->transport_version(), /*last_frame_in_packet=*/true, length); + if (static_cast<QuicByteCount>(message_frame_size) > + max_datagram_frame_size_) { + return false; + } if (BytesFree() >= message_frame_size) { return true; } @@ -1705,8 +1732,12 @@ latched_hard_max_packet_length_ == 0 ? max_plaintext_size_ : framer_->GetMaxPlaintextSize(latched_hard_max_packet_length_); - return max_plaintext_size - - std::min(max_plaintext_size, packet_header_size + kQuicFrameTypeSize); + size_t largest_frame = + max_plaintext_size - std::min(max_plaintext_size, packet_header_size); + if (static_cast<QuicByteCount>(largest_frame) > max_datagram_frame_size_) { + largest_frame = static_cast<size_t>(max_datagram_frame_size_); + } + return largest_frame - std::min(largest_frame, kQuicFrameTypeSize); } QuicPacketLength QuicPacketCreator::GetGuaranteedLargestMessagePayload() const { @@ -1739,9 +1770,13 @@ latched_hard_max_packet_length_ == 0 ? max_plaintext_size_ : framer_->GetMaxPlaintextSize(latched_hard_max_packet_length_); + size_t largest_frame = + max_plaintext_size - std::min(max_plaintext_size, packet_header_size); + if (static_cast<QuicByteCount>(largest_frame) > max_datagram_frame_size_) { + largest_frame = static_cast<size_t>(max_datagram_frame_size_); + } const QuicPacketLength largest_payload = - max_plaintext_size - - std::min(max_plaintext_size, packet_header_size + kQuicFrameTypeSize); + largest_frame - std::min(largest_frame, kQuicFrameTypeSize); // This must always be less than or equal to GetCurrentLargestMessagePayload. DCHECK_LE(largest_payload, GetCurrentLargestMessagePayload()); return largest_payload;
diff --git a/quic/core/quic_packet_creator.h b/quic/core/quic_packet_creator.h index 508cd56..fbe046f 100644 --- a/quic/core/quic_packet_creator.h +++ b/quic/core/quic_packet_creator.h
@@ -278,6 +278,9 @@ // Sets the maximum packet length. void SetMaxPacketLength(QuicByteCount length); + // Sets the maximum DATAGRAM/MESSAGE frame size we can send. + void SetMaxDatagramFrameSize(QuicByteCount max_datagram_frame_size); + // Set a soft maximum packet length in the creator. If a packet cannot be // successfully created, creator will remove the soft limit and use the actual // max packet length. @@ -589,6 +592,11 @@ // SetSoftMaxPacketLength is called and max_packet_length_ gets // set to a soft value. QuicByteCount latched_hard_max_packet_length_; + + // The maximum length of a MESSAGE/DATAGRAM frame that our peer is willing to + // accept. There is no limit for QUIC_CRYPTO connections, but QUIC+TLS + // negotiates this during the handshake. + QuicByteCount max_datagram_frame_size_; }; } // namespace quic
diff --git a/quic/core/quic_packet_creator_test.cc b/quic/core/quic_packet_creator_test.cc index 19fc5cb..b11417d 100644 --- a/quic/core/quic_packet_creator_test.cc +++ b/quic/core/quic_packet_creator_test.cc
@@ -1778,6 +1778,9 @@ if (!VersionSupportsMessageFrames(client_framer_.transport_version())) { return; } + if (client_framer_.version().UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE); EXPECT_CALL(delegate_, OnSerializedPacket(_)) .Times(3) @@ -1830,6 +1833,9 @@ if (!VersionSupportsMessageFrames(client_framer_.transport_version())) { return; } + if (client_framer_.version().UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } std::string message_data(kDefaultMaxPacketSize, 'a'); quiche::QuicheStringPiece message_buffer(message_data); QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); @@ -1871,21 +1877,93 @@ } } -// Regression test for bugfix of GetPacketHeaderSize. TEST_P(QuicPacketCreatorTest, GetGuaranteedLargestMessagePayload) { - QuicTransportVersion version = creator_.transport_version(); - if (!VersionSupportsMessageFrames(version)) { + ParsedQuicVersion version = GetParam().version; + if (!version.SupportsMessageFrames()) { return; } + if (version.UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } QuicPacketLength expected_largest_payload = 1319; - if (QuicVersionHasLongHeaderLengths(version)) { + if (version.HasLongHeaderLengths()) { expected_largest_payload -= 2; } - if (GetParam().version.HasLengthPrefixedConnectionIds()) { + if (version.HasLengthPrefixedConnectionIds()) { expected_largest_payload -= 1; } EXPECT_EQ(expected_largest_payload, creator_.GetGuaranteedLargestMessagePayload()); + EXPECT_TRUE(creator_.HasRoomForMessageFrame( + creator_.GetGuaranteedLargestMessagePayload())); + + // Now test whether SetMaxDatagramFrameSize works. + creator_.SetMaxDatagramFrameSize(expected_largest_payload + 1 + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload, + creator_.GetGuaranteedLargestMessagePayload()); + EXPECT_TRUE(creator_.HasRoomForMessageFrame( + creator_.GetGuaranteedLargestMessagePayload())); + + creator_.SetMaxDatagramFrameSize(expected_largest_payload + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload, + creator_.GetGuaranteedLargestMessagePayload()); + EXPECT_TRUE(creator_.HasRoomForMessageFrame( + creator_.GetGuaranteedLargestMessagePayload())); + + creator_.SetMaxDatagramFrameSize(expected_largest_payload - 1 + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload - 1, + creator_.GetGuaranteedLargestMessagePayload()); + EXPECT_TRUE(creator_.HasRoomForMessageFrame( + creator_.GetGuaranteedLargestMessagePayload())); + + constexpr QuicPacketLength kFrameSizeLimit = 1000; + constexpr QuicPacketLength kPayloadSizeLimit = + kFrameSizeLimit - kQuicFrameTypeSize; + creator_.SetMaxDatagramFrameSize(kFrameSizeLimit); + EXPECT_EQ(creator_.GetGuaranteedLargestMessagePayload(), kPayloadSizeLimit); + EXPECT_TRUE(creator_.HasRoomForMessageFrame(kPayloadSizeLimit)); + EXPECT_FALSE(creator_.HasRoomForMessageFrame(kPayloadSizeLimit + 1)); +} + +TEST_P(QuicPacketCreatorTest, GetCurrentLargestMessagePayload) { + ParsedQuicVersion version = GetParam().version; + if (!version.SupportsMessageFrames()) { + return; + } + if (version.UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } + QuicPacketLength expected_largest_payload = 1319; + if (version.SendsVariableLengthPacketNumberInLongHeader()) { + expected_largest_payload += 3; + } + if (version.HasLongHeaderLengths()) { + expected_largest_payload -= 2; + } + if (version.HasLengthPrefixedConnectionIds()) { + expected_largest_payload -= 1; + } + EXPECT_EQ(expected_largest_payload, + creator_.GetCurrentLargestMessagePayload()); + + // Now test whether SetMaxDatagramFrameSize works. + creator_.SetMaxDatagramFrameSize(expected_largest_payload + 1 + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload, + creator_.GetCurrentLargestMessagePayload()); + + creator_.SetMaxDatagramFrameSize(expected_largest_payload + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload, + creator_.GetCurrentLargestMessagePayload()); + + creator_.SetMaxDatagramFrameSize(expected_largest_payload - 1 + + kQuicFrameTypeSize); + EXPECT_EQ(expected_largest_payload - 1, + creator_.GetCurrentLargestMessagePayload()); } TEST_P(QuicPacketCreatorTest, PacketTransmissionType) { @@ -2189,6 +2267,9 @@ // Same for message frame. if (VersionSupportsMessageFrames(client_framer_.transport_version())) { creator_.SetSoftMaxPacketLength(overhead); + if (client_framer_.version().UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } // Verify GetCurrentLargestMessagePayload is based on the actual // max_packet_length. EXPECT_LT(1u, creator_.GetCurrentLargestMessagePayload()); @@ -3677,6 +3758,9 @@ if (!VersionSupportsMessageFrames(framer_.transport_version())) { return; } + if (framer_.version().UsesTls()) { + creator_.SetMaxDatagramFrameSize(kMaxAcceptedDatagramFrameSize); + } quic::QuicMemSliceStorage storage(nullptr, 0, nullptr, 0); delegate_.SetCanWriteAnything(); EXPECT_CALL(delegate_, OnSerializedPacket(_))
diff --git a/quic/test_tools/quic_config_peer.cc b/quic/test_tools/quic_config_peer.cc index f8f3f42..3e75215 100644 --- a/quic/test_tools/quic_config_peer.cc +++ b/quic/test_tools/quic_config_peer.cc
@@ -110,5 +110,12 @@ config->received_original_connection_id_ = original_connection_id; } +// static +void QuicConfigPeer::SetReceivedMaxDatagramFrameSize( + QuicConfig* config, + uint64_t max_datagram_frame_size) { + config->max_datagram_frame_size_.SetReceivedValue(max_datagram_frame_size); +} + } // namespace test } // namespace quic
diff --git a/quic/test_tools/quic_config_peer.h b/quic/test_tools/quic_config_peer.h index 94f30b4..c435f22 100644 --- a/quic/test_tools/quic_config_peer.h +++ b/quic/test_tools/quic_config_peer.h
@@ -65,6 +65,9 @@ static void SetReceivedOriginalConnectionId( QuicConfig* config, const QuicConnectionId& original_connection_id); + + static void SetReceivedMaxDatagramFrameSize(QuicConfig* config, + uint64_t max_datagram_frame_size); }; } // namespace test