Refactoring server preferred address (SPA) validation interfaces. And add sanity checks in QuicConnection client code to prevent client from kicking off connection migration while validating the SPA.

Change QuicConnection::ValidateServerPreferredAddress() to an abstract interface in QuicSession.

The change is client-side-only and behind connection option "SPAD".

PiperOrigin-RevId: 500024246
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index a929e3a..e340897 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -242,43 +242,6 @@
   return false;
 }
 
-// Client migrates to server preferred address on path validation suceeds.
-// Otherwise, client cleans up alternative path.
-class ServerPreferredAddressResultDelegate
-    : public QuicPathValidator::ResultDelegate {
- public:
-  explicit ServerPreferredAddressResultDelegate(QuicConnection* connection)
-      : connection_(connection) {}
-  void OnPathValidationSuccess(
-      std::unique_ptr<QuicPathValidationContext> context,
-      QuicTime /*start_time*/) override {
-    QUIC_DLOG(INFO) << "Server preferred address: " << context->peer_address()
-                    << " validated. Migrating path, self_address: "
-                    << context->self_address()
-                    << ", peer_address: " << context->peer_address();
-    connection_->mutable_stats().server_preferred_address_validated = true;
-    const bool success = connection_->MigratePath(context->self_address(),
-                                                  context->peer_address(),
-                                                  context->WriterToUse(),
-                                                  /*owns_writer*/ false);
-    QUIC_BUG_IF(failed to migrate to server preferred address, !success)
-        << "Failed to migrate to server preferred address: "
-        << context->peer_address() << " after successful validation";
-  }
-
-  void OnPathValidationFailure(
-      std::unique_ptr<QuicPathValidationContext> context) override {
-    QUIC_DLOG(INFO) << "Failed to validate server preferred address : "
-                    << context->peer_address();
-    connection_->mutable_stats().failed_to_validate_server_preferred_address =
-        true;
-    connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false);
-  }
-
- private:
-  QuicConnection* connection_;
-};
-
 }  // namespace
 
 #define ENDPOINT \
@@ -706,13 +669,12 @@
                config.HasReceivedIPv6AlternateServerAddress()) {
       server_preferred_address_ = config.ReceivedIPv6AlternateServerAddress();
     }
-    AddKnownServerAddress(server_preferred_address_);
     if (server_preferred_address_.IsInitialized()) {
       QUICHE_DLOG(INFO) << ENDPOINT << "Received server preferred address: "
                         << server_preferred_address_;
       if (config.HasClientRequestedIndependentOption(kSPA2, perspective_)) {
         accelerated_server_preferred_address_ = true;
-        ValidateServerPreferredAddress();
+        visitor_->OnServerPreferredAddressAvailable(server_preferred_address_);
       }
     }
   }
@@ -4005,7 +3967,7 @@
   if (!accelerated_server_preferred_address_ &&
       server_preferred_address_.IsInitialized()) {
     QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_);
-    ValidateServerPreferredAddress();
+    visitor_->OnServerPreferredAddressAvailable(server_preferred_address_);
   }
 }
 
@@ -6428,21 +6390,6 @@
       ->MaybeIssueNewConnectionIdForPreferredAddress();
 }
 
