Implement QUIC server preferred address (SPA) on server side. Basically advertising SPA and handling clients migrating to using SPA.

Current implementation is a half-way SPA which advertises SPA in transport parameters and accepts packets received on SPA, but the server doesn't validate the migration nor does it switch to use SPA to send packets(go/quic_server_migration).

The behavior is behind config knobs QuicConfig::SetIPv(4|6)AlternateServerAddressToSend().

PiperOrigin-RevId: 505741961
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 4625ebb..1f8d6fe 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -440,6 +440,37 @@
     // to connect to the server.
     StartServer();
 
+    if (use_preferred_address_) {
+      // At this point, the server has an ephemeral port to listen on. Restart
+      // the server with the preferred address.
+      StopServer();
+      // server_address_ now contains the random listening port.
+      server_preferred_address_ =
+          QuicSocketAddress(TestLoopback(2), server_address_.port());
+      if (server_preferred_address_ == server_address_) {
+        ADD_FAILURE() << "Preferred address and server address are the same "
+                      << server_address_;
+        return false;
+      }
+      // Send server preferred address and let server listen on Any.
+      if (server_preferred_address_.host().IsIPv4()) {
+        server_listening_address_ =
+            QuicSocketAddress(QuicIpAddress::Any4(), server_address_.port());
+        server_config_.SetIPv4AlternateServerAddressToSend(
+            server_preferred_address_);
+      } else {
+        server_listening_address_ =
+            QuicSocketAddress(QuicIpAddress::Any6(), server_address_.port());
+        server_config_.SetIPv6AlternateServerAddressToSend(
+            server_preferred_address_);
+      }
+      // Server restarts.
+      server_writer_ = new PacketDroppingTestWriter();
+      StartServer();
+
+      client_config_.SetConnectionOptionsToSend(QuicTagVector{kRVCM, kSPAD});
+    }
+
     if (!connect_to_server_on_initialize_) {
       initialized_ = true;
       return true;
@@ -918,6 +949,8 @@
   uint8_t expected_server_connection_id_length_;
   bool enable_web_transport_ = false;
   std::vector<std::string> received_webtransport_unidirectional_streams_;
+  bool use_preferred_address_ = false;
+  QuicSocketAddress server_preferred_address_;
 };
 
 // Run all end to end tests with all supported versions.
@@ -5477,80 +5510,54 @@
 }
 
 TEST_P(EndToEndTest, SimpleServerPreferredAddressTest) {
+  use_preferred_address_ = true;
   ASSERT_TRUE(Initialize());
   if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
-  client_->Disconnect();
-  StopServer();
-  // server_address_ now contains the random listening port.
-  const QuicSocketAddress kServerPreferredAddress =
-      QuicSocketAddress(TestLoopback(2), server_address_.port());
-  ASSERT_NE(server_address_, kServerPreferredAddress);
-  // Send server preferred address and let server listen on Any.
-  if (kServerPreferredAddress.host().IsIPv4()) {
-    server_listening_address_ =
-        QuicSocketAddress(QuicIpAddress::Any4(), server_address_.port());
-    server_config_.SetIPv4AlternateServerAddressToSend(kServerPreferredAddress);
-  } else {
-    server_listening_address_ =
-        QuicSocketAddress(QuicIpAddress::Any6(), server_address_.port());
-    server_config_.SetIPv6AlternateServerAddressToSend(kServerPreferredAddress);
-  }
-  // Server restarts.
-  server_writer_ = new PacketDroppingTestWriter();
-  StartServer();
-
-  client_config_.SetConnectionOptionsToSend(QuicTagVector{kRVCM, kSPAD});
   client_.reset(CreateQuicClient(nullptr));
+  QuicConnection* client_connection = GetClientConnection();
   EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+  EXPECT_EQ(server_address_, client_connection->effective_peer_address());
+  EXPECT_EQ(server_address_, client_connection->peer_address());
+  EXPECT_TRUE(client_->client()->HasPendingPathValidation());
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+
+  SendSynchronousFooRequestAndCheckResponse();
   while (client_->client()->HasPendingPathValidation()) {
     client_->client()->WaitForEvents();
   }
-  // TODO(b/262386897): Currently, server drops packets received on preferred
-  // address because self address change is disallowed.
+  EXPECT_EQ(server_preferred_address_,
+            client_connection->effective_peer_address());
+  EXPECT_EQ(server_preferred_address_, client_connection->peer_address());
+  EXPECT_NE(server_cid1, client_connection->connection_id());
+
   const auto client_stats = GetClientConnection()->GetStats();
-  EXPECT_FALSE(client_stats.server_preferred_address_validated);
-  EXPECT_TRUE(client_stats.failed_to_validate_server_preferred_address);
+  EXPECT_TRUE(client_stats.server_preferred_address_validated);
+  EXPECT_FALSE(client_stats.failed_to_validate_server_preferred_address);
 }
 
 TEST_P(EndToEndTest, OptimizedServerPreferredAddress) {
+  use_preferred_address_ = true;
   ASSERT_TRUE(Initialize());
   if (!GetClientConnection()->connection_migration_use_new_cid()) {
     return;
   }
-  client_->Disconnect();
-  StopServer();
-  // server_address_ now contains the random listening port.
-  const QuicSocketAddress kServerPreferredAddress =
-      QuicSocketAddress(TestLoopback(2), server_address_.port());
-  ASSERT_NE(server_address_, kServerPreferredAddress);
-  // Send server preferred address and let server listen on Any.
-  if (kServerPreferredAddress.host().IsIPv4()) {
-    server_listening_address_ =
-        QuicSocketAddress(QuicIpAddress::Any4(), server_address_.port());
-    server_config_.SetIPv4AlternateServerAddressToSend(kServerPreferredAddress);
-  } else {
-    server_listening_address_ =
-        QuicSocketAddress(QuicIpAddress::Any6(), server_address_.port());
-    server_config_.SetIPv6AlternateServerAddressToSend(kServerPreferredAddress);
-  }
-  // Server restarts.
-  server_writer_ = new PacketDroppingTestWriter();
-  StartServer();
-
-  client_config_.SetConnectionOptionsToSend(QuicTagVector{kRVCM, kSPAD});
   client_config_.SetClientConnectionOptions(QuicTagVector{kSPA2});
   client_.reset(CreateQuicClient(nullptr));
-  EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+  QuicConnection* client_connection = GetClientConnection();
+  EXPECT_TRUE(client_->client()->WaitForOneRttKeysAvailable());
+  EXPECT_EQ(server_address_, client_connection->effective_peer_address());
+  EXPECT_EQ(server_address_, client_connection->peer_address());
+  EXPECT_TRUE(client_->client()->HasPendingPathValidation());
+  SendSynchronousFooRequestAndCheckResponse();
   while (client_->client()->HasPendingPathValidation()) {
     client_->client()->WaitForEvents();
   }
-  // TODO(b/262386897): Currently, server drops packets received on preferred
-  // address because self address change is disallowed.
+
   const auto client_stats = GetClientConnection()->GetStats();
-  EXPECT_FALSE(client_stats.server_preferred_address_validated);
-  EXPECT_TRUE(client_stats.failed_to_validate_server_preferred_address);
+  EXPECT_TRUE(client_stats.server_preferred_address_validated);
+  EXPECT_FALSE(client_stats.failed_to_validate_server_preferred_address);
 }
 
 TEST_P(EndToEndPacketReorderingTest, ReorderedPathChallenge) {
@@ -7171,6 +7178,124 @@
   client_->Disconnect();
 }
 
