gfe-relnote: If batch writer is used in a QuicConneciton, flush it right after a MTU probe is sent. Protected by --gfe2_reloadable_flag_quic_batch_writer_flush_after_mtu_probe. PiperOrigin-RevId: 292547985 Change-Id: I14c3eab267c5d2a78c0fa742285910c7b1281161
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 09820f3..9d9f1fc 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -337,7 +337,9 @@ GetQuicReloadableFlag(quic_use_handshaker_delegate2) || version().handshake_protocol == PROTOCOL_TLS1_3), check_handshake_timeout_before_idle_timeout_(GetQuicReloadableFlag( - quic_check_handshake_timeout_before_idle_timeout)) { + quic_check_handshake_timeout_before_idle_timeout)), + batch_writer_flush_after_mtu_probe_( + GetQuicReloadableFlag(quic_batch_writer_flush_after_mtu_probe)) { QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID " << server_connection_id << " and version: " << ParsedQuicVersionToString(version()); @@ -2192,8 +2194,9 @@ ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return true; } - SerializedPacketFate fate = DeterminePacketFate( - /*is_mtu_discovery=*/packet->encrypted_length > long_term_mtu_); + const bool looks_like_mtu_probe = packet->retransmittable_frames.empty() && + packet->encrypted_length > long_term_mtu_; + SerializedPacketFate fate = DeterminePacketFate(looks_like_mtu_probe); // Termination packets are encrypted and saved, so don't exit early. const bool is_termination_packet = IsTerminationPacket(*packet); QuicPacketNumber packet_number = packet->packet_number; @@ -2211,8 +2214,6 @@ new QuicEncryptedPacket(buffer_copy, encrypted_length, true)); } - const bool looks_like_mtu_probe = packet->retransmittable_frames.empty() && - packet->encrypted_length > long_term_mtu_; DCHECK_LE(encrypted_length, kMaxOutgoingPacketSize); if (!looks_like_mtu_probe) { DCHECK_LE(encrypted_length, packet_creator_.max_packet_length()); @@ -2285,6 +2286,18 @@ result = writer_->WritePacket(packet->encrypted_buffer, encrypted_length, self_address().host(), peer_address(), per_packet_options_); + // This is a work around for an issue with linux UDP GSO batch writers. + // When sending a GSO packet with 2 segments, if the first segment is + // larger than the path MTU, instead of EMSGSIZE, the linux kernel returns + // EINVAL, which translates to WRITE_STATUS_ERROR and causes conneciton to + // be closed. By manually flush the writer here, the MTU probe is sent in + // a normal(non-GSO) packet, so the kernel can return EMSGSIZE and we will + // not close the connection. + if (batch_writer_flush_after_mtu_probe_ && looks_like_mtu_probe && + writer_->IsBatchMode()) { + QUIC_RELOADABLE_FLAG_COUNT(quic_batch_writer_flush_after_mtu_probe); + result = writer_->Flush(); + } break; case FAILED_TO_WRITE_COALESCED_PACKET: // Failed to send existing coalesced packet when determining packet fate,
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index bcedee0..12a899f 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -1520,6 +1520,9 @@ // Latched value of quic_check_handshake_timeout_before_idle_timeout. const bool check_handshake_timeout_before_idle_timeout_; + + // Latched value of quic_batch_writer_flush_after_mtu_probe. + const bool batch_writer_flush_after_mtu_probe_; }; } // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 7809cc5..8f02bbb 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -340,24 +340,7 @@ TestPacketWriter(ParsedQuicVersion version, MockClock* clock) : version_(version), framer_(SupportedVersions(version_), Perspective::IS_SERVER), - last_packet_size_(0), - write_blocked_(false), - write_should_fail_(false), - block_on_next_flush_(false), - block_on_next_write_(false), - next_packet_too_large_(false), - always_get_packet_too_large_(false), - is_write_blocked_data_buffered_(false), - is_batch_mode_(false), - final_bytes_of_last_packet_(0), - final_bytes_of_previous_packet_(0), - use_tagging_decrypter_(false), - packets_write_attempts_(0), - connection_close_packets_(0), - clock_(clock), - write_pause_time_delta_(QuicTime::Delta::Zero()), - max_packet_size_(kMaxOutgoingPacketSize), - supports_release_time_(false) { + clock_(clock) { QuicFramerPeer::SetLastSerializedServerConnectionId(framer_.framer(), TestConnectionId()); framer_.framer()->SetInitialObfuscators(TestConnectionId()); @@ -430,6 +413,10 @@ if (!write_pause_time_delta_.IsZero()) { clock_->AdvanceTime(write_pause_time_delta_); } + if (is_batch_mode_) { + bytes_buffered_ += last_packet_size_; + return WriteResult(WRITE_STATUS_OK, 0); + } return WriteResult(WRITE_STATUS_OK, last_packet_size_); } @@ -459,12 +446,15 @@ } WriteResult Flush() override { + flush_attempts_++; if (block_on_next_flush_) { block_on_next_flush_ = false; SetWriteBlocked(); return WriteResult(WRITE_STATUS_BLOCKED, /*errno*/ -1); } - return WriteResult(WRITE_STATUS_OK, 0); + int bytes_flushed = bytes_buffered_; + bytes_buffered_ = 0; + return WriteResult(WRITE_STATUS_OK, bytes_flushed); } void BlockOnNextFlush() { block_on_next_flush_ = true; } @@ -572,7 +562,9 @@ void use_tagging_decrypter() { use_tagging_decrypter_ = true; } - uint32_t packets_write_attempts() { return packets_write_attempts_; } + uint32_t packets_write_attempts() const { return packets_write_attempts_; } + + uint32_t flush_attempts() const { return flush_attempts_; } uint32_t connection_close_packets() const { return connection_close_packets_; @@ -597,27 +589,32 @@ private: ParsedQuicVersion version_; SimpleQuicFramer framer_; - size_t last_packet_size_; + size_t last_packet_size_ = 0; QuicPacketHeader last_packet_header_; - bool write_blocked_; - bool write_should_fail_; - bool block_on_next_flush_; - bool block_on_next_write_; - bool next_packet_too_large_; - bool always_get_packet_too_large_; - bool is_write_blocked_data_buffered_; - bool is_batch_mode_; - uint32_t final_bytes_of_last_packet_; - uint32_t final_bytes_of_previous_packet_; - bool use_tagging_decrypter_; - uint32_t packets_write_attempts_; - uint32_t connection_close_packets_; - MockClock* clock_; + bool write_blocked_ = false; + bool write_should_fail_ = false; + bool block_on_next_flush_ = false; + bool block_on_next_write_ = false; + bool next_packet_too_large_ = false; + bool always_get_packet_too_large_ = false; + bool is_write_blocked_data_buffered_ = false; + bool is_batch_mode_ = false; + // Number of times Flush() was called. + uint32_t flush_attempts_ = 0; + // (Batch mode only) Number of bytes buffered in writer. It is used as the + // return value of a successful Flush(). + uint32_t bytes_buffered_ = 0; + uint32_t final_bytes_of_last_packet_ = 0; + uint32_t final_bytes_of_previous_packet_ = 0; + bool use_tagging_decrypter_ = false; + uint32_t packets_write_attempts_ = 0; + uint32_t connection_close_packets_ = 0; + MockClock* clock_ = nullptr; // If non-zero, the clock will pause during WritePacket for this amount of // time. - QuicTime::Delta write_pause_time_delta_; - QuicByteCount max_packet_size_; - bool supports_release_time_; + QuicTime::Delta write_pause_time_delta_ = QuicTime::Delta::Zero(); + QuicByteCount max_packet_size_ = kMaxOutgoingPacketSize; + bool supports_release_time_ = false; }; class TestConnection : public QuicConnection { @@ -4836,6 +4833,25 @@ EXPECT_EQ(QuicPacketNumber(4u), creator_->packet_number()); } +// Verifies that when a MTU probe packet is sent and buffered in a batch writer, +// the writer is flushed immediately. +TEST_P(QuicConnectionTest, BatchWriterFlushedAfterMtuDiscoveryPacket) { + writer_->SetBatchMode(true); + MtuDiscoveryTestInit(); + + // Send an MTU probe. + const size_t target_mtu = kDefaultMaxPacketSize + 100; + QuicByteCount mtu_probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&mtu_probe_size)); + const uint32_t prior_flush_attempts = writer_->flush_attempts(); + connection_.SendMtuDiscoveryPacket(target_mtu); + EXPECT_EQ(target_mtu, mtu_probe_size); + if (GetQuicReloadableFlag(quic_batch_writer_flush_after_mtu_probe)) { + EXPECT_EQ(writer_->flush_attempts(), prior_flush_attempts + 1); + } +} + // Tests whether MTU discovery does not happen when it is not explicitly enabled // by the connection options. TEST_P(QuicConnectionTest, MtuDiscoveryDisabled) {