Change IPv6 flow labels when QUIC connections have a retransmission timeout or when a new packet that creates a gap is received with a new flow lable.
PiperOrigin-RevId: 686570306
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 9be867e..d6e2fa1 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -1166,6 +1166,26 @@
has_path_challenge_in_current_packet_ = false;
current_effective_peer_migration_type_ = NO_CHANGE;
+ if (enable_black_hole_avoidance_via_flow_label_) {
+ if (!GetLargestReceivedPacket().IsInitialized()) {
+ last_flow_label_received_ = last_received_packet_info_.flow_label;
+ } else if (header.packet_number > GetLargestReceivedPacket() &&
+ last_received_packet_info_.flow_label !=
+ last_flow_label_received_) {
+ if (expect_peer_flow_label_change_) {
+ expect_peer_flow_label_change_ = false;
+ } else if (header.packet_number > GetLargestReceivedPacket() + 1) {
+ // This packet introduced a packet number gap and came with a new flow
+ // label so the peer is RTO'ing. In response, send a different flow
+ // label.
+ uint32_t flow_label;
+ random_generator_->RandBytes(&flow_label, sizeof(flow_label));
+ set_outgoing_flow_label(flow_label);
+ }
+ last_flow_label_received_ = last_received_packet_info_.flow_label;
+ }
+ }
+
if (perspective_ == Perspective::IS_CLIENT) {
if (!GetLargestReceivedPacket().IsInitialized() ||
header.packet_number > GetLargestReceivedPacket()) {
@@ -4177,6 +4197,12 @@
debug_visitor_->OnNPacketNumbersSkipped(num_packet_numbers_to_skip,
clock_->Now());
}
+ if (enable_black_hole_avoidance_via_flow_label_) {
+ uint32_t flow_label;
+ random_generator_->RandBytes(&flow_label, sizeof(flow_label));
+ set_outgoing_flow_label(flow_label);
+ expect_peer_flow_label_change_ = true;
+ }
}
if (default_enable_5rto_blackhole_detection_ &&
!sent_packet_manager_.HasInFlightPackets() &&
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 61d783d..be9bcb6 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1448,6 +1448,14 @@
return last_received_packet_info_.flow_label;
}
+ void EnableBlackholeAvoidanceViaFlowLabel() {
+ enable_black_hole_avoidance_via_flow_label_ = true;
+ }
+
+ bool enable_black_hole_avoidance_via_flow_label() const {
+ return enable_black_hole_avoidance_via_flow_label_;
+ }
+
void OnDiscardZeroRttDecryptionKeysAlarm() override;
void OnIdleDetectorAlarm() override;
void OnNetworkBlackholeDetectorAlarm() override;
@@ -2572,6 +2580,15 @@
uint32_t last_flow_label_sent_ = 0;
// The flow label to be sent for outgoing packets.
uint32_t outgoing_flow_label_ = 0;
+ // The flow label of the packet with the largest packet number received
+ // from the peer.
+ uint32_t last_flow_label_received_ = 0;
+ // True if the peer is expected to change their flow label in response to
+ // a flow label change made by this connection.
+ bool expect_peer_flow_label_change_ = false;
+ // If true then flow labels will be changed when a PTO fires, or when
+ // a PTO'd packet from a peer is detected.
+ bool enable_black_hole_avoidance_via_flow_label_ = false;
// If true, the peer has indicated that it supports the RESET_STREAM_AT frame.
bool reliable_stream_reset_ = false;
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 69dd796..8d36e98 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -992,6 +992,11 @@
level);
}
+ size_t ProcessDataPacketAtLevel(uint64_t number, bool has_stop_waiting,
+ EncryptionLevel level) {
+ return ProcessDataPacketAtLevel(number, has_stop_waiting, level, 0);
+ }
+
size_t ProcessCryptoPacketAtLevel(uint64_t number, EncryptionLevel level) {
QuicPacketHeader header = ConstructPacketHeader(number, level);
QuicFrames frames;
@@ -1019,7 +1024,7 @@
}
size_t ProcessDataPacketAtLevel(uint64_t number, bool has_stop_waiting,
- EncryptionLevel level) {
+ EncryptionLevel level, uint32_t flow_label) {
std::unique_ptr<QuicPacket> packet(
ConstructDataPacket(number, has_stop_waiting, level));
char buffer[kMaxOutgoingPacketSize];
@@ -1029,7 +1034,12 @@
buffer, kMaxOutgoingPacketSize);
connection_.ProcessUdpPacket(
kSelfAddress, kPeerAddress,
- QuicReceivedPacket(buffer, encrypted_length, clock_.Now(), false));
+ QuicReceivedPacket(buffer, encrypted_length, clock_.Now(), false,
+ 0 /* ttl */, true /* ttl_valid */,
+ nullptr /* packet_headers */, 0 /* headers_length */,
+ false /* owns_header_buffer */, ECN_NOT_ECT,
+ flow_label));
+
if (connection_.GetSendAlarm()->IsSet()) {
connection_.GetSendAlarm()->Fire();
}
@@ -9977,6 +9987,87 @@
EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
}
+TEST_P(QuicConnectionTest, PtoChangesFlowLabel) {
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(k1PTO);
+ connection_options.push_back(kPTOS);
+ config.SetConnectionOptionsToSend(connection_options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ EXPECT_FALSE(connection_.GetRetransmissionAlarm()->IsSet());
+ connection_.set_outgoing_flow_label(1);
+ connection_.EnableBlackholeAvoidanceViaFlowLabel();
+
+ QuicStreamId stream_id = 2;
+ QuicPacketNumber last_packet;
+ SendStreamDataToPeer(stream_id, "foooooo", 0, NO_FIN, &last_packet);
+ SendStreamDataToPeer(stream_id, "foooooo", 7, NO_FIN, &last_packet);
+ EXPECT_EQ(QuicPacketNumber(2), last_packet);
+ EXPECT_TRUE(connection_.GetRetransmissionAlarm()->IsSet());
+
+ // Fire PTO and verify the flow label has changed.
+ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)).Times(1);
+ connection_.GetRetransmissionAlarm()->Fire();
+ EXPECT_NE(1, connection_.outgoing_flow_label());
+}
+
+TEST_P(QuicConnectionTest, NewReceiveNewFlowLabelWithGapChangesFlowLabel) {
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(k1PTO);
+ connection_options.push_back(kPTOS);
+ config.SetConnectionOptionsToSend(connection_options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ connection_.EnableBlackholeAvoidanceViaFlowLabel();
+ const uint32_t flow_label = 1;
+ connection_.set_outgoing_flow_label(flow_label);
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+
+ // Receive the first packet to initialize the flow label.
+ ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_INITIAL, 0);
+ EXPECT_EQ(1, connection_.outgoing_flow_label());
+
+ // Receive the second packet with the same flow label
+ ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_INITIAL, flow_label);
+ EXPECT_EQ(1, connection_.outgoing_flow_label());
+
+ // Receive a packet with gap and a new flow label and verify the outgoing
+ // flow label has changed.
+ ProcessDataPacketAtLevel(4, !kHasStopWaiting, ENCRYPTION_INITIAL,
+ flow_label + 1);
+ EXPECT_NE(flow_label, connection_.outgoing_flow_label());
+}
+
+TEST_P(QuicConnectionTest,
+ NewReceiveNewFlowLabelWithNoGapDoesNotChangeFlowLabel) {
+ QuicConfig config;
+ QuicTagVector connection_options;
+ connection_options.push_back(k1PTO);
+ connection_options.push_back(kPTOS);
+ config.SetConnectionOptionsToSend(connection_options);
+ EXPECT_CALL(*send_algorithm_, SetFromConfig(_, _));
+ connection_.SetFromConfig(config);
+ connection_.EnableBlackholeAvoidanceViaFlowLabel();
+ const uint32_t flow_label = 1;
+ connection_.set_outgoing_flow_label(flow_label);
+ EXPECT_CALL(visitor_, OnStreamFrame(_)).Times(AnyNumber());
+
+ // Receive the first packet to initialize the flow label.
+ ProcessDataPacketAtLevel(1, !kHasStopWaiting, ENCRYPTION_INITIAL, 0);
+ EXPECT_EQ(1, connection_.outgoing_flow_label());
+
+ // Receive the second packet with the same flow label
+ ProcessDataPacketAtLevel(2, !kHasStopWaiting, ENCRYPTION_INITIAL, flow_label);
+ EXPECT_EQ(1, connection_.outgoing_flow_label());
+
+ // Receive a packet with no gap and a new flow label and verify the outgoing
+ // flow label has not changed.
+ ProcessDataPacketAtLevel(3, !kHasStopWaiting, ENCRYPTION_INITIAL, flow_label);
+ EXPECT_EQ(flow_label, connection_.outgoing_flow_label());
+}
+
TEST_P(QuicConnectionTest, SendCoalescedPackets) {
if (!connection_.version().CanSendCoalescedPackets()) {
return;