Preempt stream data with handshake packet before handshake gets confirmed. Protected by FLAGS_quic_reloadable_flag_quic_preempt_stream_data_with_handshake_packet. PiperOrigin-RevId: 360493291 Change-Id: I5d827932b223621eff3b7384f91896cfe03a22d6
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc index 7e9a679..1b300e9 100644 --- a/quic/core/http/end_to_end_test.cc +++ b/quic/core/http/end_to_end_test.cc
@@ -2385,6 +2385,50 @@ SendSynchronousBarRequestAndCheckResponse(); } +// Regression test for b/180737158. +TEST_P( + EndToEndTest, + HalfRttResponseBlocksShloRetransmissionWithoutTokenBasedAddressValidation) { + // Turn off token based address validation to make the server get constrained + // by amplification factor during handshake. + // TODO(fayang): Keep this test while deprecating + // quic_enable_token_based_address_validation. For example, consider always + // rejecting the received address token. + SetQuicReloadableFlag(quic_enable_token_based_address_validation, false); + ASSERT_TRUE(Initialize()); + if (!version_.SupportsAntiAmplificationLimit()) { + return; + } + // Perform a full 1-RTT handshake to get the new session ticket such that the + // next connection will perform a 0-RTT handshake. + EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); + client_->Disconnect(); + + server_thread_->Pause(); + // Drop the 1st server packet which is the coalesced INITIAL + HANDSHAKE + + // 1RTT. + PacketDroppingTestWriter* writer = new PacketDroppingTestWriter(); + writer->set_fake_drop_first_n_packets(1); + QuicDispatcherPeer::UseWriter( + QuicServerPeer::GetDispatcher(server_thread_->server()), writer); + server_thread_->Resume(); + + // Large response (100KB) for 0-RTT request. + std::string large_body(102400, 'a'); + AddToCache("/large_response", 200, large_body); + if (GetQuicReloadableFlag(quic_preempt_stream_data_with_handshake_packet)) { + SendSynchronousRequestAndCheckResponse(client_.get(), "/large_response", + large_body); + } else { + // Server consistently gets constrained by amplification factor, hence PTO + // never gets armed. The CHLO retransmission would trigger the + // retransmission of SHLO, however, the ENCRYPTION_HANDSHAKE packet NEVER + // gets retransmitted since half RTT data consumes the remaining space in + // the coalescer. + EXPECT_EQ("", client_->SendSynchronousRequest("/large_response")); + } +} + TEST_P(EndToEndTest, MaxStreamsUberTest) { // Connect with lower fake packet loss than we'd like to test. Until // b/10126687 is fixed, losing handshake packets is pretty brutal.
diff --git a/quic/core/quic_coalesced_packet.cc b/quic/core/quic_coalesced_packet.cc index 2ae9b41..28f3ece 100644 --- a/quic/core/quic_coalesced_packet.cc +++ b/quic/core/quic_coalesced_packet.cc
@@ -152,6 +152,16 @@ return transmission_types_[level]; } +size_t QuicCoalescedPacket::NumberOfPackets() const { + size_t num_of_packets = 0; + for (int8_t i = ENCRYPTION_INITIAL; i < NUM_ENCRYPTION_LEVELS; ++i) { + if (ContainsPacketOfEncryptionLevel(static_cast<EncryptionLevel>(i))) { + ++num_of_packets; + } + } + return num_of_packets; +} + std::string QuicCoalescedPacket::ToString(size_t serialized_length) const { // Total length and padding size. std::string info = absl::StrCat(
diff --git a/quic/core/quic_coalesced_packet.h b/quic/core/quic_coalesced_packet.h index e3f674e..e8bc2c3 100644 --- a/quic/core/quic_coalesced_packet.h +++ b/quic/core/quic_coalesced_packet.h
@@ -46,6 +46,9 @@ // when this coalesced packet contains packet of |level|. TransmissionType TransmissionTypeOfPacket(EncryptionLevel level) const; + // Returns number of packets contained in this coalesced packet. + size_t NumberOfPackets() const; + const SerializedPacket* initial_packet() const { return initial_packet_.get(); }
diff --git a/quic/core/quic_coalesced_packet_test.cc b/quic/core/quic_coalesced_packet_test.cc index 1d629ae..34f6da3 100644 --- a/quic/core/quic_coalesced_packet_test.cc +++ b/quic/core/quic_coalesced_packet_test.cc
@@ -19,6 +19,7 @@ coalesced.ToString(0)); SimpleBufferAllocator allocator; EXPECT_EQ(0u, coalesced.length()); + EXPECT_EQ(0u, coalesced.NumberOfPackets()); char buffer[1000]; QuicSocketAddress self_address(QuicIpAddress::Loopback4(), 1); QuicSocketAddress peer_address(QuicIpAddress::Loopback4(), 2); @@ -35,6 +36,7 @@ coalesced.TransmissionTypeOfPacket(ENCRYPTION_INITIAL)); EXPECT_EQ(1500u, coalesced.max_packet_length()); EXPECT_EQ(500u, coalesced.length()); + EXPECT_EQ(1u, coalesced.NumberOfPackets()); EXPECT_EQ( "total_length: 1500 padding_size: 1000 packets: {ENCRYPTION_INITIAL}", coalesced.ToString(1500)); @@ -54,6 +56,7 @@ &allocator, 1500)); EXPECT_EQ(1500u, coalesced.max_packet_length()); EXPECT_EQ(1000u, coalesced.length()); + EXPECT_EQ(2u, coalesced.NumberOfPackets()); EXPECT_EQ(LOSS_RETRANSMISSION, coalesced.TransmissionTypeOfPacket(ENCRYPTION_ZERO_RTT)); EXPECT_EQ( @@ -77,6 +80,7 @@ peer_address, &allocator, 1500)); EXPECT_EQ(1500u, coalesced.max_packet_length()); EXPECT_EQ(1000u, coalesced.length()); + EXPECT_EQ(2u, coalesced.NumberOfPackets()); // Max packet number length changed. SerializedPacket packet6(QuicPacketNumber(6), PACKET_4BYTE_PACKET_NUMBER, @@ -87,6 +91,7 @@ "Max packet length changes in the middle of the write path"); EXPECT_EQ(1500u, coalesced.max_packet_length()); EXPECT_EQ(1000u, coalesced.length()); + EXPECT_EQ(2u, coalesced.NumberOfPackets()); } TEST(QuicCoalescedPacketTest, CopyEncryptedBuffers) {
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 471331b..bbdb64c 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -2315,6 +2315,21 @@ QuicUtils::IsCryptoStreamId(transport_version(), id)) { MaybeActivateLegacyVersionEncapsulation(); } + if (GetQuicReloadableFlag(quic_preempt_stream_data_with_handshake_packet) && + perspective_ == Perspective::IS_SERVER && + version().CanSendCoalescedPackets() && !IsHandshakeConfirmed()) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_preempt_stream_data_with_handshake_packet, + 1, 2); + if (coalesced_packet_.ContainsPacketOfEncryptionLevel(ENCRYPTION_INITIAL) && + coalesced_packet_.NumberOfPackets() == 1u) { + // Handshake is not confirmed yet, if there is only an initial packet in + // the coalescer, try to bundle an ENCRYPTION_HANDSHAKE packet before + // sending stream data. + QUIC_RELOADABLE_FLAG_COUNT_N( + quic_preempt_stream_data_with_handshake_packet, 2, 2); + sent_packet_manager_.RetransmitDataOfSpaceIfAny(HANDSHAKE_DATA); + } + } QuicConsumedData consumed_data(0, false); { // Opportunistically bundle an ack with every outgoing packet.
diff --git a/quic/core/quic_flags_list.h b/quic/core/quic_flags_list.h index 5b4cbbc..19a466f 100644 --- a/quic/core/quic_flags_list.h +++ b/quic/core/quic_flags_list.h
@@ -47,6 +47,7 @@ QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_fix_willing_and_able_to_write2, true) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_h3_datagram, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_pass_path_response_to_validator, false) +QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_preempt_stream_data_with_handshake_packet, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_require_handshake_confirmation, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_path_response, false) QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_send_timestamps, false)