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 b1ece0c1c1e..bb00bd04a5a 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 @@ -37,6 +37,7 @@ import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.Future; import lombok.Getter; +import org.checkerframework.checker.nullness.qual.Nullable; import org.cloudburstmc.netty.channel.raknet.RakChannelFactory; import org.cloudburstmc.netty.channel.raknet.config.DefaultRakServerThrottle; import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; @@ -234,9 +235,24 @@ private ServerBootstrap createBootstrap() { .childHandler(serverInitializer); } - public boolean onConnectionRequest(InetSocketAddress inetSocketAddress, InetSocketAddress clientAddress) { + public boolean onConnectionRequest(InetSocketAddress inetSocketAddress, @Nullable InetSocketAddress clientAddress) { + boolean haproxyEnabled = geyser.config().advanced().bedrock().useHaproxyProtocol(); + + if (clientAddress == null) { + // getClientAddress returns null when no PROXY header has been cached for this sender: + // upstream not emitting a header per UDP datagram, a PROXY v2 LOCAL frame the upstream + // library mishandles, or the cached entry expired. + if (haproxyEnabled) { + geyser.getLogger().warning( + "Rejecting Bedrock connection from " + inetSocketAddress + ": use-haproxy-protocol is enabled but no PROXY header was received. Verify your reverse proxy sends a PROXY v2 header on every UDP datagram." + ); + } + connectionAttempts++; + return false; + } + List allowedProxyIPs = geyser.config().advanced().bedrock().haproxyProtocolWhitelistedIps(); - if (geyser.config().advanced().bedrock().useHaproxyProtocol() && !allowedProxyIPs.isEmpty()) { + if (haproxyEnabled && !allowedProxyIPs.isEmpty()) { boolean isWhitelistedIP = false; for (CIDRMatcher matcher : getWhitelistedIPsMatchers()) { if (matcher.matches(inetSocketAddress.getAddress())) { @@ -255,7 +271,7 @@ public boolean onConnectionRequest(InetSocketAddress inetSocketAddress, InetSock ConnectionRequestEvent requestEvent = new ConnectionRequestEvent( clientAddress, - geyser.config().advanced().bedrock().useHaproxyProtocol() ? inetSocketAddress : null + haproxyEnabled ? inetSocketAddress : null ); geyser.eventBus().fire(requestEvent); if (requestEvent.isCancelled()) { diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakPingHandler.java b/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakPingHandler.java index 9d475f28602..0afc104a62c 100644 --- a/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakPingHandler.java +++ b/core/src/main/java/org/geysermc/geyser/network/netty/handler/RakPingHandler.java @@ -50,6 +50,12 @@ protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) { InetSocketAddress address = msg.getSender(); InetSocketAddress clientAddress = ((RakServerChannel) ctx.channel()).getClientAddress(address); + if (clientAddress == null) { + // Drop silently when no PROXY header was resolved. Responding with the raw sender (the + // proxy's IP) would leak that address to ping passthrough. GeyserServer#onConnectionRequest + // surfaces the misconfiguration via a warning when a client actually tries to connect. + return; + } RakPong pong = msg.reply(guid, this.server.onQuery(ctx.channel(), clientAddress).toByteBuf()); ctx.writeAndFlush(pong); }