From 87d9907413e30c96d588d2c7257e0f590cd169c4 Mon Sep 17 00:00:00 2001 From: chris Date: Sun, 23 Feb 2025 18:37:01 +0100 Subject: [PATCH] Increase packet limits for multiple connected clients playing from one IP address (#5351) * Fix https://github.com/GeyserMC/Geyser/issues/4926 * Extend the RakServerRateLimiter, now that it is possible * Update core/src/main/java/org/geysermc/geyser/network/netty/handler/RakGeyserRateLimiter.java * cast to int --- .../geyser/network/netty/GeyserServer.java | 4 ++ .../netty/handler/RakGeyserRateLimiter.java | 50 +++++++++++++++++++ .../geyser/session/SessionManager.java | 35 ++++++++++++- gradle/libs.versions.toml | 2 +- 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/network/netty/handler/RakGeyserRateLimiter.java diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java index efbd8bdff..fec254996 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/GeyserServer.java @@ -59,6 +59,7 @@ import org.geysermc.geyser.network.CIDRMatcher; import org.geysermc.geyser.network.GameProtocol; import org.geysermc.geyser.network.GeyserServerInitializer; import org.geysermc.geyser.network.netty.handler.RakConnectionRequestHandler; +import org.geysermc.geyser.network.netty.handler.RakGeyserRateLimiter; import org.geysermc.geyser.network.netty.handler.RakPingHandler; import org.geysermc.geyser.network.netty.proxy.ProxyServerHandler; import org.geysermc.geyser.ping.GeyserPingInfo; @@ -175,6 +176,9 @@ public final class GeyserServer { if (Boolean.parseBoolean(System.getProperty("Geyser.RakRateLimitingDisabled", "false")) || isWhitelistedProxyProtocol) { // We would already block any non-whitelisted IP addresses in onConnectionRequest so we can remove the rate limiter channel.pipeline().remove(RakServerRateLimiter.NAME); + } else { + // Use our own rate limiter to allow multiple players from the same IP + channel.pipeline().replace(RakServerRateLimiter.NAME, RakGeyserRateLimiter.NAME, new RakGeyserRateLimiter(channel)); } } diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakGeyserRateLimiter.java b/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakGeyserRateLimiter.java new file mode 100644 index 000000000..65e66eb33 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakGeyserRateLimiter.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2025 GeyserMC. http://geysermc.org + * + * 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. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.network.netty.handler; + +import io.netty.channel.Channel; +import org.cloudburstmc.netty.channel.raknet.RakServerChannel; +import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerRateLimiter; +import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.session.SessionManager; + +import java.net.InetAddress; + +public class RakGeyserRateLimiter extends RakServerRateLimiter { + public static final String NAME = "rak-geyser-rate-limiter"; + private final SessionManager sessionManager; + + public RakGeyserRateLimiter(Channel channel) { + super((RakServerChannel) channel); + this.sessionManager = GeyserImpl.getInstance().getSessionManager(); + } + + @Override + protected int getAddressMaxPacketCount(InetAddress address) { + // Using a factor of 0.8 for now, as the default packet count is already padded for multiple + return (int) (super.getAddressMaxPacketCount(address) * sessionManager.getAddressMultiplier(address) * 0.8); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java index 14881d059..2175e3aaa 100644 --- a/core/src/main/java/org/geysermc/geyser/session/SessionManager.java +++ b/core/src/main/java/org/geysermc/geyser/session/SessionManager.java @@ -32,8 +32,15 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.text.GeyserLocale; -import java.util.*; +import java.net.InetAddress; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; public final class SessionManager { /** @@ -47,6 +54,13 @@ public final class SessionManager { @Getter private final Map sessions = new ConcurrentHashMap<>(); + /** + * Stores the number of connected sessions per address they're connected from. + * Used to raise per-IP connection limits. + */ + @Getter(AccessLevel.PACKAGE) + private final Map connectedClients = new ConcurrentHashMap<>(); + /** * Called once the player has successfully authenticated to the Geyser server. */ @@ -60,6 +74,14 @@ public final class SessionManager { public void addSession(UUID uuid, GeyserSession session) { pendingSessions.remove(session); sessions.put(uuid, session); + connectedClients.compute(session.getSocketAddress().getAddress(), (key, count) -> { + if (count == null) { + return new AtomicInteger(0); + } + + count.getAndIncrement(); + return count; + }); } public void removeSession(GeyserSession session) { @@ -68,6 +90,17 @@ public final class SessionManager { // Connection was likely pending pendingSessions.remove(session); } + connectedClients.computeIfPresent(session.getSocketAddress().getAddress(), (key, count) -> { + if (count.decrementAndGet() <= 0) { + return null; + } + return count; + }); + } + + public int getAddressMultiplier(InetAddress ip) { + AtomicInteger atomicInteger = connectedClients.get(ip); + return atomicInteger == null ? 1 : atomicInteger.get(); } public @Nullable GeyserSession sessionByXuid(@NonNull String xuid) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15691e2d9..ae35c6e34 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ websocket = "1.5.1" protocol-connection = "3.0.0.Beta6-20250212.131009-3" protocol-common = "3.0.0.Beta6-20250212.131009-3" protocol-codec = "3.0.0.Beta6-20250212.131009-3" -raknet = "1.0.0.CR3-20250128.101054-17" +raknet = "1.0.0.CR3-20250218.160705-18" minecraftauth = "4.1.1" mcprotocollib = "1.21.4-20250218.175633-22" adventure = "4.14.0"