In QUIC, do not check amplification limit if there is pending timer credit. This would guarantee CRYPTO frame be retransmitted because of
1) PTO fires
2) bundled with outgoing ACKs.
Protected by FLAGS_quic_reloadable_flag_quic_donot_check_amplification_limit_with_pending_timer_credit.
PiperOrigin-RevId: 408392907
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index 7c4f004..87b1c86 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -3300,7 +3300,12 @@
return packet_creator_.HasSoftMaxPacketLength();
}
- if (LimitedByAmplificationFactor()) {
+ const bool donot_check_amplification_limit_with_pending_timer_credit =
+ GetQuicReloadableFlag(
+ quic_donot_check_amplification_limit_with_pending_timer_credit);
+
+ if (!donot_check_amplification_limit_with_pending_timer_credit &&
+ LimitedByAmplificationFactor()) {
// Server is constrained by the amplification restriction.
QUIC_CODE_COUNT(quic_throttled_by_amplification_limit);
QUIC_DVLOG(1) << ENDPOINT
@@ -3314,10 +3319,31 @@
}
if (sent_packet_manager_.pending_timer_transmission_count() > 0) {
- // Force sending the retransmissions for HANDSHAKE, TLP, RTO, PROBING cases.
+ // Allow sending if there are pending tokens, which occurs when:
+ // 1) firing PTO,
+ // 2) bundling CRYPTO data with ACKs,
+ // 3) coalescing CRYPTO data of higher space.
return true;
}
+ if (donot_check_amplification_limit_with_pending_timer_credit) {
+ QUIC_RELOADABLE_FLAG_COUNT(
+ quic_donot_check_amplification_limit_with_pending_timer_credit);
+ if (LimitedByAmplificationFactor()) {
+ // Server is constrained by the amplification restriction.
+ QUIC_CODE_COUNT(quic_throttled_by_amplification_limit);
+ QUIC_DVLOG(1)
+ << ENDPOINT
+ << "Constrained by amplification restriction to peer address "
+ << default_path_.peer_address << " bytes received "
+ << default_path_.bytes_received_before_address_validation
+ << ", bytes sent"
+ << default_path_.bytes_sent_before_address_validation;
+ ++stats_.num_amplification_throttling;
+ return false;
+ }
+ }
+
if (HandleWriteBlocked()) {
return false;
}
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index a6ed2a1..8879a32 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -763,7 +763,6 @@
std::unique_ptr<QuicDecrypter> decrypter) {
if (connection_.version().KnowsWhichDecrypterToUse()) {
connection_.InstallDecrypter(level, std::move(decrypter));
- connection_.RemoveDecrypter(ENCRYPTION_INITIAL);
} else {
connection_.SetDecrypter(level, std::move(decrypter));
}
@@ -898,7 +897,6 @@
connection_.InstallDecrypter(
QuicPacketCreatorPeer::GetEncryptionLevel(&peer_creator_),
std::make_unique<StrictTaggingDecrypter>(0x01));
- connection_.RemoveDecrypter(ENCRYPTION_INITIAL);
} else {
connection_.SetDecrypter(
QuicPacketCreatorPeer::GetEncryptionLevel(&peer_creator_),
@@ -15560,6 +15558,94 @@
}
}
+// Regression test for b/201643321.
+TEST_P(QuicConnectionTest, FailedToRetransmitShlo) {
+ 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);
+ EXPECT_TRUE(connection_.HasPendingAcks());
+
+ 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>(0x02));
+ SetDecrypter(ENCRYPTION_ZERO_RTT,
+ std::make_unique<StrictTaggingDecrypter>(0x02));
+ connection_.SetEncrypter(ENCRYPTION_FORWARD_SECURE,
+ std::make_unique<TaggingEncrypter>(0x04));
+ // Received ENCRYPTION_ZERO_RTT 1.
+ ProcessDataPacketAtLevel(1, !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("foo", 0, ENCRYPTION_HANDSHAKE);
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ // Send half RTT data to exhaust amplification credit.
+ connection_.SendStreamDataWithString(0, std::string(100 * 1024, 'a'), 0,
+ NO_FIN);
+ }
+ // Received INITIAL 2.
+ ProcessCryptoPacketAtLevel(2, ENCRYPTION_INITIAL);
+ ASSERT_TRUE(connection_.HasPendingAcks());
+ // Verify ACK delay is 1ms.
+ EXPECT_EQ(clock_.Now() + kAlarmGranularity,
+ connection_.GetAckAlarm()->deadline());
+ if (!GetQuicReloadableFlag(
+ quic_donot_check_amplification_limit_with_pending_timer_credit)) {
+ // ACK is not sent because of amplification limit throttled.
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(0);
+ } else {
+ // ACK is not throttled by amplification limit, and SHLO is bundled. Also
+ // HANDSHAKE packet gets coalesced.
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(2);
+ }
+ // ACK alarm fires.
+ clock_.AdvanceTime(kAlarmGranularity);
+ connection_.GetAckAlarm()->Fire();
+ if (GetQuicReloadableFlag(
+ quic_donot_check_amplification_limit_with_pending_timer_credit)) {
+ // 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());
+ EXPECT_EQ(1u, writer_->crypto_frames().size());
+ // Process the coalesced HANDSHAKE packet.
+ ASSERT_TRUE(writer_->coalesced_packet() != nullptr);
+ auto packet = writer_->coalesced_packet()->Clone();
+ writer_->framer()->ProcessPacket(*packet);
+ EXPECT_EQ(0u, writer_->ack_frames().size());
+ EXPECT_EQ(1u, writer_->crypto_frames().size());
+ ASSERT_TRUE(writer_->coalesced_packet() == nullptr);
+ }
+
+ // Received INITIAL 3.
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(AnyNumber());
+ ProcessCryptoPacketAtLevel(3, ENCRYPTION_INITIAL);
+ if (!GetQuicReloadableFlag(
+ quic_donot_check_amplification_limit_with_pending_timer_credit)) {
+ EXPECT_FALSE(connection_.HasPendingAcks());
+ } else {
+ EXPECT_TRUE(connection_.HasPendingAcks());
+ }
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index 5105d90..d625fd0 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -15,6 +15,8 @@
QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_false, false)
// A testonly restart flag that will always default to true.
QUIC_FLAG(FLAGS_quic_restart_flag_quic_testonly_default_true, true)
+// Donot check amplification limit if there is available pending_timer_transmission_count.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_donot_check_amplification_limit_with_pending_timer_credit, false)
// If bytes in flight has dipped below 1.25*MaxBW in the last round, do not exit PROBE_UP due to excess queue buildup.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_no_probe_up_exit_if_no_queue, true)
// If true and connection option B201 is used, check if cwnd limited before aggregation epoch, instead of ack event, in PROBE_UP phase.