While sending PATH_CHALLENGE, make sure QuicConnection flushes probing packet on alternative path before bundling ACK.
Protected by quic_reloadable_flag_quic_not_bundle_ack_on_alternative_path.
PiperOrigin-RevId: 442816394
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index c5edb1d..44a7830 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -34,6 +34,7 @@
#include "quiche/quic/core/quic_legacy_version_encapsulator.h"
#include "quiche/quic/core/quic_packet_creator.h"
#include "quiche/quic/core/quic_packet_writer.h"
+#include "quiche/quic/core/quic_packets.h"
#include "quiche/quic/core/quic_path_validator.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/core/quic_utils.h"
@@ -210,6 +211,20 @@
return kCubicBytes;
}
+bool ContainsNonProbingFrame(const SerializedPacket& packet) {
+ for (const QuicFrame& frame : packet.nonretransmittable_frames) {
+ if (!QuicUtils::IsProbingFrame(frame.type)) {
+ return true;
+ }
+ }
+ for (const QuicFrame& frame : packet.retransmittable_frames) {
+ if (!QuicUtils::IsProbingFrame(frame.type)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace
#define ENDPOINT \
@@ -3360,7 +3375,18 @@
WriteResult result(WRITE_STATUS_OK, encrypted_length);
QuicSocketAddress send_to_address = packet->peer_address;
// Self address is always the default self address on this code path.
- bool send_on_current_path = send_to_address == peer_address();
+ const bool send_on_current_path = send_to_address == peer_address();
+ if (!send_on_current_path && only_send_probing_frames_on_alternative_path_) {
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_not_bundle_ack_on_alternative_path, 2, 2);
+ QUIC_BUG_IF(quic_send_non_probing_frames_on_alternative_path,
+ ContainsNonProbingFrame(*packet))
+ << "Packet " << packet->packet_number
+ << " with non-probing frames was sent on alternative path: "
+ "nonretransmittable_frames: "
+ << QuicFramesToString(packet->nonretransmittable_frames)
+ << " retransmittable_frames: "
+ << QuicFramesToString(packet->retransmittable_frames);
+ }
switch (fate) {
case DISCARD:
++stats_.packets_discarded;
@@ -6417,28 +6443,60 @@
return connected_;
}
if (connection_migration_use_new_cid_) {
- {
- QuicConnectionId client_cid, server_cid;
- FindOnPathConnectionIds(self_address, effective_peer_address, &client_cid,
- &server_cid);
+ if (!only_send_probing_frames_on_alternative_path_) {
+ {
+ QuicConnectionId client_cid, server_cid;
+ FindOnPathConnectionIds(self_address, effective_peer_address,
+ &client_cid, &server_cid);
+ QuicPacketCreator::ScopedPeerAddressContext context(
+ &packet_creator_, peer_address, client_cid, server_cid,
+ connection_migration_use_new_cid_);
+ if (writer == writer_) {
+ ScopedPacketFlusher flusher(this);
+ // It's on current path, add the PATH_CHALLENGE the same way as other
+ // frames. This may cause connection to be closed.
+ packet_creator_.AddPathChallengeFrame(data_buffer);
+ } else {
+ std::unique_ptr<SerializedPacket> probing_packet =
+ packet_creator_.SerializePathChallengeConnectivityProbingPacket(
+ data_buffer);
+ QUICHE_DCHECK_EQ(IsRetransmittable(*probing_packet),
+ NO_RETRANSMITTABLE_DATA);
+ QUICHE_DCHECK_EQ(self_address, alternative_path_.self_address);
+ WritePacketUsingWriter(std::move(probing_packet), writer,
+ self_address, peer_address,
+ /*measure_rtt=*/false);
+ }
+ }
+ return connected_;
+ }
+ QUIC_RELOADABLE_FLAG_COUNT_N(quic_not_bundle_ack_on_alternative_path, 1, 2);
+ QuicConnectionId client_cid, server_cid;
+ FindOnPathConnectionIds(self_address, effective_peer_address, &client_cid,
+ &server_cid);
+ if (writer == writer_) {
+ ScopedPacketFlusher flusher(this);
+ {
+ QuicPacketCreator::ScopedPeerAddressContext context(
+ &packet_creator_, peer_address, client_cid, server_cid,
+ connection_migration_use_new_cid_);
+ // It's using the default writer, add the PATH_CHALLENGE the same way as
+ // other frames. This may cause connection to be closed.
+ packet_creator_.AddPathChallengeFrame(data_buffer);
+ }
+ } else {
+ // Switch to the right CID and source/peer addresses.
QuicPacketCreator::ScopedPeerAddressContext context(
&packet_creator_, peer_address, client_cid, server_cid,
connection_migration_use_new_cid_);
- if (writer == writer_) {
- ScopedPacketFlusher flusher(this);
- // It's on current path, add the PATH_CHALLENGE the same way as other
- // frames. This may cause connection to be closed.
- packet_creator_.AddPathChallengeFrame(data_buffer);
- } else {
- std::unique_ptr<SerializedPacket> probing_packet =
- packet_creator_.SerializePathChallengeConnectivityProbingPacket(
- data_buffer);
- QUICHE_DCHECK_EQ(IsRetransmittable(*probing_packet),
- NO_RETRANSMITTABLE_DATA);
- QUICHE_DCHECK_EQ(self_address, alternative_path_.self_address);
- WritePacketUsingWriter(std::move(probing_packet), writer, self_address,
- peer_address, /*measure_rtt=*/false);
- }
+ std::unique_ptr<SerializedPacket> probing_packet =
+ packet_creator_.SerializePathChallengeConnectivityProbingPacket(
+ data_buffer);
+ QUICHE_DCHECK_EQ(IsRetransmittable(*probing_packet),
+ NO_RETRANSMITTABLE_DATA);
+ QUICHE_DCHECK_EQ(self_address, alternative_path_.self_address);
+ WritePacketUsingWriter(std::move(probing_packet), writer, self_address,
+ peer_address, /*measure_rtt=*/false);
}
return connected_;
}
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 72b3fdd..6aca00b 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -2251,6 +2251,9 @@
// fixed.
absl::optional<QuicWallTime> quic_bug_10511_43_timestamp_;
std::string quic_bug_10511_43_error_detail_;
+
+ bool only_send_probing_frames_on_alternative_path_ =
+ GetQuicReloadableFlag(quic_not_bundle_ack_on_alternative_path);
};
} // namespace quic
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 522c78e..ac4ad7c 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -14037,6 +14037,123 @@
EXPECT_EQ(1u, connection_.GetStats().num_validated_peer_migration);
}
+// Regression test of b/228645208.
+TEST_P(QuicConnectionTest, NoNonProbingFrameOnAlternativePath) {
+ if (!connection_.connection_migration_use_new_cid()) {
+ return;
+ }
+
+ PathProbeTestInit(Perspective::IS_SERVER);
+ SetClientConnectionId(TestConnectionId(1));
+ connection_.CreateConnectionIdManager();
+
+ QuicConnectionId server_cid0 = connection_.connection_id();
+ QuicConnectionId client_cid0 = connection_.client_connection_id();
+ QuicConnectionId client_cid1 = TestConnectionId(2);
+ QuicConnectionId server_cid1;
+ // Sends new server CID to client.
+ EXPECT_CALL(visitor_, OnServerConnectionIdIssued(_))
+ .WillOnce(
+ Invoke([&](const QuicConnectionId& cid) { server_cid1 = cid; }));
+ EXPECT_CALL(visitor_, SendNewConnectionId(_));
+ connection_.MaybeSendConnectionIdToClient();
+ // Receives new client CID from client.
+ QuicNewConnectionIdFrame new_cid_frame;
+ new_cid_frame.connection_id = client_cid1;
+ new_cid_frame.sequence_number = 1u;
+ new_cid_frame.retire_prior_to = 0u;
+ connection_.OnNewConnectionIdFrame(new_cid_frame);
+ auto* packet_creator = QuicConnectionPeer::GetPacketCreator(&connection_);
+ ASSERT_EQ(packet_creator->GetDestinationConnectionId(), client_cid0);
+ ASSERT_EQ(packet_creator->GetSourceConnectionId(), server_cid0);
+
+ peer_creator_.SetServerConnectionId(server_cid1);
+ const QuicSocketAddress kNewPeerAddress =
+ QuicSocketAddress(QuicIpAddress::Loopback4(), /*port=*/23456);
+ QuicPathFrameBuffer path_challenge_payload{0, 1, 2, 3, 4, 5, 6, 7};
+ QuicFrames frames1;
+ frames1.push_back(
+ QuicFrame(QuicPathChallengeFrame(0, path_challenge_payload)));
+ QuicPathFrameBuffer payload;
+ EXPECT_CALL(*send_algorithm_,
+ OnPacketSent(_, _, _, _, NO_RETRANSMITTABLE_DATA))
+ .Times(AtLeast(1))
+ .WillOnce(Invoke([&]() {
+ EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address());
+ EXPECT_EQ(kPeerAddress, connection_.peer_address());
+ EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+ EXPECT_FALSE(writer_->path_response_frames().empty());
+ EXPECT_FALSE(writer_->path_challenge_frames().empty());
+ payload = writer_->path_challenge_frames().front().data_buffer;
+ }));
+ ProcessFramesPacketWithAddresses(frames1, kSelfAddress, kNewPeerAddress,
+ ENCRYPTION_FORWARD_SECURE);
+ EXPECT_EQ(kPeerAddress, connection_.peer_address());
+ EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+ EXPECT_TRUE(connection_.HasPendingPathValidation());
+ const auto* default_path = QuicConnectionPeer::GetDefaultPath(&connection_);
+ const auto* alternative_path =
+ QuicConnectionPeer::GetAlternativePath(&connection_);
+ EXPECT_EQ(default_path->client_connection_id, client_cid0);
+ EXPECT_EQ(default_path->server_connection_id, server_cid0);
+ EXPECT_EQ(alternative_path->client_connection_id, client_cid1);
+ EXPECT_EQ(alternative_path->server_connection_id, server_cid1);
+ EXPECT_EQ(packet_creator->GetDestinationConnectionId(), client_cid0);
+ EXPECT_EQ(packet_creator->GetSourceConnectionId(), server_cid0);
+
+ // Process non-probing packets on the default path.
+ peer_creator_.SetServerConnectionId(server_cid0);
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).WillRepeatedly(Invoke([=]() {
+ EXPECT_EQ(kPeerAddress, connection_.peer_address());
+ }));
+ // Receives packets 3 - 39 to send 19 ACK-only packets, which will force the
+ // connection to reach |kMaxConsecutiveNonRetransmittablePackets| while
+ // sending the next ACK.
+ for (size_t i = 3; i <= 39; ++i) {
+ ProcessDataPacket(i);
+ }
+ EXPECT_EQ(kPeerAddress, connection_.peer_address());
+ EXPECT_EQ(kPeerAddress, connection_.effective_peer_address());
+
+ EXPECT_TRUE(connection_.HasPendingAcks());
+ QuicTime ack_time = connection_.GetAckAlarm()->deadline();
+ QuicTime path_validation_retry_time =
+ connection_.GetRetryTimeout(kNewPeerAddress, writer_.get());
+ // Advance time to simultaneously fire path validation retry and ACK alarms.
+ clock_.AdvanceTime(std::max(ack_time, path_validation_retry_time) -
+ clock_.ApproximateNow());
+
+ // The 20th ACK should bundle with a WINDOW_UPDATE frame.
+ EXPECT_CALL(visitor_, OnAckNeedsRetransmittableFrame())
+ .WillOnce(Invoke([this]() {
+ connection_.SendControlFrame(QuicFrame(QuicWindowUpdateFrame(1, 0, 0)));
+ }));
+ if (GetQuicReloadableFlag(quic_not_bundle_ack_on_alternative_path)) {
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _))
+ .WillOnce(Invoke([&]() {
+ EXPECT_EQ(kNewPeerAddress, writer_->last_write_peer_address());
+ EXPECT_FALSE(writer_->path_challenge_frames().empty());
+ // Retry path validation shouldn't bundle ACK.
+ EXPECT_TRUE(writer_->ack_frames().empty());
+ }))
+ .WillOnce(Invoke([&]() {
+ EXPECT_EQ(kPeerAddress, writer_->last_write_peer_address());
+ EXPECT_FALSE(writer_->ack_frames().empty());
+ EXPECT_FALSE(writer_->window_update_frames().empty());
+ }));
+ static_cast<TestAlarmFactory::TestAlarm*>(
+ QuicPathValidatorPeer::retry_timer(
+ QuicConnectionPeer::path_validator(&connection_)))
+ ->Fire();
+ } else {
+ EXPECT_QUIC_BUG(static_cast<TestAlarmFactory::TestAlarm*>(
+ QuicPathValidatorPeer::retry_timer(
+ QuicConnectionPeer::path_validator(&connection_)))
+ ->Fire(),
+ "quic_bug_12645_2");
+ }
+}
+
TEST_P(QuicConnectionTest,
ProbedOnAnotherPathAfterPeerIpAddressChangeAtServer) {
PathProbeTestInit(Perspective::IS_SERVER);
diff --git a/quiche/quic/core/quic_flags_list.h b/quiche/quic/core/quic_flags_list.h
index 794e4f7..092ce91 100644
--- a/quiche/quic/core/quic_flags_list.h
+++ b/quiche/quic/core/quic_flags_list.h
@@ -57,6 +57,8 @@
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_disable_server_blackhole_detection, false)
// If true, discard INITIAL packet if the key has been dropped.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_discard_initial_packet_with_key_dropped, true)
+// If true, do not bundle ACK while sending PATH_CHALLENGE on alternative path.
+QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_not_bundle_ack_on_alternative_path, true)
// If true, do not count bytes sent/received on the alternative path into the bytes sent/received on the default path.
QUIC_FLAG(FLAGS_quic_reloadable_flag_quic_count_bytes_on_alternative_path_seperately, true)
// If true, enable server retransmittable on wire PING.