Use new connection IDs in IETF quic connection migration.

Feature is also guarded by "RVCM" connection option.
Also mark NEW_CONNECTION_ID_FRAME as retransmittable as it is a control frame type.

Protected by FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid.

PiperOrigin-RevId: 372122692
Change-Id: I76005ba39f50fad976b8146bd34fb4687c4d2ba1
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index d5bff8c..4b30bae 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -17,6 +17,7 @@
 #include "quic/core/http/http_constants.h"
 #include "quic/core/http/quic_spdy_client_stream.h"
 #include "quic/core/http/web_transport_http3.h"
+#include "quic/core/quic_connection.h"
 #include "quic/core/quic_data_writer.h"
 #include "quic/core/quic_epoll_connection_helper.h"
 #include "quic/core/quic_error_codes.h"
@@ -750,6 +751,18 @@
     }
   }
 
+  void WaitForNewConnectionIds() {
+    // Wait until a new server CID is available for another migration.
+    const auto* client_connection = GetClientConnection();
+    while (!QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(
+               client_connection) ||
+           (!client_connection->client_connection_id().IsEmpty() &&
+            !QuicConnectionPeer::HasSelfIssuedConnectionIdToConsume(
+                client_connection))) {
+      client_->client()->WaitForEvents();
+    }
+  }
+
   ScopedEnvironmentForThreads environment_;
   bool initialized_;
   // If true, the Initialize() function will create |client_| and starts to
@@ -2620,52 +2633,250 @@
   server_thread_->Resume();
 }
 
