Add rate limiter for incoming packets

Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
TheMode 2022-04-15 12:25:58 +02:00
parent 164cf9fe2b
commit 198618ba98
4 changed files with 10 additions and 98 deletions

View File

@ -60,10 +60,6 @@ public final class MinecraftServer {
public static final int TICK_PER_SECOND = Integer.getInteger("minestom.tps", 20);
public static final int TICK_MS = 1000 / TICK_PER_SECOND;
// Network monitoring
private static int rateLimit = 300;
private static int maxPacketSize = 30_000;
// In-Game Manager
private static volatile ServerProcess serverProcess;
@ -112,42 +108,6 @@ public final class MinecraftServer {
PacketUtils.broadcastPacket(PluginMessagePacket.getBrandPacket());
}
/**
* Gets the maximum number of packets a client can send over 1 second.
*
* @return the packet count limit over 1 second, 0 if not enabled
*/
public static int getRateLimit() {
return rateLimit;
}
/**
* Changes the number of packet a client can send over 1 second without being disconnected.
*
* @param rateLimit the number of packet, 0 to disable
*/
public static void setRateLimit(int rateLimit) {
MinecraftServer.rateLimit = rateLimit;
}
/**
* Gets the maximum packet size (in bytes) that a client can send without getting disconnected.
*
* @return the maximum packet size
*/
public static int getMaxPacketSize() {
return maxPacketSize;
}
/**
* Changes the maximum packet size (in bytes) that a client can send without getting disconnected.
*
* @param maxPacketSize the new max packet size
*/
public static void setMaxPacketSize(int maxPacketSize) {
MinecraftServer.maxPacketSize = maxPacketSize;
}
/**
* Gets the server difficulty showed in game option.
*

View File

@ -44,6 +44,7 @@ import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.metadata.WrittenBookMeta;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.message.ChatMessageType;
import net.minestom.server.message.ChatPosition;
import net.minestom.server.message.Messenger;
@ -106,6 +107,8 @@ import java.util.function.UnaryOperator;
public class Player extends LivingEntity implements CommandSender, Localizable, HoverEventSource<ShowEntity>, Identified, NamedAndIdentified {
private static final Component REMOVE_MESSAGE = Component.text("You have been removed from the server without reason.", NamedTextColor.RED);
private static final int PACKET_PER_TICK = Integer.getInteger("minestom.packet-per-tick", 20);
private static final int PACKET_QUEUE_SIZE = Integer.getInteger("minestom.packet-queue-size", 1000);
private long lastKeepAlive;
private boolean answerKeepAlive;
@ -319,9 +322,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@Override
public void update(long time) {
// Network tick
this.playerConnection.update();
// Process received packets
interpretPacketQueue();
@ -1731,8 +1731,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
@ApiStatus.Internal
@ApiStatus.Experimental
public void interpretPacketQueue() {
if (this.packets.size() >= PACKET_QUEUE_SIZE) {
kick(Component.text("Too Many Packets", NamedTextColor.RED));
return;
}
final PacketListenerManager manager = MinecraftServer.getPacketListenerManager();
// This method is NOT thread-safe
this.packets.drain(packet -> MinecraftServer.getPacketListenerManager().processClientPacket(packet, this));
this.packets.drain(packet -> manager.processClientPacket(packet, this), PACKET_PER_TICK);
}
/**

View File

@ -1,6 +1,5 @@
package net.minestom.server.network;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.client.ClientPacketsHandler;
@ -41,11 +40,7 @@ public record PacketProcessor(@NotNull ClientPacketsHandler statusHandler,
}
public void process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {
if (MinecraftServer.getRateLimit() > 0) {
// Increment packet count (checked in PlayerConnection#update)
connection.getPacketCounter().incrementAndGet();
}
var packet = create(connection.getConnectionState(), packetId, body);
final ClientPacket packet = create(connection.getConnectionState(), packetId, body);
if (packet instanceof ClientPreplayPacket prePlayPacket) {
prePlayPacket.process(connection);
} else {

View File

@ -1,11 +1,8 @@
package net.minestom.server.network.player;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
import net.minestom.server.listener.manager.PacketListenerManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.packet.server.SendablePacket;
import org.jetbrains.annotations.ApiStatus;
@ -15,57 +12,21 @@ import org.jetbrains.annotations.Nullable;
import java.net.SocketAddress;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A PlayerConnection is an object needed for all created {@link Player}.
* It can be extended to create a new kind of player (NPC for instance).
*/
public abstract class PlayerConnection {
protected static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
private Player player;
private volatile ConnectionState connectionState;
volatile boolean online;
// Text used to kick client sending too many packets
private static final Component rateLimitKickMessage = Component.text("Too Many Packets", NamedTextColor.RED);
//Connection Stats
private final AtomicInteger packetCounter = new AtomicInteger(0);
private final AtomicInteger lastPacketCounter = new AtomicInteger(0);
private short tickCounter = 0;
public PlayerConnection() {
this.online = true;
this.connectionState = ConnectionState.UNKNOWN;
}
/**
* Updates values related to the network connection.
*/
public void update() {
// Check rate limit
if (MinecraftServer.getRateLimit() > 0) {
tickCounter++;
if (tickCounter % MinecraftServer.TICK_PER_SECOND == 0 && tickCounter > 0) {
tickCounter = 0;
// Retrieve the packet count
final int count = packetCounter.getAndSet(0);
this.lastPacketCounter.set(count);
if (count > MinecraftServer.getRateLimit()) {
// Sent too many packets
player.kick(rateLimitKickMessage);
disconnect();
}
}
}
}
public @NotNull AtomicInteger getPacketCounter() {
return packetCounter;
}
/**
* Returns a printable identifier for this connection, will be the player username
* or the connection remote address.
@ -189,15 +150,6 @@ public abstract class PlayerConnection {
return connectionState;
}
/**
* Gets the number of packet the client sent over the last second.
*
* @return the number of packet sent over the last second
*/
public int getLastPacketCounter() {
return lastPacketCounter.get();
}
@Override
public String toString() {
return "PlayerConnection{" +