[QUIC]Ignore duplicate NEW_CID frame.

Protected by quic_reloadable_flag_quic_ignore_duplicate_new_cid_frame.

PiperOrigin-RevId: 558807416
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index cee8ef1..693da98 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -394,6 +394,9 @@
     AddKnownServerAddress(initial_peer_address);
   }
   packet_creator_.SetDefaultPeerAddress(initial_peer_address);
+  if (ignore_duplicate_new_cid_frame_) {
+    QUIC_RELOADABLE_FLAG_COUNT(quic_ignore_duplicate_new_cid_frame);
+  }
 }
 
 void QuicConnection::InstallInitialCrypters(QuicConnectionId connection_id) {
@@ -1917,28 +1920,32 @@
   }
 }
 
-bool QuicConnection::OnNewConnectionIdFrameInner(
+NewConnectionIdResult QuicConnection::OnNewConnectionIdFrameInner(
     const QuicNewConnectionIdFrame& frame) {
   if (peer_issued_cid_manager_ == nullptr) {
     CloseConnection(
         IETF_QUIC_PROTOCOL_VIOLATION,
         "Receives NEW_CONNECTION_ID while peer uses zero length connection ID",
         ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
-    return false;
+    return NewConnectionIdResult::kProtocolViolation;
   }
   std::string error_detail;
-  QuicErrorCode error =
-      peer_issued_cid_manager_->OnNewConnectionIdFrame(frame, &error_detail);
+  bool duplicate_new_connection_id = false;
+  QuicErrorCode error = peer_issued_cid_manager_->OnNewConnectionIdFrame(
+      frame, &error_detail, &duplicate_new_connection_id);
   if (error != QUIC_NO_ERROR) {
     CloseConnection(error, error_detail,
                     ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET);
-    return false;
+    return NewConnectionIdResult::kProtocolViolation;
+  }
+  if (duplicate_new_connection_id && ignore_duplicate_new_cid_frame_) {
+    return NewConnectionIdResult::kDuplicateFrame;
   }
   if (perspective_ == Perspective::IS_SERVER) {
     OnClientConnectionIdAvailable();
   }
   MaybeUpdateAckTimeout();
-  return true;
+  return NewConnectionIdResult::kOk;
 }
 
 bool QuicConnection::OnNewConnectionIdFrame(
@@ -1956,11 +1963,17 @@
     debug_visitor_->OnNewConnectionIdFrame(frame);
   }
 
-  if (!OnNewConnectionIdFrameInner(frame)) {
-    return false;
-  }
-  if (multi_port_stats_ != nullptr) {
-    MaybeCreateMultiPortPath();
+  NewConnectionIdResult result = OnNewConnectionIdFrameInner(frame);
+  switch (result) {
+    case NewConnectionIdResult::kOk:
+      if (multi_port_stats_ != nullptr) {
+        MaybeCreateMultiPortPath();
+      }
+      break;
+    case NewConnectionIdResult::kProtocolViolation:
+      return false;
+    case NewConnectionIdResult::kDuplicateFrame:
+      break;
   }
   return true;
 }
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 01b9049..053017e 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1980,7 +1980,8 @@
 
   // Process NewConnectionIdFrame either sent from peer or synsthesized from
   // preferred_address transport parameter.
-  bool OnNewConnectionIdFrameInner(const QuicNewConnectionIdFrame& frame);
+  NewConnectionIdResult OnNewConnectionIdFrameInner(
+      const QuicNewConnectionIdFrame& frame);
 
   // Called to patch missing client connection ID on default/alternative paths
   // when a new client connection ID is received.
@@ -2367,6 +2368,9 @@
   const bool ignore_gquic_probing_ =
       GetQuicReloadableFlag(quic_ignore_gquic_probing);
 
+  const bool ignore_duplicate_new_cid_frame_ =
+      GetQuicReloadableFlag(quic_ignore_duplicate_new_cid_frame);
+
   RetransmittableOnWireBehavior retransmittable_on_wire_behavior_ = DEFAULT;
 
   // Server addresses that are known to the client.
diff --git a/quiche/quic/core/quic_connection_id_manager.cc b/quiche/quic/core/quic_connection_id_manager.cc
index c4bf3ee..b8524d1 100644
--- a/quiche/quic/core/quic_connection_id_manager.cc
+++ b/quiche/quic/core/quic_connection_id_manager.cc
@@ -125,10 +125,12 @@
 }
 
 QuicErrorCode QuicPeerIssuedConnectionIdManager::OnNewConnectionIdFrame(
-    const QuicNewConnectionIdFrame& frame, std::string* error_detail) {
+    const QuicNewConnectionIdFrame& frame, std::string* error_detail,
+    bool* is_duplicate_frame) {
   if (recent_new_connection_id_sequence_numbers_.Contains(
           frame.sequence_number)) {
     // This frame has a recently seen sequence number. Ignore.
+    *is_duplicate_frame = true;
     return QUIC_NO_ERROR;
   }
   if (!IsConnectionIdNew(frame)) {
diff --git a/quiche/quic/core/quic_connection_id_manager.h b/quiche/quic/core/quic_connection_id_manager.h
index b8454af..dfc7e11 100644
--- a/quiche/quic/core/quic_connection_id_manager.h
+++ b/quiche/quic/core/quic_connection_id_manager.h
@@ -68,7 +68,8 @@
   ~QuicPeerIssuedConnectionIdManager();
 
   QuicErrorCode OnNewConnectionIdFrame(const QuicNewConnectionIdFrame& frame,
-                                       std::string* error_detail);
+                                       std::string* error_detail,
+                                       bool* is_duplicate_frame);
 
   bool HasUnusedConnectionId() const {
     return !unused_connection_id_data_.empty();
diff --git a/quiche/quic/core/quic_connection_id_manager_test.cc b/quiche/quic/core/quic_connection_id_manager_test.cc
index f694cb0..466ba27 100644
--- a/quiche/quic/core/quic_connection_id_manager_test.cc
+++ b/quiche/quic/core/quic_connection_id_manager_test.cc
@@ -104,6 +104,7 @@
   QuicPeerIssuedConnectionIdManager peer_issued_cid_manager_;
   QuicAlarm* retire_peer_issued_cid_alarm_ = nullptr;
   std::string error_details_;
+  bool duplicate_frame_ = false;
 };
 
 TEST_F(QuicPeerIssuedConnectionIdManagerTest,
@@ -116,9 +117,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
 
     // Start to use CID #1 for alternative path.
     const QuicConnectionIdData* aternative_connection_id_data =
@@ -148,9 +149,9 @@
     frame.retire_prior_to = 1u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #2 for alternative path.
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
     // Connection migration succeed. Prepares to retire CID #1.
@@ -172,9 +173,9 @@
     frame.retire_prior_to = 2u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #3 for alternative path.
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
     // Connection migration succeed. Prepares to retire CID #2.
@@ -196,9 +197,9 @@
     frame.retire_prior_to = 3u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 }
 
@@ -212,9 +213,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #1 for alternative path.
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
     // Connection migration fails. Prepares to retire CID #1.
@@ -236,9 +237,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #2 for alternative path.
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
     // Connection migration fails again. Prepares to retire CID #2.
@@ -260,9 +261,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #3 for alternative path.
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
     // Connection migration succeed. Prepares to retire CID #0.
@@ -286,9 +287,9 @@
     frame.retire_prior_to = 3u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    EXPECT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    EXPECT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     EXPECT_FALSE(retire_peer_issued_cid_alarm_->IsSet());
   }
 }