-TEST_P(EndToEndTest, ConnectionMigrationClientIPChangedWithNonEmptyClientCID) {
+TEST_P(EndToEndTest, IetfConnectionMigrationClientIPChangedMultipleTimes) {
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
+    return;
+  }
+  SendSynchronousFooRequestAndCheckResponse();
+
+  // Store the client IP address which was used to send the first request.
+  QuicIpAddress host0 =
+      client_->client()->network_helper()->GetLatestClientAddress().host();
+  QuicConnection* client_connection = GetClientConnection();
+  ASSERT_TRUE(client_connection != nullptr);
+
+  // Migrate socket to a new IP address.
+  QuicIpAddress host1 = TestLoopback(2);
+  EXPECT_NE(host0, host1);
+  ASSERT_TRUE(
+      QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection));
+  QuicConnectionId server_cid0 = client_connection->connection_id();
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(client_->client()->MigrateSocket(host1));
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  EXPECT_FALSE(server_cid1.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid1);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
+  // Send a request using the new socket.
+  SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(1u,
+            client_connection->GetStats().num_connectivity_probing_received);
+
+  // Send another request and wait for response making sure path response is
+  // received at server.
+  SendSynchronousBarRequestAndCheckResponse();
+
+  // Migrate socket to a new IP address.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+  QuicIpAddress host2 = TestLoopback(3);
+  EXPECT_NE(host0, host2);
+  EXPECT_NE(host1, host2);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(client_->client()->MigrateSocket(host2));
+  QuicConnectionId server_cid2 = client_connection->connection_id();
+  EXPECT_FALSE(server_cid2.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid2);
+  EXPECT_NE(server_cid1, server_cid2);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
+  // Send another request using the new socket and wait for response making sure
+  // path response is received at server.
+  SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(2u,
+            client_connection->GetStats().num_connectivity_probing_received);
+
+  // Migrate socket back to an old IP address.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(2u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(client_->client()->MigrateSocket(host1));
+  QuicConnectionId server_cid3 = client_connection->connection_id();
+  EXPECT_FALSE(server_cid3.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid3);
+  EXPECT_NE(server_cid1, server_cid3);
+  EXPECT_NE(server_cid2, server_cid3);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  const auto* client_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(client_connection);
+  EXPECT_TRUE(client_packet_creator->GetClientConnectionId().IsEmpty());
+  EXPECT_EQ(server_cid3, client_packet_creator->GetServerConnectionId());
+
+  // Send another request using the new socket and wait for response making sure
+  // path response is received at server.
+  SendSynchronousBarRequestAndCheckResponse();
+  // Even this is an old path, server has forgotten about it and thus needs to
+  // validate the path again.
+  EXPECT_EQ(3u,
+            client_connection->GetStats().num_connectivity_probing_received);
+
+  WaitForNewConnectionIds();
+  EXPECT_EQ(3u, client_connection->GetStats().num_retire_connection_id_sent);
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  // By the time the 2nd request is completed, the PATH_RESPONSE must have been
+  // received by the server.
+  EXPECT_FALSE(server_connection->HasPendingPathValidation());
+  EXPECT_EQ(3u, server_connection->GetStats().num_validated_peer_migration);
+  EXPECT_EQ(server_cid3, server_connection->connection_id());
+  const auto* server_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(server_connection);
+  EXPECT_EQ(server_cid3, server_packet_creator->GetServerConnectionId());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+  EXPECT_EQ(4u, server_connection->GetStats().num_new_connection_id_sent);
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest,
+       ConnectionMigrationWithNonZeroConnectionIDClientIPChangedMultipleTimes) {
   if (!version_.SupportsClientConnectionIds()) {
     ASSERT_TRUE(Initialize());
     return;
   }
   override_client_connection_id_length_ = kQuicDefaultConnectionIdLength;
   ASSERT_TRUE(Initialize());
-  if (!version_.HasIetfQuicFrames() ||
-      !client_->client()->session()->connection()->validate_client_address()) {
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
   SendSynchronousFooRequestAndCheckResponse();
 
   // Store the client IP address which was used to send the first request.
-  QuicIpAddress old_host =
+  QuicIpAddress host0 =
       client_->client()->network_helper()->GetLatestClientAddress().host();
-  auto* client_connection = GetClientConnection();
-  QuicConnectionId client_cid0 = client_connection->client_connection_id();
+  QuicConnection* client_connection = GetClientConnection();
+  ASSERT_TRUE(client_connection != nullptr);
+
+  // Migrate socket to a new IP address.
+  QuicIpAddress host1 = TestLoopback(2);
+  EXPECT_NE(host0, host1);
+  ASSERT_TRUE(
+      QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection));
   QuicConnectionId server_cid0 = client_connection->connection_id();
+  QuicConnectionId client_cid0 = client_connection->client_connection_id();
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(client_->client()->MigrateSocket(host1));
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  QuicConnectionId client_cid1 = client_connection->client_connection_id();
+  EXPECT_FALSE(server_cid1.IsEmpty());
+  EXPECT_FALSE(client_cid1.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid1);
+  EXPECT_NE(client_cid0, client_cid1);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
 
-  // Migrate socket to the new IP address.
-  QuicIpAddress new_host = TestLoopback(2);
-  EXPECT_NE(old_host, new_host);
-  ASSERT_TRUE(client_->client()->MigrateSocket(new_host));
-
-  // Send a request using the new socket.
+  // Send another request to ensure that the server will have time to finish the
+  // reverse path validation and send address token.
   SendSynchronousBarRequestAndCheckResponse();
-
   EXPECT_EQ(1u,
             client_connection->GetStats().num_connectivity_probing_received);
 
-  // Send another request.
+  // Migrate socket to a new IP address.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(2u, client_connection->GetStats().num_new_connection_id_sent);
+  QuicIpAddress host2 = TestLoopback(3);
+  EXPECT_NE(host0, host2);
+  EXPECT_NE(host1, host2);
+  EXPECT_TRUE(client_->client()->MigrateSocket(host2));
+  QuicConnectionId server_cid2 = client_connection->connection_id();
+  QuicConnectionId client_cid2 = client_connection->client_connection_id();
+  EXPECT_FALSE(server_cid2.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid2);
+  EXPECT_NE(server_cid1, server_cid2);
+  EXPECT_FALSE(client_cid2.IsEmpty());
+  EXPECT_NE(client_cid0, client_cid2);
+  EXPECT_NE(client_cid1, client_cid2);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
+  // Send another request to ensure that the server will have time to finish the
+  // reverse path validation and send address token.
   SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(2u,
+            client_connection->GetStats().num_connectivity_probing_received);
 
-  EXPECT_EQ(client_cid0, client_connection->client_connection_id());
-  EXPECT_EQ(server_cid0, client_connection->connection_id());
+  // Migrate socket back to an old IP address.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(2u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(3u, client_connection->GetStats().num_new_connection_id_sent);
+  EXPECT_TRUE(client_->client()->MigrateSocket(host1));
+  QuicConnectionId server_cid3 = client_connection->connection_id();
+  QuicConnectionId client_cid3 = client_connection->client_connection_id();
+  EXPECT_FALSE(server_cid3.IsEmpty());
+  EXPECT_NE(server_cid0, server_cid3);
+  EXPECT_NE(server_cid1, server_cid3);
+  EXPECT_NE(server_cid2, server_cid3);
+  EXPECT_FALSE(client_cid3.IsEmpty());
+  EXPECT_NE(client_cid0, client_cid3);
+  EXPECT_NE(client_cid1, client_cid3);
+  EXPECT_NE(client_cid2, client_cid3);
+  const auto* client_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(client_connection);
+  EXPECT_EQ(client_cid3, client_packet_creator->GetClientConnectionId());
+  EXPECT_EQ(server_cid3, client_packet_creator->GetServerConnectionId());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
 
+  // Send another request to ensure that the server will have time to finish the
+  // reverse path validation and send address token.
+  SendSynchronousBarRequestAndCheckResponse();
+  // Even this is an old path, server has forgotten about it and thus needs to
+  // validate the path again.
+  EXPECT_EQ(3u,
+            client_connection->GetStats().num_connectivity_probing_received);
+
+  WaitForNewConnectionIds();
+  EXPECT_EQ(3u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(4u, client_connection->GetStats().num_new_connection_id_sent);
+
+  server_thread_->Pause();
   // By the time the 2nd request is completed, the PATH_RESPONSE must have been
   // received by the server.
-  server_thread_->Pause();
   QuicConnection* server_connection = GetServerConnection();
-  ASSERT_THAT(server_connection, NotNull());
   EXPECT_FALSE(server_connection->HasPendingPathValidation());
-  EXPECT_EQ(1u, server_connection->GetStats().num_validated_peer_migration);
-  EXPECT_EQ(client_cid0, server_connection->client_connection_id());
-  EXPECT_EQ(server_cid0, server_connection->connection_id());
+  EXPECT_EQ(3u, server_connection->GetStats().num_validated_peer_migration);
+  EXPECT_EQ(server_cid3, server_connection->connection_id());
+  EXPECT_EQ(client_cid3, server_connection->client_connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+  const auto* server_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(server_connection);
+  EXPECT_EQ(client_cid3, server_packet_creator->GetClientConnectionId());
+  EXPECT_EQ(server_cid3, server_packet_creator->GetServerConnectionId());
+  EXPECT_EQ(3u, server_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(4u, server_connection->GetStats().num_new_connection_id_sent);
   server_thread_->Resume();
 }
 
@@ -2693,7 +2904,7 @@
   EXPECT_EQ(1u,
             client_connection->GetStats().num_connectivity_probing_received);
 
-  // Send another request to ensure that the server will time to finish the
+  // Send another request to ensure that the server will have time to finish the
   // reverse path validation and send address token.
   SendSynchronousBarRequestAndCheckResponse();
 
@@ -2758,14 +2969,19 @@
 
 TEST_P(EndToEndTest, ClientAddressSpoofedForSomePeriod) {
   ASSERT_TRUE(Initialize());
-  if (!version_.HasIetfQuicFrames() ||
-      !client_->client()->session()->connection()->validate_client_address()) {
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
   auto writer = new DuplicatePacketWithSpoofedSelfAddressWriter();
   client_.reset(CreateQuicClient(writer));
+
+  // Make sure client has unused peer connection ID before migration.
+  SendSynchronousFooRequestAndCheckResponse();
+  ASSERT_TRUE(QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(
+      GetClientConnection()));
+
   QuicIpAddress real_host = TestLoopback(1);
-  client_->MigrateSocket(real_host);
+  ASSERT_TRUE(client_->MigrateSocket(real_host));
   SendSynchronousFooRequestAndCheckResponse();
   EXPECT_EQ(
       0u, GetClientConnection()->GetStats().num_connectivity_probing_received);
@@ -2804,10 +3020,10 @@
   EXPECT_EQ(large_body, client_->response_body());
 }
 
-TEST_P(EndToEndTest, AsynchronousConnectionMigrationClientIPChanged) {
+TEST_P(EndToEndTest,
+       AsynchronousConnectionMigrationClientIPChangedMultipleTimes) {
   ASSERT_TRUE(Initialize());
-  if (!version_.HasIetfQuicFrames() ||
-      !client_->client()->session()->connection()->use_path_validator()) {
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
   client_.reset(CreateQuicClient(nullptr));
@@ -2815,24 +3031,86 @@
   SendSynchronousFooRequestAndCheckResponse();
 
   // Store the client IP address which was used to send the first request.
-  QuicIpAddress old_host =
+  QuicIpAddress host0 =
       client_->client()->network_helper()->GetLatestClientAddress().host();
+  QuicConnection* client_connection = GetClientConnection();
+  const QuicConnection* server_connection = GetServerConnection();
+  QuicConnectionId server_cid0 = client_connection->connection_id();
+  EXPECT_EQ(server_connection->connection_id(), server_cid0);
+  // Server should have one new connection ID upon handshake completion.
+  ASSERT_TRUE(
+      QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection));
 
-  // Migrate socket to the new IP address.
-  QuicIpAddress new_host = TestLoopback(2);
-  EXPECT_NE(old_host, new_host);
-  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(new_host));
+  // Migrate socket to new IP address #1.
+  QuicIpAddress host1 = TestLoopback(2);
+  EXPECT_NE(host0, host1);
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host1));
+  while (client_->client()->HasPendingPathValidation()) {
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(host1, client_->client()->session()->self_address().host());
+  EXPECT_EQ(1u,
+            client_connection->GetStats().num_connectivity_probing_received);
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  EXPECT_NE(server_cid0, server_cid1);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
+  // Send a request using the new socket.
+  SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(server_connection->connection_id(), server_cid1);
+
+  // Migrate socket to new IP address #2.
+  WaitForNewConnectionIds();
+  QuicIpAddress host2 = TestLoopback(3);
+  EXPECT_NE(host0, host1);
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host2));
 
   while (client_->client()->HasPendingPathValidation()) {
     client_->client()->WaitForEvents();
   }
-  EXPECT_EQ(new_host, client_->client()->session()->self_address().host());
-  QuicConnection* client_connection = GetClientConnection();
-  ASSERT_TRUE(client_connection);
-  EXPECT_EQ(client_connection->validate_client_address() ? 1u : 0,
+  EXPECT_EQ(host2, client_->client()->session()->self_address().host());
+  EXPECT_EQ(2u,
             client_connection->GetStats().num_connectivity_probing_received);
+  QuicConnectionId server_cid2 = client_connection->connection_id();
+  EXPECT_NE(server_cid0, server_cid2);
+  EXPECT_NE(server_cid1, server_cid2);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
   // Send a request using the new socket.
   SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(server_connection->connection_id(), server_cid2);
+
+  // Migrate socket back to IP address #1.
+  WaitForNewConnectionIds();
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host1));
+
+  while (client_->client()->HasPendingPathValidation()) {
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(host1, client_->client()->session()->self_address().host());
+  EXPECT_EQ(3u,
+            client_connection->GetStats().num_connectivity_probing_received);
+  QuicConnectionId server_cid3 = client_connection->connection_id();
+  EXPECT_NE(server_cid0, server_cid3);
+  EXPECT_NE(server_cid1, server_cid3);
+  EXPECT_NE(server_cid2, server_cid3);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+
+  // Send a request using the new socket.
+  SendSynchronousBarRequestAndCheckResponse();
+  EXPECT_EQ(server_connection->connection_id(), server_cid3);
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+
+  // There should be 1 new connection ID issued by the server.
+  WaitForNewConnectionIds();
 }
 
 TEST_P(EndToEndTest,
@@ -2843,8 +3121,7 @@
   }
   override_client_connection_id_length_ = kQuicDefaultConnectionIdLength;
   ASSERT_TRUE(Initialize());
-  if (!version_.HasIetfQuicFrames() ||
-      !client_->client()->session()->connection()->validate_client_address()) {
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
   client_.reset(CreateQuicClient(nullptr));
@@ -2869,13 +3146,23 @@
   EXPECT_EQ(new_host, client_->client()->session()->self_address().host());
   EXPECT_EQ(1u,
             client_connection->GetStats().num_connectivity_probing_received);
+  QuicConnectionId client_cid1 = client_connection->client_connection_id();
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  const auto* client_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(client_connection);
+  EXPECT_EQ(client_cid1, client_packet_creator->GetClientConnectionId());
+  EXPECT_EQ(server_cid1, client_packet_creator->GetServerConnectionId());
   // Send a request using the new socket.
   SendSynchronousBarRequestAndCheckResponse();
 
   server_thread_->Pause();
   QuicConnection* server_connection = GetServerConnection();
-  EXPECT_EQ(client_cid0, server_connection->client_connection_id());
-  EXPECT_EQ(server_cid0, server_connection->connection_id());
+  EXPECT_EQ(client_cid1, server_connection->client_connection_id());
+  EXPECT_EQ(server_cid1, server_connection->connection_id());
+  const auto* server_packet_creator =
+      QuicConnectionPeer::GetPacketCreator(server_connection);
+  EXPECT_EQ(client_cid1, server_packet_creator->GetClientConnectionId());
+  EXPECT_EQ(server_cid1, server_packet_creator->GetServerConnectionId());
   server_thread_->Resume();
 }
 
@@ -4794,6 +5081,219 @@
   server_thread_->Resume();
 }
 