-void QuicConnection::ValidateServerPreferredAddress() {
-  QUICHE_DCHECK(server_preferred_address_.IsInitialized());
-  // Validate received server preferred address.
-  auto context = visitor_->CreatePathValidationContextForServerPreferredAddress(
-      server_preferred_address_);
-  if (context == nullptr) {
-    return;
-  }
-  QUICHE_DLOG(INFO) << ENDPOINT << "Start validating server preferred address: "
-                    << server_preferred_address_;
-  auto result_delegate =
-      std::make_unique<ServerPreferredAddressResultDelegate>(this);
-  ValidatePath(std::move(context), std::move(result_delegate));
-}
-
 bool QuicConnection::ShouldDetectBlackhole() const {
   if (!connected_ || blackhole_detection_disabled_) {
     return false;
@@ -6564,6 +6511,13 @@
         default_path_.stateless_reset_token);
   }
   if (path_validator_.HasPendingPathValidation()) {
+    if (perspective_ == Perspective::IS_CLIENT &&
+        IsValidatingServerPreferredAddress()) {
+      QUIC_CLIENT_HISTOGRAM_BOOL(
+          "QuicSession.ServerPreferredAddressValidationCancelled", true,
+          "How often the caller kicked off another validation while there is "
+          "an on-going server preferred address validation.");
+    }
     // Cancel and fail any earlier validation.
     path_validator_.CancelPathValidation();
   }
@@ -6603,6 +6557,10 @@
   }
   path_validator_.StartPathValidation(std::move(context),
                                       std::move(result_delegate));
+  if (perspective_ == Perspective::IS_CLIENT &&
+      IsValidatingServerPreferredAddress()) {
+    AddKnownServerAddress(server_preferred_address_);
+  }
 }
 
 bool QuicConnection::SendPathResponse(
@@ -6682,7 +6640,7 @@
   path_validator_.CancelPathValidation();
 }
 
-bool QuicConnection::UpdateConnectionIdsOnClientMigration(
+bool QuicConnection::UpdateConnectionIdsOnMigration(
     const QuicSocketAddress& self_address,
     const QuicSocketAddress& peer_address) {
   QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT);
@@ -6765,7 +6723,7 @@
   QUICHE_DCHECK(!version().UsesHttp3() || IsHandshakeConfirmed());
 
   if (connection_migration_use_new_cid_) {
-    if (!UpdateConnectionIdsOnClientMigration(self_address, peer_address)) {
+    if (!UpdateConnectionIdsOnMigration(self_address, peer_address)) {
       if (owns_writer) {
         delete writer;
       }
@@ -6800,7 +6758,8 @@
   return true;
 }
 
-void QuicConnection::OnPathValidationFailureAtClient(bool is_multi_port) {
+void QuicConnection::OnPathValidationFailureAtClient(
+    bool is_multi_port, const QuicPathValidationContext& context) {
   if (connection_migration_use_new_cid_) {
     QUICHE_DCHECK(perspective_ == Perspective::IS_CLIENT);
     alternative_path_.Clear();
@@ -6815,6 +6774,13 @@
     }
   }
 
+  if (context.peer_address() == server_preferred_address_ &&
+      server_preferred_address_ != default_path_.peer_address) {
+    QUIC_DLOG(INFO) << "Failed to validate server preferred address : "
+                    << server_preferred_address_;
+    mutable_stats().failed_to_validate_server_preferred_address = true;
+  }
+
   RetirePeerIssuedConnectionIdsOnPathValidationFailure();
 }
 
@@ -7072,8 +7038,9 @@
 
 void QuicConnection::MultiPortPathValidationResultDelegate::
     OnPathValidationFailure(
-        std::unique_ptr<QuicPathValidationContext> /*context*/) {
-  connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/true);
+        std::unique_ptr<QuicPathValidationContext> context) {
+  connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/true,
+                                               *context);
 }
 
 QuicConnection::ReversePathValidationResultDelegate::
@@ -7231,5 +7198,29 @@
       retransmittable_on_wire_timeout);
 }
 
