From a81d52a54f7d7cb331433dad41ab13ce12236032 Mon Sep 17 00:00:00 2001 From: Myles Date: Sun, 17 Apr 2016 12:55:18 +0100 Subject: [PATCH] Implement a packet limiting system, This means that if a client sends more than a maximum, it will be disconnected. It also means if a client goes over a threshold multiple times over a period of time, it will be disconnected. Improvements are welcome, it's mostly down to how the user configures it. (Some values are higher so that we don't kill every server with a tiny bit of lag) --- .../us/myles/ViaVersion/ViaVersionPlugin.java | 59 +++++++++++++++++ .../ViaVersion/api/ViaVersionConfig.java | 42 +++++++++++++ .../ViaVersion/api/data/UserConnection.java | 63 ++++++++++++++++--- .../commands/ViaCommandHandler.java | 1 + .../commands/defaultsubs/PPSSubCmd.java | 62 ++++++++++++++++++ .../ViaVersion/handlers/ViaDecodeHandler.java | 17 ++++- .../ViaVersion/handlers/ViaEncodeHandler.java | 4 +- src/main/resources/config.yml | 21 ++++++- 8 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 src/main/java/us/myles/ViaVersion/commands/defaultsubs/PPSSubCmd.java diff --git a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java index 465b1ab5d..80073b5f0 100644 --- a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java +++ b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java @@ -397,6 +397,36 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe return getConfig().getBoolean("block-break-patch", true); } + @Override + public int getMaxPPS() { + return getConfig().getInt("max-pps", 140); + } + + @Override + public String getMaxPPSKickMessage() { + return getConfig().getString("max-pps-kick-msg", "Sending packets too fast? lag?"); + } + + @Override + public int getTrackingPeriod() { + return getConfig().getInt("tracking-period", 5); + } + + @Override + public int getWarningPPS() { + return getConfig().getInt("tracking-warning-pps", 120); + } + + @Override + public int getMaxWarnings() { + return getConfig().getInt("tracking-max-warnings", 3); + } + + @Override + public String getMaxWarningsKickMessage() { + return getConfig().getString("tracking-max-kick-msg", "You are sending too many packets, :("); + } + public boolean isAutoTeam() { // Collision has to be enabled first return isPreventCollision() && getConfig().getBoolean("auto-team", true); @@ -432,4 +462,33 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe public Map getPortedPlayers() { return portedPlayers; } + + public boolean handlePPS(UserConnection info) { + // Max PPS Checker + if (getMaxPPS() > 0) { + if (info.getPacketsPerSecond() >= getMaxPPS()) { + info.disconnect(getMaxPPSKickMessage()); + return true; // don't send current packet + } + } + + // Tracking PPS Checker + if (getMaxWarnings() > 0 && getTrackingPeriod() > 0) { + if (info.getSecondsObserved() > getTrackingPeriod()) { + // Reset + info.setWarnings(0); + info.setSecondsObserved(1); + } else { + info.setSecondsObserved(info.getSecondsObserved() + 1); + if (info.getPacketsPerSecond() >= getWarningPPS()) { + info.setWarnings(info.getWarnings() + 1); + } + } + if (info.getWarnings() >= getMaxWarnings()) { + info.disconnect(getMaxWarningsKickMessage()); + return true; // don't send current packet + } + } + return false; + } } diff --git a/src/main/java/us/myles/ViaVersion/api/ViaVersionConfig.java b/src/main/java/us/myles/ViaVersion/api/ViaVersionConfig.java index 78c8e9cb1..5f294077a 100644 --- a/src/main/java/us/myles/ViaVersion/api/ViaVersionConfig.java +++ b/src/main/java/us/myles/ViaVersion/api/ViaVersionConfig.java @@ -100,4 +100,46 @@ public interface ViaVersionConfig { * @return true if it is enabled. */ boolean isBlockBreakPatch(); + + /** + * Get the maximum number of packets a client can send per second. + * + * @return The number of packets a client can send per second. + */ + int getMaxPPS(); + + /** + * Get the kick message sent if the user hits the max packets per second. + * + * @return Kick message, with colour codes using '&' + */ + String getMaxPPSKickMessage(); + + /** + * The time in seconds that should be tracked for warnings + * + * @return Time in seconds that should be tracked for warnings + */ + int getTrackingPeriod(); + + /** + * The number of packets per second to count as a warning + * + * @return The number of packets per second to count as a warning. + */ + int getWarningPPS(); + + /** + * Get the maximum number of warnings the client can have in the interval + * + * @return The number of packets a client can send per second. + */ + int getMaxWarnings(); + + /** + * Get the kick message sent if the user goes over the warnings in the interval + * + * @return Kick message, with colour codes using '&' + */ + String getMaxWarningsKickMessage(); } diff --git a/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java b/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java index 80d13fde0..d253cf31f 100644 --- a/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java +++ b/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java @@ -1,28 +1,37 @@ package us.myles.ViaVersion.api.data; +import com.google.gson.Gson; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.socket.SocketChannel; +import lombok.Data; import lombok.Getter; import lombok.Setter; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; import java.util.Map; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +@Data public class UserConnection { - @Getter private final SocketChannel channel; Map storedObjects = new ConcurrentHashMap<>(); - @Getter - @Setter private boolean active = true; - @Getter - @Setter + private boolean pendingDisconnect = false; private Object lastPacket; - @Getter private long sentPackets = 0L; - @Getter private long receivedPackets = 0L; + // Used for tracking pps + private long startTime = 0L; + private long intervalPackets = 0L; + private long packetsPerSecond = -1L; + // Used for handling warnings (over time) + private int secondsObserved = 0; + private int warnings = 0; public UserConnection(SocketChannel socketChannel) { @@ -97,7 +106,45 @@ public class UserConnection { /** * Used for incrementing the number of packets received from the client */ - public void incrementReceived() { + public boolean incrementReceived() { + // handle stats + Long diff = System.currentTimeMillis() - startTime; + if (diff >= 1000) { + packetsPerSecond = intervalPackets; + startTime = System.currentTimeMillis(); + intervalPackets = 1; + return true; + } else { + intervalPackets++; + } + // increase total this.receivedPackets++; + return false; + } + + /** + * Disconnect a connection + * + * @param reason The reason to use, not used if player is not active. + */ + public void disconnect(final String reason) { + if(!getChannel().isOpen()) return; + if(pendingDisconnect) return; + pendingDisconnect = true; + if (get(ProtocolInfo.class).getUuid() != null) { + final UUID uuid = get(ProtocolInfo.class).getUuid(); + if (Bukkit.getPlayer(uuid) != null) { + Bukkit.getScheduler().runTask(Bukkit.getPluginManager().getPlugin("ViaVersion"), new Runnable() { + @Override + public void run() { + Player player = Bukkit.getPlayer(uuid); + if (player != null) + player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason)); + } + }); + return; + } + } + getChannel().close(); // =) } } diff --git a/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java b/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java index deab22688..b7b9a936b 100644 --- a/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java +++ b/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java @@ -130,6 +130,7 @@ public class ViaCommandHandler implements ViaVersionCommand, CommandExecutor, Ta private void registerDefaults() throws Exception { registerSubCommand(new ListSubCmd()); + registerSubCommand(new PPSSubCmd()); registerSubCommand(new DebugSubCmd()); registerSubCommand(new DisplayLeaksSubCmd()); registerSubCommand(new DontBugMeSubCmd()); diff --git a/src/main/java/us/myles/ViaVersion/commands/defaultsubs/PPSSubCmd.java b/src/main/java/us/myles/ViaVersion/commands/defaultsubs/PPSSubCmd.java new file mode 100644 index 000000000..e8accd2ab --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/commands/defaultsubs/PPSSubCmd.java @@ -0,0 +1,62 @@ +package us.myles.ViaVersion.commands.defaultsubs; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import us.myles.ViaVersion.ViaVersionPlugin; +import us.myles.ViaVersion.api.ViaVersion; +import us.myles.ViaVersion.api.command.ViaSubCommand; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; + +import java.util.*; + +public class PPSSubCmd extends ViaSubCommand { + @Override + public String name() { + return "pps"; + } + + @Override + public String description() { + return "Shows the packets per second of online players"; + } + + @Override + public String usage() { + return "pps"; + } + + @Override + public boolean execute(CommandSender sender, String[] args) { + Map> playerVersions = new HashMap<>(); + int totalPackets = 0; + int clients = 0; + long max = 0; + + for (Player p : Bukkit.getOnlinePlayers()) { + int playerVersion = ViaVersion.getInstance().getPlayerVersion(p); + if (!playerVersions.containsKey(playerVersion)) + playerVersions.put(playerVersion, new HashSet()); + UserConnection uc = ((ViaVersionPlugin) ViaVersion.getInstance()).getConnection(p); + if (uc.getPacketsPerSecond() > -1) { + playerVersions.get(playerVersion).add(p.getName() + " (" + uc.getPacketsPerSecond() + " PPS)"); + totalPackets += uc.getPacketsPerSecond(); + if (uc.getPacketsPerSecond() > max) { + max = uc.getPacketsPerSecond(); + } + clients++; + } + } + Map> sorted = new TreeMap<>(playerVersions); + sendMessage(sender, "&4Live Packets Per Second"); + if (clients > 1) { + sendMessage(sender, "&cAverage: &f" + (totalPackets / clients)); + sendMessage(sender, "&cHighest: &f" + max); + } + for (Map.Entry> entry : sorted.entrySet()) + sendMessage(sender, "&8[&6%s&8]: &b%s", ProtocolVersion.getProtocol(entry.getKey()).getName(), entry.getValue()); + sorted.clear(); + return true; + } +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java index 06a33a134..6c597c2b6 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaDecodeHandler.java @@ -3,7 +3,10 @@ package us.myles.ViaVersion.handlers; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; +import us.myles.ViaVersion.ViaVersionPlugin; import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.ViaVersion; +import us.myles.ViaVersion.api.ViaVersionAPI; import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.exception.CancelException; @@ -29,9 +32,19 @@ public class ViaDecodeHandler extends ByteToMessageDecoder { protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List list) throws Exception { // use transformers if (bytebuf.readableBytes() > 0) { + // Ignore if pending disconnect + if (info.isPendingDisconnect()) { + return; + } + // Increment received + boolean second = info.incrementReceived(); + // Check PPS + if(second) { + if (((ViaVersionPlugin) ViaVersion.getConfig()).handlePPS(info)) + return; + } + if (info.isActive()) { - // Increment received - info.incrementReceived(); // Handle ID int id = Type.VAR_INT.read(bytebuf); // Transform diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java index 3e77afaf2..c86091f80 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java @@ -39,9 +39,9 @@ public class ViaEncodeHandler extends MessageToByteEncoder { if (bytebuf.readableBytes() == 0) { throw new CancelException(); } + // Increment sent + info.incrementSent(); if (info.isActive()) { - // Increment sent - info.incrementSent(); // Handle ID int id = Type.VAR_INT.read(bytebuf); // Transform diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e17bdb175..8264a6d1c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -30,4 +30,23 @@ suppress-entityid-errors: false # Our patch for block breaking issue, if you have issues with block updates then disable this. block-break-patch: true # Should we cache our items, this will prevent server from being lagged out, however the cost is a constant task caching items -item-cache: true \ No newline at end of file +item-cache: true +# Anti-Cheat, Packets Per Second (PPS) limiter +# Clients by default send around 20-90 packets per second. + +# What is the maximum per second a client can send +# Use -1 to disable. +max-pps: 400 +max-pps-kick-msg: "You are sending too many packets!" + +# We can also kick them if over a period they send over a threshold a certain amount of times. + +# Period to track (in seconds) +# Use -1 to disable. +tracking-period: 6 +# How many packets per second counts as a warning +tracking-warning-pps: 120 +# How many warnings over the interval can we have +# This can never be higher than "tracking-interval" +tracking-max-warnings: 4 +tracking-max-kick-msg: "You are sending too many packets, :(" \ No newline at end of file