mirror of
https://github.com/PaperMC/Velocity.git
synced 2026-02-17 14:37:43 +01:00
Add basic packet limiter
This commit is contained in:
@@ -93,6 +93,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
private @Nullable Favicon favicon;
|
private @Nullable Favicon favicon;
|
||||||
@Expose
|
@Expose
|
||||||
private boolean forceKeyAuthentication = true; // Added in 1.19
|
private boolean forceKeyAuthentication = true; // Added in 1.19
|
||||||
|
@Expose
|
||||||
|
private PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.DEFAULT;
|
||||||
|
|
||||||
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
|
||||||
Query query, Metrics metrics) {
|
Query query, Metrics metrics) {
|
||||||
@@ -109,7 +111,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
|
boolean onlineModeKickExistingPlayers, PingPassthroughMode pingPassthrough,
|
||||||
boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
|
boolean samplePlayersInPing, boolean enablePlayerAddressLogging, Servers servers,
|
||||||
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
|
ForcedHosts forcedHosts, Advanced advanced, Query query, Metrics metrics,
|
||||||
boolean forceKeyAuthentication) {
|
boolean forceKeyAuthentication, PacketLimiterConfig packetLimiterConfig) {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
this.motd = motd;
|
this.motd = motd;
|
||||||
this.showMaxPlayers = showMaxPlayers;
|
this.showMaxPlayers = showMaxPlayers;
|
||||||
@@ -128,6 +130,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
this.query = query;
|
this.query = query;
|
||||||
this.metrics = metrics;
|
this.metrics = metrics;
|
||||||
this.forceKeyAuthentication = forceKeyAuthentication;
|
this.forceKeyAuthentication = forceKeyAuthentication;
|
||||||
|
this.packetLimiterConfig = packetLimiterConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -449,6 +452,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
return advanced.isEnableReusePort();
|
return advanced.isEnableReusePort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PacketLimiterConfig getPacketLimiterConfig() {
|
||||||
|
return packetLimiterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
@@ -466,6 +473,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
.add("favicon", favicon)
|
.add("favicon", favicon)
|
||||||
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
|
.add("enablePlayerAddressLogging", enablePlayerAddressLogging)
|
||||||
.add("forceKeyAuthentication", forceKeyAuthentication)
|
.add("forceKeyAuthentication", forceKeyAuthentication)
|
||||||
|
.add("packetLimiterConfig", packetLimiterConfig)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +565,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
final boolean kickExisting = config.getOrElse("kick-existing-players", false);
|
final boolean kickExisting = config.getOrElse("kick-existing-players", false);
|
||||||
final boolean enablePlayerAddressLogging = config.getOrElse(
|
final boolean enablePlayerAddressLogging = config.getOrElse(
|
||||||
"enable-player-address-logging", true);
|
"enable-player-address-logging", true);
|
||||||
|
final PacketLimiterConfig packetLimiterConfig = PacketLimiterConfig.fromConfig(config.get("packet-limiter"));
|
||||||
|
|
||||||
// Throw an exception if the forwarding-secret file is empty and the proxy is using a
|
// Throw an exception if the forwarding-secret file is empty and the proxy is using a
|
||||||
// forwarding mode that requires it.
|
// forwarding mode that requires it.
|
||||||
@@ -584,7 +593,8 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
new Advanced(advancedConfig),
|
new Advanced(advancedConfig),
|
||||||
new Query(queryConfig),
|
new Query(queryConfig),
|
||||||
new Metrics(metricsConfig),
|
new Metrics(metricsConfig),
|
||||||
forceKeyAuthentication
|
forceKeyAuthentication,
|
||||||
|
packetLimiterConfig
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -987,4 +997,27 @@ public class VelocityConfiguration implements ProxyConfig {
|
|||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for packet limiting.
|
||||||
|
*
|
||||||
|
* @param interval the interval in seconds to measure packets over
|
||||||
|
* @param pps the maximum number of packets per second allowed
|
||||||
|
* @param bytes the maximum number of bytes per second allowed
|
||||||
|
*/
|
||||||
|
public record PacketLimiterConfig(int interval, int pps, int bytes) {
|
||||||
|
public static PacketLimiterConfig DEFAULT = new PacketLimiterConfig(7, 500, -1);
|
||||||
|
|
||||||
|
public static PacketLimiterConfig fromConfig(CommentedConfig config) {
|
||||||
|
if (config != null) {
|
||||||
|
return new PacketLimiterConfig(
|
||||||
|
config.getIntOrElse("interval", DEFAULT.interval()),
|
||||||
|
config.getIntOrElse("pps", DEFAULT.pps()),
|
||||||
|
config.getIntOrElse("bytes", DEFAULT.bytes())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
|||||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
|
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||||
|
import com.velocitypowered.proxy.network.limiter.SimpleBytesPerSecondLimiter;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
|
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
|
||||||
@@ -72,6 +74,17 @@ public class ServerChannelInitializer extends ChannelInitializer<Channel> {
|
|||||||
new HandshakeSessionHandler(connection, this.server));
|
new HandshakeSessionHandler(connection, this.server));
|
||||||
ch.pipeline().addLast(Connections.HANDLER, connection);
|
ch.pipeline().addLast(Connections.HANDLER, connection);
|
||||||
|
|
||||||
|
VelocityConfiguration.PacketLimiterConfig packetLimiterConfig =
|
||||||
|
server.getConfiguration().getPacketLimiterConfig();
|
||||||
|
int configuredInterval = packetLimiterConfig.interval();
|
||||||
|
int configuredPPS = packetLimiterConfig.pps();
|
||||||
|
int configuredBytes = packetLimiterConfig.bytes();
|
||||||
|
|
||||||
|
if (configuredInterval > 0 && (configuredBytes > 0 || configuredPPS > 0)) {
|
||||||
|
ch.pipeline().get(MinecraftVarintFrameDecoder.class).setPacketLimiter(
|
||||||
|
new SimpleBytesPerSecondLimiter(configuredPPS, configuredBytes, configuredInterval)
|
||||||
|
);
|
||||||
|
}
|
||||||
if (this.server.getConfiguration().isProxyProtocol()) {
|
if (this.server.getConfiguration().isProxyProtocol()) {
|
||||||
ch.pipeline().addFirst(new HAProxyMessageDecoder());
|
ch.pipeline().addFirst(new HAProxyMessageDecoder());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.network.limiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PacketLimiter enforces a limit on the number of bytes processed over a time window.
|
||||||
|
* Implementations should be thread-safe.
|
||||||
|
*/
|
||||||
|
public interface PacketLimiter {
|
||||||
|
/**
|
||||||
|
* Attempts to record the specified number of bytes within the current window.
|
||||||
|
*
|
||||||
|
* @param bytes the number of bytes to record
|
||||||
|
* @return true if the bytes are allowed and recorded; false if the limit would be exceeded
|
||||||
|
*/
|
||||||
|
boolean account(int bytes);
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.network.limiter;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.util.IntervalledCounter;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A moving-window limiter over a configurable number of seconds.
|
||||||
|
* It enforces both packets-per-second and average bytes-per-second limits.
|
||||||
|
* The effective cap over the full window equals limitPerSecond * windowSeconds.
|
||||||
|
*/
|
||||||
|
public final class SimpleBytesPerSecondLimiter implements PacketLimiter {
|
||||||
|
@Nullable
|
||||||
|
private final IntervalledCounter bytesCounter;
|
||||||
|
@Nullable
|
||||||
|
private final IntervalledCounter packetsCounter;
|
||||||
|
private final int packetsPerSecond;
|
||||||
|
private final int bytesPerSecond;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SimpleBytesPerSecondLimiter.
|
||||||
|
*
|
||||||
|
* @param packetsPerSecond maximum average packets per second allowed (> 0)
|
||||||
|
* @param bytesPerSecond maximum average bytes per second allowed (> 0)
|
||||||
|
* @param windowSeconds number of seconds in the moving window (> 0)
|
||||||
|
*/
|
||||||
|
public SimpleBytesPerSecondLimiter(int packetsPerSecond, int bytesPerSecond, int windowSeconds) {
|
||||||
|
this.packetsPerSecond = packetsPerSecond;
|
||||||
|
if (windowSeconds <= 0) {
|
||||||
|
throw new IllegalArgumentException("windowSeconds must be > 0");
|
||||||
|
}
|
||||||
|
this.bytesPerSecond = bytesPerSecond;
|
||||||
|
this.packetsCounter = packetsPerSecond > 0 ? new IntervalledCounter(windowSeconds) : null;
|
||||||
|
this.bytesCounter = bytesPerSecond > 0 ? new IntervalledCounter(windowSeconds) : null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the given payload length as one packet and returns whether it is allowed.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean account(int bytes) {
|
||||||
|
long currTime = System.nanoTime();
|
||||||
|
if (packetsCounter != null) {
|
||||||
|
packetsCounter.updateAndAdd(1, currTime);
|
||||||
|
if (packetsCounter.getRate() > packetsPerSecond) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesCounter != null) {
|
||||||
|
bytesCounter.updateAndAdd(bytes, currTime);
|
||||||
|
if (bytesCounter.getRate() > bytesPerSecond) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.netty;
|
|||||||
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
|
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
import com.velocitypowered.api.network.ProtocolVersion;
|
||||||
|
import com.velocitypowered.proxy.network.limiter.PacketLimiter;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
@@ -32,6 +33,7 @@ import io.netty.handler.codec.CorruptedFrameException;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding.
|
* Frames Minecraft server packets which are prefixed by a 21-bit VarInt encoding.
|
||||||
@@ -52,6 +54,8 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
private final ProtocolUtils.Direction direction;
|
private final ProtocolUtils.Direction direction;
|
||||||
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
|
||||||
private StateRegistry state;
|
private StateRegistry state;
|
||||||
|
@Nullable
|
||||||
|
private PacketLimiter packetLimiter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}.
|
* Creates a new {@code MinecraftVarintFrameDecoder} decoding packets from the specified {@code Direction}.
|
||||||
@@ -131,6 +135,15 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
// note that zero-length packets are ignored
|
// note that zero-length packets are ignored
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
|
// If enabled, rate-limit serverbound payload bytes based on frame length
|
||||||
|
if (packetLimiter != null) {
|
||||||
|
if (!packetLimiter.account(length)) {
|
||||||
|
throw new QuietDecoderException(
|
||||||
|
"Rate limit exceeded while processing packets for %s".formatted(
|
||||||
|
ctx.channel().remoteAddress()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (in.readableBytes() < length) {
|
if (in.readableBytes() < length) {
|
||||||
in.resetReaderIndex();
|
in.resetReaderIndex();
|
||||||
} else {
|
} else {
|
||||||
@@ -240,4 +253,8 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
|||||||
public void setState(StateRegistry stateRegistry) {
|
public void setState(StateRegistry stateRegistry) {
|
||||||
this.state = stateRegistry;
|
this.state = stateRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPacketLimiter(@Nullable PacketLimiter packetLimiter) {
|
||||||
|
this.packetLimiter = packetLimiter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2025 Velocity Contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.velocitypowered.proxy.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IntervalledCounter maintains a rolling sum of values associated with timestamps, keeping
|
||||||
|
* only those entries that fall within a fixed time interval from the most recent timestamp.
|
||||||
|
*
|
||||||
|
* <p>Time values must be provided in the same unit as {@link System#nanoTime()} (nanoseconds),
|
||||||
|
* and the configured interval is also expressed in nanoseconds. Callers are expected to
|
||||||
|
* periodically advance the counter to the current time using {@link #updateCurrentTime()} or
|
||||||
|
* {@link #updateCurrentTime(long)} to evict expired entries before adding new ones via
|
||||||
|
* {@link #addTime(long)} or {@link #addTime(long, long)}.</p>
|
||||||
|
*
|
||||||
|
* <p>This class is not thread-safe. If multiple threads access an instance concurrently,
|
||||||
|
* external synchronization is required.</p>
|
||||||
|
*/
|
||||||
|
public final class IntervalledCounter {
|
||||||
|
|
||||||
|
private static final int INITIAL_SIZE = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ring buffer holding the timestamp (in nanoseconds) for each data point.
|
||||||
|
*/
|
||||||
|
protected long[] times;
|
||||||
|
/**
|
||||||
|
* Ring buffer holding the count associated with each timestamp.
|
||||||
|
*/
|
||||||
|
protected long[] counts;
|
||||||
|
/**
|
||||||
|
* The sliding window size in nanoseconds. Only entries with time >= (currentTime - interval)
|
||||||
|
* are considered part of the window.
|
||||||
|
*/
|
||||||
|
protected final long interval;
|
||||||
|
/**
|
||||||
|
* Cached lower bound of the window (in nanoseconds) after the last update.
|
||||||
|
*/
|
||||||
|
protected long minTime;
|
||||||
|
/**
|
||||||
|
* Running sum of all counts currently within the window.
|
||||||
|
*/
|
||||||
|
protected long sum;
|
||||||
|
/**
|
||||||
|
* Head index (inclusive) of the ring buffer.
|
||||||
|
*/
|
||||||
|
protected int head; // inclusive
|
||||||
|
/**
|
||||||
|
* Tail index (exclusive) of the ring buffer.
|
||||||
|
*/
|
||||||
|
protected int tail; // exclusive
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new counter with the specified interval.
|
||||||
|
*
|
||||||
|
* @param interval the window size in nanoseconds (compatible with {@link System#nanoTime()})
|
||||||
|
*/
|
||||||
|
public IntervalledCounter(final long interval) {
|
||||||
|
this.times = new long[INITIAL_SIZE];
|
||||||
|
this.counts = new long[INITIAL_SIZE];
|
||||||
|
this.interval = interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the window to the current time using {@link System#nanoTime()}, evicting any
|
||||||
|
* data points that have fallen outside of the interval and updating the running sum.
|
||||||
|
*/
|
||||||
|
public void updateCurrentTime() {
|
||||||
|
this.updateCurrentTime(System.nanoTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advances the window to the provided time, evicting any data points older than
|
||||||
|
* {@code currentTime - interval} and updating the running sum.
|
||||||
|
*
|
||||||
|
* @param currentTime the current time in nanoseconds (as from {@link System#nanoTime()})
|
||||||
|
*/
|
||||||
|
public void updateCurrentTime(final long currentTime) {
|
||||||
|
long sum = this.sum;
|
||||||
|
int head = this.head;
|
||||||
|
final int tail = this.tail;
|
||||||
|
final long minTime = currentTime - this.interval;
|
||||||
|
|
||||||
|
final int arrayLen = this.times.length;
|
||||||
|
|
||||||
|
// guard against overflow by using subtraction
|
||||||
|
while (head != tail && this.times[head] - minTime < 0) {
|
||||||
|
sum -= this.counts[head];
|
||||||
|
// there are two ways we can do this:
|
||||||
|
// 1. free the count when adding
|
||||||
|
// 2. free it now
|
||||||
|
// option #2
|
||||||
|
this.counts[head] = 0;
|
||||||
|
if (++head >= arrayLen) {
|
||||||
|
head = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sum = sum;
|
||||||
|
this.head = head;
|
||||||
|
this.minTime = minTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a single unit at the specified timestamp, assuming the timestamp is within the current
|
||||||
|
* window. If the timestamp is older than the current window lower bound, the value is ignored.
|
||||||
|
* This method does not automatically advance the window; callers should invoke
|
||||||
|
* {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand.
|
||||||
|
*
|
||||||
|
* @param currTime the timestamp in nanoseconds
|
||||||
|
*/
|
||||||
|
public void addTime(final long currTime) {
|
||||||
|
this.addTime(currTime, 1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds {@code count} units at the specified timestamp, assuming the timestamp is within the
|
||||||
|
* current window. If the timestamp is older than {@code minTime}, the value is ignored.
|
||||||
|
* This method does not automatically advance the window; callers should invoke
|
||||||
|
* {@link #updateCurrentTime()} or {@link #updateCurrentTime(long)} beforehand.
|
||||||
|
*
|
||||||
|
* @param currTime the timestamp in nanoseconds
|
||||||
|
* @param count the amount to add (non-negative)
|
||||||
|
*/
|
||||||
|
public void addTime(final long currTime, final long count) {
|
||||||
|
// guard against overflow by using subtraction
|
||||||
|
if (currTime - this.minTime < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int nextTail = (this.tail + 1) % this.times.length;
|
||||||
|
if (nextTail == this.head) {
|
||||||
|
this.resize();
|
||||||
|
nextTail = (this.tail + 1) % this.times.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.times[this.tail] = currTime;
|
||||||
|
this.counts[this.tail] += count;
|
||||||
|
this.sum += count;
|
||||||
|
this.tail = nextTail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method that advances the window to the current time and then adds {@code count}
|
||||||
|
* units at that time.
|
||||||
|
*
|
||||||
|
* @param count the amount to add (non-negative)
|
||||||
|
*/
|
||||||
|
public void updateAndAdd(final long count) {
|
||||||
|
final long currTime = System.nanoTime();
|
||||||
|
this.updateCurrentTime(currTime);
|
||||||
|
this.addTime(currTime, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method that advances the window to {@code currTime} and then adds {@code count}
|
||||||
|
* units at that time.
|
||||||
|
*
|
||||||
|
* @param count the amount to add (non-negative)
|
||||||
|
* @param currTime the timestamp in nanoseconds
|
||||||
|
*/
|
||||||
|
public void updateAndAdd(final long count, final long currTime) {
|
||||||
|
this.updateCurrentTime(currTime);
|
||||||
|
this.addTime(currTime, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Doubles the capacity of the internal ring buffers, preserving the order of existing data.
|
||||||
|
*/
|
||||||
|
private void resize() {
|
||||||
|
final long[] oldElements = this.times;
|
||||||
|
final long[] oldCounts = this.counts;
|
||||||
|
final long[] newElements = new long[this.times.length * 2];
|
||||||
|
final long[] newCounts = new long[this.times.length * 2];
|
||||||
|
this.times = newElements;
|
||||||
|
this.counts = newCounts;
|
||||||
|
|
||||||
|
final int head = this.head;
|
||||||
|
final int tail = this.tail;
|
||||||
|
final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head));
|
||||||
|
this.head = 0;
|
||||||
|
this.tail = size;
|
||||||
|
|
||||||
|
if (tail >= head) {
|
||||||
|
// sequentially ordered from [head, tail)
|
||||||
|
System.arraycopy(oldElements, head, newElements, 0, size);
|
||||||
|
System.arraycopy(oldCounts, head, newCounts, 0, size);
|
||||||
|
} else {
|
||||||
|
// ordered from [head, length)
|
||||||
|
// then followed by [0, tail)
|
||||||
|
|
||||||
|
System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head);
|
||||||
|
System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail);
|
||||||
|
|
||||||
|
System.arraycopy(oldCounts, head, newCounts, 0, oldCounts.length - head);
|
||||||
|
System.arraycopy(oldCounts, 0, newCounts, oldCounts.length - head, tail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current rate in units per second based on the rolling sum and the configured
|
||||||
|
* interval. Specifically: {@code sum / (intervalSeconds)} where {@code intervalSeconds}
|
||||||
|
* equals {@code interval / 1e9}.
|
||||||
|
*
|
||||||
|
* @return the rate in units per second for the current window
|
||||||
|
*/
|
||||||
|
public double getRate() {
|
||||||
|
return (double)this.sum / ((double)this.interval * 1.0E-9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured interval size in nanoseconds.
|
||||||
|
*
|
||||||
|
* @return the interval size in nanoseconds
|
||||||
|
*/
|
||||||
|
public long getInterval() {
|
||||||
|
return this.interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the rolling sum of all counts currently within the window.
|
||||||
|
*
|
||||||
|
* @return the rolling sum
|
||||||
|
*/
|
||||||
|
public long getSum() {
|
||||||
|
return this.sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of data points currently stored in the internal ring buffer. This may be
|
||||||
|
* less than or equal to the number of points added since older entries may have been evicted.
|
||||||
|
*
|
||||||
|
* @return the number of stored data points
|
||||||
|
*/
|
||||||
|
public int totalDataPoints() {
|
||||||
|
return this.tail >= this.head ? (this.tail - this.head) : (this.tail + (this.counts.length - this.head));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,6 +74,11 @@ sample-players-in-ping = false
|
|||||||
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
# If not enabled (default is true) player IP addresses will be replaced by <ip address withheld> in logs
|
||||||
enable-player-address-logging = true
|
enable-player-address-logging = true
|
||||||
|
|
||||||
|
[packet-limiter]
|
||||||
|
interval = 3000
|
||||||
|
pps = 100
|
||||||
|
bytes = 1000
|
||||||
|
|
||||||
[servers]
|
[servers]
|
||||||
# Configure your servers here. Each key represents the server's name, and the value
|
# Configure your servers here. Each key represents the server's name, and the value
|
||||||
# represents the IP address of the server to connect to.
|
# represents the IP address of the server to connect to.
|
||||||
|
|||||||
Reference in New Issue
Block a user