forked from TuxCoding/FastLogin
@@ -0,0 +1,41 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
/**
|
||||
* Limit the number of requests with a maximum size. Each requests expires after the specified time making it available
|
||||
* for another request.
|
||||
*/
|
||||
public class RateLimiter {
|
||||
|
||||
private final long[] requests;
|
||||
private final long expireTime;
|
||||
private int position;
|
||||
|
||||
public RateLimiter(int maxLimit, long expireTime) {
|
||||
this.requests = new long[maxLimit];
|
||||
this.expireTime = expireTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask if access is allowed. If so register the request.
|
||||
*
|
||||
* @return true if allowed
|
||||
*/
|
||||
public boolean tryAcquire() {
|
||||
// currentTime millis could be expensive on some systems
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
// after this the request should be expired
|
||||
long toBeExpired = now - expireTime;
|
||||
synchronized (this) {
|
||||
// having synchronized will limit the amount of concurrency a lot
|
||||
long oldest = requests[position];
|
||||
if (oldest < toBeExpired) {
|
||||
requests[position] = now;
|
||||
position = (position + 1) % requests.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.github.games647.craftapi.resolver.MojangResolver;
|
||||
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CommonUtil;
|
||||
import com.github.games647.fastlogin.core.RateLimiter;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
@@ -55,6 +56,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
|
||||
private Configuration config;
|
||||
private AuthStorage storage;
|
||||
private RateLimiter rateLimiter;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
@@ -82,8 +84,12 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
});
|
||||
} catch (IOException ioEx) {
|
||||
plugin.getLog().error("Failed to load yaml files", ioEx);
|
||||
return;
|
||||
}
|
||||
|
||||
int maxCon = config.getInt("anti-bot.connections");
|
||||
int expireTime = config.getInt("anti-bot.expire");
|
||||
rateLimiter = new RateLimiter(maxCon, expireTime * 60 * 1_000);
|
||||
Set<Proxy> proxies = config.getStringList("proxies")
|
||||
.stream()
|
||||
.map(HostAndPort::fromString)
|
||||
@@ -217,6 +223,10 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public RateLimiter getRateLimiter() {
|
||||
return rateLimiter;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,19 @@
|
||||
# You can access the newest config here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/config.yml
|
||||
|
||||
# This a **very** simple anti bot protection. Recommendation is to use a a dedicated program to approach this
|
||||
# problem. Low level firewalls like iptables, ufw are more efficient than a Minecraft plugin. TCP reverse
|
||||
# proxies could also be used and offload some work even to different host.
|
||||
#
|
||||
# The settings wil limit how many connections this plugin will handle. After hitting this limit. FastLogin will
|
||||
# completely ignore incoming connections. Effectively there will be no database requests and network requests.
|
||||
# Therefore auto logins won't be possible.
|
||||
anti-bot:
|
||||
# Total number of connections
|
||||
connections: 200
|
||||
# Amount of time after the first connection will expire and made available
|
||||
expire: 5
|
||||
|
||||
# Request a premium login without forcing the player to type a command
|
||||
#
|
||||
# If you activate autoRegister, this plugin will check/do these points on login:
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RateLimiterTest {
|
||||
|
||||
private static final long THRESHOLD_MILLI = 10;
|
||||
|
||||
/**
|
||||
* Always expired
|
||||
*/
|
||||
@Test
|
||||
public void allowExpire() throws InterruptedException {
|
||||
int size = 3;
|
||||
|
||||
// run twice the size to fill it first and then test it
|
||||
RateLimiter rateLimiter = new RateLimiter(size, 0);
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Thread.sleep(1);
|
||||
assertTrue("Should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too many requests
|
||||
*/
|
||||
@Test
|
||||
public void shoudBlock() {
|
||||
int size = 3;
|
||||
|
||||
// fill the size
|
||||
RateLimiter rateLimiter = new RateLimiter(size, TimeUnit.SECONDS.toMillis(30));
|
||||
for (int i = 0; i < size; i++) {
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire());
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocked attempts shouldn't replace existing ones.
|
||||
*/
|
||||
@Test
|
||||
public void blockedNotAdded() throws InterruptedException {
|
||||
// fill the size - 100ms should be reasonable high
|
||||
RateLimiter rateLimiter = new RateLimiter(1, 100);
|
||||
assertTrue("Filling up", rateLimiter.tryAcquire());
|
||||
|
||||
Thread.sleep(50);
|
||||
|
||||
// still is full - should fail
|
||||
assertFalse("Expired too early", rateLimiter.tryAcquire());
|
||||
|
||||
// wait the remaining time and add a threshold, because
|
||||
Thread.sleep(50 + THRESHOLD_MILLI);
|
||||
assertTrue("Request not released", rateLimiter.tryAcquire());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user