--- a/net/minecraft/server/network/ServerConnectionListener.java
+++ b/net/minecraft/server/network/ServerConnectionListener.java
@@ -37,137 +37,233 @@
 import javax.annotation.Nullable;
 import net.minecraft.CrashReport;
 import net.minecraft.ReportedException;
+import net.minecraft.network.BandwidthDebugMonitor;
 import net.minecraft.network.Connection;
 import net.minecraft.network.PacketSendListener;
 import net.minecraft.network.RateKickingConnection;
 import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
 import net.minecraft.network.protocol.PacketFlow;
 import net.minecraft.network.protocol.common.ClientboundDisconnectPacket;
 import net.minecraft.server.MinecraftServer;
 import org.slf4j.Logger;
 
 public class ServerConnectionListener {
+
     private static final Logger LOGGER = LogUtils.getLogger();
-    public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(
-        () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).build())
-    );
-    public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(
-        () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build())
-    );
+    public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(() -> {
+        return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
+    });
+    public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> {
+        return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
+    });
     final MinecraftServer server;
     public volatile boolean running;
     private final List<ChannelFuture> channels = Collections.synchronizedList(Lists.newArrayList());
     final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList());
+    // Paper start - prevent blocking on adding a new connection while the server is ticking
+    private final java.util.Queue<Connection> pending = new java.util.concurrent.ConcurrentLinkedQueue<>();
+    private static final boolean disableFlushConsolidation = Boolean.getBoolean("Paper.disableFlushConsolidate"); // Paper - Optimize network
+    private final void addPending() {
+        Connection connection;
+        while ((connection = pending.poll()) != null) {
+            connections.add(connection);
+            connection.isPending = false; // Paper - Optimize network
+        }
+    }
+    // Paper end - prevent blocking on adding a new connection while the server is ticking
 
     public ServerConnectionListener(MinecraftServer server) {
         this.server = server;
         this.running = true;
     }
 
+    // Paper start - Unix domain socket support
     public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
+        bind(new java.net.InetSocketAddress(address, port));
+    }
+    public void bind(java.net.SocketAddress address) throws IOException {
+    // Paper end - Unix domain socket support
+        List list = this.channels;
+
         synchronized (this.channels) {
-            Class<? extends ServerSocketChannel> class_;
-            EventLoopGroup eventLoopGroup;
+            Class oclass;
+            EventLoopGroup eventloopgroup;
+
             if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
-                class_ = EpollServerSocketChannel.class;
-                eventLoopGroup = SERVER_EPOLL_EVENT_GROUP.get();
-                LOGGER.info("Using epoll channel type");
+                // Paper start - Unix domain socket support
+                if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
+                    oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
+                } else {
+                oclass = EpollServerSocketChannel.class;
+                }
+                // Paper end - Unix domain socket support
+                eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get();
+                ServerConnectionListener.LOGGER.info("Using epoll channel type");
             } else {
-                class_ = NioServerSocketChannel.class;
-                eventLoopGroup = SERVER_EVENT_GROUP.get();
-                LOGGER.info("Using default channel type");
+                oclass = NioServerSocketChannel.class;
+                eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EVENT_GROUP.get();
+                ServerConnectionListener.LOGGER.info("Using default channel type");
             }
 
-            this.channels.add(new ServerBootstrap().channel(class_).childHandler(new ChannelInitializer<Channel>() {
+            // Paper start - Use Velocity cipher
+            ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.compress.getLoadedVariant() + " compression from Velocity.");
+            ServerConnectionListener.LOGGER.info("Paper: Using " + com.velocitypowered.natives.util.Natives.cipher.getLoadedVariant() + " cipher from Velocity.");
+            // Paper end - Use Velocity cipher
+
+            this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
                 protected void initChannel(Channel channel) {
                     try {
                         channel.config().setOption(ChannelOption.TCP_NODELAY, true);
-                    } catch (ChannelException var5) {
+                    } catch (ChannelException channelexception) {
+                        ;
                     }
 
-                    ChannelPipeline channelPipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30));
+                    if (!disableFlushConsolidation) channel.pipeline().addFirst(new io.netty.handler.flush.FlushConsolidationHandler()); // Paper - Optimize network
+                    ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30));
+
                     if (ServerConnectionListener.this.server.repliesToStatus()) {
-                        channelPipeline.addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
+                        channelpipeline.addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
                     }
 