+TEST_P(EndToEndPacketReorderingTest, MigrateAgainAfterPathValidationFailure) {
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
+    return;
+  }
+
+  client_.reset(CreateQuicClient(nullptr));
+  // Finish one request to make sure handshake established.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  // Wait for the connection to become idle, to make sure the packet gets
+  // delayed is the connectivity probing packet.
+  client_->WaitForDelayedAcks();
+
+  QuicSocketAddress addr1 = client_->client()->session()->self_address();
+  QuicConnection* client_connection = GetClientConnection();
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+
+  // Migrate socket to the new IP address.
+  QuicIpAddress host2 = TestLoopback(2);
+  EXPECT_NE(addr1.host(), host2);
+
+  // Drop PATH_RESPONSE packets to timeout the path validation.
+  server_writer_->set_fake_packet_loss_percentage(100);
+  ASSERT_TRUE(
+      QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection));
+
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host2));
+
+  QuicConnectionId server_cid2 =
+      QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_FALSE(server_cid2.IsEmpty());
+  EXPECT_NE(server_cid2, server_cid1);
+  // Wait until path validation fails at the client.
+  while (client_->client()->HasPendingPathValidation()) {
+    EXPECT_EQ(server_cid2,
+              QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection));
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(addr1, client_->client()->session()->self_address());
+  EXPECT_EQ(server_cid1, GetClientConnection()->connection_id());
+
+  server_writer_->set_fake_packet_loss_percentage(0);
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+
+  WaitForNewConnectionIds();
+  EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(0u, client_connection->GetStats().num_new_connection_id_sent);
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  // Server has received 3 path challenges.
+  EXPECT_EQ(3u,
+            server_connection->GetStats().num_connectivity_probing_received);
+  EXPECT_EQ(server_cid1, server_connection->connection_id());
+  EXPECT_EQ(0u, server_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(2u, server_connection->GetStats().num_new_connection_id_sent);
+  server_thread_->Resume();
+
+  // Migrate socket to a new IP address again.
+  QuicIpAddress host3 = TestLoopback(3);
+  EXPECT_NE(addr1.host(), host3);
+  EXPECT_NE(host2, host3);
+
+  WaitForNewConnectionIds();
+  EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(0u, client_connection->GetStats().num_new_connection_id_sent);
+
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host3));
+  QuicConnectionId server_cid3 =
+      QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_FALSE(server_cid3.IsEmpty());
+  EXPECT_NE(server_cid1, server_cid3);
+  EXPECT_NE(server_cid2, server_cid3);
+  while (client_->client()->HasPendingPathValidation()) {
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(host3, client_->client()->session()->self_address().host());
+  EXPECT_EQ(server_cid3, GetClientConnection()->connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ(server_cid3, server_connection->connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+
+  // Server should send a new connection ID to client.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(2u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(0u, client_connection->GetStats().num_new_connection_id_sent);
+}
+
+TEST_P(EndToEndPacketReorderingTest,
+       MigrateAgainAfterPathValidationFailureWithNonZeroClientConnectionId) {
+  if (!version_.SupportsClientConnectionIds()) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  override_client_connection_id_length_ = kQuicDefaultConnectionIdLength;
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
+    return;
+  }
+
+  client_.reset(CreateQuicClient(nullptr));
+  // Finish one request to make sure handshake established.
+  EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo"));
+
+  // Wait for the connection to become idle, to make sure the packet gets
+  // delayed is the connectivity probing packet.
+  client_->WaitForDelayedAcks();
+
+  QuicSocketAddress addr1 = client_->client()->session()->self_address();
+  QuicConnection* client_connection = GetClientConnection();
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  QuicConnectionId client_cid1 = client_connection->client_connection_id();
+
+  // Migrate socket to the new IP address.
+  QuicIpAddress host2 = TestLoopback(2);
+  EXPECT_NE(addr1.host(), host2);
+
+  // Drop PATH_RESPONSE packets to timeout the path validation.
+  server_writer_->set_fake_packet_loss_percentage(100);
+  ASSERT_TRUE(
+      QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(client_connection));
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host2));
+  QuicConnectionId server_cid2 =
+      QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_FALSE(server_cid2.IsEmpty());
+  EXPECT_NE(server_cid2, server_cid1);
+  QuicConnectionId client_cid2 =
+      QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_FALSE(client_cid2.IsEmpty());
+  EXPECT_NE(client_cid2, client_cid1);
+  while (client_->client()->HasPendingPathValidation()) {
+    EXPECT_EQ(server_cid2,
+              QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection));
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(addr1, client_->client()->session()->self_address());
+  EXPECT_EQ(server_cid1, GetClientConnection()->connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  server_writer_->set_fake_packet_loss_percentage(0);
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  WaitForNewConnectionIds();
+  EXPECT_EQ(1u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(2u, client_connection->GetStats().num_new_connection_id_sent);
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  if (server_connection != nullptr) {
+    EXPECT_EQ(3u,
+              server_connection->GetStats().num_connectivity_probing_received);
+    EXPECT_EQ(server_cid1, server_connection->connection_id());
+  } else {
+    ADD_FAILURE() << "Missing server connection";
+  }
+  EXPECT_EQ(1u, server_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(2u, server_connection->GetStats().num_new_connection_id_sent);
+  server_thread_->Resume();
+
+  // Migrate socket to a new IP address again.
+  QuicIpAddress host3 = TestLoopback(3);
+  EXPECT_NE(addr1.host(), host3);
+  EXPECT_NE(host2, host3);
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host3));
+
+  QuicConnectionId server_cid3 =
+      QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_FALSE(server_cid3.IsEmpty());
+  EXPECT_NE(server_cid1, server_cid3);
+  EXPECT_NE(server_cid2, server_cid3);
+  QuicConnectionId client_cid3 =
+      QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_NE(client_cid1, client_cid3);
+  EXPECT_NE(client_cid2, client_cid3);
+  while (client_->client()->HasPendingPathValidation()) {
+    client_->client()->WaitForEvents();
+  }
+  EXPECT_EQ(host3, client_->client()->session()->self_address().host());
+  EXPECT_EQ(server_cid3, GetClientConnection()->connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  client_connection)
+                  .IsEmpty());
+  EXPECT_EQ(kBarResponseBody, client_->SendSynchronousRequest("/bar"));
+  EXPECT_EQ(server_cid3, server_connection->connection_id());
+  EXPECT_EQ(client_cid3, server_connection->client_connection_id());
+  EXPECT_TRUE(QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+  EXPECT_TRUE(QuicConnectionPeer::GetClientConnectionIdOnAlternativePath(
+                  server_connection)
+                  .IsEmpty());
+
+  // Server should send new server connection ID to client and retires old
+  // client connection ID.
+  WaitForNewConnectionIds();
+  EXPECT_EQ(2u, client_connection->GetStats().num_retire_connection_id_sent);
+  EXPECT_EQ(3u, client_connection->GetStats().num_new_connection_id_sent);
+}
+
 TEST_P(EndToEndPacketReorderingTest, Buffer0RttRequest) {
   ASSERT_TRUE(Initialize());
   // Finish one request to make sure handshake established.
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc
index d8efb89..9c26695 100644
--- a/quic/core/quic_connection.cc
+++ b/quic/core/quic_connection.cc
@@ -715,6 +715,17 @@
     QUIC_CODE_COUNT_N(quic_server_reverse_validate_new_path3, 6, 6);
     validate_client_addresses_ = true;
   }
