Re-land cl/443493674 by fixing build error.
Original description: When retransmittable-on-wire timeout, re-send 1st 1-RTT packet or random bytes (which are behind client connection options ROWF and ROWR, respectively).
PiperOrigin-RevId: 443532932
diff --git a/quiche/quic/core/crypto/crypto_protocol.h b/quiche/quic/core/crypto/crypto_protocol.h
index aef3ff1..6ca1797 100644
--- a/quiche/quic/core/crypto/crypto_protocol.h
+++ b/quiche/quic/core/crypto/crypto_protocol.h
@@ -426,6 +426,10 @@
const QuicTag kSRWP = TAG('S', 'R', 'W', 'P'); // Enable retransmittable on
// wire PING (ROWP) on the
// server side.
+const QuicTag kROWF = TAG('R', 'O', 'W', 'F'); // Send first 1-RTT packet on
+ // ROWP timeout.
+const QuicTag kROWR = TAG('R', 'O', 'W', 'R'); // Send random bytes on ROWP
+ // timeout.
const QuicTag kGSR0 = TAG('G', 'S', 'R', '0'); // Selective Resumption
const QuicTag kNRES = TAG('N', 'R', 'E', 'S'); // No resumption
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index db2597b..34f6b74 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -589,6 +589,16 @@
if (config.HasClientRequestedIndependentOption(kFIDT, perspective_)) {
idle_network_detector_.enable_shorter_idle_timeout_on_sent_packet();
}
+ if (perspective_ == Perspective::IS_CLIENT && version().HasIetfQuicFrames()) {
+ // Only conduct those experiments in IETF QUIC because random packets may
+ // elicit reset and gQUIC PUBLIC_RESET will cause connection close.
+ if (config.HasClientRequestedIndependentOption(kROWF, perspective_)) {
+ retransmittable_on_wire_behavior_ = SEND_FIRST_FORWARD_SECURE_PACKET;
+ }
+ if (config.HasClientRequestedIndependentOption(kROWR, perspective_)) {
+ retransmittable_on_wire_behavior_ = SEND_RANDOM_BYTES;
+ }
+ }
if (config.HasClientRequestedIndependentOption(k3AFF, perspective_)) {
anti_amplification_factor_ = 3;
}
@@ -3925,6 +3935,12 @@
} else {
consecutive_num_packets_with_no_retransmittable_frames_ = 0;
}
+ if (retransmittable_on_wire_behavior_ == SEND_FIRST_FORWARD_SECURE_PACKET &&
+ first_serialized_one_rtt_packet_ == nullptr &&
+ serialized_packet.encryption_level == ENCRYPTION_FORWARD_SECURE) {
+ first_serialized_one_rtt_packet_ = std::make_unique<BufferedPacket>(
+ serialized_packet, self_address(), peer_address());
+ }
SendOrQueuePacket(std::move(serialized_packet));
}
@@ -4929,6 +4945,17 @@
memcpy(data.get(), encrypted_buffer, encrypted_length);
}
+QuicConnection::BufferedPacket::BufferedPacket(
+ QuicRandom& random, QuicPacketLength encrypted_length,
+ const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address)
+ : length(encrypted_length),
+ self_address(self_address),
+ peer_address(peer_address) {
+ data = std::make_unique<char[]>(encrypted_length);
+ random.RandBytes(data.get(), encrypted_length);
+}
+
HasRetransmittableData QuicConnection::IsRetransmittable(
const SerializedPacket& packet) {
// Retransmitted packets retransmittable frames are owned by the unacked
@@ -6278,6 +6305,42 @@
!visitor_->ShouldKeepConnectionAlive()) {
return;
}
+ bool packet_buffered = false;
+ switch (retransmittable_on_wire_behavior_) {
+ case DEFAULT:
+ break;
+ case SEND_FIRST_FORWARD_SECURE_PACKET:
+ if (first_serialized_one_rtt_packet_ != nullptr) {
+ buffered_packets_.emplace_back(
+ first_serialized_one_rtt_packet_->data.get(),
+ first_serialized_one_rtt_packet_->length, self_address(),
+ peer_address());
+ packet_buffered = true;
+ }
+ break;
+ case SEND_RANDOM_BYTES:
+ const QuicPacketLength random_bytes_length = std::max<QuicPacketLength>(
+ QuicFramer::GetMinStatelessResetPacketLength() + 1,
+ random_generator_->RandUint64() %
+ packet_creator_.max_packet_length());
+ buffered_packets_.emplace_back(*random_generator_, random_bytes_length,
+ self_address(), peer_address());
+ packet_buffered = true;
+ break;
+ }
+ if (packet_buffered) {
+ if (!writer_->IsWriteBlocked()) {
+ WriteQueuedPackets();
+ }
+ if (connected_) {
+ // Always reset PING alarm with has_in_flight_packets=true. This is used
+ // to avoid re-arming the alarm in retransmittable-on-wire mode.
+ ping_manager_.SetAlarm(clock_->ApproximateNow(),
+ visitor_->ShouldKeepConnectionAlive(),
+ /*has_in_flight_packets=*/true);
+ }
+ return;
+ }
SendPingAtLevel(framer().GetEncryptionLevelToSendApplicationData());
}
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index b73f5c4..d7b83e5 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1338,6 +1338,12 @@
private:
friend class test::QuicConnectionPeer;
+ enum RetransmittableOnWireBehavior {
+ DEFAULT, // Send packet containing a PING frame.
+ SEND_FIRST_FORWARD_SECURE_PACKET, // Send 1st 1-RTT packet.
+ SEND_RANDOM_BYTES // Send random bytes which is an unprocessable packet.
+ };
+
struct QUIC_EXPORT_PRIVATE PendingPathChallenge {
QuicPathFrameBuffer received_path_challenge;
QuicSocketAddress peer_address;
@@ -1399,6 +1405,11 @@
QuicPacketLength encrypted_length,
const QuicSocketAddress& self_address,
const QuicSocketAddress& peer_address);
+ // Please note, this buffered packet contains random bytes (and is not
+ // *actually* a QUIC packet).
+ BufferedPacket(QuicRandom& random, QuicPacketLength encrypted_length,
+ const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address);
BufferedPacket(const BufferedPacket& other) = delete;
BufferedPacket(const BufferedPacket&& other) = delete;
@@ -2252,6 +2263,11 @@
QuicPingManager ping_manager_;
+ // Records first serialized 1-RTT packet.
+ std::unique_ptr<BufferedPacket> first_serialized_one_rtt_packet_;
+
+ RetransmittableOnWireBehavior retransmittable_on_wire_behavior_ = DEFAULT;
+
// TODO(b/205023946) Debug-only fields, to be deprecated after the bug is
// fixed.
absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 97f69f3..b77cc15 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -1285,6 +1285,10 @@
void SimulateNextPacketTooLarge() { writer_->SimulateNextPacketTooLarge(); }
+ void ExpectNextPacketUnprocessable() {
+ writer_->ExpectNextPacketUnprocessable();
+ }
+
void AlwaysGetPacketTooLarge() { writer_->AlwaysGetPacketTooLarge(); }
void SetWritePauseTimeDelta(QuicTime::Delta delta) {
@@ -7646,6 +7650,150 @@
connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
}
+TEST_P(QuicConnectionTest, RetransmittableOnWireSendFirstPacket) {
+ if (!GetQuicReloadableFlag(quic_use_ping_manager) ||
+ !VersionHasIetfQuicFrames(connection_.version().transport_version)) {
+ return;
+ }
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+ const QuicTime::Delta kRetransmittableOnWireTimeout =
+ QuicTime::Delta::FromMilliseconds(200);
+ const QuicTime::Delta kTestRtt = QuicTime::Delta::FromMilliseconds(100);
+
+ connection_.set_initial_retransmittable_on_wire_timeout(
+ kRetransmittableOnWireTimeout);
+
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(kROWF);
+ config.SetClientConnectionOptions(connection_options);
+ connection_.SetFromConfig(config);
+
+ // Send a request.
+ connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+ // Receive an ACK after 1-RTT.
+ clock_.AdvanceTime(kTestRtt);
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame =
+ InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}});
+ ProcessAckPacket(&frame);
+ ASSERT_TRUE(connection_.GetPingAlarm()->IsSet());
+ EXPECT_EQ(kRetransmittableOnWireTimeout,
+ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+
+ // Fire retransmittable-on-wire alarm.
+ clock_.AdvanceTime(kRetransmittableOnWireTimeout);
+ connection_.GetPingAlarm()->Fire();
+ EXPECT_EQ(2u, writer_->packets_write_attempts());
+ // Verify alarm is set in keep-alive mode.
+ ASSERT_TRUE(connection_.GetPingAlarm()->IsSet());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+}
+
+TEST_P(QuicConnectionTest, RetransmittableOnWireSendRandomBytes) {
+ if (!GetQuicReloadableFlag(quic_use_ping_manager) ||
+ !VersionHasIetfQuicFrames(connection_.version().transport_version)) {
+ return;
+ }
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+
+ const QuicTime::Delta kRetransmittableOnWireTimeout =
+ QuicTime::Delta::FromMilliseconds(200);
+ const QuicTime::Delta kTestRtt = QuicTime::Delta::FromMilliseconds(100);
+
+ connection_.set_initial_retransmittable_on_wire_timeout(
+ kRetransmittableOnWireTimeout);
+
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(kROWR);
+ config.SetClientConnectionOptions(connection_options);
+ connection_.SetFromConfig(config);
+
+ // Send a request.
+ connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+ // Receive an ACK after 1-RTT.
+ clock_.AdvanceTime(kTestRtt);
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame =
+ InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}});
+ ProcessAckPacket(&frame);
+ ASSERT_TRUE(connection_.GetPingAlarm()->IsSet());
+ EXPECT_EQ(kRetransmittableOnWireTimeout,
+ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+
+ // Fire retransmittable-on-wire alarm.
+ clock_.AdvanceTime(kRetransmittableOnWireTimeout);
+ // Next packet is not processable by the framer in the test writer.
+ ExpectNextPacketUnprocessable();
+ connection_.GetPingAlarm()->Fire();
+ EXPECT_EQ(2u, writer_->packets_write_attempts());
+ // Verify alarm is set in keep-alive mode.
+ ASSERT_TRUE(connection_.GetPingAlarm()->IsSet());
+ EXPECT_EQ(QuicTime::Delta::FromSeconds(kPingTimeoutSecs),
+ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+}
+
+TEST_P(QuicConnectionTest,
+ RetransmittableOnWireSendRandomBytesWithWriterBlocked) {
+ if (!GetQuicReloadableFlag(quic_use_ping_manager) ||
+ !VersionHasIetfQuicFrames(connection_.version().transport_version)) {
+ return;
+ }
+ EXPECT_CALL(visitor_, ShouldKeepConnectionAlive())
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_));
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(1);
+
+ const QuicTime::Delta kRetransmittableOnWireTimeout =
+ QuicTime::Delta::FromMilliseconds(200);
+ const QuicTime::Delta kTestRtt = QuicTime::Delta::FromMilliseconds(100);
+
+ connection_.set_initial_retransmittable_on_wire_timeout(
+ kRetransmittableOnWireTimeout);
+
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(kROWR);
+ config.SetClientConnectionOptions(connection_options);
+ connection_.SetFromConfig(config);
+
+ // Send a request.
+ connection_.SendStreamDataWithString(3, "foo", 0, NO_FIN);
+ // Receive an ACK after 1-RTT.
+ clock_.AdvanceTime(kTestRtt);
+ EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _));
+ QuicAckFrame frame =
+ InitAckFrame({{QuicPacketNumber(1), QuicPacketNumber(2)}});
+ ProcessAckPacket(&frame);
+ ASSERT_TRUE(connection_.GetPingAlarm()->IsSet());
+ EXPECT_EQ(kRetransmittableOnWireTimeout,
+ connection_.GetPingAlarm()->deadline() - clock_.ApproximateNow());
+ EXPECT_EQ(1u, writer_->packets_write_attempts());
+ // Receive an out of order data packet and block the ACK packet.
+ BlockOnNextWrite();
+ ProcessDataPacket(3);
+ EXPECT_EQ(2u, writer_->packets_write_attempts());
+ EXPECT_EQ(1u, connection_.NumQueuedPackets());
+
+ // Fire retransmittable-on-wire alarm.
+ clock_.AdvanceTime(kRetransmittableOnWireTimeout);
+ connection_.GetPingAlarm()->Fire();
+ // Verify the random bytes packet gets queued.
+ EXPECT_EQ(2u, connection_.NumQueuedPackets());
+}
+
// This test verifies that the connection marks path as degrading and does not
// spin timer to detect path degrading when a new packet is sent on the
// degraded path.
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc
index b182dc5..741159a 100644
--- a/quiche/quic/test_tools/quic_test_utils.cc
+++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -1335,9 +1335,10 @@
ENCRYPTION_FORWARD_SECURE,
std::make_unique<NullDecrypter>(framer_.framer()->perspective()));
}
- EXPECT_TRUE(framer_.ProcessPacket(packet))
+ EXPECT_EQ(next_packet_processable_, framer_.ProcessPacket(packet))
<< framer_.framer()->detailed_error() << " perspective "
<< framer_.framer()->perspective();
+ next_packet_processable_ = true;
if (block_on_next_write_) {
write_blocked_ = true;
block_on_next_write_ = false;
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index b7bda22..e074008 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -1777,6 +1777,8 @@
void SimulateNextPacketTooLarge() { next_packet_too_large_ = true; }
+ void ExpectNextPacketUnprocessable() { next_packet_processable_ = false; }
+
void AlwaysGetPacketTooLarge() { always_get_packet_too_large_ = true; }
// Sets the amount of time that the writer should before the actual write.
@@ -1923,6 +1925,7 @@
bool block_on_next_flush_ = false;
bool block_on_next_write_ = false;
bool next_packet_too_large_ = false;
+ bool next_packet_processable_ = true;
bool always_get_packet_too_large_ = false;
bool is_write_blocked_data_buffered_ = false;
bool is_batch_mode_ = false;