Allow client to buffer Handshake packets when the server Initial is delayed, even if the connection ID changes.
PiperOrigin-RevId: 478100528
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index bf50a86..0e71a7f 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -214,6 +214,20 @@
header.long_packet_type == RETRY);
}
+// Due to a lost Initial packet, a Handshake packet might use a new connection
+// ID we haven't seen before. We shouldn't update the connection ID based on
+// this, but should buffer the packet in case it works out.
+bool NewServerConnectionIdMightBeValid(const QuicPacketHeader& header,
+ Perspective perspective,
+ bool connection_id_already_replaced) {
+ return perspective == Perspective::IS_CLIENT &&
+ header.form == IETF_QUIC_LONG_HEADER_PACKET &&
+ header.version.IsKnown() &&
+ header.version.AllowsVariableLengthConnectionIds() &&
+ header.long_packet_type == HANDSHAKE &&
+ !connection_id_already_replaced;
+}
+
CongestionControlType GetDefaultCongestionControlType() {
if (GetQuicReloadableFlag(quic_default_to_bbr_v2)) {
return kBBRv2;
@@ -1015,6 +1029,11 @@
return true;
}
+ if (NewServerConnectionIdMightBeValid(
+ header, perspective_, server_connection_id_replaced_by_initial_)) {
+ return true;
+ }
+
return false;
}
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 8b96894..4d9c91d 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -15670,6 +15670,85 @@
}
}
+ACTION_P(InstallHandshakeKeys, conn) {
+ conn->SetEncrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<TaggingEncrypter>(0x02));
+ conn->InstallDecrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<StrictTaggingDecrypter>(0x02));
+ conn->SetDefaultEncryptionLevel(ENCRYPTION_HANDSHAKE);
+}
+
+TEST_P(QuicConnectionTest, ServerConnectionIdChangeWithLostInitial) {
+ if (!connection_.version().HasIetfQuicFrames()) {
+ return;
+ }
+ // Call SetFromConfig so that the undecrypted packet buffer size is
+ // initialized above zero.
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(1);
+ QuicConfig config;
+ connection_.SetFromConfig(config);
+
+ // Send Client Initial.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+ connection_.SendCryptoStreamData();
+
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+ // Server Handshake Packet Arrives with new connection ID.
+ QuicConnectionId old_id = connection_id_;
+ connection_id_ = TestConnectionId(2);
+ peer_creator_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<TaggingEncrypter>(0x02));
+ ProcessCryptoPacketAtLevel(0, ENCRYPTION_HANDSHAKE);
+ // Packet is buffered.
+ EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 1u);
+ EXPECT_EQ(connection_.connection_id(), old_id);
+
+ // Pretend the server Initial packet will yield the Handshake keys.
+ EXPECT_CALL(visitor_, OnCryptoFrame(_))
+ .Times(2)
+ .WillOnce(InstallHandshakeKeys(&connection_))
+ .WillOnce(Return());
+ ProcessCryptoPacketAtLevel(0, ENCRYPTION_INITIAL);
+ // Two packets processed, connection ID changed.
+ EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 0u);
+ EXPECT_EQ(connection_.connection_id(), connection_id_);
+}
+
+TEST_P(QuicConnectionTest, ServerConnectionIdChangeTwiceWithLostInitial) {
+ if (!connection_.version().HasIetfQuicFrames()) {
+ return;
+ }
+ // Call SetFromConfig so that the undecrypted packet buffer size is
+ // initialized above zero.
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _)).Times(1);
+ QuicConfig config;
+ connection_.SetFromConfig(config);
+
+ // Send Client Initial.
+ connection_.SetDefaultEncryptionLevel(ENCRYPTION_INITIAL);
+ connection_.SendCryptoStreamData();
+
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+ // Server Handshake Packet Arrives with new connection ID.
+ QuicConnectionId old_id = connection_id_;
+ connection_id_ = TestConnectionId(2);
+ peer_creator_.SetEncrypter(ENCRYPTION_HANDSHAKE,
+ std::make_unique<TaggingEncrypter>(0x02));
+ ProcessCryptoPacketAtLevel(0, ENCRYPTION_HANDSHAKE);
+ // Packet is buffered.
+ EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 1u);
+ EXPECT_EQ(connection_.connection_id(), old_id);
+
+ // Pretend the server Initial packet will yield the Handshake keys.
+ EXPECT_CALL(visitor_, OnCryptoFrame(_))
+ .WillOnce(InstallHandshakeKeys(&connection_));
+ connection_id_ = TestConnectionId(1);
+ ProcessCryptoPacketAtLevel(0, ENCRYPTION_INITIAL);
+ // Handshake packet discarded because there's a different connection ID.
+ EXPECT_EQ(QuicConnectionPeer::NumUndecryptablePackets(&connection_), 0u);
+ EXPECT_EQ(connection_.connection_id(), connection_id_);
+}
+
} // namespace
} // namespace test
} // namespace quic