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