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