1
0
mirror of https://github.com/facebookincubator/mvfst.git synced 2025-11-10 21:22:20 +03:00
Files
mvfst/quic/server/QuicServerWorker.cpp
Udip Pant bb06a1d81d log misrouting of packets at host level with increased verbosity
Reviewed By: lnicco

Differential Revision: D21988330

fbshipit-source-id: 33978dd66bf10f929994dd147ec531691b67a1ee
2020-06-10 18:12:18 -07:00

1072 lines
36 KiB
C++

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
#include <folly/Format.h>
#include <folly/io/Cursor.h>
#include <folly/io/SocketOptionMap.h>
#include <folly/system/ThreadId.h>
#include <quic/QuicConstants.h>
#include <quic/common/SocketUtil.h>
#include <quic/common/Timers.h>
#include <quic/server/QuicServerWorker.h>
#include <quic/server/handshake/StatelessResetGenerator.h>
namespace quic {
QuicServerWorker::QuicServerWorker(
std::shared_ptr<QuicServerWorker::WorkerCallback> callback,
bool setEventCallback)
: callback_(callback),
setEventCallback_(setEventCallback),
takeoverPktHandler_(this) {}
folly::EventBase* QuicServerWorker::getEventBase() const {
return evb_;
}
void QuicServerWorker::setSocket(
std::unique_ptr<folly::AsyncUDPSocket> socket) {
socket_ = std::move(socket);
evb_ = socket_->getEventBase();
}
void QuicServerWorker::bind(const folly::SocketAddress& address) {
DCHECK(!supportedVersions_.empty());
CHECK(socket_);
if (setEventCallback_) {
socket_->setEventCallback(this);
}
if (socketOptions_) {
applySocketOptions(
*socket_.get(),
*socketOptions_,
address.getFamily(),
folly::SocketOptionKey::ApplyPos::PRE_BIND);
}
socket_->bind(address);
if (socketOptions_) {
applySocketOptions(
*socket_.get(),
*socketOptions_,
address.getFamily(),
folly::SocketOptionKey::ApplyPos::POST_BIND);
}
socket_->setDFAndTurnOffPMTU();
if (transportSettings_.numGROBuffers_ > kDefaultNumGROBuffers) {
socket_->setGRO(true);
auto ret = socket_->getGRO();
if (ret > 0) {
numGROBuffers_ = (transportSettings_.numGROBuffers_ < kMaxNumGROBuffers)
? transportSettings_.numGROBuffers_
: kMaxNumGROBuffers;
}
}
}
void QuicServerWorker::applyAllSocketOptions() {
CHECK(socket_);
if (socketOptions_) {
applySocketOptions(
*socket_.get(),
*socketOptions_,
getAddress().getFamily(),
folly::SocketOptionKey::ApplyPos::PRE_BIND);
applySocketOptions(
*socket_.get(),
*socketOptions_,
getAddress().getFamily(),
folly::SocketOptionKey::ApplyPos::POST_BIND);
}
}
void QuicServerWorker::setTransportSettingsOverrideFn(
TransportSettingsOverrideFn fn) {
transportSettingsOverrideFn_ = std::move(fn);
}
void QuicServerWorker::setTransportStatsCallback(
std::unique_ptr<QuicTransportStatsCallback> statsCallback) noexcept {
CHECK(statsCallback);
statsCallback_ = std::move(statsCallback);
}
QuicTransportStatsCallback* QuicServerWorker::getTransportStatsCallback() const
noexcept {
return statsCallback_.get();
}
void QuicServerWorker::setConnectionIdAlgo(
std::unique_ptr<ConnectionIdAlgo> connIdAlgo) noexcept {
CHECK(connIdAlgo);
connIdAlgo_ = std::move(connIdAlgo);
}
void QuicServerWorker::setCongestionControllerFactory(
std::shared_ptr<CongestionControllerFactory> ccFactory) {
CHECK(ccFactory);
ccFactory_ = ccFactory;
}
void QuicServerWorker::setRateLimiter(
std::unique_ptr<RateLimiter> rateLimiter) {
newConnRateLimiter_ = std::move(rateLimiter);
}
void QuicServerWorker::start() {
CHECK(socket_);
if (!pacingTimer_) {
pacingTimer_ = TimerHighRes::newTimer(
evb_, transportSettings_.pacingTimerTickInterval);
}
socket_->resumeRead(this);
VLOG(10) << folly::format(
"Registered read on worker={}, thread={}, processId={}",
this,
folly::getCurrentThreadID(),
(int)processId_);
}
void QuicServerWorker::pauseRead() {
CHECK(socket_);
socket_->pauseRead();
}
int QuicServerWorker::getFD() {
CHECK(socket_);
return socket_->getNetworkSocket().toFd();
}
const folly::SocketAddress& QuicServerWorker::getAddress() const {
CHECK(socket_);
return socket_->address();
}
void QuicServerWorker::getReadBuffer(void** buf, size_t* len) noexcept {
readBuffer_ = folly::IOBuf::create(
transportSettings_.maxRecvPacketSize * numGROBuffers_);
*buf = readBuffer_->writableData();
*len = transportSettings_.maxRecvPacketSize * numGROBuffers_;
}
// Returns true if we either drop the packet or send a version
// negotiation packet to the client. Returns false if there's
// no need for version negotiation.
bool QuicServerWorker::maybeSendVersionNegotiationPacketOrDrop(
const folly::SocketAddress& client,
bool isInitial,
LongHeaderInvariant& invariant) {
folly::Optional<std::pair<VersionNegotiationPacket, Buf>>
versionNegotiationPacket;
if (rejectNewConnections_ && isInitial) {
VersionNegotiationPacketBuilder builder(
invariant.dstConnId,
invariant.srcConnId,
std::vector<QuicVersion>{QuicVersion::MVFST_INVALID});
versionNegotiationPacket =
folly::make_optional(std::move(builder).buildPacket());
}
if (!versionNegotiationPacket) {
bool negotiationNeeded = std::find(
supportedVersions_.begin(),
supportedVersions_.end(),
invariant.version) == supportedVersions_.end();
if (negotiationNeeded && !isInitial) {
VLOG(3) << "Dropping non-initial packet due to invalid version";
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::INVALID_PACKET);
return true;
}
if (negotiationNeeded) {
VersionNegotiationPacketBuilder builder(
invariant.dstConnId, invariant.srcConnId, supportedVersions_);
versionNegotiationPacket =
folly::make_optional(std::move(builder).buildPacket());
}
}
if (versionNegotiationPacket) {
VLOG(4) << "Version negotiation sent to client=" << client;
auto len = versionNegotiationPacket->second->computeChainDataLength();
QUIC_STATS(statsCallback_, onWrite, len);
QUIC_STATS(statsCallback_, onPacketProcessed);
QUIC_STATS(statsCallback_, onPacketSent);
socket_->write(client, versionNegotiationPacket->second);
return true;
}
return false;
}
void QuicServerWorker::onDataAvailable(
const folly::SocketAddress& client,
size_t len,
bool truncated,
OnDataAvailableParams params) noexcept {
// TODO: we can get better receive time accuracy than this, with
// SO_TIMESTAMP or SIOCGSTAMP.
auto packetReceiveTime = Clock::now();
VLOG(10) << folly::format(
"Worker={}, Received data on thread={}, processId={}",
this,
folly::getCurrentThreadID(),
(int)processId_);
// Move readBuffer_ first so that we can get rid
// of it immediately so that if we return early,
// we've flushed it.
Buf data = std::move(readBuffer_);
if (params.gro_ <= 0) {
if (truncated) {
// This is an error, drop the packet.
return;
}
data->append(len);
QUIC_STATS(statsCallback_, onPacketReceived);
QUIC_STATS(statsCallback_, onRead, len);
handleNetworkData(client, std::move(data), packetReceiveTime);
} else {
// if we receive a truncated packet
// we still need to consider the prev valid ones
// AsyncUDPSocket::handleRead() sets the len to be the
// buffer size in case the data is truncated
if (truncated) {
len -= len % params.gro_;
}
data->append(len);
QUIC_STATS(statsCallback_, onPacketReceived);
QUIC_STATS(statsCallback_, onRead, len);
size_t remaining = len;
size_t offset = 0;
while (remaining) {
if (static_cast<int>(remaining) > params.gro_) {
auto tmp = data->cloneOne();
// start at offset
tmp->trimStart(offset);
// the actual len is len - offset now
// leave params.gro_ bytes
tmp->trimEnd(len - offset - params.gro_);
DCHECK_EQ(tmp->length(), params.gro_);
offset += params.gro_;
remaining -= params.gro_;
handleNetworkData(client, std::move(tmp), packetReceiveTime);
} else {
// do not clone the last packet
// start at offset, use all the remaining data
data->trimStart(offset);
DCHECK_EQ(data->length(), remaining);
remaining = 0;
handleNetworkData(client, std::move(data), packetReceiveTime);
}
}
}
}
void QuicServerWorker::handleNetworkData(
const folly::SocketAddress& client,
Buf data,
const TimePoint& packetReceiveTime,
bool isForwardedData) noexcept {
try {
if (shutdown_) {
VLOG(4) << "Packet received after shutdown, dropping";
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::SERVER_SHUTDOWN);
return;
}
if (!callback_) {
VLOG(0) << "Worker callback is null. Dropping packet.";
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::WORKER_NOT_INITIALIZED);
return;
}
folly::io::Cursor cursor(data.get());
if (!cursor.canAdvance(sizeof(uint8_t))) {
VLOG(4) << "Dropping packet too small";
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::INVALID_PACKET);
return;
}
uint8_t initialByte = cursor.readBE<uint8_t>();
HeaderForm headerForm = getHeaderForm(initialByte);
if (headerForm == HeaderForm::Short) {
folly::Expected<ShortHeaderInvariant, TransportErrorCode>
parsedShortHeader = parseShortHeaderInvariants(initialByte, cursor);
if (!parsedShortHeader) {
if (!tryHandlingAsHealthCheck(client, *data)) {
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::PARSE_ERROR);
VLOG(6) << "Failed to parse short header";
}
return;
}
RoutingData routingData(
headerForm,
false,
false,
std::move(parsedShortHeader->destinationConnId),
folly::none);
return forwardNetworkData(
client,
std::move(routingData),
NetworkData(std::move(data), packetReceiveTime),
isForwardedData);
}
folly::Expected<ParsedLongHeaderInvariant, TransportErrorCode>
parsedLongHeader = parseLongHeaderInvariant(initialByte, cursor);
if (!parsedLongHeader) {
if (!tryHandlingAsHealthCheck(client, *data)) {
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::PARSE_ERROR);
VLOG(6) << "Failed to parse long header";
}
return;
}
// TODO: check version before looking at type
LongHeader::Types longHeaderType = parseLongHeaderType(initialByte);
bool isInitial = longHeaderType == LongHeader::Types::Initial;
bool isUsingClientConnId =
isInitial || longHeaderType == LongHeader::Types::ZeroRtt;
if (isInitial) {
// This stats gets updated even if the client initial will be dropped.
QUIC_STATS(statsCallback_, onClientInitialReceived);
}
if (maybeSendVersionNegotiationPacketOrDrop(
client, isInitial, parsedLongHeader->invariant)) {
return;
}
if (!isUsingClientConnId &&
parsedLongHeader->invariant.dstConnId.size() <
kMinSelfConnectionIdSize) {
// drop packet if connId is present but is not valid.
VLOG(3) << "Dropping packet due to invalid connectionId";
QUIC_STATS(
statsCallback_, onPacketDropped, PacketDropReason::INVALID_PACKET);
return;
}
RoutingData routingData(
headerForm,
isInitial,
isUsingClientConnId,
std::move(parsedLongHeader->invariant.dstConnId),
std::move(parsedLongHeader->invariant.srcConnId));
return forwardNetworkData(
client,
std::move(routingData),
NetworkData(std::move(data), packetReceiveTime),
isForwardedData);
} catch (const std::exception& ex) {
// Drop the packet.
QUIC_STATS(statsCallback_, onPacketDropped, PacketDropReason::PARSE_ERROR);
VLOG(6) << "Failed to parse packet header " << ex.what();
}
}
void QuicServerWorker::eventRecvmsgCallback(MsgHdr* msgHdr, int res) {
auto bytesRead = res;
int gro = -1;
auto& msg = msgHdr->data_;
if (bytesRead > 0) {
#ifdef FOLLY_HAVE_MSG_ERRQUEUE
if (msgHdr->data_.msg_control) {
struct cmsghdr* cmsg;
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == SOL_UDP && cmsg->cmsg_type == UDP_GRO) {
auto grosizeptr = (uint16_t*)CMSG_DATA(cmsg);
gro = *grosizeptr;
break;
}
}
}
#endif
bool truncated = false;
if ((size_t)bytesRead > msgHdr->len_) {
truncated = true;
bytesRead = ssize_t(msgHdr->len_);
}
readBuffer_ = std::move(msgHdr->ioBuf_);
folly::SocketAddress addr;
addr.setFromSockaddr(
reinterpret_cast<sockaddr*>(msg.msg_name), msg.msg_namelen);
OnDataAvailableParams params;
params.gro_ = gro;
onDataAvailable(addr, bytesRead, truncated, params);
}
msgHdr_.reset(msgHdr);
}
bool QuicServerWorker::tryHandlingAsHealthCheck(
const folly::SocketAddress& client,
const folly::IOBuf& data) {
// If we cannot parse the long header then it is not a QUIC invariant
// packet, so just drop it after checking whether it could be a health
// check.
if (!healthCheckToken_) {
return false;
}
folly::IOBufEqualTo eq;
// TODO: make this constant time, the token might be secret, but we're
// current assuming it's not.
if (eq(*healthCheckToken_.value(), data)) {
// say that we are OK. The response is much smaller than the
// request, so we are not creating an amplification vector. Also
// ignore the error code.
VLOG(4) << "Health check request, response=OK";
socket_->write(client, folly::IOBuf::copyBuffer("OK"));
return true;
}
return false;
}
void QuicServerWorker::forwardNetworkData(
const folly::SocketAddress& client,
RoutingData&& routingData,
NetworkData&& networkData,
bool isForwardedData) {
// if it's not Client initial or ZeroRtt, AND if the connectionId version
// mismatches: foward if pktForwarding is enabled else dropPacket
if (!routingData.isUsingClientConnId &&
!connIdAlgo_->canParse(routingData.destinationConnId)) {
if (packetForwardingEnabled_ && !isForwardedData) {
VLOG(3) << folly::format(
"Forwarding packet with unknown connId version from client={} to another process, routingInfo={}",
client.describe(),
logRoutingInfo(routingData.destinationConnId));
auto recvTime = networkData.receiveTimePoint;
takeoverPktHandler_.forwardPacketToAnotherServer(
client, std::move(networkData).moveAllData(), recvTime);
QUIC_STATS(statsCallback_, onPacketForwarded);
return;
} else {
VLOG(3) << folly::format(
"Dropping packet due to unknown connectionId version, routingInfo={}",
logRoutingInfo(routingData.destinationConnId));
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::CONNECTION_NOT_FOUND);
}
return;
}
callback_->routeDataToWorker(
client, std::move(routingData), std::move(networkData), isForwardedData);
}
void QuicServerWorker::setPacingTimer(
TimerHighRes::SharedPtr pacingTimer) noexcept {
pacingTimer_ = std::move(pacingTimer);
}
void QuicServerWorker::dispatchPacketData(
const folly::SocketAddress& client,
RoutingData&& routingData,
NetworkData&& networkData,
bool isForwardedData) noexcept {
DCHECK(socket_);
QuicServerTransport::Ptr transport;
bool dropPacket = false;
auto cit = connectionIdMap_.find(routingData.destinationConnId);
if (cit != connectionIdMap_.end()) {
transport = cit->second;
VLOG(10) << "Found existing connection for CID="
<< routingData.destinationConnId.hex() << " " << *transport;
} else if (routingData.headerForm != HeaderForm::Long) {
// Drop the packet if the header form is not long
VLOG(3) << folly::format(
"Dropping non-long header packet with no connid match"
" headerForm={}, routingInfo={}",
static_cast<typename std::underlying_type<HeaderForm>::type>(
routingData.headerForm),
logRoutingInfo(routingData.destinationConnId));
// Try forwarding the packet to the old server (if it is enabled)
dropPacket = true;
}
bool cannotMakeTransport = false;
if (!dropPacket && !transport) {
// For LongHeader packets without existing associated connection, try to
// route with destinationConnId chosen by the peer and IP address of the
// peer.
CHECK(transportFactory_);
auto source = std::make_pair(client, routingData.destinationConnId);
auto sit = sourceAddressMap_.find(source);
if (sit == sourceAddressMap_.end()) {
// TODO for O-RTT types we need to create new connections to handle
// the case, where the new server gets packets sent to the old one due
// to network reordering
if (!routingData.isInitial) {
VLOG(3) << folly::format(
"Dropping packet from client={}, routingInfo={}",
client.describe(),
logRoutingInfo(routingData.destinationConnId));
dropPacket = true;
} else {
VLOG(4) << folly::format(
"Creating new connection for client={}, routingInfo={}",
client.describe(),
logRoutingInfo(routingData.destinationConnId));
// This could be a new connection, add it in the map
// verify that the initial packet is at least min initial bytes
// to avoid amplification attacks.
if (networkData.totalData < kMinInitialPacketSize) {
// Don't even attempt to forward the packet, just drop it.
VLOG(3) << "Dropping small initial packet from client=" << client;
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::INVALID_PACKET);
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
auto sock = makeSocket(getEventBase());
auto trans = transportFactory_->make(
getEventBase(), std::move(sock), client, ctx_);
if (!trans) {
dropPacket = true;
cannotMakeTransport = true;
} else {
CHECK(trans);
if (transportSettings_.dataPathType ==
DataPathType::ContinuousMemory &&
bufAccessor_) {
trans->setBufAccessor(bufAccessor_.get());
}
trans->setPacingTimer(pacingTimer_);
trans->setRoutingCallback(this);
trans->setSupportedVersions(supportedVersions_);
trans->setOriginalPeerAddress(client);
trans->setCongestionControllerFactory(ccFactory_);
if (transportSettingsOverrideFn_) {
folly::Optional<TransportSettings> overridenTransportSettings =
transportSettingsOverrideFn_(
transportSettings_, client.getIPAddress());
if (overridenTransportSettings) {
if (overridenTransportSettings->dataPathType !=
transportSettings_.dataPathType) {
// It's too complex to support that.
LOG(ERROR)
<< "Overriding DataPathType isn't supported. Requested daapath="
<< (overridenTransportSettings->dataPathType ==
DataPathType::ContinuousMemory
? "ContinuousMemory"
: "ChainedMemory");
}
trans->setTransportSettings(*overridenTransportSettings);
} else {
trans->setTransportSettings(transportSettings_);
}
} else {
trans->setTransportSettings(transportSettings_);
}
trans->setConnectionIdAlgo(connIdAlgo_.get());
trans->setServerConnectionIdRejector(this);
if (routingData.sourceConnId) {
trans->setClientConnectionId(*routingData.sourceConnId);
}
trans->setClientChosenDestConnectionId(routingData.destinationConnId);
// parameters to create server chosen connection id
ServerConnectionIdParams serverConnIdParams(
hostId_, static_cast<uint8_t>(processId_), workerId_);
trans->setServerConnectionIdParams(std::move(serverConnIdParams));
if (statsCallback_) {
trans->setTransportStatsCallback(statsCallback_.get());
}
trans->accept();
auto result = sourceAddressMap_.emplace(std::make_pair(
std::make_pair(client, routingData.destinationConnId), trans));
if (!result.second) {
LOG(ERROR) << folly::format(
"Routing entry already exists for client={}, routingInfo={}",
client.describe(),
logRoutingInfo(routingData.destinationConnId));
dropPacket = true;
}
transport = trans;
}
}
} else {
transport = sit->second;
VLOG(4) << "Found existing connection for client=" << client << " "
<< *transport;
}
}
if (!dropPacket) {
DCHECK(transport->getEventBase()->isInEventBaseThread());
transport->onNetworkData(client, std::move(networkData));
return;
}
if (cannotMakeTransport) {
VLOG(3)
<< "Dropping packet due to transport factory did not make transport";
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::CANNOT_MAKE_TRANSPORT);
return;
}
if (!connIdAlgo_->canParse(routingData.destinationConnId)) {
VLOG(3) << "Dropping packet with bad DCID, routingInfo="
<< logRoutingInfo(routingData.destinationConnId);
QUIC_STATS(statsCallback_, onPacketDropped, PacketDropReason::PARSE_ERROR);
// TODO do we need to reset?
return;
}
auto connIdParam =
connIdAlgo_->parseConnectionId(routingData.destinationConnId);
if (connIdParam.hasError()) {
VLOG(3) << folly::format(
"Dropping packet due to DCID parsing error={}, , errorCode={}, routingInfo={}",
connIdParam.error().what(),
folly::to<std::string>(connIdParam.error().errorCode()),
logRoutingInfo(routingData.destinationConnId));
QUIC_STATS(statsCallback_, onPacketDropped, PacketDropReason::PARSE_ERROR);
// TODO do we need to reset?
return;
}
if (connIdParam->hostId != hostId_) {
VLOG_EVERY_N(2, 100) << "Dropping packet routed to wrong host, routingInfo="
<< logRoutingInfo(routingData.destinationConnId);
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::ROUTING_ERROR_WRONG_HOST);
return sendResetPacket(
routingData.headerForm,
client,
networkData,
routingData.destinationConnId);
}
if (!packetForwardingEnabled_ || isForwardedData) {
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::CONNECTION_NOT_FOUND);
return sendResetPacket(
routingData.headerForm,
client,
networkData,
routingData.destinationConnId);
}
// There's no existing connection for the packet's CID or the client's
// addr, and doesn't belong to the old server. Send a Reset.
if (connIdParam->processId == static_cast<uint8_t>(processId_)) {
QUIC_STATS(
statsCallback_,
onPacketDropped,
PacketDropReason::CONNECTION_NOT_FOUND);
return sendResetPacket(
routingData.headerForm,
client,
networkData,
routingData.destinationConnId);
}
// Optimistically route to another server
// if the packet type is not Initial and if there is not any connection
// associated with the given packet
VLOG(4) << folly::format(
"Forwarding packet from client={} to another process, routingInfo={}",
client.describe(),
logRoutingInfo(routingData.destinationConnId));
auto recvTime = networkData.receiveTimePoint;
takeoverPktHandler_.forwardPacketToAnotherServer(
client, std::move(networkData).moveAllData(), recvTime);
QUIC_STATS(statsCallback_, onPacketForwarded);
}
void QuicServerWorker::sendResetPacket(
const HeaderForm& headerForm,
const folly::SocketAddress& client,
const NetworkData& networkData,
const ConnectionId& connId) {
if (headerForm != HeaderForm::Short) {
// Only send resets in response to short header packets.
return;
}
auto packetSize = networkData.totalData;
auto resetSize = std::min<uint16_t>(packetSize, kDefaultMaxUDPPayload);
// Per the spec, less than 43 we should respond with packet size - 1.
if (packetSize < 43) {
resetSize = std::max<uint16_t>(packetSize - 1, kMinStatelessPacketSize);
} else {
resetSize = std::max<uint16_t>(
folly::Random::secureRand32() % resetSize, kMinStatelessPacketSize);
}
CHECK(transportSettings_.statelessResetTokenSecret.has_value());
StatelessResetGenerator generator(
*transportSettings_.statelessResetTokenSecret,
getAddress().getFullyQualified());
StatelessResetToken token = generator.generateToken(connId);
StatelessResetPacketBuilder builder(resetSize, token);
auto resetData = std::move(builder).buildPacket();
auto resetDataLen = resetData->computeChainDataLength();
socket_->write(client, std::move(resetData));
QUIC_STATS(statsCallback_, onWrite, resetDataLen);
QUIC_STATS(statsCallback_, onPacketSent);
QUIC_STATS(statsCallback_, onStatelessReset);
}
void QuicServerWorker::allowBeingTakenOver(
std::unique_ptr<folly::AsyncUDPSocket> socket,
const folly::SocketAddress& address) {
DCHECK(!takeoverCB_);
// We instantiate and bind the TakeoverHandlerCallback to the given address.
// It is reset at shutdownAllConnections (i.e. only when the process dies).
takeoverCB_ = std::make_unique<TakeoverHandlerCallback>(
this, takeoverPktHandler_, transportSettings_, std::move(socket));
takeoverCB_->bind(address);
}
const folly::SocketAddress& QuicServerWorker::overrideTakeoverHandlerAddress(
std::unique_ptr<folly::AsyncUDPSocket> socket,
const folly::SocketAddress& address) {
CHECK(takeoverCB_);
takeoverCB_->rebind(std::move(socket), address);
return takeoverCB_->getAddress();
}
void QuicServerWorker::startPacketForwarding(
const folly::SocketAddress& destAddr) {
packetForwardingEnabled_ = true;
takeoverPktHandler_.setDestination(destAddr);
}
void QuicServerWorker::stopPacketForwarding() {
packetForwardingEnabled_ = false;
takeoverPktHandler_.stop();
}
void QuicServerWorker::onReadError(
const folly::AsyncSocketException& ex) noexcept {
VLOG(4) << "QuicServer readerr: " << ex.what();
if (!callback_) {
VLOG(0) << "Worker callback is null. Ignoring worker error.";
return;
}
callback_->handleWorkerError(LocalErrorCode::INTERNAL_ERROR);
}
void QuicServerWorker::onReadClosed() noexcept {
shutdownAllConnections(LocalErrorCode::SHUTTING_DOWN);
}
int QuicServerWorker::getTakeoverHandlerSocketFD() {
CHECK(takeoverCB_);
return takeoverCB_->getSocketFD();
}
TakeoverProtocolVersion QuicServerWorker::getTakeoverProtocolVersion() const
noexcept {
return takeoverPktHandler_.getTakeoverProtocolVersion();
}
void QuicServerWorker::setProcessId(enum ProcessId id) noexcept {
processId_ = id;
}
ProcessId QuicServerWorker::getProcessId() const noexcept {
return processId_;
}
void QuicServerWorker::setWorkerId(uint8_t id) noexcept {
workerId_ = id;
}
uint8_t QuicServerWorker::getWorkerId() const noexcept {
return workerId_;
}
void QuicServerWorker::setHostId(uint16_t hostId) noexcept {
hostId_ = hostId;
}
void QuicServerWorker::setNewConnectionSocketFactory(
QuicUDPSocketFactory* factory) {
socketFactory_ = factory;
takeoverPktHandler_.setSocketFactory(socketFactory_);
}
void QuicServerWorker::setTransportFactory(
QuicServerTransportFactory* factory) {
transportFactory_ = factory;
}
void QuicServerWorker::setSupportedVersions(
const std::vector<QuicVersion>& supportedVersions) {
supportedVersions_ = supportedVersions;
}
void QuicServerWorker::setFizzContext(
std::shared_ptr<const fizz::server::FizzServerContext> ctx) {
ctx_ = ctx;
}
void QuicServerWorker::setTransportSettings(
TransportSettings transportSettings) {
transportSettings_ = transportSettings;
if (transportSettings_.batchingMode != QuicBatchingMode::BATCHING_MODE_GSO) {
if (transportSettings_.dataPathType == DataPathType::ContinuousMemory) {
LOG(ERROR) << "Unsupported data path type and batching mode combinartoin";
}
transportSettings_.dataPathType = DataPathType::ChainedMemory;
}
if (transportSettings_.dataPathType == DataPathType::ContinuousMemory) {
// TODO: maxBatchSize is only a good start value when each transport does
// its own socket writing. If we experiment with multiple transports GSO
// together, we will need a better value.
bufAccessor_ = std::make_unique<SimpleBufAccessor>(
kDefaultMaxUDPPayload * transportSettings_.maxBatchSize);
VLOG(10) << "GSO write buf accessor created for ContinuousMemory data path";
}
}
void QuicServerWorker::rejectNewConnections(bool rejectNewConnections) {
rejectNewConnections_ = rejectNewConnections;
}
void QuicServerWorker::enablePartialReliability(bool enabled) {
transportSettings_.partialReliabilityEnabled = enabled;
}
void QuicServerWorker::setHealthCheckToken(
const std::string& healthCheckToken) {
healthCheckToken_ = folly::IOBuf::copyBuffer(healthCheckToken);
}
std::unique_ptr<folly::AsyncUDPSocket> QuicServerWorker::makeSocket(
folly::EventBase* evb) const {
CHECK(socket_);
return socketFactory_->make(evb, socket_->getNetworkSocket().toFd());
}
std::unique_ptr<folly::AsyncUDPSocket> QuicServerWorker::makeSocket(
folly::EventBase* evb,
int fd) const {
return socketFactory_->make(evb, fd);
}
const QuicServerWorker::ConnIdToTransportMap&
QuicServerWorker::getConnectionIdMap() const {
return connectionIdMap_;
}
const QuicServerWorker::SrcToTransportMap&
QuicServerWorker::getSrcToTransportMap() const {
return sourceAddressMap_;
}
void QuicServerWorker::onConnectionIdAvailable(
QuicServerTransport::Ptr transport,
ConnectionId id) noexcept {
VLOG(4) << "Adding into connectionIdMap_ for CID=" << id << " " << *transport;
QuicServerTransport* transportPtr = transport.get();
std::weak_ptr<QuicServerTransport> weakTransport = transport;
auto result =
connectionIdMap_.emplace(std::make_pair(id, std::move(transport)));
if (!result.second) {
// In the case of duplicates, log if they represent the same transport,
// or different ones.
auto it = result.first;
QuicServerTransport* existingTransportPtr = it->second.get();
LOG(ERROR) << "connectionIdMap_ already has CID=" << id
<< " Is same transport: "
<< (existingTransportPtr == transportPtr);
} else if (boundServerTransports_.emplace(transportPtr, weakTransport)
.second) {
QUIC_STATS(statsCallback_, onNewConnection);
}
}
void QuicServerWorker::onConnectionIdBound(
QuicServerTransport::Ptr transport) noexcept {
auto clientInitialDestCid = transport->getClientChosenDestConnectionId();
CHECK(clientInitialDestCid);
auto source = std::make_pair(
transport->getOriginalPeerAddress(), *clientInitialDestCid);
VLOG(4) << "Removing from sourceAddressMap_ address=" << source.first;
auto iter = sourceAddressMap_.find(source);
if (iter == sourceAddressMap_.end() || iter->second != transport) {
LOG(ERROR) << "Transport not match, client=" << *transport;
} else {
sourceAddressMap_.erase(source);
}
}
void QuicServerWorker::onConnectionUnbound(
QuicServerTransport* transport,
const QuicServerTransport::SourceIdentity& source,
const std::vector<ConnectionIdData>& connectionIdData) noexcept {
VLOG(4) << "Removing from sourceAddressMap_ address=" << source.first;
// Ensures we only process `onConnectionUnbound()` once.
transport->setRoutingCallback(nullptr);
boundServerTransports_.erase(transport);
if (connectionIdData.size()) {
QUIC_STATS(statsCallback_, onConnectionClose, folly::none);
}
for (auto& connId : connectionIdData) {
VLOG(4) << folly::format(
"Removing CID from connectionIdMap_, routingInfo={}",
logRoutingInfo(connId.connId));
auto it = connectionIdMap_.find(connId.connId);
// This should be nullptr in most cases. In order to investigate if
// an incorrect server transport is removed, this will be set to the value
// of the incorrect transport, to see if boundServerTransports_ will
// still hold a pointer to the incorrect transport.
QuicServerTransport* incorrectTransportPtr = nullptr;
if (it == connectionIdMap_.end()) {
LOG(ERROR) << "connectionIdMap_ didn't include CID= " << connId.connId;
} else {
QuicServerTransport* existingPtr = it->second.get();
if (existingPtr != transport) {
LOG(ERROR) << "Incorrect transport being removed for duplicate CID="
<< connId.connId;
incorrectTransportPtr = existingPtr;
}
}
connectionIdMap_.erase(connId.connId);
if (incorrectTransportPtr != nullptr) {
if (boundServerTransports_.find(incorrectTransportPtr) !=
boundServerTransports_.end()) {
LOG(ERROR)
<< "boundServerTransports_ contains deleted transport for duplicate CID="
<< connId.connId;
}
}
}
// TODO: verify we are removing the right transport
sourceAddressMap_.erase(source);
}
void QuicServerWorker::shutdownAllConnections(LocalErrorCode error) {
VLOG(4) << "QuicServer shutdown all connections."
<< " addressMap=" << sourceAddressMap_.size()
<< " connectionIdMap=" << connectionIdMap_.size();
if (shutdown_) {
return;
}
shutdown_ = true;
if (socket_) {
socket_->pauseRead();
}
if (takeoverCB_) {
takeoverCB_->pause();
}
callback_ = nullptr;
// Shut down all transports without bound connection ids.
for (auto& it : sourceAddressMap_) {
auto transport = it.second;
transport->setRoutingCallback(nullptr);
transport->setTransportStatsCallback(nullptr);
transport->closeNow(
std::make_pair(QuicErrorCode(error), std::string("shutting down")));
}
// Shut down all transports with bound connection ids.
for (auto transport : boundServerTransports_) {
if (auto t = transport.second.lock()) {
t->setRoutingCallback(nullptr);
t->setTransportStatsCallback(nullptr);
t->closeNow(
std::make_pair(QuicErrorCode(error), std::string("shutting down")));
QUIC_STATS(statsCallback_, onConnectionClose, folly::none);
}
}
sourceAddressMap_.clear();
connectionIdMap_.clear();
takeoverPktHandler_.stop();
if (statsCallback_) {
statsCallback_.reset();
}
socket_.reset();
takeoverCB_.reset();
}
QuicServerWorker::~QuicServerWorker() {
shutdownAllConnections(LocalErrorCode::SHUTTING_DOWN);
}
bool QuicServerWorker::rejectConnectionId(const ConnectionId& candidate) const
noexcept {
return connectionIdMap_.find(candidate) != connectionIdMap_.end();
}
std::string QuicServerWorker::logRoutingInfo(const ConnectionId& connId) const {
folly::StringPiece base =
"CID={}, workerId={}, processId={}, hostId={}, threadId={}, ";
if (!connIdAlgo_->canParse(connId)) {
return folly::format(
base,
connId.hex(),
(uint32_t)workerId_,
(uint32_t)processId_,
(uint32_t)hostId_,
folly::getCurrentThreadID())
.str();
}
auto connIdParam = connIdAlgo_->parseConnectionId(connId);
if (connIdParam.hasError()) {
return folly::format(
base,
connId.hex(),
(uint32_t)workerId_,
(uint32_t)processId_,
(uint32_t)hostId_,
folly::getCurrentThreadID())
.str();
}
std::string extended = base.toString() +
"workerId in packet={}, processId in packet={}, hostId in packet={}, ";
return folly::format(
extended,
connId.hex(),
(uint32_t)workerId_,
(uint32_t)processId_,
(uint32_t)hostId_,
folly::getCurrentThreadID(),
(uint32_t)connIdParam->workerId,
(uint32_t)connIdParam->processId,
(uint32_t)connIdParam->hostId)
.str();
}
} // namespace quic