mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-08-08 09:42:06 +03:00
Change Implementation of WritableBytesLimit
Summary: - updating usage of WritableBytesLimit Reviewed By: mjoras Differential Revision: D33079816 fbshipit-source-id: 1854f40a7b00526afb2167764aeddf55edb1771f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
9fee9edcc9
commit
c8bf098e5d
@@ -287,6 +287,7 @@ enum class QuicVersion : uint32_t {
|
|||||||
MVFST_INVALID = 0xfaceb00f,
|
MVFST_INVALID = 0xfaceb00f,
|
||||||
MVFST_EXPERIMENTAL2 = 0xfaceb011, // Experimental alias for MVFST
|
MVFST_EXPERIMENTAL2 = 0xfaceb011, // Experimental alias for MVFST
|
||||||
MVFST_ALIAS2 = 0xfaceb012,
|
MVFST_ALIAS2 = 0xfaceb012,
|
||||||
|
MVFST_EXPERIMENTAL3 = 0xfaceb013, // Experimental alias for MVFST
|
||||||
};
|
};
|
||||||
|
|
||||||
using QuicVersionType = std::underlying_type<QuicVersion>::type;
|
using QuicVersionType = std::underlying_type<QuicVersion>::type;
|
||||||
@@ -395,10 +396,10 @@ constexpr uint64_t kDefaultMaxCwndInMss = 2000;
|
|||||||
// Max cwnd limit for perf test purpose
|
// Max cwnd limit for perf test purpose
|
||||||
constexpr uint64_t kLargeMaxCwndInMss = 860000;
|
constexpr uint64_t kLargeMaxCwndInMss = 860000;
|
||||||
|
|
||||||
// When server receives early data attempt without valid source address token,
|
// When server receives initial data without valid source address token,
|
||||||
// server will limit bytes in flight to avoid amplification attack until CFIN
|
// server will limit bytes in flight to avoid amplification attack until CFIN
|
||||||
// is received which proves sender owns the address.
|
// is received which proves sender owns the address.
|
||||||
constexpr uint64_t kLimitedCwndInMss = 3;
|
constexpr uint64_t kLimitedCwndInMss = 5;
|
||||||
|
|
||||||
/* Hybrid slow start: */
|
/* Hybrid slow start: */
|
||||||
// The first kAckSampling Acks within a RTT round will be used to sample delays
|
// The first kAckSampling Acks within a RTT round will be used to sample delays
|
||||||
|
@@ -915,6 +915,7 @@ uint64_t congestionControlWritableBytes(const QuicConnectionStateBase& conn) {
|
|||||||
conn.lossState.srtt == 0us ? kDefaultInitialRtt : conn.lossState.srtt);
|
conn.lossState.srtt == 0us ? kDefaultInitialRtt : conn.lossState.srtt);
|
||||||
} else if (conn.writableBytesLimit) {
|
} else if (conn.writableBytesLimit) {
|
||||||
if (*conn.writableBytesLimit <= conn.lossState.totalBytesSent) {
|
if (*conn.writableBytesLimit <= conn.lossState.totalBytesSent) {
|
||||||
|
QUIC_STATS(conn.statsCallback, onConnectionWritableBytesLimited);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
writableBytes = *conn.writableBytesLimit - conn.lossState.totalBytesSent;
|
writableBytes = *conn.writableBytesLimit - conn.lossState.totalBytesSent;
|
||||||
@@ -1468,7 +1469,7 @@ uint64_t writeProbingDataToSocket(
|
|||||||
builder,
|
builder,
|
||||||
pnSpace,
|
pnSpace,
|
||||||
cloningScheduler,
|
cloningScheduler,
|
||||||
unlimitedWritableBytes,
|
congestionControlWritableBytes,
|
||||||
probesToSend,
|
probesToSend,
|
||||||
aead,
|
aead,
|
||||||
headerCipher,
|
headerCipher,
|
||||||
@@ -1492,7 +1493,7 @@ uint64_t writeProbingDataToSocket(
|
|||||||
builder,
|
builder,
|
||||||
pnSpace,
|
pnSpace,
|
||||||
pingScheduler,
|
pingScheduler,
|
||||||
unlimitedWritableBytes,
|
congestionControlWritableBytes,
|
||||||
probesToSend - written,
|
probesToSend - written,
|
||||||
aead,
|
aead,
|
||||||
headerCipher,
|
headerCipher,
|
||||||
|
@@ -2970,6 +2970,8 @@ TEST_F(QuicTransportFunctionsTest, WriteProbingNewData) {
|
|||||||
auto currentPacketSeqNum = conn->ackStates.appDataAckState.nextPacketNum;
|
auto currentPacketSeqNum = conn->ackStates.appDataAckState.nextPacketNum;
|
||||||
auto mockCongestionController =
|
auto mockCongestionController =
|
||||||
std::make_unique<NiceMock<MockCongestionController>>();
|
std::make_unique<NiceMock<MockCongestionController>>();
|
||||||
|
EXPECT_CALL(*mockCongestionController, getWritableBytes())
|
||||||
|
.WillRepeatedly(Return(2000));
|
||||||
auto rawCongestionController = mockCongestionController.get();
|
auto rawCongestionController = mockCongestionController.get();
|
||||||
conn->congestionController = std::move(mockCongestionController);
|
conn->congestionController = std::move(mockCongestionController);
|
||||||
EventBase evb;
|
EventBase evb;
|
||||||
@@ -3059,6 +3061,8 @@ TEST_F(QuicTransportFunctionsTest, WriteProbingCryptoData) {
|
|||||||
// Replace real congestionController with MockCongestionController:
|
// Replace real congestionController with MockCongestionController:
|
||||||
auto mockCongestionController =
|
auto mockCongestionController =
|
||||||
std::make_unique<NiceMock<MockCongestionController>>();
|
std::make_unique<NiceMock<MockCongestionController>>();
|
||||||
|
EXPECT_CALL(*mockCongestionController, getWritableBytes())
|
||||||
|
.WillRepeatedly(Return(2000));
|
||||||
auto rawCongestionController = mockCongestionController.get();
|
auto rawCongestionController = mockCongestionController.get();
|
||||||
conn.congestionController = std::move(mockCongestionController);
|
conn.congestionController = std::move(mockCongestionController);
|
||||||
EventBase evb;
|
EventBase evb;
|
||||||
|
@@ -455,6 +455,8 @@ std::string toString(QuicVersion version) {
|
|||||||
return "MVFST_EXPERIMENTAL2";
|
return "MVFST_EXPERIMENTAL2";
|
||||||
case QuicVersion::MVFST_ALIAS2:
|
case QuicVersion::MVFST_ALIAS2:
|
||||||
return "MVFST_ALIAS2";
|
return "MVFST_ALIAS2";
|
||||||
|
case QuicVersion::MVFST_EXPERIMENTAL3:
|
||||||
|
return "MVFST_EXPERIMENTAL3";
|
||||||
}
|
}
|
||||||
LOG(WARNING) << "toString has unhandled version type";
|
LOG(WARNING) << "toString has unhandled version type";
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
|
@@ -403,36 +403,50 @@ class FakeServerHandshake : public FizzServerHandshake {
|
|||||||
|
|
||||||
MOCK_METHOD(void, writeNewSessionTicket, (const AppToken&));
|
MOCK_METHOD(void, writeNewSessionTicket, (const AppToken&));
|
||||||
|
|
||||||
void doHandshake(std::unique_ptr<folly::IOBuf> data, EncryptionLevel)
|
void onClientHello(bool chloWithCert = false) {
|
||||||
override {
|
|
||||||
folly::IOBufEqualTo eq;
|
|
||||||
auto chlo = folly::IOBuf::copyBuffer("CHLO");
|
|
||||||
auto clientFinished = folly::IOBuf::copyBuffer("FINISHED");
|
|
||||||
if (eq(data, chlo)) {
|
|
||||||
if (chloSync_) {
|
|
||||||
// Do NOT invoke onCryptoEventAvailable callback
|
// Do NOT invoke onCryptoEventAvailable callback
|
||||||
// Fall through and let the ServerStateMachine to process the event
|
// Fall through and let the ServerStateMachine to process the event
|
||||||
writeDataToQuicStream(
|
writeDataToQuicStream(
|
||||||
*getCryptoStream(*conn_.cryptoState, EncryptionLevel::Initial),
|
*getCryptoStream(*conn_.cryptoState, EncryptionLevel::Initial),
|
||||||
folly::IOBuf::copyBuffer("SHLO"));
|
folly::IOBuf::copyBuffer("SHLO"));
|
||||||
|
if (chloWithCert) {
|
||||||
|
/* write 4000 bytes of data to the handshake crypto stream */
|
||||||
|
writeDataToQuicStream(
|
||||||
|
*getCryptoStream(*conn_.cryptoState, EncryptionLevel::Handshake),
|
||||||
|
folly::IOBuf::copyBuffer(std::string(4000, '.')));
|
||||||
|
}
|
||||||
|
|
||||||
if (allowZeroRttKeys_) {
|
if (allowZeroRttKeys_) {
|
||||||
validateAndUpdateSourceToken(conn_, sourceAddrs_);
|
validateAndUpdateSourceToken(conn_, sourceAddrs_);
|
||||||
phase_ = Phase::KeysDerived;
|
phase_ = Phase::KeysDerived;
|
||||||
setEarlyKeys();
|
setEarlyKeys();
|
||||||
}
|
}
|
||||||
setHandshakeKeys();
|
setHandshakeKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onClientFin() {
|
||||||
|
// Do NOT invoke onCryptoEventAvailable callback
|
||||||
|
// Fall through and let the ServerStateMachine to process the event
|
||||||
|
setOneRttKeys();
|
||||||
|
phase_ = Phase::Established;
|
||||||
|
handshakeDone_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void doHandshake(std::unique_ptr<folly::IOBuf> data, EncryptionLevel)
|
||||||
|
override {
|
||||||
|
folly::IOBufEqualTo eq;
|
||||||
|
auto chlo = folly::IOBuf::copyBuffer("CHLO");
|
||||||
|
auto chloWithCert = folly::IOBuf::copyBuffer("CHLO_CERT");
|
||||||
|
auto clientFinished = folly::IOBuf::copyBuffer("FINISHED");
|
||||||
|
bool sendHandshakeBytes = false;
|
||||||
|
|
||||||
|
if (eq(data, chlo) || (sendHandshakeBytes = eq(data, chloWithCert))) {
|
||||||
|
if (chloSync_) {
|
||||||
|
onClientHello(sendHandshakeBytes);
|
||||||
} else {
|
} else {
|
||||||
// Asynchronously schedule the callback
|
// Asynchronously schedule the callback
|
||||||
executor_->add([&] {
|
executor_->add([sendHandshakeBytes, this] {
|
||||||
writeDataToQuicStream(
|
onClientHello(sendHandshakeBytes);
|
||||||
*getCryptoStream(*conn_.cryptoState, EncryptionLevel::Initial),
|
|
||||||
folly::IOBuf::copyBuffer("SHLO"));
|
|
||||||
if (allowZeroRttKeys_) {
|
|
||||||
validateAndUpdateSourceToken(conn_, sourceAddrs_);
|
|
||||||
phase_ = Phase::KeysDerived;
|
|
||||||
setEarlyKeys();
|
|
||||||
}
|
|
||||||
setHandshakeKeys();
|
|
||||||
if (callback_) {
|
if (callback_) {
|
||||||
callback_->onCryptoEventAvailable();
|
callback_->onCryptoEventAvailable();
|
||||||
}
|
}
|
||||||
@@ -440,17 +454,11 @@ class FakeServerHandshake : public FizzServerHandshake {
|
|||||||
}
|
}
|
||||||
} else if (eq(data, clientFinished)) {
|
} else if (eq(data, clientFinished)) {
|
||||||
if (cfinSync_) {
|
if (cfinSync_) {
|
||||||
// Do NOT invoke onCryptoEventAvailable callback
|
onClientFin();
|
||||||
// Fall through and let the ServerStateMachine to process the event
|
|
||||||
setOneRttKeys();
|
|
||||||
phase_ = Phase::Established;
|
|
||||||
handshakeDone_ = true;
|
|
||||||
} else {
|
} else {
|
||||||
// Asynchronously schedule the callback
|
// Asynchronously schedule the callback
|
||||||
executor_->add([&] {
|
executor_->add([&] {
|
||||||
setOneRttKeys();
|
onClientFin();
|
||||||
phase_ = Phase::Established;
|
|
||||||
handshakeDone_ = true;
|
|
||||||
if (callback_) {
|
if (callback_) {
|
||||||
callback_->onCryptoEventAvailable();
|
callback_->onCryptoEventAvailable();
|
||||||
}
|
}
|
||||||
|
@@ -399,6 +399,7 @@ class QuicServer : public QuicServerWorker::WorkerCallback,
|
|||||||
{QuicVersion::MVFST,
|
{QuicVersion::MVFST,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL,
|
QuicVersion::MVFST_EXPERIMENTAL,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL2,
|
QuicVersion::MVFST_EXPERIMENTAL2,
|
||||||
|
QuicVersion::MVFST_EXPERIMENTAL3,
|
||||||
QuicVersion::MVFST_ALIAS,
|
QuicVersion::MVFST_ALIAS,
|
||||||
QuicVersion::QUIC_V1,
|
QuicVersion::QUIC_V1,
|
||||||
QuicVersion::QUIC_DRAFT,
|
QuicVersion::QUIC_DRAFT,
|
||||||
|
@@ -676,6 +676,13 @@ void QuicServerTransport::onTransportKnobs(Buf knobBlob) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuicServerTransport::verifiedClientAddress() {
|
||||||
|
if (serverConn_) {
|
||||||
|
serverConn_->isClientAddrVerified = true;
|
||||||
|
conn_->writableBytesLimit = folly::none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void QuicServerTransport::registerAllTransportKnobParamHandlers() {
|
void QuicServerTransport::registerAllTransportKnobParamHandlers() {
|
||||||
registerTransportKnobParamHandler(
|
registerTransportKnobParamHandler(
|
||||||
static_cast<uint64_t>(
|
static_cast<uint64_t>(
|
||||||
|
@@ -125,6 +125,8 @@ class QuicServerTransport
|
|||||||
|
|
||||||
void setClientChosenDestConnectionId(const ConnectionId& serverCid);
|
void setClientChosenDestConnectionId(const ConnectionId& serverCid);
|
||||||
|
|
||||||
|
void verifiedClientAddress();
|
||||||
|
|
||||||
// From QuicTransportBase
|
// From QuicTransportBase
|
||||||
void onReadData(
|
void onReadData(
|
||||||
const folly::SocketAddress& peer,
|
const folly::SocketAddress& peer,
|
||||||
|
@@ -696,6 +696,9 @@ void QuicServerWorker::dispatchPacketData(
|
|||||||
trans->setHandshakeFinishedCallback(this);
|
trans->setHandshakeFinishedCallback(this);
|
||||||
trans->setSupportedVersions(supportedVersions_);
|
trans->setSupportedVersions(supportedVersions_);
|
||||||
trans->setOriginalPeerAddress(client);
|
trans->setOriginalPeerAddress(client);
|
||||||
|
if (isValidNewToken) {
|
||||||
|
trans->verifiedClientAddress();
|
||||||
|
}
|
||||||
#ifdef CCP_ENABLED
|
#ifdef CCP_ENABLED
|
||||||
trans->setCcpDatapath(getCcpReader()->getDatapath());
|
trans->setCcpDatapath(getCcpReader()->getDatapath());
|
||||||
#endif
|
#endif
|
||||||
|
@@ -90,7 +90,8 @@ void recoverOrResetCongestionAndRttState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setExperimentalSettings(QuicServerConnectionState& conn) {
|
void maybeSetExperimentalSettings(QuicServerConnectionState& conn) {
|
||||||
|
if (conn.version == QuicVersion::MVFST_EXPERIMENTAL) {
|
||||||
// MVFST_EXPERIMENTAL currently enables experimental congestion control
|
// MVFST_EXPERIMENTAL currently enables experimental congestion control
|
||||||
// and experimental pacer. (here and in the client transport)
|
// and experimental pacer. (here and in the client transport)
|
||||||
if (conn.congestionController) {
|
if (conn.congestionController) {
|
||||||
@@ -99,6 +100,9 @@ void setExperimentalSettings(QuicServerConnectionState& conn) {
|
|||||||
if (conn.pacer) {
|
if (conn.pacer) {
|
||||||
conn.pacer->setExperimental(true);
|
conn.pacer->setExperimental(true);
|
||||||
}
|
}
|
||||||
|
} else if (conn.version == QuicVersion::MVFST_EXPERIMENTAL3) {
|
||||||
|
conn.enableWritableBytesLimit = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -386,6 +390,7 @@ void updateHandshakeState(QuicServerConnectionState& conn) {
|
|||||||
conn.qLogger->addTransportStateUpdate(kDerivedOneRttReadCipher);
|
conn.qLogger->addTransportStateUpdate(kDerivedOneRttReadCipher);
|
||||||
}
|
}
|
||||||
// Clear limit because CFIN is received at this point
|
// Clear limit because CFIN is received at this point
|
||||||
|
conn.isClientAddrVerified = true;
|
||||||
conn.writableBytesLimit = folly::none;
|
conn.writableBytesLimit = folly::none;
|
||||||
conn.readCodec->setOneRttReadCipher(std::move(oneRttReadCipher));
|
conn.readCodec->setOneRttReadCipher(std::move(oneRttReadCipher));
|
||||||
}
|
}
|
||||||
@@ -478,6 +483,9 @@ void updateWritableByteLimitOnRecvPacket(QuicServerConnectionState& conn) {
|
|||||||
if (conn.writableBytesLimit) {
|
if (conn.writableBytesLimit) {
|
||||||
conn.writableBytesLimit = *conn.writableBytesLimit +
|
conn.writableBytesLimit = *conn.writableBytesLimit +
|
||||||
conn.transportSettings.limitedCwndInMss * conn.udpSendPacketLen;
|
conn.transportSettings.limitedCwndInMss * conn.udpSendPacketLen;
|
||||||
|
} else if (!conn.isClientAddrVerified && conn.enableWritableBytesLimit) {
|
||||||
|
conn.writableBytesLimit =
|
||||||
|
conn.transportSettings.limitedCwndInMss * conn.udpSendPacketLen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -895,9 +903,7 @@ void onServerReadDataFromOpen(
|
|||||||
"Invalid packet type", TransportErrorCode::PROTOCOL_VIOLATION);
|
"Invalid packet type", TransportErrorCode::PROTOCOL_VIOLATION);
|
||||||
}
|
}
|
||||||
conn.version = longHeader->getVersion();
|
conn.version = longHeader->getVersion();
|
||||||
if (conn.version == QuicVersion::MVFST_EXPERIMENTAL) {
|
maybeSetExperimentalSettings(conn);
|
||||||
setExperimentalSettings(conn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conn.peerAddress != readData.peer) {
|
if (conn.peerAddress != readData.peer) {
|
||||||
|
@@ -134,6 +134,13 @@ struct QuicServerConnectionState : public QuicConnectionStateBase {
|
|||||||
// Number of bytes the server has written during the handshake.
|
// Number of bytes the server has written during the handshake.
|
||||||
uint64_t numHandshakeBytesSent{0};
|
uint64_t numHandshakeBytesSent{0};
|
||||||
|
|
||||||
|
// Whether or not the client has verified their address (thru CFIN or
|
||||||
|
// NewToken).
|
||||||
|
bool isClientAddrVerified{false};
|
||||||
|
|
||||||
|
// Whether or not to enable WritableBytes limit
|
||||||
|
bool enableWritableBytesLimit{false};
|
||||||
|
|
||||||
#ifdef CCP_ENABLED
|
#ifdef CCP_ENABLED
|
||||||
// Pointer to struct that maintains state needed for interacting with libccp.
|
// Pointer to struct that maintains state needed for interacting with libccp.
|
||||||
// Once instance of this struct is created for each instance of
|
// Once instance of this struct is created for each instance of
|
||||||
@@ -158,6 +165,7 @@ struct QuicServerConnectionState : public QuicConnectionStateBase {
|
|||||||
{QuicVersion::MVFST,
|
{QuicVersion::MVFST,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL,
|
QuicVersion::MVFST_EXPERIMENTAL,
|
||||||
QuicVersion::MVFST_EXPERIMENTAL2,
|
QuicVersion::MVFST_EXPERIMENTAL2,
|
||||||
|
QuicVersion::MVFST_EXPERIMENTAL3,
|
||||||
QuicVersion::MVFST_ALIAS,
|
QuicVersion::MVFST_ALIAS,
|
||||||
QuicVersion::QUIC_V1,
|
QuicVersion::QUIC_V1,
|
||||||
QuicVersion::QUIC_DRAFT,
|
QuicVersion::QUIC_DRAFT,
|
||||||
|
@@ -3261,6 +3261,212 @@ TEST_F(QuicUnencryptedServerTransportTest, TestNoAckOnlyCryptoInitial) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
QuicUnencryptedServerTransportTest,
|
||||||
|
TestHandshakeNotWritableBytesLimited) {
|
||||||
|
/**
|
||||||
|
* Set the WritableBytes limit to 5x (~ 5 * 1200 = 6,000). This will be enough
|
||||||
|
* for the handshake to fit (1200 initial + 3000 handshake = 4,200 < 6,000).
|
||||||
|
*/
|
||||||
|
auto transportSettings = server->getTransportSettings();
|
||||||
|
transportSettings.limitedCwndInMss = 5;
|
||||||
|
server->setTransportSettings(transportSettings);
|
||||||
|
server->getNonConstConn().enableWritableBytesLimit = true;
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
|
|
||||||
|
recvClientHello(true, QuicVersion::MVFST, "CHLO_CERT");
|
||||||
|
|
||||||
|
EXPECT_GE(serverWrites.size(), 3);
|
||||||
|
|
||||||
|
AckStates ackStates;
|
||||||
|
|
||||||
|
auto clientCodec = makeClientEncryptedCodec(true);
|
||||||
|
bool hasCryptoInitialFrame = false;
|
||||||
|
bool hasCryptoHandshakeFrame = false;
|
||||||
|
bool hasAckFrame = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that we've written some cypto frames (initial, handshake packet
|
||||||
|
* spaces) and some acks.
|
||||||
|
*/
|
||||||
|
for (auto& write : serverWrites) {
|
||||||
|
auto packetQueue = bufToQueue(write->clone());
|
||||||
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
||||||
|
auto& regularPacket = *result.regularPacket();
|
||||||
|
// EXPECT_TRUE(regularPacket);
|
||||||
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
||||||
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
||||||
|
bool hasCryptoFrame = false;
|
||||||
|
for (auto& frame : regularPacket.frames) {
|
||||||
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
||||||
|
hasAckFrame |= frame.asReadAckFrame() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCryptoInitialFrame |=
|
||||||
|
(protectionType == ProtectionType::Initial && hasCryptoFrame);
|
||||||
|
hasCryptoHandshakeFrame |=
|
||||||
|
(protectionType == ProtectionType::Handshake && hasCryptoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(hasCryptoInitialFrame);
|
||||||
|
EXPECT_TRUE(hasCryptoHandshakeFrame);
|
||||||
|
// skipping ack-only initial should not kick in here since we also have crypto
|
||||||
|
// data to write.
|
||||||
|
EXPECT_TRUE(hasAckFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
QuicUnencryptedServerTransportTest,
|
||||||
|
TestHandshakeWritableBytesLimitedWithCFin) {
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited())
|
||||||
|
.Times(AtLeast(1));
|
||||||
|
/**
|
||||||
|
* Set the WritableBytes limit to 3x (~ 3 * 1200 = 3,600). This will not be
|
||||||
|
* enough for the handshake to fit (1200 initial + 4000 handshake = 5,200 >
|
||||||
|
* 3,600). We expect to be WritableBytes limited. After receiving an ack/cfin
|
||||||
|
* from the client, the limit should increase and we're now unblocked.
|
||||||
|
*/
|
||||||
|
auto transportSettings = server->getTransportSettings();
|
||||||
|
transportSettings.limitedCwndInMss = 3;
|
||||||
|
server->setTransportSettings(transportSettings);
|
||||||
|
server->getNonConstConn().enableWritableBytesLimit = true;
|
||||||
|
|
||||||
|
recvClientHello(true, QuicVersion::MVFST, "CHLO_CERT");
|
||||||
|
|
||||||
|
// basically the maximum we can write is three packets before we hit the limit
|
||||||
|
EXPECT_EQ(serverWrites.size(), 3);
|
||||||
|
|
||||||
|
AckStates ackStates;
|
||||||
|
|
||||||
|
auto clientCodec = makeClientEncryptedCodec(true);
|
||||||
|
bool hasCryptoInitialFrame, hasCryptoHandshakeFrame, hasAckFrame;
|
||||||
|
hasCryptoInitialFrame = hasCryptoHandshakeFrame = hasAckFrame = false;
|
||||||
|
|
||||||
|
for (auto& write : serverWrites) {
|
||||||
|
auto packetQueue = bufToQueue(write->clone());
|
||||||
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
||||||
|
auto& regularPacket = *result.regularPacket();
|
||||||
|
// EXPECT_TRUE(regularPacket);
|
||||||
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
||||||
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
||||||
|
bool hasCryptoFrame = false;
|
||||||
|
for (auto& frame : regularPacket.frames) {
|
||||||
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
||||||
|
hasAckFrame |= frame.asReadAckFrame() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCryptoInitialFrame |=
|
||||||
|
(protectionType == ProtectionType::Initial && hasCryptoFrame);
|
||||||
|
hasCryptoHandshakeFrame |=
|
||||||
|
(protectionType == ProtectionType::Handshake && hasCryptoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_TRUE(hasCryptoInitialFrame);
|
||||||
|
EXPECT_TRUE(hasCryptoHandshakeFrame);
|
||||||
|
EXPECT_TRUE(hasAckFrame);
|
||||||
|
/**
|
||||||
|
* Let's now send an ack/cfin to the server which will unblock and let us
|
||||||
|
* finish the handshake. The packets written by the server at this point are
|
||||||
|
* expected to have crypto data and acks only in the handshake pn space, not
|
||||||
|
* initial.
|
||||||
|
*/
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
|
serverWrites.clear();
|
||||||
|
recvClientFinished();
|
||||||
|
EXPECT_TRUE(server->getConn().isClientAddrVerified);
|
||||||
|
EXPECT_FALSE(server->getConn().writableBytesLimit);
|
||||||
|
EXPECT_GT(serverWrites.size(), 0);
|
||||||
|
|
||||||
|
hasCryptoInitialFrame = hasCryptoHandshakeFrame = hasAckFrame = false;
|
||||||
|
|
||||||
|
for (auto& write : serverWrites) {
|
||||||
|
auto packetQueue = bufToQueue(write->clone());
|
||||||
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
||||||
|
auto& regularPacket = *result.regularPacket();
|
||||||
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
||||||
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
||||||
|
bool hasCryptoFrame = false;
|
||||||
|
for (auto& frame : regularPacket.frames) {
|
||||||
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
||||||
|
hasAckFrame |= frame.asReadAckFrame() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCryptoHandshakeFrame |=
|
||||||
|
(protectionType == ProtectionType::Handshake && hasCryptoFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't expect crypto frame in initial pnspace since we're done
|
||||||
|
EXPECT_FALSE(hasCryptoInitialFrame);
|
||||||
|
EXPECT_TRUE(hasCryptoHandshakeFrame);
|
||||||
|
EXPECT_TRUE(hasAckFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(
|
||||||
|
QuicUnencryptedServerTransportTest,
|
||||||
|
TestHandshakeWritableBytesLimitedPartialAck) {
|
||||||
|
/**
|
||||||
|
* Set the WritableBytes limit to 3x (~ 3 * 1200 = 3,600). This will not be
|
||||||
|
* enough for the handshake to fit (1200 initial + 4000 handshake = 5,200 >
|
||||||
|
* 3,600). We expect to be WritableBytes limited. After receiving an ack
|
||||||
|
* from the client acking only the initial crypto data, the pto should fire
|
||||||
|
* immediately to resend the handshake crypto data.
|
||||||
|
*/
|
||||||
|
auto transportSettings = server->getTransportSettings();
|
||||||
|
transportSettings.limitedCwndInMss = 3;
|
||||||
|
server->setTransportSettings(transportSettings);
|
||||||
|
server->getNonConstConn().enableWritableBytesLimit = true;
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited())
|
||||||
|
.Times(AtLeast(1));
|
||||||
|
|
||||||
|
recvClientHello(true, QuicVersion::MVFST, "CHLO_CERT");
|
||||||
|
|
||||||
|
// basically the maximum we can write is three packets before we hit the limit
|
||||||
|
EXPECT_EQ(serverWrites.size(), 3);
|
||||||
|
|
||||||
|
AckStates ackStates;
|
||||||
|
|
||||||
|
auto clientCodec = makeClientEncryptedCodec(true);
|
||||||
|
|
||||||
|
for (auto& write : serverWrites) {
|
||||||
|
auto packetQueue = bufToQueue(write->clone());
|
||||||
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
||||||
|
EXPECT_TRUE(result.regularPacket());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let's now send an partial ack to the server, acking only the initial pn
|
||||||
|
* space, which will unblock and let us finish the handshake. Since we've
|
||||||
|
* already sent the handshake data, we expect a pto to fire immediately and
|
||||||
|
*/
|
||||||
|
|
||||||
|
serverWrites.clear();
|
||||||
|
auto nextPacketNum = clientNextInitialPacketNum++;
|
||||||
|
auto aead = getInitialCipher();
|
||||||
|
auto headerCipher = getInitialHeaderCipher();
|
||||||
|
AckBlocks acks;
|
||||||
|
auto start = getFirstOutstandingPacket(
|
||||||
|
server->getNonConstConn(), PacketNumberSpace::Initial)
|
||||||
|
->packet.header.getPacketSequenceNum();
|
||||||
|
auto end = getLastOutstandingPacket(
|
||||||
|
server->getNonConstConn(), PacketNumberSpace::Initial)
|
||||||
|
->packet.header.getPacketSequenceNum();
|
||||||
|
acks.insert(start, end);
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
|
deliverData(packetToBufCleartext(
|
||||||
|
createAckPacket(
|
||||||
|
server->getNonConstConn(),
|
||||||
|
nextPacketNum,
|
||||||
|
acks,
|
||||||
|
PacketNumberSpace::Initial,
|
||||||
|
aead.get()),
|
||||||
|
*aead,
|
||||||
|
*headerCipher,
|
||||||
|
nextPacketNum));
|
||||||
|
|
||||||
|
// The server is unblocked and should now be able to finish the handshake
|
||||||
|
EXPECT_GE(serverWrites.size(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(QuicUnencryptedServerTransportTest, TestCorruptedDstCidInitialTest) {
|
TEST_F(QuicUnencryptedServerTransportTest, TestCorruptedDstCidInitialTest) {
|
||||||
auto chlo = folly::IOBuf::copyBuffer("CHLO");
|
auto chlo = folly::IOBuf::copyBuffer("CHLO");
|
||||||
auto nextPacketNum = clientNextInitialPacketNum++;
|
auto nextPacketNum = clientNextInitialPacketNum++;
|
||||||
@@ -3387,6 +3593,7 @@ TEST_F(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDone) {
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDone) {
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
||||||
getFakeHandshakeLayer()->allowZeroRttKeys();
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
||||||
setupClientReadCodec();
|
setupClientReadCodec();
|
||||||
@@ -3430,6 +3637,7 @@ std::pair<int, std::vector<const NewTokenFrame*>> getNewTokenFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDoneNewTokenFrame) {
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDoneNewTokenFrame) {
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
std::array<uint8_t, kRetryTokenSecretLength> secret;
|
std::array<uint8_t, kRetryTokenSecretLength> secret;
|
||||||
folly::Random::secureRandom(secret.data(), secret.size());
|
folly::Random::secureRandom(secret.data(), secret.size());
|
||||||
server->getNonConstConn().transportSettings.retryTokenSecret = secret;
|
server->getNonConstConn().transportSettings.retryTokenSecret = secret;
|
||||||
|
@@ -353,6 +353,9 @@ class QuicServerTransportTestBase : public virtual testing::Test {
|
|||||||
virtual void setupConnection() {
|
virtual void setupConnection() {
|
||||||
EXPECT_EQ(server->getConn().readCodec, nullptr);
|
EXPECT_EQ(server->getConn().readCodec, nullptr);
|
||||||
EXPECT_EQ(server->getConn().statsCallback, quicStats_.get());
|
EXPECT_EQ(server->getConn().statsCallback, quicStats_.get());
|
||||||
|
// None of these connections should cause the server to get WritableBytes
|
||||||
|
// limited.
|
||||||
|
EXPECT_CALL(*quicStats_, onConnectionWritableBytesLimited()).Times(0);
|
||||||
// Not all connections are successful, in which case we don't call
|
// Not all connections are successful, in which case we don't call
|
||||||
// onConnectionClose. The best we can test here is that onConnectionClose
|
// onConnectionClose. The best we can test here is that onConnectionClose
|
||||||
// doesn't get invoked more than once
|
// doesn't get invoked more than once
|
||||||
|
@@ -333,7 +333,7 @@ struct QuicConnectionStateBase : public folly::DelayedDestruction {
|
|||||||
// When server receives early data attempt without valid source address token,
|
// When server receives early data attempt without valid source address token,
|
||||||
// server will limit bytes in flight to avoid amplification attack.
|
// server will limit bytes in flight to avoid amplification attack.
|
||||||
// This limit should be cleared and set back to max after CFIN is received.
|
// This limit should be cleared and set back to max after CFIN is received.
|
||||||
folly::Optional<uint32_t> writableBytesLimit;
|
folly::Optional<uint64_t> writableBytesLimit;
|
||||||
|
|
||||||
std::unique_ptr<PendingPathRateLimiter> pathValidationLimiter;
|
std::unique_ptr<PendingPathRateLimiter> pathValidationLimiter;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user