+bool QuicConnection::IsValidatingServerPreferredAddress() const {
+  QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
+  return server_preferred_address_.IsInitialized() &&
+         server_preferred_address_ != default_path_.peer_address &&
+         path_validator_.HasPendingPathValidation() &&
+         path_validator_.GetContext()->peer_address() ==
+             server_preferred_address_;
+}
+
+void QuicConnection::OnServerPreferredAddressValidated(
+    QuicPathValidationContext& context, bool owns_writer) {
+  QUIC_DLOG(INFO) << "Server preferred address: " << context.peer_address()
+                  << " validated. Migrating path, self_address: "
+                  << context.self_address()
+                  << ", peer_address: " << context.peer_address();
+  mutable_stats().server_preferred_address_validated = true;
+  const bool success =
+      MigratePath(context.self_address(), context.peer_address(),
+                  context.WriterToUse(), owns_writer);
+  QUIC_BUG_IF(failed to migrate to server preferred address, !success)
+      << "Failed to migrate to server preferred address: "
+      << context.peer_address() << " after successful validation";
+}
+
 #undef ENDPOINT  // undef for jumbo builds
 }  // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index ca25805..5c06949 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -241,9 +241,8 @@
   virtual std::unique_ptr<QuicPathValidationContext>
   CreateContextForMultiPortPath() = 0;
 
-  // Creates context to validate server preferred address.
-  virtual std::unique_ptr<QuicPathValidationContext>
-  CreatePathValidationContextForServerPreferredAddress(
+  // Called when the client receives a preferred address from its peer.
+  virtual void OnServerPreferredAddressAvailable(
       const QuicSocketAddress& server_preferred_address) = 0;
 };
 
@@ -1219,7 +1218,8 @@
 
   // Called to clear the alternative_path_ when path validation failed on the
   // client side.
-  void OnPathValidationFailureAtClient(bool is_multi_port);
+  void OnPathValidationFailureAtClient(
+      bool is_multi_port, const QuicPathValidationContext& context);
 
   void SetSourceAddressTokenToSend(absl::string_view token);
 
@@ -1275,6 +1275,16 @@
   // Kicks off validation of received server preferred address.
   void ValidateServerPreferredAddress();
 
+  // Returns true if the client is validating the server preferred address which
+  // hasn't been used before.
+  bool IsValidatingServerPreferredAddress() const;
+
+  // Called by client to start sending packets to the preferred address.
+  // If |owns_writer| is true, the ownership of the writer in the |context| is
+  // also passed in.
+  void OnServerPreferredAddressValidated(QuicPathValidationContext& context,
+                                         bool owns_writer);
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -1604,11 +1614,11 @@
   // Returns true if header contains valid server connection ID.
   bool ValidateServerConnectionId(const QuicPacketHeader& header) const;
 
-  // Update the connection IDs when client migrates with/without validation.
+  // Update the connection IDs when client migrates its own address
+  // (with/without validation) or switches to server preferred address.
   // Returns false if required connection ID is not available.
-  bool UpdateConnectionIdsOnClientMigration(
-      const QuicSocketAddress& self_address,
-      const QuicSocketAddress& peer_address);
+  bool UpdateConnectionIdsOnMigration(const QuicSocketAddress& self_address,
+                                      const QuicSocketAddress& peer_address);
 
   // Retire active peer issued connection IDs after they are no longer used on
   // any path.
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 874eeb1..e1e318b 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -102,6 +102,13 @@
 const QuicSocketAddress kSelfAddress =
     QuicSocketAddress(QuicIpAddress::Loopback6(),
                       /*port=*/443);
+const QuicSocketAddress kServerPreferredAddress = QuicSocketAddress(
+    []() {
+      QuicIpAddress address;
+      address.FromString("2604:31c0::");
+      return address;
+    }(),
+    /*port=*/443);
 
 QuicStreamId GetNthClientInitiatedStreamId(int n,
                                            QuicTransportVersion version) {
@@ -1468,9 +1475,6 @@
     ASSERT_TRUE(version().HasIetfQuicFrames());
     ASSERT_TRUE(connection_.self_address().host().IsIPv6());
     SetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2, true);
-    QuicIpAddress host;
-    host.FromString("2604:31c0::");
-    QuicSocketAddress server_preferred_address(host, 443);
     const QuicConnectionId connection_id = TestConnectionId(17);
     const StatelessResetToken reset_token =
         QuicUtils::GenerateStatelessResetToken(connection_id);
