From 5ef5dd2c098b69908a04a4ca2dc06824ec2e75c1 Mon Sep 17 00:00:00 2001 From: md_5 Date: Thu, 27 Dec 2018 10:25:29 +1100 Subject: [PATCH] #2570: Implement more aggressive connection throttling. Once an IP has connected connection_throttle_limit times within connection_throttle milliseconds, it must wait connection_throttle milliseconds before attempting to connect again. --- .../main/java/net/md_5/bungee/BungeeCord.java | 2 +- .../net/md_5/bungee/ConnectionThrottle.java | 30 ++++++++++++++----- .../net/md_5/bungee/conf/Configuration.java | 2 ++ .../bungee/connection/InitialHandler.java | 9 +++--- .../net/md_5/bungee/netty/PipelineUtils.java | 7 +++++ proxy/src/main/resources/messages.properties | 1 - .../java/net/md_5/bungee/ThrottleTest.java | 14 +++++++-- 7 files changed, 48 insertions(+), 17 deletions(-) diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 20e14557..3550690b 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -281,7 +281,7 @@ public class BungeeCord extends ProxyServer if ( config.getThrottle() > 0 ) { - connectionThrottle = new ConnectionThrottle( config.getThrottle() ); + connectionThrottle = new ConnectionThrottle( config.getThrottle(), config.getThrottleLimit() ); } startListeners(); diff --git a/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java b/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java index 4c9190e2..f1716a1f 100644 --- a/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java +++ b/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java @@ -1,29 +1,45 @@ package net.md_5.bungee; -import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import java.net.InetAddress; import java.util.concurrent.TimeUnit; public class ConnectionThrottle { - private final Cache throttle; + private final LoadingCache throttle; + private final int throttleLimit; - public ConnectionThrottle(int throttleTime) + public ConnectionThrottle(int throttleTime, int throttleLimit) { this.throttle = CacheBuilder.newBuilder() .concurrencyLevel( Runtime.getRuntime().availableProcessors() ) .initialCapacity( 100 ) .expireAfterWrite( throttleTime, TimeUnit.MILLISECONDS ) - .build(); + .build( new CacheLoader() + { + @Override + public Integer load(InetAddress key) throws Exception + { + return 0; + } + } ); + this.throttleLimit = throttleLimit; + } + + public void unthrottle(InetAddress address) + { + int throttleCount = throttle.getUnchecked( address ) - 1; + throttle.put( address, throttleCount ); } public boolean throttle(InetAddress address) { - boolean isThrottled = throttle.getIfPresent( address ) != null; - throttle.put( address, true ); + int throttleCount = throttle.getUnchecked( address ) + 1; + throttle.put( address, throttleCount ); - return isThrottled; + return throttleCount > throttleLimit; } } diff --git a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java index 36cac069..77d28348 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java @@ -55,6 +55,7 @@ public class Configuration implements ProxyConfig private int playerLimit = -1; private Collection disabledCommands; private int throttle = 4000; + private int throttleLimit = 3; private boolean ipForward; private Favicon favicon; private int compressionThreshold = 256; @@ -87,6 +88,7 @@ public class Configuration implements ProxyConfig logPings = adapter.getBoolean( "log_pings", logPings ); playerLimit = adapter.getInt( "player_limit", playerLimit ); throttle = adapter.getInt( "connection_throttle", throttle ); + throttleLimit = adapter.getInt( "connection_throttle_limit", throttleLimit ); ipForward = adapter.getBoolean( "ip_forward", ipForward ); compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold ); preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections ); diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index dbd77a35..fff532de 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -235,6 +235,10 @@ public class InitialHandler extends PacketHandler implements PendingConnection { Gson gson = BungeeCord.getInstance().gson; unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) ); + if ( bungee.getConnectionThrottle() != null ) + { + bungee.getConnectionThrottle().unthrottle( getAddress().getAddress() ); + } } }; @@ -326,11 +330,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection } return; } - - if ( bungee.getConnectionThrottle() != null && bungee.getConnectionThrottle().throttle( getAddress().getAddress() ) ) - { - disconnect( bungee.getTranslation( "join_throttle_kick", TimeUnit.MILLISECONDS.toSeconds( bungee.getConfig().getThrottle() ) ) ); - } break; default: throw new IllegalArgumentException( "Cannot request protocol " + handshake.getRequestedProtocol() ); diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index ffee7062..16e07a4c 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -21,6 +21,7 @@ import io.netty.handler.codec.haproxy.HAProxyMessageDecoder; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.util.AttributeKey; import io.netty.util.internal.PlatformDependent; +import java.net.InetSocketAddress; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -50,6 +51,12 @@ public class PipelineUtils @Override protected void initChannel(Channel ch) throws Exception { + if ( BungeeCord.getInstance().getConnectionThrottle() != null && BungeeCord.getInstance().getConnectionThrottle().throttle( ( (InetSocketAddress) ch.remoteAddress() ).getAddress() ) ) + { + ch.close(); + return; + } + ListenerInfo listener = ch.attr( LISTENER ).get(); BASE.initChannel( ch ); diff --git a/proxy/src/main/resources/messages.properties b/proxy/src/main/resources/messages.properties index 84405e99..dfb58c12 100644 --- a/proxy/src/main/resources/messages.properties +++ b/proxy/src/main/resources/messages.properties @@ -23,5 +23,4 @@ total_players=Total players online: {0} name_too_long=Cannot have username longer than 16 characters name_invalid=Username contains invalid characters. ping_cannot_connect=\u00a7c[Bungee] Can't connect to server. -join_throttle_kick=You have connected too fast. You must wait at least {0} seconds between connections. offline_mode_player=Not authenticated with Minecraft.net diff --git a/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java b/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java index 5f501f7d..881aa06a 100644 --- a/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java +++ b/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java @@ -11,7 +11,7 @@ public class ThrottleTest @Test public void testThrottle() throws InterruptedException, UnknownHostException { - ConnectionThrottle throttle = new ConnectionThrottle( 10 ); + ConnectionThrottle throttle = new ConnectionThrottle( 10, 3 ); InetAddress address; try @@ -22,9 +22,17 @@ public class ThrottleTest address = InetAddress.getByName( null ); } - Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); - Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 1 + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 2 + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 3 + Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); // The 3rd one must be throttled, but also increased the count to 4 + throttle.unthrottle( address ); // We are back at 3, next attempt will make it 4 and throttle + throttle.unthrottle( address ); // Now we are at 2, will not be throttled + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 3 + Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); // 4 + + // Now test expiration Thread.sleep( 50 ); Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); }