-                    Connection.configureSerialization(channelPipeline, PacketFlow.SERVERBOUND, false, null);
-                    int i = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
-                    Connection connection = (Connection)(i > 0 ? new RateKickingConnection(i) : new Connection(PacketFlow.SERVERBOUND));
-                    ServerConnectionListener.this.connections.add(connection);
-                    connection.configurePacketHandler(channelPipeline);
-                    connection.setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection));
+                    Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null);
+                    int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
+                    Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error
+
+                    //ServerConnectionListener.this.connections.add(object); // Paper
+                    // Paper start - Add support for Proxy Protocol
+                    if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
+                        channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
+                        channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
+                            @Override
+                            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+                                if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
+                                    if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
+                                        String realaddress = message.sourceAddress();
+                                        int realport = message.sourcePort();
+
+                                        SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
+
+                                        Connection connection = (Connection) channel.pipeline().get("packet_handler");
+                                        connection.address = socketaddr;
+
+                                        // Paper start - Add API to get player's proxy address
+                                        final String proxyAddress = message.destinationAddress();
+                                        final int proxyPort = message.destinationPort();
+
+                                        connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort);
+                                        // Paper end - Add API to get player's proxy address
+                                    }
+                                } else {
+                                    super.channelRead(ctx, msg);
+                                }
+                            }
+                        });
+                    }
+                    // Paper end - Add support for proxy protocol
+                    pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking
+                    ((Connection) object).configurePacketHandler(channelpipeline);
+                    ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
+                    io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
                 }
-            }).group(eventLoopGroup).localAddress(address, port).bind().syncUninterruptibly());
+            }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support
+        }
+    }
+
+    // CraftBukkit start
+    public void acceptConnections() {
+        synchronized (this.channels) {
+            for (ChannelFuture future : this.channels) {
+                future.channel().config().setAutoRead(true);
+            }
         }
     }
+    // CraftBukkit end
 
     public SocketAddress startMemoryChannel() {
-        ChannelFuture channelFuture;
+        List list = this.channels;
+        ChannelFuture channelfuture;
+
         synchronized (this.channels) {
-            channelFuture = new ServerBootstrap()
-                .channel(LocalServerChannel.class)
-                .childHandler(
-                    new ChannelInitializer<Channel>() {
-                        protected void initChannel(Channel channel) {
-                            Connection connection = new Connection(PacketFlow.SERVERBOUND);
-                            connection.setListenerForServerboundHandshake(
-                                new MemoryServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
-                            );
-                            ServerConnectionListener.this.connections.add(connection);
-                            ChannelPipeline channelPipeline = channel.pipeline();
-                            Connection.configureInMemoryPipeline(channelPipeline, PacketFlow.SERVERBOUND);
-                            connection.configurePacketHandler(channelPipeline);
-                        }
-                    }
-                )
-                .group(SERVER_EVENT_GROUP.get())
-                .localAddress(LocalAddress.ANY)
-                .bind()
-                .syncUninterruptibly();
-            this.channels.add(channelFuture);
+            channelfuture = ((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(LocalServerChannel.class)).childHandler(new ChannelInitializer<Channel>() {
+                protected void initChannel(Channel channel) {
+                    Connection networkmanager = new Connection(PacketFlow.SERVERBOUND);
+
+                    networkmanager.setListenerForServerboundHandshake(new MemoryServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, networkmanager));
+                    ServerConnectionListener.this.connections.add(networkmanager);
+                    ChannelPipeline channelpipeline = channel.pipeline();
+
+                    Connection.configureInMemoryPipeline(channelpipeline, PacketFlow.SERVERBOUND);
+                    networkmanager.configurePacketHandler(channelpipeline);
+                }
+            }).group((EventLoopGroup) ServerConnectionListener.SERVER_EVENT_GROUP.get()).localAddress(LocalAddress.ANY)).bind().syncUninterruptibly();
+            this.channels.add(channelfuture);
         }
 