@@ -1491,7 +1495,7 @@
     QuicConfigPeer::SetReceivedStatelessResetToken(&config,
                                                    kTestStatelessResetToken);
     QuicConfigPeer::SetReceivedAlternateServerAddress(&config,
-                                                      server_preferred_address);
+                                                      kServerPreferredAddress);
     QuicConfigPeer::SetPreferredAddressConnectionIdAndToken(
         &config, connection_id, reset_token);
     EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
@@ -1499,7 +1503,7 @@
 
     ASSERT_TRUE(QuicConnectionPeer::GetServerPreferredAddress(&connection_)
                     .IsInitialized());
-    EXPECT_EQ(server_preferred_address,
+    EXPECT_EQ(kServerPreferredAddress,
               QuicConnectionPeer::GetServerPreferredAddress(&connection_));
   }
 
@@ -2514,7 +2518,8 @@
     EXPECT_EQ(expected_self_address_, context->self_address());
     EXPECT_EQ(expected_peer_address_, context->peer_address());
     if (connection_->perspective() == Perspective::IS_CLIENT) {
-      connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false);
+      connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false,
+                                                   *context);
     }
     *success_ = false;
   }
@@ -2526,6 +2531,32 @@
   bool* success_;
 };
 
+// A test implementation which migrates to server preferred address
+// on path validation suceeds. Otherwise, client cleans up alternative path.
+class QUICHE_EXPORT ServerPreferredAddressTestResultDelegate
+    : public QuicPathValidator::ResultDelegate {
+ public:
+  explicit ServerPreferredAddressTestResultDelegate(QuicConnection* connection)
+      : connection_(connection) {}
+  void OnPathValidationSuccess(
+      std::unique_ptr<QuicPathValidationContext> context,
+      QuicTime /*start_time*/) override {
+    connection_->OnServerPreferredAddressValidated(*context, false);
+  }
+
+  void OnPathValidationFailure(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false,
+                                                 *context);
+  }
+
+ protected:
+  QuicConnection* connection() { return connection_; }
+
+ private:
+  QuicConnection* connection_;
+};
+
 // Receive a path probe request at the server side, i.e.,
 // in non-IETF version: receive a padded PING packet with a peer addess change;
 // in IETF version: receive a packet contains PATH CHALLENGE with peer address
@@ -2946,6 +2977,53 @@
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 }
 
+TEST_P(QuicConnectionTest,
+       PeerAddressChangesToPreferredAddressBeforeClientInitiates) {
+  if (!version().HasIetfQuicFrames()) {
+    return;
+  }
+  ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective());
+  ASSERT_TRUE(connection_.self_address().host().IsIPv6());
+  SetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2, true);
+  const QuicConnectionId connection_id = TestConnectionId(17);
+  const StatelessResetToken reset_token =
+      QuicUtils::GenerateStatelessResetToken(connection_id);
+
+  connection_.CreateConnectionIdManager();
+
+  connection_.SendCryptoStreamData();
+  EXPECT_CALL(*loss_algorithm_, DetectLosses(_, _, _, _, _, _));
+  EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+  QuicAckFrame frame = InitAckFrame(1);
+  // Received ACK for packet 1.
+  ProcessFramePacketAtLevel(1, QuicFrame(&frame), ENCRYPTION_INITIAL);
+  // Discard INITIAL key.
+  connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
+  QuicConfigPeer::SetReceivedStatelessResetToken(&config,
+                                                 kTestStatelessResetToken);
+  QuicConfigPeer::SetReceivedAlternateServerAddress(&config,
+                                                    kServerPreferredAddress);
+  QuicConfigPeer::SetPreferredAddressConnectionIdAndToken(
+      &config, connection_id, reset_token);
+  EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+  connection_.SetFromConfig(config);
+  EXPECT_EQ(kPeerAddress, connection_.peer_address());
+  EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+  ASSERT_TRUE(QuicConnectionPeer::GetServerPreferredAddress(&connection_)
+                  .IsInitialized());
+  EXPECT_EQ(kServerPreferredAddress,
+            QuicConnectionPeer::GetServerPreferredAddress(&connection_));
+
+  EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(0);
+  ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress,
+                                  kServerPreferredAddress, ENCRYPTION_INITIAL);
+}
+
 TEST_P(QuicConnectionTest, MaxPacketSize) {
   EXPECT_EQ(Perspective::IS_CLIENT, connection_.perspective());
   EXPECT_EQ(1250u, connection_.max_packet_length());
@@ -15989,8 +16067,6 @@
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD});
   ServerPreferredAddressInit(config);
