diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java index 5b4da559..8ce23daf 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java @@ -41,7 +41,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; - import protocolsupport.api.events.ConnectionCloseEvent; import protocolsupport.api.events.PlayerLoginStartEvent; import protocolsupport.api.events.PlayerProfileCompleteEvent; diff --git a/core/src/main/java/com/github/games647/fastlogin/core/RateLimiter.java b/core/src/main/java/com/github/games647/fastlogin/core/RateLimiter.java index 6cd5dc4e..c18cb9f2 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/RateLimiter.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/RateLimiter.java @@ -1,76 +1,7 @@ -/* - * SPDX-License-Identifier: MIT - * - * The MIT License (MIT) - * - * Copyright (c) 2015-2021 - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ package com.github.games647.fastlogin.core; -import com.google.common.base.Ticker; +@FunctionalInterface +public interface RateLimiter { -import java.util.Arrays; - -/** - * Limit the number of requests with a maximum size. Each requests expire after the specified time making it available - * for another request. - */ -public class RateLimiter { - - private final Ticker ticker; - - private final long[] requests; - private final long expireTime; - private int position; - - public RateLimiter(Ticker ticker, int maxLimit, long expireTime) { - this.ticker = ticker; - - this.requests = new long[maxLimit]; - this.expireTime = expireTime; - - // fill the array with expired entry, because nanoTime could overflow and include negative numbers - long nowMilli = ticker.read() / 1_000_000; - long initialVal = nowMilli - expireTime; - Arrays.fill(requests, initialVal); - } - - /** - * Ask if access is allowed. If so register the request. - * - * @return true if allowed - false otherwise without any side effects - */ - public boolean tryAcquire() { - // current time millis is not monotonic - it can jump back depending on user choice or NTP - long nowMilli = ticker.read() / 1_000_000; - synchronized (this) { - // having synchronized will limit the amount of concurrency a lot - long oldest = requests[position]; - if (nowMilli - oldest >= expireTime) { - requests[position] = nowMilli; - position = (position + 1) % requests.length; - return true; - } - - return false; - } - } + boolean tryAcquire(); } diff --git a/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java b/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java new file mode 100644 index 00000000..f732a8cf --- /dev/null +++ b/core/src/main/java/com/github/games647/fastlogin/core/TickingRateLimiter.java @@ -0,0 +1,77 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2021 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.core; + +import com.google.common.base.Ticker; + +import java.util.Arrays; + +/** + * Limit the number of requests with a maximum size. Each requests expire after the specified time making it available + * for another request. + */ +public class TickingRateLimiter implements RateLimiter { + + private final Ticker ticker; + + private final long[] requests; + private final long expireTime; + private int position; + + public TickingRateLimiter(Ticker ticker, int maxLimit, long expireTime) { + this.ticker = ticker; + + this.requests = new long[maxLimit]; + this.expireTime = expireTime; + + // fill the array with expired entry, because nanoTime could overflow and include negative numbers + long nowMilli = ticker.read() / 1_000_000; + long initialVal = nowMilli - expireTime; + Arrays.fill(requests, initialVal); + } + + /** + * Ask if access is allowed. If so register the request. + * + * @return true if allowed - false otherwise without any side effects + */ + @Override + public boolean tryAcquire() { + // current time millis is not monotonic - it can jump back depending on user choice or NTP + long nowMilli = ticker.read() / 1_000_000; + synchronized (this) { + // having synchronized will limit the amount of concurrency a lot + long oldest = requests[position]; + if (nowMilli - oldest >= expireTime) { + requests[position] = nowMilli; + position = (position + 1) % requests.length; + return true; + } + + return false; + } + } +} diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java index 630873f1..cc2438d1 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/FastLoginCore.java @@ -29,6 +29,7 @@ import com.github.games647.craftapi.resolver.MojangResolver; import com.github.games647.craftapi.resolver.http.RotatingProxySelector; import com.github.games647.fastlogin.core.CommonUtil; import com.github.games647.fastlogin.core.RateLimiter; +import com.github.games647.fastlogin.core.TickingRateLimiter; import com.github.games647.fastlogin.core.hooks.AuthPlugin; import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator; import com.github.games647.fastlogin.core.hooks.PasswordGenerator; @@ -117,13 +118,7 @@ public class FastLoginCore

> { return; } - int maxCon = config.getInt("anti-bot.connections", 200); - long expireTime = config.getLong("anti-bot.expire", 5) * 60 * 1_000L; - if (expireTime > MAX_EXPIRE_RATE) { - expireTime = MAX_EXPIRE_RATE; - } - - rateLimiter = new RateLimiter(Ticker.systemTicker(), maxCon, expireTime); + rateLimiter = createRateLimiter(config.getSection("anti-bot")); Set proxies = config.getStringList("proxies") .stream() .map(HostAndPort::fromString) @@ -145,6 +140,22 @@ public class FastLoginCore

> { resolver.setOutgoingAddresses(addresses); } + private RateLimiter createRateLimiter(Configuration botSection) { + boolean enabled = botSection.getBoolean("enabled", true); + if (!enabled) { + // no-op rate limiter + return () -> true; + } + + int maxCon = botSection.getInt("anti-bot.connections", 200); + long expireTime = botSection.getLong("anti-bot.expire", 5) * 60 * 1_000L; + if (expireTime > MAX_EXPIRE_RATE) { + expireTime = MAX_EXPIRE_RATE; + } + + return new TickingRateLimiter(Ticker.systemTicker(), maxCon, expireTime); + } + private Configuration loadFile(String fileName) throws IOException { ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class); diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 3a59a999..e3a01799 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -13,6 +13,7 @@ # completely ignore incoming connections. Effectively there will be no database requests and network requests. # Therefore, auto logins won't be possible. anti-bot: + enabled: true # Image the following like bucket. The following is total amount that is allowed in this bucket, while expire # means how long it takes for every entry to expire. # Total number of connections diff --git a/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java index fd90a7eb..5b831636 100644 --- a/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java +++ b/core/src/test/java/com/github/games647/fastlogin/core/RateLimiterTest.java @@ -47,7 +47,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(5_000_000L); // run twice the size to fill it first and then test it - RateLimiter rateLimiter = new RateLimiter(ticker, size, 0); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0); for (int i = 0; i < size; i++) { assertTrue("Filling up", rateLimiter.tryAcquire()); } @@ -65,7 +65,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(-5_000_000L); // run twice the size to fill it first and then test it - RateLimiter rateLimiter = new RateLimiter(ticker, size, 0); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0); for (int i = 0; i < size; i++) { assertTrue("Filling up", rateLimiter.tryAcquire()); } @@ -86,7 +86,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(5_000_000L); // fill the size - RateLimiter rateLimiter = new RateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); for (int i = 0; i < size; i++) { assertTrue("Filling up", rateLimiter.tryAcquire()); } @@ -104,7 +104,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(-5_000_000L); // fill the size - RateLimiter rateLimiter = new RateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); for (int i = 0; i < size; i++) { assertTrue("Filling up", rateLimiter.tryAcquire()); } @@ -120,7 +120,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(5_000_000L); // fill the size - 100ms should be reasonable high - RateLimiter rateLimiter = new RateLimiter(ticker, 1, 100); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100); assertTrue("Filling up", rateLimiter.tryAcquire()); ticker.add(Duration.ofMillis(50)); @@ -141,7 +141,7 @@ public class RateLimiterTest { FakeTicker ticker = new FakeTicker(-5_000_000L); // fill the size - 100ms should be reasonable high - RateLimiter rateLimiter = new RateLimiter(ticker, 1, 100); + TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100); assertTrue("Filling up", rateLimiter.tryAcquire()); ticker.add(Duration.ofMillis(50));