Add connection attempt rate-limiting.

This commit is contained in:
Andrew Steinborn
2018-08-09 03:23:27 -04:00
parent db8b7c807c
commit 254508a5cf
6 changed files with 123 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.proxy.command.CommandManager;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.Ratelimiter;
import com.velocitypowered.proxy.util.ServerMap;
import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component;
@@ -71,6 +72,7 @@ public class VelocityServer implements ProxyServer {
return true;
}
};
private final Ratelimiter ipAttemptLimiter = new Ratelimiter(3000); // TODO: Configurable.
private VelocityServer() {
commandManager.registerCommand("velocity", new VelocityCommand());
@@ -162,6 +164,10 @@ public class VelocityServer implements ProxyServer {
return httpClient;
}
public Ratelimiter getIpAttemptLimiter() {
return ipAttemptLimiter;
}
public boolean registerConnection(ConnectedPlayer connection) {
String lowerName = connection.getUsername().toLowerCase(Locale.US);
if (connectionsByName.putIfAbsent(lowerName, connection) != null) {

View File

@@ -15,6 +15,7 @@ import net.kyori.text.TextComponent;
import net.kyori.text.TranslatableComponent;
import net.kyori.text.format.TextColor;
import java.net.InetAddress;
import java.net.InetSocketAddress;
public class HandshakeSessionHandler implements MinecraftSessionHandler {
@@ -50,6 +51,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
connection.closeWith(Disconnect.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
return;
} else {
InetAddress address = ((InetSocketAddress) connection.getChannel().remoteAddress()).getAddress();
if (!VelocityServer.getServer().getIpAttemptLimiter().attempt(address)) {
connection.closeWith(Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
return;
}
connection.setSessionHandler(new LoginSessionHandler(connection));
}
break;

View File

@@ -91,6 +91,11 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
VelocityServer.getServer().getHttpClient()
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
.thenAcceptAsync(profileResponse -> {
if (inbound.isClosed()) {
// The player disconnected after we authenticated them.
return;
}
try {
inbound.enableEncryption(decryptedSharedSecret);
} catch (GeneralSecurityException e) {

View File

@@ -0,0 +1,41 @@
package com.velocitypowered.proxy.util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import java.net.InetAddress;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Ratelimiter {
private final Cache<InetAddress, Long> expiringCache;
private final long timeoutNanos;
public Ratelimiter(long timeoutMs) {
this(timeoutMs, Ticker.systemTicker());
}
@VisibleForTesting
Ratelimiter(long timeoutMs, Ticker ticker) {
this.timeoutNanos = TimeUnit.MILLISECONDS.toNanos(timeoutMs);
this.expiringCache = CacheBuilder.newBuilder()
.ticker(ticker)
.concurrencyLevel(Runtime.getRuntime().availableProcessors())
.expireAfterWrite(timeoutMs, TimeUnit.MILLISECONDS)
.build();
}
public boolean attempt(InetAddress address) {
long expectedNewValue = System.nanoTime() + timeoutNanos;
long last;
try {
last = expiringCache.get(address, () -> expectedNewValue);
} catch (ExecutionException e) {
// It should be impossible for this to fail.
throw new AssertionError(e);
}
return expectedNewValue == last;
}
}