Optimize client handling of server preferred address:
1) Start validating server preferred address on handshake complete (rather than confirmed)
2) While validation is pending, duplicate coalesced packets (up to 5) on both paths.
This is behind client_connection_option SPA2.

PiperOrigin-RevId: 498291601
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index 08dcc42..31937bc 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -285,6 +285,10 @@
                                                  // upon client address change.
 
 const QuicTag kSPAD = TAG('S', 'P', 'A', 'D');   // Use server preferred address
+const QuicTag kSPA2 = TAG('S', 'P', 'A', '2');   // Start validating server
+                                                 // preferred address once it is
+                                                 // received. Send all coalesced
+                                                 // packets to both addresses.
 
 // Optional support of truncated Connection IDs.  If sent by a peer, the value
 // is the minimum number of bytes allowed for the connection ID sent to the
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 091618b..f3b7f03 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -5511,6 +5511,45 @@
   EXPECT_TRUE(client_stats.failed_to_validate_server_preferred_address);
 }
 
+TEST_P(EndToEndTest, OptimizedServerPreferredAddress) {
+  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});
+  client_config_.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  client_.reset(CreateQuicClient(nullptr));
+  EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed());
+  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);
+}
+
 TEST_P(EndToEndPacketReorderingTest, ReorderedPathChallenge) {
   ASSERT_TRUE(Initialize());
   if (!version_.HasIetfQuicFrames()) {
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 2f5f56f..a929e3a 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -706,10 +706,15 @@
                config.HasReceivedIPv6AlternateServerAddress()) {
       server_preferred_address_ = config.ReceivedIPv6AlternateServerAddress();
     }
-    QUIC_DLOG_IF(INFO, server_preferred_address_.IsInitialized())
-        << ENDPOINT
-        << "Received server preferred address: " << server_preferred_address_;
     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();
+      }
+    }
   }
   if (config.HasReceivedMaxPacketSize()) {
     peer_max_packet_size_ = config.ReceivedMaxPacketSize();
@@ -3997,20 +4002,10 @@
   // Re-arm ack alarm.
   ack_alarm_->Update(uber_received_packet_manager_.GetEarliestAckTimeout(),
                      kAlarmGranularity);
-  if (server_preferred_address_.IsInitialized()) {
+  if (!accelerated_server_preferred_address_ &&
+      server_preferred_address_.IsInitialized()) {
     QUICHE_DCHECK_EQ(Perspective::IS_CLIENT, perspective_);
-    // Validate received server preferred address.
-    auto context =
-        visitor_->CreatePathValidationContextForServerPreferredAddress(
-            server_preferred_address_);
-    if (context != nullptr) {
-      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));
-    }
+    ValidateServerPreferredAddress();
   }
 }
 
@@ -5924,6 +5919,16 @@
       }
     }
   }