+TEST_P(EndToEndTest, ClientMigrationAfterHalfwayServerMigration) {
+  use_preferred_address_ = true;
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
+    return;
+  }
+  client_.reset(EndToEndTest::CreateQuicClient(nullptr));
+  QuicConnection* client_connection = GetClientConnection();
+  EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+  EXPECT_EQ(server_address_, client_connection->effective_peer_address());
+  EXPECT_EQ(server_address_, client_connection->peer_address());
+  EXPECT_TRUE(client_->client()->HasPendingPathValidation());
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+
+  SendSynchronousFooRequestAndCheckResponse();
+  EXPECT_TRUE(client_->WaitUntil(
+      1000, [&]() { return !client_->client()->HasPendingPathValidation(); }));
+  EXPECT_EQ(server_preferred_address_,
+            client_connection->effective_peer_address());
+  EXPECT_EQ(server_preferred_address_, client_connection->peer_address());
+  EXPECT_NE(server_cid1, client_connection->connection_id());
+  EXPECT_EQ(0u,
+            client_connection->GetStats().num_connectivity_probing_received);
+  const auto client_stats = GetClientConnection()->GetStats();
+  EXPECT_TRUE(client_stats.server_preferred_address_validated);
+  EXPECT_FALSE(client_stats.failed_to_validate_server_preferred_address);
+
+  WaitForNewConnectionIds();
+  // Migrate socket to a new IP address.
+  QuicIpAddress host = TestLoopback(2);
+  ASSERT_NE(
+      client_->client()->network_helper()->GetLatestClientAddress().host(),
+      host);
+  ASSERT_TRUE(client_->client()->ValidateAndMigrateSocket(host));
+  EXPECT_TRUE(client_->WaitUntil(
+      1000, [&]() { return !client_->client()->HasPendingPathValidation(); }));
+  EXPECT_EQ(host, client_->client()->session()->self_address().host());
+
+  SendSynchronousBarRequestAndCheckResponse();
+
+  // Wait for the PATH_CHALLENGE.
+  EXPECT_TRUE(client_->WaitUntil(1000, [&]() {
+    return client_connection->GetStats().num_connectivity_probing_received >= 1;
+  }));
+
+  // Send another request to ensure that the server will have time to finish the
+  // reverse path validation and send address token.
+  SendSynchronousBarRequestAndCheckResponse();
+  // By the time the above request is completed, the PATH_RESPONSE must have
+  // been received by the server. Check server stats.
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  EXPECT_FALSE(server_connection->HasPendingPathValidation());
+  EXPECT_EQ(2u, server_connection->GetStats().num_validated_peer_migration);
+  EXPECT_EQ(2u, server_connection->GetStats().num_new_connection_id_sent);
+  server_thread_->Resume();
+}
+
+TEST_P(EndToEndTest, MultiPortCreationFollowingServerMigration) {
+  use_preferred_address_ = true;
+  ASSERT_TRUE(Initialize());
+  if (!GetClientConnection()->connection_migration_use_new_cid()) {
+    return;
+  }
+
+  client_config_.SetClientConnectionOptions(QuicTagVector{kMPQC});
+  client_.reset(EndToEndTest::CreateQuicClient(nullptr));
+  QuicConnection* client_connection = GetClientConnection();
+  EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+  EXPECT_EQ(server_address_, client_connection->effective_peer_address());
+  EXPECT_EQ(server_address_, client_connection->peer_address());
+  QuicConnectionId server_cid1 = client_connection->connection_id();
+  EXPECT_TRUE(client_connection->IsValidatingServerPreferredAddress());
+
+  SendSynchronousFooRequestAndCheckResponse();
+  EXPECT_TRUE(client_->WaitUntil(1000, [&]() {
+    return !client_connection->IsValidatingServerPreferredAddress();
+  }));
+  EXPECT_EQ(server_preferred_address_,
+            client_connection->effective_peer_address());
+  EXPECT_EQ(server_preferred_address_, client_connection->peer_address());
+  const auto client_stats = GetClientConnection()->GetStats();
+  EXPECT_TRUE(client_stats.server_preferred_address_validated);
+  EXPECT_FALSE(client_stats.failed_to_validate_server_preferred_address);
+
+  QuicConnectionId server_cid2 = client_connection->connection_id();
+  EXPECT_NE(server_cid1, server_cid2);
+  EXPECT_TRUE(client_->WaitUntil(1000, [&]() {
+    return client_connection->GetStats().num_path_response_received == 2;
+  }));
+  EXPECT_TRUE(
+      QuicConnectionPeer::IsAlternativePathValidated(client_connection));
+  QuicConnectionId server_cid3 =
+      QuicConnectionPeer::GetServerConnectionIdOnAlternativePath(
+          client_connection);
+  EXPECT_NE(server_cid2, server_cid3);
+  EXPECT_NE(server_cid1, server_cid3);
+}
+
+TEST_P(EndToEndTest, DoNotAdvertisePreferredAddressWithoutSPAD) {
+  if (!version_.HasIetfQuicFrames()) {
+    ASSERT_TRUE(Initialize());
+    return;
+  }
+  server_config_.SetIPv4AlternateServerAddressToSend(
+      QuicSocketAddress(QuicIpAddress::Any4(), 12345));
+  server_config_.SetIPv6AlternateServerAddressToSend(
+      QuicSocketAddress(QuicIpAddress::Any6(), 12345));
+  NiceMock<MockQuicConnectionDebugVisitor> visitor;
+  connection_debug_visitor_ = &visitor;
+  EXPECT_CALL(visitor, OnTransportParametersReceived(_))
+      .WillOnce(Invoke([](const TransportParameters& transport_parameters) {
+        EXPECT_EQ(nullptr, transport_parameters.preferred_address);
+      }));
+  ASSERT_TRUE(Initialize());
+  EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_config.cc b/quiche/quic/core/quic_config.cc
index f8895b0..43f4bbd 100644
--- a/quiche/quic/core/quic_config.cc
+++ b/quiche/quic/core/quic_config.cc
@@ -373,6 +373,11 @@
   send_value_ = value;
 }
 
