diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java index 1f330bd218..1141c51616 100644 --- a/api/src/main/java/net/md_5/bungee/api/ProxyServer.java +++ b/api/src/main/java/net/md_5/bungee/api/ProxyServer.java @@ -16,6 +16,7 @@ import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.plugin.PluginManager; import net.md_5.bungee.api.scheduler.TaskScheduler; +import net.md_5.bungee.protocol.channel.BungeeChannelInitializer; public abstract class ProxyServer { @@ -311,4 +312,56 @@ public static void setInstance(ProxyServer instance) */ public abstract Title createTitle(); + /** + * Get the unsafe methods of this class. + * + * @return the unsafe method interface + */ + public abstract Unsafe unsafe(); + + public interface Unsafe + { + /** + * Gets the frontend channel initializer + * + * @return the frontend channel initializer + */ + BungeeChannelInitializer getFrontendChannelInitializer(); + + /** + * Gets the backend channel initializer + * + * @return the backend channel initializer + */ + BungeeChannelInitializer getBackendChannelInitializer(); + + /** + * Gets the server info channel initializer + * + * @return the server info channel initializer + */ + BungeeChannelInitializer getServerInfoChannelInitializer(); + + /** + * Set the frontend channel initializer of this proxy + * + * @param channelInitializer the frontend channelInitializer to set + */ + void setFrontendChannelInitializer(BungeeChannelInitializer channelInitializer); + + /** + * Set the backend channel initializer of this proxy + * + * @param channelInitializer the backend channelInitializer to set + */ + void setBackendChannelInitializer(BungeeChannelInitializer channelInitializer); + + /** + * Set the server info channel initializer of this proxy + * + * @param channelInitializer the server info channelInitializer to set + */ + void setServerInfoChannelInitializer(BungeeChannelInitializer channelInitializer); + } + } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/channel/BungeeChannelInitializer.java b/protocol/src/main/java/net/md_5/bungee/protocol/channel/BungeeChannelInitializer.java new file mode 100644 index 0000000000..64de20607e --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/channel/BungeeChannelInitializer.java @@ -0,0 +1,49 @@ +package net.md_5.bungee.protocol.channel; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * This class hold a netty channel initializer that calls the given {@link ChannelAcceptor} + * Use {@link BungeeChannelInitializer#create(ChannelAcceptor)} to create a new instance + *

+ * Please note that this API is unsafe and doesn't provide any guarantees about the stability of the + * channel pipeline. Use at your own risk. + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public abstract class BungeeChannelInitializer +{ + public abstract ChannelAcceptor getChannelAcceptor(); + public abstract ChannelInitializer getChannelInitializer(); + + /** + * Creates a new instance of BungeeChannelInitializer + * + * @param acceptor the {@link ChannelAcceptor} that will accept the channel and initializer the pipeline + * @return {@link BungeeChannelInitializer} containing a cached {@link ChannelInitializer} that will call the acceptor + */ + public static BungeeChannelInitializer create(ChannelAcceptor acceptor) + { + return new BungeeChannelInitializer() + { + @Getter + private final ChannelAcceptor channelAcceptor = acceptor; + + @Getter // cache the ChannelInitializer + private final ChannelInitializer channelInitializer = new ChannelInitializer() + { + @Override + protected void initChannel(Channel channel) throws Exception + { + if ( !getChannelAcceptor().accept( channel ) ) + { + channel.close(); + } + } + }; + }; + } +} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/channel/ChannelAcceptor.java b/protocol/src/main/java/net/md_5/bungee/protocol/channel/ChannelAcceptor.java new file mode 100644 index 0000000000..b67ce23414 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/channel/ChannelAcceptor.java @@ -0,0 +1,15 @@ +package net.md_5.bungee.protocol.channel; + +import io.netty.channel.Channel; + +@FunctionalInterface +public interface ChannelAcceptor +{ + /** + * Inside this method the pipeline should be initialized. + * + * @param channel the channel to be accepted and initialized + * @return if the channel was accepted + */ + boolean accept(Channel channel); +} diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index e7f3f09af9..17af51455e 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -95,6 +95,7 @@ import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.ProtocolConstants; +import net.md_5.bungee.protocol.channel.BungeeChannelInitializer; import net.md_5.bungee.protocol.packet.PluginMessage; import net.md_5.bungee.query.RemoteQuery; import net.md_5.bungee.scheduler.BungeeScheduler; @@ -188,6 +189,21 @@ public static BungeeCord getInstance() return (BungeeCord) ProxyServer.getInstance(); } + private final Unsafe unsafe = new Unsafe() + { + @Getter + @Setter + private BungeeChannelInitializer frontendChannelInitializer; + + @Getter + @Setter + private BungeeChannelInitializer backendChannelInitializer; + + @Getter + @Setter + private BungeeChannelInitializer serverInfoChannelInitializer; + }; + @SuppressFBWarnings("DM_DEFAULT_ENCODING") public BungeeCord() throws IOException { @@ -360,7 +376,7 @@ public void operationComplete(ChannelFuture future) throws Exception .channel( PipelineUtils.getServerChannel( info.getSocketAddress() ) ) .option( ChannelOption.SO_REUSEADDR, true ) // TODO: Move this elsewhere! .childAttr( PipelineUtils.LISTENER, info ) - .childHandler( PipelineUtils.SERVER_CHILD ) + .childHandler( ProxyServer.getInstance().unsafe().getFrontendChannelInitializer().getChannelInitializer() ) .group( eventLoops ) .localAddress( info.getSocketAddress() ) .bind().addListener( listener ); @@ -831,4 +847,10 @@ public Title createTitle() { return new BungeeTitle(); } + + @Override + public Unsafe unsafe() + { + return unsafe; + } } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java index 671cf96ff2..7700f1d6ca 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -186,7 +186,7 @@ public void operationComplete(ChannelFuture future) throws Exception new Bootstrap() .channel( PipelineUtils.getChannel( socketAddress ) ) .group( BungeeCord.getInstance().eventLoops ) - .handler( PipelineUtils.BASE_SERVERSIDE ) + .handler( ProxyServer.getInstance().unsafe().getServerInfoChannelInitializer().getChannelInitializer() ) .option( ChannelOption.CONNECT_TIMEOUT_MILLIS, BungeeCord.getInstance().getConfig().getRemotePingTimeout() ) .remoteAddress( socketAddress ) .connect() diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index b58a01c6dd..8f52c50eb7 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -104,6 +104,7 @@ public void exception(Throwable t) throws Exception @Override public void connected(ChannelWrapper channel) throws Exception { + channel.setVersion( user.getPendingConnection().getVersion() ); this.ch = channel; this.handshakeHandler = new ForgeServerHandler( user, ch, target ); diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index 8f121aa952..a76f46f0f7 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -3,10 +3,8 @@ import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.util.internal.PlatformDependent; import java.net.InetSocketAddress; @@ -50,8 +48,6 @@ import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.protocol.DefinedPacket; -import net.md_5.bungee.protocol.MinecraftDecoder; -import net.md_5.bungee.protocol.MinecraftEncoder; import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; @@ -362,17 +358,6 @@ private void connect0(final ServerConnectRequest request) pendingConnects.add( target ); - ChannelInitializer initializer = new ChannelInitializer() - { - @Override - protected void initChannel(Channel ch) throws Exception - { - PipelineUtils.BASE_SERVERSIDE.initChannel( ch ); - ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); - ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) ); - ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) ); - } - }; ChannelFutureListener listener = new ChannelFutureListener() { @Override @@ -401,13 +386,16 @@ public void operationComplete(ChannelFuture future) throws Exception { sendMessage( bungee.getTranslation( "fallback_kick", connectionFailMessage( future.cause() ) ) ); } + } else + { + future.channel().pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) ); } } }; Bootstrap b = new Bootstrap() .channel( PipelineUtils.getChannel( target.getAddress() ) ) .group( ch.getHandle().eventLoop() ) - .handler( initializer ) + .handler( ProxyServer.getInstance().unsafe().getBackendChannelInitializer().getChannelInitializer() ) .option( ChannelOption.CONNECT_TIMEOUT_MILLIS, request.getConnectTimeout() ) .remoteAddress( target.getAddress() ); // Windows is bugged, multi homed users will just have to live with random connecting IPs diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index 2446f48919..cc1d1110e5 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -4,7 +4,6 @@ import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.Channel; import io.netty.channel.ChannelException; -import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; @@ -51,33 +50,33 @@ import net.md_5.bungee.protocol.Varint21FrameDecoder; import net.md_5.bungee.protocol.Varint21LengthFieldExtraBufPrepender; import net.md_5.bungee.protocol.Varint21LengthFieldPrepender; +import net.md_5.bungee.protocol.channel.BungeeChannelInitializer; +import net.md_5.bungee.protocol.channel.ChannelAcceptor; public class PipelineUtils { public static final AttributeKey LISTENER = AttributeKey.valueOf( "ListerInfo" ); - public static final ChannelInitializer SERVER_CHILD = new ChannelInitializer() + + private static void setChannelInitializerHolders() { - @Override - protected void initChannel(Channel ch) throws Exception + ProxyServer.getInstance().unsafe().setFrontendChannelInitializer( BungeeChannelInitializer.create( ch -> { SocketAddress remoteAddress = ( ch.remoteAddress() == null ) ? ch.parent().localAddress() : ch.remoteAddress(); if ( BungeeCord.getInstance().getConnectionThrottle() != null && BungeeCord.getInstance().getConnectionThrottle().throttle( remoteAddress ) ) { - ch.close(); - return; + return false; } ListenerInfo listener = ch.attr( LISTENER ).get(); if ( BungeeCord.getInstance().getPluginManager().callEvent( new ClientConnectEvent( remoteAddress, listener ) ).isCancelled() ) { - ch.close(); - return; + return false; } - BASE.initChannel( ch ); + BASE.accept( ch ); ch.pipeline().addBefore( FRAME_DECODER, LEGACY_DECODER, new LegacyDecoder() ); ch.pipeline().addAfter( FRAME_DECODER, PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) ); ch.pipeline().addAfter( FRAME_PREPENDER, PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) ); @@ -88,10 +87,22 @@ protected void initChannel(Channel ch) throws Exception { ch.pipeline().addFirst( new HAProxyMessageDecoder() ); } - } - }; - public static final Base BASE = new Base( false ); - public static final Base BASE_SERVERSIDE = new Base( true ); + return true; + } ) ); + + ProxyServer.getInstance().unsafe().setBackendChannelInitializer( BungeeChannelInitializer.create( ch -> + { + PipelineUtils.BASE_SERVERSIDE.accept( ch ); + ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, ProxyServer.getInstance().getProtocolVersion() ) ); + ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, ProxyServer.getInstance().getProtocolVersion() ) ); + return true; + } ) ); + + ProxyServer.getInstance().unsafe().setServerInfoChannelInitializer( BungeeChannelInitializer.create( BASE_SERVERSIDE ) ); + } + + private static final ChannelAcceptor BASE = new Base( false ); + private static final ChannelAcceptor BASE_SERVERSIDE = new Base( true ); private static final KickStringWriter legacyKicker = new KickStringWriter(); private static final Varint21LengthFieldExtraBufPrepender serverFramePrepender = new Varint21LengthFieldExtraBufPrepender(); public static final String TIMEOUT_HANDLER = "timeout"; @@ -137,6 +148,8 @@ protected void initChannel(Channel ch) throws Exception } } } + + setChannelInitializerHolders(); } public static EventLoopGroup newEventLoopGroup(int threads, ThreadFactory factory) @@ -179,13 +192,13 @@ public static Class getDatagramChannel() @NoArgsConstructor // for backwards compatibility @AllArgsConstructor - public static final class Base extends ChannelInitializer + public static final class Base implements ChannelAcceptor { private boolean toServer = false; @Override - public void initChannel(Channel ch) throws Exception + public boolean accept(Channel ch) { try { @@ -204,6 +217,7 @@ public void initChannel(Channel ch) throws Exception ch.pipeline().addLast( FRAME_PREPENDER, ( toServer ) ? serverFramePrepender : new Varint21LengthFieldPrepender() ); ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() ); + return true; } } }