diff --git a/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java b/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
index 126847cd5..d659f3d37 100644
--- a/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
+++ b/core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java
@@ -43,32 +43,6 @@ public class GeyserServerInitializer extends BedrockServerInitializer {
         this.geyser = geyser;
     }
 
-    //TODO
-    /*
-    @Override
-    public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
-        List<String> allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs();
-        if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) {
-            boolean isWhitelistedIP = false;
-            for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) {
-                if (matcher.matches(inetSocketAddress.getAddress())) {
-                    isWhitelistedIP = true;
-                    break;
-                }
-            }
-
-            if (!isWhitelistedIP) {
-                return false;
-            }
-        }
-
-        String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
-        geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
-        return true;
-    }
-
-     */
-
     @Override
     public void initSession(@Nonnull BedrockServerSession bedrockServerSession) {
         try {
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 5e1c1331f..1d8605504 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
@@ -44,6 +44,7 @@ import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandle
 import org.cloudburstmc.protocol.bedrock.BedrockPong;
 import org.geysermc.geyser.GeyserImpl;
 import org.geysermc.geyser.configuration.GeyserConfiguration;
+import org.geysermc.geyser.network.CIDRMatcher;
 import org.geysermc.geyser.network.GameProtocol;
 import org.geysermc.geyser.network.GeyserServerInitializer;
 import org.geysermc.geyser.ping.GeyserPingInfo;
@@ -53,6 +54,7 @@ import org.geysermc.geyser.translator.text.MessageTranslator;
 
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.IntFunction;
 
@@ -95,7 +97,9 @@ public final class GeyserServer {
         });
 
         // Add our ping handler
-        this.future.channel().pipeline().addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
+        this.future.channel().pipeline()
+                .addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
+                .addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));
         return future;
     }
 
@@ -126,6 +130,27 @@ public final class GeyserServer {
                 .childHandler(new GeyserServerInitializer(this.geyser));
     }
 
+    public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
+        List<String> allowedProxyIPs = geyser.getConfig().getBedrock().getProxyProtocolWhitelistedIPs();
+        if (geyser.getConfig().getBedrock().isEnableProxyProtocol() && !allowedProxyIPs.isEmpty()) {
+            boolean isWhitelistedIP = false;
+            for (CIDRMatcher matcher : geyser.getConfig().getBedrock().getWhitelistedIPsMatchers()) {
+                if (matcher.matches(inetSocketAddress.getAddress())) {
+                    isWhitelistedIP = true;
+                    break;
+                }
+            }
+
+            if (!isWhitelistedIP) {
+                return false;
+            }
+        }
+
+        String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
+        geyser.getLogger().info(GeyserLocale.getLocaleStringLog("geyser.network.attempt_connect", ip));
+        return true;
+    }
+
     public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
         if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
             String ip = geyser.getConfig().isLogPlayerIpAddresses() ? inetSocketAddress.toString() : "<IP address withheld>";
diff --git a/core/src/main/java/org/geysermc/geyser/network/netty/RakConnectionRequestHandler.java b/core/src/main/java/org/geysermc/geyser/network/netty/RakConnectionRequestHandler.java
new file mode 100644
index 000000000..2aa3cd6b9
--- /dev/null
+++ b/core/src/main/java/org/geysermc/geyser/network/netty/RakConnectionRequestHandler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2019-2023 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;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.socket.DatagramPacket;
+import lombok.RequiredArgsConstructor;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
+
+import java.net.InetSocketAddress;
+
+import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_CONNECTION_BANNED;
+import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_OPEN_CONNECTION_REQUEST_1;
+
+@ChannelHandler.Sharable
+@RequiredArgsConstructor
+public class RakConnectionRequestHandler extends ChannelInboundHandlerAdapter {
+    public static final String NAME = "rak-connection-request-handler";
+
+    private final GeyserServer server;
+
+    @Override
+    public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) throws Exception {
+        if (!(msg instanceof DatagramPacket packet)) {
+            ctx.fireChannelRead(msg);
+            return;
+        }
+
+        ByteBuf buf = packet.content();
+        if (!buf.isReadable()) {
+            return; // No packet ID
+        }
+
+        boolean readableMagic = true;
+        int startIndex = buf.readerIndex();
+        try {
+            int packetId = buf.readUnsignedByte();
+            if (packetId == ID_OPEN_CONNECTION_REQUEST_1) {
+                ByteBuf magicBuf = ctx.channel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
+                if (!buf.isReadable(magicBuf.readableBytes()) || !ByteBufUtil.equals(buf.readSlice(magicBuf.readableBytes()), magicBuf)) {
+                    readableMagic = false;
+                }
+            } else {
+                readableMagic = false;
+            }
+        } finally {
+            buf.readerIndex(startIndex);
+        }
+
+        if (!readableMagic) {
+            ctx.fireChannelRead(msg);
+            return;
+        }
+
+        ByteBuf magicBuf = ctx.channel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC);
+        long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID);
+
+        if (!this.server.onConnectionRequest(packet.sender())) {
+            this.sendConnectionBanned(ctx, packet.sender(), magicBuf, guid);
+        } else {
+            ctx.fireChannelRead(msg);
+        }
+    }
+
+    private void sendConnectionBanned(ChannelHandlerContext ctx, InetSocketAddress recipient, ByteBuf magicBuf, long guid) {
+        ByteBuf buffer = ctx.alloc().ioBuffer(25, 25);
+        buffer.writeByte(ID_CONNECTION_BANNED);
+        buffer.writeBytes(magicBuf, magicBuf.readerIndex(), magicBuf.readableBytes());
+        buffer.writeLong(guid);
+        ctx.writeAndFlush(new DatagramPacket(buffer, recipient));
+    }
+}