+void QuicFixedSocketAddress::ClearSendValue() {
+  has_send_value_ = false;
+  send_value_ = QuicSocketAddress();
+}
+
 bool QuicFixedSocketAddress::HasReceivedValue() const {
   return has_receive_value_;
 }
@@ -850,7 +855,7 @@
 
 void QuicConfig::SetIPv6AlternateServerAddressToSend(
     const QuicSocketAddress& alternate_server_address_ipv6) {
-  if (!alternate_server_address_ipv6.host().IsIPv6()) {
+  if (!alternate_server_address_ipv6.Normalized().host().IsIPv6()) {
     QUIC_BUG(quic_bug_10575_9)
         << "Cannot use SetIPv6AlternateServerAddressToSend with "
         << alternate_server_address_ipv6;
@@ -891,7 +896,9 @@
 void QuicConfig::SetPreferredAddressConnectionIdAndTokenToSend(
     const QuicConnectionId& connection_id,
     const StatelessResetToken& stateless_reset_token) {
-  if (!CanSendPreferredAddressConnectionIdAndToken()) {
+  if ((!alternate_server_address_ipv4_.HasSendValue() &&
+       !alternate_server_address_ipv6_.HasSendValue()) ||
+      preferred_address_connection_id_and_token_.has_value()) {
     QUIC_BUG(quic_bug_10575_17)
         << "Can not send connection ID and token for preferred address";
     return;
@@ -900,14 +907,6 @@
       std::make_pair(connection_id, stateless_reset_token);
 }
 
-bool QuicConfig::CanSendPreferredAddressConnectionIdAndToken() const {
-  if (!alternate_server_address_ipv4_.HasSendValue() &&
-      !alternate_server_address_ipv6_.HasSendValue()) {
-    return false;
-  }
-  return !preferred_address_connection_id_and_token_.has_value();
-}
-
 bool QuicConfig::HasReceivedPreferredAddressConnectionIdAndToken() const {
   return (HasReceivedIPv6AlternateServerAddress() ||
           HasReceivedIPv4AlternateServerAddress()) &&
@@ -1409,4 +1408,27 @@
   received_google_handshake_message_.reset();
 }
 
+absl::optional<QuicSocketAddress> QuicConfig::GetPreferredAddressToSend(
+    quiche::IpAddressFamily address_family) const {
+  if (alternate_server_address_ipv6_.HasSendValue() &&
+      address_family == quiche::IpAddressFamily::IP_V6) {
+    return alternate_server_address_ipv6_.GetSendValue();
+  }
+
+  if (alternate_server_address_ipv4_.HasSendValue() &&
+      address_family == quiche::IpAddressFamily::IP_V4) {
+    return alternate_server_address_ipv4_.GetSendValue();
+  }
+  return absl::nullopt;
+}
+
+void QuicConfig::ClearAlternateServerAddressToSend(
+    quiche::IpAddressFamily address_family) {
+  if (address_family == quiche::IpAddressFamily::IP_V4) {
+    alternate_server_address_ipv4_.ClearSendValue();
+  } else if (address_family == quiche::IpAddressFamily::IP_V6) {
+    alternate_server_address_ipv6_.ClearSendValue();
+  }
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_config.h b/quiche/quic/core/quic_config.h
index 52cc3b4..f60b481 100644
--- a/quiche/quic/core/quic_config.h
+++ b/quiche/quic/core/quic_config.h
@@ -216,6 +216,8 @@
 
   void SetSendValue(const QuicSocketAddress& value);
 
+  void ClearSendValue();
+
   bool HasReceivedValue() const;
 
   const QuicSocketAddress& GetReceivedValue() const;
@@ -407,14 +409,15 @@
   void SetPreferredAddressConnectionIdAndTokenToSend(
       const QuicConnectionId& connection_id,
       const StatelessResetToken& stateless_reset_token);
-  // Returns true if server preferred address has been set via
-  // SetIPv(4|6)AlternateServerAddressToSend.
-  bool CanSendPreferredAddressConnectionIdAndToken() const;
 
   // Preferred Address Connection ID and Token.
   bool HasReceivedPreferredAddressConnectionIdAndToken() const;
   const std::pair<QuicConnectionId, StatelessResetToken>&
   ReceivedPreferredAddressConnectionIdAndToken() const;
+  absl::optional<QuicSocketAddress> GetPreferredAddressToSend(
+      quiche::IpAddressFamily address_family) const;
+  void ClearAlternateServerAddressToSend(
+      quiche::IpAddressFamily address_family);
 
   // Original destination connection ID.
   void SetOriginalConnectionIdToSend(
diff --git a/quiche/quic/core/quic_config_test.cc b/quiche/quic/core/quic_config_test.cc
index 495127c..86e5a99 100644
--- a/quiche/quic/core/quic_config_test.cc
+++ b/quiche/quic/core/quic_config_test.cc
@@ -482,12 +482,17 @@
   QuicConnectionId new_connection_id = TestConnectionId(5);
   StatelessResetToken new_stateless_reset_token =
       QuicUtils::GenerateStatelessResetToken(new_connection_id);
-  EXPECT_FALSE(config_.CanSendPreferredAddressConnectionIdAndToken());
   config_.SetIPv4AlternateServerAddressToSend(kTestServerAddress);
-  ASSERT_TRUE(config_.CanSendPreferredAddressConnectionIdAndToken());
+  QuicSocketAddress kTestServerAddressV6 =
+      QuicSocketAddress(QuicIpAddress::Any6(), 1234);
+  config_.SetIPv6AlternateServerAddressToSend(kTestServerAddressV6);
   config_.SetPreferredAddressConnectionIdAndTokenToSend(
       new_connection_id, new_stateless_reset_token);
-  EXPECT_FALSE(config_.CanSendPreferredAddressConnectionIdAndToken());
+  config_.ClearAlternateServerAddressToSend(quiche::IpAddressFamily::IP_V6);
+  EXPECT_TRUE(config_.GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V4)
+                  .has_value());
+  EXPECT_FALSE(config_.GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V6)
+                   .has_value());
 
   TransportParameters params;
   config_.FillTransportParameters(&params);
@@ -523,12 +528,46 @@
       params.min_ack_delay_us.value());
 
   EXPECT_EQ(params.preferred_address->ipv4_socket_address, kTestServerAddress);
+  EXPECT_EQ(params.preferred_address->ipv6_socket_address,
+            QuicSocketAddress(QuicIpAddress::Any6(), 0));
+
   EXPECT_EQ(*reinterpret_cast<StatelessResetToken*>(
                 &params.preferred_address->stateless_reset_token.front()),
             new_stateless_reset_token);
   EXPECT_EQ(kFakeGoogleHandshakeMessage, params.google_handshake_message);
 }
 
+TEST_P(QuicConfigTest, FillTransportParamsNoV4PreferredAddress) {
+  if (!version_.UsesTls()) {
+    // TransportParameters are only used for QUIC+TLS.
+    return;
+  }
+
+  QuicIpAddress host;
+  host.FromString("127.0.3.1");
+  QuicSocketAddress kTestServerAddress = QuicSocketAddress(host, 1234);
+  QuicConnectionId new_connection_id = TestConnectionId(5);
+  StatelessResetToken new_stateless_reset_token =
+      QuicUtils::GenerateStatelessResetToken(new_connection_id);
+  config_.SetIPv4AlternateServerAddressToSend(kTestServerAddress);
+  QuicSocketAddress kTestServerAddressV6 =
+      QuicSocketAddress(QuicIpAddress::Any6(), 1234);
+  config_.SetIPv6AlternateServerAddressToSend(kTestServerAddressV6);
+  config_.SetPreferredAddressConnectionIdAndTokenToSend(
+      new_connection_id, new_stateless_reset_token);
+  config_.ClearAlternateServerAddressToSend(quiche::IpAddressFamily::IP_V4);
+  EXPECT_FALSE(config_.GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V4)
+                   .has_value());
+  config_.ClearAlternateServerAddressToSend(quiche::IpAddressFamily::IP_V4);
+
+  TransportParameters params;
+  config_.FillTransportParameters(&params);
+  EXPECT_EQ(params.preferred_address->ipv4_socket_address,
+            QuicSocketAddress(QuicIpAddress::Any4(), 0));
+  EXPECT_EQ(params.preferred_address->ipv6_socket_address,
+            kTestServerAddressV6);
+}
+
 TEST_P(QuicConfigTest, ProcessTransportParametersServer) {
   if (!version_.UsesTls()) {
     // TransportParameters are only used for QUIC+TLS.
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index de88ae6..9d4f882 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -664,20 +664,24 @@
       config.HasClientSentConnectionOption(kSPAD, perspective_)) {
     if (self_address().host().IsIPv4() &&
         config.HasReceivedIPv4AlternateServerAddress()) {
-      server_preferred_address_ = config.ReceivedIPv4AlternateServerAddress();
+      received_server_preferred_address_ =
+          config.ReceivedIPv4AlternateServerAddress();
     } else if (self_address().host().IsIPv6() &&
                config.HasReceivedIPv6AlternateServerAddress()) {
-      server_preferred_address_ = config.ReceivedIPv6AlternateServerAddress();
+      received_server_preferred_address_ =
+          config.ReceivedIPv6AlternateServerAddress();
     }
-    if (server_preferred_address_.IsInitialized()) {
+    if (received_server_preferred_address_.IsInitialized()) {
       QUICHE_DLOG(INFO) << ENDPOINT << "Received server preferred address: "
-                        << server_preferred_address_;
+                        << received_server_preferred_address_;
       if (config.HasClientRequestedIndependentOption(kSPA2, perspective_)) {
         accelerated_server_preferred_address_ = true;
-        visitor_->OnServerPreferredAddressAvailable(server_preferred_address_);
+        visitor_->OnServerPreferredAddressAvailable(
+            received_server_preferred_address_);
       }
     }
   }
+
   if (config.HasReceivedMaxPacketSize()) {
     peer_max_packet_size_ = config.ReceivedMaxPacketSize();
     packet_creator_.SetMaxPacketLength(
@@ -1237,7 +1241,7 @@
     if (!GetLargestReceivedPacket().IsInitialized() ||
         header.packet_number > GetLargestReceivedPacket()) {
       if (version().HasIetfQuicFrames()) {
-        // Client processes packets from any known server address. Client only
+        // Client processes packets from any known server address, but only
         // updates peer address on initialization and/or to validated server
         // preferred address.
       } else {
@@ -2693,6 +2697,14 @@
 
   if (!default_path_.self_address.IsInitialized()) {
     default_path_.self_address = last_received_packet_info_.destination_address;
+  } else if (default_path_.self_address != self_address &&
+             sent_server_preferred_address_.IsInitialized() &&
+             self_address.Normalized() ==
+                 sent_server_preferred_address_.Normalized()) {
+    // If the packet is received at the preferred address, treat it as if it is
+    // received on the original server address.
+    last_received_packet_info_.destination_address = default_path_.self_address;
+    last_received_packet_info_.actual_destination_address = self_address;
   }
 
   if (!direct_peer_address_.IsInitialized()) {
@@ -2930,6 +2942,8 @@
   // Client should only send packets on either default or alternative path, so
   // it shouldn't fail here. If the server fail to find CID to use, no packet
   // will be generated on this path.
+  // TODO(danzh) fix SendPathResponse() to respond to probes from a different
+  // client port with non-Zero client CID.
   QUIC_BUG_IF(failed to find on path connection ids,
               perspective_ == Perspective::IS_CLIENT)
       << "Fails to find on path connection IDs";
@@ -2970,6 +2984,8 @@
             "Self address migration is not supported at the server, current "
             "address: ",
             default_path_.self_address.ToString(),
+            ", server preferred address: ",
+            sent_server_preferred_address_.ToString(),
             ", received packet address: ",
             last_received_packet_info_.destination_address.ToString(),
             ", size: ", last_received_packet_info_.length,
@@ -2985,6 +3001,19 @@
     default_path_.self_address = last_received_packet_info_.destination_address;
   }
 
+  if (perspective_ == Perspective::IS_SERVER &&
+      last_received_packet_info_.actual_destination_address.IsInitialized() &&
+      !IsHandshakeConfirmed() &&
+      GetEffectivePeerAddressFromCurrentPacket() !=
+          default_path_.peer_address) {
+    // Our client implementation has an optimization to spray packets from
+    // different sockets to the server's preferred address before handshake
+    // gets confirmed. In this case, do not kick off client address migration
+    // detection.
+    QUICHE_DCHECK(sent_server_preferred_address_.IsInitialized());
+    last_received_packet_info_.source_address = direct_peer_address_;
+  }
+
   if (PacketCanReplaceServerConnectionId(header, perspective_) &&
       default_path_.server_connection_id != header.source_connection_id) {
     QUICHE_DCHECK_EQ(header.long_packet_type, INITIAL);
@@ -3987,9 +4016,10 @@
   ack_alarm_->Update(uber_received_packet_manager_.GetEarliestAckTimeout(),
                      kAlarmGranularity);
   if (!accelerated_server_preferred_address_ &&
-      server_preferred_address_.IsInitialized()) {
+      received_server_preferred_address_.IsInitialized()) {
     QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_);
-    visitor_->OnServerPreferredAddressAvailable(server_preferred_address_);
+    visitor_->OnServerPreferredAddressAvailable(
+        received_server_preferred_address_);
   }
 }
 
@@ -5916,9 +5946,9 @@
           kMaxDuplicatedPacketsSentToServerPreferredAddress) {
     // Send coalesced packets to both addresses while the server preferred
     // address validation is pending.
-    QUICHE_DCHECK(server_preferred_address_.IsInitialized());
-    path_validator_.MaybeWritePacketToAddress(buffer, length,
-                                              server_preferred_address_);
+    QUICHE_DCHECK(received_server_preferred_address_.IsInitialized());
+    path_validator_.MaybeWritePacketToAddress(
+        buffer, length, received_server_preferred_address_);
     ++stats_.num_duplicated_packets_sent_to_server_preferred_address;
   }
   // Account for added padding.
@@ -6590,7 +6620,7 @@
                                       std::move(result_delegate), reason);
   if (perspective_ == Perspective::IS_CLIENT &&
       IsValidatingServerPreferredAddress()) {
-    AddKnownServerAddress(server_preferred_address_);
+    AddKnownServerAddress(received_server_preferred_address_);
   }
 }
 
@@ -6751,7 +6781,8 @@
     }
     return false;
   }
-  QUICHE_DCHECK(!version().UsesHttp3() || IsHandshakeConfirmed());
+  QUICHE_DCHECK(!version().UsesHttp3() || IsHandshakeConfirmed() ||
+                accelerated_server_preferred_address_);
 
   if (connection_migration_use_new_cid_) {
     if (!UpdateConnectionIdsOnMigration(self_address, peer_address)) {
@@ -6805,10 +6836,10 @@
     }
   }
 
-  if (context.peer_address() == server_preferred_address_ &&
-      server_preferred_address_ != default_path_.peer_address) {
+  if (context.peer_address() == received_server_preferred_address_ &&
+      received_server_preferred_address_ != default_path_.peer_address) {
     QUIC_DLOG(INFO) << "Failed to validate server preferred address : "
-                    << server_preferred_address_;
+                    << received_server_preferred_address_;
     mutable_stats().failed_to_validate_server_preferred_address = true;
   }
 
@@ -7242,11 +7273,11 @@
 
 bool QuicConnection::IsValidatingServerPreferredAddress() const {
   QUICHE_DCHECK_EQ(perspective_, Perspective::IS_CLIENT);
-  return server_preferred_address_.IsInitialized() &&
-         server_preferred_address_ != default_path_.peer_address &&
+  return received_server_preferred_address_.IsInitialized() &&
+         received_server_preferred_address_ != default_path_.peer_address &&
          path_validator_.HasPendingPathValidation() &&
          path_validator_.GetContext()->peer_address() ==
-             server_preferred_address_;
+             received_server_preferred_address_;
 }
 
 void QuicConnection::OnServerPreferredAddressValidated(
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 7969a0e..6d73f93 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1287,6 +1287,11 @@
   void OnServerPreferredAddressValidated(QuicPathValidationContext& context,
                                          bool owns_writer);
 
+  void set_sent_server_preferred_address(
+      const QuicSocketAddress& sent_server_preferred_address) {
+    sent_server_preferred_address_ = sent_server_preferred_address;
+  }
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -1500,6 +1505,10 @@
     QuicPacketHeader header;
     absl::InlinedVector<QuicFrameType, 1> frames;
     QuicEcnCodepoint ecn_codepoint = ECN_NOT_ECT;
+    // Stores the actual address this packet is received on when it is received
+    // on the preferred address. In this case, |destination_address| will
+    // be overridden to the current default self address.
+    QuicSocketAddress actual_destination_address;
   };
 
   QUIC_EXPORT_PRIVATE friend std::ostream& operator<<(
@@ -2304,7 +2313,10 @@
 
   // Stores received server preferred address in transport param. Client side
   // only.
-  QuicSocketAddress server_preferred_address_;
+  QuicSocketAddress received_server_preferred_address_;
+
+  // Stores sent server preferred address in transport param. Server side only.
+  QuicSocketAddress sent_server_preferred_address_;
 
   // If true, kicks off validation of server_preferred_address_ once it is
   // received. Also, send all coalesced packets on both paths until handshake is
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index c4fe88f..84c8cdb 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1491,6 +1491,51 @@
     }
   }
 
+  void ServerHandlePreferredAddressInit() {
+    ASSERT_TRUE(GetParam().version.HasIetfQuicFrames());
+    set_perspective(Perspective::IS_SERVER);
+    connection_.CreateConnectionIdManager();
+    QuicPacketCreatorPeer::SetSendVersionInPacket(creator_, false);
+    SetQuicReloadableFlag(quic_connection_migration_use_new_cid_v2, true);
+    EXPECT_CALL(visitor_, AllowSelfAddressChange())
+        .WillRepeatedly(Return(true));
+
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+    peer_creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE);
+    // Discard INITIAL key.
+    connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+    connection_.NeuterUnencryptedPackets();
+    // Prevent packets from being coalesced.
+    EXPECT_CALL(visitor_, GetHandshakeState())
+        .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+    if (version().SupportsAntiAmplificationLimit()) {
+      QuicConnectionPeer::SetAddressValidated(&connection_);
+    }
+    // Clear direct_peer_address.
+    QuicConnectionPeer::SetDirectPeerAddress(&connection_, QuicSocketAddress());
+    // Clear effective_peer_address, it is the same as direct_peer_address for
+    // this test.
+    QuicConnectionPeer::SetEffectivePeerAddress(&connection_,
+                                                QuicSocketAddress());
+    EXPECT_FALSE(connection_.effective_peer_address().IsInitialized());
+
+    if (QuicVersionUsesCryptoFrames(connection_.transport_version())) {
+      EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(AnyNumber());
+    } else {
+      EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+    }
+    QuicPacketCreatorPeer::SetPacketNumber(&peer_creator_, 2);
+    ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress,
+                                    kPeerAddress, ENCRYPTION_FORWARD_SECURE);
+    EXPECT_EQ(kPeerAddress, connection_.peer_address());
+    EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+    QuicConfig config;
+    config.SetInitialReceivedConnectionOptions(QuicTagVector{kRVCM});
+    EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+    connection_.SetFromConfig(config);
+    connection_.set_sent_server_preferred_address(kServerPreferredAddress);
+  }
+
   // Receive server preferred address.
   void ServerPreferredAddressInit(QuicConfig& config) {
     ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective());
