mirror of
https://github.com/PaperMC/Velocity.git
synced 2024-11-21 03:11:38 +08:00
Add connection attempt rate-limiting.
This commit is contained in:
parent
db8b7c807c
commit
254508a5cf
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Ticker;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class RatelimiterTest {
|
||||
|
||||
@Test
|
||||
void attempt() {
|
||||
long base = System.nanoTime();
|
||||
AtomicLong extra = new AtomicLong();
|
||||
Ticker testTicker = new Ticker() {
|
||||
@Override
|
||||
public long read() {
|
||||
return base + extra.get();
|
||||
}
|
||||
};
|
||||
Ratelimiter ratelimiter = new Ratelimiter(1000, testTicker);
|
||||
assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress()));
|
||||
assertFalse(ratelimiter.attempt(InetAddress.getLoopbackAddress()));
|
||||
extra.addAndGet(TimeUnit.SECONDS.toNanos(2));
|
||||
assertTrue(ratelimiter.attempt(InetAddress.getLoopbackAddress()));
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ServerMapTest {
|
||||
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
|
||||
|
||||
@Test
|
||||
void respectsCaseInsensitivity() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
|
||||
assertEquals(Optional.of(info), map.getServer("TestServer"));
|
||||
assertEquals(Optional.of(info), map.getServer("testserver"));
|
||||
assertEquals(Optional.of(info), map.getServer("TESTSERVER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsRepeatedRegisterAttempts() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
|
||||
ServerInfo willReject = new ServerInfo("TESTSERVER", TEST_ADDRESS);
|
||||
assertThrows(IllegalArgumentException.class, () -> map.register(willReject));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user