#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.
This commit is contained in:
parent
7dd09289ee
commit
5ef5dd2c09
@ -281,7 +281,7 @@ public class BungeeCord extends ProxyServer
|
|||||||
|
|
||||||
if ( config.getThrottle() > 0 )
|
if ( config.getThrottle() > 0 )
|
||||||
{
|
{
|
||||||
connectionThrottle = new ConnectionThrottle( config.getThrottle() );
|
connectionThrottle = new ConnectionThrottle( config.getThrottle(), config.getThrottleLimit() );
|
||||||
}
|
}
|
||||||
startListeners();
|
startListeners();
|
||||||
|
|
||||||
|
@ -1,29 +1,45 @@
|
|||||||
package net.md_5.bungee;
|
package net.md_5.bungee;
|
||||||
|
|
||||||
import com.google.common.cache.Cache;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ConnectionThrottle
|
public class ConnectionThrottle
|
||||||
{
|
{
|
||||||
|
|
||||||
private final Cache<InetAddress, Boolean> throttle;
|
private final LoadingCache<InetAddress, Integer> throttle;
|
||||||
|
private final int throttleLimit;
|
||||||
|
|
||||||
public ConnectionThrottle(int throttleTime)
|
public ConnectionThrottle(int throttleTime, int throttleLimit)
|
||||||
{
|
{
|
||||||
this.throttle = CacheBuilder.newBuilder()
|
this.throttle = CacheBuilder.newBuilder()
|
||||||
.concurrencyLevel( Runtime.getRuntime().availableProcessors() )
|
.concurrencyLevel( Runtime.getRuntime().availableProcessors() )
|
||||||
.initialCapacity( 100 )
|
.initialCapacity( 100 )
|
||||||
.expireAfterWrite( throttleTime, TimeUnit.MILLISECONDS )
|
.expireAfterWrite( throttleTime, TimeUnit.MILLISECONDS )
|
||||||
.build();
|
.build( new CacheLoader<InetAddress, Integer>()
|
||||||
|
{
|
||||||
|
@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)
|
public boolean throttle(InetAddress address)
|
||||||
{
|
{
|
||||||
boolean isThrottled = throttle.getIfPresent( address ) != null;
|
int throttleCount = throttle.getUnchecked( address ) + 1;
|
||||||
throttle.put( address, true );
|
throttle.put( address, throttleCount );
|
||||||
|
|
||||||
return isThrottled;
|
return throttleCount > throttleLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ public class Configuration implements ProxyConfig
|
|||||||
private int playerLimit = -1;
|
private int playerLimit = -1;
|
||||||
private Collection<String> disabledCommands;
|
private Collection<String> disabledCommands;
|
||||||
private int throttle = 4000;
|
private int throttle = 4000;
|
||||||
|
private int throttleLimit = 3;
|
||||||
private boolean ipForward;
|
private boolean ipForward;
|
||||||
private Favicon favicon;
|
private Favicon favicon;
|
||||||
private int compressionThreshold = 256;
|
private int compressionThreshold = 256;
|
||||||
@ -87,6 +88,7 @@ public class Configuration implements ProxyConfig
|
|||||||
logPings = adapter.getBoolean( "log_pings", logPings );
|
logPings = adapter.getBoolean( "log_pings", logPings );
|
||||||
playerLimit = adapter.getInt( "player_limit", playerLimit );
|
playerLimit = adapter.getInt( "player_limit", playerLimit );
|
||||||
throttle = adapter.getInt( "connection_throttle", throttle );
|
throttle = adapter.getInt( "connection_throttle", throttle );
|
||||||
|
throttleLimit = adapter.getInt( "connection_throttle_limit", throttleLimit );
|
||||||
ipForward = adapter.getBoolean( "ip_forward", ipForward );
|
ipForward = adapter.getBoolean( "ip_forward", ipForward );
|
||||||
compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold );
|
compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold );
|
||||||
preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections );
|
preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections );
|
||||||
|
@ -235,6 +235,10 @@ public class InitialHandler extends PacketHandler implements PendingConnection
|
|||||||
{
|
{
|
||||||
Gson gson = BungeeCord.getInstance().gson;
|
Gson gson = BungeeCord.getInstance().gson;
|
||||||
unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) );
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( bungee.getConnectionThrottle() != null && bungee.getConnectionThrottle().throttle( getAddress().getAddress() ) )
|
|
||||||
{
|
|
||||||
disconnect( bungee.getTranslation( "join_throttle_kick", TimeUnit.MILLISECONDS.toSeconds( bungee.getConfig().getThrottle() ) ) );
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException( "Cannot request protocol " + handshake.getRequestedProtocol() );
|
throw new IllegalArgumentException( "Cannot request protocol " + handshake.getRequestedProtocol() );
|
||||||
|
@ -21,6 +21,7 @@ import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
|
|||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -50,6 +51,12 @@ public class PipelineUtils
|
|||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel ch) throws Exception
|
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();
|
ListenerInfo listener = ch.attr( LISTENER ).get();
|
||||||
|
|
||||||
BASE.initChannel( ch );
|
BASE.initChannel( ch );
|
||||||
|
@ -23,5 +23,4 @@ total_players=Total players online: {0}
|
|||||||
name_too_long=Cannot have username longer than 16 characters
|
name_too_long=Cannot have username longer than 16 characters
|
||||||
name_invalid=Username contains invalid characters.
|
name_invalid=Username contains invalid characters.
|
||||||
ping_cannot_connect=\u00a7c[Bungee] Can't connect to server.
|
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
|
offline_mode_player=Not authenticated with Minecraft.net
|
||||||
|
@ -11,7 +11,7 @@ public class ThrottleTest
|
|||||||
@Test
|
@Test
|
||||||
public void testThrottle() throws InterruptedException, UnknownHostException
|
public void testThrottle() throws InterruptedException, UnknownHostException
|
||||||
{
|
{
|
||||||
ConnectionThrottle throttle = new ConnectionThrottle( 10 );
|
ConnectionThrottle throttle = new ConnectionThrottle( 10, 3 );
|
||||||
InetAddress address;
|
InetAddress address;
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -22,9 +22,17 @@ public class ThrottleTest
|
|||||||
address = InetAddress.getByName( null );
|
address = InetAddress.getByName( null );
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
|
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 1
|
||||||
Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) );
|
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 );
|
Thread.sleep( 50 );
|
||||||
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
|
Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) );
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user