[QUIC] Do not send multi-port probing when there's no active stream. Limit the max number of multi-port path creations to 5.

PiperOrigin-RevId: 492305120
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index fdb0a0d..100dd22 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -13008,10 +13008,11 @@
       &connection_, kNewSelfAddress, connection_.peer_address()));
 }
 
-TEST_P(QuicConnectionTest, MultiPortCreation) {
+TEST_P(QuicConnectionTest, MultiPortConnection) {
   set_perspective(Perspective::IS_CLIENT);
   QuicConfig config;
-  config.SetConnectionOptionsToSend(QuicTagVector{kMPQC, kRVCM});
+  config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+  config.SetClientConnectionOptions(QuicTagVector{kMPQC});
   EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
   connection_.SetFromConfig(config);
   if (!connection_.connection_migration_use_new_cid()) {
@@ -13030,6 +13031,7 @@
   EXPECT_NE(kNewSelfAddress, self_address);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
 
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive()).WillOnce(Return(false));
   QuicNewConnectionIdFrame frame;
   frame.connection_id = TestConnectionId(1234);
   ASSERT_NE(frame.connection_id, connection_.connection_id());
@@ -13055,7 +13057,7 @@
 
   QuicFrames frames;
   frames.push_back(QuicFrame(QuicPathResponseFrame(
-      99, new_writer.path_challenge_frames().front().data_buffer)));
+      99, new_writer.path_challenge_frames().back().data_buffer)));
   ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
                                    ENCRYPTION_FORWARD_SECURE);
   // No migration should happen and the alternative path should still be alive.
@@ -13071,6 +13073,46 @@
   EXPECT_EQ(kTestRTT,
             stats->rtt_stats_when_default_path_degrading.latest_rtt());
 
+  // When there's no active request, the probing shouldn't happen. But the
+  // probing context should be saved.
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive()).WillOnce(Return(false));
+  connection_.GetMultiPortProbingAlarm()->Fire();
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  EXPECT_FALSE(connection_.GetMultiPortProbingAlarm()->IsSet());
+
+  // Simulate the situation where a new request stream is created.
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+      .WillRepeatedly(Return(true));
+  random_generator_.ChangeValue();
+  connection_.MaybeProbeMultiPortPath();
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress, connection_.peer_address()));
+  EXPECT_TRUE(alt_path->validated);
+  // Fake a response delay.
+  clock_.AdvanceTime(kTestRTT);
+  QuicFrames frames2;
+  frames2.push_back(QuicFrame(QuicPathResponseFrame(
+      99, new_writer.path_challenge_frames().back().data_buffer)));
+  ProcessFramesPacketWithAddresses(frames2, kNewSelfAddress, kPeerAddress,
+                                   ENCRYPTION_FORWARD_SECURE);
+  // No migration should happen and the alternative path should still be alive.
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress, connection_.peer_address()));
+  EXPECT_TRUE(alt_path->validated);
+  EXPECT_EQ(1, stats->num_path_degrading);
+  EXPECT_EQ(0, stats->num_multi_port_probe_failures_when_path_degrading);
+  EXPECT_EQ(kTestRTT, stats->rtt_stats.latest_rtt());
+  EXPECT_EQ(kTestRTT,
+            stats->rtt_stats_when_default_path_degrading.latest_rtt());
+
+  EXPECT_TRUE(connection_.GetMultiPortProbingAlarm()->IsSet());
+  // Since there's already a scheduled probing alarm, manual calls won't have
+  // any effect.
+  connection_.MaybeProbeMultiPortPath();
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+
+  // Simulate the case where the path validation fails after retries.
   connection_.GetMultiPortProbingAlarm()->Fire();
   EXPECT_TRUE(connection_.HasPendingPathValidation());
   EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
@@ -13091,6 +13133,116 @@
   EXPECT_EQ(0, stats->num_multi_port_probe_failures_when_path_not_degrading);
 }
 