+  // Having connection_migration_use_new_cid_ depends on the same set of flags
+  // and connection option on both client and server sides has the advantage of:
+  // 1) Less chance of skew in using new connection ID or not between client
+  //    and server in unit tests with random flag combinations.
+  // 2) Client side's rollout can be protected by the same connection option.
+  connection_migration_use_new_cid_ =
+      support_multiple_connection_ids_ && validate_client_addresses_ &&
+      use_connection_id_on_default_path_ &&
+      group_path_response_and_challenge_sending_closer_ &&
+      GetQuicReloadableFlag(quic_drop_unsent_path_response) &&
+      GetQuicReloadableFlag(quic_connection_migration_use_new_cid);
   if (config.HasReceivedMaxPacketSize()) {
     peer_max_packet_size_ = config.ReceivedMaxPacketSize();
     MaybeUpdatePacketCreatorMaxPacketLengthAndPadding();
@@ -1816,7 +1827,7 @@
                   << current_effective_peer_address;
     QUIC_CODE_COUNT_N(quic_kick_off_client_address_validation, 2, 6);
     ValidatePath(std::make_unique<ReversePathValidationContext>(
-                     default_path_.self_address, current_effective_peer_address,
+                     default_path_.self_address, last_packet_source_address_,
                      current_effective_peer_address, this),
                  std::make_unique<ReversePathValidationResultDelegate>(
                      this, peer_address()));
@@ -2013,6 +2024,7 @@
 }
 
 void QuicConnection::OnClientConnectionIdAvailable() {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid, 3, 5);
   QUICHE_DCHECK(perspective_ == Perspective::IS_SERVER);
   if (!peer_issued_cid_manager_->HasUnusedConnectionId()) {
     return;
@@ -2102,7 +2114,8 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnRetireConnectionIdFrame(frame);
   }
