quic/server/state/ServerStateMachine.cpp (1,268 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include <quic/server/handshake/TokenGenerator.h> #include <quic/server/state/ServerStateMachine.h> #include <quic/api/QuicTransportFunctions.h> #include <quic/common/BufUtil.h> #include <quic/congestion_control/CongestionControllerFactory.h> #include <quic/congestion_control/TokenlessPacer.h> #include <quic/fizz/handshake/FizzCryptoFactory.h> #include <quic/flowcontrol/QuicFlowController.h> #include <quic/handshake/TransportParameters.h> #include <quic/logging/QLoggerConstants.h> #include <quic/state/DatagramHandlers.h> #include <quic/state/QuicPacingFunctions.h> #include <quic/state/QuicStreamFunctions.h> #include <quic/state/QuicTransportStatsCallback.h> #include <quic/state/SimpleFrameFunctions.h> #include <quic/state/stream/StreamReceiveHandlers.h> #include <quic/state/stream/StreamSendHandlers.h> namespace quic { using namespace std::chrono_literals; namespace { using PacketDropReason = QuicTransportStatsCallback::PacketDropReason; constexpr size_t kConnIdEncodingRetryLimit = 16; bool maybeNATRebinding( const folly::SocketAddress& newPeerAddress, const folly::SocketAddress& oldPeerAddress) { auto& newIPAddr = newPeerAddress.getIPAddress(); auto& oldIPAddr = oldPeerAddress.getIPAddress(); // Port changed if (newIPAddr == oldIPAddr) { return true; } return newIPAddr.isV4() && oldIPAddr.isV4() && newIPAddr.inSubnet(oldIPAddr, 24); } CongestionAndRttState moveCurrentCongestionAndRttState( QuicServerConnectionState& conn) { CongestionAndRttState state; state.peerAddress = conn.peerAddress; state.recordTime = Clock::now(); state.congestionController = std::move(conn.congestionController); state.srtt = conn.lossState.srtt; state.lrtt = conn.lossState.lrtt; state.rttvar = conn.lossState.rttvar; state.mrtt = conn.lossState.mrtt; return state; } void resetCongestionAndRttState(QuicServerConnectionState& conn) { CHECK(conn.congestionControllerFactory) << "CongestionControllerFactory is not set."; conn.congestionController = conn.congestionControllerFactory->makeCongestionController( conn, conn.transportSettings.defaultCongestionController); conn.lossState.srtt = 0us; conn.lossState.lrtt = 0us; conn.lossState.rttvar = 0us; conn.lossState.mrtt = kDefaultMinRtt; } void recoverOrResetCongestionAndRttState( QuicServerConnectionState& conn, const folly::SocketAddress& peerAddress) { auto& lastState = conn.migrationState.lastCongestionAndRtt; if (lastState && lastState->peerAddress == peerAddress && (Clock::now() - lastState->recordTime <= kTimeToRetainLastCongestionAndRttState)) { // recover from matched non-stale state conn.congestionController = std::move(lastState->congestionController); conn.lossState.srtt = lastState->srtt; conn.lossState.lrtt = lastState->lrtt; conn.lossState.rttvar = lastState->rttvar; conn.lossState.mrtt = lastState->mrtt; conn.migrationState.lastCongestionAndRtt = folly::none; } else { resetCongestionAndRttState(conn); } } void setExperimentalSettings(QuicServerConnectionState& conn) { // MVFST_EXPERIMENTAL currently enables experimental congestion control // and experimental pacer. (here and in the client transport) if (conn.congestionController) { conn.congestionController->setExperimental(true); } if (conn.pacer) { conn.pacer->setExperimental(true); } } } // namespace void processClientInitialParams( QuicServerConnectionState& conn, const ClientTransportParameters& clientParams) { auto preferredAddress = getIntegerParameter( TransportParameterId::preferred_address, clientParams.parameters); auto origConnId = getIntegerParameter( TransportParameterId::original_destination_connection_id, clientParams.parameters); auto statelessResetToken = getIntegerParameter( TransportParameterId::stateless_reset_token, clientParams.parameters); auto retrySourceConnId = getIntegerParameter( TransportParameterId::retry_source_connection_id, clientParams.parameters); auto maxData = getIntegerParameter( TransportParameterId::initial_max_data, clientParams.parameters); auto maxStreamDataBidiLocal = getIntegerParameter( TransportParameterId::initial_max_stream_data_bidi_local, clientParams.parameters); auto maxStreamDataBidiRemote = getIntegerParameter( TransportParameterId::initial_max_stream_data_bidi_remote, clientParams.parameters); auto maxStreamDataUni = getIntegerParameter( TransportParameterId::initial_max_stream_data_uni, clientParams.parameters); auto maxStreamsBidi = getIntegerParameter( TransportParameterId::initial_max_streams_bidi, clientParams.parameters); auto maxStreamsUni = getIntegerParameter( TransportParameterId::initial_max_streams_uni, clientParams.parameters); auto idleTimeout = getIntegerParameter( TransportParameterId::idle_timeout, clientParams.parameters); auto ackDelayExponent = getIntegerParameter( TransportParameterId::ack_delay_exponent, clientParams.parameters); auto packetSize = getIntegerParameter( TransportParameterId::max_packet_size, clientParams.parameters); auto activeConnectionIdLimit = getIntegerParameter( TransportParameterId::active_connection_id_limit, clientParams.parameters); auto d6dBasePMTU = getIntegerParameter( static_cast<TransportParameterId>(kD6DBasePMTUParameterId), clientParams.parameters); auto d6dRaiseTimeout = getIntegerParameter( static_cast<TransportParameterId>(kD6DRaiseTimeoutParameterId), clientParams.parameters); auto d6dProbeTimeout = getIntegerParameter( static_cast<TransportParameterId>(kD6DProbeTimeoutParameterId), clientParams.parameters); auto minAckDelay = getIntegerParameter( TransportParameterId::min_ack_delay, clientParams.parameters); auto maxAckDelay = getIntegerParameter( TransportParameterId::max_ack_delay, clientParams.parameters); auto maxDatagramFrameSize = getIntegerParameter( TransportParameterId::max_datagram_frame_size, clientParams.parameters); if (conn.version == QuicVersion::QUIC_DRAFT || conn.version == QuicVersion::QUIC_V1) { auto initialSourceConnId = getConnIdParameter( TransportParameterId::initial_source_connection_id, clientParams.parameters); if (!initialSourceConnId || initialSourceConnId.value() != conn.readCodec->getClientConnectionId()) { throw QuicTransportException( "Initial CID does not match.", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } } // validate that we didn't receive original connection ID, stateless // reset token, or preferred address. if (preferredAddress && *preferredAddress != 0) { throw QuicTransportException( "Preferred Address is received by server", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } if (origConnId && *origConnId != 0) { throw QuicTransportException( "OriginalDestinationConnectionId is received by server", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } if (statelessResetToken && statelessResetToken.value() != 0) { throw QuicTransportException( "Stateless Reset Token is received by server", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } if (retrySourceConnId && retrySourceConnId.value() != 0) { throw QuicTransportException( "Retry Source Connection ID is received by server", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } if (maxAckDelay && *maxAckDelay >= kMaxAckDelay) { throw QuicTransportException( "Max Ack Delay is greater than 2^14 ", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } // TODO Validate active_connection_id_limit if (packetSize && *packetSize < kMinMaxUDPPayload) { throw QuicTransportException( folly::to<std::string>( "Max packet size too small. received max_packetSize = ", *packetSize), TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } VLOG(10) << "Client advertised flow control "; VLOG(10) << "conn=" << maxData.value_or(0); VLOG(10) << " stream bidi local=" << maxStreamDataBidiLocal.value_or(0) << " "; VLOG(10) << " stream bidi remote=" << maxStreamDataBidiRemote.value_or(0) << " "; VLOG(10) << " stream uni=" << maxStreamDataUni.value_or(0) << " "; VLOG(10) << conn; conn.flowControlState.peerAdvertisedMaxOffset = maxData.value_or(0); conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiLocal = maxStreamDataBidiLocal.value_or(0); conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetBidiRemote = maxStreamDataBidiRemote.value_or(0); conn.flowControlState.peerAdvertisedInitialMaxStreamOffsetUni = maxStreamDataUni.value_or(0); conn.streamManager->setMaxLocalBidirectionalStreams( maxStreamsBidi.value_or(0)); conn.streamManager->setMaxLocalUnidirectionalStreams( maxStreamsUni.value_or(0)); conn.peerIdleTimeout = std::chrono::milliseconds(idleTimeout.value_or(0)); conn.peerIdleTimeout = timeMin(conn.peerIdleTimeout, kMaxIdleTimeout); if (ackDelayExponent && *ackDelayExponent > kMaxAckDelayExponent) { throw QuicTransportException( "ack_delay_exponent too large", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } conn.peerAckDelayExponent = ackDelayExponent.value_or(kDefaultAckDelayExponent); if (minAckDelay.hasValue()) { conn.peerMinAckDelay = std::chrono::microseconds(minAckDelay.value()); } if (maxDatagramFrameSize.hasValue()) { if (maxDatagramFrameSize.value() > 0 && maxDatagramFrameSize.value() <= kMaxDatagramPacketOverhead) { throw QuicTransportException( "max_datagram_frame_size too small", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } conn.datagramState.maxWriteFrameSize = maxDatagramFrameSize.value(); } // Default to max because we can probe PMTU now, and this will be the upper // limit uint64_t maxUdpPayloadSize = kDefaultMaxUDPPayload; if (packetSize) { maxUdpPayloadSize = std::min(*packetSize, maxUdpPayloadSize); conn.peerMaxUdpPayloadSize = maxUdpPayloadSize; if (conn.transportSettings.canIgnorePathMTU) { if (*packetSize > kDefaultMaxUDPPayload) { // A good peer should never set oversized limit, so to be safe we // fallback to default conn.udpSendPacketLen = kDefaultUDPSendPacketLen; } else { // Otherwise, canIgnorePathMTU forces us to immediately set // udpSendPacketLen // TODO: rename "canIgnorePathMTU" to "forciblySetPathMTU" conn.udpSendPacketLen = maxUdpPayloadSize; } } } conn.peerActiveConnectionIdLimit = activeConnectionIdLimit.value_or(kDefaultActiveConnectionIdLimit); if (conn.transportSettings.d6dConfig.enabled) { // Sanity check if (d6dBasePMTU) { if (*d6dBasePMTU >= kMinMaxUDPPayload && *d6dBasePMTU <= kDefaultMaxUDPPayload) { // The reason to take the max is because we don't want d6d to send // probes with a smaller packet size than udpSendPacketLen, which would // be useless and cause meaningless delay on finding the upper bound. conn.d6d.basePMTU = std::max(*d6dBasePMTU, conn.udpSendPacketLen); conn.d6d.maxPMTU = maxUdpPayloadSize; VLOG(10) << "conn.d6d.basePMTU=" << conn.d6d.basePMTU; // Start from base conn.d6d.state = D6DMachineState::BASE; conn.d6d.meta.lastNonSearchState = D6DMachineState::DISABLED; conn.d6d.meta.timeLastNonSearchState = Clock::now(); // Temporary, should be removed after transport knob pipeline works conn.d6d.noBlackholeDetection = true; } else { LOG(ERROR) << "client d6dBasePMTU fails sanity check: " << *d6dBasePMTU; // We treat base pmtu transport param as client's swich to activate d6d, // so not receiving that means there's no need to configure the rest d6d // params return; } } if (d6dRaiseTimeout) { if (*d6dRaiseTimeout >= kMinD6DRaiseTimeout.count()) { conn.d6d.raiseTimeout = std::chrono::seconds(*d6dRaiseTimeout); VLOG(10) << "conn.d6d.raiseTimeout=" << conn.d6d.raiseTimeout.count(); } else { LOG(ERROR) << "client d6dRaiseTimeout fails sanity check: " << *d6dRaiseTimeout; } } if (d6dProbeTimeout) { if (*d6dProbeTimeout >= kMinD6DProbeTimeout.count()) { conn.d6d.probeTimeout = std::chrono::seconds(*d6dProbeTimeout); VLOG(10) << "conn.d6d.probeTimeout=" << conn.d6d.probeTimeout.count(); } else { LOG(ERROR) << "client d6dProbeTimeout fails sanity check: " << *d6dProbeTimeout; } } } } void updateHandshakeState(QuicServerConnectionState& conn) { // Zero RTT read cipher is available after chlo is processed with the // condition that early data attempt is accepted. auto handshakeLayer = conn.serverHandshakeLayer; auto zeroRttReadCipher = handshakeLayer->getZeroRttReadCipher(); auto zeroRttHeaderCipher = handshakeLayer->getZeroRttReadHeaderCipher(); // One RTT write cipher is available at Fizz layer after chlo is processed. // However, the cipher is only exported to QUIC if early data attempt is // accepted. Otherwise, the cipher will be available after cfin is // processed. auto oneRttWriteCipher = handshakeLayer->getOneRttWriteCipher(); // One RTT read cipher is available after cfin is processed. auto oneRttReadCipher = handshakeLayer->getOneRttReadCipher(); auto oneRttWriteHeaderCipher = handshakeLayer->getOneRttWriteHeaderCipher(); auto oneRttReadHeaderCipher = handshakeLayer->getOneRttReadHeaderCipher(); if (zeroRttReadCipher) { conn.usedZeroRtt = true; if (conn.qLogger) { conn.qLogger->addTransportStateUpdate(kDerivedZeroRttReadCipher); } conn.readCodec->setZeroRttReadCipher(std::move(zeroRttReadCipher)); } if (zeroRttHeaderCipher) { conn.readCodec->setZeroRttHeaderCipher(std::move(zeroRttHeaderCipher)); } if (oneRttWriteHeaderCipher) { conn.oneRttWriteHeaderCipher = std::move(oneRttWriteHeaderCipher); } if (oneRttReadHeaderCipher) { conn.readCodec->setOneRttHeaderCipher(std::move(oneRttReadHeaderCipher)); } if (oneRttWriteCipher) { if (conn.qLogger) { conn.qLogger->addTransportStateUpdate(kDerivedOneRttWriteCipher); } if (conn.oneRttWriteCipher) { throw QuicTransportException( "Duplicate 1-rtt write cipher", TransportErrorCode::CRYPTO_ERROR); } conn.oneRttWriteCipher = std::move(oneRttWriteCipher); updatePacingOnKeyEstablished(conn); // We negotiate the transport parameters whenever we have the 1-RTT write // keys available. auto clientParams = handshakeLayer->getClientTransportParams(); if (!clientParams) { throw QuicTransportException( "No client transport params", TransportErrorCode::TRANSPORT_PARAMETER_ERROR); } processClientInitialParams(conn, std::move(*clientParams)); } if (oneRttReadCipher) { if (conn.qLogger) { conn.qLogger->addTransportStateUpdate(kDerivedOneRttReadCipher); } // Clear limit because CFIN is received at this point conn.writableBytesLimit = folly::none; conn.readCodec->setOneRttReadCipher(std::move(oneRttReadCipher)); } auto handshakeReadCipher = handshakeLayer->getHandshakeReadCipher(); auto handshakeReadHeaderCipher = handshakeLayer->getHandshakeReadHeaderCipher(); if (handshakeReadCipher) { CHECK(handshakeReadHeaderCipher); conn.readCodec->setHandshakeReadCipher(std::move(handshakeReadCipher)); conn.readCodec->setHandshakeHeaderCipher( std::move(handshakeReadHeaderCipher)); } if (handshakeLayer->isHandshakeDone()) { CHECK(conn.oneRttWriteCipher); if (!conn.sentHandshakeDone) { sendSimpleFrame(conn, HandshakeDoneFrame()); conn.sentHandshakeDone = true; } if (!conn.sentNewTokenFrame && conn.transportSettings.retryTokenSecret.has_value()) { // Create NewToken struct – defaults timestamp to now NewToken token(conn.peerAddress.getIPAddress()); // Encrypt two tuple -> (clientIp, curTimeInMs) TokenGenerator generator(conn.transportSettings.retryTokenSecret.value()); auto encryptedToken = generator.encryptToken(token); CHECK(encryptedToken.has_value()); std::string encryptedTokenStr = encryptedToken.value()->moveToFbString().toStdString(); sendSimpleFrame(conn, NewTokenFrame(std::move(encryptedTokenStr))); QUIC_STATS(conn.statsCallback, onNewTokenIssued); conn.sentNewTokenFrame = true; } } } bool validateAndUpdateSourceToken( QuicServerConnectionState& conn, std::vector<folly::IPAddress> sourceAddresses) { DCHECK(conn.peerAddress.isInitialized()); bool foundMatch = false; for (int ii = sourceAddresses.size() - 1; ii >= 0; --ii) { // TODO T33014230 subnet matching if (conn.peerAddress.getIPAddress() == sourceAddresses[ii]) { foundMatch = true; // If peer address is found in the token, move the element to the end // of vector to increase its favorability. sourceAddresses.erase(sourceAddresses.begin() + ii); sourceAddresses.push_back(conn.peerAddress.getIPAddress()); } } conn.sourceTokenMatching = foundMatch; bool acceptZeroRtt = (conn.transportSettings.zeroRttSourceTokenMatchingPolicy != ZeroRttSourceTokenMatchingPolicy::ALWAYS_REJECT) && foundMatch; if (!foundMatch) { // Add peer address to token for next resumption if (sourceAddresses.size() >= kMaxNumTokenSourceAddresses) { sourceAddresses.erase(sourceAddresses.begin()); } sourceAddresses.push_back(conn.peerAddress.getIPAddress()); switch (conn.transportSettings.zeroRttSourceTokenMatchingPolicy) { case ZeroRttSourceTokenMatchingPolicy::ALWAYS_REJECT: case ZeroRttSourceTokenMatchingPolicy::REJECT_IF_NO_EXACT_MATCH: acceptZeroRtt = false; break; case ZeroRttSourceTokenMatchingPolicy::LIMIT_IF_NO_EXACT_MATCH: acceptZeroRtt = true; conn.writableBytesLimit = conn.transportSettings.limitedCwndInMss * conn.udpSendPacketLen; break; } } // Save the source token so that it can be written to client via NST later conn.tokenSourceAddresses = std::move(sourceAddresses); return acceptZeroRtt; } void updateWritableByteLimitOnRecvPacket(QuicServerConnectionState& conn) { // When we receive a packet we increase the limit again. The reasoning this is // that a peer can do the same by opening a new connection. if (conn.writableBytesLimit) { conn.writableBytesLimit = *conn.writableBytesLimit + conn.transportSettings.limitedCwndInMss * conn.udpSendPacketLen; } } void updateTransportParamsFromTicket( QuicServerConnectionState& conn, uint64_t idleTimeout, uint64_t maxRecvPacketSize, uint64_t initialMaxData, uint64_t initialMaxStreamDataBidiLocal, uint64_t initialMaxStreamDataBidiRemote, uint64_t initialMaxStreamDataUni, uint64_t initialMaxStreamsBidi, uint64_t initialMaxStreamsUni) { conn.transportSettings.idleTimeout = std::chrono::milliseconds(idleTimeout); conn.transportSettings.maxRecvPacketSize = maxRecvPacketSize; conn.transportSettings.advertisedInitialConnectionWindowSize = initialMaxData; conn.transportSettings.advertisedInitialBidiLocalStreamWindowSize = initialMaxStreamDataBidiLocal; conn.transportSettings.advertisedInitialBidiRemoteStreamWindowSize = initialMaxStreamDataBidiRemote; conn.transportSettings.advertisedInitialUniStreamWindowSize = initialMaxStreamDataUni; updateFlowControlStateWithSettings( conn.flowControlState, conn.transportSettings); conn.transportSettings.advertisedInitialMaxStreamsBidi = initialMaxStreamsBidi; conn.transportSettings.advertisedInitialMaxStreamsUni = initialMaxStreamsUni; } void onConnectionMigration( QuicServerConnectionState& conn, const folly::SocketAddress& newPeerAddress, bool isIntentional) { if (conn.migrationState.numMigrations >= kMaxNumMigrationsAllowed) { if (conn.qLogger) { conn.qLogger->addPacketDrop( 0, QuicTransportStatsCallback::toString( PacketDropReason::PEER_ADDRESS_CHANGE)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PEER_ADDRESS_CHANGE); throw QuicTransportException( "Too many migrations", TransportErrorCode::INVALID_MIGRATION); } ++conn.migrationState.numMigrations; bool hasPendingPathChallenge = conn.pendingEvents.pathChallenge.has_value(); // Clear any pending path challenge frame that is not sent conn.pendingEvents.pathChallenge = folly::none; auto& previousPeerAddresses = conn.migrationState.previousPeerAddresses; auto it = std::find( previousPeerAddresses.begin(), previousPeerAddresses.end(), newPeerAddress); if (it == previousPeerAddresses.end()) { // Send new path challenge uint64_t pathData; folly::Random::secureRandom(&pathData, sizeof(pathData)); conn.pendingEvents.pathChallenge = PathChallengeFrame(pathData); // If we are already in the middle of a migration reset // the available bytes in the rate-limited window, but keep the // window. conn.pathValidationLimiter = std::make_unique<PendingPathRateLimiter>(conn.udpSendPacketLen); } else { previousPeerAddresses.erase(it); } // At this point, path validation scheduled, writable bytes limit set // However if this is NAT rebinding, keep congestion state unchanged bool isNATRebinding = maybeNATRebinding(newPeerAddress, conn.peerAddress); // Cancel current path validation if any if (hasPendingPathChallenge || conn.outstandingPathValidation) { conn.pendingEvents.schedulePathValidationTimeout = false; conn.outstandingPathValidation = folly::none; // Only change congestion & rtt state if not NAT rebinding if (!isNATRebinding) { recoverOrResetCongestionAndRttState(conn, newPeerAddress); } } else { // Only add validated addresses to previousPeerAddresses conn.migrationState.previousPeerAddresses.push_back(conn.peerAddress); // Only change congestion & rtt state if not NAT rebinding if (!isNATRebinding) { // Current peer address is validated, // remember its congestion state and rtt stats CongestionAndRttState state = moveCurrentCongestionAndRttState(conn); recoverOrResetCongestionAndRttState(conn, newPeerAddress); conn.migrationState.lastCongestionAndRtt = std::move(state); } } if (conn.qLogger) { conn.qLogger->addConnectionMigrationUpdate(isIntentional); } conn.peerAddress = newPeerAddress; } void onServerReadData( QuicServerConnectionState& conn, ServerEvents::ReadData& readData) { switch (conn.state) { case ServerState::Open: onServerReadDataFromOpen(conn, readData); return; case ServerState::Closed: onServerReadDataFromClosed(conn, readData); return; } } void handleCipherUnavailable( CipherUnavailable* originalData, QuicServerConnectionState& conn, size_t packetSize, ServerEvents::ReadData& readData) { if (!originalData->packet || originalData->packet->empty()) { VLOG(10) << "drop because no data " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kNoData); } return; } if (originalData->protectionType != ProtectionType::ZeroRtt && originalData->protectionType != ProtectionType::KeyPhaseZero) { VLOG(10) << "drop because unexpected protection level " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kUnexpectedProtectionLevel); } return; } size_t combinedSize = (conn.pendingZeroRttData ? conn.pendingZeroRttData->size() : 0) + (conn.pendingOneRttData ? conn.pendingOneRttData->size() : 0); if (combinedSize >= conn.transportSettings.maxPacketsToBuffer) { VLOG(10) << "drop because max buffered " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kMaxBuffered); } return; } auto& pendingData = originalData->protectionType == ProtectionType::ZeroRtt ? conn.pendingZeroRttData : conn.pendingOneRttData; if (pendingData) { if (conn.qLogger) { conn.qLogger->addPacketBuffered(originalData->protectionType, packetSize); } ServerEvents::ReadData pendingReadData; pendingReadData.peer = readData.peer; pendingReadData.networkData = NetworkDataSingle( std::move(originalData->packet), readData.networkData.receiveTimePoint); pendingData->emplace_back(std::move(pendingReadData)); VLOG(10) << "Adding pending data to " << toString(originalData->protectionType) << " buffer size=" << pendingData->size() << " " << conn; } else { VLOG(10) << "drop because " << toString(originalData->protectionType) << " buffer no longer available " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kBufferUnavailable); } } } void onServerReadDataFromOpen( QuicServerConnectionState& conn, ServerEvents::ReadData& readData) { CHECK_EQ(conn.state, ServerState::Open); // Don't bother parsing if the data is empty. if (!readData.networkData.data || readData.networkData.data->computeChainDataLength() == 0) { return; } bool firstPacketFromPeer = false; if (!conn.readCodec) { firstPacketFromPeer = true; folly::io::Cursor cursor(readData.networkData.data.get()); auto initialByte = cursor.readBE<uint8_t>(); auto parsedLongHeader = parseLongHeaderInvariant(initialByte, cursor); if (!parsedLongHeader) { VLOG(4) << "Could not parse initial packet header"; if (conn.qLogger) { conn.qLogger->addPacketDrop( 0, QuicTransportStatsCallback::toString( PacketDropReason::PARSE_ERROR)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PARSE_ERROR); return; } QuicVersion version = parsedLongHeader->invariant.version; if (version == QuicVersion::VERSION_NEGOTIATION) { VLOG(4) << "Server droppiong VN packet"; if (conn.qLogger) { conn.qLogger->addPacketDrop( 0, QuicTransportStatsCallback::toString( PacketDropReason::INVALID_PACKET)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::INVALID_PACKET); return; } const auto& clientConnectionId = parsedLongHeader->invariant.srcConnId; const auto& initialDestinationConnectionId = parsedLongHeader->invariant.dstConnId; if (initialDestinationConnectionId.size() < kDefaultConnectionIdSize) { VLOG(4) << "Initial connectionid too small"; if (conn.qLogger) { conn.qLogger->addPacketDrop( 0, QuicTransportStatsCallback::toString( PacketDropReason::INITIAL_CONNID_SMALL)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::INITIAL_CONNID_SMALL); return; } CHECK(conn.connIdAlgo) << "ConnectionIdAlgo is not set."; CHECK(!conn.serverConnectionId.has_value()); // serverConnIdParams must be set by the QuicServerTransport CHECK(conn.serverConnIdParams); auto newServerConnIdData = conn.createAndAddNewSelfConnId(); CHECK(newServerConnIdData.has_value()); conn.serverConnectionId = newServerConnIdData->connId; auto customTransportParams = setSupportedExtensionTransportParameters(conn); QUIC_STATS(conn.statsCallback, onStatelessReset); conn.serverHandshakeLayer->accept( std::make_shared<ServerTransportParametersExtension>( version, conn.transportSettings.advertisedInitialConnectionWindowSize, conn.transportSettings.advertisedInitialBidiLocalStreamWindowSize, conn.transportSettings.advertisedInitialBidiRemoteStreamWindowSize, conn.transportSettings.advertisedInitialUniStreamWindowSize, conn.transportSettings.advertisedInitialMaxStreamsBidi, conn.transportSettings.advertisedInitialMaxStreamsUni, conn.transportSettings.idleTimeout, conn.transportSettings.ackDelayExponent, conn.transportSettings.maxRecvPacketSize, *newServerConnIdData->token, conn.serverConnectionId.value(), initialDestinationConnectionId, customTransportParams)); conn.transportParametersEncoded = true; const CryptoFactory& cryptoFactory = conn.serverHandshakeLayer->getCryptoFactory(); conn.readCodec = std::make_unique<QuicReadCodec>(QuicNodeType::Server); conn.readCodec->setInitialReadCipher(cryptoFactory.getClientInitialCipher( initialDestinationConnectionId, version)); conn.readCodec->setClientConnectionId(clientConnectionId); conn.readCodec->setServerConnectionId(*conn.serverConnectionId); if (conn.qLogger) { conn.qLogger->setScid(conn.serverConnectionId); conn.qLogger->setDcid(initialDestinationConnectionId); } conn.readCodec->setCodecParameters( CodecParameters(conn.peerAckDelayExponent, version)); conn.initialWriteCipher = cryptoFactory.getServerInitialCipher( initialDestinationConnectionId, version); conn.readCodec->setInitialHeaderCipher( cryptoFactory.makeClientInitialHeaderCipher( initialDestinationConnectionId, version)); conn.initialHeaderCipher = cryptoFactory.makeServerInitialHeaderCipher( initialDestinationConnectionId, version); conn.peerAddress = conn.originalPeerAddress; } BufQueue udpData; udpData.append(std::move(readData.networkData.data)); for (uint16_t processedPackets = 0; !udpData.empty() && processedPackets < kMaxNumCoalescedPackets; processedPackets++) { size_t dataSize = udpData.chainLength(); auto parsedPacket = conn.readCodec->parsePacket(udpData, conn.ackStates); size_t packetSize = dataSize - udpData.chainLength(); switch (parsedPacket.type()) { case CodecResult::Type::CIPHER_UNAVAILABLE: { handleCipherUnavailable( parsedPacket.cipherUnavailable(), conn, packetSize, readData); break; } case CodecResult::Type::RETRY: { VLOG(10) << "drop because the server is not supposed to " << "receive a retry " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kRetry); } break; } case CodecResult::Type::STATELESS_RESET: { VLOG(10) << "drop because reset " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kReset); } break; } case CodecResult::Type::NOTHING: { VLOG(10) << "drop cipher unavailable, no data " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kCipherUnavailable); } if (firstPacketFromPeer) { throw QuicInternalException( "Failed to decrypt first packet from peer", LocalErrorCode::CONNECTION_ABANDONED); } break; } case CodecResult::Type::REGULAR_PACKET: break; } RegularQuicPacket* regularOptional = parsedPacket.regularPacket(); if (!regularOptional) { // We were unable to parse the packet, drop for now. All the drop reasons // should have already been logged into QLogger and QuicTrace inside the // previous switch-case block. We just need to update QUIC_STATS here. VLOG(10) << "Not able to parse QUIC packet " << conn; QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PARSE_ERROR); continue; } if (regularOptional->frames.empty()) { // This packet had a pareseable header (most probably short header) // but no data. This is a protocol violation so we throw an exception. // This drop has not been recorded in the switch-case block above // so we record it here. if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PROTOCOL_VIOLATION)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PROTOCOL_VIOLATION); throw QuicTransportException( "Packet has no frames", TransportErrorCode::PROTOCOL_VIOLATION); } auto protectionLevel = regularOptional->header.getProtectionType(); auto encryptionLevel = protectionTypeToEncryptionLevel(protectionLevel); auto packetNum = regularOptional->header.getPacketSequenceNum(); auto packetNumberSpace = regularOptional->header.getPacketNumberSpace(); auto& regularPacket = *regularOptional; bool isProtectedPacket = protectionLevel == ProtectionType::ZeroRtt || protectionLevel == ProtectionType::KeyPhaseZero || protectionLevel == ProtectionType::KeyPhaseOne; if (!isProtectedPacket) { for (auto& quicFrame : regularPacket.frames) { auto isPadding = quicFrame.asPaddingFrame(); auto isAck = quicFrame.asReadAckFrame(); auto isClose = quicFrame.asConnectionCloseFrame(); auto isCrypto = quicFrame.asReadCryptoFrame(); auto isPing = quicFrame.asPingFrame(); // TODO: add path challenge and response if (!isPadding && !isAck && !isClose && !isCrypto && !isPing) { QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PROTOCOL_VIOLATION); if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PROTOCOL_VIOLATION)); } throw QuicTransportException( "Invalid frame", TransportErrorCode::PROTOCOL_VIOLATION); } } } CHECK(conn.clientConnectionId); if (conn.qLogger) { conn.qLogger->addPacket(regularPacket, packetSize); } // We assume that the higher layer takes care of validating that the version // is supported. if (!conn.version) { LongHeader* longHeader = regularPacket.header.asLong(); if (!longHeader) { throw QuicTransportException( "Invalid packet type", TransportErrorCode::PROTOCOL_VIOLATION); } conn.version = longHeader->getVersion(); if (conn.version == QuicVersion::MVFST_EXPERIMENTAL) { setExperimentalSettings(conn); } } if (conn.peerAddress != readData.peer) { if (encryptionLevel != EncryptionLevel::AppData) { if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PEER_ADDRESS_CHANGE)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PEER_ADDRESS_CHANGE); throw QuicTransportException( "Migration not allowed during handshake", TransportErrorCode::INVALID_MIGRATION); } if (conn.transportSettings.disableMigration) { if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PEER_ADDRESS_CHANGE)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PEER_ADDRESS_CHANGE); throw QuicTransportException( "Migration disabled", TransportErrorCode::INVALID_MIGRATION); } } auto& ackState = getAckState(conn, packetNumberSpace); bool outOfOrder = updateLargestReceivedPacketNum( ackState, packetNum, readData.networkData.receiveTimePoint); if (outOfOrder) { QUIC_STATS(conn.statsCallback, onOutOfOrderPacketReceived); } DCHECK(hasReceivedPackets(conn)); bool pktHasRetransmittableData = false; bool pktHasCryptoData = false; bool isNonProbingPacket = false; bool handshakeConfirmedThisLoop = false; for (auto& quicFrame : regularPacket.frames) { switch (quicFrame.type()) { case QuicFrame::Type::ReadAckFrame: { VLOG(10) << "Server received ack frame packet=" << packetNum << " " << conn; isNonProbingPacket = true; ReadAckFrame& ackFrame = *quicFrame.asReadAckFrame(); conn.lastProcessedAckEvents.emplace_back(processAckFrame( conn, packetNumberSpace, ackFrame, [&](const OutstandingPacket& packet, const QuicWriteFrame& packetFrame, const ReadAckFrame&) { switch (packetFrame.type()) { case QuicWriteFrame::Type::WriteStreamFrame: { const WriteStreamFrame& frame = *packetFrame.asWriteStreamFrame(); VLOG(4) << "Server received ack for stream=" << frame.streamId << " offset=" << frame.offset << " fin=" << frame.fin << " len=" << frame.len << " " << conn; auto ackedStream = conn.streamManager->getStream(frame.streamId); if (ackedStream) { sendAckSMHandler(*ackedStream, frame); } break; } case QuicWriteFrame::Type::WriteCryptoFrame: { const WriteCryptoFrame& frame = *packetFrame.asWriteCryptoFrame(); auto cryptoStream = getCryptoStream(*conn.cryptoState, encryptionLevel); processCryptoStreamAck( *cryptoStream, frame.offset, frame.len); break; } case QuicWriteFrame::Type::RstStreamFrame: { const RstStreamFrame& frame = *packetFrame.asRstStreamFrame(); VLOG(4) << "Server received ack for reset stream=" << frame.streamId << " " << conn; auto stream = conn.streamManager->getStream(frame.streamId); if (stream) { sendRstAckSMHandler(*stream); } break; } case QuicWriteFrame::Type::WriteAckFrame: { const WriteAckFrame& frame = *packetFrame.asWriteAckFrame(); DCHECK(!frame.ackBlocks.empty()); VLOG(4) << "Server received ack for largestAcked=" << frame.ackBlocks.front().end << " " << conn; commonAckVisitorForAckFrame(ackState, frame); break; } case QuicWriteFrame::Type::PingFrame: if (!packet.metadata.isD6DProbe) { conn.pendingEvents.cancelPingTimeout = true; } return; case QuicWriteFrame::Type::QuicSimpleFrame: { const QuicSimpleFrame& frame = *packetFrame.asQuicSimpleFrame(); // ACK of HandshakeDone is a server-specific behavior. if (frame.asHandshakeDoneFrame()) { // Call handshakeConfirmed outside of the packet // processing loop to avoid a re-entrancy. handshakeConfirmedThisLoop = true; } break; } default: { break; } } }, markPacketLoss, readData.networkData.receiveTimePoint)); break; } case QuicFrame::Type::RstStreamFrame: { RstStreamFrame& frame = *quicFrame.asRstStreamFrame(); VLOG(10) << "Server received reset stream=" << frame.streamId << " " << conn; pktHasRetransmittableData = true; isNonProbingPacket = true; auto stream = conn.streamManager->getStream(frame.streamId); if (!stream) { break; } receiveRstStreamSMHandler(*stream, frame); break; } case QuicFrame::Type::ReadCryptoFrame: { pktHasRetransmittableData = true; pktHasCryptoData = true; isNonProbingPacket = true; ReadCryptoFrame& cryptoFrame = *quicFrame.asReadCryptoFrame(); VLOG(10) << "Server received crypto data offset=" << cryptoFrame.offset << " len=" << cryptoFrame.data->computeChainDataLength() << " currentReadOffset=" << getCryptoStream(*conn.cryptoState, encryptionLevel) ->currentReadOffset << " " << conn; appendDataToReadBuffer( *getCryptoStream(*conn.cryptoState, encryptionLevel), StreamBuffer( std::move(cryptoFrame.data), cryptoFrame.offset, false)); break; } case QuicFrame::Type::ReadStreamFrame: { ReadStreamFrame& frame = *quicFrame.asReadStreamFrame(); VLOG(10) << "Server received stream data for stream=" << frame.streamId << ", offset=" << frame.offset << " len=" << frame.data->computeChainDataLength() << " fin=" << frame.fin << " " << conn; pktHasRetransmittableData = true; isNonProbingPacket = true; auto stream = conn.streamManager->getStream(frame.streamId); // Ignore data from closed streams that we don't have the // state for any more. if (stream) { receiveReadStreamFrameSMHandler(*stream, std::move(frame)); } break; } case QuicFrame::Type::MaxDataFrame: { MaxDataFrame& connWindowUpdate = *quicFrame.asMaxDataFrame(); VLOG(10) << "Server received max data offset=" << connWindowUpdate.maximumData << " " << conn; pktHasRetransmittableData = true; isNonProbingPacket = true; handleConnWindowUpdate(conn, connWindowUpdate, packetNum); break; } case QuicFrame::Type::MaxStreamDataFrame: { MaxStreamDataFrame& streamWindowUpdate = *quicFrame.asMaxStreamDataFrame(); VLOG(10) << "Server received max stream data stream=" << streamWindowUpdate.streamId << " offset=" << streamWindowUpdate.maximumData << " " << conn; if (isReceivingStream(conn.nodeType, streamWindowUpdate.streamId)) { throw QuicTransportException( "Received MaxStreamDataFrame for receiving stream.", TransportErrorCode::STREAM_STATE_ERROR); } pktHasRetransmittableData = true; isNonProbingPacket = true; auto stream = conn.streamManager->getStream(streamWindowUpdate.streamId); if (stream) { handleStreamWindowUpdate( *stream, streamWindowUpdate.maximumData, packetNum); } break; } case QuicFrame::Type::DataBlockedFrame: { VLOG(10) << "Server received blocked " << conn; pktHasRetransmittableData = true; isNonProbingPacket = true; handleConnBlocked(conn); break; } case QuicFrame::Type::StreamDataBlockedFrame: { StreamDataBlockedFrame& blocked = *quicFrame.asStreamDataBlockedFrame(); VLOG(10) << "Server received blocked stream=" << blocked.streamId << " " << conn; pktHasRetransmittableData = true; isNonProbingPacket = true; auto stream = conn.streamManager->getStream(blocked.streamId); if (stream) { handleStreamBlocked(*stream); } break; } case QuicFrame::Type::StreamsBlockedFrame: { StreamsBlockedFrame& blocked = *quicFrame.asStreamsBlockedFrame(); // peer wishes to open a stream, but is unable to due to the maximum // stream limit set by us // TODO implement the handler isNonProbingPacket = true; VLOG(10) << "Server received streams blocked limit=" << blocked.streamLimit << ", " << conn; break; } case QuicFrame::Type::ConnectionCloseFrame: { isNonProbingPacket = true; ConnectionCloseFrame& connFrame = *quicFrame.asConnectionCloseFrame(); auto errMsg = folly::to<std::string>( "Server closed by peer reason=", connFrame.reasonPhrase); VLOG(4) << errMsg << " " << conn; // we want to deliver app callbacks with the peer supplied error, // but send a NO_ERROR to the peer. if (conn.qLogger) { conn.qLogger->addTransportStateUpdate(getPeerClose(errMsg)); } conn.peerConnectionError = QuicError(QuicErrorCode(connFrame.errorCode), std::move(errMsg)); if (getSendConnFlowControlBytesWire(conn) == 0 && conn.flowControlState.sumCurStreamBufferLen) { VLOG(2) << "Client gives up a flow control blocked connection"; } throw QuicTransportException( "Peer closed", TransportErrorCode::NO_ERROR); break; } case QuicFrame::Type::PingFrame: isNonProbingPacket = true; // Ping isn't retransmittable data. But we would like to ack them // early. pktHasRetransmittableData = true; conn.pendingEvents.notifyPingReceived = true; break; case QuicFrame::Type::PaddingFrame: break; case QuicFrame::Type::QuicSimpleFrame: { pktHasRetransmittableData = true; QuicSimpleFrame& simpleFrame = *quicFrame.asQuicSimpleFrame(); isNonProbingPacket |= updateSimpleFrameOnPacketReceived( conn, simpleFrame, packetNum, readData.peer != conn.peerAddress); break; } case QuicFrame::Type::DatagramFrame: { DatagramFrame& frame = *quicFrame.asDatagramFrame(); VLOG(10) << "Server received datagram data: " << " len=" << frame.length; // Datagram isn't retransmittable. But we would like to ack them // early. So, make Datagram frames count towards ack policy pktHasRetransmittableData = true; handleDatagram(conn, frame, readData.networkData.receiveTimePoint); break; } default: { break; } } } if (handshakeConfirmedThisLoop) { handshakeConfirmed(conn); } // 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 // increase the limit. updateWritableByteLimitOnRecvPacket(conn); if (conn.peerAddress != readData.peer) { // TODO use new conn id, make sure the other endpoint has new conn id if (isNonProbingPacket) { if (packetNum == ackState.largestReceivedPacketNum) { ShortHeader* shortHeader = regularPacket.header.asShort(); bool intentionalMigration = false; if (shortHeader && shortHeader->getConnectionId() != conn.serverConnectionId) { intentionalMigration = true; } onConnectionMigration(conn, readData.peer, intentionalMigration); } } else { // Server will need to response with PathResponse to the new address // while not updating peerAddress to new address if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PEER_ADDRESS_CHANGE)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PEER_ADDRESS_CHANGE); throw QuicTransportException( "Probing not supported yet", TransportErrorCode::INVALID_MIGRATION); } } // Try reading bytes off of crypto, and performing a handshake. auto data = readDataFromCryptoStream( *getCryptoStream(*conn.cryptoState, encryptionLevel)); if (data) { conn.serverHandshakeLayer->doHandshake(std::move(data), encryptionLevel); try { updateHandshakeState(conn); } catch (...) { if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::TRANSPORT_PARAMETER_ERROR)); } QUIC_STATS( conn.statsCallback, onPacketDropped, QuicTransportStatsCallback::PacketDropReason:: TRANSPORT_PARAMETER_ERROR); throw; } } updateAckSendStateOnRecvPacket( conn, ackState, outOfOrder, pktHasRetransmittableData, pktHasCryptoData, packetNumberSpace == PacketNumberSpace::Initial); if (encryptionLevel == EncryptionLevel::Handshake && conn.initialWriteCipher) { conn.initialWriteCipher.reset(); conn.initialHeaderCipher.reset(); conn.readCodec->setInitialReadCipher(nullptr); conn.readCodec->setInitialHeaderCipher(nullptr); implicitAckCryptoStream(conn, EncryptionLevel::Initial); } QUIC_STATS(conn.statsCallback, onPacketProcessed); } VLOG_IF(4, !udpData.empty()) << "Leaving " << udpData.chainLength() << " bytes unprocessed after attempting to process " << kMaxNumCoalescedPackets << " packets."; } void onServerReadDataFromClosed( QuicServerConnectionState& conn, ServerEvents::ReadData& readData) { CHECK_EQ(conn.state, ServerState::Closed); BufQueue udpData; udpData.append(std::move(readData.networkData.data)); auto packetSize = udpData.empty() ? 0 : udpData.chainLength(); if (!conn.readCodec) { // drop data. We closed before we even got the first packet. This is // normally not possible but might as well. if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::SERVER_STATE_CLOSED)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::SERVER_STATE_CLOSED); return; } if (conn.peerConnectionError) { // We already got a peer error. We can ignore any futher peer errors. if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::SERVER_STATE_CLOSED)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::SERVER_STATE_CLOSED); return; } auto parsedPacket = conn.readCodec->parsePacket(udpData, conn.ackStates); switch (parsedPacket.type()) { case CodecResult::Type::CIPHER_UNAVAILABLE: { VLOG(10) << "drop cipher unavailable " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kCipherUnavailable); } break; } case CodecResult::Type::RETRY: { VLOG(10) << "drop because the server is not supposed to " << "receive a retry " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kRetry); } break; } case CodecResult::Type::STATELESS_RESET: { VLOG(10) << "drop because reset " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kReset); } break; } case CodecResult::Type::NOTHING: { VLOG(10) << "drop cipher unavailable, no data " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop(packetSize, kCipherUnavailable); } break; } case CodecResult::Type::REGULAR_PACKET: break; } auto regularOptional = parsedPacket.regularPacket(); if (!regularOptional) { // We were unable to parse the packet, drop for now. VLOG(10) << "Not able to parse QUIC packet " << conn; if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString(PacketDropReason::PARSE_ERROR)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PARSE_ERROR); return; } if (regularOptional->frames.empty()) { // This packet had a pareseable header (most probably short header) // but no data. This is a protocol violation so we throw an exception. // This drop has not been recorded in the switch-case block above // so we record it here. if (conn.qLogger) { conn.qLogger->addPacketDrop( packetSize, QuicTransportStatsCallback::toString( PacketDropReason::PROTOCOL_VIOLATION)); } QUIC_STATS( conn.statsCallback, onPacketDropped, PacketDropReason::PROTOCOL_VIOLATION); throw QuicTransportException( "Packet has no frames", TransportErrorCode::PROTOCOL_VIOLATION); } auto& regularPacket = *regularOptional; auto packetNum = regularPacket.header.getPacketSequenceNum(); auto pnSpace = regularPacket.header.getPacketNumberSpace(); if (conn.qLogger) { conn.qLogger->addPacket(regularPacket, packetSize); } // Only process the close frames in the packet for (auto& quicFrame : regularPacket.frames) { switch (quicFrame.type()) { case QuicFrame::Type::ConnectionCloseFrame: { ConnectionCloseFrame& connFrame = *quicFrame.asConnectionCloseFrame(); auto errMsg = folly::to<std::string>( "Server closed by peer reason=", connFrame.reasonPhrase); VLOG(4) << errMsg << " " << conn; if (conn.qLogger) { conn.qLogger->addTransportStateUpdate(getPeerClose(errMsg)); } // we want to deliver app callbacks with the peer supplied error, // but send a NO_ERROR to the peer. conn.peerConnectionError = QuicError(QuicErrorCode(connFrame.errorCode), std::move(errMsg)); break; } default: break; } } // We only need to set the largest received packet number in order to // determine whether or not we need to send a new close. auto& largestReceivedPacketNum = getAckState(conn, pnSpace).largestReceivedPacketNum; largestReceivedPacketNum = std::max<PacketNum>( largestReceivedPacketNum.value_or(packetNum), packetNum); } void onServerClose(QuicServerConnectionState& conn) { switch (conn.state) { case ServerState::Open: onServerCloseOpenState(conn); return; case ServerState::Closed: return; } } void onServerCloseOpenState(QuicServerConnectionState& conn) { conn.state = ServerState::Closed; } folly::Optional<ConnectionIdData> QuicServerConnectionState::createAndAddNewSelfConnId() { // Should be set right after server transport construction. CHECK(connIdAlgo); CHECK(serverConnIdParams); CHECK(transportSettings.statelessResetTokenSecret); StatelessResetGenerator generator( transportSettings.statelessResetTokenSecret.value(), serverAddr.getFullyQualified()); // The default connectionId algo has 36 bits of randomness. auto encodedCid = connIdAlgo->encodeConnectionId(*serverConnIdParams); size_t encodedTimes = 0; while (encodedCid && connIdRejector && connIdRejector->rejectConnectionId(*encodedCid) && ++encodedTimes < kConnIdEncodingRetryLimit) { encodedCid = connIdAlgo->encodeConnectionId(*serverConnIdParams); } LOG_IF(ERROR, encodedTimes == kConnIdEncodingRetryLimit) << "Quic CIDRejector rejected all conneectionIDs"; if (encodedCid.hasError()) { return folly::none; } auto newConnIdData = ConnectionIdData{*encodedCid, nextSelfConnectionIdSequence++}; newConnIdData.token = generator.generateToken(newConnIdData.connId); selfConnectionIds.push_back(newConnIdData); return newConnIdData; } std::vector<TransportParameter> setSupportedExtensionTransportParameters( QuicServerConnectionState& conn) { std::vector<TransportParameter> customTransportParams; if (conn.transportSettings.datagramConfig.enabled) { auto maxDatagramFrameSize = std::make_unique<CustomIntegralTransportParameter>( static_cast<uint64_t>( TransportParameterId::max_datagram_frame_size), conn.datagramState.maxReadFrameSize); customTransportParams.push_back(maxDatagramFrameSize->encode()); } return customTransportParams; } } // namespace quic