diff --git a/quic/api/QuicPacketScheduler.cpp b/quic/api/QuicPacketScheduler.cpp index 280da23bc..889260e06 100644 --- a/quic/api/QuicPacketScheduler.cpp +++ b/quic/api/QuicPacketScheduler.cpp @@ -658,4 +658,83 @@ SchedulingResult CloningScheduler::scheduleFramesForPacket( std::string CloningScheduler::name() const { return name_; } + +D6DProbeScheduler::D6DProbeScheduler( + QuicConnectionStateBase& conn, + std::string name, + uint64_t cipherOverhead, + uint32_t probeSize) + : conn_(conn), + name_(std::move(name)), + cipherOverhead_(cipherOverhead), + probeSize_(probeSize) {} + +/** + * This scheduler always has data since all it does is send PING with PADDINGs + */ +bool D6DProbeScheduler::hasData() const { + return !probeSent_; +} + +/** + * D6DProbeScheduler ignores writableBytes because it does not respect + * congestion control. The reasons it doesn't are that + * - d6d probes are occasional burst of bytes in a single packet + * - no rtx needed when probe lost + */ +SchedulingResult D6DProbeScheduler::scheduleFramesForPacket( + PacketBuilderInterface&& builder, + uint32_t /* writableBytes */) { + builder.encodePacketHeader(); + int res = writeFrame(PingFrame(), builder); + CHECK_GT(res, 0) << __func__ << " " + << "failed to write ping frame" + << "remainingBytes: " << builder.remainingSpaceInPkt(); + CHECK(builder.canBuildPacket()) << __func__ << " " + << "inner builder cannot build packet"; + + auto pingOnlyPacket = std::move(builder).buildPacket(); + + std::unique_ptr sizeEnforcedBuilder; + if (conn_.transportSettings.dataPathType == DataPathType::ChainedMemory) { + sizeEnforcedBuilder = std::make_unique( + std::move(pingOnlyPacket), probeSize_, cipherOverhead_); + } else { + CHECK(conn_.bufAccessor && conn_.bufAccessor->ownsBuffer()); + sizeEnforcedBuilder = std::make_unique( + *conn_.bufAccessor, + std::move(pingOnlyPacket), + probeSize_, + cipherOverhead_); + } + CHECK(sizeEnforcedBuilder->canBuildPacket()) + << __func__ << " " + << "sizeEnforcedBuilder cannot build packet"; + + auto resultPacket = std::move(*sizeEnforcedBuilder).buildPacket(); + + auto resultPacketSize = resultPacket.header->computeChainDataLength() + + resultPacket.body->computeChainDataLength() + cipherOverhead_; + CHECK_EQ(resultPacketSize, probeSize_) + << __func__ << " " + << "result packet does not have enforced size," + << " expecting: " << probeSize_ << " getting: " << resultPacketSize; + + VLOG_IF(4, conn_.d6d.lastProbe.has_value()) + << __func__ << " " + << "invalidating old non-acked d6d probe," + << " seq: " << conn_.d6d.lastProbe->packetNum + << " packet size: " << conn_.d6d.lastProbe->packetSize; + + conn_.d6d.lastProbe = QuicConnectionStateBase::D6DProbePacket( + resultPacket.packet.header.getPacketSequenceNum(), probeSize_); + + probeSent_ = true; + return SchedulingResult(folly::none, std::move(resultPacket)); +} + +std::string D6DProbeScheduler::name() const { + return name_; +} + } // namespace quic diff --git a/quic/api/QuicPacketScheduler.h b/quic/api/QuicPacketScheduler.h index cae2a644d..565d5fb86 100644 --- a/quic/api/QuicPacketScheduler.h +++ b/quic/api/QuicPacketScheduler.h @@ -407,5 +407,33 @@ class CloningScheduler : public QuicPacketScheduler { std::string name_; uint64_t cipherOverhead_; }; + +/** + * This is the packet scheduler for D6D probe packets. It only schedule a PING + * frame followed by many PADDING frames, forming a probeSize-sized packet. + */ +class D6DProbeScheduler : public QuicPacketScheduler { + public: + D6DProbeScheduler( + QuicConnectionStateBase& conn, + std::string name, + uint64_t cipherOverhead, + uint32_t probSize); + + FOLLY_NODISCARD bool hasData() const override; + + SchedulingResult scheduleFramesForPacket( + PacketBuilderInterface&& builder, + uint32_t writableBytes) override; + + FOLLY_NODISCARD std::string name() const override; + + private: + QuicConnectionStateBase& conn_; + std::string name_; + uint64_t cipherOverhead_; + uint32_t probeSize_; + bool probeSent_{false}; +}; } // namespace quic #include diff --git a/quic/api/test/QuicPacketSchedulerTest.cpp b/quic/api/test/QuicPacketSchedulerTest.cpp index 2e2c57a78..03eb832aa 100644 --- a/quic/api/test/QuicPacketSchedulerTest.cpp +++ b/quic/api/test/QuicPacketSchedulerTest.cpp @@ -24,6 +24,8 @@ using namespace quic; using namespace testing; +enum PacketBuilderType { Regular, Inplace }; + namespace { PacketNum addInitialOutstandingPacket(QuicConnectionStateBase& conn) { @@ -79,7 +81,7 @@ PacketNum addOutstandingPacket(QuicConnectionStateBase& conn) { namespace quic { namespace test { -class QuicPacketSchedulerTest : public Test { +class QuicPacketSchedulerTest : public TestWithParam { public: QuicVersion version{QuicVersion::MVFST}; }; @@ -462,6 +464,51 @@ TEST_F(QuicPacketSchedulerTest, CloningSchedulerTest) { EXPECT_EQ(packetNum, result.packetEvent->packetNumber); } +TEST_P(QuicPacketSchedulerTest, D6DProbeSchedulerTest) { + QuicClientConnectionState conn( + FizzClientQuicHandshakeContext::Builder().build()); + uint64_t cipherOverhead = 2; + uint32_t probeSize = 1450; + auto connId = getTestConnectionId(); + D6DProbeScheduler d6dProbeScheduler( + conn, "d6d probe", cipherOverhead, probeSize); + EXPECT_TRUE(d6dProbeScheduler.hasData()); + + ShortHeader shortHeader( + ProtectionType::KeyPhaseZero, + connId, + getNextPacketNum(conn, PacketNumberSpace::AppData)); + auto param = GetParam(); + size_t packetSize = 0; + if (param == PacketBuilderType::Regular) { + RegularQuicPacketBuilder builder( + conn.udpSendPacketLen, + std::move(shortHeader), + conn.ackStates.appDataAckState.largestAckedByPeer.value_or(0)); + auto result = d6dProbeScheduler.scheduleFramesForPacket( + std::move(builder), kDefaultUDPSendPacketLen); + ASSERT_TRUE(result.packet.has_value()); + packetSize = result.packet->header->computeChainDataLength() + + result.packet->body->computeChainDataLength() + cipherOverhead; + } else { + // Just enough to build the probe + auto simpleBufAccessor = std::make_unique(probeSize); + InplaceQuicPacketBuilder builder( + *simpleBufAccessor, + conn.udpSendPacketLen, + std::move(shortHeader), + conn.ackStates.appDataAckState.largestAckedByPeer.value_or(0)); + auto result = d6dProbeScheduler.scheduleFramesForPacket( + std::move(builder), kDefaultUDPSendPacketLen); + ASSERT_TRUE(result.packet.has_value()); + packetSize = result.packet->header->computeChainDataLength() + + result.packet->body->computeChainDataLength() + cipherOverhead; + } + + EXPECT_FALSE(d6dProbeScheduler.hasData()); + EXPECT_EQ(packetSize, probeSize); +} + TEST_F(QuicPacketSchedulerTest, WriteOnlyOutstandingPacketsTest) { QuicClientConnectionState conn( FizzClientQuicHandshakeContext::Builder().build()); @@ -1387,5 +1434,10 @@ TEST_F( EXPECT_EQ(buf->length(), 0); } +INSTANTIATE_TEST_CASE_P( + QuicPacketSchedulerTests, + QuicPacketSchedulerTest, + Values(PacketBuilderType::Regular, PacketBuilderType::Inplace)); + } // namespace test } // namespace quic