-  if (use_connection_id_on_default_path_) {
+  if (use_connection_id_on_default_path_ &&
+      !connection_migration_use_new_cid_) {
     // Do not respond to RetireConnectionId frame.
     return true;
   }
@@ -4110,6 +4123,11 @@
 
 void QuicConnection::OnHandshakeComplete() {
   sent_packet_manager_.SetHandshakeConfirmed();
+  if (connection_migration_use_new_cid_ &&
+      perspective_ == Perspective::IS_SERVER &&
+      self_issued_cid_manager_ != nullptr) {
+    self_issued_cid_manager_->MaybeSendNewConnectionIds();
+  }
   if (send_ack_frequency_on_handshake_completion_ &&
       sent_packet_manager_.CanSendAckFrequency()) {
     QUIC_RELOADABLE_FLAG_COUNT_N(quic_can_send_ack_frequency, 2, 3);
@@ -6439,6 +6457,7 @@
       peer_issued_cid_manager_->ConsumeToBeRetiredConnectionIdSequenceNumbers();
   QUICHE_DCHECK(!retired_cid_sequence_numbers.empty());
   for (const auto& sequence_number : retired_cid_sequence_numbers) {
+    ++stats_.num_retire_connection_id_sent;
     visitor_->SendRetireConnectionId(sequence_number);
   }
 }
@@ -6446,6 +6465,7 @@
 bool QuicConnection::SendNewConnectionId(
     const QuicNewConnectionIdFrame& frame) {
   visitor_->SendNewConnectionId(frame);
+  ++stats_.num_new_connection_id_sent;
   return connected_;
 }
 
@@ -6782,6 +6802,7 @@
 }
 
 void QuicConnection::RetirePeerIssuedConnectionIdsNoLongerOnPath() {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid, 4, 5);
   if (!connection_migration_use_new_cid_ ||
       peer_issued_cid_manager_ == nullptr) {
     return;
diff --git a/quic/core/quic_connection_stats.h b/quic/core/quic_connection_stats.h
index 06b7f58..c2a64ce 100644
--- a/quic/core/quic_connection_stats.h
+++ b/quic/core/quic_connection_stats.h
@@ -209,6 +209,10 @@
   // which was canceled because the peer migrated again. Such migration is also
   // counted as invalid peer migration.
   size_t num_peer_migration_while_validating_default_path = 0;
+  // Number of NEW_CONNECTION_ID frames sent.
+  size_t num_new_connection_id_sent = 0;
+  // Number of RETIRE_CONNECTION_ID frames sent.
+  size_t num_retire_connection_id_sent = 0;
 
   struct QUIC_NO_EXPORT TlsServerOperationStats {
     bool success = false;
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc
index 00af323..833a692 100644
--- a/quic/core/quic_connection_test.cc
+++ b/quic/core/quic_connection_test.cc
@@ -1428,7 +1428,8 @@
     EXPECT_TRUE(connection_.connected());
   }
 
-  void PathProbeTestInit(Perspective perspective) {
+  void PathProbeTestInit(Perspective perspective,
+                         bool receive_new_server_connection_id = true) {
     set_perspective(perspective);
     connection_.CreateConnectionIdManager();
     EXPECT_EQ(connection_.perspective(), perspective);
@@ -1462,6 +1463,17 @@
                                     kPeerAddress, ENCRYPTION_FORWARD_SECURE);
     EXPECT_EQ(kPeerAddress, connection_.peer_address());
     EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+    if (perspective == Perspective::IS_CLIENT &&
+        receive_new_server_connection_id) {
+      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;
+      connection_.OnNewConnectionIdFrame(frame);
+    }
   }
 
   void TestClientRetryHandling(bool invalid_retry_tag,
@@ -1877,14 +1889,11 @@
 
 TEST_P(QuicConnectionTest, PeerIpAddressChangeAtServerWithMissingConnectionId) {
   set_perspective(Perspective::IS_SERVER);
-  if (!connection_.validate_client_address() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.support_multiple_connection_ids()) {
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
   QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false);
   EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective());
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
 
   QuicConnectionId client_cid0 = TestConnectionId(1);
   QuicConnectionId client_cid1 = TestConnectionId(3);
@@ -1896,14 +1905,13 @@
   EXPECT_CALL(visitor_, GetHandshakeState())
       .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
   QuicConnectionPeer::SetAddressValidated(&connection_);
-  connection_.OnHandshakeComplete();
 
   // Sends new server CID to client.
   EXPECT_CALL(visitor_, OnServerConnectionIdIssued(_))
       .WillOnce(
           Invoke([&](const QuicConnectionId& cid) { server_cid1 = cid; }));
   EXPECT_CALL(visitor_, SendNewConnectionId(_));
-  connection_.MaybeSendConnectionIdToClient();
+  connection_.OnHandshakeComplete();
 
   // Clear direct_peer_address.
   QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress());
@@ -2073,14 +2081,11 @@
 
 TEST_P(QuicConnectionTest, ReversePathValidationFailureAtServer) {
   set_perspective(Perspective::IS_SERVER);
-  if (!connection_.validate_client_address() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.support_multiple_connection_ids()) {
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
   QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false);
   EXPECT_EQ(Perspective::IS_SERVER, connection_.perspective());
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
   SetClientConnectionId(TestConnectionId(1));
   connection_.CreateConnectionIdManager();
   connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
@@ -2088,7 +2093,6 @@
   EXPECT_CALL(visitor_, GetHandshakeState())
       .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
   QuicConnectionPeer::SetAddressValidated(&connection_);
-  connection_.OnHandshakeComplete();
 
   QuicConnectionId client_cid0 = connection_.client_connection_id();
   QuicConnectionId client_cid1 = TestConnectionId(2);
@@ -2099,7 +2103,7 @@
       .WillOnce(
           Invoke([&](const QuicConnectionId& cid) { server_cid1 = cid; }));
   EXPECT_CALL(visitor_, SendNewConnectionId(_));
-  connection_.MaybeSendConnectionIdToClient();
+  connection_.OnHandshakeComplete();
   // Receives new client CID from client.
   QuicNewConnectionIdFrame new_cid_frame;
   new_cid_frame.connection_id = client_cid1;
@@ -11918,15 +11922,17 @@
   const QuicSocketAddress kNewSelfAddress2(QuicIpAddress::Any4(), 12346);
   EXPECT_NE(kNewSelfAddress2, connection_.self_address());
   TestPacketWriter new_writer2(version(), &clock_, Perspective::IS_CLIENT);
-  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
-      .Times(AtLeast(1u))
-      .WillOnce(Invoke([&]() {
-        EXPECT_EQ(1u, new_writer2.packets_write_attempts());
-        EXPECT_EQ(1u, new_writer2.path_challenge_frames().size());
-        EXPECT_EQ(1u, new_writer2.padding_frames().size());
-        EXPECT_EQ(kNewSelfAddress2.host(),
-                  new_writer2.last_write_source_address());
-      }));
+  if (!connection_.connection_migration_use_new_cid()) {
+    EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+        .Times(AtLeast(1u))
+        .WillOnce(Invoke([&]() {
+          EXPECT_EQ(1u, new_writer2.packets_write_attempts());
+          EXPECT_EQ(1u, new_writer2.path_challenge_frames().size());
+          EXPECT_EQ(1u, new_writer2.padding_frames().size());
+          EXPECT_EQ(kNewSelfAddress2.host(),
+                    new_writer2.last_write_source_address());
+        }));
+  }
   bool success2 = false;
   connection_.ValidatePath(
       std::make_unique<TestQuicPathValidationContext>(
@@ -11935,7 +11941,13 @@
           &connection_, kNewSelfAddress2, connection_.peer_address(),
           &success2));
   EXPECT_FALSE(success);
-  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  if (connection_.connection_migration_use_new_cid()) {
+    // There is no pening path validation as there is no available connection
+    // ID.
+    EXPECT_FALSE(connection_.HasPendingPathValidation());
+  } else {
+    EXPECT_TRUE(connection_.HasPendingPathValidation());
+  }
 }
 
 // Regression test for b/182571515.
@@ -13889,13 +13901,10 @@
 
 TEST_P(QuicConnectionTest, PathChallengeBeforePeerIpAddressChangeAtServer) {
   set_perspective(Perspective::IS_SERVER);
-  if (!connection_.validate_client_address() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.support_multiple_connection_ids()) {
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
   PathProbeTestInit(Perspective::IS_SERVER);
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
   SetClientConnectionId(TestConnectionId(1));
   connection_.CreateConnectionIdManager();
 
@@ -14039,13 +14048,10 @@
 TEST_P(QuicConnectionTest,
        PathValidationSucceedsBeforePeerIpAddressChangeAtServer) {
   set_perspective(Perspective::IS_SERVER);
-  if (!connection_.validate_client_address() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.support_multiple_connection_ids()) {
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
   PathProbeTestInit(Perspective::IS_SERVER);
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
   connection_.CreateConnectionIdManager();
 
   QuicConnectionId server_cid0 = connection_.connection_id();
@@ -14220,13 +14226,15 @@
 
 TEST_P(QuicConnectionTest,
        PathValidationFailedOnClientDueToLackOfServerConnectionId) {
-  if (!connection_.support_multiple_connection_ids() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.use_path_validator()) {
+  QuicConfig config;
+  config.SetConnectionOptionsToSend({kRVCM});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
-  PathProbeTestInit(Perspective::IS_CLIENT);
+  PathProbeTestInit(Perspective::IS_CLIENT,
+                    /*receive_new_server_connection_id=*/false);
 
   const QuicSocketAddress kNewSelfAddress(QuicIpAddress::Loopback4(),
                                           /*port=*/34567);
@@ -14243,14 +14251,15 @@
 
 TEST_P(QuicConnectionTest,
        PathValidationFailedOnClientDueToLackOfClientConnectionIdTheSecondTime) {
-  if (!connection_.support_multiple_connection_ids() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.use_path_validator()) {
+  QuicConfig config;
+  config.SetConnectionOptionsToSend({kRVCM});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
-
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
-  PathProbeTestInit(Perspective::IS_CLIENT);
+  PathProbeTestInit(Perspective::IS_CLIENT,
+                    /*receive_new_server_connection_id=*/false);
   SetClientConnectionId(TestConnectionId(1));
 
   // Make sure server connection ID is available for the 1st validation.
@@ -14331,13 +14340,13 @@
 }
 
 TEST_P(QuicConnectionTest, ServerConnectionIdRetiredUponPathValidationFailure) {
-  if (!connection_.support_multiple_connection_ids() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.use_path_validator()) {
+  QuicConfig config;
+  config.SetConnectionOptionsToSend({kRVCM});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
-
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
   PathProbeTestInit(Perspective::IS_CLIENT);
 
   // Make sure server connection ID is available for validation.
@@ -14378,12 +14387,15 @@
 
 TEST_P(QuicConnectionTest,
        MigratePathDirectlyFailedDueToLackOfServerConnectionId) {
-  if (!connection_.support_multiple_connection_ids() ||
-      !connection_.use_connection_id_on_default_path()) {
+  QuicConfig config;
+  config.SetConnectionOptionsToSend({kRVCM});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
-  PathProbeTestInit(Perspective::IS_CLIENT);
+  PathProbeTestInit(Perspective::IS_CLIENT,
+                    /*receive_new_server_connection_id=*/false);
   const QuicSocketAddress kSelfAddress1(QuicIpAddress::Any4(), 12345);
   ASSERT_NE(kSelfAddress1, connection_.self_address());
 
@@ -14395,13 +14407,15 @@
 
 TEST_P(QuicConnectionTest,
        MigratePathDirectlyFailedDueToLackOfClientConnectionIdTheSecondTime) {
-  if (!connection_.support_multiple_connection_ids() ||
-      !connection_.use_connection_id_on_default_path() ||
-      !connection_.use_path_validator()) {
+  QuicConfig config;
+  config.SetConnectionOptionsToSend({kRVCM});
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  if (!connection_.connection_migration_use_new_cid()) {
     return;
   }
-  QuicConnectionPeer::EnableConnectionMigrationUseNewCID(&connection_);
-  PathProbeTestInit(Perspective::IS_CLIENT);
+  PathProbeTestInit(Perspective::IS_CLIENT,
+                    /*receive_new_server_connection_id=*/false);
   SetClientConnectionId(TestConnectionId(1));
 
   // Make sure server connection ID is available for the 1st migration.
@@ -14695,6 +14709,9 @@
 
 TEST_P(QuicConnectionTest,
        ServerRetireSelfIssuedConnectionIdWithoutSendingNewConnectionIdBefore) {
+  if (!connection_.support_multiple_connection_ids()) {
+    return;
+  }
   QuicConnectionPeer::EnableMultipleConnectionIdSupport(&connection_);
   set_perspective(Perspective::IS_SERVER);
   connection_.CreateConnectionIdManager();
@@ -14706,7 +14723,8 @@
   QuicConnectionId cid0 = connection_id_;
   QuicRetireConnectionIdFrame frame;
   frame.sequence_number = 0u;
-  if (!GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2)) {
+  if (!GetQuicReloadableFlag(quic_use_connection_id_on_default_path_v2) ||
+      connection_.connection_migration_use_new_cid()) {
     EXPECT_CALL(visitor_, OnServerConnectionIdIssued(_)).Times(2);
     EXPECT_CALL(visitor_, SendNewConnectionId(_)).Times(2);
   }
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index ccf2c3f..394253b 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -1026,6 +1026,7 @@
         << server_connection_id << " new_connection_id: " << new_connection_id;
     return;
   }
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid, 5, 5);
   auto insertion_result = reference_counted_session_map_.insert(
       std::make_pair(new_connection_id, it->second));
   QUICHE_DCHECK(insertion_result.second);
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h
index f056cd5..0698606 100644
--- a/quic/core/quic_flags_list.h
+++ b/quic/core/quic_flags_list.h
@@ -16,6 +16,7 @@
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_avoid_too_low_probe_bw_cwnd, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_bbr2_fix_bw_lo_mode, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_can_send_ack_frequency, true)
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_migration_use_new_cid, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_connection_support_multiple_cids_v4, true)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_bursts, false)
 QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false)
diff --git a/quic/core/quic_session.cc b/quic/core/quic_session.cc
index fb564f0..60abaf1 100644
--- a/quic/core/quic_session.cc
+++ b/quic/core/quic_session.cc
@@ -2101,12 +2101,14 @@
 }
 
 void QuicSession::SendNewConnectionId(const QuicNewConnectionIdFrame& frame) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid, 1, 5);
   control_frame_manager_.WriteOrBufferNewConnectionId(
       frame.connection_id, frame.sequence_number, frame.retire_prior_to,
       frame.stateless_reset_token);
 }
 
 void QuicSession::SendRetireConnectionId(uint64_t sequence_number) {
+  QUIC_RELOADABLE_FLAG_COUNT_N(quic_connection_migration_use_new_cid, 2, 5);
   control_frame_manager_.WriteOrBufferRetireConnectionId(sequence_number);
 }
 
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc
index bdd9eff..71036cb 100644
--- a/quic/test_tools/quic_connection_peer.cc
+++ b/quic/test_tools/quic_connection_peer.cc
@@ -490,12 +490,6 @@
 }
 
 // static
-void QuicConnectionPeer::EnableConnectionMigrationUseNewCID(
-    QuicConnection* connection) {
-  connection->connection_migration_use_new_cid_ = true;
-}
-
-// static
 void QuicConnectionPeer::ResetPeerIssuedConnectionIdManager(
     QuicConnection* connection) {
   connection->peer_issued_cid_manager_ = nullptr;
@@ -519,5 +513,17 @@
   connection->RetirePeerIssuedConnectionIdsNoLongerOnPath();
 }
 
+// static
+bool QuicConnectionPeer::HasUnusedPeerIssuedConnectionId(
+    const QuicConnection* connection) {
+  return connection->peer_issued_cid_manager_->HasUnusedConnectionId();
+}
+
+// static
+bool QuicConnectionPeer::HasSelfIssuedConnectionIdToConsume(
+    const QuicConnection* connection) {
+  return connection->self_issued_cid_manager_->HasConnectionIdToConsume();
+}
+
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h
index df2a823..cbc829a 100644
--- a/quic/test_tools/quic_connection_peer.h
+++ b/quic/test_tools/quic_connection_peer.h
@@ -199,9 +199,6 @@
 
   static void EnableMultipleConnectionIdSupport(QuicConnection* connection);
 
-  // Remove this method once the boolean is enabled via reloadable flag.
-  static void EnableConnectionMigrationUseNewCID(QuicConnection* connection);
-
   static void ResetPeerIssuedConnectionIdManager(QuicConnection* connection);
 
   static QuicConnection::PathState* GetDefaultPath(QuicConnection* connection);
@@ -211,6 +208,11 @@
 
   static void RetirePeerIssuedConnectionIdsNoLongerOnPath(
       QuicConnection* connection);
+
+  static bool HasUnusedPeerIssuedConnectionId(const QuicConnection* connection);
+
+  static bool HasSelfIssuedConnectionIdToConsume(
+      const QuicConnection* connection);
 };
 
 }  // namespace test
diff --git a/quic/tools/quic_client_base.cc b/quic/tools/quic_client_base.cc
index 66f28b7..53c6800 100644
--- a/quic/tools/quic_client_base.cc
+++ b/quic/tools/quic_client_base.cc
@@ -64,6 +64,7 @@
       std::unique_ptr<QuicPathValidationContext> context) override {
     QUIC_LOG(WARNING) << "Fail to validate path " << *context
                       << ", stop migrating.";
+    client_->session()->connection()->OnPathValidationFailureAtClient();
   }
 
  private:
@@ -257,6 +258,8 @@
     const QuicIpAddress& new_host,
     int port) {
   if (!connected()) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed as connection has closed";
     return false;
   }
 
@@ -264,11 +267,17 @@
   std::unique_ptr<QuicPacketWriter> writer =
       CreateWriterForNewNetwork(new_host, port);
   if (writer == nullptr) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed from writer creation";
     return false;
   }
-  session()->MigratePath(network_helper_->GetLatestClientAddress(),
-                         session()->connection()->peer_address(), writer.get(),
-                         false);
+  if (!session()->MigratePath(network_helper_->GetLatestClientAddress(),
+                              session()->connection()->peer_address(),
+                              writer.get(), false)) {
+    QUICHE_DVLOG(1)
+        << "MigrateSocketWithSpecifiedPort failed from session()->MigratePath";
+    return false;
+  }
   set_writer(writer.release());
   return true;
 }