@@ -304,9 +305,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
     // Start to use CID #1 for alternative path.
     // Outcome: (active: #0 #1 unused: None)
     peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
@@ -321,9 +322,9 @@
     frame.retire_prior_to = 2u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   {
@@ -335,9 +336,9 @@
     frame.retire_prior_to = 1u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   {
@@ -371,9 +372,9 @@
   frame.retire_prior_to = 0u;
   frame.stateless_reset_token =
       QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-  ASSERT_THAT(
-      peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-      IsQuicNoError());
+  ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                  frame, &error_details_, &duplicate_frame_),
+              IsQuicNoError());
   // Start to use CID #1 for alternative path.
   // Outcome: (active: #0 #1 unused: None)
   peer_issued_cid_manager_.ConsumeOneUnusedConnectionId();
@@ -387,9 +388,10 @@
       cid_manager_visitor_.most_recent_retired_connection_id_sequence_numbers(),
       ElementsAre(1u));
   // Receives the same frame again. Should be a no-op.
-  ASSERT_THAT(
-      peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-      IsQuicNoError());
+  ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                  frame, &error_details_, &duplicate_frame_),
+              IsQuicNoError());
+  EXPECT_EQ(true, duplicate_frame_);
   EXPECT_THAT(peer_issued_cid_manager_.ConsumeOneUnusedConnectionId(),
               testing::IsNull());
 }
@@ -405,9 +407,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   {
@@ -417,9 +419,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsError(QUIC_CONNECTION_ID_LIMIT_ERROR));
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsError(QUIC_CONNECTION_ID_LIMIT_ERROR));
   }
 }
 
@@ -432,9 +434,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   {
@@ -444,9 +446,9 @@
     frame.retire_prior_to = 1u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(TestConnectionId(2));
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsError(IETF_QUIC_PROTOCOL_VIOLATION));
   }
 }
 
