gfe-relnote: Implement QUIC MTU discovery v2. Protected by --gfe2_reloadable_flag_quic_mtu_discovery_v2. PiperOrigin-RevId: 274290098 Change-Id: I6a55ff6aced1702fd42841f9a130f4d84076c3ec
diff --git a/quic/core/quic_connection.cc b/quic/core/quic_connection.cc index 6ebc20b..398fc03 100644 --- a/quic/core/quic_connection.cc +++ b/quic/core/quic_connection.cc
@@ -328,7 +328,8 @@ address_validated_(false), skip_packet_number_for_pto_(false), treat_queued_packets_as_sent_( - GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) { + GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)), + mtu_discovery_v2_(GetQuicReloadableFlag(quic_mtu_discovery_v2)) { QUIC_DLOG(INFO) << ENDPOINT << "Created connection with server connection ID " << server_connection_id << " and version: " << ParsedQuicVersionToString(version()); @@ -2051,6 +2052,15 @@ packet.encrypted_buffer.data(), packet.encrypted_buffer.length(), packet.self_address.host(), packet.peer_address, per_packet_options_); QUIC_DVLOG(1) << ENDPOINT << "Sending buffered packet, result: " << result; + if (mtu_discovery_v2_ && IsMsgTooBig(result) && + packet.encrypted_buffer.length() > long_term_mtu_) { + // When MSG_TOO_BIG is returned, the system typically knows what the + // actual MTU is, so there is no need to probe further. + // TODO(wub): Reduce max packet size to a safe default, or the actual MTU. + mtu_discoverer_.Disable(); + mtu_discovery_alarm_->Cancel(); + continue; + } if (IsWriteError(result.status)) { OnWriteError(result.error_code); break; @@ -2260,8 +2270,12 @@ } } + const bool looks_like_mtu_probe = packet->retransmittable_frames.empty() && + packet->encrypted_length > long_term_mtu_; DCHECK_LE(encrypted_length, kMaxOutgoingPacketSize); - DCHECK_LE(encrypted_length, packet_generator_.GetCurrentMaxPacketLength()); + if (!looks_like_mtu_probe) { + DCHECK_LE(encrypted_length, packet_generator_.GetCurrentMaxPacketLength()); + } QUIC_DVLOG(1) << ENDPOINT << "Sending packet " << packet_number << " : " << (IsRetransmittable(*packet) == HAS_RETRANSMITTABLE_DATA ? "data bearing " @@ -2336,9 +2350,18 @@ // In some cases, an MTU probe can cause EMSGSIZE. This indicates that the // MTU discovery is permanently unsuccessful. - if (IsMsgTooBig(result) && packet->retransmittable_frames.empty() && - packet->encrypted_length > long_term_mtu_) { - mtu_discovery_target_ = 0; + if (IsMsgTooBig(result) && looks_like_mtu_probe) { + if (mtu_discovery_v2_) { + // When MSG_TOO_BIG is returned, the system typically knows what the + // actual MTU is, so there is no need to probe further. + // TODO(wub): Reduce max packet size to a safe default, or the actual MTU. + QUIC_DVLOG(1) << ENDPOINT << " MTU probe packet too big, size:" + << packet->encrypted_length + << ", long_term_mtu_:" << long_term_mtu_; + mtu_discoverer_.Disable(); + } else { + mtu_discovery_target_ = 0; + } mtu_discovery_alarm_->Cancel(); // The write failed, but the writer is not blocked, so return true. return true; @@ -2347,10 +2370,9 @@ if (IsWriteError(result.status)) { OnWriteError(result.error_code); QUIC_LOG_FIRST_N(ERROR, 10) - << ENDPOINT << "failed writing " << encrypted_length - << " bytes from host " << self_address().host().ToString() - << " to address " << peer_address().ToString() << " with error code " - << result.error_code; + << ENDPOINT << "failed writing packet " << packet_number << " of " + << encrypted_length << " bytes from " << self_address().host() << " to " + << peer_address() << ", with error code " << result.error_code; return false; } @@ -2557,7 +2579,12 @@ void QuicConnection::OnPathMtuIncreased(QuicPacketLength packet_size) { if (packet_size > max_packet_length()) { + const QuicByteCount old_max_packet_length = max_packet_length(); SetMaxPacketLength(packet_size); + if (mtu_discovery_v2_) { + mtu_discoverer_.OnMaxPacketLengthUpdated(old_max_packet_length, + max_packet_length()); + } } } @@ -3144,6 +3171,15 @@ } void QuicConnection::MaybeSetMtuAlarm(QuicPacketNumber sent_packet_number) { + if (mtu_discovery_v2_) { + if (mtu_discovery_alarm_->IsSet() || + !mtu_discoverer_.ShouldProbeMtu(sent_packet_number)) { + return; + } + mtu_discovery_alarm_->Set(clock_->ApproximateNow()); + return; + } + // Do not set the alarm if the target size is less than the current size. // This covers the case when |mtu_discovery_target_| is at its default value, // zero. @@ -3294,7 +3330,13 @@ } void QuicConnection::SetMtuDiscoveryTarget(QuicByteCount target) { - mtu_discovery_target_ = GetLimitedMaxPacketSize(target); + if (mtu_discovery_v2_) { + mtu_discoverer_.Disable(); + mtu_discoverer_.Enable(max_packet_length(), + GetLimitedMaxPacketSize(target)); + } else { + mtu_discovery_target_ = GetLimitedMaxPacketSize(target); + } } QuicByteCount QuicConnection::GetLimitedMaxPacketSize( @@ -3465,6 +3507,18 @@ void QuicConnection::DiscoverMtu() { DCHECK(!mtu_discovery_alarm_->IsSet()); + if (mtu_discovery_v2_) { + const QuicPacketNumber largest_sent_packet = + sent_packet_manager_.GetLargestSentPacket(); + if (mtu_discoverer_.ShouldProbeMtu(largest_sent_packet)) { + ++mtu_probe_count_; + SendMtuDiscoveryPacket( + mtu_discoverer_.GetUpdatedMtuProbeSize(largest_sent_packet)); + } + DCHECK(!mtu_discovery_alarm_->IsSet()); + return; + } + // Check if the MTU has been already increased. if (mtu_discovery_target_ <= max_packet_length()) { return;
diff --git a/quic/core/quic_connection.h b/quic/core/quic_connection.h index bb4464b..3d07946 100644 --- a/quic/core/quic_connection.h +++ b/quic/core/quic_connection.h
@@ -34,6 +34,7 @@ #include "net/third_party/quiche/src/quic/core/quic_connection_id.h" #include "net/third_party/quiche/src/quic/core/quic_connection_stats.h" #include "net/third_party/quiche/src/quic/core/quic_framer.h" +#include "net/third_party/quiche/src/quic/core/quic_mtu_discovery.h" #include "net/third_party/quiche/src/quic/core/quic_one_block_arena.h" #include "net/third_party/quiche/src/quic/core/quic_packet_creator.h" #include "net/third_party/quiche/src/quic/core/quic_packet_generator.h" @@ -59,35 +60,6 @@ class QuicConnectionPeer; } // namespace test -// The initial number of packets between MTU probes. After each attempt the -// number is doubled. -const QuicPacketCount kPacketsBetweenMtuProbesBase = 100; - -// The number of MTU probes that get sent before giving up. -const size_t kMtuDiscoveryAttempts = 3; - -// Ensure that exponential back-off does not result in an integer overflow. -// The number of packets can be potentially capped, but that is not useful at -// current kMtuDiscoveryAttempts value, and hence is not implemented at present. -static_assert(kMtuDiscoveryAttempts + 8 < 8 * sizeof(QuicPacketNumber), - "The number of MTU discovery attempts is too high"); -static_assert(kPacketsBetweenMtuProbesBase < (1 << 8), - "The initial number of packets between MTU probes is too high"); - -// The incresed packet size targeted when doing path MTU discovery. -const QuicByteCount kMtuDiscoveryTargetPacketSizeHigh = 1450; -const QuicByteCount kMtuDiscoveryTargetPacketSizeLow = 1430; - -static_assert(kMtuDiscoveryTargetPacketSizeLow <= kMaxOutgoingPacketSize, - "MTU discovery target is too large"); -static_assert(kMtuDiscoveryTargetPacketSizeHigh <= kMaxOutgoingPacketSize, - "MTU discovery target is too large"); - -static_assert(kMtuDiscoveryTargetPacketSizeLow > kDefaultMaxPacketSize, - "MTU discovery target does not exceed the default packet size"); -static_assert(kMtuDiscoveryTargetPacketSizeHigh > kDefaultMaxPacketSize, - "MTU discovery target does not exceed the default packet size"); - // Class that receives callbacks from the connection when frames are received // and when other interesting events happen. class QUIC_EXPORT_PRIVATE QuicConnectionVisitorInterface { @@ -1541,6 +1513,11 @@ // Latched value of quic_treat_queued_packets_as_sent. const bool treat_queued_packets_as_sent_; + + // Latched value of quic_mtu_discovery_v2. + const bool mtu_discovery_v2_; + // Only used if quic_mtu_discovery_v2 is true. + QuicConnectionMtuDiscoverer mtu_discoverer_; }; } // namespace quic
diff --git a/quic/core/quic_connection_test.cc b/quic/core/quic_connection_test.cc index 97119e8..3d923a2 100644 --- a/quic/core/quic_connection_test.cc +++ b/quic/core/quic_connection_test.cc
@@ -1574,10 +1574,16 @@ void set_packets_between_probes_base( const QuicPacketCount packets_between_probes_base) { - QuicConnectionPeer::SetPacketsBetweenMtuProbes(&connection_, - packets_between_probes_base); - QuicConnectionPeer::SetNextMtuProbeAt( - &connection_, QuicPacketNumber(packets_between_probes_base)); + if (GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + QuicConnectionPeer::ReInitializeMtuDiscoverer( + &connection_, packets_between_probes_base, + QuicPacketNumber(packets_between_probes_base)); + } else { + QuicConnectionPeer::SetPacketsBetweenMtuProbes( + &connection_, packets_between_probes_base); + QuicConnectionPeer::SetNextMtuProbeAt( + &connection_, QuicPacketNumber(packets_between_probes_base)); + } } bool IsDefaultTestConfiguration() { @@ -4933,7 +4939,7 @@ } } -// Tests whether MTU discovery works when the probe gets acknowledged on the +// Tests whether MTU discovery works when all probes are acknowledged on the // first try. TEST_P(QuicConnectionTest, MtuDiscoveryEnabled) { EXPECT_TRUE(connection_.connected()); @@ -4949,11 +4955,11 @@ // for ones using TLS. connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr); - connection_.EnablePathMtuDiscovery(send_algorithm_); - const QuicPacketCount packets_between_probes_base = 5; set_packets_between_probes_base(packets_between_probes_base); + connection_.EnablePathMtuDiscovery(send_algorithm_); + // Send enough packets so that the next one triggers path MTU discovery. for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { SendStreamDataToPeer(3, ".", i, NO_FIN, nullptr); @@ -4968,7 +4974,12 @@ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) .WillOnce(SaveArg<3>(&probe_size)); connection_.GetMtuDiscoveryAlarm()->Fire(); - EXPECT_EQ(kMtuDiscoveryTargetPacketSizeHigh, probe_size); + if (GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + EXPECT_THAT(probe_size, InRange(connection_.max_packet_length(), + kMtuDiscoveryTargetPacketSizeHigh)); + } else { + EXPECT_EQ(kMtuDiscoveryTargetPacketSizeHigh, probe_size); + } const QuicPacketNumber probe_packet_number = FirstSendingPacketNumber() + packets_between_probes_base; @@ -4977,19 +4988,99 @@ // Acknowledge all packets sent so far. QuicAckFrame probe_ack = InitAckFrame(probe_packet_number); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)) + .Times(AnyNumber()); ProcessAckPacket(&probe_ack); - EXPECT_EQ(kMtuDiscoveryTargetPacketSizeHigh, connection_.max_packet_length()); + EXPECT_EQ(probe_size, connection_.max_packet_length()); EXPECT_EQ(0u, connection_.GetBytesInFlight()); - // Send more packets, and ensure that none of them sets the alarm. - for (QuicPacketCount i = 0; i < 4 * packets_between_probes_base; i++) { - SendStreamDataToPeer(3, ".", packets_between_probes_base + i, NO_FIN, - nullptr); + EXPECT_EQ(1u, connection_.mtu_probe_count()); + + if (!GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + // Send more packets, and ensure that none of them sets the alarm. + for (QuicPacketCount i = 0; i < 4 * packets_between_probes_base; i++) { + SendStreamDataToPeer(3, ".", packets_between_probes_base + i, NO_FIN, + nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + return; + } + + QuicStreamOffset stream_offset = packets_between_probes_base; + for (size_t num_probes = 1; num_probes < kMtuDiscoveryAttempts; + ++num_probes) { + // Send just enough packets without triggering the next probe. + for (QuicPacketCount i = 0; + i < (packets_between_probes_base << num_probes) - 1; ++i) { + SendStreamDataToPeer(3, ".", stream_offset++, NO_FIN, nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + // Trigger the next probe. + SendStreamDataToPeer(3, "!", stream_offset++, NO_FIN, nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + QuicByteCount new_probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&new_probe_size)); + connection_.GetMtuDiscoveryAlarm()->Fire(); + EXPECT_THAT(new_probe_size, + InRange(probe_size, kMtuDiscoveryTargetPacketSizeHigh)); + EXPECT_EQ(num_probes + 1, connection_.mtu_probe_count()); + + // Acknowledge all packets sent so far. + QuicAckFrame probe_ack = InitAckFrame(creator_->packet_number()); + ProcessAckPacket(&probe_ack); + EXPECT_EQ(new_probe_size, connection_.max_packet_length()); + EXPECT_EQ(0u, connection_.GetBytesInFlight()); + + probe_size = new_probe_size; + } + + // The last probe size should be equal to the target. + EXPECT_EQ(probe_size, kMtuDiscoveryTargetPacketSizeHigh); +} + +// Simulate the case where the first attempt to send a probe is write blocked, +// and after unblock, the second attempt returns a MSG_TOO_BIG error. +TEST_P(QuicConnectionTest, MtuDiscoveryWriteBlocked) { + EXPECT_TRUE(connection_.connected()); + + connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr); + + const QuicPacketCount packets_between_probes_base = 5; + set_packets_between_probes_base(packets_between_probes_base); + + connection_.EnablePathMtuDiscovery(send_algorithm_); + + // Send enough packets so that the next one triggers path MTU discovery. + for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { + SendStreamDataToPeer(3, ".", i, NO_FIN, nullptr); ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); } + QuicByteCount original_max_packet_length = connection_.max_packet_length(); + + // Trigger the probe. + SendStreamDataToPeer(3, "!", packets_between_probes_base - 1, NO_FIN, + nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + if (GetQuicReloadableFlag(quic_treat_queued_packets_as_sent)) { + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)); + } + BlockOnNextWrite(); + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + connection_.GetMtuDiscoveryAlarm()->Fire(); EXPECT_EQ(1u, connection_.mtu_probe_count()); + EXPECT_EQ(1u, connection_.NumQueuedPackets()); + ASSERT_TRUE(connection_.connected()); + + writer_->SetWritable(); + SimulateNextPacketTooLarge(); + connection_.OnCanWrite(); + EXPECT_EQ(0u, connection_.NumQueuedPackets()); + EXPECT_EQ(original_max_packet_length, connection_.max_packet_length()); + EXPECT_TRUE(connection_.connected()); } // Tests whether MTU discovery works correctly when the probes never get @@ -4997,17 +5088,18 @@ TEST_P(QuicConnectionTest, MtuDiscoveryFailed) { EXPECT_TRUE(connection_.connected()); - connection_.EnablePathMtuDiscovery(send_algorithm_); - - const QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(100); - - EXPECT_EQ(kPacketsBetweenMtuProbesBase, - QuicConnectionPeer::GetPacketsBetweenMtuProbes(&connection_)); // Lower the number of probes between packets in order to make the test go // much faster. const QuicPacketCount packets_between_probes_base = 5; set_packets_between_probes_base(packets_between_probes_base); + connection_.EnablePathMtuDiscovery(send_algorithm_); + + const QuicTime::Delta rtt = QuicTime::Delta::FromMilliseconds(100); + + EXPECT_EQ(packets_between_probes_base, + QuicConnectionPeer::GetPacketsBetweenMtuProbes(&connection_)); + // This tests sends more packets than strictly necessary to make sure that if // the connection was to send more discovery packets than needed, those would // get caught as well. @@ -5074,6 +5166,108 @@ EXPECT_EQ(kMtuDiscoveryAttempts, connection_.mtu_probe_count()); } +// Probe 3 times, the first one succeeds, then fails, then succeeds again. +TEST_P(QuicConnectionTest, MtuDiscoverySecondProbeFailed) { + if (!GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + return; + } + EXPECT_TRUE(connection_.connected()); + + // QuicFramer::GetMaxPlaintextSize uses the smallest max plaintext size across + // all encrypters. The initial encrypter used with IETF QUIC has a 16-byte + // overhead, while the NullEncrypter used throughout this test has a 12-byte + // overhead. This test tests behavior that relies on computing the packet size + // correctly, so by unsetting the initial encrypter, we avoid having a + // mismatch between the overheads for the encrypters used. In non-test + // scenarios all encrypters used for a given connection have the same + // overhead, either 12 bytes for ones using Google QUIC crypto, or 16 bytes + // for ones using TLS. + connection_.SetEncrypter(ENCRYPTION_INITIAL, nullptr); + + const QuicPacketCount packets_between_probes_base = 5; + set_packets_between_probes_base(packets_between_probes_base); + + connection_.EnablePathMtuDiscovery(send_algorithm_); + + // Send enough packets so that the next one triggers path MTU discovery. + QuicStreamOffset stream_offset = 0; + for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { + SendStreamDataToPeer(3, ".", stream_offset++, NO_FIN, nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + // Trigger the probe. + SendStreamDataToPeer(3, "!", packets_between_probes_base - 1, NO_FIN, + nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + QuicByteCount probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&probe_size)); + connection_.GetMtuDiscoveryAlarm()->Fire(); + EXPECT_THAT(probe_size, InRange(connection_.max_packet_length(), + kMtuDiscoveryTargetPacketSizeHigh)); + + const QuicPacketNumber probe_packet_number = + FirstSendingPacketNumber() + packets_between_probes_base; + ASSERT_EQ(probe_packet_number, creator_->packet_number()); + + // Acknowledge all packets sent so far. + QuicAckFrame first_ack = InitAckFrame(probe_packet_number); + EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)) + .Times(AnyNumber()); + ProcessAckPacket(&first_ack); + EXPECT_EQ(probe_size, connection_.max_packet_length()); + EXPECT_EQ(0u, connection_.GetBytesInFlight()); + + EXPECT_EQ(1u, connection_.mtu_probe_count()); + + // Send just enough packets without triggering the second probe. + for (QuicPacketCount i = 0; i < (packets_between_probes_base << 1) - 1; ++i) { + SendStreamDataToPeer(3, ".", stream_offset++, NO_FIN, nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + // Trigger the second probe. + SendStreamDataToPeer(3, "!", stream_offset++, NO_FIN, nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + QuicByteCount second_probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&second_probe_size)); + connection_.GetMtuDiscoveryAlarm()->Fire(); + EXPECT_THAT(second_probe_size, + InRange(probe_size, kMtuDiscoveryTargetPacketSizeHigh)); + EXPECT_EQ(2u, connection_.mtu_probe_count()); + + // Acknowledge all packets sent so far, except the second probe. + QuicPacketNumber second_probe_packet_number = creator_->packet_number(); + QuicAckFrame second_ack = InitAckFrame(second_probe_packet_number - 1); + ProcessAckPacket(&first_ack); + EXPECT_EQ(probe_size, connection_.max_packet_length()); + + // Send just enough packets without triggering the third probe. + for (QuicPacketCount i = 0; i < (packets_between_probes_base << 2) - 1; ++i) { + SendStreamDataToPeer(3, "@", stream_offset++, NO_FIN, nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + // Trigger the third probe. + SendStreamDataToPeer(3, "#", stream_offset++, NO_FIN, nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + QuicByteCount third_probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&third_probe_size)); + connection_.GetMtuDiscoveryAlarm()->Fire(); + EXPECT_THAT(third_probe_size, InRange(probe_size, second_probe_size)); + EXPECT_EQ(3u, connection_.mtu_probe_count()); + + // Acknowledge all packets sent so far, except the second probe. + QuicAckFrame third_ack = + ConstructAckFrame(creator_->packet_number(), second_probe_packet_number); + ProcessAckPacket(&third_ack); + EXPECT_EQ(third_probe_size, connection_.max_packet_length()); +} + // Tests whether MTU discovery works when the writer has a limit on how large a // packet can be. TEST_P(QuicConnectionTest, MtuDiscoveryWriterLimited) { @@ -5092,11 +5286,12 @@ const QuicByteCount mtu_limit = kMtuDiscoveryTargetPacketSizeHigh - 1; writer_->set_max_packet_size(mtu_limit); - connection_.EnablePathMtuDiscovery(send_algorithm_); const QuicPacketCount packets_between_probes_base = 5; set_packets_between_probes_base(packets_between_probes_base); + connection_.EnablePathMtuDiscovery(send_algorithm_); + // Send enough packets so that the next one triggers path MTU discovery. for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { SendStreamDataToPeer(3, ".", i, NO_FIN, nullptr); @@ -5111,7 +5306,12 @@ EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) .WillOnce(SaveArg<3>(&probe_size)); connection_.GetMtuDiscoveryAlarm()->Fire(); - EXPECT_EQ(mtu_limit, probe_size); + if (GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + EXPECT_THAT(probe_size, + InRange(connection_.max_packet_length(), mtu_limit)); + } else { + EXPECT_EQ(mtu_limit, probe_size); + } const QuicPacketNumber probe_sequence_number = FirstSendingPacketNumber() + packets_between_probes_base; @@ -5120,19 +5320,56 @@ // Acknowledge all packets sent so far. QuicAckFrame probe_ack = InitAckFrame(probe_sequence_number); EXPECT_CALL(visitor_, OnSuccessfulVersionNegotiation(_)); - EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)); + EXPECT_CALL(*send_algorithm_, OnCongestionEvent(true, _, _, _, _)) + .Times(AnyNumber()); ProcessAckPacket(&probe_ack); - EXPECT_EQ(mtu_limit, connection_.max_packet_length()); + EXPECT_EQ(probe_size, connection_.max_packet_length()); EXPECT_EQ(0u, connection_.GetBytesInFlight()); - // Send more packets, and ensure that none of them sets the alarm. - for (QuicPacketCount i = 0; i < 4 * packets_between_probes_base; i++) { - SendStreamDataToPeer(3, ".", packets_between_probes_base + i, NO_FIN, - nullptr); - ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + EXPECT_EQ(1u, connection_.mtu_probe_count()); + + if (!GetQuicReloadableFlag(quic_mtu_discovery_v2)) { + // Send more packets, and ensure that none of them sets the alarm. + for (QuicPacketCount i = 0; i < 4 * packets_between_probes_base; i++) { + SendStreamDataToPeer(3, ".", packets_between_probes_base + i, NO_FIN, + nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + return; } - EXPECT_EQ(1u, connection_.mtu_probe_count()); + QuicStreamOffset stream_offset = packets_between_probes_base; + for (size_t num_probes = 1; num_probes < kMtuDiscoveryAttempts; + ++num_probes) { + // Send just enough packets without triggering the next probe. + for (QuicPacketCount i = 0; + i < (packets_between_probes_base << num_probes) - 1; ++i) { + SendStreamDataToPeer(3, ".", stream_offset++, NO_FIN, nullptr); + ASSERT_FALSE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + } + + // Trigger the next probe. + SendStreamDataToPeer(3, "!", stream_offset++, NO_FIN, nullptr); + ASSERT_TRUE(connection_.GetMtuDiscoveryAlarm()->IsSet()); + QuicByteCount new_probe_size; + EXPECT_CALL(*send_algorithm_, OnPacketSent(_, _, _, _, _)) + .WillOnce(SaveArg<3>(&new_probe_size)); + connection_.GetMtuDiscoveryAlarm()->Fire(); + EXPECT_THAT(new_probe_size, InRange(probe_size, mtu_limit)); + EXPECT_EQ(num_probes + 1, connection_.mtu_probe_count()); + + // Acknowledge all packets sent so far. + QuicAckFrame probe_ack = InitAckFrame(creator_->packet_number()); + ProcessAckPacket(&probe_ack); + EXPECT_EQ(new_probe_size, connection_.max_packet_length()); + EXPECT_EQ(0u, connection_.GetBytesInFlight()); + + probe_size = new_probe_size; + } + + // The last probe size should be equal to the target. + EXPECT_EQ(probe_size, mtu_limit); } // Tests whether MTU discovery works when the writer returns an error despite @@ -5144,11 +5381,12 @@ const QuicByteCount initial_mtu = connection_.max_packet_length(); EXPECT_LT(initial_mtu, mtu_limit); writer_->set_max_packet_size(mtu_limit); - connection_.EnablePathMtuDiscovery(send_algorithm_); const QuicPacketCount packets_between_probes_base = 5; set_packets_between_probes_base(packets_between_probes_base); + connection_.EnablePathMtuDiscovery(send_algorithm_); + // Send enough packets so that the next one triggers path MTU discovery. for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { SendStreamDataToPeer(3, ".", i, NO_FIN, nullptr); @@ -5192,11 +5430,11 @@ TEST_P(QuicConnectionTest, NoMtuDiscoveryAfterConnectionClosed) { EXPECT_TRUE(connection_.connected()); - connection_.EnablePathMtuDiscovery(send_algorithm_); - const QuicPacketCount packets_between_probes_base = 10; set_packets_between_probes_base(packets_between_probes_base); + connection_.EnablePathMtuDiscovery(send_algorithm_); + // Send enough packets so that the next one triggers path MTU discovery. for (QuicPacketCount i = 0; i < packets_between_probes_base - 1; i++) { SendStreamDataToPeer(3, ".", i, NO_FIN, nullptr);
diff --git a/quic/core/quic_mtu_discovery.cc b/quic/core/quic_mtu_discovery.cc new file mode 100644 index 0000000..c89b41a --- /dev/null +++ b/quic/core/quic_mtu_discovery.cc
@@ -0,0 +1,142 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/third_party/quiche/src/quic/core/quic_mtu_discovery.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h" +#include "net/third_party/quiche/src/quic/platform/api/quic_stack_trace.h" + +namespace quic { + +QuicConnectionMtuDiscoverer::QuicConnectionMtuDiscoverer( + QuicPacketCount packets_between_probes_base, + QuicPacketNumber next_probe_at) + : packets_between_probes_(packets_between_probes_base), + next_probe_at_(next_probe_at) {} + +void QuicConnectionMtuDiscoverer::Enable( + QuicByteCount max_packet_length, + QuicByteCount target_max_packet_length) { + DCHECK(!IsEnabled()); + + if (target_max_packet_length <= max_packet_length) { + QUIC_DVLOG(1) << "MtuDiscoverer not enabled. target_max_packet_length:" + << target_max_packet_length + << " <= max_packet_length:" << max_packet_length; + return; + } + + min_probe_length_ = max_packet_length; + max_probe_length_ = target_max_packet_length; + DCHECK(IsEnabled()); + + QUIC_DVLOG(1) << "MtuDiscoverer enabled. min:" << min_probe_length_ + << ", max:" << max_probe_length_ + << ", next:" << next_probe_packet_length(); +} + +void QuicConnectionMtuDiscoverer::Disable() { + *this = QuicConnectionMtuDiscoverer(packets_between_probes_, next_probe_at_); +} + +bool QuicConnectionMtuDiscoverer::IsEnabled() const { + return min_probe_length_ < max_probe_length_; +} + +bool QuicConnectionMtuDiscoverer::ShouldProbeMtu( + QuicPacketNumber largest_sent_packet) const { + if (!IsEnabled()) { + return false; + } + + if (remaining_probe_count_ == 0) { + QUIC_DVLOG(1) + << "ShouldProbeMtu returns false because max probe count reached"; + return false; + } + + if (largest_sent_packet < next_probe_at_) { + QUIC_DVLOG(1) << "ShouldProbeMtu returns false because not enough packets " + "sent since last probe. largest_sent_packet:" + << largest_sent_packet + << ", next_probe_at_:" << next_probe_at_; + return false; + } + + QUIC_DVLOG(1) << "ShouldProbeMtu returns true. largest_sent_packet:" + << largest_sent_packet; + return true; +} + +QuicPacketLength QuicConnectionMtuDiscoverer::GetUpdatedMtuProbeSize( + QuicPacketNumber largest_sent_packet) { + DCHECK(ShouldProbeMtu(largest_sent_packet)); + + QuicPacketLength probe_packet_length = next_probe_packet_length(); + if (probe_packet_length == last_probe_length_) { + // The next probe packet is as big as the previous one. Assuming the + // previous one exceeded MTU, we need to decrease the probe packet length. + max_probe_length_ = probe_packet_length; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_mtu_discovery_v2, 1, 3); + } else { + DCHECK_GT(probe_packet_length, last_probe_length_); + QUIC_RELOADABLE_FLAG_COUNT_N(quic_mtu_discovery_v2, 2, 3); + } + last_probe_length_ = next_probe_packet_length(); + + packets_between_probes_ *= 2; + next_probe_at_ = largest_sent_packet + packets_between_probes_ + 1; + if (remaining_probe_count_ > 0) { + --remaining_probe_count_; + } + + QUIC_DVLOG(1) << "GetUpdatedMtuProbeSize: probe_packet_length:" + << last_probe_length_ + << ", New packets_between_probes_:" << packets_between_probes_ + << ", next_probe_at_:" << next_probe_at_ + << ", remaining_probe_count_:" << remaining_probe_count_; + DCHECK(!ShouldProbeMtu(largest_sent_packet)); + return last_probe_length_; +} + +QuicPacketLength QuicConnectionMtuDiscoverer::next_probe_packet_length() const { + DCHECK_NE(min_probe_length_, 0); + DCHECK_NE(max_probe_length_, 0); + DCHECK_GE(max_probe_length_, min_probe_length_); + + const QuicPacketLength normal_next_probe_length = + (min_probe_length_ + max_probe_length_ + 1) / 2; + + if (remaining_probe_count_ == 1 && + normal_next_probe_length > last_probe_length_) { + // If the previous probe succeeded, and there is only one last probe to + // send, use |max_probe_length_| for the last probe. + return max_probe_length_; + } + return normal_next_probe_length; +} + +void QuicConnectionMtuDiscoverer::OnMaxPacketLengthUpdated( + QuicByteCount old_value, + QuicByteCount new_value) { + if (!IsEnabled() || new_value <= old_value) { + return; + } + + DCHECK_EQ(old_value, min_probe_length_); + min_probe_length_ = new_value; + QUIC_RELOADABLE_FLAG_COUNT_N(quic_mtu_discovery_v2, 3, 3); +} + +std::ostream& operator<<(std::ostream& os, + const QuicConnectionMtuDiscoverer& d) { + os << "{ min_probe_length_:" << d.min_probe_length_ + << " max_probe_length_:" << d.max_probe_length_ + << " last_probe_length_:" << d.last_probe_length_ + << " remaining_probe_count_:" << d.remaining_probe_count_ + << " packets_between_probes_:" << d.packets_between_probes_ + << " next_probe_at_:" << d.next_probe_at_ << " }"; + return os; +} + +} // namespace quic
diff --git a/quic/core/quic_mtu_discovery.h b/quic/core/quic_mtu_discovery.h new file mode 100644 index 0000000..5ec5133 --- /dev/null +++ b/quic/core/quic_mtu_discovery.h
@@ -0,0 +1,117 @@ +// Copyright (c) 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef QUICHE_QUIC_CORE_QUIC_MTU_DISCOVERY_H_ +#define QUICHE_QUIC_CORE_QUIC_MTU_DISCOVERY_H_ + +#include <iostream> + +#include "net/third_party/quiche/src/quic/core/quic_constants.h" +#include "net/third_party/quiche/src/quic/core/quic_types.h" + +namespace quic { + +// The initial number of packets between MTU probes. After each attempt the +// number is doubled. +const QuicPacketCount kPacketsBetweenMtuProbesBase = 100; + +// The number of MTU probes that get sent before giving up. +const size_t kMtuDiscoveryAttempts = 3; + +// Ensure that exponential back-off does not result in an integer overflow. +// The number of packets can be potentially capped, but that is not useful at +// current kMtuDiscoveryAttempts value, and hence is not implemented at present. +static_assert(kMtuDiscoveryAttempts + 8 < 8 * sizeof(QuicPacketNumber), + "The number of MTU discovery attempts is too high"); +static_assert(kPacketsBetweenMtuProbesBase < (1 << 8), + "The initial number of packets between MTU probes is too high"); + +// The incresed packet size targeted when doing path MTU discovery. +const QuicByteCount kMtuDiscoveryTargetPacketSizeHigh = 1450; +const QuicByteCount kMtuDiscoveryTargetPacketSizeLow = 1430; + +static_assert(kMtuDiscoveryTargetPacketSizeLow <= kMaxOutgoingPacketSize, + "MTU discovery target is too large"); +static_assert(kMtuDiscoveryTargetPacketSizeHigh <= kMaxOutgoingPacketSize, + "MTU discovery target is too large"); + +static_assert(kMtuDiscoveryTargetPacketSizeLow > kDefaultMaxPacketSize, + "MTU discovery target does not exceed the default packet size"); +static_assert(kMtuDiscoveryTargetPacketSizeHigh > kDefaultMaxPacketSize, + "MTU discovery target does not exceed the default packet size"); + +// QuicConnectionMtuDiscoverer is a MTU discovery controller, it answers two +// questions: +// 1) Probe scheduling: Whether a connection should send a MTU probe packet +// right now. +// 2) MTU search stradegy: When it is time to send, what should be the size of +// the probing packet. +// Note the discoverer does not actually send or process probing packets. +// +// Unit tests are in QuicConnectionTest.MtuDiscovery*. +class QUIC_EXPORT_PRIVATE QuicConnectionMtuDiscoverer { + public: + // Construct a discoverer in the disabled state. + QuicConnectionMtuDiscoverer() = default; + + // Construct a discoverer in the disabled state, with the given parameters. + QuicConnectionMtuDiscoverer(QuicPacketCount packets_between_probes_base, + QuicPacketNumber next_probe_at); + + // Enable the discoverer by setting the probe target. + // max_packet_length: The max packet length currently used. + // target_max_packet_length: The target max packet length to probe. + void Enable(QuicByteCount max_packet_length, + QuicByteCount target_max_packet_length); + + // Disable the discoverer by unsetting the probe target. + void Disable(); + + // Whether a MTU probe packet should be sent right now. + // Always return false if disabled. + bool ShouldProbeMtu(QuicPacketNumber largest_sent_packet) const; + + // Called immediately before a probing packet is sent, to get the size of the + // packet. + // REQUIRES: ShouldProbeMtu(largest_sent_packet) == true. + QuicPacketLength GetUpdatedMtuProbeSize(QuicPacketNumber largest_sent_packet); + + // Called after the max packet length is updated, which is triggered by a ack + // of a probing packet. + void OnMaxPacketLengthUpdated(QuicByteCount old_value, + QuicByteCount new_value); + + QuicPacketCount packets_between_probes() const { + return packets_between_probes_; + } + + QuicPacketNumber next_probe_at() const { return next_probe_at_; } + + QUIC_EXPORT_PRIVATE friend std::ostream& operator<<( + std::ostream& os, + const QuicConnectionMtuDiscoverer& d); + + private: + bool IsEnabled() const; + QuicPacketLength next_probe_packet_length() const; + + QuicPacketLength min_probe_length_ = 0; + QuicPacketLength max_probe_length_ = 0; + + QuicPacketLength last_probe_length_ = 0; + + uint16_t remaining_probe_count_ = kMtuDiscoveryAttempts; + + // The number of packets between MTU probes. + QuicPacketCount packets_between_probes_ = kPacketsBetweenMtuProbesBase; + + // The packet number of the packet after which the next MTU probe will be + // sent. + QuicPacketNumber next_probe_at_ = + QuicPacketNumber(kPacketsBetweenMtuProbesBase); +}; + +} // namespace quic + +#endif // QUICHE_QUIC_CORE_QUIC_MTU_DISCOVERY_H_
diff --git a/quic/test_tools/quic_connection_peer.cc b/quic/test_tools/quic_connection_peer.cc index ef2ffb3..8b99730 100644 --- a/quic/test_tools/quic_connection_peer.cc +++ b/quic/test_tools/quic_connection_peer.cc
@@ -226,6 +226,9 @@ // static QuicPacketCount QuicConnectionPeer::GetPacketsBetweenMtuProbes( QuicConnection* connection) { + if (connection->mtu_discovery_v2_) { + return connection->mtu_discoverer_.packets_between_probes(); + } return connection->packets_between_mtu_probes_; } @@ -242,6 +245,15 @@ } // static +void QuicConnectionPeer::ReInitializeMtuDiscoverer( + QuicConnection* connection, + QuicPacketCount packets_between_probes_base, + QuicPacketNumber next_probe_at) { + connection->mtu_discoverer_ = + QuicConnectionMtuDiscoverer(packets_between_probes_base, next_probe_at); +} + +// static void QuicConnectionPeer::SetAckMode(QuicConnection* connection, AckMode ack_mode) { for (auto& received_packet_manager :
diff --git a/quic/test_tools/quic_connection_peer.h b/quic/test_tools/quic_connection_peer.h index f329a1c..5b97ae6 100644 --- a/quic/test_tools/quic_connection_peer.h +++ b/quic/test_tools/quic_connection_peer.h
@@ -111,6 +111,10 @@ QuicPacketCount packets); static void SetNextMtuProbeAt(QuicConnection* connection, QuicPacketNumber number); + static void ReInitializeMtuDiscoverer( + QuicConnection* connection, + QuicPacketCount packets_between_probes_base, + QuicPacketNumber next_probe_at); static void SetAckMode(QuicConnection* connection, AckMode ack_mode); static void SetFastAckAfterQuiescence(QuicConnection* connection, bool fast_ack_after_quiescence);
diff --git a/quic/test_tools/quic_test_utils.h b/quic/test_tools/quic_test_utils.h index fbadf13..1cd2683 100644 --- a/quic/test_tools/quic_test_utils.h +++ b/quic/test_tools/quic_test_utils.h
@@ -1258,6 +1258,10 @@ return arg.destination_connection_id == destination_connection_id; } +MATCHER_P2(InRange, min, max, "") { + return arg >= min && arg <= max; +} + } // namespace test } // namespace quic