relnote: On server side, do not process the first initial QUIC packet received from a client if the UDP datagram is < 1200 bytes. Instead, send a connection close with PROTOCOL_VIOLATION if the version is supported. Protected by quic_reloadable_flag_quic_donot_process_small_initial_packets.
PiperOrigin-RevId: 270157435
Change-Id: Id0faa750300b2e75c7e87cc05dd8635c580f01ba
diff --git a/quic/core/http/quic_spdy_client_session_test.cc b/quic/core/http/quic_spdy_client_session_test.cc
index 3e951b3..306f6f5 100644
--- a/quic/core/http/quic_spdy_client_session_test.cc
+++ b/quic/core/http/quic_spdy_client_session_test.cc
@@ -517,7 +517,7 @@
QuicConnectionId source_connection_id = connection_id;
std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
destination_connection_id, source_connection_id, false, false, 100,
- "data", CONNECTION_ID_ABSENT, CONNECTION_ID_ABSENT,
+ "data", true, CONNECTION_ID_ABSENT, CONNECTION_ID_ABSENT,
PACKET_4BYTE_PACKET_NUMBER, &versions, Perspective::IS_SERVER));
std::unique_ptr<QuicReceivedPacket> received(
ConstructReceivedPacket(*packet, QuicTime::Zero()));
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc
index beb70ca..3187c78 100644
--- a/quic/core/quic_dispatcher.cc
+++ b/quic/core/quic_dispatcher.cc
@@ -32,6 +32,9 @@
namespace {
+// Minimal INITIAL packet length sent by clients is 1200.
+const QuicPacketLength kMinClientInitialPacketLength = 1200;
+
// An alarm that informs the QuicDispatcher to delete old sessions.
class DeleteSessionsAlarm : public QuicAlarm::Delegate {
public:
@@ -142,11 +145,35 @@
framer_.set_data_producer(nullptr);
}
+ // Serializes a packet containing CONNECTION_CLOSE frame and send it (without
+ // adding connection to the time wait).
+ void StatelesslyCloseConnection(const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ QuicErrorCode error_code,
+ const std::string& error_details) {
+ SerializeConnectionClosePacket(error_code, error_details);
+
+ for (const auto& packet : *collector_.packets()) {
+ time_wait_list_manager_->SendPacket(self_address, peer_address, *packet);
+ }
+ }
+
// Generates a packet containing a CONNECTION_CLOSE frame specifying
// |error_code| and |error_details| and add the connection to time wait.
void CloseConnection(QuicErrorCode error_code,
const std::string& error_details,
bool ietf_quic) {
+ SerializeConnectionClosePacket(error_code, error_details);
+
+ time_wait_list_manager_->AddConnectionIdToTimeWait(
+ server_connection_id_, ietf_quic,
+ QuicTimeWaitListManager::SEND_TERMINATION_PACKETS,
+ quic::ENCRYPTION_INITIAL, collector_.packets());
+ }
+
+ private:
+ void SerializeConnectionClosePacket(QuicErrorCode error_code,
+ const std::string& error_details) {
QuicConnectionCloseFrame* frame = new QuicConnectionCloseFrame(
framer_.transport_version(), error_code, error_details,
/*transport_close_frame_type=*/0);
@@ -158,13 +185,8 @@
}
creator_.FlushCurrentPacket();
DCHECK_EQ(1u, collector_.packets()->size());
- time_wait_list_manager_->AddConnectionIdToTimeWait(
- server_connection_id_, ietf_quic,
- QuicTimeWaitListManager::SEND_TERMINATION_PACKETS,
- quic::ENCRYPTION_INITIAL, collector_.packets());
}
- private:
QuicConnectionId server_connection_id_;
QuicFramer framer_;
// Set as the visitor of |creator_| to collect any generated packets.
@@ -256,9 +278,9 @@
QuicStringPiece retry_token;
error = QuicFramer::ParsePublicHeaderDispatcher(
packet, expected_server_connection_id_length_, &packet_info.form,
- &packet_info.version_flag, &packet_info.use_length_prefix,
- &packet_info.version_label, &packet_info.version,
- &packet_info.destination_connection_id,
+ &packet_info.long_packet_type, &packet_info.version_flag,
+ &packet_info.use_length_prefix, &packet_info.version_label,
+ &packet_info.version, &packet_info.destination_connection_id,
&packet_info.source_connection_id, &retry_token_present, &retry_token,
&detailed_error);
}
@@ -447,6 +469,24 @@
}
return true;
}
+
+ if (GetQuicReloadableFlag(quic_use_parse_public_header) &&
+ GetQuicReloadableFlag(quic_donot_process_small_initial_packets) &&
+ crypto_config()->validate_chlo_size() &&
+ packet_info.form == IETF_QUIC_LONG_HEADER_PACKET &&
+ packet_info.long_packet_type == INITIAL &&
+ packet_info.packet.length() < kMinClientInitialPacketLength) {
+ QUIC_RELOADABLE_FLAG_COUNT(quic_donot_process_small_initial_packets);
+ StatelessConnectionTerminator terminator(
+ packet_info.destination_connection_id, packet_info.version,
+ helper_.get(), time_wait_list_manager_.get());
+ QUIC_DVLOG(1) << "Initial packet too small: "
+ << packet_info.packet.length();
+ terminator.StatelesslyCloseConnection(
+ packet_info.self_address, packet_info.peer_address,
+ IETF_QUIC_PROTOCOL_VIOLATION, "Initial packet too small");
+ return true;
+ }
}
return false;
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc
index 34408e0..f2114c2 100644
--- a/quic/core/quic_dispatcher_test.cc
+++ b/quic/core/quic_dispatcher_test.cc
@@ -262,7 +262,7 @@
QuicPacketNumberLength packet_number_length,
uint64_t packet_number) {
ProcessPacket(peer_address, server_connection_id, has_version_flag,
- CurrentSupportedVersions().front(), data,
+ CurrentSupportedVersions().front(), data, true,
server_connection_id_included, packet_number_length,
packet_number);
}
@@ -273,11 +273,12 @@
bool has_version_flag,
ParsedQuicVersion version,
const std::string& data,
+ bool full_padding,
QuicConnectionIdIncluded server_connection_id_included,
QuicPacketNumberLength packet_number_length,
uint64_t packet_number) {
ProcessPacket(peer_address, server_connection_id, EmptyQuicConnectionId(),
- has_version_flag, version, data,
+ has_version_flag, version, data, full_padding,
server_connection_id_included, CONNECTION_ID_ABSENT,
packet_number_length, packet_number);
}
@@ -289,6 +290,7 @@
bool has_version_flag,
ParsedQuicVersion version,
const std::string& data,
+ bool full_padding,
QuicConnectionIdIncluded server_connection_id_included,
QuicConnectionIdIncluded client_connection_id_included,
QuicPacketNumberLength packet_number_length,
@@ -296,7 +298,7 @@
ParsedQuicVersionVector versions(SupportedVersions(version));
std::unique_ptr<QuicEncryptedPacket> packet(ConstructEncryptedPacket(
server_connection_id, client_connection_id, has_version_flag, false,
- packet_number, data, server_connection_id_included,
+ packet_number, data, full_padding, server_connection_id_included,
client_connection_id_included, packet_number_length, &versions));
std::unique_ptr<QuicReceivedPacket> received_packet(
ConstructReceivedPacket(*packet, mock_helper_.GetClock()->Now()));
@@ -383,7 +385,7 @@
ShouldCreateOrBufferPacketForConnection(
ReceivedPacketInfoConnectionIdEquals(connection_id)));
ProcessPacket(client_address, connection_id, true, version, SerializeCHLO(),
- CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ true, CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
}
void VerifyVersionNotSupported(ParsedQuicVersion version) {
@@ -393,7 +395,7 @@
QuicStringPiece("hq"), _))
.Times(0);
ProcessPacket(client_address, connection_id, true, version, SerializeCHLO(),
- CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ true, CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
}
MockQuicConnectionHelper mock_helper_;
@@ -439,7 +441,8 @@
client_address, TestConnectionId(1), true,
ParsedQuicVersion(PROTOCOL_TLS1_3,
CurrentSupportedVersions().front().transport_version),
- SerializeCHLO(), CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ SerializeCHLO(), true, CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ 1);
}
TEST_F(QuicDispatcherTest, ProcessPackets) {
@@ -514,14 +517,15 @@
client_address, TestConnectionId(1), true,
ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO,
CurrentSupportedVersions().front().transport_version),
- SerializeCHLO(), CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+ SerializeCHLO(), true, CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER,
+ 1);
// Packet number 256 with packet number length 1 would be considered as 0 in
// dispatcher.
ProcessPacket(
client_address, TestConnectionId(1), false,
ParsedQuicVersion(PROTOCOL_QUIC_CRYPTO,
CurrentSupportedVersions().front().transport_version),
- "", CONNECTION_ID_PRESENT, PACKET_1BYTE_PACKET_NUMBER, 256);
+ "", true, CONNECTION_ID_PRESENT, PACKET_1BYTE_PACKET_NUMBER, 256);
}
TEST_F(QuicDispatcherTest, StatelessVersionNegotiation) {
@@ -539,7 +543,7 @@
std::string chlo = SerializeCHLO() + std::string(1200, 'a');
DCHECK_LE(1200u, chlo.length());
ProcessPacket(client_address, TestConnectionId(1), true,
- QuicVersionReservedForNegotiation(), chlo,
+ QuicVersionReservedForNegotiation(), chlo, true,
CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
}
@@ -558,7 +562,7 @@
std::string chlo = SerializeCHLO() + std::string(1200, 'a');
DCHECK_LE(1200u, chlo.length());
ProcessPacket(client_address, TestConnectionId(1), TestConnectionId(2), true,
- QuicVersionReservedForNegotiation(), chlo,
+ QuicVersionReservedForNegotiation(), chlo, true,
CONNECTION_ID_PRESENT, CONNECTION_ID_PRESENT,
PACKET_4BYTE_PACKET_NUMBER, 1);
}
@@ -578,7 +582,7 @@
std::string truncated_chlo = chlo.substr(0, 1100);
DCHECK_EQ(1100u, truncated_chlo.length());
ProcessPacket(client_address, TestConnectionId(1), true,
- QuicVersionReservedForNegotiation(), truncated_chlo,
+ QuicVersionReservedForNegotiation(), truncated_chlo, false,
CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
}
@@ -602,7 +606,7 @@
std::string truncated_chlo = chlo.substr(0, 1100);
DCHECK_EQ(1100u, truncated_chlo.length());
ProcessPacket(client_address, TestConnectionId(1), true,
- QuicVersionReservedForNegotiation(), truncated_chlo,
+ QuicVersionReservedForNegotiation(), truncated_chlo, true,
CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
}
@@ -1327,6 +1331,68 @@
sizeof(connection_id_bytes));
}
+TEST_F(QuicDispatcherTest, DoNotProcessSmallPacket) {
+ SetQuicReloadableFlag(quic_donot_process_small_initial_packets, true);
+ SetQuicReloadableFlag(quic_use_parse_public_header, true);
+ CreateTimeWaitListManager();
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0);
+ EXPECT_CALL(*time_wait_list_manager_, SendPacket(_, _, _)).Times(1);
+ ProcessPacket(client_address, TestConnectionId(1), true,
+ CurrentSupportedVersions()[0], SerializeCHLO(), false,
+ CONNECTION_ID_PRESENT, PACKET_4BYTE_PACKET_NUMBER, 1);
+}
+
+TEST_F(QuicDispatcherTest, ProcessSmallCoalescedPacket) {
+ SetQuicReloadableFlag(quic_enable_version_99, true);
+ CreateTimeWaitListManager();
+ QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);
+
+ EXPECT_CALL(*time_wait_list_manager_, SendPacket(_, _, _)).Times(0);
+
+ // clang-format off
+ char coalesced_packet[1200] = {
+ // first coalesced packet
+ // public flags (long header with packet type INITIAL and
+ // 4-byte packet number)
+ 0xC3,
+ // version
+ 'Q', '0', '9', '9',
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x05,
+ // packet number
+ 0x12, 0x34, 0x56, 0x78,
+ // Padding
+ 0x00,
+ // second coalesced packet
+ // public flags (long header with packet type ZERO_RTT_PROTECTED and
+ // 4-byte packet number)
+ 0xC3,
+ // version
+ 'Q', '0', '9', '9',
+ // destination connection ID length
+ 0x08,
+ // destination connection ID
+ 0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10,
+ // source connection ID length
+ 0x00,
+ // long header packet length
+ 0x1E,
+ // packet number
+ 0x12, 0x34, 0x56, 0x79,
+ };
+ // clang-format on
+ QuicReceivedPacket packet(coalesced_packet, 1200, QuicTime::Zero());
+ dispatcher_->ProcessPacket(server_address_, client_address, packet);
+}
+
// Verify the stopgap test: Packets with truncated connection IDs should be
// dropped.
class QuicDispatcherTestStrayPacketConnectionId : public QuicDispatcherTest {};
@@ -2118,7 +2184,7 @@
})));
}
ProcessPacket(client_addr_, TestConnectionId(conn_id), true, version,
- SerializeFullCHLO(), CONNECTION_ID_PRESENT,
+ SerializeFullCHLO(), true, CONNECTION_ID_PRESENT,
PACKET_4BYTE_PACKET_NUMBER, 1);
}
diff --git a/quic/core/quic_framer.cc b/quic/core/quic_framer.cc
index e33dc00..84dd00a 100644
--- a/quic/core/quic_framer.cc
+++ b/quic/core/quic_framer.cc
@@ -6303,6 +6303,7 @@
const QuicEncryptedPacket& packet,
uint8_t expected_destination_connection_id_length,
PacketHeaderFormat* format,
+ QuicLongHeaderType* long_packet_type,
bool* version_present,
bool* has_length_prefix,
QuicVersionLabel* version_label,
@@ -6321,12 +6322,11 @@
const bool ietf_format = QuicUtils::IsIetfPacketHeader(first_byte);
uint8_t unused_first_byte;
QuicVariableLengthIntegerLength retry_token_length_length;
- QuicLongHeaderType unused_log_packet_type;
- const QuicErrorCode error_code = ParsePublicHeader(
+ QuicErrorCode error_code = ParsePublicHeader(
&reader, expected_destination_connection_id_length, ietf_format,
&unused_first_byte, format, version_present, has_length_prefix,
version_label, parsed_version, destination_connection_id,
- source_connection_id, &unused_log_packet_type, &retry_token_length_length,
+ source_connection_id, long_packet_type, &retry_token_length_length,
retry_token, detailed_error);
*retry_token_present =
retry_token_length_length != VARIABLE_LENGTH_INTEGER_LENGTH_0;
diff --git a/quic/core/quic_framer.h b/quic/core/quic_framer.h
index ffeeab1..3d4c063 100644
--- a/quic/core/quic_framer.h
+++ b/quic/core/quic_framer.h
@@ -422,6 +422,7 @@
const QuicEncryptedPacket& packet,
uint8_t expected_destination_connection_id_length,
PacketHeaderFormat* format,
+ QuicLongHeaderType* long_packet_type,
bool* version_present,
bool* has_length_prefix,
QuicVersionLabel* version_label,
diff --git a/quic/core/quic_framer_test.cc b/quic/core/quic_framer_test.cc
index 9211d68..deb66d3 100644
--- a/quic/core/quic_framer_test.cc
+++ b/quic/core/quic_framer_test.cc
@@ -1015,6 +1015,7 @@
CheckFramingBoundaries(fragments, QUIC_INVALID_PACKET_HEADER);
PacketHeaderFormat format;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_flag;
QuicConnectionId destination_connection_id, source_connection_id;
QuicVersionLabel version_label;
@@ -1030,8 +1031,8 @@
QuicStringPiece retry_token;
ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
error_code = QuicFramer::ParsePublicHeaderDispatcher(
- *encrypted, kQuicDefaultConnectionIdLength, &format, &version_flag,
- &use_length_prefix, &version_label, &parsed_version,
+ *encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_flag, &use_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_FALSE(retry_token_present);
@@ -1086,6 +1087,7 @@
CheckFramingBoundaries(packet46, QUIC_INVALID_PACKET_HEADER);
PacketHeaderFormat format;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_flag;
QuicConnectionId destination_connection_id, source_connection_id;
QuicVersionLabel version_label;
@@ -1101,8 +1103,8 @@
QuicStringPiece retry_token;
ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
error_code = QuicFramer::ParsePublicHeaderDispatcher(
- *encrypted, kQuicDefaultConnectionIdLength, &format, &version_flag,
- &use_length_prefix, &version_label, &parsed_version,
+ *encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_flag, &use_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_EQ(retry_token_present, framer_.version().SupportsRetry());
@@ -1146,6 +1148,7 @@
QuicEncryptedPacket encrypted(AsChars(packet), QUIC_ARRAYSIZE(packet), false);
PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_flag = false;
QuicConnectionId destination_connection_id, source_connection_id;
QuicVersionLabel version_label = 0;
@@ -1161,8 +1164,8 @@
QuicStringPiece retry_token;
ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
error_code = QuicFramer::ParsePublicHeaderDispatcher(
- encrypted, kQuicDefaultConnectionIdLength, &format, &version_flag,
- &use_length_prefix, &version_label, &parsed_version,
+ encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_flag, &use_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_FALSE(retry_token_present);
@@ -13152,6 +13155,7 @@
visitor_.header_.get()->destination_connection_id);
PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_present = false, has_length_prefix = false;
QuicVersionLabel version_label = 0;
ParsedQuicVersion parsed_version = QuicVersionReservedForNegotiation();
@@ -13162,8 +13166,8 @@
std::string detailed_error = "foobar";
QuicErrorCode parse_result = QuicFramer::ParsePublicHeaderDispatcher(
- encrypted, kQuicDefaultConnectionIdLength, &format, &version_present,
- &has_length_prefix, &version_label, &parsed_version,
+ encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_EQ(QUIC_NO_ERROR, parse_result);
@@ -13302,6 +13306,7 @@
QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet),
sizeof(packet));
PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_present = false, has_length_prefix = true;
QuicVersionLabel version_label = 33;
ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
@@ -13311,8 +13316,8 @@
QuicStringPiece retry_token;
std::string detailed_error = "foobar";
QuicErrorCode header_parse_result = QuicFramer::ParsePublicHeaderDispatcher(
- encrypted, kQuicDefaultConnectionIdLength, &format, &version_present,
- &has_length_prefix, &version_label, &parsed_version,
+ encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_EQ(QUIC_NO_ERROR, header_parse_result);
@@ -13380,6 +13385,7 @@
QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet),
sizeof(packet));
PacketHeaderFormat format = GOOGLE_QUIC_PACKET;
+ QuicLongHeaderType long_packet_type = INVALID_PACKET_TYPE;
bool version_present = false, has_length_prefix = false;
QuicVersionLabel version_label = 33;
ParsedQuicVersion parsed_version = UnsupportedQuicVersion();
@@ -13389,8 +13395,8 @@
QuicStringPiece retry_token;
std::string detailed_error = "foobar";
QuicErrorCode header_parse_result = QuicFramer::ParsePublicHeaderDispatcher(
- encrypted, kQuicDefaultConnectionIdLength, &format, &version_present,
- &has_length_prefix, &version_label, &parsed_version,
+ encrypted, kQuicDefaultConnectionIdLength, &format, &long_packet_type,
+ &version_present, &has_length_prefix, &version_label, &parsed_version,
&destination_connection_id, &source_connection_id, &retry_token_present,
&retry_token, &detailed_error);
EXPECT_EQ(QUIC_NO_ERROR, header_parse_result);
diff --git a/quic/core/quic_packets.cc b/quic/core/quic_packets.cc
index 0352056..139fcda 100644
--- a/quic/core/quic_packets.cc
+++ b/quic/core/quic_packets.cc
@@ -502,6 +502,7 @@
peer_address(peer_address),
packet(packet),
form(GOOGLE_QUIC_PACKET),
+ long_packet_type(INVALID_PACKET_TYPE),
version_flag(false),
use_length_prefix(false),
version_label(0),
diff --git a/quic/core/quic_packets.h b/quic/core/quic_packets.h
index 5c34b64..7faa741 100644
--- a/quic/core/quic_packets.h
+++ b/quic/core/quic_packets.h
@@ -437,6 +437,8 @@
// Fields below are populated by QuicFramer::ProcessPacketDispatcher.
PacketHeaderFormat form;
+ // This is only used if the form is IETF_QUIC_LONG_HEADER_PACKET.
+ QuicLongHeaderType long_packet_type;
bool version_flag;
bool use_length_prefix;
QuicVersionLabel version_label;
diff --git a/quic/core/quic_time_wait_list_manager.cc b/quic/core/quic_time_wait_list_manager.cc
index b163185..b8becf6 100644
--- a/quic/core/quic_time_wait_list_manager.cc
+++ b/quic/core/quic_time_wait_list_manager.cc
@@ -271,6 +271,14 @@
packet_context.get());
}
+void QuicTimeWaitListManager::SendPacket(const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ const QuicEncryptedPacket& packet) {
+ SendOrQueuePacket(std::make_unique<QueuedPacket>(self_address, peer_address,
+ packet.Clone()),
+ nullptr);
+}
+
std::unique_ptr<QuicEncryptedPacket> QuicTimeWaitListManager::BuildPublicReset(
const QuicPublicResetPacket& packet) {
return QuicFramer::BuildPublicResetPacket(packet);
diff --git a/quic/core/quic_time_wait_list_manager.h b/quic/core/quic_time_wait_list_manager.h
index 9a807c0..e2a4594 100644
--- a/quic/core/quic_time_wait_list_manager.h
+++ b/quic/core/quic_time_wait_list_manager.h
@@ -139,6 +139,11 @@
bool ietf_quic,
std::unique_ptr<QuicPerPacketContext> packet_context);
+ // Called to send |packet|.
+ virtual void SendPacket(const QuicSocketAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ const QuicEncryptedPacket& packet);
+
// Return a non-owning pointer to the packet writer.
QuicPacketWriter* writer() { return writer_; }