Adopt TlsChloExtractor from QuicDispatcher This CL allows the dispatcher to parse TLS ClientHellos like it handles QUIC_CRYPTO CHLOs. gfe-relnote: parse TLS CHLO, protected by TLS flags PiperOrigin-RevId: 308293400 Change-Id: Ia72f4a7dd5d96b994479e312183fa6bb6763fb32
diff --git a/quic/core/quic_buffered_packet_store.cc b/quic/core/quic_buffered_packet_store.cc index 24ca6ef..cafe871 100644 --- a/quic/core/quic_buffered_packet_store.cc +++ b/quic/core/quic_buffered_packet_store.cc
@@ -86,23 +86,25 @@ QuicSocketAddress self_address, QuicSocketAddress peer_address, bool is_chlo, - const std::string& alpn, + const std::vector<std::string>& alpns, const ParsedQuicVersion& version) { QUIC_BUG_IF(!GetQuicFlag(FLAGS_quic_allow_chlo_buffering)) << "Shouldn't buffer packets if disabled via flag."; QUIC_BUG_IF(is_chlo && QuicContainsKey(connections_with_chlo_, connection_id)) << "Shouldn't buffer duplicated CHLO on connection " << connection_id; - QUIC_BUG_IF(!is_chlo && !alpn.empty()) + QUIC_BUG_IF(!is_chlo && !alpns.empty()) << "Shouldn't have an ALPN defined for a non-CHLO packet."; QUIC_BUG_IF(is_chlo && version.transport_version == QUIC_VERSION_UNSUPPORTED) << "Should have version for CHLO packet."; - if (!QuicContainsKey(undecryptable_packets_, connection_id) && - ShouldBufferPacket(is_chlo)) { - // Drop the packet if the upper limit of undecryptable packets has been - // reached or the whole capacity of the store has been reached. - return TOO_MANY_CONNECTIONS; - } else if (!QuicContainsKey(undecryptable_packets_, connection_id)) { + const bool is_first_packet = + !QuicContainsKey(undecryptable_packets_, connection_id); + if (is_first_packet) { + if (ShouldBufferPacket(is_chlo)) { + // Drop the packet if the upper limit of undecryptable packets has been + // reached or the whole capacity of the store has been reached. + return TOO_MANY_CONNECTIONS; + } undecryptable_packets_.emplace( std::make_pair(connection_id, BufferedPacketList())); undecryptable_packets_.back().second.ietf_quic = ietf_quic; @@ -138,14 +140,25 @@ // Add CHLO to the beginning of buffered packets so that it can be delivered // first later. queue.buffered_packets.push_front(std::move(new_entry)); - queue.alpns = {alpn}; + queue.alpns = alpns; connections_with_chlo_[connection_id] = false; // Dummy value. // Set the version of buffered packets of this connection on CHLO. queue.version = version; } else { // Buffer non-CHLO packets in arrival order. queue.buffered_packets.push_back(std::move(new_entry)); + + // Attempt to parse multi-packet TLS CHLOs. + if (is_first_packet) { + queue.tls_chlo_extractor.IngestPacket(version, packet); + // Since this is the first packet and it's not a CHLO, the + // TlsChloExtractor should not have the entire CHLO. + QUIC_BUG_IF(queue.tls_chlo_extractor.HasParsedFullChlo()) + << "First packet in list should not contain full CHLO"; + } + // TODO(b/154857081) Reorder CHLO packets ahead of other ones. } + MaybeSetExpirationAlarm(); return SUCCESS; } @@ -240,4 +253,25 @@ return QuicContainsKey(connections_with_chlo_, connection_id); } +bool QuicBufferedPacketStore::IngestPacketForTlsChloExtraction( + const QuicConnectionId& connection_id, + const ParsedQuicVersion& version, + const QuicReceivedPacket& packet, + std::vector<std::string>* out_alpns) { + DCHECK_NE(out_alpns, nullptr); + DCHECK_EQ(version.handshake_protocol, PROTOCOL_TLS1_3); + auto it = undecryptable_packets_.find(connection_id); + if (it == undecryptable_packets_.end()) { + QUIC_BUG << "Cannot ingest packet for unknown connection ID " + << connection_id; + return false; + } + it->second.tls_chlo_extractor.IngestPacket(version, packet); + if (!it->second.tls_chlo_extractor.HasParsedFullChlo()) { + return false; + } + *out_alpns = it->second.tls_chlo_extractor.alpns(); + return true; +} + } // namespace quic
diff --git a/quic/core/quic_buffered_packet_store.h b/quic/core/quic_buffered_packet_store.h index 3d9290e..0cc3708 100644 --- a/quic/core/quic_buffered_packet_store.h +++ b/quic/core/quic_buffered_packet_store.h
@@ -13,6 +13,7 @@ #include "net/third_party/quiche/src/quic/core/quic_clock.h" #include "net/third_party/quiche/src/quic/core/quic_packets.h" #include "net/third_party/quiche/src/quic/core/quic_time.h" +#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h" #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h" #include "net/third_party/quiche/src/quic/platform/api/quic_export.h" #include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h" @@ -73,6 +74,7 @@ // If buffered_packets contains the CHLO, it is the version of the CHLO. // Otherwise, it is the version of the first packet in |buffered_packets|. ParsedQuicVersion version; + TlsChloExtractor tls_chlo_extractor; }; typedef QuicLinkedHashMap<QuicConnectionId, @@ -108,12 +110,22 @@ QuicSocketAddress self_address, QuicSocketAddress peer_address, bool is_chlo, - const std::string& alpn, + const std::vector<std::string>& alpns, const ParsedQuicVersion& version); // Returns true if there are any packets buffered for |connection_id|. bool HasBufferedPackets(QuicConnectionId connection_id) const; + // Ingests this packet into the corresponding TlsChloExtractor. This should + // only be called when HasBufferedPackets(connection_id) is true. + // Returns whether we've now parsed a full multi-packet TLS CHLO. + // When this returns true, |out_alpns| is populated with the list of ALPNs + // extracted from the CHLO. + bool IngestPacketForTlsChloExtraction(const QuicConnectionId& connection_id, + const ParsedQuicVersion& version, + const QuicReceivedPacket& packet, + std::vector<std::string>* out_alpns); + // Returns the list of buffered packets for |connection_id| and removes them // from the store. Returns an empty list if no early arrived packets for this // connection are present.
diff --git a/quic/core/quic_buffered_packet_store_test.cc b/quic/core/quic_buffered_packet_store_test.cc index 1b1544b..d99f4e0 100644 --- a/quic/core/quic_buffered_packet_store_test.cc +++ b/quic/core/quic_buffered_packet_store_test.cc
@@ -74,7 +74,7 @@ TEST_F(QuicBufferedPacketStoreTest, SimpleEnqueueAndDeliverPacket) { QuicConnectionId connection_id = TestConnectionId(1); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); EXPECT_TRUE(store_.HasBufferedPackets(connection_id)); auto packets = store_.DeliverPackets(connection_id); const std::list<BufferedPacket>& queue = packets.buffered_packets; @@ -97,9 +97,9 @@ QuicSocketAddress addr_with_new_port(QuicIpAddress::Any4(), 256); QuicConnectionId connection_id = TestConnectionId(1); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - addr_with_new_port, false, "", invalid_version_); + addr_with_new_port, false, {}, invalid_version_); std::list<BufferedPacket> queue = store_.DeliverPackets(connection_id).buffered_packets; ASSERT_EQ(2u, queue.size()); @@ -114,9 +114,9 @@ for (uint64_t conn_id = 1; conn_id <= num_connections; ++conn_id) { QuicConnectionId connection_id = TestConnectionId(conn_id); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); } // Deliver packets in reversed order. @@ -138,12 +138,12 @@ // keep. EXPECT_EQ(QuicBufferedPacketStore::SUCCESS, store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, true, "", valid_version_)); + peer_address_, true, {}, valid_version_)); for (size_t i = 1; i <= num_packets; ++i) { // Only first |kDefaultMaxUndecryptablePackets packets| will be buffered. EnqueuePacketResult result = store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); if (i <= kDefaultMaxUndecryptablePackets) { EXPECT_EQ(EnqueuePacketResult::SUCCESS, result); } else { @@ -165,7 +165,7 @@ QuicConnectionId connection_id = TestConnectionId(conn_id); EnqueuePacketResult result = store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); if (conn_id <= kMaxConnectionsWithoutCHLO) { EXPECT_EQ(EnqueuePacketResult::SUCCESS, result); } else { @@ -194,7 +194,7 @@ for (uint64_t conn_id = 1; conn_id <= num_chlos; ++conn_id) { EXPECT_EQ(EnqueuePacketResult::SUCCESS, store_.EnqueuePacket(TestConnectionId(conn_id), false, packet_, - self_address_, peer_address_, true, "", + self_address_, peer_address_, true, {}, valid_version_)); } @@ -205,7 +205,7 @@ QuicConnectionId connection_id = TestConnectionId(conn_id); EnqueuePacketResult result = store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, true, "", valid_version_); + peer_address_, true, {}, valid_version_); if (conn_id <= kDefaultMaxConnectionsInStore) { EXPECT_EQ(EnqueuePacketResult::SUCCESS, result); } else { @@ -220,7 +220,7 @@ QuicConnectionId connection_id = TestConnectionId(conn_id); EXPECT_EQ(EnqueuePacketResult::SUCCESS, store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_)); + peer_address_, false, {}, invalid_version_)); } // Buffer CHLOs on other connections till store is full. @@ -229,7 +229,7 @@ QuicConnectionId connection_id = TestConnectionId(i); EnqueuePacketResult rs = store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, true, "", valid_version_); + peer_address_, true, {}, valid_version_); if (i <= kDefaultMaxConnectionsInStore) { EXPECT_EQ(EnqueuePacketResult::SUCCESS, rs); EXPECT_TRUE(store_.HasChloForConnection(connection_id)); @@ -246,7 +246,7 @@ EXPECT_EQ(EnqueuePacketResult::SUCCESS, store_.EnqueuePacket( /*connection_id=*/TestConnectionId(1), false, packet_, - self_address_, peer_address_, true, "", valid_version_)); + self_address_, peer_address_, true, {}, valid_version_)); EXPECT_TRUE(store_.HasChloForConnection( /*connection_id=*/TestConnectionId(1))); @@ -274,14 +274,14 @@ TEST_F(QuicBufferedPacketStoreTest, PacketQueueExpiredBeforeDelivery) { QuicConnectionId connection_id = TestConnectionId(1); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); EXPECT_EQ(EnqueuePacketResult::SUCCESS, store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, true, "", valid_version_)); + peer_address_, true, {}, valid_version_)); QuicConnectionId connection_id2 = TestConnectionId(2); EXPECT_EQ(EnqueuePacketResult::SUCCESS, store_.EnqueuePacket(connection_id2, false, packet_, self_address_, - peer_address_, false, "", invalid_version_)); + peer_address_, false, {}, invalid_version_)); // CHLO on connection 3 arrives 1ms later. clock_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1)); @@ -290,7 +290,7 @@ // connections. QuicSocketAddress another_client_address(QuicIpAddress::Any4(), 255); store_.EnqueuePacket(connection_id3, false, packet_, self_address_, - another_client_address, true, "", valid_version_); + another_client_address, true, {}, valid_version_); // Advance clock to the time when connection 1 and 2 expires. clock_.AdvanceTime( @@ -322,9 +322,9 @@ // for them to expire. QuicConnectionId connection_id4 = TestConnectionId(4); store_.EnqueuePacket(connection_id4, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id4, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); clock_.AdvanceTime( QuicBufferedPacketStorePeer::expiration_alarm(&store_)->deadline() - clock_.ApproximateNow()); @@ -339,9 +339,9 @@ // Enqueue some packets store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); EXPECT_TRUE(store_.HasBufferedPackets(connection_id)); EXPECT_FALSE(store_.HasChlosBuffered()); @@ -365,11 +365,11 @@ // Enqueue some packets, which include a CHLO store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, true, "", valid_version_); + peer_address_, true, {}, valid_version_); store_.EnqueuePacket(connection_id, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); EXPECT_TRUE(store_.HasBufferedPackets(connection_id)); EXPECT_TRUE(store_.HasChlosBuffered()); @@ -394,11 +394,11 @@ // Enqueue some packets for two connection IDs store_.EnqueuePacket(connection_id_1, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id_1, false, packet_, self_address_, - peer_address_, false, "", invalid_version_); + peer_address_, false, {}, invalid_version_); store_.EnqueuePacket(connection_id_2, false, packet_, self_address_, - peer_address_, true, "h3", valid_version_); + peer_address_, true, {"h3"}, valid_version_); EXPECT_TRUE(store_.HasBufferedPackets(connection_id_1)); EXPECT_TRUE(store_.HasBufferedPackets(connection_id_2)); EXPECT_TRUE(store_.HasChlosBuffered());
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc index 18b6e56..e9bd2f0 100644 --- a/quic/core/quic_dispatcher.cc +++ b/quic/core/quic_dispatcher.cc
@@ -15,6 +15,7 @@ #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_utils.h" #include "net/third_party/quiche/src/quic/core/quic_versions.h" +#include "net/third_party/quiche/src/quic/core/tls_chlo_extractor.h" #include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flag_utils.h" #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h" @@ -481,9 +482,37 @@ switch (fate) { case kFateProcess: { if (packet_info->version.handshake_protocol == PROTOCOL_TLS1_3) { - // TODO(nharper): Support buffering non-ClientHello packets when using - // TLS. - ProcessChlo(/*alpn=*/"", packet_info); + bool has_full_tls_chlo = false; + std::vector<std::string> alpns; + if (buffered_packets_.HasBufferedPackets( + packet_info->destination_connection_id)) { + // If we already have buffered packets for this connection ID, + // use the associated TlsChloExtractor to parse this packet. + has_full_tls_chlo = + buffered_packets_.IngestPacketForTlsChloExtraction( + packet_info->destination_connection_id, packet_info->version, + packet_info->packet, &alpns); + } else { + // If we do not have a BufferedPacketList for this connection ID, + // create a single-use one to check whether this packet contains a + // full single-packet CHLO. + TlsChloExtractor tls_chlo_extractor; + tls_chlo_extractor.IngestPacket(packet_info->version, + packet_info->packet); + if (tls_chlo_extractor.HasParsedFullChlo()) { + // This packet contains a full single-packet CHLO. + has_full_tls_chlo = true; + alpns = tls_chlo_extractor.alpns(); + } + } + if (has_full_tls_chlo) { + ProcessChlo(alpns, packet_info); + } else { + // This packet does not contain a full CHLO. It could be a 0-RTT + // packet that arrived before the CHLO (due to loss or reordering), + // or it could be a fragment of a multi-packet CHLO. + BufferEarlyPacket(*packet_info); + } break; } if (GetQuicFlag(FLAGS_quic_allow_chlo_buffering) && @@ -495,7 +524,7 @@ BufferEarlyPacket(*packet_info); break; } - ProcessChlo(alpn_extractor.ConsumeAlpn(), packet_info); + ProcessChlo({alpn_extractor.ConsumeAlpn()}, packet_info); } break; case kFateTimeWait: // Add this connection_id to the time-wait state, to safely reject @@ -911,13 +940,13 @@ packet_info.destination_connection_id, packet_info.form != GOOGLE_QUIC_PACKET, packet_info.packet, packet_info.self_address, packet_info.peer_address, /*is_chlo=*/false, - /*alpn=*/"", packet_info.version); + /*alpns=*/{}, packet_info.version); if (rs != EnqueuePacketResult::SUCCESS) { OnBufferPacketFailure(rs, packet_info.destination_connection_id); } } -void QuicDispatcher::ProcessChlo(const std::string& alpn, +void QuicDispatcher::ProcessChlo(const std::vector<std::string>& alpns, ReceivedPacketInfo* packet_info) { if (!buffered_packets_.HasBufferedPackets( packet_info->destination_connection_id) && @@ -933,7 +962,7 @@ packet_info->destination_connection_id, packet_info->form != GOOGLE_QUIC_PACKET, packet_info->packet, packet_info->self_address, packet_info->peer_address, - /*is_chlo=*/true, alpn, packet_info->version); + /*is_chlo=*/true, alpns, packet_info->version); if (rs != EnqueuePacketResult::SUCCESS) { OnBufferPacketFailure(rs, packet_info->destination_connection_id); } @@ -945,6 +974,7 @@ packet_info->destination_connection_id = MaybeReplaceServerConnectionId( original_connection_id, packet_info->version); // Creates a new session and process all buffered packets for this connection. + std::string alpn = SelectAlpn(alpns); std::unique_ptr<QuicSession> session = CreateQuicSession(packet_info->destination_connection_id, packet_info->peer_address, alpn, packet_info->version);
diff --git a/quic/core/quic_dispatcher.h b/quic/core/quic_dispatcher.h index b85f667..cd58130 100644 --- a/quic/core/quic_dispatcher.h +++ b/quic/core/quic_dispatcher.h
@@ -196,7 +196,8 @@ // Called when |packet_info| is a CHLO packet. Creates a new connection and // delivers any buffered packets for that connection id. - void ProcessChlo(const std::string& alpn, ReceivedPacketInfo* packet_info); + void ProcessChlo(const std::vector<std::string>& alpns, + ReceivedPacketInfo* packet_info); // Return true if dispatcher wants to destroy session outside of // OnConnectionClosed() call stack.
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc index fe5adb0..62e1e7b 100644 --- a/quic/core/quic_dispatcher_test.cc +++ b/quic/core/quic_dispatcher_test.cc
@@ -14,6 +14,7 @@ #include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_crypto_server_config.h" #include "net/third_party/quiche/src/quic/core/crypto/quic_random.h" +#include "net/third_party/quiche/src/quic/core/quic_config.h" #include "net/third_party/quiche/src/quic/core/quic_connection_id.h" #include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h" #include "net/third_party/quiche/src/quic/core/quic_packet_writer_wrapper.h" @@ -304,7 +305,8 @@ const QuicSocketAddress& peer_address, const ParsedQuicVersion& version, const QuicConnectionId& server_connection_id) { - if (ChloExtractor::Extract(*received_packet, version, {}, nullptr, + if (version.handshake_protocol == PROTOCOL_QUIC_CRYPTO && + ChloExtractor::Extract(*received_packet, version, {}, nullptr, server_connection_id.length())) { // Add CHLO packet to the beginning to be verified first, because it is // also processed first by new session. @@ -392,10 +394,6 @@ } std::string ExpectedAlpnForVersion(ParsedQuicVersion version) { - if (version.handshake_protocol == PROTOCOL_TLS1_3) { - // TODO(b/149597791) Remove this once we can parse ALPN with TLS. - return ""; - } return AlpnForVersion(version); } @@ -434,6 +432,8 @@ ProcessFirstFlight(version, client_address, connection_id); } + void TestTlsMultiPacketClientHello(bool add_reordering); + ParsedQuicVersion version_; MockQuicConnectionHelper mock_helper_; MockAlarmFactory mock_alarm_factory_; @@ -488,6 +488,62 @@ ProcessFirstFlight(client_address, TestConnectionId(1)); } +void QuicDispatcherTestBase::TestTlsMultiPacketClientHello( + bool add_reordering) { + if (version_.handshake_protocol != PROTOCOL_TLS1_3) { + return; + } + QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); + QuicConnectionId server_connection_id = TestConnectionId(); + QuicConfig client_config = DefaultQuicConfig(); + // Add a 2000-byte custom parameter to increase the length of the CHLO. + constexpr auto kCustomParameterId = + static_cast<TransportParameters::TransportParameterId>(0xff33); + std::string kCustomParameterValue(2000, '-'); + client_config.custom_transport_parameters_to_send()[kCustomParameterId] = + kCustomParameterValue; + std::vector<std::unique_ptr<QuicReceivedPacket>> packets = + GetFirstFlightOfPackets(version_, client_config, server_connection_id); + ASSERT_EQ(packets.size(), 2u); + if (add_reordering) { + std::swap(packets[0], packets[1]); + } + + // Processing the first packet should not create a new session. + EXPECT_CALL(*dispatcher_, + ShouldCreateOrBufferPacketForConnection( + ReceivedPacketInfoConnectionIdEquals(server_connection_id))); + ProcessReceivedPacket(std::move(packets[0]), client_address, version_, + server_connection_id); + + EXPECT_EQ(dispatcher_->session_map().size(), 0u) + << "No session should be created before the rest of the CHLO arrives."; + + // Processing the second packet should create the new session. + EXPECT_CALL(*dispatcher_, + CreateQuicSession(server_connection_id, client_address, + Eq(ExpectedAlpn()), _)) + .WillOnce(Return(ByMove(CreateSession( + dispatcher_.get(), config_, server_connection_id, client_address, + &mock_helper_, &mock_alarm_factory_, &crypto_config_, + QuicDispatcherPeer::GetCache(dispatcher_.get()), &session1_)))); + EXPECT_CALL(*reinterpret_cast<MockQuicConnection*>(session1_->connection()), + ProcessUdpPacket(_, _, _)) + .Times(2); + + ProcessReceivedPacket(std::move(packets[1]), client_address, version_, + server_connection_id); + EXPECT_EQ(dispatcher_->session_map().size(), 1u); +} + +TEST_P(QuicDispatcherTestAllVersions, TlsMultiPacketClientHello) { + TestTlsMultiPacketClientHello(/*add_reordering=*/false); +} + +TEST_P(QuicDispatcherTestAllVersions, TlsMultiPacketClientHelloWithReordering) { + TestTlsMultiPacketClientHello(/*add_reordering=*/true); +} + TEST_P(QuicDispatcherTestAllVersions, ProcessPackets) { QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1);