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;