-  const QuicSocketAddress kServerPreferredAddress =
-      QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
@@ -16001,11 +16077,15 @@
       .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
   // Kick off path validation of server preferred address on handshake
   // confirmed.
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   connection_.OnHandshakeComplete();
   EXPECT_TRUE(connection_.HasPendingPathValidation());
   EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
@@ -16027,7 +16107,7 @@
   EXPECT_TRUE(connection_.IsValidStatelessResetToken(kTestStatelessResetToken));
   EXPECT_FALSE(connection_.IsValidStatelessResetToken(kNewStatelessResetToken));
 
-  // Receive path challenge from server preferred address.
+  // Receive path response from server preferred address.
   QuicFrames frames;
   frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload)));
   // Verify send_algorithm gets reset after migration (new sent packet is not
@@ -16069,8 +16149,6 @@
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD});
   ServerPreferredAddressInit(config);
-  const QuicSocketAddress kServerPreferredAddress =
-      QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
@@ -16079,11 +16157,15 @@
       .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
   // Kick off path validation of server preferred address on handshake
   // confirmed.
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   connection_.OnHandshakeComplete();
   EXPECT_TRUE(connection_.HasPendingPathValidation());
   ASSERT_FALSE(new_writer.path_challenge_frames().empty());
@@ -16096,7 +16178,7 @@
             writer_->last_packet_header().destination_connection_id);
   EXPECT_EQ(kPeerAddress, writer_->last_write_peer_address());
 
-  // Receive path challenge from original server address.
+  // Receive path response from original server address.
   QuicFrames frames;
   frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload)));
   ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
@@ -16135,8 +16217,6 @@
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD});
   ServerPreferredAddressInit(config);
-  const QuicSocketAddress kServerPreferredAddress =
-      QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
@@ -16145,13 +16225,17 @@
       .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
   // Kick off path validation of server preferred address on handshake
   // confirmed.
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   connection_.OnHandshakeComplete();
-  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  EXPECT_TRUE(connection_.IsValidatingServerPreferredAddress());
   EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
       &connection_, kNewSelfAddress, kServerPreferredAddress));
   ASSERT_FALSE(new_writer.path_challenge_frames().empty());
@@ -16200,17 +16284,18 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  QuicIpAddress host;
-  host.FromString("2604:31c0::");
-  const QuicSocketAddress kServerPreferredAddress(host, 443);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
   ServerPreferredAddressInit(config);
@@ -16236,17 +16321,18 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  QuicIpAddress host;
-  host.FromString("2604:31c0::");
-  const QuicSocketAddress kServerPreferredAddress(host, 443);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
   ServerPreferredAddressInit(config);
@@ -16278,17 +16364,18 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  QuicIpAddress host;
-  host.FromString("2604:31c0::");
-  const QuicSocketAddress kServerPreferredAddress(host, 443);
   const QuicSocketAddress kNewSelfAddress =
       QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
   TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
-  EXPECT_CALL(visitor_, CreatePathValidationContextForServerPreferredAddress(
-                            kServerPreferredAddress))
-      .WillOnce(Return(
-          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
-              kNewSelfAddress, kServerPreferredAddress, &new_writer))));
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
   QuicConfig config;
   config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
   ServerPreferredAddressInit(config);