@@ -1523,10 +1568,12 @@
     EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
     connection_.SetFromConfig(config);
 
-    ASSERT_TRUE(QuicConnectionPeer::GetServerPreferredAddress(&connection_)
-                    .IsInitialized());
-    EXPECT_EQ(kServerPreferredAddress,
-              QuicConnectionPeer::GetServerPreferredAddress(&connection_));
+    ASSERT_TRUE(
+        QuicConnectionPeer::GetReceivedServerPreferredAddress(&connection_)
+            .IsInitialized());
+    EXPECT_EQ(
+        kServerPreferredAddress,
+        QuicConnectionPeer::GetReceivedServerPreferredAddress(&connection_));
   }
 
   void TestClientRetryHandling(bool invalid_retry_tag,
@@ -2583,7 +2630,7 @@
 // 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
 // change.
-TEST_P(QuicConnectionTest, ReceivePathProbingAtServer) {
+TEST_P(QuicConnectionTest, ReceivePathProbingFromNewPeerAddressAtServer) {
   PathProbeTestInit(Perspective::IS_SERVER);
 
   EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0);
@@ -2697,6 +2744,68 @@
   EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
 }
 
+// Receive a packet contains PATH CHALLENGE with self address change.
+TEST_P(QuicConnectionTest, ReceivePathProbingToPreferredAddressAtServer) {
+  if (!GetParam().version.HasIetfQuicFrames()) {
+    return;
+  }
+  ServerHandlePreferredAddressInit();
+
+  EXPECT_CALL(visitor_, OnConnectionMigration(PORT_CHANGE)).Times(0);
+  EXPECT_CALL(visitor_, OnPacketReceived(_, _, _)).Times(0);
+
+  // Process a probing packet to the server preferred address.
+  std::unique_ptr<SerializedPacket> probing_packet = ConstructProbingPacket();
+  std::unique_ptr<QuicReceivedPacket> received(ConstructReceivedPacket(
+      QuicEncryptedPacket(probing_packet->encrypted_buffer,
+                          probing_packet->encrypted_length),
+      clock_.Now()));
+  uint64_t num_probing_received =
+      connection_.GetStats().num_connectivity_probing_received;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(AtLeast(1u))
+      .WillOnce(Invoke([&]() {
+        EXPECT_EQ(1u, writer_->path_response_frames().size());
+        // Verify that the PATH_RESPONSE is sent from the original self address.
+        EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+        EXPECT_EQ(kPeerAddress, writer_->last_write_peer_address());
+      }));
+  ProcessReceivedPacket(kServerPreferredAddress, kPeerAddress, *received);
+
+  EXPECT_EQ(num_probing_received + 1,
+            connection_.GetStats().num_connectivity_probing_received);
+  EXPECT_FALSE(QuicConnectionPeer::IsAlternativePath(
+      &connection_, kServerPreferredAddress, kPeerAddress));
+  EXPECT_NE(kServerPreferredAddress, connection_.self_address());
+
+  // Receiving another probing packet from a new client address.
+  const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(),
+                                          /*port=*/34567);
+  probing_packet = ConstructProbingPacket();
+  received.reset(ConstructReceivedPacket(
+      QuicEncryptedPacket(probing_packet->encrypted_buffer,
+                          probing_packet->encrypted_length),
+      clock_.Now()));
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(AtLeast(1u))
+      .WillOnce(Invoke([&]() {
+        EXPECT_EQ(1u, writer_->path_response_frames().size());
+        EXPECT_EQ(1u, writer_->path_challenge_frames().size());
+        EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+        // The responses should still be sent from the original address.
+        EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address());
+      }));
+  ProcessReceivedPacket(kServerPreferredAddress, kNewPeerAddress, *received);
+
+  EXPECT_EQ(num_probing_received + 2,
+            connection_.GetStats().num_connectivity_probing_received);
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(&connection_, kSelfAddress,
+                                                    kNewPeerAddress));
+  EXPECT_LT(0u, QuicConnectionPeer::BytesSentOnAlternativePath(&connection_));
+  EXPECT_EQ(received->length(),
+            QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_));
+}
+
 // Receive a padded PING packet with a port change on server side.
 TEST_P(QuicConnectionTest, ReceivePaddedPingWithPortChangeAtServer) {
   set_perspective(Perspective::IS_SERVER);
@@ -3036,10 +3145,12 @@
   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_));
