Flush creator after MaybeCoalescePacketOfHigherSpace. This would allow connection to coalesce HANDSHAKE + 1-RTT packets while there is an INITIAL in the coalescer.
Protected by FLAGS_quic_reloadable_flag_quic_flush_after_coalesce_higher_space_packets.
PiperOrigin-RevId: 436835803
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 5dfff49..6234489 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -4786,10 +4786,30 @@
connection_->SendAck();
}
}
- connection_->packet_creator_.Flush();
- if (connection_->version().CanSendCoalescedPackets()) {
- connection_->MaybeCoalescePacketOfHigherSpace();
- connection_->FlushCoalescedPacket();
+
+ if (connection_->flush_after_coalesce_higher_space_packets_) {
+ // INITIAL or HANDSHAKE retransmission could cause peer to derive new
+ // keys, such that the buffered undecryptable packets may be processed.
+ // This endpoint would derive an inflated RTT sample when receiving ACKs
+ // of those undecryptable packets. To mitigate this, tries to coalesce as
+ // many higher space packets as possible (via for loop inside
+ // MaybeCoalescePacketOfHigherSpace) to fill the remaining space in the
+ // coalescer.
+ QUIC_RELOADABLE_FLAG_COUNT(
+ quic_flush_after_coalesce_higher_space_packets);
+ if (connection_->version().CanSendCoalescedPackets()) {
+ connection_->MaybeCoalescePacketOfHigherSpace();
+ }
+ connection_->packet_creator_.Flush();
+ if (connection_->version().CanSendCoalescedPackets()) {
+ connection_->FlushCoalescedPacket();
+ }
+ } else {
+ connection_->packet_creator_.Flush();
+ if (connection_->version().CanSendCoalescedPackets()) {
+ connection_->MaybeCoalescePacketOfHigherSpace();
+ connection_->FlushCoalescedPacket();
+ }
}
connection_->FlushPackets();
if (!handshake_packet_sent_ && connection_->handshake_packet_sent_) {
@@ -5831,16 +5851,15 @@
}
void QuicConnection::MaybeCoalescePacketOfHigherSpace() {
- if (!connected() || !packet_creator_.HasSoftMaxPacketLength() ||
- fill_coalesced_packet_) {
- // Make sure MaybeCoalescePacketOfHigherSpace is not re-entrant.
+ if (!connected() || !packet_creator_.HasSoftMaxPacketLength()) {
return;
}
- // INITIAL or HANDSHAKE retransmission could cause peer to derive new
- // keys, such that the buffered undecryptable packets may be processed.
- // This endpoint would derive an inflated RTT sample (which includes the PTO
- // timeout) when receiving ACKs of those undecryptable packets. To mitigate
- // this, tries to coalesce a packet of higher encryption level.
+ if (fill_coalesced_packet_) {
+ // Make sure MaybeCoalescePacketOfHigherSpace is not re-entrant.
+ QUIC_BUG_IF(quic_coalesce_packet_reentrant,
+ flush_after_coalesce_higher_space_packets_);
+ return;
+ }
for (EncryptionLevel retransmission_level :
{ENCRYPTION_INITIAL, ENCRYPTION_HANDSHAKE}) {
// Coalesce HANDSHAKE with INITIAL retransmission, and coalesce 1-RTT with
@@ -5854,6 +5873,9 @@
NOT_RETRANSMISSION &&
framer_.HasEncrypterOfEncryptionLevel(coalesced_level) &&
!coalesced_packet_.ContainsPacketOfEncryptionLevel(coalesced_level)) {
+ QUIC_DVLOG(1) << ENDPOINT
+ << "Trying to coalesce packet of encryption level: "
+ << EncryptionLevelToString(coalesced_level);
fill_coalesced_packet_ = true;
sent_packet_manager_.RetransmitDataOfSpaceIfAny(
QuicUtils::GetPacketNumberSpace(coalesced_level));
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 0f7cac3..ec39ff1 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -2252,6 +2252,9 @@
// Enable this via reloadable flag once this feature is complete.
bool connection_migration_use_new_cid_ = false;
+ const bool flush_after_coalesce_higher_space_packets_ =
+ GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets);
+
// TODO(b/205023946) Debug-only fields, to be deprecated after the bug is
// fixed.
absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 2716e00..9fe34d3 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -15253,13 +15253,22 @@
EXPECT_EQ(clock_.Now() + kAlarmGranularity,
connection_.GetAckAlarm()->deadline());
// ACK is not throttled by amplification limit, and SHLO is bundled. Also
- // HANDSHAKE packet gets coalesced.
- EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+ // HANDSHAKE + 1RTT packets get coalesced.
+ if (GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets)) {
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(3);
+ } else {
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+ }
// ACK alarm fires.
clock_.AdvanceTime(kAlarmGranularity);
connection_.GetAckAlarm()->Fire();
- // Verify HANDSHAKE packet is coalesced with INITIAL ACK + SHLO.
- EXPECT_EQ(0x03030303u, writer_->final_bytes_of_last_packet());
+ if (GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets)) {
+ // Verify 1-RTT packet is coalesced.
+ EXPECT_EQ(0x04040404u, writer_->final_bytes_of_last_packet());
+ } else {
+ // Verify HANDSHAKE packet is coalesced with INITIAL ACK + SHLO.
+ EXPECT_EQ(0x03030303u, writer_->final_bytes_of_last_packet());
+ }
// Only the first packet in the coalesced packet has been processed,
// verify SHLO is bundled with INITIAL ACK.
EXPECT_EQ(1u, writer_->ack_frames().size());
@@ -15270,7 +15279,16 @@
writer_->framer()->ProcessPacket(*packet);
EXPECT_EQ(0u, writer_->ack_frames().size());
EXPECT_EQ(1u, writer_->crypto_frames().size());
- ASSERT_TRUE(writer_->coalesced_packet() == nullptr);
+ if (GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets)) {
+ // Process the coalesced 1-RTT packet.
+ ASSERT_TRUE(writer_->coalesced_packet() != nullptr);
+ packet = writer_->coalesced_packet()->Clone();
+ writer_->framer()->ProcessPacket(*packet);
+ EXPECT_EQ(0u, writer_->crypto_frames().size());
+ EXPECT_EQ(1u, writer_->stream_frames().size());
+ } else {
+ ASSERT_TRUE(writer_->coalesced_packet() == nullptr);
+ }
// Received INITIAL 3.
EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
@@ -15608,6 +15626,79 @@
}
}
+TEST_P(QuicConnectionTest, CoalesceOneRTTPacketWithInitialAndHandshakePackets) {
+ if (!version().HasIetfQuicFrames()) {
+ return;
+ }
+ set_perspective(Perspective::IS_SERVER);
+ EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+ use_tagging_decrypter();
+
+ // Received INITIAL 1.
+ ProcessCryptoPacketAtLevel(1, ENCRYPTION_INITIAL);
+
+ peer_framer_.SetEncrypter(ENCRYPTION_ZERO_RTT,
+ std::make_unique<TaggingEncrypter>(0x02));
+
+ connection_.SetEncrypter(ENCRYPTION_INITIAL,
+ std::make_unique<TaggingEncrypter>(0x01));
+ connection_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<TaggingEncrypter>(0x03));
+ SetDecrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<StrictTaggingDecrypter>(0x03));
+ SetDecrypter(ENCRYPTION_ZERO_RTT,
+ std::make_unique<StrictTaggingDecrypter>(0x02));
+ connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<TaggingEncrypter>(0x04));
+
+ // Received ENCRYPTION_ZERO_RTT 2.
+ ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+
+ {
+ QuicConnection::ScopedPacketFlusher flusher(&connection_);
+ // Send INITIAL 1.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+ connection_.SendCryptoDataWithString("foo", 0, ENCRYPTION_INITIAL);
+ // Send HANDSHAKE 2.
+ EXPECT_CALL(visitor_, OnHandshakePacketSent()).Times(1);
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+ connection_.SendCryptoDataWithString(std::string(200, 'a'), 0,
+ ENCRYPTION_HANDSHAKE);
+ // Send 1-RTT data.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ connection_.SendStreamDataWithString(0, std::string(2000, 'b'), 0, FIN);
+ }
+ // Verify coalesced packet [INITIAL 1 + HANDSHAKE 2 + part of 1-RTT data] +
+ // rest of 1-RTT data get sent.
+ EXPECT_EQ(2u, writer_->packets_write_attempts());
+
+ // Received ENCRYPTION_INITIAL 3.
+ ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_INITIAL);
+
+ // Verify a coalesced packet gets sent.
+ EXPECT_EQ(3u, writer_->packets_write_attempts());
+
+ // Only the first INITIAL packet has been processed yet.
+ EXPECT_EQ(1u, writer_->ack_frames().size());
+ EXPECT_EQ(1u, writer_->crypto_frames().size());
+
+ // Process HANDSHAKE packet.
+ ASSERT_TRUE(writer_->coalesced_packet() != nullptr);
+ auto packet = writer_->coalesced_packet()->Clone();
+ writer_->framer()->ProcessPacket(*packet);
+ EXPECT_EQ(1u, writer_->crypto_frames().size());
+ if (!GetQuicReloadableFlag(quic_flush_after_coalesce_higher_space_packets)) {
+ ASSERT_TRUE(writer_->coalesced_packet() == nullptr);
+ return;
+ }
+ // Process 1-RTT packet.
+ ASSERT_TRUE(writer_->coalesced_packet() != nullptr);
+ packet = writer_->coalesced_packet()->Clone();
+ writer_->framer()->ProcessPacket(*packet);
+ EXPECT_EQ(1u, writer_->stream_frames().size());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index cb521fc..f053116 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -65,6 +65,8 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_count_bytes_on_alternative_path_seperately, true)
// If true, enable server retransmittable on wire PING.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_enable_server_on_wire_ping, true)
+// If true, flush creator after coalesce packet of higher space.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_flush_after_coalesce_higher_space_packets, true)
// If true, flush pending frames as well as pending padding bytes on connection migration.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_flush_pending_frames_and_padding_bytes_on_migration, true)
// If true, ietf connection migration is no longer conditioned on connection option RVCM.