@@ -16318,6 +16405,96 @@
   EXPECT_TRUE(new_writer.ping_frames().empty());
 }
 
+TEST_P(QuicConnectionTest, MultiPortCreationAfterServerMigration) {
+  if (!GetParam().version.HasIetfQuicFrames()) {
+    return;
+  }
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kMPQC, kSPAD});
+  ServerPreferredAddressInit(config);
+  if (!connection_.connection_migration_use_new_cid()) {
+    return;
+  }
+  connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  QuicConnectionId cid_for_preferred_address = TestConnectionId(17);
+  const QuicSocketAddress kNewSelfAddress =
+      QuicSocketAddress(QuicIpAddress::Loopback6(), /*port=*/23456);
+  TestPacketWriter new_writer(version(), &clock_, Perspective::IS_CLIENT);
+  EXPECT_CALL(visitor_,
+              OnServerPreferredAddressAvailable(kServerPreferredAddress))
+      .WillOnce(Invoke([&]() {
+        connection_.ValidatePath(
+            std::make_unique<TestQuicPathValidationContext>(
+                kNewSelfAddress, kServerPreferredAddress, &new_writer),
+            std::make_unique<ServerPreferredAddressTestResultDelegate>(
+                &connection_));
+      }));
+  // The connection should start probing the preferred address after handshake
+  // confirmed.
+  QuicPathFrameBuffer payload;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(testing::AtLeast(1u))
+      .WillOnce(Invoke([&]() {
+        EXPECT_EQ(1u, new_writer.path_challenge_frames().size());
+        payload = new_writer.path_challenge_frames().front().data_buffer;
+        EXPECT_EQ(kServerPreferredAddress,
+                  new_writer.last_write_peer_address());
+      }));
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  EXPECT_TRUE(connection_.IsValidatingServerPreferredAddress());
+
+  // Receiving PATH_RESPONSE should cause the connection to migrate to the
+  // preferred address.
+  QuicFrames frames;
+  frames.push_back(QuicFrame(QuicPathResponseFrame(99, payload)));
+  ProcessFramesPacketWithAddresses(frames, kNewSelfAddress, kPeerAddress,
+                                   ENCRYPTION_FORWARD_SECURE);
+  EXPECT_FALSE(connection_.IsValidatingServerPreferredAddress());
+  EXPECT_EQ(kServerPreferredAddress, connection_.effective_peer_address());
+  EXPECT_EQ(kNewSelfAddress, connection_.self_address());
+  EXPECT_EQ(connection_.connection_id(), cid_for_preferred_address);
+
+  // As the default path changed, the server issued CID 1 should be retired.
+  auto* retire_peer_issued_cid_alarm =
+      connection_.GetRetirePeerIssuedConnectionIdAlarm();
+  ASSERT_TRUE(retire_peer_issued_cid_alarm->IsSet());
+  EXPECT_CALL(visitor_, SendRetireConnectionId(/*sequence_number=*/0u));
+  retire_peer_issued_cid_alarm->Fire();
+
+  const QuicSocketAddress kNewSelfAddress2(kNewSelfAddress.host(),
+                                           kNewSelfAddress.port() + 1);
+  EXPECT_NE(kNewSelfAddress2, kNewSelfAddress);
+  TestPacketWriter new_writer2(version(), &clock_, Perspective::IS_CLIENT);
+  QuicNewConnectionIdFrame frame;
+  frame.connection_id = TestConnectionId(789);
+  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 = 2u;
+  EXPECT_CALL(visitor_, CreateContextForMultiPortPath())
+      .WillOnce(Return(
+          testing::ByMove(std::make_unique<TestQuicPathValidationContext>(
+              kNewSelfAddress2, connection_.peer_address(), &new_writer2))));
+  connection_.OnNewConnectionIdFrame(frame);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  EXPECT_EQ(1u, new_writer.path_challenge_frames().size());
+  payload = new_writer.path_challenge_frames().front().data_buffer;
+  EXPECT_EQ(kServerPreferredAddress, new_writer.last_write_peer_address());
+  EXPECT_EQ(kNewSelfAddress2.host(), new_writer.last_write_source_address());
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kNewSelfAddress2, connection_.peer_address()));
+  auto* alt_path = QuicConnectionPeer::GetAlternativePath(&connection_);
+  EXPECT_FALSE(alt_path->validated);
+  QuicFrames frames2;
+  frames2.push_back(QuicFrame(QuicPathResponseFrame(99, payload)));
+  ProcessFramesPacketWithAddresses(frames2, kNewSelfAddress2, kPeerAddress,
+                                   ENCRYPTION_FORWARD_SECURE);
+  EXPECT_TRUE(alt_path->validated);
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_session.h b/quiche/quic/core/quic_session.h
index 6576325..bf9531a 100644
--- a/quiche/quic/core/quic_session.h
+++ b/quiche/quic/core/quic_session.h
@@ -179,12 +179,8 @@
       override {
     return nullptr;
   }