@@ -459,9 +461,9 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   {
@@ -471,9 +473,10 @@
     frame.retire_prior_to = 0u;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(TestConnectionId(2));
-    EXPECT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    EXPECT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
+    EXPECT_EQ(true, duplicate_frame_);
     EXPECT_EQ(
         peer_issued_cid_manager_.ConsumeOneUnusedConnectionId()->connection_id,
         TestConnectionId(1));
@@ -492,9 +495,9 @@
     frame.retire_prior_to = i;
     frame.stateless_reset_token =
         QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-    ASSERT_THAT(
-        peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-        IsQuicNoError());
+    ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                    frame, &error_details_, &duplicate_frame_),
+                IsQuicNoError());
   }
 
   // Interval [40, 41) goes over the limit.
@@ -504,9 +507,9 @@
   frame.retire_prior_to = 40u;
   frame.stateless_reset_token =
       QuicUtils::GenerateStatelessResetToken(frame.connection_id);
-  ASSERT_THAT(
-      peer_issued_cid_manager_.OnNewConnectionIdFrame(frame, &error_details_),
-      IsError(IETF_QUIC_PROTOCOL_VIOLATION));
+  ASSERT_THAT(peer_issued_cid_manager_.OnNewConnectionIdFrame(
+                  frame, &error_details_, &duplicate_frame_),
+              IsError(IETF_QUIC_PROTOCOL_VIOLATION));
 }
 
 TEST_F(QuicPeerIssuedConnectionIdManagerTest, ReplaceConnectionId) {
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index ccb65bf..0e213fd 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -13136,7 +13136,6 @@
   EXPECT_TRUE(QuicConnectionPeer::IsAlternativePath(
       &connection_, kNewSelfAddress, connection_.peer_address()));
   EXPECT_TRUE(alt_path->validated);
-
   auto stats = connection_.multi_port_stats();
   EXPECT_EQ(1, connection_.GetStats().num_path_degrading);
   EXPECT_EQ(0, stats->num_multi_port_probe_failures_when_path_degrading);
@@ -13144,6 +13143,12 @@
   EXPECT_EQ(kTestRTT,
             stats->rtt_stats_when_default_path_degrading.latest_rtt());
 
+  // Receiving the retransmitted NEW_CID frame now should still have no effect.
+  if (GetQuicReloadableFlag(quic_ignore_duplicate_new_cid_frame)) {
+    EXPECT_CALL(visitor_, CreateContextForMultiPortPath).Times(0);
+    connection_.OnNewConnectionIdFrame(frame);
+  }
+
   // When there's no active request, the probing shouldn't happen. But the
   // probing context should be saved.
   EXPECT_CALL(visitor_, ShouldKeepConnectionAlive()).WillOnce(Return(false));
@@ -15346,6 +15351,7 @@
   QuicWindowUpdateFrame window_update_frame;
   QuicPathChallengeFrame path_challenge_frame;
   QuicNewConnectionIdFrame new_connection_id_frame;
+  new_connection_id_frame.sequence_number = 1u;
   QuicRetireConnectionIdFrame retire_connection_id_frame;
   retire_connection_id_frame.sequence_number = 1u;
   QuicStopSendingFrame stop_sending_frame;
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index b586bdc..ce9eed3 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -31,6 +31,8 @@
 QUIC_FLAG(quic_reloadable_flag_quic_enable_mtu_discovery_at_server, false)
 // If true, QuicGsoBatchWriter will support release time if it is available and the process has the permission to do so.
 QUIC_FLAG(quic_restart_flag_quic_support_release_time_for_gso, false)
+// If true, a duplicate NEW_CID frame will be ignore during QUIC packet processing.
+QUIC_FLAG(quic_reloadable_flag_quic_ignore_duplicate_new_cid_frame, true)
 // If true, ack frequency frame can be sent from server to client.
 QUIC_FLAG(quic_reloadable_flag_quic_can_send_ack_frequency, true)
 // If true, allow client to enable BBRv2 on server via connection option \'B2ON\'.
diff --git a/quiche/quic/core/quic_types.h b/quiche/quic/core/quic_types.h
index c17bf22..55183bb 100644
--- a/quiche/quic/core/quic_types.h
+++ b/quiche/quic/core/quic_types.h
@@ -715,6 +715,13 @@
   PACKETS_ACKED_IN_WRONG_PACKET_NUMBER_SPACE,
 };
 
+// Used to return the result of processing a received NEW_CID frame.
+enum class NewConnectionIdResult : uint8_t {
+  kOk,
+  kDuplicateFrame,  // Not an error.
+  kProtocolViolation,
+};
+
 // Indicates the fate of a serialized packet in WritePacket().
 enum SerializedPacketFate : uint8_t {
   DISCARD,         // Discard the packet.