Implement Connection ID rotation. Every 20 hours, GfeQuicConnection will check if the CID generator is encrypting, and if so it will retire all existing IDs and issue new ones. The machinery is in QUICHE QuicConnection, because it would be useful for other QUICHE deployments that use encrypted connection IDs. However, it is inactive in Quiche because only GfeQuicConnection sets the timer. With the flag off, QuicConnectionIdManager::retire_prior_to_ is always zero and therefore, the NEW_CONNECTION_ID retire_prior_to field is always set to active_connection_ids_.front().second, as in tip of tree. Thus this is a no-op with the flag disabled. Protected by FLAGS_quic_restart_flag_quic_lb_dev. PiperOrigin-RevId: 1002648349
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 5feb5e4..4ee3e96 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -70,6 +70,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_use_received_client_addresses_cache, true, true, "If true, use a LRU cache to record client addresses of packets received on server's original address.") QUICHE_FLAG(bool, quiche_restart_flag_quic_client_cert_support, false, true, "If true, enables dynamic client certs in QUIC.") QUICHE_FLAG(bool, quiche_restart_flag_quic_dispatcher_close_connection_on_invalid_ack, false, false, "An invalid ack is an ack that the peer sent for a packet that was not sent by the dispatcher. If true, the dispatcher will close the connection if it receives an invalid ack.") +QUICHE_FLAG(bool, quiche_restart_flag_quic_lb_dev, false, false, "Protects GFE code in development for the QUIC-LB project. Will never be enabled. When ready to rollout, will be replaced by a new restart flag") QUICHE_FLAG(bool, quiche_restart_flag_quic_support_release_time_for_gso, false, false, "If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.") QUICHE_FLAG(bool, quiche_restart_flag_quic_testonly_default_false, false, false, "A testonly restart flag that will always default to false.") QUICHE_FLAG(bool, quiche_restart_flag_quic_testonly_default_true, true, true, "A testonly restart flag that will always default to true.")
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 4edbe61..a3c3ccf 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -4999,6 +4999,40 @@ self_issued_cid_manager_.reset(); } +void QuicConnection::SetConnectionIdRotationAlarm(QuicTime::Delta interval) { + cid_rotation_interval_ = interval; + if (connection_id_rotation_alarm().IsSet()) { + if (interval.IsZero()) { + connection_id_rotation_alarm().Cancel(); + } + return; + } + connection_id_rotation_alarm().Set(clock_->ApproximateNow() + interval); +} + +void QuicConnection::OnConnectionIdRotationAlarm() { + if (!connected_) { + return; + } + if (self_issued_cid_manager_ != nullptr) { + if (self_issued_cid_manager_->num_active_connection_ids() > + self_issued_cid_manager_->active_connection_id_limit()) { + CloseConnection(QUIC_CONNECTION_ID_LIMIT_ERROR, + "Peer failed to retire connection IDs.", + ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return; + } + if (connection_id_generator_.IsEncrypted()) { + ScopedPacketFlusher flusher(this); + self_issued_cid_manager_->RetireAndSendNewConnectionIds(); + } + } + if (connected_) { + connection_id_rotation_alarm().Set(clock_->ApproximateNow() + + cid_rotation_interval_); + } +} + void QuicConnection::CancelAllAlarms() { QUIC_DVLOG(1) << "Cancelling all QuicConnection alarms."; @@ -5017,6 +5051,7 @@ multi_port_probing_alarm().PermanentCancel(); blackhole_detector_.StopDetection(/*permanent=*/true); idle_network_detector_.StopDetection(); + connection_id_rotation_alarm().PermanentCancel(); } QuicByteCount QuicConnection::max_packet_length() const {
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index a136cbb..e8a8d90 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -960,6 +960,13 @@ multi_port_probing_interval_ = probing_interval; } + // Starts a repeating timer to rotate self-issued connection IDs at the given + // `interval`. + void SetConnectionIdRotationAlarm(QuicTime::Delta interval); + + // Called when the connection ID rotation alarm fires. + void OnConnectionIdRotationAlarm() override; + const MultiPortStats* multi_port_stats() const { return multi_port_stats_.get(); } @@ -2193,6 +2200,9 @@ QuicAlarmProxy multi_port_probing_alarm() { return QuicAlarmProxy(&alarms_, QuicAlarmSlot::kMultiPortProbing); } + QuicAlarmProxy connection_id_rotation_alarm() { + return QuicAlarmProxy(&alarms_, QuicAlarmSlot::kConnectionIdRotation); + } // Extract destination connection ID from ReceivedPacketInfo. inline QuicConnectionId GetDestinationConnectionId( @@ -2414,6 +2424,14 @@ ConnectionIdGeneratorInterface& connection_id_generator_; + // Interval for rotating self-issued connection IDs. When nonzero, + // after cid_rotation_interval_ the connection will check if the + // connection_id_generator is encrypting. If so, it will ask the peer to + // retire all existing connection IDs and issue new ones. If the peer has + // not retired connection IDs since the previous request, it will close the + // connection. + QuicTime::Delta cid_rotation_interval_ = QuicTime::Delta::Zero(); + // This LRU cache records source addresses of packets received on server's // original address. QuicLRUCache<QuicSocketAddress, bool, QuicSocketAddressHash>
diff --git a/quiche/quic/core/quic_connection_alarms.cc b/quiche/quic/core/quic_connection_alarms.cc index d1361d9..764dee8 100644 --- a/quiche/quic/core/quic_connection_alarms.cc +++ b/quiche/quic/core/quic_connection_alarms.cc
@@ -88,6 +88,8 @@ return "NetworkBlackholeDetector"; case QuicAlarmSlot::kPing: return "Ping"; + case QuicAlarmSlot::kConnectionIdRotation: + return "ConnectionIdRotation"; case QuicAlarmSlot::kSlotCount: break; } @@ -100,7 +102,7 @@ : deadlines_({QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero(), - QuicTime::Zero(), QuicTime::Zero()}), + QuicTime::Zero(), QuicTime::Zero(), QuicTime::Zero()}), now_alarm_(alarm_factory.CreateAlarm( arena.New<MultiplexerAlarmDelegate>(this), &arena)), later_alarm_(alarm_factory.CreateAlarm( @@ -260,6 +262,9 @@ case QuicAlarmSlot::kPing: connection_->OnPingAlarm(); return; + case QuicAlarmSlot::kConnectionIdRotation: + connection_->OnConnectionIdRotationAlarm(); + return; case QuicAlarmSlot::kSlotCount: break; }
diff --git a/quiche/quic/core/quic_connection_alarms.h b/quiche/quic/core/quic_connection_alarms.h index 2bb9d22..69c417e 100644 --- a/quiche/quic/core/quic_connection_alarms.h +++ b/quiche/quic/core/quic_connection_alarms.h
@@ -37,6 +37,7 @@ virtual void OnIdleDetectorAlarm() = 0; virtual void OnNetworkBlackholeDetectorAlarm() = 0; virtual void OnPingAlarm() = 0; + virtual void OnConnectionIdRotationAlarm() = 0; virtual QuicConnectionContext* context() = 0; virtual const QuicClock* clock() const = 0; @@ -75,6 +76,8 @@ kNetworkBlackholeDetector, // An alarm for QuicPingManager. kPing, + // An alarm for QuicConnectionIdManager. + kConnectionIdRotation, // Must be the last element. kSlotCount
diff --git a/quiche/quic/core/quic_connection_id_manager.cc b/quiche/quic/core/quic_connection_id_manager.cc index 7c6a4bc..bb90f50 100644 --- a/quiche/quic/core/quic_connection_id_manager.cc +++ b/quiche/quic/core/quic_connection_id_manager.cc
@@ -11,6 +11,7 @@ #include <utility> #include <vector> +#include "quiche/quic/core/frames/quic_new_connection_id_frame.h" #include "quiche/quic/core/quic_clock.h" #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_error_codes.h" @@ -321,7 +322,16 @@ QuicUtils::GenerateStatelessResetToken(frame.connection_id); active_connection_ids_.emplace_back(frame.connection_id, frame.sequence_number); - frame.retire_prior_to = active_connection_ids_.front().second; + if (!GetQuicRestartFlag(quic_lb_dev)) { + frame.retire_prior_to = active_connection_ids_.front().second; + } else { + QUIC_RESTART_FLAG_COUNT_N(quic_lb_dev, 8, 8); + const size_t num_active = active_connection_ids_.size(); + const size_t oldest_to_keep = num_active > active_connection_id_limit_ + ? num_active - active_connection_id_limit_ + : 0; + frame.retire_prior_to = active_connection_ids_[oldest_to_keep].second; + } last_connection_id_ = frame.connection_id; return frame; } @@ -435,6 +445,24 @@ } } +void QuicSelfIssuedConnectionIdManager::RetireAndSendNewConnectionIds() { + if (active_connection_ids_.empty()) { + QUIC_BUG(quic_bug_no_active_self_issued_connection_ids) + << "No active self-issued connection IDs"; + return; + } + for (size_t i = 0; i < active_connection_id_limit_; ++i) { + std::optional<QuicNewConnectionIdFrame> frame = MaybeIssueNewConnectionId(); + if (!frame.has_value()) { + break; + } + + if (!visitor_->SendNewConnectionId(*frame)) { + break; + } + } +} + bool QuicSelfIssuedConnectionIdManager::HasConnectionIdToConsume() const { for (const auto& active_cid_data : active_connection_ids_) { if (active_cid_data.second >
diff --git a/quiche/quic/core/quic_connection_id_manager.h b/quiche/quic/core/quic_connection_id_manager.h index 5276eca..242e2e6 100644 --- a/quiche/quic/core/quic_connection_id_manager.h +++ b/quiche/quic/core/quic_connection_id_manager.h
@@ -11,6 +11,7 @@ #define QUICHE_QUIC_CORE_QUIC_CONNECTION_ID_MANAGER_H_ #include <cstddef> +#include <cstdint> #include <memory> #include <optional> @@ -23,7 +24,7 @@ #include "quiche/quic/core/quic_connection_id.h" #include "quiche/quic/core/quic_interval_set.h" #include "quiche/quic/core/quic_types.h" -#include "quiche/quic/platform/api/quic_export.h" +#include "quiche/common/platform/api/quiche_export.h" namespace quic { @@ -161,6 +162,18 @@ // Sends new connection IDs if more can be sent. void MaybeSendNewConnectionIds(); + // Issues active_connection_id_limit_ new connection IDs, each incrementing + // retire_prior_to by one. + void RetireAndSendNewConnectionIds(); + + size_t num_active_connection_ids() const { + return active_connection_ids_.size(); + } + + size_t active_connection_id_limit() const { + return active_connection_id_limit_; + } + // The two functions are called on the client side to associate a client // connection ID with a new probing/migration path when client uses // non-empty connection ID.
diff --git a/quiche/quic/core/quic_connection_id_manager_test.cc b/quiche/quic/core/quic_connection_id_manager_test.cc index 9f6a2da..b3ff4ff 100644 --- a/quiche/quic/core/quic_connection_id_manager_test.cc +++ b/quiche/quic/core/quic_connection_id_manager_test.cc
@@ -1102,5 +1102,35 @@ << error_details; } +TEST_F(QuicSelfIssuedConnectionIdManagerTest, RetireAndSendNewConnectionIds) { + SetQuicRestartFlag(quic_lb_dev, true); + QuicConnectionId cid0 = initial_connection_id_; + QuicConnectionId cid1 = CheckGenerate(cid0); + QuicConnectionId cid2 = CheckGenerate(cid1); + QuicConnectionId cid3 = CheckGenerate(cid2); + + EXPECT_CALL(cid_manager_visitor_, MaybeReserveConnectionId(cid1)) + .WillOnce(Return(true)); + EXPECT_CALL(cid_manager_visitor_, + SendNewConnectionId(ExpectedNewConnectionIdFrame(cid1, 1u, 0u))) + .WillOnce(Return(true)); + cid_manager_.MaybeSendNewConnectionIds(); + EXPECT_EQ(cid_manager_.num_active_connection_ids(), 2u); + EXPECT_EQ(cid_manager_.active_connection_id_limit(), 2u); + + EXPECT_CALL(cid_manager_visitor_, MaybeReserveConnectionId(cid2)) + .WillOnce(Return(true)); + EXPECT_CALL(cid_manager_visitor_, + SendNewConnectionId(ExpectedNewConnectionIdFrame(cid2, 2u, 1u))) + .WillOnce(Return(true)); + EXPECT_CALL(cid_manager_visitor_, MaybeReserveConnectionId(cid3)) + .WillOnce(Return(true)); + EXPECT_CALL(cid_manager_visitor_, + SendNewConnectionId(ExpectedNewConnectionIdFrame(cid3, 3u, 2u))) + .WillOnce(Return(true)); + cid_manager_.RetireAndSendNewConnectionIds(); + EXPECT_EQ(cid_manager_.num_active_connection_ids(), 4u); +} + } // namespace } // namespace quic::test
diff --git a/quiche/quic/test_tools/mock_quic_connection_alarms.h b/quiche/quic/test_tools/mock_quic_connection_alarms.h index 8c29ee7..de79b27 100644 --- a/quiche/quic/test_tools/mock_quic_connection_alarms.h +++ b/quiche/quic/test_tools/mock_quic_connection_alarms.h
@@ -26,6 +26,7 @@ MOCK_METHOD(void, OnIdleDetectorAlarm, (), (override)); MOCK_METHOD(void, OnNetworkBlackholeDetectorAlarm, (), (override)); MOCK_METHOD(void, OnPingAlarm, (), (override)); + MOCK_METHOD(void, OnConnectionIdRotationAlarm, (), (override)); QuicConnectionContext* context() override { return nullptr; } const QuicClock* clock() const override { return &clock_; }
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc index a51fb8f..c30a98e 100644 --- a/quiche/quic/test_tools/quic_connection_peer.cc +++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -197,6 +197,13 @@ } // static +QuicAlarmProxy QuicConnectionPeer::GetConnectionIdRotationAlarm( + QuicConnection* connection) { + return QuicAlarmProxy(&connection->alarms_, + QuicAlarmSlot::kConnectionIdRotation); +} + +// static QuicPacketWriter* QuicConnectionPeer::GetWriter(QuicConnection* connection) { return connection->writer_; }
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h index 9d999b1..7ff6b4e 100644 --- a/quiche/quic/test_tools/quic_connection_peer.h +++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -111,6 +111,8 @@ QuicConnection* connection); static QuicAlarm* GetRetireSelfIssuedConnectionIdAlarm( QuicConnection* connection); + static QuicAlarmProxy GetConnectionIdRotationAlarm( + QuicConnection* connection); static QuicPacketWriter* GetWriter(QuicConnection* connection); // If |owns_writer| is true, takes ownership of |writer|.