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