QUIC+TLS server should temporarily retain 0-RTT keys so that re-ordered packets can be decoded.
Protected by FLAGS_quic_restart_flag_quic_server_temporarily_retain_tls_zero_rtt_keys.
PiperOrigin-RevId: 345557425
Change-Id: I27f0a13c98d7362ed702de26c33215252e686d2b
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index d7abc1a..825a0e9 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -191,6 +191,25 @@
QuicConnection* connection_;
};
+class DiscardZeroRttDecryptionKeysAlarmDelegate : public QuicAlarm::Delegate {
+ public:
+ explicit DiscardZeroRttDecryptionKeysAlarmDelegate(QuicConnection* connection)
+ : connection_(connection) {}
+ DiscardZeroRttDecryptionKeysAlarmDelegate(
+ const DiscardZeroRttDecryptionKeysAlarmDelegate&) = delete;
+ DiscardZeroRttDecryptionKeysAlarmDelegate& operator=(
+ const DiscardZeroRttDecryptionKeysAlarmDelegate&) = delete;
+
+ void OnAlarm() override {
+ DCHECK(connection_->connected());
+ QUIC_DLOG(INFO) << "0-RTT discard alarm fired";
+ connection_->RemoveDecrypter(ENCRYPTION_ZERO_RTT);
+ }
+
+ private:
+ QuicConnection* connection_;
+};
+
// When the clearer goes out of scope, the coalesced packet gets cleared.
class ScopedCoalescedPacketClearer {
public:
@@ -305,6 +324,9 @@
discard_previous_one_rtt_keys_alarm_(alarm_factory_->CreateAlarm(
arena_.New<DiscardPreviousOneRttKeysAlarmDelegate>(this),
&arena_)),
+ discard_zero_rtt_decryption_keys_alarm_(alarm_factory_->CreateAlarm(
+ arena_.New<DiscardZeroRttDecryptionKeysAlarmDelegate>(this),
+ &arena_)),
visitor_(nullptr),
debug_visitor_(nullptr),
packet_creator_(server_connection_id_, &framer_, random_generator_, this),
@@ -427,6 +449,17 @@
delete writer_;
}
ClearQueuedPackets();
+ if (stats_
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter >
+ 0) {
+ QUIC_CODE_COUNT_N(
+ quic_server_received_tls_zero_rtt_packet_after_discarding_decrypter, 2,
+ 3);
+ } else {
+ QUIC_CODE_COUNT_N(
+ quic_server_received_tls_zero_rtt_packet_after_discarding_decrypter, 3,
+ 3);
+ }
}
void QuicConnection::ClearQueuedPackets() {
@@ -1150,6 +1183,22 @@
EncryptionLevel level) {
last_decrypted_packet_level_ = level;
last_packet_decrypted_ = true;
+ if (level == ENCRYPTION_FORWARD_SECURE &&
+ !have_decrypted_first_one_rtt_packet_) {
+ have_decrypted_first_one_rtt_packet_ = true;
+ if (GetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys) &&
+ version().UsesTls() && perspective_ == Perspective::IS_SERVER) {
+ // Servers MAY temporarily retain 0-RTT keys to allow decrypting reordered
+ // packets without requiring their contents to be retransmitted with 1-RTT
+ // keys. After receiving a 1-RTT packet, servers MUST discard 0-RTT keys
+ // within a short time; the RECOMMENDED time period is three times the
+ // Probe Timeout.
+ // https://quicwg.org/base-drafts/draft-ietf-quic-tls.html#name-discarding-0-rtt-keys
+ QUIC_RESTART_FLAG_COUNT(quic_server_temporarily_retain_tls_zero_rtt_keys);
+ discard_zero_rtt_decryption_keys_alarm_->Set(
+ clock_->ApproximateNow() + sent_packet_manager_.GetPtoDelay() * 3);
+ }
+ }
if (EnforceAntiAmplificationLimit() &&
(last_decrypted_packet_level_ == ENCRYPTION_HANDSHAKE ||
last_decrypted_packet_level_ == ENCRYPTION_FORWARD_SECURE)) {
@@ -2246,6 +2295,16 @@
}
}
}
+
+ if (version().UsesTls() && perspective_ == Perspective::IS_SERVER &&
+ decryption_level == ENCRYPTION_ZERO_RTT && !has_decryption_key &&
+ had_zero_rtt_decrypter_) {
+ QUIC_CODE_COUNT_N(
+ quic_server_received_tls_zero_rtt_packet_after_discarding_decrypter, 1,
+ 3);
+ stats_
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter++;
+ }
}
bool QuicConnection::ShouldEnqueueUnDecryptablePacket(
@@ -3689,6 +3748,9 @@
void QuicConnection::InstallDecrypter(
EncryptionLevel level,
std::unique_ptr<QuicDecrypter> decrypter) {
+ if (level == ENCRYPTION_ZERO_RTT) {
+ had_zero_rtt_decrypter_ = true;
+ }
framer_.InstallDecrypter(level, std::move(decrypter));
if (!undecryptable_packets_.empty() &&
!process_undecryptable_packets_alarm_->IsSet()) {
@@ -4048,6 +4110,7 @@
mtu_discovery_alarm_->Cancel();
process_undecryptable_packets_alarm_->Cancel();
discard_previous_one_rtt_keys_alarm_->Cancel();
+ discard_zero_rtt_decryption_keys_alarm_->Cancel();
blackhole_detector_.StopDetection();
idle_network_detector_.StopDetection();
}
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h
index 327a696..8cc3a4b 100644
--- a/quic/core/quic_connection.h
+++ b/quic/core/quic_connection.h
@@ -1719,6 +1719,10 @@
// An alarm that fires to discard keys for the previous key phase some time
// after a key update has completed.
QuicArenaScopedPtr<QuicAlarm> discard_previous_one_rtt_keys_alarm_;
+ // An alarm that fires to discard 0-RTT decryption keys some time after the
+ // first 1-RTT packet has been decrypted. Only used on server connections with
+ // TLS handshaker.
+ QuicArenaScopedPtr<QuicAlarm> discard_zero_rtt_decryption_keys_alarm_;
// Neither visitor is owned by this class.
QuicConnectionVisitorInterface* visitor_;
QuicConnectionDebugVisitor* debug_visitor_;
@@ -1956,6 +1960,13 @@
// Indicate whether AckFrequency frame has been sent.
bool ack_frequency_sent_ = false;
+ // True if a 0-RTT decrypter was or is installed at some point in the
+ // connection's lifetime.
+ bool had_zero_rtt_decrypter_ = false;
+
+ // True after the first 1-RTT packet has successfully decrypted.
+ bool have_decrypted_first_one_rtt_packet_ = false;
+
const bool fix_missing_initial_keys_ =
GetQuicReloadableFlag(quic_fix_missing_initial_keys2);
diff --git a/quic/core/quic_connection_stats.cc b/quic/core/quic_connection_stats.cc
index d7440b4..a693677 100644
--- a/quic/core/quic_connection_stats.cc
+++ b/quic/core/quic_connection_stats.cc
@@ -58,6 +58,8 @@
os << " key_update_count: " << s.key_update_count;
os << " num_failed_authentication_packets_received: "
<< s.num_failed_authentication_packets_received;
+ os << " num_tls_server_zero_rtt_packets_received_after_discarding_decrypter: "
+ << s.num_tls_server_zero_rtt_packets_received_after_discarding_decrypter;
os << " }";
return os;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 95cf61e..409e8ec 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -178,6 +178,11 @@
// Counts the number of undecryptable packets received across all keys. Does
// not include packets where a decryption key for that level was absent.
QuicPacketCount num_failed_authentication_packets_received = 0;
+
+ // Counts the number of QUIC+TLS 0-RTT packets received after 0-RTT decrypter
+ // was discarded, only on server connections.
+ QuicPacketCount
+ num_tls_server_zero_rtt_packets_received_after_discarding_decrypter = 0;
};
} // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index b3dc2d8..e06eb86 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -462,6 +462,11 @@
QuicConnectionPeer::GetDiscardPreviousOneRttKeysAlarm(this));
}
+ TestAlarmFactory::TestAlarm* GetDiscardZeroRttDecryptionKeysAlarm() {
+ return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
+ QuicConnectionPeer::GetDiscardZeroRttDecryptionKeysAlarm(this));
+ }
+
TestAlarmFactory::TestAlarm* GetBlackholeDetectorAlarm() {
return reinterpret_cast<TestAlarmFactory::TestAlarm*>(
QuicConnectionPeer::GetBlackholeDetectorAlarm(this));
@@ -12879,6 +12884,102 @@
}
}
+TEST_P(QuicConnectionTest,
+ ServerReceivedZeroRttPacketAfterOneRttPacketWithoutRetainedKey) {
+ SetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys, false);
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ set_perspective(Perspective::IS_SERVER);
+ SetDecrypter(ENCRYPTION_ZERO_RTT,
+ std::make_unique<NullDecrypter>(Perspective::IS_SERVER));
+
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+
+ // Finish handshake.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ notifier_.NeuterUnencryptedData();
+ connection_.NeuterUnencryptedPackets();
+ connection_.OnHandshakeComplete();
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_COMPLETE));
+ // When quic_server_temporarily_retain_tls_zero_rtt_keys=false,
+ // TlsServerHandshaker::FinishHandshake will remove the ENCRYPTION_ZERO_RTT
+ // decrypter, simulate that here:
+ connection_.RemoveDecrypter(ENCRYPTION_ZERO_RTT);
+
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+ EXPECT_FALSE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet());
+ EXPECT_EQ(
+ 0u,
+ connection_.GetStats()
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter);
+
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(0);
+ ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+ EXPECT_EQ(
+ 1u,
+ connection_.GetStats()
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter);
+}
+
+TEST_P(QuicConnectionTest,
+ ServerReceivedZeroRttPacketAfterOneRttPacketWithRetainedKey) {
+ SetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys, true);
+ if (!connection_.version().UsesTls()) {
+ return;
+ }
+
+ set_perspective(Perspective::IS_SERVER);
+ SetDecrypter(ENCRYPTION_ZERO_RTT,
+ std::make_unique<NullDecrypter>(Perspective::IS_SERVER));
+
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+
+ // Finish handshake.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+ notifier_.NeuterUnencryptedData();
+ connection_.NeuterUnencryptedPackets();
+ connection_.OnHandshakeComplete();
+ EXPECT_CALL(visitor_, GetHandshakeState())
+ .WillRepeatedly(Return(HANDSHAKE_COMPLETE));
+
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(4, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+ EXPECT_TRUE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet());
+
+ // 0-RTT packet received out of order should be decoded since the decrypter
+ // is temporarily retained.
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+ EXPECT_EQ(
+ 0u,
+ connection_.GetStats()
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter);
+
+ // Simulate the timeout for discarding 0-RTT keys passing.
+ connection_.GetDiscardZeroRttDecryptionKeysAlarm()->Fire();
+
+ // Another 0-RTT packet received now should not be decoded.
+ EXPECT_FALSE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet());
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(0);
+ ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_ZERO_RTT);
+ EXPECT_EQ(
+ 1u,
+ connection_.GetStats()
+ .num_tls_server_zero_rtt_packets_received_after_discarding_decrypter);
+
+ // The |discard_zero_rtt_decryption_keys_alarm_| should only be set on the
+ // first 1-RTT packet received.
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+ ProcessDataPacketAtLevel(5, !kHasStopWaiting, ENCRYPTION_FORWARD_SECURE);
+ EXPECT_FALSE(connection_.GetDiscardZeroRttDecryptionKeysAlarm()->IsSet());
+}
+
} // namespace
} // namespace test
} // namespace quic
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index f9530bb..7a8bde0 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -68,6 +68,7 @@
QUIC_FLAG(FLAGS_quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false)
QUIC_FLAG(FLAGS_quic_restart_flag_quic_enable_zero_rtt_for_tls_v2, true)
QUIC_FLAG(FLAGS_quic_restart_flag_quic_offload_pacing_to_usps2, false)
+QUIC_FLAG(FLAGS_quic_restart_flag_quic_server_temporarily_retain_tls_zero_rtt_keys, false)
QUIC_FLAG(FLAGS_quic_restart_flag_quic_session_tickets_always_enabled, true)
QUIC_FLAG(FLAGS_quic_restart_flag_quic_startup_faster_interval_set, false)
QUIC_FLAG(FLAGS_quic_restart_flag_quic_support_release_time_for_gso, false)
diff --git a/quic/core/quic_one_block_arena.h b/quic/core/quic_one_block_arena.h
index d821803..15c0a99 100644
--- a/quic/core/quic_one_block_arena.h
+++ b/quic/core/quic_one_block_arena.h
@@ -75,7 +75,7 @@
// QuicConnections currently use around 1KB of polymorphic types which would
// ordinarily be on the heap. Instead, store them inline in an arena.
-using QuicConnectionArena = QuicOneBlockArena<1024>;
+using QuicConnectionArena = QuicOneBlockArena<1056>;
} // namespace quic
diff --git a/quic/core/quic_path_validator.cc b/quic/core/quic_path_validator.cc
index 04d1be2..e24e053 100644
--- a/quic/core/quic_path_validator.cc
+++ b/quic/core/quic_path_validator.cc
@@ -30,7 +30,7 @@
}
QuicPathValidator::QuicPathValidator(QuicAlarmFactory* alarm_factory,
- QuicOneBlockArena<1024>* arena,
+ QuicConnectionArena* arena,
SendDelegate* send_delegate,
QuicRandom* random)
: send_delegate_(send_delegate),
diff --git a/quic/core/tls_server_handshaker.cc b/quic/core/tls_server_handshaker.cc
index 3d0d8dc..93f6c58 100644
--- a/quic/core/tls_server_handshaker.cc
+++ b/quic/core/tls_server_handshaker.cc
@@ -404,7 +404,15 @@
handshaker_delegate()->OnTlsHandshakeComplete();
handshaker_delegate()->DiscardOldEncryptionKey(ENCRYPTION_HANDSHAKE);
handshaker_delegate()->DiscardOldDecryptionKey(ENCRYPTION_HANDSHAKE);
- handshaker_delegate()->DiscardOldDecryptionKey(ENCRYPTION_ZERO_RTT);
+ if (!GetQuicRestartFlag(quic_server_temporarily_retain_tls_zero_rtt_keys)) {
+ handshaker_delegate()->DiscardOldDecryptionKey(ENCRYPTION_ZERO_RTT);
+ } else {
+ // ENCRYPTION_ZERO_RTT decryption key is not discarded here as "Servers MAY
+ // temporarily retain 0-RTT keys to allow decrypting reordered packets
+ // without requiring their contents to be retransmitted with 1-RTT keys."
+ // It is expected that QuicConnection will discard the key at an
+ // appropriate time.
+ }
}
QuicAsyncStatus TlsServerHandshaker::VerifyCertChain(