+  if (accelerated_server_preferred_address_ &&
+      stats_.num_duplicated_packets_sent_to_server_preferred_address <
+          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_);
+    ++stats_.num_duplicated_packets_sent_to_server_preferred_address;
+  }
   // Account for added padding.
   if (length > coalesced_packet_.length()) {
     if (IsDefaultPath(coalesced_packet_.self_address(),
@@ -6423,6 +6428,21 @@
       ->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;
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index cc22c19..ca25805 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1272,6 +1272,9 @@
   absl::optional<QuicNewConnectionIdFrame>
   MaybeIssueNewConnectionIdForPreferredAddress();
 
+  // Kicks off validation of received server preferred address.
+  void ValidateServerPreferredAddress();
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -2282,6 +2285,11 @@
   // only.
   QuicSocketAddress 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
+  // confirmed.
+  bool accelerated_server_preferred_address_ = false;
+
   // If true, throttle sending if next created packet will exceed amplification
   // limit.
   const bool enforce_strict_amplification_factor_ =
diff --git a/quiche/quic/core/quic_connection_stats.cc b/quiche/quic/core/quic_connection_stats.cc
index 0321498..fd74285 100644
--- a/quiche/quic/core/quic_connection_stats.cc
+++ b/quiche/quic/core/quic_connection_stats.cc
@@ -67,6 +67,8 @@
      << s.server_preferred_address_validated;
   os << " failed_to_validate_server_preferred_address: "
      << s.failed_to_validate_server_preferred_address;
+  os << " num_duplicated_packets_sent_to_server_preferred_address: "
+     << s.num_duplicated_packets_sent_to_server_preferred_address;
   os << " }";
 
   return os;
diff --git a/quiche/quic/core/quic_connection_stats.h b/quiche/quic/core/quic_connection_stats.h
index 94acd93..82b0255 100644
--- a/quiche/quic/core/quic_connection_stats.h
+++ b/quiche/quic/core/quic_connection_stats.h
@@ -220,6 +220,9 @@
 
   bool server_preferred_address_validated = false;
   bool failed_to_validate_server_preferred_address = false;
+  // Number of duplicated packets that have been sent to server preferred
+  // address while the validation is pending.
+  size_t num_duplicated_packets_sent_to_server_preferred_address = 0;
 
   struct QUIC_NO_EXPORT TlsServerOperationStats {
     bool success = false;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 314f491..874eeb1 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1463,7 +1463,7 @@
   }
 
   // Receive server preferred address.
-  void ServerPreferredAddressInit() {
+  void ServerPreferredAddressInit(QuicConfig& config) {
     ASSERT_EQ(Perspective::IS_CLIENT, connection_.perspective());
     ASSERT_TRUE(version().HasIetfQuicFrames());
     ASSERT_TRUE(connection_.self_address().host().IsIPv6());
@@ -1485,10 +1485,9 @@
     ProcessFramePacketAtLevel(1, QuicFrame(&frame), ENCRYPTION_INITIAL);
     // Discard INITIAL key.
     connection_.RemoveEncrypter(ENCRYPTION_INITIAL);
+    connection_.SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
 
-    QuicConfig config;
     config.SetConnectionOptionsToSend(QuicTagVector{kRVCM});
-    config.SetClientConnectionOptions(QuicTagVector{kSPAD});
     QuicConfigPeer::SetReceivedStatelessResetToken(&config,
                                                    kTestStatelessResetToken);
     QuicConfigPeer::SetReceivedAlternateServerAddress(&config,
@@ -15987,7 +15986,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16065,7 +16066,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16129,7 +16132,9 @@
   if (!connection_.version().HasIetfQuicFrames()) {
     return;
   }
-  ServerPreferredAddressInit();
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD});
+  ServerPreferredAddressInit(config);
   const QuicSocketAddress kServerPreferredAddress =
       QuicConnectionPeer::GetServerPreferredAddress(&connection_);
   const QuicSocketAddress kNewSelfAddress =
@@ -16191,6 +16196,128 @@
       connection_.GetStats().failed_to_validate_server_preferred_address);
 }
 
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress) {
+  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))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+  // Verify the packet is sent on both paths.
+  EXPECT_FALSE(writer_->stream_frames().empty());
+  EXPECT_FALSE(new_writer.stream_frames().empty());
+
+  // Verify packet duplication stops on handshake confirmed.
+  EXPECT_CALL(visitor_, GetHandshakeState())
+      .WillRepeatedly(Return(HANDSHAKE_CONFIRMED));
+  connection_.OnHandshakeComplete();
+  SendPing();
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, OptimizedServerPreferredAddress2) {
+  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))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+  // Verify the packet is sent on both paths.
+  EXPECT_FALSE(writer_->stream_frames().empty());
+  EXPECT_FALSE(new_writer.stream_frames().empty());
+
+  // Simluate path validation times out.
+  for (size_t i = 0; i < QuicPathValidator::kMaxRetryTimes + 1; ++i) {
+    clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(3 * kInitialRttMs));
+    static_cast<TestAlarmFactory::TestAlarm*>(
+        QuicPathValidatorPeer::retry_timer(
+            QuicConnectionPeer::path_validator(&connection_)))
+        ->Fire();
+  }
+  EXPECT_FALSE(connection_.HasPendingPathValidation());
+  // Verify packet duplication stops if there is no pending validation.
+  SendPing();
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
+TEST_P(QuicConnectionTest, MaxDuplicatedPacketsSentToServerPreferredAddress) {
+  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))));
+  QuicConfig config;
+  config.SetClientConnectionOptions(QuicTagVector{kSPAD, kSPA2});
+  ServerPreferredAddressInit(config);
+  EXPECT_TRUE(connection_.HasPendingPathValidation());
+  ASSERT_FALSE(new_writer.path_challenge_frames().empty());
+
+  // Send data packet while path validation is pending.
+  size_t write_limit = writer_->packets_write_attempts();
+  size_t new_write_limit = new_writer.packets_write_attempts();
+  for (size_t i = 0; i < kMaxDuplicatedPacketsSentToServerPreferredAddress;
+       ++i) {
+    connection_.SendStreamDataWithString(3, "foo", i * 3, NO_FIN);
+    // Verify the packet is sent on both paths.
+    ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+    ASSERT_EQ(new_write_limit + 1, new_writer.packets_write_attempts());
+    ++write_limit;
+    ++new_write_limit;
+    EXPECT_FALSE(writer_->stream_frames().empty());
+    EXPECT_FALSE(new_writer.stream_frames().empty());
+  }
+
+  // Verify packet duplication stops if duplication limit is hit.
+  SendPing();
+  ASSERT_EQ(write_limit + 1, writer_->packets_write_attempts());
+  ASSERT_EQ(new_write_limit, new_writer.packets_write_attempts());
+  EXPECT_FALSE(writer_->ping_frames().empty());
+  EXPECT_TRUE(new_writer.ping_frames().empty());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quiche/quic/core/quic_constants.h b/quiche/quic/core/quic_constants.h
index 4589189..dfd908e 100644
--- a/quiche/quic/core/quic_constants.h
+++ b/quiche/quic/core/quic_constants.h
@@ -326,6 +326,8 @@
 
 inline constexpr size_t kMaxNumMultiPortPaths = 5;
 
