mirror of
https://github.com/Minestom/Minestom.git
synced 2025-01-01 14:07:43 +01:00
Add rate limiter for incoming packets
Signed-off-by: TheMode <themode@outlook.fr>
This commit is contained in:
parent
164cf9fe2b
commit
198618ba98
@ -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_PER_SECOND = Integer.getInteger("minestom.tps", 20);
|
||||||
public static final int TICK_MS = 1000 / TICK_PER_SECOND;
|
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
|
// In-Game Manager
|
||||||
private static volatile ServerProcess serverProcess;
|
private static volatile ServerProcess serverProcess;
|
||||||
|
|
||||||
@ -112,42 +108,6 @@ public final class MinecraftServer {
|
|||||||
PacketUtils.broadcastPacket(PluginMessagePacket.getBrandPacket());
|
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.
|
* Gets the server difficulty showed in game option.
|
||||||
*
|
*
|
||||||
|
@ -44,6 +44,7 @@ import net.minestom.server.inventory.PlayerInventory;
|
|||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.item.Material;
|
import net.minestom.server.item.Material;
|
||||||
import net.minestom.server.item.metadata.WrittenBookMeta;
|
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.ChatMessageType;
|
||||||
import net.minestom.server.message.ChatPosition;
|
import net.minestom.server.message.ChatPosition;
|
||||||
import net.minestom.server.message.Messenger;
|
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 {
|
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 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 long lastKeepAlive;
|
||||||
private boolean answerKeepAlive;
|
private boolean answerKeepAlive;
|
||||||
@ -319,9 +322,6 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(long time) {
|
public void update(long time) {
|
||||||
// Network tick
|
|
||||||
this.playerConnection.update();
|
|
||||||
|
|
||||||
// Process received packets
|
// Process received packets
|
||||||
interpretPacketQueue();
|
interpretPacketQueue();
|
||||||
|
|
||||||
@ -1731,8 +1731,13 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
|
|||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
@ApiStatus.Experimental
|
@ApiStatus.Experimental
|
||||||
public void interpretPacketQueue() {
|
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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package net.minestom.server.network;
|
package net.minestom.server.network;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.network.packet.client.ClientPacket;
|
import net.minestom.server.network.packet.client.ClientPacket;
|
||||||
import net.minestom.server.network.packet.client.ClientPacketsHandler;
|
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) {
|
public void process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {
|
||||||
if (MinecraftServer.getRateLimit() > 0) {
|
final ClientPacket packet = create(connection.getConnectionState(), packetId, body);
|
||||||
// Increment packet count (checked in PlayerConnection#update)
|
|
||||||
connection.getPacketCounter().incrementAndGet();
|
|
||||||
}
|
|
||||||
var packet = create(connection.getConnectionState(), packetId, body);
|
|
||||||
if (packet instanceof ClientPreplayPacket prePlayPacket) {
|
if (packet instanceof ClientPreplayPacket prePlayPacket) {
|
||||||
prePlayPacket.process(connection);
|
prePlayPacket.process(connection);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package net.minestom.server.network.player;
|
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.MinecraftServer;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
|
||||||
import net.minestom.server.network.ConnectionState;
|
import net.minestom.server.network.ConnectionState;
|
||||||
import net.minestom.server.network.packet.server.SendablePacket;
|
import net.minestom.server.network.packet.server.SendablePacket;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@ -15,57 +12,21 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A PlayerConnection is an object needed for all created {@link Player}.
|
* 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).
|
* It can be extended to create a new kind of player (NPC for instance).
|
||||||
*/
|
*/
|
||||||
public abstract class PlayerConnection {
|
public abstract class PlayerConnection {
|
||||||
protected static final PacketListenerManager PACKET_LISTENER_MANAGER = MinecraftServer.getPacketListenerManager();
|
|
||||||
|
|
||||||
private Player player;
|
private Player player;
|
||||||
private volatile ConnectionState connectionState;
|
private volatile ConnectionState connectionState;
|
||||||
volatile boolean online;
|
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() {
|
public PlayerConnection() {
|
||||||
this.online = true;
|
this.online = true;
|
||||||
this.connectionState = ConnectionState.UNKNOWN;
|
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
|
* Returns a printable identifier for this connection, will be the player username
|
||||||
* or the connection remote address.
|
* or the connection remote address.
|
||||||
@ -189,15 +150,6 @@ public abstract class PlayerConnection {
|
|||||||
return connectionState;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PlayerConnection{" +
|
return "PlayerConnection{" +
|
||||||
|
Loading…
Reference in New Issue
Block a user