-        return channelFuture.channel().localAddress();
+        return channelfuture.channel().localAddress();
     }
 
     public void stop() {
         this.running = false;
+        Iterator iterator = this.channels.iterator();
+
+        while (iterator.hasNext()) {
+            ChannelFuture channelfuture = (ChannelFuture) iterator.next();
 
-        for (ChannelFuture channelFuture : this.channels) {
             try {
-                channelFuture.channel().close().sync();
-            } catch (InterruptedException var4) {
-                LOGGER.error("Interrupted whilst closing channel");
+                channelfuture.channel().close().sync();
+            } catch (InterruptedException interruptedexception) {
+                ServerConnectionListener.LOGGER.error("Interrupted whilst closing channel");
             }
         }
+
     }
 
     public void tick() {
+        List list = this.connections;
+
         synchronized (this.connections) {
+            // Spigot Start
+            this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking
+            // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order
+            if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 )
+            {
+                Collections.shuffle( this.connections );
+            }
+            // Spigot End
             Iterator<Connection> iterator = this.connections.iterator();
 
             while (iterator.hasNext()) {
-                Connection connection = iterator.next();
-                if (!connection.isConnecting()) {
-                    if (connection.isConnected()) {
+                Connection networkmanager = (Connection) iterator.next();
+
+                if (!networkmanager.isConnecting()) {
+                    if (networkmanager.isConnected()) {
                         try {
-                            connection.tick();
-                        } catch (Exception var7) {
-                            if (connection.isMemoryConnection()) {
-                                throw new ReportedException(CrashReport.forThrowable(var7, "Ticking memory connection"));
+                            networkmanager.tick();
+                        } catch (Exception exception) {
+                            if (networkmanager.isMemoryConnection()) {
+                                throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection"));
                             }
 
-                            LOGGER.warn("Failed to handle packet for {}", connection.getLoggableAddress(this.server.logIPs()), var7);
-                            Component component = Component.literal("Internal server error");
-                            connection.send(new ClientboundDisconnectPacket(component), PacketSendListener.thenRun(() -> connection.disconnect(component)));
-                            connection.setReadOnly();
+                            ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", networkmanager.getLoggableAddress(this.server.logIPs()), exception);
+                            MutableComponent ichatmutablecomponent = Component.literal("Internal server error");
+
+                            networkmanager.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> {
+                                networkmanager.disconnect(ichatmutablecomponent);
+                            }));
+                            networkmanager.setReadOnly();
                         }
                     } else {
+                        // Spigot Start
+                        // Fix a race condition where a NetworkManager could be unregistered just before connection.
+                        if (networkmanager.preparing) continue;
+                        // Spigot End
                         iterator.remove();
-                        connection.handleDisconnection();
+                        networkmanager.handleDisconnection();
                     }
                 }
             }
+
         }
     }
 
@@ -179,7 +275,8 @@
         return this.connections;
     }
 
-    static class LatencySimulator extends ChannelInboundHandlerAdapter {
+    private static class LatencySimulator extends ChannelInboundHandlerAdapter {
+
         private static final Timer TIMER = new HashedWheelTimer();
         private final int delay;
         private final int jitter;
@@ -190,22 +287,25 @@
             this.jitter = extraDelay;
         }
 
-        public void channelRead(ChannelHandlerContext channelHandlerContext, Object object) {
-            this.delayDownstream(channelHandlerContext, object);
+        public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) {
+            this.delayDownstream(channelhandlercontext, object);
         }
 
         private void delayDownstream(ChannelHandlerContext ctx, Object msg) {
-            int i = this.delay + (int)(Math.random() * (double)this.jitter);
+            int i = this.delay + (int) (Math.random() * (double) this.jitter);
+
             this.queuedMessages.add(new ServerConnectionListener.LatencySimulator.DelayedMessage(ctx, msg));
-            TIMER.newTimeout(this::onTimeout, (long)i, TimeUnit.MILLISECONDS);
+            ServerConnectionListener.LatencySimulator.TIMER.newTimeout(this::onTimeout, (long) i, TimeUnit.MILLISECONDS);
         }
 
         private void onTimeout(Timeout timeout) {
-            ServerConnectionListener.LatencySimulator.DelayedMessage delayedMessage = this.queuedMessages.remove(0);
-            delayedMessage.ctx.fireChannelRead(delayedMessage.msg);
+            ServerConnectionListener.LatencySimulator.DelayedMessage serverconnection_latencysimulator_delayedmessage = (ServerConnectionListener.LatencySimulator.DelayedMessage) this.queuedMessages.remove(0);
+
+            serverconnection_latencysimulator_delayedmessage.ctx.fireChannelRead(serverconnection_latencysimulator_delayedmessage.msg);
         }
 
-        static class DelayedMessage {
+        private static class DelayedMessage {
+
             public final ChannelHandlerContext ctx;
             public final Object msg;
 
