mirror of
https://github.com/facebookincubator/mvfst.git
synced 2025-11-10 21:22:20 +03:00
Introduce new connection rate limits.
Summary: This introduces a rate limit to new connections created by a worker. Right now it will simply send a VN, but eventually this will only issue a RETRY for unverified initials. Reviewed By: udippant Differential Revision: D21614905 fbshipit-source-id: 1832fbdad525c53fb1cb810aa9d7bae868c267d6
This commit is contained in:
committed by
Facebook GitHub Bot
parent
99e5aedc21
commit
30bff94e85
@@ -15,6 +15,7 @@
|
|||||||
#include <quic/server/QuicReusePortUDPSocketFactory.h>
|
#include <quic/server/QuicReusePortUDPSocketFactory.h>
|
||||||
#include <quic/server/QuicServerTransport.h>
|
#include <quic/server/QuicServerTransport.h>
|
||||||
#include <quic/server/QuicSharedUDPSocketFactory.h>
|
#include <quic/server/QuicSharedUDPSocketFactory.h>
|
||||||
|
#include <quic/server/SlidingWindowRateLimiter.h>
|
||||||
|
|
||||||
namespace quic {
|
namespace quic {
|
||||||
|
|
||||||
@@ -60,6 +61,10 @@ void QuicServer::setCongestionControllerFactory(
|
|||||||
ccFactory_ = std::move(ccFactory);
|
ccFactory_ = std::move(ccFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuicServer::setRateLimit(uint64_t count, std::chrono::seconds window) {
|
||||||
|
rateLimit_ = folly::make_optional<RateLimit>(count, window);
|
||||||
|
}
|
||||||
|
|
||||||
void QuicServer::setSupportedVersion(const std::vector<QuicVersion>& versions) {
|
void QuicServer::setSupportedVersion(const std::vector<QuicVersion>& versions) {
|
||||||
supportedVersions_ = versions;
|
supportedVersions_ = versions;
|
||||||
}
|
}
|
||||||
@@ -164,6 +169,10 @@ void QuicServer::initializeWorkers(
|
|||||||
}
|
}
|
||||||
worker->setConnectionIdAlgo(connIdAlgoFactory_->make());
|
worker->setConnectionIdAlgo(connIdAlgoFactory_->make());
|
||||||
worker->setCongestionControllerFactory(ccFactory_);
|
worker->setCongestionControllerFactory(ccFactory_);
|
||||||
|
if (rateLimit_) {
|
||||||
|
worker->setRateLimiter(std::make_unique<SlidingWindowRateLimiter>(
|
||||||
|
rateLimit_->count, rateLimit_->window));
|
||||||
|
}
|
||||||
worker->setWorkerId(i);
|
worker->setWorkerId(i);
|
||||||
worker->setTransportSettingsOverrideFn(transportSettingsOverrideFn_);
|
worker->setTransportSettingsOverrideFn(transportSettingsOverrideFn_);
|
||||||
workers_.push_back(std::move(worker));
|
workers_.push_back(std::move(worker));
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ class QuicServer : public QuicServerWorker::WorkerCallback,
|
|||||||
void setCongestionControllerFactory(
|
void setCongestionControllerFactory(
|
||||||
std::shared_ptr<CongestionControllerFactory> ccFactory);
|
std::shared_ptr<CongestionControllerFactory> ccFactory);
|
||||||
|
|
||||||
|
void setRateLimit(uint64_t count, std::chrono::seconds window);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set list of supported QUICVersion for this server. These versions will be
|
* Set list of supported QUICVersion for this server. These versions will be
|
||||||
* used during the 'Version-Negotiation' phase with the client.
|
* used during the 'Version-Negotiation' phase with the client.
|
||||||
@@ -380,6 +382,13 @@ class QuicServer : public QuicServerWorker::WorkerCallback,
|
|||||||
// address that the server is bound to
|
// address that the server is bound to
|
||||||
folly::SocketAddress boundAddress_;
|
folly::SocketAddress boundAddress_;
|
||||||
folly::SocketOptionMap socketOptions_;
|
folly::SocketOptionMap socketOptions_;
|
||||||
|
// Rate limits
|
||||||
|
struct RateLimit {
|
||||||
|
RateLimit(uint64_t c, std::chrono::seconds w) : count(c), window(w) {}
|
||||||
|
uint64_t count;
|
||||||
|
std::chrono::seconds window;
|
||||||
|
};
|
||||||
|
folly::Optional<RateLimit> rateLimit_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace quic
|
} // namespace quic
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ void QuicServerWorker::setCongestionControllerFactory(
|
|||||||
ccFactory_ = ccFactory;
|
ccFactory_ = ccFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QuicServerWorker::setRateLimiter(
|
||||||
|
std::unique_ptr<RateLimiter> rateLimiter) {
|
||||||
|
newConnRateLimiter_ = std::move(rateLimiter);
|
||||||
|
}
|
||||||
|
|
||||||
void QuicServerWorker::start() {
|
void QuicServerWorker::start() {
|
||||||
CHECK(socket_);
|
CHECK(socket_);
|
||||||
if (!pacingTimer_) {
|
if (!pacingTimer_) {
|
||||||
@@ -490,6 +495,19 @@ void QuicServerWorker::dispatchPacketData(
|
|||||||
PacketDropReason::INVALID_PACKET);
|
PacketDropReason::INVALID_PACKET);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (newConnRateLimiter_ &&
|
||||||
|
newConnRateLimiter_->check(networkData.receiveTimePoint)) {
|
||||||
|
// TODO RETRY
|
||||||
|
VersionNegotiationPacketBuilder builder(
|
||||||
|
routingData.destinationConnId,
|
||||||
|
routingData.sourceConnId.value_or(
|
||||||
|
ConnectionId(std::vector<uint8_t>())),
|
||||||
|
std::vector<QuicVersion>{QuicVersion::MVFST_INVALID});
|
||||||
|
auto versionNegotiationPacket = std::move(builder).buildPacket();
|
||||||
|
socket_->write(client, versionNegotiationPacket.second);
|
||||||
|
QUIC_STATS(statsCallback_, onConnectionRateLimited);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// create 'accepting' transport
|
// create 'accepting' transport
|
||||||
auto sock = makeSocket(getEventBase());
|
auto sock = makeSocket(getEventBase());
|
||||||
auto trans = transportFactory_->make(
|
auto trans = transportFactory_->make(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include <quic/server/QuicServerPacketRouter.h>
|
#include <quic/server/QuicServerPacketRouter.h>
|
||||||
#include <quic/server/QuicServerTransportFactory.h>
|
#include <quic/server/QuicServerTransportFactory.h>
|
||||||
#include <quic/server/QuicUDPSocketFactory.h>
|
#include <quic/server/QuicUDPSocketFactory.h>
|
||||||
|
#include <quic/server/RateLimiter.h>
|
||||||
#include <quic/server/state/ServerConnectionIdRejector.h>
|
#include <quic/server/state/ServerConnectionIdRejector.h>
|
||||||
#include <quic/state/QuicTransportStatsCallback.h>
|
#include <quic/state/QuicTransportStatsCallback.h>
|
||||||
|
|
||||||
@@ -226,6 +227,11 @@ class QuicServerWorker : public folly::AsyncUDPSocket::ReadCallback,
|
|||||||
void setCongestionControllerFactory(
|
void setCongestionControllerFactory(
|
||||||
std::shared_ptr<CongestionControllerFactory> factory);
|
std::shared_ptr<CongestionControllerFactory> factory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the rate limiter which will be used to rate limit new connections.
|
||||||
|
*/
|
||||||
|
void setRateLimiter(std::unique_ptr<RateLimiter> rateLimiter);
|
||||||
|
|
||||||
// Read callback
|
// Read callback
|
||||||
void getReadBuffer(void** buf, size_t* len) noexcept override;
|
void getReadBuffer(void** buf, size_t* len) noexcept override;
|
||||||
|
|
||||||
@@ -414,6 +420,9 @@ class QuicServerWorker : public folly::AsyncUDPSocket::ReadCallback,
|
|||||||
|
|
||||||
// Output buffer to be used for continuous memory GSO write
|
// Output buffer to be used for continuous memory GSO write
|
||||||
std::unique_ptr<BufAccessor> bufAccessor_;
|
std::unique_ptr<BufAccessor> bufAccessor_;
|
||||||
|
|
||||||
|
// Rate limits the creation of new connections for this worker.
|
||||||
|
std::unique_ptr<RateLimiter> newConnRateLimiter_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace quic
|
} // namespace quic
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <quic/codec/QuicHeaderCodec.h>
|
#include <quic/codec/QuicHeaderCodec.h>
|
||||||
#include <quic/codec/test/Mocks.h>
|
#include <quic/codec/test/Mocks.h>
|
||||||
#include <quic/common/test/TestUtils.h>
|
#include <quic/common/test/TestUtils.h>
|
||||||
|
#include <quic/server/SlidingWindowRateLimiter.h>
|
||||||
#include <quic/server/handshake/StatelessResetGenerator.h>
|
#include <quic/server/handshake/StatelessResetGenerator.h>
|
||||||
#include <quic/server/test/Mocks.h>
|
#include <quic/server/test/Mocks.h>
|
||||||
#include <quic/state/test/MockQuicStats.h>
|
#include <quic/state/test/MockQuicStats.h>
|
||||||
@@ -378,6 +379,86 @@ TEST_F(QuicServerWorkerTest, NoConnFoundTestReset) {
|
|||||||
QuicTransportStatsCallback::PacketDropReason::CONNECTION_NOT_FOUND);
|
QuicTransportStatsCallback::PacketDropReason::CONNECTION_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(QuicServerWorkerTest, RateLimit) {
|
||||||
|
worker_->setRateLimiter(std::make_unique<SlidingWindowRateLimiter>(2, 60s));
|
||||||
|
EXPECT_CALL(*transportInfoCb_, onConnectionRateLimited()).Times(1);
|
||||||
|
|
||||||
|
NiceMock<MockConnectionCallback> connCb1;
|
||||||
|
auto mockSock1 =
|
||||||
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&eventbase_);
|
||||||
|
EXPECT_CALL(*mockSock1, address()).WillRepeatedly(ReturnRef(fakeAddress_));
|
||||||
|
MockQuicTransport::Ptr testTransport1 = std::make_shared<MockQuicTransport>(
|
||||||
|
worker_->getEventBase(), std::move(mockSock1), connCb1, nullptr);
|
||||||
|
EXPECT_CALL(*testTransport1, getEventBase())
|
||||||
|
.WillRepeatedly(Return(&eventbase_));
|
||||||
|
EXPECT_CALL(*testTransport1, getOriginalPeerAddress())
|
||||||
|
.WillRepeatedly(ReturnRef(kClientAddr));
|
||||||
|
auto connId1 = getTestConnectionId(hostId_);
|
||||||
|
PacketNum num = 1;
|
||||||
|
QuicVersion version = QuicVersion::MVFST;
|
||||||
|
RoutingData routingData(HeaderForm::Long, true, true, connId1, connId1);
|
||||||
|
|
||||||
|
auto data = createData(kMinInitialPacketSize + 10);
|
||||||
|
EXPECT_CALL(
|
||||||
|
*testTransport1, onNetworkData(kClientAddr, NetworkDataMatches(*data)));
|
||||||
|
EXPECT_CALL(*factory_, _make(_, _, _, _)).WillOnce(Return(testTransport1));
|
||||||
|
|
||||||
|
worker_->dispatchPacketData(
|
||||||
|
kClientAddr,
|
||||||
|
std::move(routingData),
|
||||||
|
NetworkData(data->clone(), Clock::now()));
|
||||||
|
|
||||||
|
const auto& addrMap = worker_->getSrcToTransportMap();
|
||||||
|
EXPECT_EQ(addrMap.count(std::make_pair(kClientAddr, connId1)), 1);
|
||||||
|
eventbase_.loop();
|
||||||
|
|
||||||
|
auto caddr2 = folly::SocketAddress("2.3.4.5", 1234);
|
||||||
|
NiceMock<MockConnectionCallback> connCb2;
|
||||||
|
auto mockSock2 =
|
||||||
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&eventbase_);
|
||||||
|
EXPECT_CALL(*mockSock2, address()).WillRepeatedly(ReturnRef(caddr2));
|
||||||
|
MockQuicTransport::Ptr testTransport2 = std::make_shared<MockQuicTransport>(
|
||||||
|
worker_->getEventBase(), std::move(mockSock2), connCb2, nullptr);
|
||||||
|
EXPECT_CALL(*testTransport2, getEventBase())
|
||||||
|
.WillRepeatedly(Return(&eventbase_));
|
||||||
|
EXPECT_CALL(*testTransport2, getOriginalPeerAddress())
|
||||||
|
.WillRepeatedly(ReturnRef(caddr2));
|
||||||
|
ConnectionId connId2({2, 4, 5, 6});
|
||||||
|
num = 1;
|
||||||
|
version = QuicVersion::MVFST;
|
||||||
|
RoutingData routingData2(HeaderForm::Long, true, true, connId2, connId2);
|
||||||
|
|
||||||
|
auto data2 = createData(kMinInitialPacketSize + 10);
|
||||||
|
EXPECT_CALL(
|
||||||
|
*testTransport2, onNetworkData(caddr2, NetworkDataMatches(*data2)));
|
||||||
|
EXPECT_CALL(*factory_, _make(_, _, _, _)).WillOnce(Return(testTransport2));
|
||||||
|
worker_->dispatchPacketData(
|
||||||
|
caddr2,
|
||||||
|
std::move(routingData2),
|
||||||
|
NetworkData(data2->clone(), Clock::now()));
|
||||||
|
|
||||||
|
EXPECT_EQ(addrMap.count(std::make_pair(caddr2, connId2)), 1);
|
||||||
|
eventbase_.loop();
|
||||||
|
|
||||||
|
auto caddr3 = folly::SocketAddress("3.3.4.5", 1234);
|
||||||
|
auto mockSock3 =
|
||||||
|
std::make_unique<NiceMock<folly::test::MockAsyncUDPSocket>>(&eventbase_);
|
||||||
|
ConnectionId connId3({8, 4, 5, 6});
|
||||||
|
num = 1;
|
||||||
|
version = QuicVersion::MVFST;
|
||||||
|
RoutingData routingData3(HeaderForm::Long, true, true, connId3, connId3);
|
||||||
|
auto data3 = createData(kMinInitialPacketSize + 10);
|
||||||
|
EXPECT_CALL(*factory_, _make(_, _, _, _)).Times(0);
|
||||||
|
worker_->dispatchPacketData(
|
||||||
|
caddr3,
|
||||||
|
std::move(routingData3),
|
||||||
|
NetworkData(data2->clone(), Clock::now()));
|
||||||
|
|
||||||
|
EXPECT_EQ(addrMap.count(std::make_pair(caddr3, connId3)), 0);
|
||||||
|
EXPECT_EQ(addrMap.size(), 2);
|
||||||
|
eventbase_.loop();
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(QuicServerWorkerTest, QuicServerWorkerUnbindBeforeCidAvailable) {
|
TEST_F(QuicServerWorkerTest, QuicServerWorkerUnbindBeforeCidAvailable) {
|
||||||
NiceMock<MockConnectionCallback> connCb;
|
NiceMock<MockConnectionCallback> connCb;
|
||||||
auto mockSock =
|
auto mockSock =
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class QuicTransportStatsCallback {
|
|||||||
|
|
||||||
virtual void onClientInitialReceived() = 0;
|
virtual void onClientInitialReceived() = 0;
|
||||||
|
|
||||||
|
virtual void onConnectionRateLimited() = 0;
|
||||||
|
|
||||||
// connection level metrics:
|
// connection level metrics:
|
||||||
virtual void onNewConnection() = 0;
|
virtual void onNewConnection() = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class MockQuicStats : public QuicTransportStatsCallback {
|
|||||||
MOCK_METHOD0(onForwardedPacketReceived, void());
|
MOCK_METHOD0(onForwardedPacketReceived, void());
|
||||||
MOCK_METHOD0(onForwardedPacketProcessed, void());
|
MOCK_METHOD0(onForwardedPacketProcessed, void());
|
||||||
MOCK_METHOD0(onClientInitialReceived, void());
|
MOCK_METHOD0(onClientInitialReceived, void());
|
||||||
|
MOCK_METHOD0(onConnectionRateLimited, void());
|
||||||
MOCK_METHOD0(onNewConnection, void());
|
MOCK_METHOD0(onNewConnection, void());
|
||||||
MOCK_METHOD1(onConnectionClose, void(folly::Optional<ConnectionCloseReason>));
|
MOCK_METHOD1(onConnectionClose, void(folly::Optional<ConnectionCloseReason>));
|
||||||
MOCK_METHOD0(onNewQuicStream, void());
|
MOCK_METHOD0(onNewQuicStream, void());
|
||||||
|
|||||||
Reference in New Issue
Block a user