Workaround for Android UDP network conformance test The Android network conformance test contains a UDP test that sends an invalid QUIC packet and expects a response. The Android team wrote a fix for their test but it will take 2 months to deploy so we're adding this workaround to unblock their testing until then. The workaround is protected by quic_reply_to_old_android_conformance_test. gfe-relnote: respond to a conformance test packet, protected by disabled flag gfe2_reloadable_flag_quic_reply_to_old_android_conformance_test PiperOrigin-RevId: 264248926 Change-Id: I18672295da33f039f1087ad62174c598700bc47b
diff --git a/quic/core/quic_dispatcher.cc b/quic/core/quic_dispatcher.cc index cb81f05..4d7b70e 100644 --- a/quic/core/quic_dispatcher.cc +++ b/quic/core/quic_dispatcher.cc
@@ -533,6 +533,37 @@ // set. Since this may be a client continuing a connection we lost track of // via server restart, send a rejection to fast-fail the connection. if (!packet_info.version_flag) { + if (GetQuicReloadableFlag(quic_reply_to_old_android_conformance_test)) { + QUIC_RELOADABLE_FLAG_COUNT(quic_reply_to_old_android_conformance_test); + // The Android network conformance test contains a UDP test that sends a + // 12-byte packet with the following format: + // - 0x0c (public flags: 8-byte connection ID, 1-byte packet number) + // - randomized 8-byte connection ID + // - 0x01 (1-byte packet number) + // - 0x00 (private flags) + // - 0x07 (PING frame). + // That packet is invalid and we would normally drop it but in order to + // unblock this conformance testing we have the following workaround that + // will be removed once the fixed test is deployed. + // TODO(b/139691956) Remove this workaround once fixed test is deployed. + if (packet_info.packet.length() == 12 && + packet_info.packet.data()[0] == 0x0c && + packet_info.packet.data()[9] == 0x01 && + packet_info.packet.data()[10] == 0x00 && + packet_info.packet.data()[11] == 0x07) { + QUIC_DLOG(INFO) << "Received Android UDP network conformance test " + "packet with connection ID " + << packet_info.destination_connection_id; + // Respond with a public reset that the test will know how to parse + // then return kFateDrop to stop processing of this packet. + time_wait_list_manager()->SendPublicReset( + packet_info.self_address, packet_info.peer_address, + packet_info.destination_connection_id, + /*ietf_quic=*/false, GetPerPacketContext()); + return kFateDrop; + } + } + QUIC_DLOG(INFO) << "Packet without version arrived for unknown connection ID " << packet_info.destination_connection_id;
diff --git a/quic/core/quic_dispatcher_test.cc b/quic/core/quic_dispatcher_test.cc index b3f13d9..d589854 100644 --- a/quic/core/quic_dispatcher_test.cc +++ b/quic/core/quic_dispatcher_test.cc
@@ -1180,6 +1180,156 @@ destination_connection_id_bytes, sizeof(destination_connection_id_bytes)); } +TEST_F(QuicDispatcherTest, AndroidConformanceTestOld) { + // TODO(b/139691956) Remove this test once the workaround is removed. + // This test requires the workaround behind this flag to pass. + SetQuicReloadableFlag(quic_reply_to_old_android_conformance_test, true); + SavingWriter* saving_writer = new SavingWriter(); + // dispatcher_ takes ownership of saving_writer. + QuicDispatcherPeer::UseWriter(dispatcher_.get(), saving_writer); + + QuicTimeWaitListManager* time_wait_list_manager = new QuicTimeWaitListManager( + saving_writer, dispatcher_.get(), mock_helper_.GetClock(), + &mock_alarm_factory_); + // dispatcher_ takes ownership of time_wait_list_manager. + QuicDispatcherPeer::SetTimeWaitListManager(dispatcher_.get(), + time_wait_list_manager); + // clang-format off + static const unsigned char packet[] = { + // Android UDP network conformance test packet as it was before this change: + // https://android-review.googlesource.com/c/platform/cts/+/1104285 + 0x0c, // public flags: 8-byte connection ID, 1-byte packet number + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 8-byte connection ID + 0x01, // 1-byte packet number + 0x00, // private flags + 0x07, // PING frame + }; + // clang-format on + + QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet), + sizeof(packet), false); + std::unique_ptr<QuicReceivedPacket> received_packet( + ConstructReceivedPacket(encrypted, mock_helper_.GetClock()->Now())); + EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0); + + QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); + dispatcher_->ProcessPacket(server_address_, client_address, *received_packet); + ASSERT_EQ(1u, saving_writer->packets()->size()); + + // The Android UDP network conformance test directly checks that bytes 1-9 + // of the response match the connection ID that was sent. + static const char connection_id_bytes[] = {0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78}; + ASSERT_GE((*(saving_writer->packets()))[0]->length(), + 1u + sizeof(connection_id_bytes)); + test::CompareCharArraysWithHexError( + "response connection ID", &(*(saving_writer->packets()))[0]->data()[1], + sizeof(connection_id_bytes), connection_id_bytes, + sizeof(connection_id_bytes)); +} + +TEST_F(QuicDispatcherTest, AndroidConformanceTestNewWithWorkaround) { + // TODO(b/139691956) Remove this test once the workaround is removed. + // This test doesn't need the workaround but we make sure that it passes even + // when the flag is true, also see AndroidConformanceTest below. + SetQuicReloadableFlag(quic_reply_to_old_android_conformance_test, true); + SavingWriter* saving_writer = new SavingWriter(); + // dispatcher_ takes ownership of saving_writer. + QuicDispatcherPeer::UseWriter(dispatcher_.get(), saving_writer); + + QuicTimeWaitListManager* time_wait_list_manager = new QuicTimeWaitListManager( + saving_writer, dispatcher_.get(), mock_helper_.GetClock(), + &mock_alarm_factory_); + // dispatcher_ takes ownership of time_wait_list_manager. + QuicDispatcherPeer::SetTimeWaitListManager(dispatcher_.get(), + time_wait_list_manager); + // clang-format off + static const unsigned char packet[1200] = { + // Android UDP network conformance test packet as it was after this change: + // https://android-review.googlesource.com/c/platform/cts/+/1104285 + 0x0d, // public flags: version, 8-byte connection ID, 1-byte packet number + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 8-byte connection ID + 0xaa, 0xda, 0xca, 0xaa, // reserved-space version number + 0x01, // 1-byte packet number + 0x00, // private flags + 0x07, // PING frame + }; + // clang-format on + + QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet), + sizeof(packet), false); + std::unique_ptr<QuicReceivedPacket> received_packet( + ConstructReceivedPacket(encrypted, mock_helper_.GetClock()->Now())); + EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0); + + QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); + dispatcher_->ProcessPacket(server_address_, client_address, *received_packet); + ASSERT_EQ(1u, saving_writer->packets()->size()); + + // The Android UDP network conformance test directly checks that bytes 1-9 + // of the response match the connection ID that was sent. + static const char connection_id_bytes[] = {0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78}; + ASSERT_GE((*(saving_writer->packets()))[0]->length(), + 1u + sizeof(connection_id_bytes)); + test::CompareCharArraysWithHexError( + "response connection ID", &(*(saving_writer->packets()))[0]->data()[1], + sizeof(connection_id_bytes), connection_id_bytes, + sizeof(connection_id_bytes)); +} + +TEST_F(QuicDispatcherTest, AndroidConformanceTest) { + // WARNING: do not remove or modify this test without making sure that we + // still have adequate coverage for the Android conformance test. + + // Set the flag to false to make sure this test passes even when the + // workaround is disabled. + SetQuicReloadableFlag(quic_reply_to_old_android_conformance_test, false); + SavingWriter* saving_writer = new SavingWriter(); + // dispatcher_ takes ownership of saving_writer. + QuicDispatcherPeer::UseWriter(dispatcher_.get(), saving_writer); + + QuicTimeWaitListManager* time_wait_list_manager = new QuicTimeWaitListManager( + saving_writer, dispatcher_.get(), mock_helper_.GetClock(), + &mock_alarm_factory_); + // dispatcher_ takes ownership of time_wait_list_manager. + QuicDispatcherPeer::SetTimeWaitListManager(dispatcher_.get(), + time_wait_list_manager); + // clang-format off + static const unsigned char packet[1200] = { + // Android UDP network conformance test packet as it was after this change: + // https://android-review.googlesource.com/c/platform/cts/+/1104285 + 0x0d, // public flags: version, 8-byte connection ID, 1-byte packet number + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, // 8-byte connection ID + 0xaa, 0xda, 0xca, 0xaa, // reserved-space version number + 0x01, // 1-byte packet number + 0x00, // private flags + 0x07, // PING frame + }; + // clang-format on + + QuicEncryptedPacket encrypted(reinterpret_cast<const char*>(packet), + sizeof(packet), false); + std::unique_ptr<QuicReceivedPacket> received_packet( + ConstructReceivedPacket(encrypted, mock_helper_.GetClock()->Now())); + EXPECT_CALL(*dispatcher_, CreateQuicSession(_, _, _, _)).Times(0); + + QuicSocketAddress client_address(QuicIpAddress::Loopback4(), 1); + dispatcher_->ProcessPacket(server_address_, client_address, *received_packet); + ASSERT_EQ(1u, saving_writer->packets()->size()); + + // The Android UDP network conformance test directly checks that bytes 1-9 + // of the response match the connection ID that was sent. + static const char connection_id_bytes[] = {0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78}; + ASSERT_GE((*(saving_writer->packets()))[0]->length(), + 1u + sizeof(connection_id_bytes)); + test::CompareCharArraysWithHexError( + "response connection ID", &(*(saving_writer->packets()))[0]->data()[1], + sizeof(connection_id_bytes), connection_id_bytes, + sizeof(connection_id_bytes)); +} + // Verify the stopgap test: Packets with truncated connection IDs should be // dropped. class QuicDispatcherTestStrayPacketConnectionId : public QuicDispatcherTest {};