1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-07-30 14:43:05 +03:00

Accommodate old MVFST clients when attempting to intiate key updates

Summary: Some old MVFST clients did not support key updates. This change prevents the server from initiating key updates to clients using the MVFST quic version until the client has initiated a key update. This ensures that key updates are triggered for clients that support them.

Reviewed By: mjoras

Differential Revision: D75022528

fbshipit-source-id: 7af2d65becbfefee347cbe98ccdfc01323cd153c
This commit is contained in:
Joseph Beshay
2025-05-20 14:04:58 -07:00
committed by Facebook GitHub Bot
parent c8cf18752b
commit 371f19df8e
2 changed files with 126 additions and 0 deletions

View File

@ -2228,6 +2228,10 @@ folly::Expected<folly::Unit, QuicError> maybeHandleIncomingKeyUpdate(
} }
conn.readCodec->setNextOneRttReadCipher( conn.readCodec->setNextOneRttReadCipher(
std::move(nextOneRttReadCipherResult.value())); std::move(nextOneRttReadCipherResult.value()));
// The peer has initiated a key update. We should use the regular key
// update interval if we are initiating key updates.
conn.transportSettings.firstKeyUpdatePacketCount.reset();
} }
return folly::unit; return folly::unit;
} }
@ -2235,6 +2239,14 @@ folly::Expected<folly::Unit, QuicError> maybeHandleIncomingKeyUpdate(
folly::Expected<folly::Unit, QuicError> maybeInitiateKeyUpdate( folly::Expected<folly::Unit, QuicError> maybeInitiateKeyUpdate(
QuicConnectionStateBase& conn) { QuicConnectionStateBase& conn) {
if (conn.transportSettings.initiateKeyUpdate) { if (conn.transportSettings.initiateKeyUpdate) {
if (conn.nodeType == QuicNodeType::Server && conn.version.has_value() &&
conn.version.value() == QuicVersion::MVFST &&
conn.transportSettings.firstKeyUpdatePacketCount.has_value()) {
// Some old versions of MVFST did not support key updates.
// So as the server, do not attempt to initiate key updates if the client
// hasn't initiated one yet.
return folly::unit;
}
auto packetsBeforeNextUpdate = auto packetsBeforeNextUpdate =
conn.transportSettings.firstKeyUpdatePacketCount conn.transportSettings.firstKeyUpdatePacketCount
? conn.transportSettings.firstKeyUpdatePacketCount.value() ? conn.transportSettings.firstKeyUpdatePacketCount.value()
@ -2244,6 +2256,9 @@ folly::Expected<folly::Unit, QuicError> maybeInitiateKeyUpdate(
conn.readCodec->canInitiateKeyUpdate()) { conn.readCodec->canInitiateKeyUpdate()) {
QUIC_STATS(conn.statsCallback, onKeyUpdateAttemptInitiated); QUIC_STATS(conn.statsCallback, onKeyUpdateAttemptInitiated);
conn.readCodec->advanceOneRttReadPhase(); conn.readCodec->advanceOneRttReadPhase();
//  We have initiated a key update. We should use the regular key
// update from now on.
conn.transportSettings.firstKeyUpdatePacketCount.reset(); conn.transportSettings.firstKeyUpdatePacketCount.reset();
auto nextOneRttWriteCipherResult = auto nextOneRttWriteCipherResult =

View File

@ -1450,6 +1450,13 @@ TYPED_TEST(QuicTypedTransportAfterStartTest, InitiateKeyUpdateFailure) {
* Initiate the first key update - Successful attempt * Initiate the first key update - Successful attempt
*/ */
TYPED_TEST(QuicTypedTransportAfterStartTest, InitiateFirstKeyUpdateSuccess) { TYPED_TEST(QuicTypedTransportAfterStartTest, InitiateFirstKeyUpdateSuccess) {
// Use QUIC_V1 for the server since MVFST has special handling to accommodate
// older client versions without key update support. That behavior is covered
// in another test
if (this->getConn().nodeType == QuicNodeType::Server) {
this->getNonConstConn().version = QuicVersion::QUIC_V1;
}
ASSERT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseZero); ASSERT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseZero);
auto numberOfWrittenPacketsInPhase = auto numberOfWrittenPacketsInPhase =
this->getConn().oneRttWritePacketsSentInCurrentPhase; this->getConn().oneRttWritePacketsSentInCurrentPhase;
@ -1523,6 +1530,110 @@ TYPED_TEST(QuicTypedTransportAfterStartTest, InitiateFirstKeyUpdateSuccess) {
this->destroyTransport(); this->destroyTransport();
} }
/**
* As a server, do not initiate key updates for QuicVersion::MVFST unless the
* client has performed a key update.
* (Old versions of MVFST did not support key updates)
*/
TYPED_TEST(
QuicTypedTransportAfterStartTest,
ServerInitiateKeyUpdateForMvfstClient) {
if (this->getConn().nodeType != QuicNodeType::Server) {
// This test is for the server behavior only
return;
}
ASSERT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseZero);
this->getNonConstConn().version = QuicVersion::MVFST;
auto numberOfWrittenPacketsInPhase =
this->getConn().oneRttWritePacketsSentInCurrentPhase;
auto streamId = this->getTransport()->createBidirectionalStream().value();
{
// Send and receive a packet in the current phase
this->getTransport()->writeChain(
streamId, IOBuf::copyBuffer("hello"), false);
this->loopForWrites();
EXPECT_EQ(
this->getConn().oneRttWritePacketsSentInCurrentPhase,
++numberOfWrittenPacketsInPhase);
this->deliverPacket(this->buildPeerPacketWithStreamData(
streamId, IOBuf::copyBuffer("hello2"), ProtectionType::KeyPhaseZero));
// Both read and writer ciphers should be in phase zero.
EXPECT_EQ(
this->getConn().readCodec->getCurrentOneRttReadPhase(),
ProtectionType::KeyPhaseZero);
EXPECT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseZero);
}
{
// First key update is due but it shouldn't trigger one because connection
// is for QuicVersion::MVFST and the peer hasn't initiated a key update yet
QuicConnectionStateBase& conn = this->getNonConstConn();
conn.transportSettings.initiateKeyUpdate = true;
// Trigger this update as a first update
conn.transportSettings.firstKeyUpdatePacketCount =
numberOfWrittenPacketsInPhase;
// Periodic interval is high enough not to be triggered
conn.transportSettings.keyUpdatePacketCountInterval =
kDefaultKeyUpdatePacketCountInterval;
// A key update should be triggered after this write is completed.
this->getTransport()->writeChain(
streamId, IOBuf::copyBuffer("hello3"), false);
this->loopForWrites();
EXPECT_EQ(
this->getConn().oneRttWritePacketsSentInCurrentPhase,
++numberOfWrittenPacketsInPhase);
// Both read and writer ciphers should still be at Phase Zero
EXPECT_EQ(
this->getConn().readCodec->getCurrentOneRttReadPhase(),
ProtectionType::KeyPhaseZero);
EXPECT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseZero);
}
// Clear firstKeyUpdatePacketCount to indicate that a key update was performed
// by the peer.
this->getNonConstConn().transportSettings.firstKeyUpdatePacketCount.reset();
{
// Now that the peer had initiated a key update, the server can trigger one
// too.
QuicConnectionStateBase& conn = this->getNonConstConn();
conn.transportSettings.initiateKeyUpdate = true;
// Indicate that a regular update is due
conn.transportSettings.keyUpdatePacketCountInterval =
numberOfWrittenPacketsInPhase;
// A key update should be triggered after this write is completed.
this->getTransport()->writeChain(
streamId, IOBuf::copyBuffer("hello3"), false);
this->loopForWrites();
numberOfWrittenPacketsInPhase = 0;
EXPECT_EQ(
this->getConn().oneRttWritePacketsSentInCurrentPhase,
numberOfWrittenPacketsInPhase);
// Read and writer ciphers should advance to Key Phase One
EXPECT_EQ(
this->getConn().readCodec->getCurrentOneRttReadPhase(),
ProtectionType::KeyPhaseOne);
EXPECT_EQ(this->getConn().oneRttWritePhase, ProtectionType::KeyPhaseOne);
}
this->getNonConstConn().outstandings.reset();
this->destroyTransport();
}
template <typename T> template <typename T>
struct AckEventMatcherBuilder { struct AckEventMatcherBuilder {
using Builder = AckEventMatcherBuilder; using Builder = AckEventMatcherBuilder;