Add method to serialize connection close packet with large packet number PiperOrigin-RevId: 656213063
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc index 64ff5a9..6bdf826 100644 --- a/quiche/quic/core/http/end_to_end_test.cc +++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -7704,6 +7704,45 @@ server_thread_->Resume(); } +TEST_P(EndToEndTest, SerializeConnectionClosePacketWithLargestPacketNumber) { + ASSERT_TRUE(Initialize()); + if (!version_.UsesTls()) { + return; + } + EXPECT_TRUE(client_->client()->WaitForHandshakeConfirmed()); + + std::unique_ptr<SerializedPacket> connection_close_packet = + GetClientConnection()->SerializeLargePacketNumberConnectionClosePacket( + QUIC_CLIENT_LOST_NETWORK_ACCESS, "EndToEndTest"); + ASSERT_NE(connection_close_packet, nullptr); + + // Send 50 requests to increase the packet number. + for (int i = 0; i < 50; ++i) { + EXPECT_EQ(kFooResponseBody, client_->SendSynchronousRequest("/foo")); + } + + server_thread_->Pause(); + QuicDispatcher* dispatcher = + QuicServerPeer::GetDispatcher(server_thread_->server()); + EXPECT_EQ(dispatcher->NumSessions(), 1); + server_thread_->Resume(); + + // Send the connection close packet to the server. + QUIC_LOG(INFO) << "Sending close connection packet"; + client_writer_->WritePacket( + connection_close_packet->encrypted_buffer, + connection_close_packet->encrypted_length, + client_->client()->network_helper()->GetLatestClientAddress().host(), + server_address_, nullptr, packet_writer_params_); + + // Wait for the server to close the connection. + EXPECT_TRUE( + server_thread_->WaitUntil([&] { return dispatcher->NumSessions() == 0; }, + QuicTime::Delta::FromSeconds(5))); + + EXPECT_EQ("", client_->SendSynchronousRequest("/foo")); + EXPECT_THAT(client_->connection_error(), IsError(QUIC_PUBLIC_RESET)); +} } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index c81536b..9e56dae 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -7342,6 +7342,18 @@ blackhole_detector_.OnAlarm(); } +std::unique_ptr<SerializedPacket> +QuicConnection::SerializeLargePacketNumberConnectionClosePacket( + QuicErrorCode error, const std::string& error_details) { + QUICHE_DCHECK(IsHandshakeConfirmed()); + QUICHE_DCHECK(!error_details.empty()); + if (!IsHandshakeConfirmed()) { + return nullptr; + } + return packet_creator_.SerializeLargePacketNumberConnectionClosePacket( + GetLargestAckedPacket(), error, error_details); +} + #undef ENDPOINT // undef for jumbo builds } // namespace quic
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h index a879b6c..c34161b 100644 --- a/quiche/quic/core/quic_connection.h +++ b/quiche/quic/core/quic_connection.h
@@ -1377,6 +1377,22 @@ void OnNetworkBlackholeDetectorAlarm() override; void OnPingAlarm() override; + // Create a CONNECTION_CLOSE packet with a large packet number. + // If this method is called before handshake is confirmed, this method returns + // nullptr. + // This packet can be pre-generated and other processes can send it later to + // close the connection. + // For example, on Android, the system server stores this packet and sends it + // to the server when the app is frozen, loses network access due to the + // firewall, or crashes. This will stop servers sending packets to the + // device and wasting the device battery. + // Note that the generated packet uses the packet number larger than the + // current largest acked packet number by (1 << 31) + 1 but the server will + // ignore this packet after the connection uses this packet number. + std::unique_ptr<SerializedPacket> + SerializeLargePacketNumberConnectionClosePacket( + QuicErrorCode error, const std::string& error_details); + protected: // Calls cancel() on all the alarms owned by this connection. void CancelAllAlarms();
diff --git a/quiche/quic/core/quic_error_codes.cc b/quiche/quic/core/quic_error_codes.cc index 51e11e8..daf62b4 100644 --- a/quiche/quic/core/quic_error_codes.cc +++ b/quiche/quic/core/quic_error_codes.cc
@@ -286,6 +286,7 @@ RETURN_STRING_LITERAL(QUIC_TLS_KEYING_MATERIAL_EXPORT_NOT_AVAILABLE); RETURN_STRING_LITERAL(QUIC_UNEXPECTED_DATA_BEFORE_ENCRYPTION_ESTABLISHED); RETURN_STRING_LITERAL(QUIC_SERVER_UNHEALTHY); + RETURN_STRING_LITERAL(QUIC_CLIENT_LOST_NETWORK_ACCESS); RETURN_STRING_LITERAL(QUIC_LAST_ERROR); // Intentionally have no default case, so we'll break the build @@ -799,6 +800,8 @@ return {true, static_cast<uint64_t>(INTERNAL_ERROR)}; case QUIC_HANDSHAKE_FAILED_PACKETS_BUFFERED_TOO_LONG: return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)}; + case QUIC_CLIENT_LOST_NETWORK_ACCESS: + return {true, static_cast<uint64_t>(NO_IETF_QUIC_ERROR)}; case QUIC_LAST_ERROR: return {false, static_cast<uint64_t>(QUIC_LAST_ERROR)}; }
diff --git a/quiche/quic/core/quic_error_codes.h b/quiche/quic/core/quic_error_codes.h index a1ea0d4..a28d595 100644 --- a/quiche/quic/core/quic_error_codes.h +++ b/quiche/quic/core/quic_error_codes.h
@@ -622,8 +622,11 @@ // Error code related to backend health-check. QUIC_SERVER_UNHEALTHY = 213, + // Client application lost network access. + QUIC_CLIENT_LOST_NETWORK_ACCESS = 215, + // No error. Used as bound while iterating. - QUIC_LAST_ERROR = 215, + QUIC_LAST_ERROR = 216, }; // QuicErrorCodes is encoded as four octets on-the-wire when doing Google QUIC, // or a varint62 when doing IETF QUIC. Ensure that its value does not exceed
diff --git a/quiche/quic/core/quic_packet_creator.cc b/quiche/quic/core/quic_packet_creator.cc index ca35a07..8823a5c 100644 --- a/quiche/quic/core/quic_packet_creator.cc +++ b/quiche/quic/core/quic_packet_creator.cc
@@ -1038,6 +1038,64 @@ return serialize_packet; } +std::unique_ptr<SerializedPacket> +QuicPacketCreator::SerializeLargePacketNumberConnectionClosePacket( + QuicPacketNumber largest_acked_packet, QuicErrorCode error, + const std::string& error_details) { + QUICHE_DCHECK_EQ(packet_.encryption_level, ENCRYPTION_FORWARD_SECURE) + << ENDPOINT; + // Largest packet number is 2^62 - 1 but the packet number is encoded to 1 to + // 4 bytes. + // Receiver decodes packet number assuming the packet number is less than or + // equal to (largest packet number that has been successfully processed) + 1 + // + (1 << (packet_number_length - 1)). + // So, generate a packet with the largest packet number in this range. + // Note that FillPacketHeader increments before fills the header. + const QuicPacketNumber largest_packet_number( + (largest_acked_packet.IsInitialized() + ? largest_acked_packet + : framer_->first_sending_packet_number()) + + (1L << 31)); + ScopedPacketContextSwitcher switcher(largest_packet_number, + PACKET_4BYTE_PACKET_NUMBER, + ENCRYPTION_FORWARD_SECURE, &packet_); + + QuicPacketHeader header; + FillPacketHeader(&header); + + QUIC_DVLOG(2) << ENDPOINT << "Serializing connection close packet " << header; + + QuicFrames frames; + QuicConnectionCloseFrame close_frame(transport_version(), error, + NO_IETF_QUIC_ERROR, error_details, 0); + frames.push_back(QuicFrame(&close_frame)); + + std::unique_ptr<char[]> buffer(new char[kMaxOutgoingPacketSize]); + const size_t length = + framer_->BuildDataPacket(header, frames, buffer.get(), + max_plaintext_size_, packet_.encryption_level); + QUICHE_DCHECK(length) << ENDPOINT; + + const size_t encrypted_length = framer_->EncryptInPlace( + packet_.encryption_level, packet_.packet_number, + GetStartOfEncryptedData(framer_->transport_version(), header), length, + kMaxOutgoingPacketSize, buffer.get()); + QUICHE_DCHECK(encrypted_length) << ENDPOINT; + + std::unique_ptr<SerializedPacket> serialize_packet( + new SerializedPacket(header.packet_number, header.packet_number_length, + buffer.release(), encrypted_length, + /*has_ack=*/false, /*has_stop_waiting=*/false)); + + serialize_packet->release_encrypted_buffer = [](const char* p) { + delete[] p; + }; + serialize_packet->encryption_level = packet_.encryption_level; + serialize_packet->transmission_type = NOT_RETRANSMISSION; + + return serialize_packet; +} + size_t QuicPacketCreator::BuildPaddedPathChallengePacket( const QuicPacketHeader& header, char* buffer, size_t packet_length, const QuicPathFrameBuffer& payload, EncryptionLevel level) {
diff --git a/quiche/quic/core/quic_packet_creator.h b/quiche/quic/core/quic_packet_creator.h index 6c3fc73..87c2000 100644 --- a/quiche/quic/core/quic_packet_creator.h +++ b/quiche/quic/core/quic_packet_creator.h
@@ -262,6 +262,21 @@ const quiche::QuicheCircularDeque<QuicPathFrameBuffer>& payloads, const bool is_padded); + // Create a CONNECTION_CLOSE packet with a large packet number. + // This packet can be pre-generated and other processes can send it later to + // close the connection. + // For example, on Android, the system server stores this packet and sends it + // to the server when the app is frozen, loses network access due to the + // firewall, or crashes. This will stop servers sending packets to the + // device and wasting the device battery. + // Note that the generated packet uses the packet number larger than the + // current largest acked packet number by (1 << 31) + 1 but the server will + // ignore this packet after the connection uses this packet number. + std::unique_ptr<SerializedPacket> + SerializeLargePacketNumberConnectionClosePacket( + QuicPacketNumber largest_acked_packet, QuicErrorCode error, + const std::string& error_details); + // Add PATH_RESPONSE to current packet, flush before or afterwards if needed. bool AddPathResponseFrame(const QuicPathFrameBuffer& data_buffer);
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc index 3946a90..0575b88 100644 --- a/quiche/quic/core/quic_packet_creator_test.cc +++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -1202,6 +1202,26 @@ encrypted->encrypted_buffer, encrypted->encrypted_length)); } +TEST_P(QuicPacketCreatorTest, SerializeLargePacketNumberConnectionClosePacket) { + creator_.set_encryption_level(ENCRYPTION_FORWARD_SECURE); + std::unique_ptr<SerializedPacket> encrypted( + creator_.SerializeLargePacketNumberConnectionClosePacket( + QuicPacketNumber(1), QUIC_CLIENT_LOST_NETWORK_ACCESS, + "QuicPacketCreatorTest")); + + InSequence s; + EXPECT_CALL(framer_visitor_, OnPacket()); + EXPECT_CALL(framer_visitor_, OnUnauthenticatedPublicHeader(_)); + EXPECT_CALL(framer_visitor_, OnUnauthenticatedHeader(_)); + EXPECT_CALL(framer_visitor_, OnDecryptedPacket(_, _)); + EXPECT_CALL(framer_visitor_, OnPacketHeader(_)); + EXPECT_CALL(framer_visitor_, OnConnectionCloseFrame(_)); + EXPECT_CALL(framer_visitor_, OnPacketComplete()); + + server_framer_.ProcessPacket(QuicEncryptedPacket( + encrypted->encrypted_buffer, encrypted->encrypted_length)); +} + TEST_P(QuicPacketCreatorTest, UpdatePacketSequenceNumberLengthLeastAwaiting) { if (!GetParam().version.SendsVariableLengthPacketNumberInLongHeader()) { EXPECT_EQ(PACKET_4BYTE_PACKET_NUMBER,