diff --git a/core/assets/maps/karst.msav b/core/assets/maps/karst.msav index cefbdedc9c..49661c4b98 100644 Binary files a/core/assets/maps/karst.msav and b/core/assets/maps/karst.msav differ diff --git a/core/src/mindustry/core/NetClient.java b/core/src/mindustry/core/NetClient.java index 188516e062..f3151b8458 100644 --- a/core/src/mindustry/core/NetClient.java +++ b/core/src/mindustry/core/NetClient.java @@ -224,6 +224,13 @@ public class NetClient implements ApplicationListener{ //do not receive chat messages from clients that are too young or not registered if(net.server() && player != null && player.con != null && (Time.timeSinceMillis(player.con.connectTime) < 500 || !player.con.hasConnected || !player.isAdded())) return; + //detect and kick for foul play + if(player != null && player.con != null && !player.con.chatRate.allow(2000, 20)){ + player.con.kick(KickReason.kick); + netServer.admins.blacklistDos(player.con.address); + return; + } + if(message.length() > maxTextLength){ throw new ValidateException(player, "Player has sent a message above the text limit."); } diff --git a/core/src/mindustry/net/Administration.java b/core/src/mindustry/net/Administration.java index e6ed620551..917be5a98b 100644 --- a/core/src/mindustry/net/Administration.java +++ b/core/src/mindustry/net/Administration.java @@ -21,6 +21,7 @@ public class Administration{ public Seq chatFilters = new Seq<>(); public Seq actionFilters = new Seq<>(); public Seq subnetBans = new Seq<>(); + public ObjectSet dosBlacklist = new ObjectSet<>(); public ObjectMap kickedIPs = new ObjectMap<>(); /** All player info. Maps UUIDs to info. This persists throughout restarts. Do not access directly. */ @@ -83,6 +84,14 @@ public class Administration{ }); } + public synchronized void blacklistDos(String address){ + dosBlacklist.add(address); + } + + public synchronized boolean isDosBlacklisted(String address){ + return dosBlacklist.contains(address); + } + /** @return time at which a player would be pardoned for a kick (0 means they were never kicked) */ public long getKickTime(String uuid, String ip){ return Math.max(getInfo(uuid).lastKicked, kickedIPs.get(ip, 0L)); @@ -162,7 +171,7 @@ public class Administration{ } public int getPlayerLimit(){ - return Core.settings.getInt("playerlimit", 0); + return Core.settings.getInt("playerlimit", headless ? 30 : 0); } public void setPlayerLimit(int limit){ diff --git a/core/src/mindustry/net/ArcNetProvider.java b/core/src/mindustry/net/ArcNetProvider.java index a9fac3cbb7..402c24b79c 100644 --- a/core/src/mindustry/net/ArcNetProvider.java +++ b/core/src/mindustry/net/ArcNetProvider.java @@ -11,6 +11,7 @@ import arc.util.*; import arc.util.Log.*; import arc.util.io.*; import mindustry.*; +import mindustry.game.EventType.*; import mindustry.net.Net.*; import mindustry.net.Packets.*; import net.jpountz.lz4.*; @@ -19,7 +20,6 @@ import java.io.*; import java.net.*; import java.nio.*; import java.nio.channels.*; -import java.util.*; import java.util.concurrent.*; import static mindustry.Vars.*; @@ -35,6 +35,8 @@ public class ArcNetProvider implements NetProvider{ private static final LZ4FastDecompressor decompressor = LZ4Factory.fastestInstance().fastDecompressor(); private static final LZ4Compressor compressor = LZ4Factory.fastestInstance().fastCompressor(); + private volatile int playerLimitCache; + public ArcNetProvider(){ ArcNet.errorHandler = e -> { if(Log.level == LogLevel.debug){ @@ -42,6 +44,11 @@ public class ArcNetProvider implements NetProvider{ } }; + //fetch this in the main thread to prevent threading issues + Events.run(Trigger.update, () -> { + playerLimitCache = netServer.admins.getPlayerLimit(); + }); + client = new Client(8192, 8192, new PacketSerializer()); client.setDiscoveryPacket(packetSupplier); client.addListener(new NetListener(){ @@ -94,6 +101,12 @@ public class ArcNetProvider implements NetProvider{ public void connected(Connection connection){ String ip = connection.getRemoteAddressTCP().getAddress().getHostAddress(); + //kill connections above the limit to prevent spam + if((playerLimitCache > 0 && server.getConnections().length > playerLimitCache) || netServer.admins.isDosBlacklisted(ip)){ + connection.close(DcReason.closed); + return; + } + ArcConnection kn = new ArcConnection(ip, connection); Connect c = new Connect(); diff --git a/core/src/mindustry/net/NetConnection.java b/core/src/mindustry/net/NetConnection.java index eb43dc84f1..4d34e2702a 100644 --- a/core/src/mindustry/net/NetConnection.java +++ b/core/src/mindustry/net/NetConnection.java @@ -28,6 +28,8 @@ public abstract class NetConnection{ public long lastReceivedClientTime; /** Build requests that have been recently rejected. This is cleared every snapshot. */ public Seq rejectedRequests = new Seq<>(); + /** Handles chat spam rate limits. */ + public Ratekeeper chatRate = new Ratekeeper(); public boolean hasConnected, hasBegunConnecting, hasDisconnected; public float viewWidth, viewHeight, viewX, viewY;