-
-  std::unique_ptr<QuicPathValidationContext>
-  CreatePathValidationContextForServerPreferredAddress(
-      const QuicSocketAddress& /*server_preferred_address*/) override {
-    return nullptr;
-  }
+  void OnServerPreferredAddressAvailable(
+      const QuicSocketAddress& /*server_preferred_address*/) override {}
 
   // QuicStreamFrameDataProducer
   WriteStreamDataResult WriteStreamData(QuicStreamId id,
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index f2e38f5..d8271e1 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -502,10 +502,8 @@
   MOCK_METHOD(bool, MaybeSendAddressToken, (), (override));
   MOCK_METHOD(std::unique_ptr<QuicPathValidationContext>,
               CreateContextForMultiPortPath, (), (override));
-  MOCK_METHOD(std::unique_ptr<QuicPathValidationContext>,
-              CreatePathValidationContextForServerPreferredAddress,
+  MOCK_METHOD(void, OnServerPreferredAddressAvailable,
               (const QuicSocketAddress&), (override));
-
   void OnBandwidthUpdateTimeout() override {}
 };
 
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint.h b/quiche/quic/test_tools/simulator/quic_endpoint.h
index d41bef3..1bb9ce0 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint.h
+++ b/quiche/quic/test_tools/simulator/quic_endpoint.h
@@ -109,11 +109,8 @@
       override {
     return nullptr;
   }
-  std::unique_ptr<QuicPathValidationContext>
-  CreatePathValidationContextForServerPreferredAddress(
-      const QuicSocketAddress& /*server_preferred_address*/) override {
-    return nullptr;
-  }
+  void OnServerPreferredAddressAvailable(
+      const QuicSocketAddress& /*server_preferred_address*/) override {}
 
   // End QuicConnectionVisitorInterface implementation.
 
diff --git a/quiche/quic/tools/quic_client_base.cc b/quiche/quic/tools/quic_client_base.cc
index 948707a..de77089 100644
--- a/quiche/quic/tools/quic_client_base.cc
+++ b/quiche/quic/tools/quic_client_base.cc
@@ -50,7 +50,7 @@
     QUIC_LOG(WARNING) << "Fail to validate path " << *context
                       << ", stop migrating.";
     client_->session()->connection()->OnPathValidationFailureAtClient(
-        /*is_multi_port=*/false);
+        /*is_multi_port=*/false, *context);
   }
 
  private:
@@ -469,7 +469,7 @@
     QUIC_LOG(WARNING) << "Fail to validate path " << *context
                       << ", stop migrating.";
     client_->session()->connection()->OnPathValidationFailureAtClient(
-        /*is_multi_port=*/false);
+        /*is_multi_port=*/false, *context);
   }
 
  private:
diff --git a/quiche/quic/tools/quic_simple_client_session.cc b/quiche/quic/tools/quic_simple_client_session.cc
index c1e1b2a..9de95d8 100644
--- a/quiche/quic/tools/quic_simple_client_session.cc
+++ b/quiche/quic/tools/quic_simple_client_session.cc
@@ -6,8 +6,39 @@
 
 #include <utility>
 
+#include "quiche/quic/core/quic_path_validator.h"
+
 namespace quic {
 
+namespace {
+
+class QUICHE_EXPORT ServerPreferredAddressResultDelegateWithWriter
+    : public QuicPathValidator::ResultDelegate {
+ public:
+  explicit ServerPreferredAddressResultDelegateWithWriter(
+      QuicConnection* connection)
+      : connection_(connection) {}
+
+  // Overridden to transfer the ownership of the new writer.
+  void OnPathValidationSuccess(
+      std::unique_ptr<QuicPathValidationContext> /*context*/,
+      QuicTime /*start_time*/) override {
+    // TODO(danzh) hand over the ownership of the released writer to client and
+    // call the connection to migrate.
+  }
+
+  void OnPathValidationFailure(
+      std::unique_ptr<QuicPathValidationContext> context) override {
+    connection_->OnPathValidationFailureAtClient(/*is_multi_port=*/false,
+                                                 *context);
+  }
+
+ private:
+  QuicConnection* connection_ = nullptr;
+};
+
+}  // namespace
+
 QuicSimpleClientSession::QuicSimpleClientSession(
     const QuicConfig& config, const ParsedQuicVersionVector& supported_versions,
     QuicConnection* connection, QuicClientBase::NetworkHelper* network_helper,
@@ -56,22 +87,24 @@
       network_helper_->GetLatestClientAddress(), peer_address());
 }
 
-std::unique_ptr<QuicPathValidationContext>
-QuicSimpleClientSession::CreatePathValidationContextForServerPreferredAddress(
+void QuicSimpleClientSession::OnServerPreferredAddressAvailable(
     const QuicSocketAddress& server_preferred_address) {
   const auto self_address = connection()->self_address();
   if (network_helper_ == nullptr ||
       !network_helper_->CreateUDPSocketAndBind(server_preferred_address,
                                                self_address.host(), 0)) {
-    return nullptr;
+    return;
   }
   QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();
   if (writer == nullptr) {
-    return nullptr;
+    return;
   }
-  return std::make_unique<PathMigrationContext>(
-      std::unique_ptr<QuicPacketWriter>(writer),
-      network_helper_->GetLatestClientAddress(), server_preferred_address);
+  connection()->ValidatePath(
+      std::make_unique<PathMigrationContext>(
+          std::unique_ptr<QuicPacketWriter>(writer),
+          network_helper_->GetLatestClientAddress(), server_preferred_address),
+      std::make_unique<ServerPreferredAddressResultDelegateWithWriter>(
+          connection()));
 }
 
 }  // namespace quic
diff --git a/quiche/quic/tools/quic_simple_client_session.h b/quiche/quic/tools/quic_simple_client_session.h
index 631b36b..4e06cc2 100644
--- a/quiche/quic/tools/quic_simple_client_session.h
+++ b/quiche/quic/tools/quic_simple_client_session.h
@@ -27,8 +27,7 @@
   HttpDatagramSupport LocalHttpDatagramSupport() override;
   std::unique_ptr<QuicPathValidationContext> CreateContextForMultiPortPath()
       override;
-  std::unique_ptr<QuicPathValidationContext>
-  CreatePathValidationContextForServerPreferredAddress(
+  void OnServerPreferredAddressAvailable(
       const QuicSocketAddress& server_preferred_address) override;
   bool drop_response_body() const { return drop_response_body_; }