+TEST_P(QuicConnectionTest, TooManyMultiPortPathCreations) {
+  set_perspective(Perspective::IS_CLIENT);
+  QuicConfig config;
+  config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+  config.SetClientConnectionOptions(QuicTagVector{kMPQC});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
+    return;
+  }
+  connection_.CreateConnectionIdManager();
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  connection_.OnHandshakeComplete();
+
+  EXPECT_CALL(visitor_, OnPathDegrading());
+  connection_.OnPathDegradingDetected();
+
+  auto self_address = connection_.self_address();
+  const QuicSocketAddress kNewSelfAddress(self_address.host(),
+                                          self_address.port() + 1);
+  EXPECT_NE(kNewSelfAddress, self_address);
+  TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+
+  EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+      .WillRepeatedly(Return(true));
+
+  QuicNewConnectionIdFrame frame;
+  frame.connection_id = TestConnectionId(1234);
+  ASSERT_NE(frame.connection_id, connection_.connection_id());
+  frame.stateless_reset_token =
+      QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+  frame.retire_prior_to = 0u;
+  frame.sequence_number = 1u;
+  EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+      .WillRepeatedly(Return(
+          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+              kNewSelfAddress, connection_.peer_address(), &new_writer))));
+  EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame));
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress, connection_.peer_address()));
+  auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_);
+  EXPECT_FALSE(alt_path->validated);
+
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress, connection_.peer_address()));
+  for (size_t i = 0; i < QuicPathValidator::kMaxRetryTimes + 1; ++i) {
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
+    static_cast<TestAlarmFactory::TestAlarm*>(
+        QuicPathValidatorPeer::retry_timer(
+            QuicConnectionPeer::path_validator(&connection_)))
+        ->Fire();
+  }
+
+  auto stats = connection_.multi_port_stats();
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  EXPECT_FALSE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress, connection_.peer_address()));
+  EXPECT_EQ(1, stats->num_path_degrading);
+  EXPECT_EQ(1, stats->num_multi_port_probe_failures_when_path_degrading);
+
+  uint64_t connection_id = 1235;
+  for (size_t i = 0; i < kMaxNumMultiPortPaths - 1; ++i) {
+    QuicNewConnectionIdFrame frame;
+    frame.connection_id = TestConnectionId(connection_id + i);
+    ASSERT_NE(frame.connection_id, connection_.connection_id());
+    frame.stateless_reset_token =
+        QuicUtils::GenerateStatelessResetToken(frame.connection_id);
+    frame.retire_prior_to = 0u;
+    frame.sequence_number = i + 2;
+    EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+        .WillRepeatedly(Return(
+            testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, connection_.peer_address(), &new_writer))));
+    EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame));
+    EXPECT_TRUE(connection_.HasPendingPathValidation());
+    EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+        &connection_, kNewSelfAddress, connection_.peer_address()));
+    EXPECT_FALSE(alt_path->validated);
+
+    for (size_t j = 0; j < QuicPathValidator::kMaxRetryTimes + 1; ++j) {
+      clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
+      static_cast<TestAlarmFactory::TestAlarm*>(
+          QuicPathValidatorPeer::retry_timer(
+              QuicConnectionPeer::path_validator(&connection_)))
+          ->Fire();
+    }
+
+    EXPECT_FALSE(connection_.HasPendingPathValidation());
+    EXPECT_FALSE(QuicConnectionPeer::IsAlternativePath(
+        &connection_, kNewSelfAddress, connection_.peer_address()));
+    EXPECT_EQ(1, stats->num_path_degrading);
+    EXPECT_EQ(i + 2, stats->num_multi_port_probe_failures_when_path_degrading);
+  }
+
+  // The 6th attemp should fail.
+  QuicNewConnectionIdFrame frame2;
+  frame2.connection_id = TestConnectionId(1239);
+  ASSERT_NE(frame2.connection_id, connection_.connection_id());
+  frame2.stateless_reset_token =
+      QuicUtils::GenerateStatelessResetToken(frame2.connection_id);
+  frame2.retire_prior_to = 0u;
+  frame2.sequence_number = 6u;
+  EXPECT_TRUE(connection_.OnNewConnectionIdFrame(frame2));
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  EXPECT_EQ(kMaxNumMultiPortPaths,
+            stats->num_multi_port_probe_failures_when_path_degrading);
+}
+
 TEST_P(QuicConnectionTest, SingleAckInPacket) {
   EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
   EXPECT_CALL(visitor_, OnConnectionClosed(_, _));