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