mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-11-09 10:00:57 +03:00
Implement handshake done and cipher dropping.
Summary: This implements the handshake done signal and also cipher dropping. Reviewed By: yangchi Differential Revision: D19584922 fbshipit-source-id: a98bec8f1076393b051ff65a2d8aae7d572b42f5
This commit is contained in:
committed by
Facebook Github Bot
parent
49d262c84c
commit
472e40a902
@@ -62,7 +62,6 @@ constexpr uint64_t kDefaultBufferSpaceAvailable =
|
||||
constexpr std::chrono::microseconds kDefaultMinRtt =
|
||||
std::chrono::microseconds::max();
|
||||
|
||||
// Frames types with values defines in Quic Draft 15+
|
||||
enum class FrameType : uint8_t {
|
||||
PADDING = 0x00,
|
||||
PING = 0x01,
|
||||
@@ -97,8 +96,9 @@ enum class FrameType : uint8_t {
|
||||
CONNECTION_CLOSE = 0x1C,
|
||||
// CONNECTION_CLOSE_APP_ERR frametype is use to indicate application errors
|
||||
CONNECTION_CLOSE_APP_ERR = 0x1D,
|
||||
MIN_STREAM_DATA = 0xFE, // subject to change (https://fburl.com/qpr)
|
||||
EXPIRED_STREAM_DATA = 0xFF, // subject to change (https://fburl.com/qpr)
|
||||
HANDSHAKE_DONE = 0x1E,
|
||||
MIN_STREAM_DATA = 0xFE, // subject to change
|
||||
EXPIRED_STREAM_DATA = 0xFF, // subject to change
|
||||
};
|
||||
|
||||
inline constexpr uint16_t toFrameError(FrameType frame) {
|
||||
@@ -187,7 +187,7 @@ enum class QuicVersion : uint32_t {
|
||||
MVFST = 0xfaceb001,
|
||||
QUIC_DRAFT_22 = 0xFF000016,
|
||||
QUIC_DRAFT_23 = 0xFF000017,
|
||||
QUIC_DRAFT = 0xFF000018, // Draft-24
|
||||
QUIC_DRAFT = 0xFF000019, // Draft-25
|
||||
MVFST_INVALID = 0xfaceb00f,
|
||||
};
|
||||
|
||||
@@ -384,10 +384,6 @@ constexpr std::chrono::milliseconds kHappyEyeballsConnAttemptDelayWithCache =
|
||||
|
||||
constexpr size_t kMaxNumTokenSourceAddresses = 3;
|
||||
|
||||
// Amount of time to retain initial keys until they are dropped after handshake
|
||||
// completion.
|
||||
constexpr std::chrono::seconds kTimeToRetainInitialKeys = 20s;
|
||||
|
||||
// Amount of time to retain zero rtt keys until they are dropped after handshake
|
||||
// completion.
|
||||
constexpr std::chrono::seconds kTimeToRetainZeroRttKeys = 20s;
|
||||
|
||||
@@ -306,13 +306,13 @@ void QuicClientTransport::processPacketData(
|
||||
// If we received an ack for data that we sent in 1-rtt from
|
||||
// the server, we can assume that the server had successfully
|
||||
// derived the 1-rtt keys and hence received the client
|
||||
// finished message. Thus we don't need to retransmit any of
|
||||
// the crypto data any longer.
|
||||
//
|
||||
// This will not cancel oneRttStream.
|
||||
//
|
||||
// TODO: replace this with a better solution later.
|
||||
cancelHandshakeCryptoStreamRetransmissions(*conn_->cryptoState);
|
||||
// finished message. Thus we can drop the ciphers and cancel
|
||||
// the handshake stream.
|
||||
DCHECK(conn_->oneRttWriteCipher);
|
||||
DCHECK(conn_->oneRttWriteHeaderCipher);
|
||||
if (conn_->handshakeWriteCipher) {
|
||||
handshakeConfirmed(*conn_);
|
||||
}
|
||||
}
|
||||
switch (packetFrame.type()) {
|
||||
case QuicWriteFrame::Type::WriteAckFrame_E: {
|
||||
@@ -516,43 +516,54 @@ void QuicClientTransport::processPacketData(
|
||||
handshakeLayer->doHandshake(std::move(cryptoData), encryptionLevel);
|
||||
auto handshakeWriteCipher = handshakeLayer->getHandshakeWriteCipher();
|
||||
auto handshakeReadCipher = handshakeLayer->getHandshakeReadCipher();
|
||||
auto handshakeReadHeaderCipher =
|
||||
handshakeLayer->getHandshakeReadHeaderCipher();
|
||||
auto handshakeWriteHeaderCipher =
|
||||
handshakeLayer->getHandshakeWriteHeaderCipher();
|
||||
auto handshakeReadHeaderCipher =
|
||||
handshakeLayer->getHandshakeReadHeaderCipher();
|
||||
if (handshakeWriteCipher) {
|
||||
CHECK(handshakeWriteHeaderCipher);
|
||||
conn_->handshakeWriteCipher = std::move(handshakeWriteCipher);
|
||||
}
|
||||
if (handshakeWriteHeaderCipher) {
|
||||
conn_->handshakeWriteHeaderCipher = std::move(handshakeWriteHeaderCipher);
|
||||
}
|
||||
if (handshakeReadCipher) {
|
||||
CHECK(handshakeReadHeaderCipher);
|
||||
conn_->readCodec->setHandshakeReadCipher(std::move(handshakeReadCipher));
|
||||
}
|
||||
if (handshakeReadHeaderCipher) {
|
||||
conn_->readCodec->setHandshakeHeaderCipher(
|
||||
std::move(handshakeReadHeaderCipher));
|
||||
}
|
||||
if (conn_->handshakeWriteCipher &&
|
||||
conn_->readCodec->getHandshakeReadCipher()) {
|
||||
// We can now drop the initial ciphers.
|
||||
conn_->initialWriteCipher.reset();
|
||||
conn_->initialHeaderCipher.reset();
|
||||
conn_->readCodec->setInitialReadCipher(nullptr);
|
||||
conn_->readCodec->setInitialHeaderCipher(nullptr);
|
||||
cancelCryptoStream(conn_->cryptoState->initialStream);
|
||||
}
|
||||
auto oneRttWriteCipher = handshakeLayer->getOneRttWriteCipher();
|
||||
auto oneRttReadCipher = handshakeLayer->getOneRttReadCipher();
|
||||
auto oneRttReadHeaderCipher = handshakeLayer->getOneRttReadHeaderCipher();
|
||||
auto oneRttWriteHeaderCipher = handshakeLayer->getOneRttWriteHeaderCipher();
|
||||
bool oneRttKeyDerivationTriggered = false;
|
||||
if (oneRttWriteCipher) {
|
||||
CHECK(oneRttWriteHeaderCipher);
|
||||
conn_->oneRttWriteCipher = std::move(oneRttWriteCipher);
|
||||
conn_->oneRttWriteHeaderCipher = std::move(oneRttWriteHeaderCipher);
|
||||
oneRttKeyDerivationTriggered = true;
|
||||
updatePacingOnKeyEstablished(*conn_);
|
||||
}
|
||||
if (oneRttWriteHeaderCipher) {
|
||||
conn_->oneRttWriteHeaderCipher = std::move(oneRttWriteHeaderCipher);
|
||||
}
|
||||
if (oneRttReadCipher) {
|
||||
CHECK(oneRttReadHeaderCipher);
|
||||
conn_->readCodec->setOneRttReadCipher(std::move(oneRttReadCipher));
|
||||
}
|
||||
if (oneRttReadHeaderCipher) {
|
||||
conn_->readCodec->setOneRttHeaderCipher(
|
||||
std::move(oneRttReadHeaderCipher));
|
||||
}
|
||||
if (oneRttWriteCipher && oneRttReadCipher) {
|
||||
conn_->zeroRttWriteCipher.reset();
|
||||
conn_->zeroRttWriteHeaderCipher.reset();
|
||||
conn_->readCodec->setZeroRttReadCipher(nullptr);
|
||||
conn_->readCodec->setZeroRttHeaderCipher(nullptr);
|
||||
}
|
||||
bool zeroRttRejected = handshakeLayer->getZeroRttRejected().value_or(false);
|
||||
if (zeroRttRejected) {
|
||||
if (conn_->qLogger) {
|
||||
@@ -643,12 +654,6 @@ void QuicClientTransport::processPacketData(
|
||||
markZeroRttPacketsLost(*conn_, markPacketLoss);
|
||||
}
|
||||
}
|
||||
if (protectionLevel == ProtectionType::KeyPhaseZero ||
|
||||
protectionLevel == ProtectionType::KeyPhaseOne) {
|
||||
DCHECK(conn_->oneRttWriteCipher);
|
||||
clientConn_->clientHandshakeLayer->onRecvOneRttProtectedData();
|
||||
conn_->readCodec->onHandshakeDone(receiveTimePoint);
|
||||
}
|
||||
updateAckSendStateOnRecvPacket(
|
||||
*conn_,
|
||||
ackState,
|
||||
@@ -696,7 +701,6 @@ void QuicClientTransport::writeData() {
|
||||
// TODO: replace with write in state machine.
|
||||
// TODO: change to draining when we move the client to have a draining state
|
||||
// as well.
|
||||
auto phase = clientConn_->clientHandshakeLayer->getPhase();
|
||||
QuicVersion version = conn_->version.value_or(*conn_->originalVersion);
|
||||
const ConnectionId& srcConnId = *conn_->clientConnectionId;
|
||||
const ConnectionId* destConnId =
|
||||
@@ -705,29 +709,39 @@ void QuicClientTransport::writeData() {
|
||||
destConnId = &(*conn_->serverConnectionId);
|
||||
}
|
||||
if (closeState_ == CloseState::CLOSED) {
|
||||
// TODO: get rid of phase
|
||||
if (phase == ClientHandshake::Phase::Established &&
|
||||
conn_->oneRttWriteCipher) {
|
||||
CHECK(conn_->oneRttWriteHeaderCipher);
|
||||
writeShortClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
*destConnId /* dst */,
|
||||
conn_->localConnectionError,
|
||||
*conn_->oneRttWriteCipher,
|
||||
*conn_->oneRttWriteHeaderCipher);
|
||||
} else if (conn_->initialWriteCipher) {
|
||||
if (conn_->initialWriteCipher) {
|
||||
CHECK(conn_->initialHeaderCipher);
|
||||
writeLongClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
srcConnId /* src */,
|
||||
*destConnId /* dst */,
|
||||
srcConnId,
|
||||
*destConnId,
|
||||
LongHeader::Types::Initial,
|
||||
conn_->localConnectionError,
|
||||
*conn_->initialWriteCipher,
|
||||
*conn_->initialHeaderCipher,
|
||||
version);
|
||||
} else if (conn_->handshakeWriteCipher) {
|
||||
CHECK(conn_->handshakeWriteHeaderCipher);
|
||||
writeLongClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
srcConnId,
|
||||
*destConnId,
|
||||
LongHeader::Types::Handshake,
|
||||
conn_->localConnectionError,
|
||||
*conn_->handshakeWriteCipher,
|
||||
*conn_->handshakeWriteHeaderCipher,
|
||||
version);
|
||||
} else if (conn_->oneRttWriteCipher) {
|
||||
CHECK(conn_->oneRttWriteHeaderCipher);
|
||||
writeShortClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
*destConnId,
|
||||
conn_->localConnectionError,
|
||||
*conn_->oneRttWriteCipher,
|
||||
*conn_->oneRttWriteHeaderCipher);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -736,15 +750,14 @@ void QuicClientTransport::writeData() {
|
||||
(isConnectionPaced(*conn_)
|
||||
? conn_->pacer->updateAndGetWriteBatchSize(Clock::now())
|
||||
: conn_->transportSettings.writeConnectionDataPacketsLimit);
|
||||
if (conn_->initialWriteCipher) {
|
||||
CryptoStreamScheduler initialScheduler(
|
||||
*conn_, *getCryptoStream(*conn_->cryptoState, EncryptionLevel::Initial));
|
||||
CryptoStreamScheduler handshakeScheduler(
|
||||
*conn_,
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Handshake));
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Initial));
|
||||
|
||||
if (initialScheduler.hasData() ||
|
||||
(conn_->ackStates.initialAckState.needsToSendAckImmediately &&
|
||||
hasAcksToSchedule(conn_->ackStates.initialAckState))) {
|
||||
CHECK(conn_->initialWriteCipher);
|
||||
CHECK(conn_->initialHeaderCipher);
|
||||
packetLimit -= writeCryptoAndAckDataToSocket(
|
||||
*socket_,
|
||||
@@ -761,10 +774,14 @@ void QuicClientTransport::writeData() {
|
||||
if (!packetLimit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (conn_->handshakeWriteCipher) {
|
||||
CryptoStreamScheduler handshakeScheduler(
|
||||
*conn_,
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Handshake));
|
||||
if (handshakeScheduler.hasData() ||
|
||||
(conn_->ackStates.handshakeAckState.needsToSendAckImmediately &&
|
||||
hasAcksToSchedule(conn_->ackStates.handshakeAckState))) {
|
||||
CHECK(conn_->handshakeWriteCipher);
|
||||
CHECK(conn_->handshakeWriteHeaderCipher);
|
||||
packetLimit -= writeCryptoAndAckDataToSocket(
|
||||
*socket_,
|
||||
@@ -780,6 +797,7 @@ void QuicClientTransport::writeData() {
|
||||
if (!packetLimit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (clientConn_->zeroRttWriteCipher && !conn_->oneRttWriteCipher) {
|
||||
CHECK(clientConn_->zeroRttWriteHeaderCipher);
|
||||
packetLimit -= writeZeroRttDataToSocket(
|
||||
|
||||
@@ -120,15 +120,9 @@ ClientHandshake::getZeroRttWriteHeaderCipher() {
|
||||
return std::move(zeroRttWriteHeaderCipher_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the crypto layer that we received one rtt protected data.
|
||||
* This allows us to know that the peer has implicitly acked the 1-rtt keys.
|
||||
*/
|
||||
void ClientHandshake::onRecvOneRttProtectedData() {
|
||||
if (phase_ != Phase::Established) {
|
||||
void ClientHandshake::handshakeConfirmed() {
|
||||
phase_ = Phase::Established;
|
||||
}
|
||||
}
|
||||
|
||||
ClientHandshake::Phase ClientHandshake::getPhase() const {
|
||||
return phase_;
|
||||
|
||||
@@ -122,10 +122,9 @@ class ClientHandshake : public Handshake {
|
||||
virtual const CryptoFactory& getCryptoFactory() const = 0;
|
||||
|
||||
/**
|
||||
* Notify the crypto layer that we received one rtt protected data.
|
||||
* This allows us to know that the peer has implicitly acked the 1-rtt keys.
|
||||
* Triggered when we have received a handshake done frame from the server.
|
||||
*/
|
||||
void onRecvOneRttProtectedData();
|
||||
void handshakeConfirmed() override;
|
||||
|
||||
Phase getPhase() const;
|
||||
|
||||
|
||||
@@ -294,7 +294,7 @@ TEST_F(ClientHandshakeTest, TestHandshakeSuccess) {
|
||||
|
||||
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::OneRttKeysDerived);
|
||||
|
||||
handshake->onRecvOneRttProtectedData();
|
||||
handshake->handshakeConfirmed();
|
||||
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
|
||||
EXPECT_FALSE(zeroRttRejected.has_value());
|
||||
EXPECT_TRUE(handshakeSuccess);
|
||||
@@ -497,7 +497,7 @@ TEST_F(ClientHandshakeZeroRttTest, TestZeroRttSuccess) {
|
||||
EXPECT_FALSE(zeroRttRejected.has_value());
|
||||
expectZeroRttCipher(true, true);
|
||||
clientServerRound();
|
||||
handshake->onRecvOneRttProtectedData();
|
||||
handshake->handshakeConfirmed();
|
||||
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
|
||||
EXPECT_EQ(handshake->getApplicationProtocol(), "h1q-fb");
|
||||
}
|
||||
@@ -521,7 +521,7 @@ TEST_F(ClientHandshakeZeroRttReject, TestZeroRttRejection) {
|
||||
// We will still keep the zero rtt key lying around.
|
||||
expectZeroRttCipher(true, true);
|
||||
clientServerRound();
|
||||
handshake->onRecvOneRttProtectedData();
|
||||
handshake->handshakeConfirmed();
|
||||
EXPECT_EQ(handshake->getPhase(), ClientHandshake::Phase::Established);
|
||||
}
|
||||
|
||||
|
||||
@@ -372,18 +372,14 @@ QuicClientTransportIntegrationTest::sendRequestAndResponse(
|
||||
auto streamData = new StreamData(streamId);
|
||||
auto dataCopy = std::shared_ptr<folly::IOBuf>(std::move(data));
|
||||
EXPECT_CALL(*readCallback, readAvailable(streamId))
|
||||
.WillRepeatedly(Invoke([c = client.get(),
|
||||
id = streamId,
|
||||
streamData,
|
||||
dataCopy](auto) mutable {
|
||||
EXPECT_EQ(
|
||||
dynamic_cast<ClientHandshake*>(c->getConn().handshakeLayer.get())
|
||||
->getPhase(),
|
||||
ClientHandshake::Phase::Established);
|
||||
.WillRepeatedly(
|
||||
Invoke([c = client.get(), id = streamId, streamData, dataCopy](
|
||||
auto) mutable {
|
||||
auto readData = c->read(id, 1000);
|
||||
auto copy = readData->first->clone();
|
||||
LOG(INFO) << "Client received data="
|
||||
<< copy->moveToFbString().toStdString() << " on stream=" << id
|
||||
<< copy->moveToFbString().toStdString()
|
||||
<< " on stream=" << id
|
||||
<< " read=" << readData->first->computeChainDataLength()
|
||||
<< " sent=" << dataCopy->computeChainDataLength();
|
||||
streamData->append(std::move(readData->first), readData->second);
|
||||
@@ -428,6 +424,16 @@ TEST_P(QuicClientTransportIntegrationTest, NetworkTest) {
|
||||
auto expected = std::shared_ptr<IOBuf>(IOBuf::copyBuffer("echo "));
|
||||
expected->prependChain(data->clone());
|
||||
sendRequestAndResponseAndWait(*expected, data->clone(), streamId, &readCb);
|
||||
if (getVersion() == QuicVersion::QUIC_DRAFT) {
|
||||
EXPECT_EQ(client->getConn().initialWriteCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().initialHeaderCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().handshakeWriteCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().handshakeWriteHeaderCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().readCodec->getInitialCipher(), nullptr);
|
||||
EXPECT_EQ(client->getConn().readCodec->getInitialHeaderCipher(), nullptr);
|
||||
EXPECT_EQ(client->getConn().readCodec->getHandshakeReadCipher(), nullptr);
|
||||
EXPECT_EQ(client->getConn().readCodec->getHandshakeHeaderCipher(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(QuicClientTransportIntegrationTest, FlowControlLimitedTest) {
|
||||
@@ -1223,6 +1229,10 @@ class FakeOneRttHandshakeLayer : public ClientHandshake {
|
||||
void doHandshake(std::unique_ptr<folly::IOBuf>, EncryptionLevel) override {
|
||||
EXPECT_EQ(writeBuf.get(), nullptr);
|
||||
if (getPhase() == Phase::Initial) {
|
||||
handshakeWriteCipher_ = test::createNoOpAead();
|
||||
handshakeWriteHeaderCipher_ = test::createNoOpHeaderCipher();
|
||||
handshakeReadCipher_ = test::createNoOpAead();
|
||||
handshakeReadHeaderCipher_ = test::createNoOpHeaderCipher();
|
||||
writeDataToQuicStream(
|
||||
conn_->cryptoState->handshakeStream,
|
||||
IOBuf::copyBuffer("ClientFinished"));
|
||||
@@ -1371,17 +1381,13 @@ class QuicClientTransportTest : public Test {
|
||||
virtual void setFakeHandshakeCiphers() {
|
||||
auto readAead = test::createNoOpAead();
|
||||
auto writeAead = test::createNoOpAead();
|
||||
auto handshakeReadAead = test::createNoOpAead();
|
||||
auto handshakeWriteAead = test::createNoOpAead();
|
||||
mockClientHandshake->setHandshakeReadCipher(std::move(handshakeReadAead));
|
||||
mockClientHandshake->setHandshakeWriteCipher(std::move(handshakeWriteAead));
|
||||
mockClientHandshake->setHandshakeReadCipher(nullptr);
|
||||
mockClientHandshake->setHandshakeWriteCipher(nullptr);
|
||||
mockClientHandshake->setOneRttReadCipher(std::move(readAead));
|
||||
mockClientHandshake->setOneRttWriteCipher(std::move(writeAead));
|
||||
|
||||
mockClientHandshake->setHandshakeReadHeaderCipher(
|
||||
test::createNoOpHeaderCipher());
|
||||
mockClientHandshake->setHandshakeWriteHeaderCipher(
|
||||
test::createNoOpHeaderCipher());
|
||||
mockClientHandshake->setHandshakeReadHeaderCipher(nullptr);
|
||||
mockClientHandshake->setHandshakeWriteHeaderCipher(nullptr);
|
||||
mockClientHandshake->setOneRttWriteHeaderCipher(
|
||||
test::createNoOpHeaderCipher());
|
||||
mockClientHandshake->setOneRttReadHeaderCipher(
|
||||
@@ -1512,12 +1518,16 @@ class QuicClientTransportTest : public Test {
|
||||
|
||||
void verifyCiphers() {
|
||||
EXPECT_NE(client->getConn().oneRttWriteCipher, nullptr);
|
||||
EXPECT_NE(client->getConn().oneRttWriteHeaderCipher, nullptr);
|
||||
EXPECT_NE(client->getConn().handshakeWriteCipher, nullptr);
|
||||
EXPECT_NE(client->getConn().handshakeWriteHeaderCipher, nullptr);
|
||||
EXPECT_NE(client->getConn().oneRttWriteHeaderCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().initialWriteCipher, nullptr);
|
||||
EXPECT_EQ(client->getConn().initialHeaderCipher, nullptr);
|
||||
|
||||
EXPECT_NE(client->getConn().readCodec->getHandshakeHeaderCipher(), nullptr);
|
||||
EXPECT_NE(client->getConn().readCodec->getOneRttReadCipher(), nullptr);
|
||||
EXPECT_NE(client->getConn().readCodec->getOneRttHeaderCipher(), nullptr);
|
||||
EXPECT_NE(client->getConn().readCodec->getHandshakeReadCipher(), nullptr);
|
||||
EXPECT_NE(client->getConn().readCodec->getHandshakeHeaderCipher(), nullptr);
|
||||
}
|
||||
|
||||
void deliverDataWithoutErrorCheck(
|
||||
@@ -1587,12 +1597,12 @@ class QuicClientTransportTest : public Test {
|
||||
}
|
||||
}
|
||||
if (shortHeader) {
|
||||
EXPECT_GT(numShort, 0);
|
||||
ASSERT_GT(numShort, 0);
|
||||
}
|
||||
if (longHeader) {
|
||||
EXPECT_GT(numLong, 0);
|
||||
CHECK_GT(numLong, 0);
|
||||
}
|
||||
EXPECT_EQ(numOthers, 0);
|
||||
ASSERT_EQ(numOthers, 0);
|
||||
}
|
||||
|
||||
RegularQuicPacket* parseRegularQuicPacket(CodecResult& codecResult) {
|
||||
@@ -1787,6 +1797,8 @@ TEST_F(QuicClientTransportTest, AddNewPeerAddressSetsPacketSize) {
|
||||
|
||||
TEST_F(QuicClientTransportTest, onNetworkSwitchNoReplace) {
|
||||
client->getNonConstConn().oneRttWriteCipher = test::createNoOpAead();
|
||||
client->getNonConstConn().oneRttWriteHeaderCipher =
|
||||
test::createNoOpHeaderCipher();
|
||||
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Client);
|
||||
client->setQLogger(mockQLogger);
|
||||
|
||||
@@ -1797,6 +1809,8 @@ TEST_F(QuicClientTransportTest, onNetworkSwitchNoReplace) {
|
||||
|
||||
TEST_F(QuicClientTransportTest, onNetworkSwitchReplaceAfterHandshake) {
|
||||
client->getNonConstConn().oneRttWriteCipher = test::createNoOpAead();
|
||||
client->getNonConstConn().oneRttWriteHeaderCipher =
|
||||
test::createNoOpHeaderCipher();
|
||||
auto mockQLogger = std::make_shared<MockQLogger>(VantagePoint::Client);
|
||||
client->setQLogger(mockQLogger);
|
||||
|
||||
@@ -3126,6 +3140,7 @@ TEST_P(QuicClientTransportAfterStartTest, ReadStreamCoalesced) {
|
||||
auto garbage = IOBuf::copyBuffer("garbage");
|
||||
auto initialCipher = cryptoFactory.getServerInitialCipher(
|
||||
*serverChosenConnId, QuicVersion::MVFST);
|
||||
auto initialHeaderCipher = test::createNoOpHeaderCipher();
|
||||
auto firstPacketNum = appDataPacketNum++;
|
||||
auto packet1 = packetToBufCleartext(
|
||||
createStreamPacket(
|
||||
@@ -3138,7 +3153,7 @@ TEST_P(QuicClientTransportAfterStartTest, ReadStreamCoalesced) {
|
||||
0 /* largestAcked */,
|
||||
std::make_pair(LongHeader::Types::Initial, QuicVersion::MVFST)),
|
||||
*initialCipher,
|
||||
getInitialHeaderCipher(),
|
||||
*initialHeaderCipher,
|
||||
firstPacketNum);
|
||||
packet1->coalesce();
|
||||
auto packet2 = packetToBuf(createStreamPacket(
|
||||
@@ -3177,6 +3192,7 @@ TEST_F(QuicClientTransportAfterStartTest, ReadStreamCoalescedMany) {
|
||||
auto garbage = IOBuf::copyBuffer("garbage");
|
||||
auto initialCipher = cryptoFactory.getServerInitialCipher(
|
||||
*serverChosenConnId, QuicVersion::MVFST);
|
||||
auto initialHeaderCipher = test::createNoOpHeaderCipher();
|
||||
auto packetNum = appDataPacketNum++;
|
||||
auto packet1 = packetToBufCleartext(
|
||||
createStreamPacket(
|
||||
@@ -3189,7 +3205,7 @@ TEST_F(QuicClientTransportAfterStartTest, ReadStreamCoalescedMany) {
|
||||
0 /* largestAcked */,
|
||||
std::make_pair(LongHeader::Types::Initial, QuicVersion::MVFST)),
|
||||
*initialCipher,
|
||||
getInitialHeaderCipher(),
|
||||
*initialHeaderCipher,
|
||||
packetNum);
|
||||
packets.append(std::move(packet1));
|
||||
}
|
||||
@@ -3261,6 +3277,32 @@ TEST_F(QuicClientTransportAfterStartTest, RecvPathChallengeAvailablePeerId) {
|
||||
EXPECT_EQ(pathResponse.pathData, pathChallenge.pathData);
|
||||
}
|
||||
|
||||
TEST_F(QuicClientTransportAfterStartTest, HandshakeDoneDrop) {
|
||||
auto& conn = client->getNonConstConn();
|
||||
conn.handshakeWriteCipher = test::createNoOpAead();
|
||||
conn.handshakeWriteHeaderCipher = test::createNoOpHeaderCipher();
|
||||
conn.readCodec->setHandshakeReadCipher(test::createNoOpAead());
|
||||
conn.readCodec->setHandshakeHeaderCipher(test::createNoOpHeaderCipher());
|
||||
conn.cryptoState->handshakeStream.writeBuffer.append(
|
||||
folly::IOBuf::copyBuffer("blah"));
|
||||
|
||||
ShortHeader header(ProtectionType::KeyPhaseZero, *conn.clientConnectionId, 1);
|
||||
RegularQuicPacketBuilder builder(
|
||||
conn.udpSendPacketLen, std::move(header), 0 /* largestAcked */);
|
||||
ASSERT_TRUE(builder.canBuildPacket());
|
||||
writeSimpleFrame(QuicSimpleFrame(HandshakeDoneFrame()), builder);
|
||||
|
||||
auto packet = std::move(builder).buildPacket();
|
||||
auto data = packetToBuf(packet);
|
||||
deliverData(data->coalesce(), false);
|
||||
|
||||
EXPECT_EQ(conn.handshakeWriteCipher, nullptr);
|
||||
EXPECT_EQ(conn.handshakeWriteHeaderCipher, nullptr);
|
||||
EXPECT_EQ(conn.readCodec->getHandshakeReadCipher(), nullptr);
|
||||
EXPECT_EQ(conn.readCodec->getHandshakeHeaderCipher(), nullptr);
|
||||
EXPECT_EQ(conn.cryptoState->handshakeStream.writeBuffer.chainLength(), 0);
|
||||
}
|
||||
|
||||
bool verifyFramePresent(
|
||||
std::vector<std::unique_ptr<folly::IOBuf>>& socketWrites,
|
||||
QuicReadCodec& readCodec,
|
||||
@@ -3493,31 +3535,9 @@ TEST_F(QuicClientTransportAfterStartTest, RecvRetransmittedHandshakeData) {
|
||||
TEST_F(QuicClientTransportAfterStartTest, RecvAckOfCryptoStream) {
|
||||
// Simulate ack from server
|
||||
auto& cryptoState = client->getConn().cryptoState;
|
||||
EXPECT_GT(cryptoState->initialStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_GT(cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(cryptoState->oneRttStream.retransmissionBuffer.size(), 0);
|
||||
|
||||
auto& aead = getInitialCipher();
|
||||
auto& headerCipher = getInitialHeaderCipher();
|
||||
// initial
|
||||
{
|
||||
AckBlocks acks;
|
||||
auto start = getFirstOutstandingPacket(
|
||||
client->getNonConstConn(), PacketNumberSpace::Initial)
|
||||
->packet.header.getPacketSequenceNum();
|
||||
auto end = getLastOutstandingPacket(
|
||||
client->getNonConstConn(), PacketNumberSpace::Initial)
|
||||
->packet.header.getPacketSequenceNum();
|
||||
acks.insert(start, end);
|
||||
auto pn = initialPacketNum++;
|
||||
auto ackPkt = createAckPacket(
|
||||
client->getNonConstConn(), pn, acks, PacketNumberSpace::Initial, &aead);
|
||||
deliverData(
|
||||
packetToBufCleartext(ackPkt, aead, headerCipher, pn)->coalesce());
|
||||
EXPECT_EQ(cryptoState->initialStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_GT(cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(cryptoState->oneRttStream.retransmissionBuffer.size(), 0);
|
||||
}
|
||||
// handshake
|
||||
{
|
||||
AckBlocks acks;
|
||||
@@ -3539,9 +3559,6 @@ TEST_F(QuicClientTransportAfterStartTest, RecvAckOfCryptoStream) {
|
||||
}
|
||||
|
||||
TEST_F(QuicClientTransportAfterStartTest, RecvOneRttAck) {
|
||||
EXPECT_GT(
|
||||
client->getConn().cryptoState->initialStream.retransmissionBuffer.size(),
|
||||
0);
|
||||
EXPECT_GT(
|
||||
client->getConn()
|
||||
.cryptoState->handshakeStream.retransmissionBuffer.size(),
|
||||
@@ -3570,9 +3587,6 @@ TEST_F(QuicClientTransportAfterStartTest, RecvOneRttAck) {
|
||||
deliverData(ackPacket->coalesce());
|
||||
|
||||
// Should have canceled retransmissions
|
||||
EXPECT_EQ(
|
||||
client->getConn().cryptoState->initialStream.retransmissionBuffer.size(),
|
||||
0);
|
||||
EXPECT_EQ(
|
||||
client->getConn()
|
||||
.cryptoState->handshakeStream.retransmissionBuffer.size(),
|
||||
@@ -3604,39 +3618,17 @@ TEST_P(QuicClientTransportAfterStartTestClose, CloseConnectionWithError) {
|
||||
std::string("stopping")));
|
||||
EXPECT_TRUE(verifyFramePresent(
|
||||
socketWrites,
|
||||
*makeEncryptedCodec(),
|
||||
*makeHandshakeCodec(),
|
||||
QuicFrame::Type::ConnectionCloseFrame_E));
|
||||
} else {
|
||||
client->close(folly::none);
|
||||
EXPECT_TRUE(verifyFramePresent(
|
||||
socketWrites,
|
||||
*makeEncryptedCodec(),
|
||||
*makeHandshakeCodec(),
|
||||
QuicFrame::Type::ConnectionCloseFrame_E));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
QuicClientTransportAfterStartTest,
|
||||
HandshakeCipherTimeoutAfterFirstData) {
|
||||
StreamId streamId = client->createBidirectionalStream().value();
|
||||
|
||||
EXPECT_NE(client->getConn().readCodec->getInitialCipher(), nullptr);
|
||||
auto expected = IOBuf::copyBuffer("hello");
|
||||
auto packet = packetToBuf(createStreamPacket(
|
||||
*serverChosenConnId /* src */,
|
||||
*originalConnId /* dest */,
|
||||
appDataPacketNum++,
|
||||
streamId,
|
||||
*expected,
|
||||
0 /* cipherOverhead */,
|
||||
0 /* largestAcked */,
|
||||
folly::none,
|
||||
true));
|
||||
deliverData(packet->coalesce());
|
||||
EXPECT_NE(client->getConn().readCodec->getInitialCipher(), nullptr);
|
||||
EXPECT_TRUE(client->getConn().readCodec->getHandshakeDoneTime().has_value());
|
||||
}
|
||||
|
||||
TEST_F(QuicClientTransportAfterStartTest, IdleTimerResetOnRecvNewData) {
|
||||
// spend some time looping the evb
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
@@ -3860,6 +3852,7 @@ TEST_F(QuicClientTransportAfterStartTest, WrongCleartextCipher) {
|
||||
|
||||
auto initialCipher = cryptoFactory.getServerInitialCipher(
|
||||
*serverChosenConnId, QuicVersion::MVFST);
|
||||
auto initialHeaderCipher = test::createNoOpHeaderCipher();
|
||||
auto packet = packetToBufCleartext(
|
||||
createStreamPacket(
|
||||
*serverChosenConnId /* src */,
|
||||
@@ -3871,7 +3864,7 @@ TEST_F(QuicClientTransportAfterStartTest, WrongCleartextCipher) {
|
||||
0 /* largestAcked */,
|
||||
std::make_pair(LongHeader::Types::Initial, QuicVersion::MVFST)),
|
||||
*initialCipher,
|
||||
getInitialHeaderCipher(),
|
||||
*initialHeaderCipher,
|
||||
nextPacketNum);
|
||||
deliverData(packet->coalesce());
|
||||
}
|
||||
|
||||
@@ -678,6 +678,10 @@ ExpiredStreamDataFrame decodeExpiredStreamDataFrame(folly::io::Cursor& cursor) {
|
||||
folly::to<StreamId>(streamId->first), minimumStreamOffset->first);
|
||||
}
|
||||
|
||||
HandshakeDoneFrame decodeHandshakeDoneFrame(folly::io::Cursor& /*cursor*/) {
|
||||
return HandshakeDoneFrame();
|
||||
}
|
||||
|
||||
QuicFrame parseFrame(
|
||||
BufQueue& queue,
|
||||
const PacketHeader& header,
|
||||
@@ -766,6 +770,8 @@ QuicFrame parseFrame(
|
||||
return QuicFrame(decodeMinStreamDataFrame(cursor));
|
||||
case FrameType::EXPIRED_STREAM_DATA:
|
||||
return QuicFrame(decodeExpiredStreamDataFrame(cursor));
|
||||
case FrameType::HANDSHAKE_DONE:
|
||||
return QuicFrame(decodeHandshakeDoneFrame(cursor));
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
error = true;
|
||||
|
||||
@@ -139,6 +139,8 @@ ReadCryptoFrame decodeCryptoFrame(folly::io::Cursor& cursor);
|
||||
|
||||
ReadNewTokenFrame decodeNewTokenFrame(folly::io::Cursor& cursor);
|
||||
|
||||
HandshakeDoneFrame decodeHandshakeDoneFrame(folly::io::Cursor& cursor);
|
||||
|
||||
/**
|
||||
* Parse the Invariant fields in Long Header.
|
||||
*
|
||||
|
||||
@@ -125,15 +125,12 @@ CodecResult QuicReadCodec::parseLongHeaderPacket(
|
||||
auto protectionType = longHeader.getProtectionType();
|
||||
switch (protectionType) {
|
||||
case ProtectionType::Initial:
|
||||
if (handshakeDoneTime_) {
|
||||
auto timeBetween = Clock::now() - *handshakeDoneTime_;
|
||||
if (timeBetween > kTimeToRetainZeroRttKeys) {
|
||||
if (!initialHeaderCipher_) {
|
||||
VLOG(4) << nodeToString(nodeType_)
|
||||
<< " dropping initial packet for exceeding key timeout"
|
||||
<< " dropping initial packet after initial keys dropped"
|
||||
<< connIdToHex();
|
||||
return CodecResult(Nothing());
|
||||
}
|
||||
}
|
||||
headerCipher = initialHeaderCipher_.get();
|
||||
cipher = initialReadCipher_.get();
|
||||
break;
|
||||
@@ -143,6 +140,7 @@ CodecResult QuicReadCodec::parseLongHeaderPacket(
|
||||
break;
|
||||
case ProtectionType::ZeroRtt:
|
||||
if (handshakeDoneTime_) {
|
||||
// TODO actually drop the 0-rtt keys in addition to dropping packets.
|
||||
auto timeBetween = Clock::now() - *handshakeDoneTime_;
|
||||
if (timeBetween > kTimeToRetainZeroRttKeys) {
|
||||
VLOG(4) << nodeToString(nodeType_)
|
||||
|
||||
@@ -494,6 +494,18 @@ size_t writeSimpleFrame(
|
||||
// no space left in packet
|
||||
return size_t(0);
|
||||
}
|
||||
case QuicSimpleFrame::Type::HandshakeDoneFrame_E: {
|
||||
const HandshakeDoneFrame& handshakeDoneFrame =
|
||||
*frame.asHandshakeDoneFrame();
|
||||
QuicInteger intFrameType(static_cast<uint8_t>(FrameType::HANDSHAKE_DONE));
|
||||
if (packetSpaceCheck(spaceLeft, intFrameType.getSize())) {
|
||||
builder.write(intFrameType);
|
||||
builder.appendFrame(QuicSimpleFrame(handshakeDoneFrame));
|
||||
return intFrameType.getSize();
|
||||
}
|
||||
// no space left in packet
|
||||
return size_t(0);
|
||||
}
|
||||
}
|
||||
folly::assume_unreachable();
|
||||
}
|
||||
|
||||
@@ -396,6 +396,8 @@ std::string toString(FrameType frame) {
|
||||
return "MIN_STREAM_DATA";
|
||||
case FrameType::EXPIRED_STREAM_DATA:
|
||||
return "EXPIRED_STREAM_DATA";
|
||||
case FrameType::HANDSHAKE_DONE:
|
||||
return "HANDSHAKE_DONE";
|
||||
}
|
||||
LOG(WARNING) << "toString has unhandled frame type";
|
||||
return "UNKNOWN";
|
||||
|
||||
@@ -549,6 +549,12 @@ struct ConnectionCloseFrame {
|
||||
}
|
||||
};
|
||||
|
||||
struct HandshakeDoneFrame {
|
||||
bool operator==(const HandshakeDoneFrame& /*rhs*/) const {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
// Frame to represent ones we skip
|
||||
struct NoopFrame {
|
||||
bool operator==(const NoopFrame&) const {
|
||||
@@ -572,7 +578,8 @@ struct StatelessReset {
|
||||
F(NewConnectionIdFrame, __VA_ARGS__) \
|
||||
F(MaxStreamsFrame, __VA_ARGS__) \
|
||||
F(RetireConnectionIdFrame, __VA_ARGS__) \
|
||||
F(PingFrame, __VA_ARGS__)
|
||||
F(PingFrame, __VA_ARGS__) \
|
||||
F(HandshakeDoneFrame, __VA_ARGS__)
|
||||
|
||||
DECLARE_VARIANT_TYPE(QuicSimpleFrame, QUIC_SIMPLE_FRAME)
|
||||
|
||||
|
||||
@@ -534,7 +534,7 @@ TEST_F(QuicReadCodecTest, TestHandshakeDone) {
|
||||
auto packetQueue =
|
||||
bufToQueue(packetToBufCleartext(packet, *aead, *headerCipher, packetNum));
|
||||
EXPECT_TRUE(parseSuccess(codec->parsePacket(packetQueue, ackStates)));
|
||||
codec->onHandshakeDone(Clock::now() - kTimeToRetainInitialKeys * 2);
|
||||
codec->onHandshakeDone(Clock::now());
|
||||
EXPECT_FALSE(parseSuccess(codec->parsePacket(packetQueue, ackStates)));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ class Handshake {
|
||||
|
||||
virtual const folly::Optional<std::string>& getApplicationProtocol()
|
||||
const = 0;
|
||||
|
||||
virtual void handshakeConfirmed() {
|
||||
LOG(FATAL) << "Not implemented";
|
||||
}
|
||||
};
|
||||
|
||||
constexpr folly::StringPiece kQuicDraft17Salt =
|
||||
|
||||
@@ -65,6 +65,10 @@ void addQuicSimpleFrameToEvent(
|
||||
frame.sequenceNumber));
|
||||
break;
|
||||
}
|
||||
case quic::QuicSimpleFrame::Type::HandshakeDoneFrame_E: {
|
||||
event->frames.push_back(std::make_unique<quic::HandshakeDoneFrameLog>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -219,6 +219,12 @@ folly::dynamic ReadNewTokenFrameLog::toDynamic() const {
|
||||
return d;
|
||||
}
|
||||
|
||||
folly::dynamic HandshakeDoneFrameLog::toDynamic() const {
|
||||
folly::dynamic d = folly::dynamic::object();
|
||||
d["frame_type"] = toString(FrameType::HANDSHAKE_DONE);
|
||||
return d;
|
||||
}
|
||||
|
||||
folly::dynamic VersionNegotiationLog::toDynamic() const {
|
||||
folly::dynamic d = folly::dynamic::object();
|
||||
d = folly::dynamic::array();
|
||||
|
||||
@@ -267,8 +267,7 @@ class RetireConnectionIdFrameLog : public QLogFrame {
|
||||
public:
|
||||
uint64_t sequence;
|
||||
|
||||
RetireConnectionIdFrameLog(uint64_t sequenceIn)
|
||||
: sequence(sequenceIn) {}
|
||||
RetireConnectionIdFrameLog(uint64_t sequenceIn) : sequence(sequenceIn) {}
|
||||
|
||||
~RetireConnectionIdFrameLog() override = default;
|
||||
folly::dynamic toDynamic() const override;
|
||||
@@ -281,6 +280,13 @@ class ReadNewTokenFrameLog : public QLogFrame {
|
||||
folly::dynamic toDynamic() const override;
|
||||
};
|
||||
|
||||
class HandshakeDoneFrameLog : public QLogFrame {
|
||||
public:
|
||||
HandshakeDoneFrameLog() = default;
|
||||
~HandshakeDoneFrameLog() override = default;
|
||||
folly::dynamic toDynamic() const override;
|
||||
};
|
||||
|
||||
class VersionNegotiationLog {
|
||||
public:
|
||||
std::vector<QuicVersion> versions;
|
||||
|
||||
@@ -542,7 +542,7 @@ TEST_F(QuicLossFunctionsTest, TestMarkCryptoLostAfterCancelRetransmission) {
|
||||
EXPECT_GT(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
auto& packet = conn->outstandingPackets.front().packet;
|
||||
auto packetNum = packet.header.getPacketSequenceNum();
|
||||
cancelHandshakeCryptoStreamRetransmissions(*conn->cryptoState);
|
||||
cancelCryptoStream(conn->cryptoState->handshakeStream);
|
||||
markPacketLoss(*conn, packet, false, packetNum);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 0);
|
||||
@@ -579,7 +579,7 @@ TEST_F(QuicLossFunctionsTest, TestMarkCryptoLostCancel) {
|
||||
markPacketLoss(*conn, packet, false, packetNum);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 1);
|
||||
cancelHandshakeCryptoStreamRetransmissions(*conn->cryptoState);
|
||||
cancelCryptoStream(conn->cryptoState->handshakeStream);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(conn->cryptoState->handshakeStream.lossBuffer.size(), 0);
|
||||
}
|
||||
|
||||
@@ -157,51 +157,50 @@ void QuicServerTransport::writeData() {
|
||||
return;
|
||||
}
|
||||
updateLargestReceivedPacketsAtLastCloseSent(*conn_);
|
||||
if (conn_->oneRttWriteCipher && conn_->readCodec->getOneRttReadCipher()) {
|
||||
CHECK(conn_->oneRttWriteHeaderCipher);
|
||||
// We do not process handshake data after we are closed. It is
|
||||
// possible that we closed the transport while handshake data was
|
||||
// pending in which case we would not derive the 1-RTT keys. We
|
||||
// shouldn't send a long header at this point, because the client may
|
||||
// have already dropped its handshake keys.
|
||||
writeShortClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
destConnId /* dst */,
|
||||
conn_->localConnectionError,
|
||||
*conn_->oneRttWriteCipher,
|
||||
*conn_->oneRttWriteHeaderCipher);
|
||||
} else if (conn_->initialWriteCipher) {
|
||||
if (conn_->initialWriteCipher) {
|
||||
CHECK(conn_->initialHeaderCipher);
|
||||
writeLongClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
srcConnId /* src */,
|
||||
destConnId /* dst */,
|
||||
srcConnId,
|
||||
destConnId,
|
||||
LongHeader::Types::Initial,
|
||||
conn_->localConnectionError,
|
||||
*conn_->initialWriteCipher,
|
||||
*conn_->initialHeaderCipher,
|
||||
version);
|
||||
} else if (conn_->handshakeWriteCipher) {
|
||||
CHECK(conn_->handshakeWriteHeaderCipher);
|
||||
writeLongClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
srcConnId,
|
||||
destConnId,
|
||||
LongHeader::Types::Initial,
|
||||
conn_->localConnectionError,
|
||||
*conn_->handshakeWriteCipher,
|
||||
*conn_->handshakeWriteHeaderCipher,
|
||||
version);
|
||||
} else if (conn_->oneRttWriteCipher) {
|
||||
CHECK(conn_->oneRttWriteHeaderCipher);
|
||||
writeShortClose(
|
||||
*socket_,
|
||||
*conn_,
|
||||
destConnId,
|
||||
conn_->localConnectionError,
|
||||
*conn_->oneRttWriteCipher,
|
||||
*conn_->oneRttWriteHeaderCipher);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!conn_->initialWriteCipher) {
|
||||
// This would be possible if we read a packet from the network which
|
||||
// could not be parsed later.
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t packetLimit =
|
||||
(isConnectionPaced(*conn_)
|
||||
? conn_->pacer->updateAndGetWriteBatchSize(Clock::now())
|
||||
: conn_->transportSettings.writeConnectionDataPacketsLimit);
|
||||
if (conn_->initialWriteCipher) {
|
||||
CryptoStreamScheduler initialScheduler(
|
||||
*conn_, *getCryptoStream(*conn_->cryptoState, EncryptionLevel::Initial));
|
||||
CryptoStreamScheduler handshakeScheduler(
|
||||
*conn_,
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Handshake));
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Initial));
|
||||
if (initialScheduler.hasData() ||
|
||||
(conn_->ackStates.initialAckState.needsToSendAckImmediately &&
|
||||
hasAcksToSchedule(conn_->ackStates.initialAckState))) {
|
||||
@@ -221,6 +220,11 @@ void QuicServerTransport::writeData() {
|
||||
if (!packetLimit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (conn_->handshakeWriteCipher) {
|
||||
CryptoStreamScheduler handshakeScheduler(
|
||||
*conn_,
|
||||
*getCryptoStream(*conn_->cryptoState, EncryptionLevel::Handshake));
|
||||
if (handshakeScheduler.hasData() ||
|
||||
(conn_->ackStates.handshakeAckState.needsToSendAckImmediately &&
|
||||
hasAcksToSchedule(conn_->ackStates.handshakeAckState))) {
|
||||
@@ -240,6 +244,7 @@ void QuicServerTransport::writeData() {
|
||||
if (!packetLimit) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (conn_->oneRttWriteCipher) {
|
||||
CHECK(conn_->oneRttWriteHeaderCipher);
|
||||
writeQuicDataToSocket(
|
||||
|
||||
@@ -242,25 +242,28 @@ void updateHandshakeState(QuicServerConnectionState& conn) {
|
||||
}
|
||||
auto handshakeWriteCipher = handshakeLayer->getHandshakeWriteCipher();
|
||||
auto handshakeReadCipher = handshakeLayer->getHandshakeReadCipher();
|
||||
if (handshakeWriteCipher) {
|
||||
conn.handshakeWriteCipher = std::move(handshakeWriteCipher);
|
||||
}
|
||||
if (handshakeReadCipher) {
|
||||
conn.readCodec->setHandshakeReadCipher(std::move(handshakeReadCipher));
|
||||
}
|
||||
auto handshakeWriteHeaderCipher =
|
||||
handshakeLayer->getHandshakeWriteHeaderCipher();
|
||||
auto handshakeReadHeaderCipher =
|
||||
handshakeLayer->getHandshakeReadHeaderCipher();
|
||||
if (handshakeWriteHeaderCipher) {
|
||||
if (handshakeWriteCipher) {
|
||||
CHECK(
|
||||
handshakeReadCipher && handshakeWriteHeaderCipher &&
|
||||
handshakeReadHeaderCipher);
|
||||
conn.handshakeWriteCipher = std::move(handshakeWriteCipher);
|
||||
conn.handshakeWriteHeaderCipher = std::move(handshakeWriteHeaderCipher);
|
||||
}
|
||||
if (handshakeReadHeaderCipher) {
|
||||
conn.readCodec->setHandshakeReadCipher(std::move(handshakeReadCipher));
|
||||
conn.readCodec->setHandshakeHeaderCipher(
|
||||
std::move(handshakeReadHeaderCipher));
|
||||
}
|
||||
if (handshakeLayer->isHandshakeDone()) {
|
||||
conn.readCodec->onHandshakeDone(Clock::now());
|
||||
CHECK(conn.oneRttWriteCipher);
|
||||
if (conn.handshakeWriteCipher) {
|
||||
handshakeConfirmed(conn);
|
||||
if (conn.version == QuicVersion::QUIC_DRAFT) {
|
||||
sendSimpleFrame(conn, HandshakeDoneFrame());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -969,6 +972,14 @@ void onServerReadDataFromOpen(
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we've processed a handshake packet, we can dicard the initial cipher.
|
||||
if (encryptionLevel == EncryptionLevel::Handshake) {
|
||||
conn.initialWriteCipher.reset();
|
||||
conn.initialHeaderCipher.reset();
|
||||
conn.readCodec->setInitialReadCipher(nullptr);
|
||||
conn.readCodec->setInitialHeaderCipher(nullptr);
|
||||
cancelCryptoStream(conn.cryptoState->initialStream);
|
||||
}
|
||||
|
||||
// Update writable limit before processing the handshake data. This is so
|
||||
// that if we haven't decided whether or not to validate the peer, we won't
|
||||
|
||||
@@ -541,36 +541,13 @@ class QuicServerTransportTest : public Test {
|
||||
EXPECT_TRUE(getCryptoStream(
|
||||
*server->getConn().cryptoState, EncryptionLevel::Initial)
|
||||
->readBuffer.empty());
|
||||
EXPECT_NE(server->getConn().initialWriteCipher, nullptr);
|
||||
EXPECT_FALSE(server->getConn().localConnectionError.has_value());
|
||||
verifyTransportParameters(kDefaultIdleTimeout);
|
||||
serverWrites.clear();
|
||||
|
||||
// Simulate ack from client
|
||||
auto& cryptoState = server->getConn().cryptoState;
|
||||
EXPECT_GT(cryptoState->initialStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(cryptoState->handshakeStream.retransmissionBuffer.size(), 0);
|
||||
EXPECT_EQ(cryptoState->oneRttStream.retransmissionBuffer.size(), 0);
|
||||
|
||||
auto aead = getInitialCipher();
|
||||
auto headerCipher = getInitialHeaderCipher();
|
||||
AckBlocks acks;
|
||||
auto start = getFirstOutstandingPacket(
|
||||
server->getNonConstConn(), PacketNumberSpace::Initial)
|
||||
->packet.header.getPacketSequenceNum();
|
||||
auto end = getLastOutstandingPacket(
|
||||
server->getNonConstConn(), PacketNumberSpace::Initial)
|
||||
->packet.header.getPacketSequenceNum();
|
||||
acks.insert(start, end);
|
||||
auto pn = clientNextInitialPacketNum++;
|
||||
auto ackPkt = createAckPacket(
|
||||
server->getNonConstConn(),
|
||||
pn,
|
||||
acks,
|
||||
PacketNumberSpace::Initial,
|
||||
aead.get());
|
||||
deliverData(packetToBufCleartext(ackPkt, *aead, *headerCipher, pn));
|
||||
EXPECT_EQ(cryptoState->initialStream.retransmissionBuffer.size(), 0);
|
||||
}
|
||||
|
||||
void verifyTransportParameters(std::chrono::milliseconds idleTimeout) {
|
||||
@@ -829,6 +806,7 @@ TEST_F(QuicServerTransportTest, IdleTimerNotResetOnDuplicatePacket) {
|
||||
TEST_F(QuicServerTransportTest, IdleTimerNotResetWhenDataOutstanding) {
|
||||
// Clear the receivedNewPacketBeforeWrite flag, since we may reveice from
|
||||
// client during the SetUp of the test case.
|
||||
server->getNonConstConn().outstandingPackets.clear();
|
||||
server->getNonConstConn().receivedNewPacketBeforeWrite = false;
|
||||
StreamId streamId = server->createBidirectionalStream().value();
|
||||
|
||||
@@ -841,18 +819,18 @@ TEST_F(QuicServerTransportTest, IdleTimerNotResetWhenDataOutstanding) {
|
||||
false);
|
||||
loopForWrites();
|
||||
// It was the first packet
|
||||
ASSERT_TRUE(server->idleTimeout().isScheduled());
|
||||
EXPECT_TRUE(server->idleTimeout().isScheduled());
|
||||
|
||||
// cancel it and write something else. This time idle timer shouldn't set.
|
||||
server->idleTimeout().cancelTimeout();
|
||||
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
||||
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
||||
server->writeChain(
|
||||
streamId,
|
||||
IOBuf::copyBuffer("And if the daylight feels like it's a long way off"),
|
||||
false,
|
||||
false);
|
||||
loopForWrites();
|
||||
ASSERT_FALSE(server->idleTimeout().isScheduled());
|
||||
EXPECT_FALSE(server->idleTimeout().isScheduled());
|
||||
}
|
||||
|
||||
TEST_F(QuicServerTransportTest, TimeoutsNotSetAfterClose) {
|
||||
@@ -2991,6 +2969,22 @@ TEST_F(
|
||||
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, 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,
|
||||
@@ -3022,6 +3016,21 @@ TEST_F(QuicServerTransportTest, ClientPortChangeNATRebinding) {
|
||||
|
||||
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, 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(
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <quic/state/QuicStateFunctions.h>
|
||||
#include <quic/state/QuicStreamFunctions.h>
|
||||
|
||||
#include <quic/common/TimeUtil.h>
|
||||
#include <quic/logging/QuicLogger.h>
|
||||
@@ -311,4 +312,16 @@ std::pair<folly::Optional<TimePoint>, PacketNumberSpace> earliestTimeAndSpace(
|
||||
return res;
|
||||
}
|
||||
|
||||
void handshakeConfirmed(QuicConnectionStateBase& conn) {
|
||||
if (conn.nodeType == QuicNodeType::Client) {
|
||||
conn.handshakeLayer->handshakeConfirmed();
|
||||
}
|
||||
conn.readCodec->onHandshakeDone(Clock::now());
|
||||
conn.handshakeWriteCipher.reset();
|
||||
conn.handshakeWriteHeaderCipher.reset();
|
||||
conn.readCodec->setHandshakeReadCipher(nullptr);
|
||||
conn.readCodec->setHandshakeHeaderCipher(nullptr);
|
||||
cancelCryptoStream(conn.cryptoState->handshakeStream);
|
||||
}
|
||||
|
||||
} // namespace quic
|
||||
|
||||
@@ -113,4 +113,7 @@ std::pair<folly::Optional<TimePoint>, PacketNumberSpace> earliestLossTimer(
|
||||
std::pair<folly::Optional<TimePoint>, PacketNumberSpace> earliestTimeAndSpace(
|
||||
const EnumArray<PacketNumberSpace, folly::Optional<TimePoint>>& times,
|
||||
bool considerAppData) noexcept;
|
||||
|
||||
void handshakeConfirmed(QuicConnectionStateBase& conn);
|
||||
|
||||
} // namespace quic
|
||||
|
||||
@@ -398,14 +398,10 @@ uint64_t getStreamNextOffsetToDeliver(const QuicStreamState& stream) {
|
||||
return minOffsetToDeliver;
|
||||
}
|
||||
|
||||
void cancelHandshakeCryptoStreamRetransmissions(QuicCryptoState& cryptoState) {
|
||||
// Cancel any retransmissions we might want to do for the crypto stream.
|
||||
// This does not include data that is already deemed as lost, or data that
|
||||
// is pending in the write buffer.
|
||||
cryptoState.initialStream.retransmissionBuffer.clear();
|
||||
cryptoState.initialStream.lossBuffer.clear();
|
||||
cryptoState.handshakeStream.retransmissionBuffer.clear();
|
||||
cryptoState.handshakeStream.lossBuffer.clear();
|
||||
void cancelCryptoStream(QuicCryptoStream& cryptoStream) {
|
||||
cryptoStream.retransmissionBuffer.clear();
|
||||
cryptoStream.lossBuffer.clear();
|
||||
cryptoStream.writeBuffer.move();
|
||||
}
|
||||
|
||||
QuicCryptoStream* getCryptoStream(
|
||||
|
||||
@@ -118,11 +118,9 @@ std::pair<Buf, bool> readDataInOrderFromReadBuffer(
|
||||
bool sinkData = false);
|
||||
|
||||
/**
|
||||
* Cancel the retransmissions of the crypto stream data.
|
||||
* TODO: remove this when we can deal with cleartext data after handshake done
|
||||
* correctly.
|
||||
* Cancel retransmissions and writes for a crypto stream.
|
||||
*/
|
||||
void cancelHandshakeCryptoStreamRetransmissions(QuicCryptoState& cryptoStream);
|
||||
void cancelCryptoStream(QuicCryptoStream& cryptoStream);
|
||||
|
||||
/**
|
||||
* Returns the appropriate crypto stream for the protection type of the packet.
|
||||
|
||||
@@ -21,7 +21,6 @@ void sendSimpleFrame(QuicConnectionStateBase& conn, QuicSimpleFrame frame) {
|
||||
void updateSimpleFrameOnAck(
|
||||
QuicConnectionStateBase& conn,
|
||||
const QuicSimpleFrame& frame) {
|
||||
// TODO implement.
|
||||
switch (frame.type()) {
|
||||
case QuicSimpleFrame::Type::PingFrame_E: {
|
||||
conn.pendingEvents.cancelPingTimeout = true;
|
||||
@@ -72,6 +71,8 @@ folly::Optional<QuicSimpleFrame> updateSimpleFrameOnPacketClone(
|
||||
case QuicSimpleFrame::Type::RetireConnectionIdFrame_E:
|
||||
// TODO junqiw
|
||||
return QuicSimpleFrame(frame);
|
||||
case QuicSimpleFrame::Type::HandshakeDoneFrame_E:
|
||||
return QuicSimpleFrame(frame);
|
||||
}
|
||||
folly::assume_unreachable();
|
||||
}
|
||||
@@ -144,6 +145,7 @@ void updateSimpleFrameOnPacketLoss(
|
||||
case QuicSimpleFrame::Type::NewConnectionIdFrame_E:
|
||||
case QuicSimpleFrame::Type::MaxStreamsFrame_E:
|
||||
case QuicSimpleFrame::Type::RetireConnectionIdFrame_E:
|
||||
case QuicSimpleFrame::Type::HandshakeDoneFrame_E:
|
||||
conn.pendingEvents.frames.push_back(frame);
|
||||
break;
|
||||
}
|
||||
@@ -289,6 +291,16 @@ bool updateSimpleFrameOnPacketReceived(
|
||||
// TODO junqiw
|
||||
return false;
|
||||
}
|
||||
case QuicSimpleFrame::Type::HandshakeDoneFrame_E: {
|
||||
if (conn.nodeType == QuicNodeType::Server) {
|
||||
throw QuicTransportException(
|
||||
"Received HANDSHAKE_DONE from client.",
|
||||
TransportErrorCode::PROTOCOL_VIOLATION,
|
||||
FrameType::HANDSHAKE_DONE);
|
||||
}
|
||||
handshakeConfirmed(conn);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
folly::assume_unreachable();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user