+  ASSERT_TRUE(
+      QuicConnectionPeer::GetReceivedServerPreferredAddress(&connection_)
+          .IsInitialized());
+  EXPECT_EQ(
+      kServerPreferredAddress,
+      QuicConnectionPeer::GetReceivedServerPreferredAddress(&connection_));
 
   EXPECT_CALL(visitor_, OnCryptoFrame(_)).Times(0);
   ProcessFramePacketWithAddresses(MakeCryptoFrame(), kSelfAddress,
@@ -16942,6 +17053,140 @@
   EXPECT_TRUE(QuicConnectionPeer::TestLastReceivedPacketInfoDefaults());
 }
 
+TEST_P(QuicConnectionTest, DetectMigrationToPreferredAddress) {
+  if (!GetParam().version.HasIetfQuicFrames()) {
+    return;
+  }
+  ServerHandlePreferredAddressInit();
+
+  // Issue a new server CID associated with the preferred address.
+  QuicConnectionId server_issued_cid_for_preferred_address =
+      TestConnectionId(17);
+  EXPECT_CALL(connection_id_generator_,
+              GenerateNextConnectionId(connection_id_))
+      .WillOnce(Return(server_issued_cid_for_preferred_address));
+  EXPECT_CALL(visitor_, MaybeReserveConnectionId(_)).WillOnce(Return(true));
+  absl::optional<QuicNewConnectionIdFrame> frame =
+      connection_.MaybeIssueNewConnectionIdForPreferredAddress();
+  ASSERT_TRUE(frame.has_value());
+
+  auto* packet_creator = QuicConnectionPeer::GetPacketCreator(&connection_);
+  ASSERT_EQ(packet_creator->GetDestinationConnectionId(),
+            connection_.client_connection_id());
+  ASSERT_EQ(packet_creator->GetSourceConnectionId(), connection_id_);
+
+  // Process a packet received at the preferred Address.
+  peer_creator_.SetServerConnectionId(server_issued_cid_for_preferred_address);
+  EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  ProcessFramePacketWithAddresses(MakeCryptoFrame(), kServerPreferredAddress,
+                                  kPeerAddress, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_EQ(kPeerAddress, connection_.peer_address());
+  // The server migrates half-way with the default path unchanged, and
+  // continuing with the client issued CID 1.
+  EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+  EXPECT_EQ(kSelfAddress, connection_.self_address());
+
+  // The peer retires CID 123.
+  QuicRetireConnectionIdFrame retire_cid_frame;
+  retire_cid_frame.sequence_number = 0u;
+  EXPECT_CALL(connection_id_generator_,
+              GenerateNextConnectionId(server_issued_cid_for_preferred_address))
+      .WillOnce(Return(TestConnectionId(456)));
+  EXPECT_CALL(visitor_, MaybeReserveConnectionId(_)).WillOnce(Return(true));
+  EXPECT_CALL(visitor_, SendNewConnectionId(_));
+  EXPECT_TRUE(connection_.OnRetireConnectionIdFrame(retire_cid_frame));
+
+  // Process another packet received at Preferred Address.
+  EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  ProcessFramePacketWithAddresses(MakeCryptoFrame(), kServerPreferredAddress,
+                                  kPeerAddress, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_EQ(kPeerAddress, connection_.peer_address());
+  EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+  EXPECT_EQ(kSelfAddress, connection_.self_address());
+}
+
+TEST_P(QuicConnectionTest,
+       DetectSimutanuousServerAndClientAddressChangeWithProbe) {
+  if (!GetParam().version.HasIetfQuicFrames()) {
+    return;
+  }
+  ServerHandlePreferredAddressInit();
+
+  // Issue a new server CID associated with the preferred address.
+  QuicConnectionId server_issued_cid_for_preferred_address =
+      TestConnectionId(17);
+  EXPECT_CALL(connection_id_generator_,
+              GenerateNextConnectionId(connection_id_))
+      .WillOnce(Return(server_issued_cid_for_preferred_address));
+  EXPECT_CALL(visitor_, MaybeReserveConnectionId(_)).WillOnce(Return(true));
+  absl::optional<QuicNewConnectionIdFrame> frame =
+      connection_.MaybeIssueNewConnectionIdForPreferredAddress();
+  ASSERT_TRUE(frame.has_value());
+
+  auto* packet_creator = QuicConnectionPeer::GetPacketCreator(&connection_);
+  ASSERT_EQ(packet_creator->GetSourceConnectionId(), connection_id_);
+  ASSERT_EQ(packet_creator->GetDestinationConnectionId(),
+            connection_.client_connection_id());
+
+  // Receiving a probing packet from a new client address to the preferred
+  // address.
+  peer_creator_.SetServerConnectionId(server_issued_cid_for_preferred_address);
+  const QuicSocketAddress kNewPeerAddress(QuicIpAddress::Loopback4(),
+                                          /*port=*/34567);
+  std::unique_ptr<SerializedPacket> probing_packet = ConstructProbingPacket();
+  std::unique_ptr<QuicReceivedPacket> received(ConstructReceivedPacket(
+      QuicEncryptedPacket(probing_packet->encrypted_buffer,
+                          probing_packet->encrypted_length),
+      clock_.Now()));
+  uint64_t num_probing_received =
+      connection_.GetStats().num_connectivity_probing_received;
+  EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+      .Times(AtLeast(1u))
+      .WillOnce(Invoke([&]() {
+        EXPECT_EQ(1u, writer_->path_response_frames().size());
+        EXPECT_EQ(1u, writer_->path_challenge_frames().size());
+        // The responses should still be sent from the original address.
+        EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+        EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address());
+      }));
+  ProcessReceivedPacket(kServerPreferredAddress, kNewPeerAddress, *received);
+  EXPECT_EQ(num_probing_received + 1,
+            connection_.GetStats().num_connectivity_probing_received);
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(&connection_, kSelfAddress,
+                                                    kNewPeerAddress));
+  EXPECT_LT(0u, QuicConnectionPeer::BytesSentOnAlternativePath(&connection_));
+  EXPECT_EQ(received->length(),
+            QuicConnectionPeer::BytesReceivedOnAlternativePath(&connection_));
+  EXPECT_EQ(kPeerAddress, connection_.peer_address());
+  EXPECT_EQ(kSelfAddress, connection_.self_address());
+
+  // Process a data packet received at the preferred Address from the new client
+  // address.
+  EXPECT_CALL(visitor_, OnConnectionMigration(IPV6_TO_IPV4_CHANGE));
+  EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  ProcessFramePacketWithAddresses(MakeCryptoFrame(), kServerPreferredAddress,
+                                  kNewPeerAddress, ENCRYPTION_FORWARD_SECURE);
+  // The server migrates half-way with the new peer address but the same default
+  // self address.
+  EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+  EXPECT_EQ(kSelfAddress, connection_.self_address());
+  EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  EXPECT_FALSE(QuicConnectionPeer::GetDefaultPath(&connection_)->validated);
+  EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(&connection_, kSelfAddress,
+                                                    kPeerAddress));
+  EXPECT_EQ(packet_creator->GetSourceConnectionId(),
+            server_issued_cid_for_preferred_address);
+
+  // Process another packet received at the preferred Address.
+  EXPECT_CALL(visitor_, OnCryptoFrame(_));
+  ProcessFramePacketWithAddresses(MakeCryptoFrame(), kServerPreferredAddress,
+                                  kNewPeerAddress, ENCRYPTION_FORWARD_SECURE);
+  EXPECT_EQ(kNewPeerAddress, connection_.peer_address());
+  EXPECT_EQ(kSelfAddress.host(), writer_->last_write_source_address());
+  EXPECT_EQ(kSelfAddress, connection_.self_address());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_session.cc b/quiche/quic/core/quic_session.cc
index a58e572..86ae879 100644
--- a/quiche/quic/core/quic_session.cc
+++ b/quiche/quic/core/quic_session.cc
@@ -1328,15 +1328,39 @@
         config_.ReceivedInitialSessionFlowControlWindowBytes());
   }
 
