NetworkSession: allow discarding consecutive identical packets without decoding in certain cases (#6715)

This allows packet handlers to tell the network session to discard any following packets if their buffers were identical to the current one.

This is a very conservative and basic limiter. If we start having problems with multiple packets being spammed at once interleaved, this will become ineffective. However, for now, all the known spam bugs are of single packets, and the buffers of said packets are always identical. Dealing with interleaved packets would be quite a bit more complex. This mechanism is very simple and should avoid any negative side effects.
This commit is contained in:
Dylan T.
2025-10-16 23:52:23 +01:00
committed by GitHub
parent 14114f6eaf
commit 40ecbe82f9
3 changed files with 71 additions and 2 deletions

View File

@@ -0,0 +1,31 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\network;
/**
* Thrown by packet handlers to instruct network sessions to drop repeated packets.
*/
final class FilterNoisyPacketException extends \RuntimeException{
}

View File

@@ -39,6 +39,7 @@ use pocketmine\lang\Translatable;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\FilterNoisyPacketException;
use pocketmine\network\mcpe\cache\ChunkCache;
use pocketmine\network\mcpe\compression\CompressBatchPromise;
use pocketmine\network\mcpe\compression\Compressor;
@@ -144,6 +145,8 @@ class NetworkSession{
private const INCOMING_GAME_PACKETS_PER_TICK = 2;
private const INCOMING_GAME_PACKETS_BUFFER_TICKS = 100;
private const INCOMING_PACKET_BATCH_HARD_LIMIT = 300;
private PacketRateLimiter $packetBatchLimiter;
private PacketRateLimiter $gamePacketLimiter;
@@ -194,6 +197,9 @@ class NetworkSession{
*/
private ObjectSet $disposeHooks;
private string $noisyPacketBuffer = "";
private int $noisyPacketsDropped = 0;
public function __construct(
private Server $server,
private NetworkSessionManager $manager,
@@ -350,6 +356,20 @@ class NetworkSession{
}
}
private function checkRepeatedPacketFilter(string $buffer) : bool{
if($buffer === $this->noisyPacketBuffer){
$this->noisyPacketsDropped++;
return true;
}
//stop filtering once we see a packet with a different buffer
//this won't be any good for interleaved spammy packets, but we haven't seen any of those so far, and this
//is the simplest and most conservative filter we can do
$this->noisyPacketBuffer = "";
$this->noisyPacketsDropped = 0;
return false;
}
/**
* @throws PacketHandlingException
*/
@@ -400,9 +420,21 @@ class NetworkSession{
$decompressed = $payload;
}
$count = 0;
try{
$stream = new ByteBufferReader($decompressed);
foreach(PacketBatch::decodeRaw($stream) as $buffer){
if(++$count >= self::INCOMING_PACKET_BATCH_HARD_LIMIT){
//this should be well more than enough; under normal conditions the game packet rate limiter
//will kick in well before this. This is only here to make sure we can't get huge batches of
//noisy packets to bog down the server, since those aren't counted by the regular limiter.
throw new PacketHandlingException("Reached hard limit of " . self::INCOMING_PACKET_BATCH_HARD_LIMIT . " per batch packet");
}
if($this->checkRepeatedPacketFilter($buffer)){
continue;
}
$this->gamePacketLimiter->decrement();
$packet = $this->packetPool->getPacket($buffer);
if($packet === null){
@@ -414,6 +446,8 @@ class NetworkSession{
}catch(PacketHandlingException $e){
$this->logger->debug($packet->getName() . ": " . base64_encode($buffer));
throw PacketHandlingException::wrap($e, "Error processing " . $packet->getName());
}catch(FilterNoisyPacketException){
$this->noisyPacketBuffer = $buffer;
}
if(!$this->isConnected()){
//handling this packet may have caused a disconnection
@@ -432,6 +466,7 @@ class NetworkSession{
/**
* @throws PacketHandlingException
* @throws FilterNoisyPacketException
*/
public function handleDataPacket(Packet $packet, string $buffer) : void{
if(!($packet instanceof ServerboundPacket)){

View File

@@ -44,6 +44,7 @@ use pocketmine\math\Facing;
use pocketmine\math\Vector3;
use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\StringTag;
use pocketmine\network\FilterNoisyPacketException;
use pocketmine\network\mcpe\InventoryManager;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\network\mcpe\protocol\ActorEventPacket;
@@ -490,7 +491,7 @@ class InGamePacketHandler extends PacketHandler{
$this->lastRightClickData = $data;
$this->lastRightClickTime = microtime(true);
if($spamBug){
return true;
throw new FilterNoisyPacketException();
}
//TODO: end hack for client spam bug
@@ -748,7 +749,9 @@ class InGamePacketHandler extends PacketHandler{
}
public function handleAnimate(AnimatePacket $packet) : bool{
return true; //Not used
//this spams harder than a firehose on left click if "Improved Input Response" is enabled, and we don't even
//use it anyway :<
throw new FilterNoisyPacketException();
}
public function handleContainerClose(ContainerClosePacket $packet) : bool{