mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-11-09 10:00:57 +03:00
Summary: Add an experimental setting in the TokenlessPacer to use an adjustable burstSize in the to compensate for delays in pacer loop timer with a limit of 5 loop intervals. This practically restores some concept of tokens in the pacer. This should help with pacer timer delays observed in relatively high BDP links. To help quantify how often the delays happen, the QuicStats pacer_lagged counter is updated every time the pacer is delayed by more than 10% when the connection is not application limited. In a test configuration across a 150 Mbps link with 150 ms round-trip delay, the experimental setting achieves ~25% higher bandwidth. Reviewed By: bschlinker, mjoras Differential Revision: D33035430 fbshipit-source-id: 4aa02821d609facf6c830647459319d44c6fc3bd
4070 lines
150 KiB
C++
4070 lines
150 KiB
C++
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#include <quic/server/test/QuicServerTransportTestUtil.h>
|
|
|
|
#include <quic/codec/QuicPacketBuilder.h>
|
|
#include <quic/dsr/Types.h>
|
|
#include <quic/dsr/test/Mocks.h>
|
|
#include <quic/fizz/handshake/FizzCryptoFactory.h>
|
|
#include <quic/fizz/server/handshake/FizzServerHandshake.h>
|
|
#include <quic/logging/FileQLogger.h>
|
|
#include <quic/server/handshake/ServerHandshake.h>
|
|
#include <quic/state/QuicStreamFunctions.h>
|
|
#include <quic/state/test/Mocks.h>
|
|
|
|
using namespace testing;
|
|
using namespace folly;
|
|
|
|
namespace quic {
|
|
namespace test {
|
|
|
|
namespace {
|
|
using ByteEvent = QuicTransportBase::ByteEvent;
|
|
using PacketDropReason = QuicTransportStatsCallback::PacketDropReason;
|
|
} // namespace
|
|
|
|
folly::Optional<QuicFrame> getFrameIfPresent(
|
|
std::vector<std::unique_ptr<folly::IOBuf>>& socketWrites,
|
|
QuicReadCodec& readCodec,
|
|
QuicFrame::Type frameType) {
|
|
AckStates ackStates;
|
|
for (auto& write : socketWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = readCodec.parsePacket(packetQueue, ackStates);
|
|
auto regularPacket = result.regularPacket();
|
|
if (!regularPacket) {
|
|
continue;
|
|
}
|
|
for (FOLLY_MAYBE_UNUSED auto& frame : regularPacket->frames) {
|
|
if (frame.type() != frameType) {
|
|
continue;
|
|
}
|
|
return frame;
|
|
}
|
|
}
|
|
return folly::none;
|
|
}
|
|
|
|
bool verifyFramePresent(
|
|
std::vector<std::unique_ptr<folly::IOBuf>>& socketWrites,
|
|
QuicReadCodec& readCodec,
|
|
QuicFrame::Type frameType) {
|
|
return getFrameIfPresent(socketWrites, readCodec, frameType).hasValue();
|
|
}
|
|
|
|
struct MigrationParam {
|
|
folly::Optional<uint64_t> clientSentActiveConnIdTransportParam;
|
|
};
|
|
|
|
class QuicServerTransportTest : public QuicServerTransportTestBase {
|
|
public:
|
|
void SetUp() override {
|
|
QuicServerTransportTestBase::SetUp();
|
|
}
|
|
};
|
|
|
|
TEST_F(QuicServerTransportTest, TestReadMultipleStreams) {
|
|
PacketNum clientPacketNum = clientNextAppDataPacketNum++;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientPacketNum);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
auto buf1 = IOBuf::copyBuffer("Aloha");
|
|
auto buf2 = IOBuf::copyBuffer("Hello");
|
|
|
|
auto dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x08,
|
|
0,
|
|
buf1->computeChainDataLength(),
|
|
buf1->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen);
|
|
ASSERT_EQ(*dataLen, buf1->computeChainDataLength());
|
|
writeStreamFrameData(builder, buf1->clone(), buf1->computeChainDataLength());
|
|
|
|
dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x0C,
|
|
0,
|
|
buf1->computeChainDataLength(),
|
|
buf1->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen);
|
|
ASSERT_EQ(*dataLen, buf1->computeChainDataLength());
|
|
writeStreamFrameData(builder, buf2->clone(), buf2->computeChainDataLength());
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
// Clear out the existing acks to make sure that we are the cause of the acks.
|
|
server->getNonConstConn().ackStates.initialAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.initialAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
server->getNonConstConn().ackStates.handshakeAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.handshakeAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
server->getNonConstConn().ackStates.appDataAckState.acks.clear();
|
|
server->getNonConstConn().ackStates.appDataAckState.largestRecvdPacketTime =
|
|
folly::none;
|
|
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(2); // for x08, x0C
|
|
deliverData(packetToBuf(packet));
|
|
|
|
EXPECT_TRUE(
|
|
server->getConn()
|
|
.ackStates.appDataAckState.largestRecvdPacketTime.has_value());
|
|
EXPECT_EQ(server->getConn().ackStates.appDataAckState.acks.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.acks.front().start,
|
|
clientPacketNum);
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.acks.front().end,
|
|
clientPacketNum);
|
|
ASSERT_EQ(server->getConn().streamManager->streamCount(), 2);
|
|
IOBufEqualTo eq;
|
|
|
|
auto stream = server->getNonConstConn().streamManager->findStream(0x08);
|
|
ASSERT_TRUE(stream);
|
|
auto streamData = readDataFromQuicStream(*stream);
|
|
EXPECT_TRUE(eq(buf1, streamData.first));
|
|
EXPECT_TRUE(streamData.second);
|
|
|
|
auto stream2 = server->getNonConstConn().streamManager->findStream(0x0C);
|
|
ASSERT_TRUE(stream2);
|
|
auto streamData2 = readDataFromQuicStream(*stream2);
|
|
EXPECT_TRUE(eq(buf2, streamData2.first));
|
|
EXPECT_TRUE(streamData2.second);
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed()).Times(2);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestInvalidServerStream) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(0);
|
|
StreamId streamId = 0x01;
|
|
auto data = IOBuf::copyBuffer("Aloha");
|
|
EXPECT_THROW(recvEncryptedStream(streamId, *data), std::runtime_error);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAckled */));
|
|
EXPECT_THROW(deliverData(std::move(packetData)), std::runtime_error);
|
|
ASSERT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerResetOnRecvNewData) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
recvEncryptedStream(streamId, *expected);
|
|
ASSERT_TRUE(server->idleTimeout().isScheduled());
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerNotResetOnDuplicatePacket) {
|
|
EXPECT_CALL(*quicStats_, onNewQuicStream()).Times(1);
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = recvEncryptedStream(streamId, *expected);
|
|
ASSERT_TRUE(server->idleTimeout().isScheduled());
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
// Try delivering the same packet again
|
|
deliverData(packet->clone(), false);
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_CALL(*quicStats_, onQuicStreamClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimerNotResetWhenDataOutstanding) {
|
|
// Clear the receivedNewPacketBeforeWrite flag, since we may reveice from
|
|
// client during the SetUp of the test case.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().receivedNewPacketBeforeWrite = false;
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
server->writeChain(
|
|
streamId,
|
|
IOBuf::copyBuffer("And if the darkness is to keep us apart"),
|
|
false);
|
|
loopForWrites();
|
|
// It was the first packet
|
|
EXPECT_TRUE(server->idleTimeout().isScheduled());
|
|
|
|
// cancel it and write something else. This time idle timer shouldn't set.
|
|
server->idleTimeout().cancelTimeout();
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
server->writeChain(
|
|
streamId,
|
|
IOBuf::copyBuffer("And if the daylight feels like it's a long way off"),
|
|
false);
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TimeoutsNotSetAfterClose) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(TransportErrorCode::INTERNAL_ERROR),
|
|
std::string("how about no")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
deliverDataWithoutErrorCheck(packet->clone());
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
ASSERT_FALSE(server->lossTimeout().isScheduled());
|
|
ASSERT_FALSE(server->ackTimeout().isScheduled());
|
|
ASSERT_TRUE(server->drainTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, InvalidMigrationNoDrain) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*expected,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(TransportErrorCode::INVALID_MIGRATION),
|
|
std::string("migration disabled")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
deliverDataWithoutErrorCheck(packet->clone());
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
ASSERT_FALSE(server->lossTimeout().isScheduled());
|
|
ASSERT_FALSE(server->ackTimeout().isScheduled());
|
|
ASSERT_FALSE(server->drainTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, IdleTimeoutExpired) {
|
|
server->idleTimeout().timeoutExpired();
|
|
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_TRUE(server->isDraining());
|
|
EXPECT_TRUE(server->isClosed());
|
|
auto serverReadCodec = makeClientEncryptedCodec();
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites, *serverReadCodec, QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites, *serverReadCodec, QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvDataAfterIdleTimeout) {
|
|
server->idleTimeout().timeoutExpired();
|
|
|
|
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
|
EXPECT_TRUE(server->isDraining());
|
|
EXPECT_TRUE(server->isClosed());
|
|
|
|
serverWrites.clear();
|
|
StreamId streamId = 11;
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
recvEncryptedStream(streamId, *expected);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(true),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithError) {
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithNoError) {
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestClientAddressChanges) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
StreamId streamId = 4;
|
|
clientAddr = folly::SocketAddress("127.0.0.1", 2000);
|
|
auto data = IOBuf::copyBuffer("data");
|
|
EXPECT_THROW(
|
|
recvEncryptedStream(streamId, *data, 0, true), std::runtime_error);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloseConnectionWithNoErrorPendingStreams) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
|
|
server->writeChain(streamId, IOBuf::copyBuffer("hello"), true);
|
|
loopForWrites();
|
|
|
|
AckBlocks acks;
|
|
auto start = getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
auto end = getLastOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
acks.insert(start, end);
|
|
deliverData(packetToBuf(createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData)));
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("stopping")));
|
|
|
|
EXPECT_THROW(
|
|
recvEncryptedStream(streamId, *IOBuf::copyBuffer("hello")),
|
|
std::runtime_error);
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceivePacketAfterLocalError) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
// Deliver a reset to non existent stream to trigger a local conn error
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
RstStreamFrame rstFrame2(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame2), builder2);
|
|
auto packet2 = std::move(builder2).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet2));
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveCloseAfterLocalError) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
// Deliver a reset to non existent stream to trigger a local conn error
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
|
|
auto currLargestReceivedPacketNum =
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum;
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder2);
|
|
|
|
auto packet2 = std::move(builder2).buildPacket();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet2));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_GT(
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum,
|
|
currLargestReceivedPacketNum);
|
|
|
|
// Deliver the same bad data again
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_LT(
|
|
server->getConn()
|
|
.ackStates.appDataAckState.largestReceivedAtLastCloseSent,
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum);
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(
|
|
qLogger, "Server closed by peer reason=Mind the gap");
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, NoDataExceptCloseProcessedAfterClosing) {
|
|
auto packetNum = clientNextAppDataPacketNum++;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
packetNum);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
auto buf = folly::IOBuf::copyBuffer("hello");
|
|
writeStreamFrameHeader(
|
|
builder,
|
|
4,
|
|
0,
|
|
buf->computeChainDataLength(),
|
|
buf->computeChainDataLength(),
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
writeStreamFrameData(builder, buf->clone(), buf->computeChainDataLength());
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("hello")));
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_TRUE(hasNotReceivedNewPacketsSinceLastCloseSent(server->getConn()));
|
|
serverWrites.clear();
|
|
|
|
// largestReceivedPacketNum won't be accurate because we will throw
|
|
// before updating the ack state.
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_EQ(
|
|
server->getConn().ackStates.appDataAckState.largestReceivedPacketNum,
|
|
packetNum);
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestOpenAckStreamFrame) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
auto data = IOBuf::copyBuffer("Aloha");
|
|
|
|
// Remove any packets that might have been queued.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
server->writeChain(streamId, data->clone(), false);
|
|
loopForWrites();
|
|
server->writeChain(streamId, data->clone(), false);
|
|
server->writeChain(streamId, data->clone(), false);
|
|
loopForWrites();
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
ASSERT_FALSE(stream->retransmissionBuffer.empty());
|
|
// We need more than one packet for this test.
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
PacketNum lastPacketNum =
|
|
getLastOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
uint32_t buffersInPacket1 = 0;
|
|
for (size_t i = 0; i < server->getNonConstConn().outstandings.packets.size();
|
|
++i) {
|
|
auto& packet = server->getNonConstConn().outstandings.packets[i];
|
|
if (packet.packet.header.getPacketNumberSpace() !=
|
|
PacketNumberSpace::AppData) {
|
|
continue;
|
|
}
|
|
PacketNum currentPacket = packet.packet.header.getPacketSequenceNum();
|
|
ASSERT_FALSE(packet.packet.frames.empty());
|
|
for (auto& quicFrame : packet.packet.frames) {
|
|
auto frame = quicFrame.asWriteStreamFrame();
|
|
if (!frame) {
|
|
continue;
|
|
}
|
|
auto it = stream->retransmissionBuffer.find(frame->offset);
|
|
ASSERT_TRUE(it != stream->retransmissionBuffer.end());
|
|
if (currentPacket == packetNum1 && frame->streamId == streamId) {
|
|
buffersInPacket1++;
|
|
}
|
|
}
|
|
}
|
|
|
|
auto originalRetransSize = stream->retransmissionBuffer.size();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
EXPECT_EQ(
|
|
stream->retransmissionBuffer.size(),
|
|
originalRetransSize - buffersInPacket1);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
// Dup ack
|
|
auto packet2 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet2));
|
|
|
|
EXPECT_EQ(
|
|
stream->retransmissionBuffer.size(),
|
|
originalRetransSize - buffersInPacket1);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
AckBlocks acks2 = {{packetNum1, lastPacketNum}};
|
|
auto packet3 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks2,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet3));
|
|
|
|
EXPECT_EQ(stream->retransmissionBuffer.size(), 0);
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Open);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
|
|
auto empty = IOBuf::create(0);
|
|
server->writeChain(streamId, std::move(empty), true);
|
|
loopForWrites();
|
|
ASSERT_FALSE(server->getConn().outstandings.packets.empty());
|
|
|
|
PacketNum finPacketNum =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
|
|
AckBlocks acks3 = {{lastPacketNum, finPacketNum}};
|
|
auto packet4 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks3,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet4));
|
|
EXPECT_EQ(stream->sendState, StreamSendState::Closed);
|
|
EXPECT_EQ(stream->recvState, StreamRecvState::Open);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrameNonexistClientStream) {
|
|
StreamId streamId = 0x00;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_TRUE(stream->streamReadError.has_value());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveRstStreamNonExistentAndOtherFrame) {
|
|
StreamId clientUnidirectional = 0x02;
|
|
|
|
// Deliver reset on peer unidirectional stream to close the stream.
|
|
RstStreamFrame rstFrame(
|
|
clientUnidirectional, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
writeFrame(rstFrame, builder);
|
|
auto packet = packetToBuf(std::move(builder).buildPacket());
|
|
deliverData(std::move(packet));
|
|
|
|
auto streamId =
|
|
server->createBidirectionalStream(false /* replaySafe */).value();
|
|
|
|
ShortHeader header2(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder2(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header2),
|
|
0 /* largestAcked */);
|
|
builder2.encodePacketHeader();
|
|
writeFrame(rstFrame, builder2);
|
|
|
|
auto data = folly::IOBuf::copyBuffer("hello");
|
|
writeStreamFrameHeader(
|
|
builder2,
|
|
streamId,
|
|
0,
|
|
data->computeChainDataLength(),
|
|
data->computeChainDataLength(),
|
|
false,
|
|
folly::none /* skipLenHint */);
|
|
writeStreamFrameData(builder2, data->clone(), data->computeChainDataLength());
|
|
auto packetObject = std::move(builder2).buildPacket();
|
|
auto packet2 = packetToBuf(std::move(packetObject));
|
|
deliverData(std::move(packet2));
|
|
|
|
auto readData = server->read(streamId, 0);
|
|
ASSERT_TRUE(readData.hasValue());
|
|
ASSERT_NE(readData.value().first, nullptr);
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(*readData.value().first, *data));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrameNonexistServerStream) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
StreamId streamId = 0x01;
|
|
RstStreamFrame rstFrame(streamId, GenericApplicationErrorCode::UNKNOWN, 0);
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvRstStreamFrame) {
|
|
clientNextAppDataPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
writeDataToQuicStream(*stream, IOBuf::copyBuffer(words.at(3)), false);
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
RstStreamFrame rstFrame(
|
|
streamId,
|
|
GenericApplicationErrorCode::UNKNOWN,
|
|
words.at(0).length() + words.at(1).length());
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(std::move(rstFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
|
|
// Verify stream receive state is cleaned up but send state isn't:
|
|
auto updatedStream =
|
|
server->getNonConstConn().streamManager->findStream(streamId);
|
|
ASSERT_TRUE(updatedStream);
|
|
EXPECT_TRUE(updatedStream->readBuffer.empty());
|
|
// We can verify retx buffer isn't empty here. The writeBuffer though could be
|
|
// empty since deliverData can cause a write synchrously.
|
|
EXPECT_FALSE(updatedStream->retransmissionBuffer.empty());
|
|
EXPECT_EQ(
|
|
words.at(0).length() + words.at(1).length(),
|
|
updatedStream->finalReadOffset.value());
|
|
// updatedStream still writable since receiving rst has no impact on egress
|
|
EXPECT_TRUE(updatedStream->writable());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrame) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterCloseStream) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
server->getNonConstConn().flowControlState.sumCurStreamBufferLen = 100;
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
server->resetStream(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
EXPECT_CALL(connCallback, onStopSending(_, _)).Times(0);
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvInvalidMaxStreamData) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x02;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
server->getNonConstConn().flowControlState.sumCurStreamBufferLen = 100;
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
MaxStreamDataFrame maxStreamDataFrame(streamId, 100);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(std::move(maxStreamDataFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterHalfCloseRemote) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId = 0x00;
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
stream->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto dataLen = writeStreamFrameHeader(
|
|
builder,
|
|
0x00,
|
|
stream->currentReadOffset,
|
|
0,
|
|
10,
|
|
true,
|
|
folly::none /* skipLenHint */);
|
|
ASSERT_TRUE(dataLen.has_value());
|
|
ASSERT_EQ(*dataLen, 0);
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingBeforeStream) {
|
|
StreamId streamId = 0x00;
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onNewBidirectionalStream(streamId));
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onStopSending(streamId, GenericApplicationErrorCode::UNKNOWN));
|
|
deliverData(packetToBuf(packet));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvStopSendingFrameAfterReset) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
std::array<std::string, 4> words = {
|
|
"Hey Bob, this is Alice, for real.",
|
|
"What message did I send you last time?",
|
|
"You don't sound like Alice",
|
|
"You are a liar!",
|
|
};
|
|
|
|
StreamId streamId1 = 0x00;
|
|
StreamId streamId2 = 0x04;
|
|
auto stream1 = server->getNonConstConn().streamManager->getStream(streamId1);
|
|
stream1->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream1->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream1->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream1->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream1->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream1->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
auto stream2 = server->getNonConstConn().streamManager->getStream(streamId2);
|
|
stream2->readBuffer.emplace_back(IOBuf::copyBuffer(words.at(0)), 0, false);
|
|
stream2->readBuffer.emplace_back(
|
|
IOBuf::copyBuffer(words.at(1)), words.at(0).length(), false);
|
|
stream2->retransmissionBuffer.emplace(
|
|
std::piecewise_construct,
|
|
std::forward_as_tuple(0),
|
|
std::forward_as_tuple(std::make_unique<StreamBuffer>(
|
|
IOBuf::copyBuffer(words.at(2)), 0, false)));
|
|
stream2->writeBuffer.append(IOBuf::copyBuffer(words.at(3)));
|
|
stream2->currentWriteOffset = words.at(2).length() + words.at(3).length();
|
|
stream2->currentReadOffset = words.at(0).length() + words.at(1).length();
|
|
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 5;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
StopSendingFrame stopSendingFrame1(
|
|
streamId1, GenericApplicationErrorCode::UNKNOWN);
|
|
StopSendingFrame stopSendingFrame2(
|
|
streamId2, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame1), builder);
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame2), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(
|
|
connCallback, onStopSending(_, GenericApplicationErrorCode::UNKNOWN))
|
|
.WillOnce(Invoke([&](StreamId /*sid*/, ApplicationErrorCode /*e*/) {
|
|
server->close(folly::none);
|
|
}));
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, StopSendingLoss) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
server->getConn().ackStates.appDataAckState.largestAckedByPeer.value_or(
|
|
0));
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
markPacketLoss(server->getNonConstConn(), packet.packet, false);
|
|
EXPECT_EQ(server->getNonConstConn().pendingEvents.frames.size(), 1);
|
|
StopSendingFrame* stopFrame = server->getNonConstConn()
|
|
.pendingEvents.frames.front()
|
|
.asStopSendingFrame();
|
|
ASSERT_NE(stopFrame, nullptr);
|
|
EXPECT_EQ(*stopFrame, stopSendingFrame);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, StopSendingLossAfterStreamClosed) {
|
|
server->getNonConstConn().ackStates.appDataAckState.nextPacketNum = 3;
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
server->getConn().ackStates.appDataAckState.largestAckedByPeer.value_or(
|
|
0));
|
|
builder.encodePacketHeader();
|
|
StopSendingFrame stopSendingFrame(
|
|
streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeFrame(QuicSimpleFrame(stopSendingFrame), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
// clear out all the streams, this is not a great way to simulate closed
|
|
// streams, but good enough for this test.
|
|
server->getNonConstConn().streamManager->clearOpenStreams();
|
|
markPacketLoss(server->getNonConstConn(), packet.packet, false);
|
|
EXPECT_EQ(server->getNonConstConn().pendingEvents.frames.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCloneStopSending) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
auto qLogger = std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
// knock every handshake outstanding packets out
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
for (auto& t : server->getNonConstConn().lossState.lossTimes) {
|
|
t.reset();
|
|
}
|
|
|
|
server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
loopForWrites();
|
|
// Find the outstanding StopSending.
|
|
auto packetItr = std::find_if(
|
|
server->getNonConstConn().outstandings.packets.begin(),
|
|
server->getNonConstConn().outstandings.packets.end(),
|
|
findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>());
|
|
|
|
ASSERT_TRUE(
|
|
packetItr != server->getNonConstConn().outstandings.packets.end());
|
|
// Force a timeout with no data so that it clones the packet
|
|
server->lossTimeout().timeoutExpired();
|
|
loopForWrites();
|
|
auto numStopSendingPackets = std::count_if(
|
|
server->getNonConstConn().outstandings.packets.begin(),
|
|
server->getNonConstConn().outstandings.packets.end(),
|
|
findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>());
|
|
|
|
EXPECT_GT(numStopSendingPackets, 1);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, kLossTimeoutExpired);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestAckStopSending) {
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->getNonConstConn().streamManager->getStream(streamId);
|
|
server->stopSending(streamId, GenericApplicationErrorCode::UNKNOWN);
|
|
loopForWrites();
|
|
auto match = findFrameInPacketFunc<QuicSimpleFrame::Type::StopSendingFrame>();
|
|
|
|
auto op = findOutstandingPacket(server->getNonConstConn(), match);
|
|
ASSERT_TRUE(op != nullptr);
|
|
PacketNum packetNum = op->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum, packetNum}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
op = findOutstandingPacket(server->getNonConstConn(), match);
|
|
EXPECT_TRUE(op == nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvPathChallenge) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
// Add additional peer id so PathResponse completes.
|
|
conn.peerConnectionIds.emplace_back(ConnectionId({1, 2, 3, 4}), 1);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero, *conn.serverConnectionId, 10);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
PathChallengeFrame pathChallenge(123);
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeSimpleFrame(QuicSimpleFrame(pathChallenge), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_TRUE(conn.pendingEvents.frames.empty());
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 2);
|
|
// The RetireConnectionId frame will be enqueued before the PathResponse.
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
PathResponseFrame& pathResponse =
|
|
*conn.pendingEvents.frames[1].asPathResponseFrame();
|
|
EXPECT_EQ(pathResponse.pathData, pathChallenge.pathData);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestAckRstStream) {
|
|
auto streamId = server->createUnidirectionalStream().value();
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
auto packetNum = rstStreamAndSendPacket(
|
|
server->getNonConstConn(),
|
|
server->getSocket(),
|
|
*stream,
|
|
GenericApplicationErrorCode::UNKNOWN);
|
|
|
|
AckBlocks acks = {{packetNum, packetNum}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
// Closed streams should be deleted.
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveConnectionClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
std::string errMsg = "Stand clear of the closing doors, please";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onConnectionEnd());
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->first,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->first,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->second, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(qLogger, std::move(closedMsg));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveApplicationClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
|
|
std::string errMsg = "Stand clear of the closing doors, please";
|
|
ConnectionCloseFrame appClose(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN), errMsg);
|
|
writeFrame(std::move(appClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_CALL(
|
|
connCallback,
|
|
onConnectionError(IsAppError(GenericApplicationErrorCode::UNKNOWN)));
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR),
|
|
server->getConn().localConnectionError->first);
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->first,
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->second, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
checkTransportStateUpdate(qLogger, std::move(closedMsg));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveConnectionCloseTwice) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
std::string errMsg = "Mind the gap";
|
|
ConnectionCloseFrame connClose(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR), errMsg);
|
|
writeFrame(std::move(connClose), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(connCallback, onConnectionEnd());
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
// Now the transport should be closed
|
|
EXPECT_EQ(
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR),
|
|
server->getConn().localConnectionError->first);
|
|
EXPECT_EQ(
|
|
server->getConn().peerConnectionError->first,
|
|
QuicErrorCode(TransportErrorCode::NO_ERROR));
|
|
auto closedMsg =
|
|
folly::to<std::string>("Server closed by peer reason=", errMsg);
|
|
EXPECT_EQ(server->getConn().peerConnectionError->second, closedMsg);
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
serverWrites.clear();
|
|
deliverDataWithoutErrorCheck(packetToBuf(packet));
|
|
EXPECT_FALSE(verifyFramePresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(),
|
|
QuicFrame::Type::ConnectionCloseFrame));
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::SERVER_STATE_CLOSED));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, CloseTransportWontUnbound) {
|
|
EXPECT_CALL(routingCallback, onConnectionUnbound(_, _, _)).Times(0);
|
|
server->closeTransport();
|
|
// Need to do this otherwise server transport destructor will still call
|
|
// onConnectionUnbound
|
|
server->setRoutingCallback(nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, UnboundConnection) {
|
|
EXPECT_CALL(routingCallback, onConnectionUnbound(_, _, _)).Times(1);
|
|
server->unbindConnection();
|
|
// Need to do this otherwise server transport destructor will still call
|
|
// onConnectionUnbound
|
|
server->setRoutingCallback(nullptr);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, DestroyWithoutClosing) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
MockReadCallback readCb;
|
|
server->setReadCallback(streamId, &readCb);
|
|
|
|
EXPECT_CALL(connCallback, onConnectionError(_)).Times(0);
|
|
EXPECT_CALL(connCallback, onConnectionEnd()).Times(0);
|
|
MockDeliveryCallback deliveryCallback;
|
|
auto write = IOBuf::copyBuffer("no");
|
|
server->writeChain(streamId, write->clone(), true, &deliveryCallback);
|
|
|
|
EXPECT_CALL(deliveryCallback, onCanceled(_, _));
|
|
EXPECT_CALL(readCb, readError(_, _));
|
|
|
|
server.reset();
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, DestroyWithoutClosingCancelByteEvents) {
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
|
|
MockReadCallback readCb;
|
|
server->setReadCallback(streamId, &readCb);
|
|
|
|
EXPECT_CALL(connCallback, onConnectionError(_)).Times(0);
|
|
EXPECT_CALL(connCallback, onConnectionEnd()).Times(0);
|
|
auto write = IOBuf::copyBuffer("no");
|
|
server->writeChain(streamId, write->clone(), true);
|
|
|
|
MockByteEventCallback txCallback;
|
|
MockByteEventCallback deliveryCallback;
|
|
|
|
server->registerByteEventCallback(
|
|
ByteEvent::Type::TX, streamId, 0, &txCallback);
|
|
server->registerByteEventCallback(
|
|
ByteEvent::Type::ACK, streamId, 0, &deliveryCallback);
|
|
|
|
EXPECT_CALL(txCallback, onByteEventCanceled(_));
|
|
EXPECT_CALL(deliveryCallback, onByteEventCanceled(_));
|
|
EXPECT_CALL(readCb, readError(_, _));
|
|
|
|
server.reset();
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SetCongestionControl) {
|
|
// Default: Cubic
|
|
auto cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
|
|
// Change to Reno
|
|
server->setCongestionControl(CongestionControlType::NewReno);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::NewReno, cc->type());
|
|
|
|
// Change back to Cubic:
|
|
server->setCongestionControl(CongestionControlType::Cubic);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, CongestionControlAggressivenessKnob) {
|
|
// Congestion controller is Cubic as default
|
|
auto cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::Cubic, cc->type());
|
|
|
|
// Set to background mode will no have an effect. It only works on BBR.
|
|
server->handleKnobParams({
|
|
{52395, 75},
|
|
});
|
|
|
|
// Switch to BBR congestion control
|
|
server->writeLooper()->setPacingTimer(TimerHighRes::newTimer(&evb, 1ms));
|
|
server->getNonConstConn().transportSettings.pacingEnabled = true;
|
|
server->setCongestionControl(CongestionControlType::BBR);
|
|
cc = server->getConn().congestionController.get();
|
|
EXPECT_EQ(CongestionControlType::BBR, cc->type());
|
|
// BBR is not in background mode initially
|
|
EXPECT_FALSE(cc->isInBackgroundMode());
|
|
|
|
// Set to background mode.
|
|
server->handleKnobParams({
|
|
{52395, 75},
|
|
});
|
|
// BBR should now be in background mode
|
|
EXPECT_TRUE(cc->isInBackgroundMode());
|
|
|
|
// Turn off background mode and verify it
|
|
server->handleKnobParams({
|
|
{52395, 100},
|
|
});
|
|
EXPECT_FALSE(cc->isInBackgroundMode());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestServerNotDetachable) {
|
|
EXPECT_FALSE(server->isDetachable());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
ReceiveDataFromChangedPeerAddressWhileMigrationIsDisabled) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second, "Migration disabled");
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsNoOtherIds) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), false);
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 0);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsOneOtherCid) {
|
|
auto& conn = server->getNonConstConn();
|
|
auto originalCid = conn.clientConnectionId;
|
|
auto secondCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{5, 6, 7, 8}), 2);
|
|
conn.peerConnectionIds.push_back(secondCid);
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), true);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 1);
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
auto replacedCid = conn.clientConnectionId;
|
|
EXPECT_NE(originalCid, *replacedCid);
|
|
EXPECT_EQ(secondCid.connId, *replacedCid);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, SwitchServerCidsMultipleCids) {
|
|
auto& conn = server->getNonConstConn();
|
|
auto originalCid = conn.clientConnectionId;
|
|
auto secondCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{5, 6, 7, 8}), 2);
|
|
auto thirdCid =
|
|
ConnectionIdData(ConnectionId(std::vector<uint8_t>{3, 3, 3, 3}), 3);
|
|
|
|
conn.peerConnectionIds.push_back(secondCid);
|
|
conn.peerConnectionIds.push_back(thirdCid);
|
|
|
|
EXPECT_EQ(conn.retireAndSwitchPeerConnectionIds(), true);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
|
|
EXPECT_EQ(conn.pendingEvents.frames.size(), 1);
|
|
auto retireFrame = conn.pendingEvents.frames[0].asRetireConnectionIdFrame();
|
|
EXPECT_EQ(retireFrame->sequenceNumber, 0);
|
|
|
|
// Uses the first unused connection id.
|
|
auto replacedCid = conn.clientConnectionId;
|
|
EXPECT_NE(originalCid, *replacedCid);
|
|
EXPECT_EQ(secondCid.connId, *replacedCid);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ShortHeaderPacketWithNoFrames) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
// Use large packet number to make sure packet is long enough to parse
|
|
PacketNum nextPacket = 0x11111111;
|
|
// Add some dummy data to make body parseable
|
|
auto dummyDataLen = 20;
|
|
server->getNonConstConn().serverConnectionId = getTestConnectionId();
|
|
auto aead = dynamic_cast<const MockAead*>(
|
|
server->getNonConstConn().readCodec->getOneRttReadCipher());
|
|
// Override the Aead mock to remove the 20 bytes of dummy data added below
|
|
ON_CALL(*aead, _tryDecrypt(_, _, _))
|
|
.WillByDefault(Invoke([&](auto& buf, auto, auto) {
|
|
buf->trimEnd(20);
|
|
return buf->clone();
|
|
}));
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
nextPacket);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto buf = packetToBuf(packet);
|
|
buf->coalesce();
|
|
buf->reserve(0, 200);
|
|
buf->append(dummyDataLen);
|
|
EXPECT_THROW(deliverData(std::move(buf)), std::runtime_error);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PROTOCOL_VIOLATION));
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ShortHeaderPacketWithNoFramesAfterClose) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
// Use large packet number to make sure packet is long enough to parse
|
|
PacketNum nextPacket = 0x11111111;
|
|
// Add some dummy data to make body parseable
|
|
auto dummyDataLen = 20;
|
|
server->getNonConstConn().serverConnectionId = getTestConnectionId();
|
|
auto aead = dynamic_cast<const MockAead*>(
|
|
server->getNonConstConn().readCodec->getOneRttReadCipher());
|
|
// Override the Aead mock to remove the 20 bytes of dummy data added below
|
|
ON_CALL(*aead, _tryDecrypt(_, _, _))
|
|
.WillByDefault(Invoke([&](auto& buf, auto, auto) {
|
|
buf->trimEnd(20);
|
|
return buf->clone();
|
|
}));
|
|
|
|
// Close the connection
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(TransportErrorCode::INTERNAL_ERROR),
|
|
std::string("test close")));
|
|
server->idleTimeout().cancelTimeout();
|
|
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
nextPacket);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto buf = packetToBuf(packet);
|
|
buf->coalesce();
|
|
buf->reserve(0, 200);
|
|
buf->append(dummyDataLen);
|
|
EXPECT_THROW(deliverData(std::move(buf)), std::runtime_error);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PROTOCOL_VIOLATION));
|
|
}
|
|
|
|
class QuicServerTransportAllowMigrationTest
|
|
: public QuicServerTransportTest,
|
|
public WithParamInterface<MigrationParam> {
|
|
public:
|
|
bool getDisableMigration() override {
|
|
return false;
|
|
}
|
|
|
|
virtual void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
false,
|
|
false,
|
|
GetParam().clientSentActiveConnIdTransportParam);
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportMigrationTests,
|
|
QuicServerTransportAllowMigrationTest,
|
|
Values(
|
|
MigrationParam{folly::none},
|
|
MigrationParam{2},
|
|
MigrationParam{4},
|
|
MigrationParam{9},
|
|
MigrationParam{50}));
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceiveProbingPacketFromChangedPeerAddress) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
// Add additional peer id so PathResponse completes.
|
|
server->getNonConstConn().peerConnectionIds.emplace_back(
|
|
ConnectionId({1, 2, 3, 4}), 1);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(PathChallengeFrame(123), builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
auto packetData = packetToBuf(packet);
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second,
|
|
"Probing not supported yet");
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 29);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceiveReorderedDataFromChangedPeerAddress) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto firstPacket = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
auto secondPacket = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
// Receive second packet first
|
|
deliverData(std::move(secondPacket));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
// Receive first packet later from a different address
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(firstPacket), true, &newPeer);
|
|
|
|
// No migration for reordered packet
|
|
EXPECT_EQ(server->getConn().peerAddress, peerAddress);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToUnvalidatedPeer) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, ResetPathRttPathResponse) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
EXPECT_FALSE(server->getConn().writableBytesLimit);
|
|
|
|
// After Pathresponse frame is received, srtt,lrtt = sampleRtt;
|
|
// sampleRtt = time from send of PathChallenge to receiving PathResponse
|
|
EXPECT_NE(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.rttvar, 0us);
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, IgnoreInvalidPathResponse) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
|
|
writeSimpleFrame(
|
|
PathResponseFrame(
|
|
server->getConn().outstandingPathValidation->pathData ^ 1),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet), false, &newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
ReceivePathResponseFromDifferentPeerAddress) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
|
|
loopForWrites();
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().pathValidationLimiter != nullptr);
|
|
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
writeSimpleFrame(
|
|
PathResponseFrame(server->getConn().outstandingPathValidation->pathData),
|
|
builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
folly::SocketAddress newPeer2("200.101.102.103", 23456);
|
|
try {
|
|
deliverData(packetToBuf(packet), false, &newPeer2);
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->isClosed());
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second,
|
|
"Probing not supported yet");
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TooManyMigrations) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
for (size_t i = 0; i < kMaxNumMigrationsAllowed; ++i) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456 + i);
|
|
deliverData(packetData->clone(), false, &newPeer);
|
|
}
|
|
|
|
folly::SocketAddress newPeer("200.101.102.103", 23456);
|
|
try {
|
|
deliverData(packetData->clone(), false, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second, "Too many migrations");
|
|
EXPECT_TRUE(server->isClosed());
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 0);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToValidatedPeer) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now();
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.mrtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto lastCongestionController =
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get();
|
|
auto lastSrtt = server->getConn().migrationState.lastCongestionAndRtt->srtt;
|
|
auto lastLrtt = server->getConn().migrationState.lastCongestionAndRtt->lrtt;
|
|
auto lastRttvar =
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar;
|
|
auto lastMrtt = server->getConn().migrationState.lastCongestionAndRtt->mrtt;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, lastSrtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lastLrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, lastRttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, lastMrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().congestionController.get(), lastCongestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportAllowMigrationTest,
|
|
MigrateToUnvalidatedPeerOverwritesCachedRttState) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now();
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.mrtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer2("200.101.102.103", 2345);
|
|
deliverData(std::move(packetData), false, &newPeer2);
|
|
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer2);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 2);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.front(), newPeer);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_P(QuicServerTransportAllowMigrationTest, MigrateToStaleValidatedPeer) {
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
server->getNonConstConn().migrationState.previousPeerAddresses.push_back(
|
|
newPeer);
|
|
CongestionAndRttState state;
|
|
state.peerAddress = newPeer;
|
|
state.recordTime = Clock::now() - 2 * kTimeToRetainLastCongestionAndRttState;
|
|
state.congestionController = ccFactory_->makeCongestionController(
|
|
server->getNonConstConn(),
|
|
server->getNonConstConn().transportSettings.defaultCongestionController);
|
|
state.srtt = 1000us;
|
|
state.lrtt = 2000us;
|
|
state.rttvar = 3000us;
|
|
state.srtt = 800us;
|
|
server->getNonConstConn().migrationState.lastCongestionAndRtt =
|
|
std::move(state);
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToValidatePeerCancelsPendingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), false, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2), false);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.pathChallenge);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, srtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, mrtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToUnvalidatePeerCancelsOutstandingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer2("200.101.102.103", 23456);
|
|
deliverData(std::move(packetData2), false, &newPeer2);
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
MigrateToValidatePeerCancelsOutstandingPathChallenge) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto peerAddress = server->getConn().peerAddress;
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
auto srtt = server->getConn().lossState.srtt;
|
|
auto lrtt = server->getConn().lossState.lrtt;
|
|
auto rttvar = server->getConn().lossState.rttvar;
|
|
auto mrtt = server->getConn().lossState.mrtt;
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_TRUE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_TRUE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.previousPeerAddresses.back(),
|
|
peerAddress);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->peerAddress,
|
|
clientAddr);
|
|
EXPECT_EQ(
|
|
server->getConn()
|
|
.migrationState.lastCongestionAndRtt->congestionController.get(),
|
|
congestionController);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->srtt, srtt);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->lrtt, lrtt);
|
|
EXPECT_EQ(
|
|
server->getConn().migrationState.lastCongestionAndRtt->rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().migrationState.lastCongestionAndRtt->mrtt, mrtt);
|
|
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
6,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2));
|
|
EXPECT_FALSE(server->getConn().outstandingPathValidation);
|
|
EXPECT_FALSE(server->getConn().pendingEvents.schedulePathValidationTimeout);
|
|
EXPECT_FALSE(server->pathValidationTimeout().isScheduled());
|
|
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 0);
|
|
EXPECT_EQ(server->getConn().lossState.srtt, srtt);
|
|
EXPECT_EQ(server->getConn().lossState.lrtt, lrtt);
|
|
EXPECT_EQ(server->getConn().lossState.rttvar, rttvar);
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, mrtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ClientPortChangeNATRebinding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data1 = IOBuf::copyBuffer("Aloha");
|
|
server->writeChain(streamId, data1->clone(), false);
|
|
loopForWrites();
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer(clientAddr.getIPAddress(), 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_NE(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_NE(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_NE(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_NE(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ClientAddressChangeNATRebinding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data1 = IOBuf::copyBuffer("Aloha");
|
|
server->writeChain(streamId, data1->clone(), false);
|
|
loopForWrites();
|
|
PacketNum packetNum1 =
|
|
getFirstOutstandingPacket(
|
|
server->getNonConstConn(), PacketNumberSpace::AppData)
|
|
->packet.header.getPacketSequenceNum();
|
|
AckBlocks acks = {{packetNum1, packetNum1}};
|
|
auto packet1 = createAckPacket(
|
|
server->getNonConstConn(),
|
|
++clientNextAppDataPacketNum,
|
|
acks,
|
|
PacketNumberSpace::AppData);
|
|
deliverData(packetToBuf(packet1));
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer("127.0.0.100", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_NE(server->getConn().lossState.srtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.lrtt, 0us);
|
|
EXPECT_NE(server->getConn().lossState.rttvar, 0us);
|
|
EXPECT_NE(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_FALSE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicServerTransportTest,
|
|
ClientNATRebindingWhilePathValidationOutstanding) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
|
|
auto congestionController = server->getConn().congestionController.get();
|
|
|
|
folly::SocketAddress newPeer("200.0.0.100", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_NE(server->getConn().congestionController.get(), nullptr);
|
|
EXPECT_NE(server->getConn().congestionController.get(), congestionController);
|
|
EXPECT_TRUE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
|
|
auto newCC = server->getConn().congestionController.get();
|
|
folly::SocketAddress newPeer2("200.0.0.200", 12345);
|
|
auto data2 = IOBuf::copyBuffer("bad data");
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2), true, &newPeer2);
|
|
|
|
EXPECT_TRUE(server->getConn().outstandingPathValidation);
|
|
|
|
EXPECT_EQ(server->getConn().peerAddress, newPeer2);
|
|
EXPECT_EQ(server->getConn().migrationState.previousPeerAddresses.size(), 1);
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.srtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.lrtt, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(
|
|
server->getConn().lossState.rttvar, std::chrono::microseconds::zero());
|
|
EXPECT_EQ(server->getConn().lossState.mrtt, kDefaultMinRtt);
|
|
EXPECT_EQ(server->getConn().congestionController.get(), newCC);
|
|
EXPECT_TRUE(server->getConn().migrationState.lastCongestionAndRtt);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, PingIsTreatedAsRetransmittable) {
|
|
PingFrame pingFrame;
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
writeFrame(pingFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
EXPECT_TRUE(server->getConn().pendingEvents.scheduleAckTimeout);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveDatagramFrameAndDiscard) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StringPiece datagramPayload = "do not rely on me. I am unreliable";
|
|
DatagramFrame datagramFrame(
|
|
datagramPayload.size(), IOBuf::copyBuffer(datagramPayload));
|
|
writeFrame(datagramFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
EXPECT_CALL(*quicStats_, onDatagramDroppedOnRead()).Times(1);
|
|
deliverData(packetToBuf(packet));
|
|
ASSERT_EQ(server->getConn().datagramState.readBuffer.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, ReceiveDatagramFrameAndStore) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.datagramState.maxReadFrameSize = std::numeric_limits<uint16_t>::max();
|
|
conn.datagramState.maxReadBufferSize = 10;
|
|
|
|
EXPECT_CALL(*quicStats_, onDatagramRead(_))
|
|
.Times(conn.datagramState.maxReadBufferSize)
|
|
.WillRepeatedly(Invoke([](uint64_t bytes) { EXPECT_GT(bytes, 0); }));
|
|
EXPECT_CALL(*quicStats_, onDatagramDroppedOnRead())
|
|
.Times(conn.datagramState.maxReadBufferSize);
|
|
for (uint64_t i = 0; i < conn.datagramState.maxReadBufferSize * 2; i++) {
|
|
ShortHeader header(
|
|
ProtectionType::KeyPhaseZero,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++);
|
|
RegularQuicPacketBuilder builder(
|
|
server->getConn().udpSendPacketLen,
|
|
std::move(header),
|
|
0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
StringPiece datagramPayload = "do not rely on me. I am unreliable";
|
|
DatagramFrame datagramFrame(
|
|
datagramPayload.size(), IOBuf::copyBuffer(datagramPayload));
|
|
writeFrame(datagramFrame, builder);
|
|
auto packet = std::move(builder).buildPacket();
|
|
deliverData(packetToBuf(packet));
|
|
if (i < conn.datagramState.maxReadBufferSize) {
|
|
ASSERT_EQ(server->getConn().datagramState.readBuffer.size(), i + 1);
|
|
}
|
|
}
|
|
ASSERT_EQ(
|
|
server->getConn().datagramState.readBuffer.size(),
|
|
conn.datagramState.maxReadBufferSize);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdValid) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 2;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 0, ConnectionId({2, 4, 2, 3}), StatelessResetToken{9, 8, 7, 6});
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].connId, newConnId.connectionId);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].sequenceNumber, newConnId.sequenceNumber);
|
|
EXPECT_EQ(conn.peerConnectionIds[1].token, newConnId.token);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdTooManyReceivedIds) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 0;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 0, ConnectionId({2, 4, 2, 3}), StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdInvalidRetire) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 1;
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(
|
|
1, 3, ConnectionId({2, 4, 2, 3}), StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 1);
|
|
EXPECT_THROW(deliverData(packetToBuf(packet), false), std::runtime_error);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdNoopValidDuplicate) {
|
|
auto& conn = server->getNonConstConn();
|
|
conn.transportSettings.selfActiveConnectionIdLimit = 1;
|
|
|
|
ConnectionId connId2({5, 5, 5, 5});
|
|
conn.peerConnectionIds.emplace_back(connId2, 1);
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(1, 0, connId2, StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
deliverData(packetToBuf(packet), false);
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, RecvNewConnectionIdExceptionInvalidDuplicate) {
|
|
auto& conn = server->getNonConstConn();
|
|
|
|
ConnectionId connId2({5, 5, 5, 5});
|
|
conn.peerConnectionIds.emplace_back(connId2, 1);
|
|
|
|
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
|
RegularQuicPacketBuilder builder(
|
|
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
ASSERT_TRUE(builder.canBuildPacket());
|
|
NewConnectionIdFrame newConnId(2, 0, connId2, StatelessResetToken());
|
|
writeSimpleFrame(QuicSimpleFrame(newConnId), builder);
|
|
|
|
auto packet = std::move(builder).buildPacket();
|
|
|
|
EXPECT_EQ(conn.peerConnectionIds.size(), 2);
|
|
EXPECT_THROW(deliverData(packetToBuf(packet)), std::runtime_error);
|
|
}
|
|
|
|
class QuicUnencryptedServerTransportTest : public QuicServerTransportTest {
|
|
public:
|
|
void setupConnection() override {}
|
|
};
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, FirstPacketProcessedCallback) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
EXPECT_CALL(connSetupCallback, onFirstPeerPacketProcessed()).Times(1);
|
|
recvClientHello();
|
|
loopForWrites();
|
|
AckBlocks acks;
|
|
acks.insert(0);
|
|
auto aead = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
EXPECT_CALL(connSetupCallback, onFirstPeerPacketProcessed()).Times(0);
|
|
deliverData(packetToBufCleartext(
|
|
createAckPacket(
|
|
server->getNonConstConn(),
|
|
clientNextInitialPacketNum,
|
|
acks,
|
|
PacketNumberSpace::Initial,
|
|
aead.get()),
|
|
*aead,
|
|
*headerCipher,
|
|
clientNextInitialPacketNum));
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestUnencryptedStream) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
StreamId streamId = 3;
|
|
auto initialCipher = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
auto packetData = packetToBufCleartext(
|
|
createStreamPacket(
|
|
*clientConnectionId,
|
|
*initialDestinationConnectionId,
|
|
nextPacket,
|
|
streamId,
|
|
*data,
|
|
initialCipher->getCipherOverhead(),
|
|
0 /* largestAcked */,
|
|
std::make_pair(LongHeader::Types::Initial, QuicVersion::MVFST)),
|
|
*initialCipher,
|
|
*headerCipher,
|
|
nextPacket);
|
|
EXPECT_THROW(deliverData(std::move(packetData)), std::runtime_error);
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestUnencryptedAck) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
AckBlocks acks = {{1, 2}};
|
|
auto expected = IOBuf::copyBuffer("hello");
|
|
PacketNum nextPacketNum = clientNextInitialPacketNum++;
|
|
LongHeader header(
|
|
LongHeader::Types::Initial,
|
|
*clientConnectionId,
|
|
server->getConn().serverConnectionId.value_or(getTestConnectionId(1)),
|
|
nextPacketNum,
|
|
QuicVersion::MVFST);
|
|
RegularQuicPacketBuilder builder(
|
|
kDefaultUDPSendPacketLen, std::move(header), 0 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
DCHECK(builder.canBuildPacket());
|
|
AckFrameMetaData ackData(acks, 0us, 0);
|
|
writeAckFrame(ackData, builder);
|
|
auto packet = packetToBufCleartext(
|
|
std::move(builder).buildPacket(),
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher(),
|
|
nextPacketNum);
|
|
EXPECT_NO_THROW(deliverData(std::move(packet)));
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 45);
|
|
EXPECT_EQ(event->dropReason, kCipherUnavailable);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestBadPacketProtectionLevel) {
|
|
// Version negotiation has no protection level.
|
|
auto packet = VersionNegotiationPacketBuilder(
|
|
*clientConnectionId /* src */,
|
|
getTestConnectionId(1) /* dest */,
|
|
{QuicVersion::MVFST})
|
|
.buildPacket();
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(packet.second->clone());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestBadCleartextEncryption) {
|
|
FizzCryptoFactory cryptoFactory;
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
auto aead = cryptoFactory.getServerInitialCipher(
|
|
*clientConnectionId, QuicVersion::MVFST);
|
|
auto packetData = packetToBufCleartext(
|
|
createInitialCryptoPacket(
|
|
*clientConnectionId,
|
|
*initialDestinationConnectionId,
|
|
nextPacket,
|
|
QuicVersion::MVFST,
|
|
*IOBuf::copyBuffer("CHLO"),
|
|
*aead,
|
|
0 /* largestAcked */),
|
|
*aead,
|
|
*getInitialHeaderCipher(),
|
|
nextPacket);
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
// If crypto data was processed, we would have generated some writes.
|
|
EXPECT_NE(server->getConn().readCodec, nullptr);
|
|
EXPECT_TRUE(server->getConn().cryptoState->initialStream.writeBuffer.empty());
|
|
EXPECT_TRUE(server->getConn()
|
|
.cryptoState->initialStream.retransmissionBuffer.empty());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestPendingZeroRttData) {
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
size_t expectedPendingLen =
|
|
server->getConn().transportSettings.maxPacketsToBuffer;
|
|
for (size_t i = 0; i < expectedPendingLen + 10; ++i) {
|
|
StreamId streamId = static_cast<StreamId>(i);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
server->getConn().serverConnectionId.value_or(getTestConnectionId(1)),
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(LongHeader::Types::ZeroRtt, QuicVersion::MVFST)));
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), expectedPendingLen);
|
|
|
|
server->getNonConstConn().pendingZeroRttData->clear();
|
|
deliverData(IOBuf::create(0));
|
|
EXPECT_TRUE(server->getConn().pendingZeroRttData->empty());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestPendingOneRttData) {
|
|
recvClientHello();
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
size_t expectedPendingLen =
|
|
server->getConn().transportSettings.maxPacketsToBuffer;
|
|
for (size_t i = 0; i < expectedPendingLen + 10; ++i) {
|
|
StreamId streamId = static_cast<StreamId>(i);
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
EXPECT_CALL(*quicStats_, onPacketDropped(_));
|
|
deliverData(std::move(packetData));
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), expectedPendingLen);
|
|
|
|
server->getNonConstConn().pendingOneRttData->clear();
|
|
deliverData(IOBuf::create(0));
|
|
EXPECT_TRUE(server->getConn().pendingOneRttData->empty());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestReceiveClientFinishedFromChangedPeerAddress) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
recvClientHello();
|
|
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
try {
|
|
recvClientFinished(true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second,
|
|
"Migration not allowed during handshake");
|
|
EXPECT_TRUE(server->isClosed());
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketDrop, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketDropEvent*>(tmp.get());
|
|
EXPECT_EQ(event->packetSize, 44);
|
|
EXPECT_EQ(
|
|
event->dropReason,
|
|
QuicTransportStatsCallback::toString(
|
|
PacketDropReason::PEER_ADDRESS_CHANGE));
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
ReceiveHandshakePacketFromChangedPeerAddress) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
try {
|
|
recvClientFinished(true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second,
|
|
"Migration not allowed during handshake");
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
ReceiveZeroRttPacketFromChangedPeerAddress) {
|
|
server->getNonConstConn().transportSettings.disableMigration = false;
|
|
fakeHandshake->allowZeroRttKeys();
|
|
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
try {
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second,
|
|
"Migration not allowed during handshake");
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestNoCipherProcessPendingOneRttDataFromChangedAddress) {
|
|
recvClientHello();
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
folly::SocketAddress newPeer("100.101.102.103", 23456);
|
|
deliverData(std::move(packetData), true, &newPeer);
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
try {
|
|
recvClientFinished();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_EQ(std::string(ex.what()), "Invalid migration");
|
|
}
|
|
EXPECT_TRUE(server->getConn().localConnectionError);
|
|
EXPECT_EQ(
|
|
server->getConn().localConnectionError->second, "Migration disabled");
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSkipAckOnlyCryptoInitial) {
|
|
auto transportSettings = server->getTransportSettings();
|
|
server->setTransportSettings(transportSettings);
|
|
|
|
// bypass doHandshake() in fakeServerHandshake by sending something other than
|
|
// "CHLO"
|
|
recvClientHello(true, QuicVersion::MVFST, "hello :)");
|
|
|
|
// we expect nothing to be written as we're skipping the initial ack-only
|
|
// packet
|
|
EXPECT_EQ(serverWrites.size(), 0);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestNoAckOnlyCryptoInitial) {
|
|
auto transportSettings = server->getTransportSettings();
|
|
server->setTransportSettings(transportSettings);
|
|
|
|
recvClientHello();
|
|
|
|
EXPECT_GE(serverWrites.size(), 1);
|
|
|
|
AckStates ackStates;
|
|
|
|
auto clientCodec = makeClientEncryptedCodec(true);
|
|
for (auto& write : serverWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
|
auto& regularPacket = *result.regularPacket();
|
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
|
bool handshakePacket = protectionType == ProtectionType::Initial ||
|
|
protectionType == ProtectionType::Handshake;
|
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
|
bool hasCryptoFrame = false;
|
|
bool hasAckFrame = false;
|
|
for (auto& frame : regularPacket.frames) {
|
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
|
hasAckFrame |= frame.asReadAckFrame() != nullptr;
|
|
}
|
|
|
|
// The packet sent by the server shouldn't be a pure ack (i.e. contains some
|
|
// crypto data as well)
|
|
if (handshakePacket) {
|
|
EXPECT_TRUE(hasCryptoFrame);
|
|
EXPECT_TRUE(hasAckFrame);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestWriteHandshakeAndZeroRtt) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->writeChain(streamId, IOBuf::copyBuffer("hello"), true);
|
|
loopForWrites();
|
|
auto clientCodec = makeClientEncryptedCodec(true);
|
|
|
|
size_t numCryptoFrames = 0;
|
|
size_t numNonCryptoFrames = 0;
|
|
EXPECT_GT(serverWrites.size(), 1);
|
|
AckStates ackStates;
|
|
for (auto& write : serverWrites) {
|
|
auto packetQueue = bufToQueue(write->clone());
|
|
auto result = clientCodec->parsePacket(packetQueue, ackStates);
|
|
auto& regularPacket = *result.regularPacket();
|
|
ProtectionType protectionType = regularPacket.header.getProtectionType();
|
|
bool handshakePacket = protectionType == ProtectionType::Initial ||
|
|
protectionType == ProtectionType::Handshake;
|
|
EXPECT_GE(regularPacket.frames.size(), 1);
|
|
bool hasCryptoFrame = false;
|
|
bool hasNonCryptoStream = false;
|
|
for (auto& frame : regularPacket.frames) {
|
|
hasCryptoFrame |= frame.asReadCryptoFrame() != nullptr;
|
|
hasNonCryptoStream |= frame.asReadStreamFrame() != nullptr;
|
|
}
|
|
if (hasCryptoFrame) {
|
|
EXPECT_TRUE(handshakePacket);
|
|
numCryptoFrames++;
|
|
}
|
|
if (hasNonCryptoStream) {
|
|
EXPECT_FALSE(handshakePacket);
|
|
numNonCryptoFrames++;
|
|
}
|
|
}
|
|
EXPECT_GE(numCryptoFrames, 1);
|
|
EXPECT_GE(numNonCryptoFrames, 1);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestEncryptedDataBeforeCFIN) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
StreamId streamId = 4;
|
|
recvEncryptedStream(streamId, *IOBuf::copyBuffer("hello"));
|
|
|
|
auto stream = server->getNonConstConn().streamManager->getStream(streamId);
|
|
ASSERT_TRUE(stream->readBuffer.empty());
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
TestClearInFlightBytesLimitationAfterCFIN) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
server->getNonConstConn().transportSettings.zeroRttSourceTokenMatchingPolicy =
|
|
ZeroRttSourceTokenMatchingPolicy::LIMIT_IF_NO_EXACT_MATCH;
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
auto originalUdpSize = server->getConn().udpSendPacketLen;
|
|
|
|
setupClientReadCodec();
|
|
|
|
recvClientHello();
|
|
ASSERT_TRUE(server->getNonConstConn().writableBytesLimit.has_value());
|
|
EXPECT_EQ(
|
|
*server->getNonConstConn().writableBytesLimit,
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
loopForWrites();
|
|
EXPECT_EQ(server->getConn().writableBytesLimit, folly::none);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 4);
|
|
std::array<::std::string, 4> updateArray = {
|
|
kDerivedZeroRttReadCipher,
|
|
kDerivedOneRttWriteCipher,
|
|
kTransportReady,
|
|
kDerivedOneRttReadCipher};
|
|
for (int i = 0; i < 4; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, updateArray[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDone) {
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
setupClientReadCodec();
|
|
recvClientHello(true, QuicVersion::QUIC_DRAFT);
|
|
recvClientFinished(true, nullptr, QuicVersion::QUIC_DRAFT);
|
|
auto& packets = server->getConn().outstandings.packets;
|
|
ASSERT_FALSE(packets.empty());
|
|
int numHandshakeDone = 0;
|
|
for (auto& p : packets) {
|
|
for (auto& f : p.packet.frames) {
|
|
auto s = f.asQuicSimpleFrame();
|
|
if (s) {
|
|
if (s->asHandshakeDoneFrame()) {
|
|
numHandshakeDone++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
EXPECT_EQ(numHandshakeDone, 1);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of new token frames (should either be zero or one).
|
|
*/
|
|
std::pair<int, std::vector<const NewTokenFrame*>> getNewTokenFrame(
|
|
const std::deque<OutstandingPacket>& packets) {
|
|
int numNewTokens = 0;
|
|
std::vector<const NewTokenFrame*> frames;
|
|
|
|
for (auto& p : packets) {
|
|
for (auto& f : p.packet.frames) {
|
|
auto s = f.asQuicSimpleFrame();
|
|
if (s && s->asNewTokenFrame()) {
|
|
numNewTokens++;
|
|
frames.push_back(s->asNewTokenFrame());
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::make_pair(numNewTokens, std::move(frames));
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestSendHandshakeDoneNewTokenFrame) {
|
|
std::array<uint8_t, kRetryTokenSecretLength> secret;
|
|
folly::Random::secureRandom(secret.data(), secret.size());
|
|
server->getNonConstConn().transportSettings.retryTokenSecret = secret;
|
|
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
setupClientReadCodec();
|
|
recvClientHello(true, QuicVersion::QUIC_DRAFT);
|
|
|
|
/**
|
|
* Receiving just the chlo should not issue a NewTokenFrame.
|
|
*/
|
|
EXPECT_EQ(getNewTokenFrame(server->getConn().outstandings.packets).first, 0);
|
|
|
|
EXPECT_CALL(*quicStats_, onNewTokenIssued());
|
|
recvClientFinished(true, nullptr, QuicVersion::QUIC_DRAFT);
|
|
|
|
/**
|
|
* After the handshake is complete, we expect only one NewTokenFrame to be
|
|
* issued.
|
|
*/
|
|
auto serverWriteNewTokenFrame =
|
|
getNewTokenFrame(server->getConn().outstandings.packets);
|
|
EXPECT_EQ(serverWriteNewTokenFrame.first, 1);
|
|
|
|
// Verify that the client parses the same token as what was written
|
|
auto clientParsedFrame = getFrameIfPresent(
|
|
serverWrites,
|
|
*makeClientEncryptedCodec(true),
|
|
QuicFrame::Type::ReadNewTokenFrame);
|
|
|
|
EXPECT_TRUE(
|
|
clientParsedFrame.hasValue() && clientParsedFrame->asReadNewTokenFrame());
|
|
|
|
auto clientReadNewTokenFrame = clientParsedFrame->asReadNewTokenFrame();
|
|
|
|
auto serverToken = serverWriteNewTokenFrame.second[0]->token;
|
|
auto clientToken =
|
|
clientReadNewTokenFrame->token->moveToFbString().toStdString();
|
|
|
|
EXPECT_EQ(clientToken, serverToken);
|
|
loopForWrites();
|
|
|
|
/**
|
|
* Receiving client data post-handshake should not issue any NewTokenFrames.
|
|
*/
|
|
|
|
// Remove any packets that might have been queued.
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
server->getNonConstConn().outstandings.packetCount = {};
|
|
|
|
StreamId streamId = server->createBidirectionalStream().value();
|
|
auto data = IOBuf::copyBuffer("data");
|
|
auto packet = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packet));
|
|
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(getNewTokenFrame(server->getConn().outstandings.packets).first, 0);
|
|
}
|
|
|
|
TEST_F(
|
|
QuicUnencryptedServerTransportTest,
|
|
IncreaseLimitAfterReceivingNewPacket) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
server->getNonConstConn().transportSettings.zeroRttSourceTokenMatchingPolicy =
|
|
ZeroRttSourceTokenMatchingPolicy::LIMIT_IF_NO_EXACT_MATCH;
|
|
|
|
auto originalUdpSize = server->getConn().udpSendPacketLen;
|
|
setupClientReadCodec();
|
|
|
|
recvClientHello();
|
|
EXPECT_EQ(
|
|
*server->getNonConstConn().writableBytesLimit,
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize);
|
|
|
|
recvClientHello();
|
|
|
|
// in tests the udp packet length changes
|
|
auto expectedLen =
|
|
server->getConn().transportSettings.limitedCwndInMss * originalUdpSize +
|
|
server->getConn().transportSettings.limitedCwndInMss *
|
|
server->getConn().udpSendPacketLen;
|
|
EXPECT_NE(originalUdpSize, server->getConn().udpSendPacketLen);
|
|
EXPECT_EQ(*server->getNonConstConn().writableBytesLimit, expectedLen);
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::TransportStateUpdate, qLogger);
|
|
EXPECT_EQ(indices.size(), 3);
|
|
std::array<::std::string, 3> updateArray = {
|
|
kDerivedZeroRttReadCipher, kDerivedOneRttWriteCipher, kTransportReady};
|
|
for (int i = 0; i < 3; ++i) {
|
|
auto tmp = std::move(qLogger->logs[indices[i]]);
|
|
auto event = dynamic_cast<QLogTransportStateUpdateEvent*>(tmp.get());
|
|
EXPECT_EQ(event->update, updateArray[i]);
|
|
}
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, MaxReceivePacketSizeTooLarge) {
|
|
getFakeHandshakeLayer()->allowZeroRttKeys();
|
|
fakeHandshake->maxRecvPacketSize = 4096;
|
|
setupClientReadCodec();
|
|
recvClientHello();
|
|
EXPECT_EQ(server->getConn().udpSendPacketLen, kDefaultUDPSendPacketLen);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestGarbageData) {
|
|
auto qLogger = std::make_shared<FileQLogger>(VantagePoint::Server);
|
|
server->getNonConstConn().qLogger = qLogger;
|
|
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
PacketNum nextPacket = clientNextInitialPacketNum++;
|
|
auto aead = getInitialCipher();
|
|
auto headerCipher = getInitialHeaderCipher();
|
|
auto packet = createCryptoPacket(
|
|
*clientConnectionId,
|
|
*clientConnectionId,
|
|
nextPacket,
|
|
QuicVersion::QUIC_DRAFT,
|
|
ProtectionType::Initial,
|
|
*IOBuf::copyBuffer("CHLO"),
|
|
*aead,
|
|
0 /* largestAcked */);
|
|
auto packetData =
|
|
packetToBufCleartext(packet, *aead, *headerCipher, nextPacket);
|
|
packetData->prependChain(IOBuf::copyBuffer("garbage in"));
|
|
deliverData(std::move(packetData));
|
|
EXPECT_NE(server->getConn().readCodec, nullptr);
|
|
EXPECT_NE(server->getConn().initialWriteCipher, nullptr);
|
|
|
|
std::vector<int> indices =
|
|
getQLogEventIndices(QLogEventType::PacketBuffered, qLogger);
|
|
EXPECT_EQ(indices.size(), 1);
|
|
auto tmp = std::move(qLogger->logs[indices[0]]);
|
|
auto event = dynamic_cast<QLogPacketBufferedEvent*>(tmp.get());
|
|
EXPECT_EQ(event->protectionType, ProtectionType::KeyPhaseZero);
|
|
EXPECT_EQ(event->packetSize, 10);
|
|
}
|
|
|
|
Buf getHandshakePacketWithFrame(
|
|
QuicWriteFrame frame,
|
|
ConnectionId connId,
|
|
Aead& clientWriteCipher,
|
|
PacketNumberCipher& headerCipher) {
|
|
PacketNum clientPacketNum = folly::Random::rand32();
|
|
LongHeader header(
|
|
LongHeader::Types::Handshake,
|
|
connId,
|
|
connId,
|
|
clientPacketNum,
|
|
QuicVersion::MVFST);
|
|
RegularQuicPacketBuilder builder(
|
|
kDefaultUDPSendPacketLen,
|
|
std::move(header),
|
|
clientPacketNum / 2 /* largestAcked */);
|
|
builder.encodePacketHeader();
|
|
builder.accountForCipherOverhead(clientWriteCipher.getCipherOverhead());
|
|
writeFrame(std::move(frame), builder);
|
|
return packetToBufCleartext(
|
|
std::move(builder).buildPacket(),
|
|
clientWriteCipher,
|
|
headerCipher,
|
|
clientPacketNum);
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestNotAllowedInUnencryptedPacket) {
|
|
// This should trigger derivation of keys.
|
|
recvClientHello();
|
|
|
|
StreamId streamId = 4;
|
|
auto data = IOBuf::copyBuffer("data");
|
|
|
|
EXPECT_THROW(
|
|
deliverData(getHandshakePacketWithFrame(
|
|
MaxStreamDataFrame(streamId, 100),
|
|
*clientConnectionId,
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher())),
|
|
std::runtime_error);
|
|
EXPECT_TRUE(server->error());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, TestCloseWhileAsyncPending) {
|
|
folly::EventBase testLooper;
|
|
setupClientReadCodec();
|
|
getFakeHandshakeLayer()->initialize(&testLooper, server.get());
|
|
|
|
recvClientHello();
|
|
testLooper.loop();
|
|
|
|
// Make sure the test looper worked.
|
|
IOBufEqualTo eq;
|
|
EXPECT_TRUE(eq(getCryptoStreamData(), IOBuf::copyBuffer("SHLO")));
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeUnfinished());
|
|
recvClientFinished();
|
|
|
|
server->close(std::make_pair(
|
|
QuicErrorCode(GenericApplicationErrorCode::UNKNOWN),
|
|
std::string("hello")));
|
|
EXPECT_TRUE(server->isClosed());
|
|
testLooper.loop();
|
|
|
|
EXPECT_EQ(server->getConn().oneRttWriteCipher, nullptr);
|
|
|
|
StreamId streamId = 4;
|
|
auto data = IOBuf::copyBuffer("data");
|
|
|
|
EXPECT_THROW(
|
|
deliverData(getHandshakePacketWithFrame(
|
|
MaxStreamDataFrame(streamId, 100),
|
|
*clientConnectionId,
|
|
*getInitialCipher(),
|
|
*getInitialHeaderCipher())),
|
|
std::runtime_error);
|
|
}
|
|
|
|
struct FizzHandshakeParam {
|
|
FizzHandshakeParam(bool argCHLOSync, bool argCFINSync, bool argAcceptZeroRtt)
|
|
: chloSync(argCHLOSync),
|
|
cfinSync(argCFINSync),
|
|
acceptZeroRtt(argAcceptZeroRtt) {}
|
|
bool chloSync;
|
|
bool cfinSync;
|
|
bool acceptZeroRtt;
|
|
};
|
|
|
|
class QuicServerTransportPendingDataTest
|
|
: public QuicUnencryptedServerTransportTest,
|
|
public WithParamInterface<FizzHandshakeParam> {
|
|
public:
|
|
~QuicServerTransportPendingDataTest() override {
|
|
loopForWrites();
|
|
}
|
|
|
|
void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
GetParam().chloSync,
|
|
GetParam().cfinSync);
|
|
if (GetParam().acceptZeroRtt) {
|
|
fakeHandshake->allowZeroRttKeys();
|
|
}
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportPendingDataTests,
|
|
QuicServerTransportPendingDataTest,
|
|
Values(
|
|
FizzHandshakeParam(false, false, false),
|
|
FizzHandshakeParam(false, false, true),
|
|
FizzHandshakeParam(false, true, false),
|
|
FizzHandshakeParam(false, true, true),
|
|
FizzHandshakeParam(true, false, false),
|
|
FizzHandshakeParam(true, false, true),
|
|
FizzHandshakeParam(true, true, false),
|
|
FizzHandshakeParam(true, true, true)));
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessPendingZeroRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello(false);
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
deliverData(std::move(packetData), false);
|
|
if (GetParam().acceptZeroRtt) {
|
|
if (!GetParam().chloSync) {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
loopForWrites();
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
} else {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
}
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessPendingOneRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello();
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
folly::none,
|
|
false));
|
|
deliverData(std::move(packetData));
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportPendingDataTest,
|
|
TestNoCipherProcessingZeroAndOneRttData) {
|
|
server->getNonConstConn().qLogger =
|
|
std::make_shared<quic::FileQLogger>(VantagePoint::Server);
|
|
recvClientHello(false);
|
|
auto data = IOBuf::copyBuffer("bad data");
|
|
StreamId streamId = 2;
|
|
// Write packet with zero rtt keys
|
|
auto packetData = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */,
|
|
std::make_pair(
|
|
LongHeader::Types::ZeroRtt, server->getConn().supportedVersions[0]),
|
|
false));
|
|
deliverData(std::move(packetData), false);
|
|
if (GetParam().acceptZeroRtt) {
|
|
if (!GetParam().chloSync) {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
loopForWrites();
|
|
}
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
} else {
|
|
EXPECT_EQ(server->getConn().streamManager->streamCount(), 0);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData->size(), 1);
|
|
}
|
|
loopForWrites();
|
|
|
|
StreamId streamId2 = 4;
|
|
// Write packet with zero rtt keys
|
|
auto packetData2 = packetToBuf(createStreamPacket(
|
|
*clientConnectionId,
|
|
*server->getConn().serverConnectionId,
|
|
clientNextAppDataPacketNum++,
|
|
streamId2,
|
|
*data,
|
|
0 /* cipherOverhead */,
|
|
0 /* largestAcked */));
|
|
deliverData(std::move(packetData2));
|
|
EXPECT_EQ(
|
|
server->getConn().streamManager->streamCount(),
|
|
GetParam().acceptZeroRtt ? 1 : 0);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData->size(), 1);
|
|
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
EXPECT_EQ(
|
|
server->getConn().streamManager->streamCount(),
|
|
GetParam().acceptZeroRtt ? 2 : 1);
|
|
EXPECT_EQ(server->getConn().pendingZeroRttData, nullptr);
|
|
EXPECT_EQ(server->getConn().pendingOneRttData, nullptr);
|
|
EXPECT_EQ(
|
|
server->getConn().qLogger->scid, server->getConn().serverConnectionId);
|
|
}
|
|
|
|
/**
|
|
* Test handshake process with different parameters:
|
|
* sync CHLO processing, sync CFIN processing, accept 0-rtt
|
|
*/
|
|
class QuicServerTransportHandshakeTest
|
|
: public QuicUnencryptedServerTransportTest,
|
|
public WithParamInterface<FizzHandshakeParam> {
|
|
public:
|
|
~QuicServerTransportHandshakeTest() override {
|
|
// We need an extra pump here for some reason.
|
|
loopForWrites();
|
|
}
|
|
|
|
void initializeServerHandshake() override {
|
|
fakeHandshake = new FakeServerHandshake(
|
|
server->getNonConstConn(),
|
|
FizzServerQuicHandshakeContext::Builder().build(),
|
|
GetParam().chloSync,
|
|
GetParam().cfinSync);
|
|
if (GetParam().acceptZeroRtt) {
|
|
fakeHandshake->allowZeroRttKeys();
|
|
}
|
|
}
|
|
|
|
void expectWriteNewSessionTicket() override {
|
|
std::string appParams("APP params");
|
|
server->setEarlyDataAppParamsFunctions(
|
|
[](const folly::Optional<std::string>&, const Buf&) { return false; },
|
|
[=]() -> Buf { return folly::IOBuf::copyBuffer(appParams); });
|
|
EXPECT_CALL(*getFakeHandshakeLayer(), writeNewSessionTicket(_))
|
|
.WillOnce(Invoke([=](const AppToken& appToken) {
|
|
auto& params = appToken.transportParams.parameters;
|
|
|
|
auto initialMaxData = *getIntegerParameter(
|
|
TransportParameterId::initial_max_data, params);
|
|
EXPECT_EQ(
|
|
initialMaxData,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialConnectionWindowSize);
|
|
|
|
auto initialMaxStreamDataBidiLocal = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_local, params);
|
|
auto initialMaxStreamDataBidiRemote = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_remote,
|
|
params);
|
|
auto initialMaxStreamDataUni = *getIntegerParameter(
|
|
TransportParameterId::initial_max_stream_data_bidi_remote,
|
|
params);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataBidiLocal,
|
|
server->getConn()
|
|
.transportSettings
|
|
.advertisedInitialBidiLocalStreamWindowSize);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataBidiRemote,
|
|
server->getConn()
|
|
.transportSettings
|
|
.advertisedInitialBidiRemoteStreamWindowSize);
|
|
EXPECT_EQ(
|
|
initialMaxStreamDataUni,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialUniStreamWindowSize);
|
|
|
|
auto initialMaxStreamsBidi = *getIntegerParameter(
|
|
TransportParameterId::initial_max_streams_bidi, params);
|
|
auto initialMaxStreamsUni = *getIntegerParameter(
|
|
TransportParameterId::initial_max_streams_uni, params);
|
|
EXPECT_EQ(
|
|
initialMaxStreamsBidi,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialMaxStreamsBidi);
|
|
EXPECT_EQ(
|
|
initialMaxStreamsUni,
|
|
server->getConn()
|
|
.transportSettings.advertisedInitialMaxStreamsUni);
|
|
|
|
auto maxRecvPacketSize = *getIntegerParameter(
|
|
TransportParameterId::max_packet_size, params);
|
|
EXPECT_EQ(
|
|
maxRecvPacketSize,
|
|
server->getConn().transportSettings.maxRecvPacketSize);
|
|
|
|
EXPECT_THAT(
|
|
appToken.sourceAddresses, ContainerEq(expectedSourceToken_));
|
|
|
|
EXPECT_TRUE(folly::IOBufEqualTo()(
|
|
appToken.appParams, folly::IOBuf::copyBuffer(appParams)));
|
|
}));
|
|
}
|
|
|
|
void testSetupConnection() {
|
|
// If 0-rtt is accepted, one rtt write cipher will be available after CHLO
|
|
// is processed
|
|
if (GetParam().acceptZeroRtt) {
|
|
EXPECT_CALL(connSetupCallback, onTransportReady());
|
|
}
|
|
recvClientHello();
|
|
|
|
// If 0-rtt is disabled, one rtt write cipher will be available after CFIN
|
|
// is processed
|
|
if (!GetParam().acceptZeroRtt) {
|
|
EXPECT_CALL(connSetupCallback, onTransportReady());
|
|
}
|
|
// onConnectionIdBound is always invoked after CFIN is processed
|
|
EXPECT_CALL(routingCallback, onConnectionIdBound(_));
|
|
// NST is always written after CFIN is processed
|
|
expectWriteNewSessionTicket();
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
}
|
|
|
|
protected:
|
|
std::vector<folly::IPAddress> expectedSourceToken_;
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
QuicServerTransportHandshakeTests,
|
|
QuicServerTransportHandshakeTest,
|
|
Values(
|
|
FizzHandshakeParam(false, false, false),
|
|
FizzHandshakeParam(false, false, true),
|
|
FizzHandshakeParam(false, true, false),
|
|
FizzHandshakeParam(false, true, true),
|
|
FizzHandshakeParam(true, false, false),
|
|
FizzHandshakeParam(true, false, true),
|
|
FizzHandshakeParam(true, true, false),
|
|
FizzHandshakeParam(true, true, true)));
|
|
|
|
TEST_P(
|
|
QuicServerTransportHandshakeTest,
|
|
TestConnectionSetupWithoutSourceTokenInPsk) {
|
|
serverCtx->setSendNewSessionTicket(false);
|
|
expectedSourceToken_ = {clientAddr.getIPAddress()};
|
|
testSetupConnection();
|
|
}
|
|
|
|
TEST_P(
|
|
QuicServerTransportHandshakeTest,
|
|
TestConnectionSetupWithSourceTokenInPsk) {
|
|
serverCtx->setSendNewSessionTicket(false);
|
|
auto ipAddr = folly::IPAddress("1.2.3.4");
|
|
getFakeHandshakeLayer()->setSourceTokens({ipAddr});
|
|
if (GetParam().acceptZeroRtt) {
|
|
expectedSourceToken_ = {ipAddr, clientAddr.getIPAddress()};
|
|
} else {
|
|
expectedSourceToken_ = {clientAddr.getIPAddress()};
|
|
}
|
|
testSetupConnection();
|
|
}
|
|
|
|
TEST_P(QuicServerTransportHandshakeTest, TestD6DStartCallback) {
|
|
Observer::Config config = {};
|
|
config.pmtuEvents = true;
|
|
auto mockObserver = std::make_unique<MockObserver>(config);
|
|
server->addObserver(mockObserver.get());
|
|
// Set oneRttReader so that maybeStartD6DPriobing passes its check
|
|
auto codec = std::make_unique<QuicReadCodec>(QuicNodeType::Server);
|
|
codec->setOneRttReadCipher(createNoOpAead());
|
|
server->getNonConstConn().readCodec = std::move(codec);
|
|
// And the state too
|
|
server->getNonConstConn().d6d.state = D6DMachineState::BASE;
|
|
EXPECT_CALL(*mockObserver, pmtuProbingStarted(_)).Times(1);
|
|
// CHLO should be enough to trigger probing
|
|
recvClientHello();
|
|
server->removeObserver(mockObserver.get());
|
|
}
|
|
|
|
TEST_F(QuicUnencryptedServerTransportTest, DuplicateOneRttWriteCipher) {
|
|
setupClientReadCodec();
|
|
recvClientHello();
|
|
EXPECT_CALL(handshakeFinishedCallback, onHandshakeFinished());
|
|
recvClientFinished();
|
|
loopForWrites();
|
|
try {
|
|
recvClientHello();
|
|
recvClientFinished();
|
|
FAIL();
|
|
} catch (const std::runtime_error& ex) {
|
|
EXPECT_THAT(ex.what(), HasSubstr("Crypto error"));
|
|
}
|
|
EXPECT_TRUE(server->isClosed());
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestRegisterAndHandleTransportKnobParams) {
|
|
int flag = 0;
|
|
server->registerKnobParamHandler(
|
|
199, [&](QuicServerTransport* /* server_conn */, uint64_t val) {
|
|
EXPECT_EQ(val, 10);
|
|
flag = 1;
|
|
});
|
|
server->registerKnobParamHandler(
|
|
200, [&](QuicServerTransport* /* server_conn */, uint64_t /* val */) {
|
|
flag = 2;
|
|
});
|
|
server->handleKnobParams({
|
|
{199, 10},
|
|
{201, 20},
|
|
});
|
|
|
|
EXPECT_EQ(flag, 1);
|
|
|
|
// ovewrite will fail, the new handler won't be called
|
|
server->registerKnobParamHandler(
|
|
199, [&](QuicServerTransport* /* server_conn */, uint64_t val) {
|
|
EXPECT_EQ(val, 30);
|
|
flag = 3;
|
|
});
|
|
|
|
server->handleKnobParams({
|
|
{199, 10},
|
|
{201, 20},
|
|
});
|
|
EXPECT_EQ(flag, 1);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestRegisterPMTUZeroBlackholeDetection) {
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(
|
|
TransportKnobParamId::ZERO_PMTU_BLACKHOLE_DETECTION),
|
|
1}});
|
|
EXPECT_TRUE(server->getConn().d6d.noBlackholeDetection);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestCCExperimentalKnobHandler) {
|
|
auto mockCongestionController =
|
|
std::make_unique<NiceMock<MockCongestionController>>();
|
|
auto rawCongestionController = mockCongestionController.get();
|
|
server->getNonConstConn().congestionController =
|
|
std::move(mockCongestionController);
|
|
|
|
EXPECT_CALL(*rawCongestionController, setExperimental(true)).Times(2);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 1}});
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 2}});
|
|
|
|
EXPECT_CALL(*rawCongestionController, setExperimental(false)).Times(1);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::CC_EXPERIMENTAL), 0}});
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, TestPacerExperimentalKnobHandler) {
|
|
auto mockPacer = std::make_unique<NiceMock<MockPacer>>();
|
|
auto rawPacer = mockPacer.get();
|
|
server->getNonConstConn().pacer = std::move(mockPacer);
|
|
|
|
EXPECT_CALL(*rawPacer, setExperimental(true)).Times(2);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 1}});
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 2}});
|
|
|
|
EXPECT_CALL(*rawPacer, setExperimental(false)).Times(1);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(TransportKnobParamId::PACER_EXPERIMENTAL), 0}});
|
|
}
|
|
|
|
class QuicServerTransportForciblySetUDUPayloadSizeTest
|
|
: public QuicServerTransportTest {
|
|
public:
|
|
bool getCanIgnorePathMTU() override {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
TEST_F(
|
|
QuicServerTransportForciblySetUDUPayloadSizeTest,
|
|
TestHandleTransportKnobParamForciblySetUDPPayloadSize) {
|
|
EXPECT_LT(server->getConn().udpSendPacketLen, 1452);
|
|
server->handleKnobParams(
|
|
{{static_cast<uint64_t>(
|
|
TransportKnobParamId::FORCIBLY_SET_UDP_PAYLOAD_SIZE),
|
|
1}});
|
|
EXPECT_EQ(server->getConn().udpSendPacketLen, 1452);
|
|
}
|
|
|
|
TEST_F(QuicServerTransportTest, WriteDSR) {
|
|
EXPECT_EQ(server->getConn().dsrPacketCount, 0);
|
|
// Make sure we are post-handshake
|
|
ASSERT_NE(nullptr, server->getConn().oneRttWriteCipher);
|
|
// Rinse anything pending
|
|
server->writeData();
|
|
loopForWrites();
|
|
server->getNonConstConn().outstandings.packets.clear();
|
|
getFakeHandshakeLayer()->setCipherSuite(
|
|
fizz::CipherSuite::TLS_AES_128_GCM_SHA256);
|
|
auto streamId = server->createBidirectionalStream().value();
|
|
server->writeChain(
|
|
streamId, folly::IOBuf::copyBuffer("Allegro Maestoso"), false);
|
|
auto mockDSRSender = std::make_unique<MockDSRPacketizationRequestSender>();
|
|
auto rawDSRSender = mockDSRSender.get();
|
|
server->setDSRPacketizationRequestSender(streamId, std::move(mockDSRSender));
|
|
BufferMeta bufMeta(2000);
|
|
server->writeBufMeta(streamId, bufMeta, true);
|
|
server->writeData();
|
|
EXPECT_FALSE(server->getConn().outstandings.packets.empty());
|
|
EXPECT_TRUE(server->getConn().outstandings.packets.back().isDSRPacket);
|
|
EXPECT_CALL(*rawDSRSender, release()).Times(1);
|
|
server->resetStream(streamId, GenericApplicationErrorCode::NO_ERROR);
|
|
EXPECT_EQ(server->getConn().dsrPacketCount, 1);
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace quic
|