Validate the correct stateless reset token on the alternate path. Currently, QuicConnection always compares the incoming potential stateless reset token against the primary path token, even if it's for a path probe that uses a new connection ID. Consequently, valid stateless resets are discarded and path probes time out instead of failing immediately. Protected by FLAGS_quic_reloadable_flag_quic_check_alternate_reset_token. PiperOrigin-RevId: 917954928
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index 08494c5..7075419 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -16,6 +16,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_bbr_always_exit_startup_on_loss, false, false, "Lets BBRv1 exit STARTUP if a large number of packets was lost even if there is no non-app-limited samples. Similar to applying connection option B1AL to all connection.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_bbr_exit_startup_on_loss_network_param, false, true, "Enables adjustment of BBRv1's exit-startup-on-loss behavior on established connections via NetworkParams::enable_bbr_exit_startup_on_loss.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_block_until_settings_received_copt, true, true, "If enabled and a BSUS connection is received, blocks server connections until SETTINGS frame is received.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_check_alternate_reset_token, false, false, "When true, checks the stateless reset token on a path probe using the alternate path's token") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_clear_body_manager_along_with_sequencer, true, true, "If true, QuicSpdyStream::StopReading always clears BodyManager along with the SequenceBuffer.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_clear_packet_on_serialization_failure, false, false, "If true, clear QuicPacketCreator state when serialization failure.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_client_check_blockage_before_on_can_write, false, false, "If true, quic clients will only call OnCanWrite() upon write events if the writer is unblocked.")
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 55b74d2..a7edde6 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -2392,9 +2392,27 @@ bool QuicConnection::IsValidStatelessResetToken( const StatelessResetToken& token) const { QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT); - return default_path_.stateless_reset_token.has_value() && - QuicUtils::AreStatelessResetTokensEqual( - token, *default_path_.stateless_reset_token); + if (GetQuicReloadableFlag(quic_check_alternate_reset_token)) { + if (IsDefaultPath(last_received_packet_info_.destination_address, + last_received_packet_info_.source_address)) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_check_alternate_reset_token, 1, 2); + return default_path_.stateless_reset_token.has_value() && + QuicUtils::AreStatelessResetTokensEqual( + token, *default_path_.stateless_reset_token); + } + if (IsAlternativePath(last_received_packet_info_.destination_address, + GetEffectivePeerAddressFromCurrentPacket())) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_check_alternate_reset_token, 2, 2); + return alternative_path_.stateless_reset_token.has_value() && + QuicUtils::AreStatelessResetTokensEqual( + token, *alternative_path_.stateless_reset_token); + } + return false; + } else { + return default_path_.stateless_reset_token.has_value() && + QuicUtils::AreStatelessResetTokensEqual( + token, *default_path_.stateless_reset_token); + } } void QuicConnection::OnAuthenticatedIetfStatelessResetPacket(
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc index 0aa8e19..162ef31 100644 --- a/quiche/quic/core/quic_connection_test.cc +++ b/quiche/quic/core/quic_connection_test.cc
@@ -6785,6 +6785,7 @@ QuicConfig config; QuicConfigPeer::SetReceivedStatelessResetToken(&config, kTestStatelessResetToken); + SetQuicReloadableFlag(quic_check_alternate_reset_token, true); EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); EXPECT_CALL(*send_algorithm_, EnableECT1()).WillOnce(Return(false)); EXPECT_CALL(*send_algorithm_, EnableECT0()).WillOnce(Return(false)); @@ -6803,6 +6804,73 @@ IsError(QUIC_PUBLIC_RESET)); } +TEST_P(QuicConnectionTest, StatelessResetIgnoredIfFromUnknownAddress) { + if (!VersionIsIetfQuic(connection_.version().transport_version)) { + return; + } + SetQuicReloadableFlag(quic_check_alternate_reset_token, true); + PathProbeTestInit(Perspective::IS_CLIENT); + QuicConfig config; + QuicConfigPeer::SetReceivedStatelessResetToken(&config, + kTestStatelessResetToken); + EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)); + EXPECT_CALL(*send_algorithm_, EnableECT1()).WillOnce(Return(false)); + EXPECT_CALL(*send_algorithm_, EnableECT0()).WillOnce(Return(false)); + connection_.SetFromConfig(config); + + // Start validating alternative path to set up alternative_path_. + const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Any4(), 12345); + EXPECT_NE(kNewSelfAddress, connection_.self_address()); + TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT); + EXPECT_CALL(*send_algorithm_, OnPacketSent).Times(AtLeast(1u)); + bool success = true; + connection_.ValidatePath( + std::make_unique<TestQuicPathValidationContext>( + kNewSelfAddress, connection_.peer_address(), &new_writer), + std::make_unique<TestValidationResultDelegate>( + &connection_, kNewSelfAddress, connection_.peer_address(), &success), + PathValidationReason::kReasonUnknown); + EXPECT_TRUE(connection_.HasPendingPathValidation()); + // Verify both default path and alternative path have set up stateless reset + // tokens. + EXPECT_TRUE(QuicConnectionPeer::GetAlternativePath(&connection_) + ->stateless_reset_token.has_value()); + + // A packet with the default path's valid stateless reset token arrives from a + // third address (unknown address) that is neither the default peer address + // nor the alternative peer address. + std::unique_ptr<QuicEncryptedPacket> packet( + QuicFramer::BuildIetfStatelessResetPacket(connection_id_, + /*received_packet_length=*/100, + kTestStatelessResetToken)); + std::unique_ptr<QuicReceivedPacket> received( + ConstructReceivedPacket(*packet, QuicTime::Zero())); + const QuicSocketAddress kThirdAddress(kPeerAddress.host(), + kPeerAddress.port() + 10); + EXPECT_CALL(visitor_, OnConnectionClosed).Times(0); + connection_.ProcessUdpPacket(kSelfAddress, kThirdAddress, *received); + // Should be ignored. The connection remains connected. + EXPECT_TRUE(connection_.connected()); + + // Same should apply if a packet with the alternative path's valid stateless + // reset token arrives from an unknown address. + std::unique_ptr<QuicEncryptedPacket> packet2( + QuicFramer::BuildIetfStatelessResetPacket( + QuicConnectionPeer::GetAlternativePath(&connection_) + ->server_connection_id, + /*received_packet_length=*/100, + *QuicConnectionPeer::GetAlternativePath(&connection_) + ->stateless_reset_token)); + std::unique_ptr<QuicReceivedPacket> received2( + ConstructReceivedPacket(*packet2, QuicTime::Zero())); + connection_.ProcessUdpPacket(kSelfAddress, kThirdAddress, *received2); + // Should also be ignored. The connection remains connected, and the alternate + // path is unaffected. + EXPECT_TRUE(connection_.connected()); + EXPECT_EQ(connection_.mutable_stats().num_stateless_resets_on_alternate_path, + 0); +} + TEST_P(QuicConnectionTest, GoAway) { if (VersionIsIetfQuic(GetParam().version.transport_version)) { // GoAway is not available in version 99. @@ -8698,6 +8766,10 @@ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(2); EXPECT_CALL(*send_algorithm_, EnableECT1()).WillRepeatedly(Return(false)); EXPECT_CALL(*send_algorithm_, EnableECT0()).WillRepeatedly(Return(false)); + QuicConnectionPeer::GetLastReceivedPacketInfo(&connection_) + .destination_address = connection_.self_address(); + QuicConnectionPeer::GetLastReceivedPacketInfo(&connection_).source_address = + connection_.peer_address(); // Token is different from received token. QuicConfigPeer::SetReceivedStatelessResetToken(&config, kTestToken); connection_.SetFromConfig(config); @@ -12072,6 +12144,7 @@ if (!VersionIsIetfQuic(connection_.version().transport_version)) { return; } + SetQuicReloadableFlag(quic_check_alternate_reset_token, true); PathProbeTestInit(Perspective::IS_CLIENT); QuicConfig config; QuicConfigPeer::SetReceivedStatelessResetToken(&config, @@ -12105,9 +12178,12 @@ EXPECT_TRUE(connection_.HasPendingPathValidation()); std::unique_ptr<QuicEncryptedPacket> packet( - QuicFramer::BuildIetfStatelessResetPacket(connection_id_, - /*received_packet_length=*/100, - kTestStatelessResetToken)); + QuicFramer::BuildIetfStatelessResetPacket( + QuicConnectionPeer::GetAlternativePath(&connection_) + ->server_connection_id, + /*received_packet_length=*/100, + *QuicConnectionPeer::GetAlternativePath(&connection_) + ->stateless_reset_token)); std::unique_ptr<QuicReceivedPacket> received( ConstructReceivedPacket(*packet, QuicTime::Zero())); EXPECT_CALL(visitor_, OnConnectionClosed(_, _)).Times(0); @@ -13849,6 +13925,7 @@ if (!version().IsIetfQuic()) { return; } + SetQuicReloadableFlag(quic_check_alternate_reset_token, true); connection_.CreateConnectionIdManager(); connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); connection_.OnHandshakeComplete(); @@ -13886,9 +13963,9 @@ ->GetPathValidationReason()); std::unique_ptr<QuicEncryptedPacket> packet( - QuicFramer::BuildIetfStatelessResetPacket(connection_id_, + QuicFramer::BuildIetfStatelessResetPacket(frame.connection_id, /*received_packet_length=*/100, - kTestStatelessResetToken)); + frame.stateless_reset_token)); std::unique_ptr<QuicReceivedPacket> received( ConstructReceivedPacket(*packet, QuicTime::Zero())); EXPECT_CALL(visitor_, OnConnectionClosed(_, ConnectionCloseSource::FROM_PEER)) @@ -17195,7 +17272,6 @@ // Verify client retires connection ID with sequence number 1. EXPECT_CALL(visitor_, SendRetireConnectionId(/*sequence_number=*/1u)); retire_peer_issued_cid_alarm->Fire(); - EXPECT_TRUE(connection_.IsValidStatelessResetToken(kTestStatelessResetToken)); EXPECT_FALSE(connection_.GetStats().server_preferred_address_validated); EXPECT_TRUE( connection_.GetStats().failed_to_validate_server_preferred_address);
diff --git a/quiche/quic/test_tools/quic_connection_peer.cc b/quiche/quic/test_tools/quic_connection_peer.cc index 818576e..2cec04f 100644 --- a/quiche/quic/test_tools/quic_connection_peer.cc +++ b/quiche/quic/test_tools/quic_connection_peer.cc
@@ -640,5 +640,11 @@ return connection->spin_bit_enabled_; } +// static +QuicConnection::ReceivedPacketInfo& +QuicConnectionPeer::GetLastReceivedPacketInfo(QuicConnection* connection) { + return connection->last_received_packet_info_; +} + } // namespace test } // namespace quic
diff --git a/quiche/quic/test_tools/quic_connection_peer.h b/quiche/quic/test_tools/quic_connection_peer.h index 0016642..fdde893 100644 --- a/quiche/quic/test_tools/quic_connection_peer.h +++ b/quiche/quic/test_tools/quic_connection_peer.h
@@ -262,6 +262,9 @@ static void SetSpinBitEnabled(QuicConnection* connection, bool enabled); static bool GetSpinBitEnabled(QuicConnection* connection); + + static QuicConnection::ReceivedPacketInfo& GetLastReceivedPacketInfo( + QuicConnection* connection); }; } // namespace test