+inline constexpr size_t kMaxDuplicatedPacketsSentToServerPreferredAddress = 5;
+
 }  // namespace quic
 
 #endif  // QUICHE_QUIC_CORE_QUIC_CONSTANTS_H_
diff --git a/quiche/quic/core/quic_path_validator.cc b/quiche/quic/core/quic_path_validator.cc
index 61e3dad..6179fe5 100644
--- a/quiche/quic/core/quic_path_validator.cc
+++ b/quiche/quic/core/quic_path_validator.cc
@@ -149,4 +149,18 @@
          path_context_->effective_peer_address() == effective_peer_address;
 }
 
+void QuicPathValidator::MaybeWritePacketToAddress(
+    const char* buffer, size_t buf_len, const QuicSocketAddress& peer_address) {
+  if (!HasPendingPathValidation() ||
+      path_context_->peer_address() != peer_address) {
+    return;
+  }
+  QUIC_DVLOG(1) << "Path validator is sending packet of size " << buf_len
+                << " from " << path_context_->self_address() << " to "
+                << path_context_->peer_address();
+  path_context_->WriterToUse()->WritePacket(
+      buffer, buf_len, path_context_->self_address().host(),
+      path_context_->peer_address(), nullptr);
+}
+
 }  // namespace quic
diff --git a/quiche/quic/core/quic_path_validator.h b/quiche/quic/core/quic_path_validator.h
index 4389ce5..4b94892 100644
--- a/quiche/quic/core/quic_path_validator.h
+++ b/quiche/quic/core/quic_path_validator.h
@@ -138,6 +138,11 @@
 
   bool IsValidatingPeerAddress(const QuicSocketAddress& effective_peer_address);
 
+  // Called to send packet to |peer_address| if the path validation to this
+  // address is pending.
+  void MaybeWritePacketToAddress(const char* buffer, size_t buf_len,
+                                 const QuicSocketAddress& peer_address);
+
  private:
   friend class test::QuicPathValidatorPeer;