-  if (version().HasIetfQuicFrames() &&
-      config_.CanSendPreferredAddressConnectionIdAndToken() &&
-      config_.HasClientSentConnectionOption(kSPAD, perspective())) {
-    // Set connection ID and token if SPAD is received.
-    absl::optional<QuicNewConnectionIdFrame> frame =
-        connection_->MaybeIssueNewConnectionIdForPreferredAddress();
-    if (frame.has_value()) {
-      config_.SetPreferredAddressConnectionIdAndTokenToSend(
-          frame->connection_id, frame->stateless_reset_token);
+  if (perspective_ == Perspective::IS_SERVER && version().HasIetfQuicFrames() &&
+      connection_->effective_peer_address().IsInitialized()) {
+    if (config_.HasClientSentConnectionOption(kSPAD, perspective_)) {
+      quiche::IpAddressFamily address_family =
+          connection_->effective_peer_address()
+              .Normalized()
+              .host()
+              .address_family();
+      absl::optional<QuicSocketAddress> preferred_address =
+          config_.GetPreferredAddressToSend(address_family);
+      if (preferred_address.has_value()) {
+        // Set connection ID and token if SPAD has received and a preferred
+        // address of the same address family is configured.
+        absl::optional<QuicNewConnectionIdFrame> frame =
+            connection_->MaybeIssueNewConnectionIdForPreferredAddress();
+        if (frame.has_value()) {
+          config_.SetPreferredAddressConnectionIdAndTokenToSend(
+              frame->connection_id, frame->stateless_reset_token);
+        }
+        connection_->set_sent_server_preferred_address(
+            preferred_address.value());
+      }
+      // Clear the alternative address of the other address family in the
+      // config.
+      config_.ClearAlternateServerAddressToSend(
+          address_family == quiche::IpAddressFamily::IP_V4
+              ? quiche::IpAddressFamily::IP_V6
+              : quiche::IpAddressFamily::IP_V4);
+    } else {
+      // Clear alternative IPv(4|6) addresses in config if the server hasn't
+      // received 'SPAD' connection option.
+      config_.ClearAlternateServerAddressToSend(quiche::IpAddressFamily::IP_V4);
+      config_.ClearAlternateServerAddressToSend(quiche::IpAddressFamily::IP_V6);
     }
   }
 
diff --git a/quiche/quic/core/quic_session_test.cc b/quiche/quic/core/quic_session_test.cc
index 1547f18..c65ae18 100644
--- a/quiche/quic/core/quic_session_test.cc
+++ b/quiche/quic/core/quic_session_test.cc
@@ -3174,6 +3174,62 @@
   EXPECT_TRUE(session_.config()->HasStatelessResetTokenToSend());
 }
 
+TEST_P(QuicSessionTestServer,
+       SetServerPreferredAddressAccordingToAddressFamily) {
+  if (!session_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  EXPECT_EQ(quiche::IpAddressFamily::IP_V4,
+            connection_->peer_address().host().address_family());
+  QuicConnectionPeer::SetEffectivePeerAddress(connection_,
+                                              connection_->peer_address());
+  QuicTagVector copt;
+  copt.push_back(kSPAD);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_.config(), copt);
+  QuicSocketAddress preferred_address(QuicIpAddress::Loopback4(), 12345);
+  session_.config()->SetIPv4AlternateServerAddressToSend(preferred_address);
+  session_.config()->SetIPv6AlternateServerAddressToSend(
+      QuicSocketAddress(QuicIpAddress::Loopback6(), 12345));
+
+  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  session_.OnConfigNegotiated();
+  EXPECT_EQ(QuicSocketAddress(QuicIpAddress::Loopback4(), 12345),
+            session_.config()
+                ->GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V4)
+                .value());
+  EXPECT_FALSE(session_.config()
+                   ->GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V6)
+                   .has_value());
+  EXPECT_EQ(preferred_address,
+            QuicConnectionPeer::GetSentServerPreferredAddress(connection_));
+}
+
+TEST_P(QuicSessionTestServer, NoServerPreferredAddressIfAddressFamilyMismatch) {
+  if (!session_.version().HasIetfQuicFrames()) {
+    return;
+  }
+  EXPECT_EQ(quiche::IpAddressFamily::IP_V4,
+            connection_->peer_address().host().address_family());
+  QuicConnectionPeer::SetEffectivePeerAddress(connection_,
+                                              connection_->peer_address());
+  QuicTagVector copt;
+  copt.push_back(kSPAD);
+  QuicConfigPeer::SetReceivedConnectionOptions(session_.config(), copt);
+  session_.config()->SetIPv6AlternateServerAddressToSend(
+      QuicSocketAddress(QuicIpAddress::Loopback6(), 12345));
+
+  connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
+  session_.OnConfigNegotiated();
+  EXPECT_FALSE(session_.config()
+                   ->GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V4)
+                   .has_value());
+  EXPECT_FALSE(session_.config()
+                   ->GetPreferredAddressToSend(quiche::IpAddressFamily::IP_V6)
+                   .has_value());
+  EXPECT_FALSE(QuicConnectionPeer::GetSentServerPreferredAddress(connection_)
+                   .IsInitialized());
+}
+
 // A client test class that can be used when the automatic configuration is not
 // desired.
 class QuicSessionTestClientUnconfigured : public QuicSessionTestBase {