Better Packet Limiter

Reference: https://github.com/Spottedleaf/PacketLimiter
This commit is contained in:
Pantera (Mad_Daniel) 2023-10-11 19:00:57 +09:00 committed by GitHub
parent ff84c8f564
commit 145d57679e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 63 deletions

View File

@ -73,8 +73,8 @@ public final class LimboConfig {
private boolean useTrafficLimits; private boolean useTrafficLimits;
private int maxPacketSize; private int maxPacketSize;
private int maxPacketsPerSec; private double interval;
private int maxBytesPerSec; private double maxPacketRate;
public LimboConfig(Path root) { public LimboConfig(Path root) {
this.root = root; this.root = root;
@ -134,9 +134,9 @@ public final class LimboConfig {
workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4); workerGroupSize = conf.node("netty", "threads", "workerGroup").getInt(4);
useTrafficLimits = conf.node("traffic", "enable").getBoolean(false); useTrafficLimits = conf.node("traffic", "enable").getBoolean(false);
maxPacketSize = conf.node("traffic", "packetSize").getInt(-1); maxPacketSize = conf.node("traffic", "maxPacketSize").getInt(-1);
maxPacketsPerSec = conf.node("traffic", "packets").getInt(-1); interval = conf.node("traffic", "interval").getDouble(-1.0);
maxBytesPerSec = conf.node("traffic", "bytes").getInt(-1); maxPacketRate = conf.node("traffic", "maxPacketRate").getDouble(-1.0);
} }
private BufferedReader getReader() throws IOException { private BufferedReader getReader() throws IOException {
@ -269,11 +269,11 @@ public final class LimboConfig {
return maxPacketSize; return maxPacketSize;
} }
public int getMaxPacketsPerSec() { public double getInterval() {
return maxPacketsPerSec; return interval;
} }
public int getMaxBytesPerSec() { public double getMaxPacketRate() {
return maxBytesPerSec; return maxPacketRate;
} }
} }

View File

@ -50,8 +50,8 @@ public class ClientChannelInitializer extends ChannelInitializer<Channel> {
if (server.getConfig().isUseTrafficLimits()) { if (server.getConfig().isUseTrafficLimits()) {
pipeline.addLast("traffic_limit", new ChannelTrafficHandler( pipeline.addLast("traffic_limit", new ChannelTrafficHandler(
server.getConfig().getMaxPacketSize(), server.getConfig().getMaxPacketSize(),
server.getConfig().getMaxPacketsPerSec(), server.getConfig().getInterval(),
server.getConfig().getMaxBytesPerSec() server.getConfig().getMaxPacketRate()
)); ));
} }

View File

@ -6,21 +6,18 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import ua.nanit.limbo.server.Logger; import ua.nanit.limbo.server.Logger;
import java.util.Arrays;
public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter { public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
private final int packetSize; private final int maxPacketSize;
private final int packetsPerSec; private final double maxPacketRate;
private final int bytesPerSec; private final PacketBucket packetBucket;
private int packetsCounter; public ChannelTrafficHandler(int maxPacketSize, double interval, double maxPacketRate) {
private int bytesCounter; this.maxPacketSize = maxPacketSize;
this.maxPacketRate = maxPacketRate;
private long lastPacket; this.packetBucket = (interval > 0.0 && maxPacketRate > 0.0) ? new PacketBucket(interval * 1000.0, 150) : null;
public ChannelTrafficHandler(int packetSize, int packetsPerSec, int bytesPerSec) {
this.packetSize = packetSize;
this.packetsPerSec = packetsPerSec;
this.bytesPerSec = bytesPerSec;
} }
@Override @Override
@ -29,47 +26,86 @@ public class ChannelTrafficHandler extends ChannelInboundHandlerAdapter {
ByteBuf in = (ByteBuf) msg; ByteBuf in = (ByteBuf) msg;
int bytes = in.readableBytes(); int bytes = in.readableBytes();
if (packetSize > 0 && bytes > packetSize) { if (maxPacketSize > 0 && bytes > maxPacketSize) {
closeConnection(ctx, "Closed %s due too large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes); closeConnection(ctx, "Closed %s due to large packet size (%d bytes)", ctx.channel().remoteAddress(), bytes);
return; return;
} }
if (!measureTraffic(ctx, bytes)) return; if (packetBucket != null) {
packetBucket.incrementPackets(1);
if (packetBucket.getCurrentPacketRate() > maxPacketRate) {
closeConnection(ctx, "Closed %s due to many packets sent (%d in the last %.1f seconds)", ctx.channel().remoteAddress(), packetBucket.sum, (packetBucket.intervalTime / 1000.0));
return;
}
}
} }
super.channelRead(ctx, msg); super.channelRead(ctx, msg);
} }
private boolean measureTraffic(ChannelHandlerContext ctx, int bytes) {
if (packetsPerSec < 0 && bytesPerSec < 0) return true;
long time = System.currentTimeMillis();
if (time - lastPacket >= 1000) {
bytesCounter = 0;
packetsCounter = 0;
}
packetsCounter++;
bytesCounter += bytes;
if (packetsPerSec > 0 && packetsCounter > packetsPerSec) {
closeConnection(ctx, "Closed %s due too frequent packet sending (%d per sec)", ctx.channel().remoteAddress(), packetsCounter);
return false;
}
if (bytesPerSec > 0 && bytesCounter > bytesPerSec) {
closeConnection(ctx, "Closed %s due too many bytes sent per second (%d per sec)", ctx.channel().remoteAddress(), bytesCounter);
return false;
}
lastPacket = time;
return true;
}
private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) { private void closeConnection(ChannelHandlerContext ctx, String reason, Object... args) {
ctx.close(); ctx.close();
Logger.info(reason, args); Logger.info(reason, args);
} }
private static class PacketBucket {
private static final double NANOSECONDS_TO_MILLISECONDS = 1.0e-6;
private static final int MILLISECONDS_TO_SECONDS = 1000;
private final double intervalTime;
private final double intervalResolution;
private final int[] data;
private int newestData;
private double lastBucketTime;
private int sum;
public PacketBucket(final double intervalTime, final int totalBuckets) {
this.intervalTime = intervalTime;
this.intervalResolution = intervalTime / totalBuckets;
this.data = new int[totalBuckets];
}
public void incrementPackets(final int packets) {
double timeMs = System.nanoTime() * NANOSECONDS_TO_MILLISECONDS;
double timeDelta = timeMs - this.lastBucketTime;
if (timeDelta < 0.0) {
timeDelta = 0.0;
}
if (timeDelta < this.intervalResolution) {
this.data[this.newestData] += packets;
this.sum += packets;
return;
}
int bucketsToMove = (int)(timeDelta / this.intervalResolution);
double nextBucketTime = this.lastBucketTime + bucketsToMove * this.intervalResolution;
if (bucketsToMove >= this.data.length) {
Arrays.fill(this.data, 0);
this.data[0] = packets;
this.sum = packets;
this.newestData = 0;
this.lastBucketTime = timeMs;
return;
}
for (int i = 1; i < bucketsToMove; ++i) {
int index = (this.newestData + i) % this.data.length;
this.sum -= this.data[index];
this.data[index] = 0;
}
int newestDataIndex = (this.newestData + bucketsToMove) % this.data.length;
this.sum += packets - this.data[newestDataIndex];
this.data[newestDataIndex] = packets;
this.newestData = newestDataIndex;
this.lastBucketTime = nextBucketTime;
}
public double getCurrentPacketRate() {
return this.sum / (this.intervalTime / MILLISECONDS_TO_SECONDS);
}
}
} }

View File

@ -114,18 +114,20 @@ netty:
bossGroup: 1 bossGroup: 1
workerGroup: 4 workerGroup: 4
# [Experimental]
# Options to check incoming traffic and kick potentially malicious connections. # Options to check incoming traffic and kick potentially malicious connections.
# Take into account that player can send many small packets, for example just moving mouse. # Take into account that player can send many small packets, for example just moving mouse.
traffic: traffic:
# If true, then additional handler will be added to channel pipeline # If true, then additional handler will be added to channel pipeline
enable: false enable: true
# Max packet size in bytes # Max packet size in bytes
# Unlimited if -1 # Unlimited if -1
packetSize: 1024 maxPacketSize: 4096
# How many packets per second allowed for single connection # The interval to measure packets over
# Ignored if -1 # Lowering this value will limit peak packets from players which would target people with bad connections
packets: -1 # Raising this value will allow higher peak packet rates, which will help with people who have poor connections
# How many bytes per second allowed for single connection # Ignored if -1.0
# Ignored if -1 interval: 7.0
bytes: 2048 # The maximum maximum packets per second for players
# It is measured over the configured interval
# Ignored if -1.0
maxPacketRate: 500.0