Implemented HAProxy protocol for backend connections

This commit is contained in:
RaphiMC 2023-04-05 15:05:46 +02:00
parent af3ab5f6eb
commit 038782a60d
3 changed files with 29 additions and 6 deletions

View File

@ -44,6 +44,7 @@ public class Options {
public static boolean ONLINE_MODE; public static boolean ONLINE_MODE;
public static boolean OPENAUTHMOD_AUTH; public static boolean OPENAUTHMOD_AUTH;
public static boolean BETACRAFT_AUTH; public static boolean BETACRAFT_AUTH;
public static URI PROXY_URL; // Example: type://address:port or type://username:password@address:port
// GUI only config options // GUI only config options
public static Account MC_ACCOUNT; public static Account MC_ACCOUNT;
@ -56,7 +57,7 @@ public class Options {
public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass public static boolean INTERNAL_SRV_MODE; // Example: ip\7port\7version\7mppass
public static boolean LOCAL_SOCKET_AUTH; public static boolean LOCAL_SOCKET_AUTH;
public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip public static String RESOURCE_PACK_URL; // Example: http://example.com/resourcepack.zip
public static URI PROXY_URL; // Example: type://address:port or type://username:password@address:port public static boolean HAPROXY_PROTOCOL;
public static void parse(final String[] args) throws IOException { public static void parse(final String[] args) throws IOException {
final OptionParser parser = new OptionParser(); final OptionParser parser = new OptionParser();
@ -76,6 +77,7 @@ public class Options {
final OptionSpec<Void> betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication servers for classic"); final OptionSpec<Void> betaCraftAuth = parser.accepts("betacraft_auth", "Use BetaCraft authentication servers for classic");
final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which all connecting clients can optionally download").withRequiredArg().ofType(String.class); final OptionSpec<String> resourcePackUrl = parser.acceptsAll(asList("resource_pack_url", "resource_pack", "rpu", "rp"), "URL of a resource pack which all connecting clients can optionally download").withRequiredArg().ofType(String.class);
final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for TCP connections").withRequiredArg().ofType(String.class); final OptionSpec<String> proxyUrl = parser.acceptsAll(asList("proxy_url", "proxy"), "URL of a SOCKS(4/5)/HTTP(S) proxy which will be used for TCP connections").withRequiredArg().ofType(String.class);
final OptionSpec<Void> haProxyProtocol = parser.acceptsAll(asList("haproxy-protocol", "haproxy"), "Send HAProxy protocol messages to the backend server");
PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser)); PluginManager.EVENT_MANAGER.call(new PreOptionsParseEvent(parser));
final OptionSet options = parser.parse(args); final OptionSet options = parser.parse(args);
@ -113,6 +115,7 @@ public class Options {
System.exit(1); System.exit(1);
} }
} }
HAPROXY_PROTOCOL = options.has(haProxyProtocol);
PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options)); PluginManager.EVENT_MANAGER.call(new PostOptionsParseEvent(options));
} }

View File

@ -26,6 +26,7 @@ import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.haproxy.*;
import net.raphimc.netminecraft.constants.ConnectionState; import net.raphimc.netminecraft.constants.ConnectionState;
import net.raphimc.netminecraft.constants.MCPackets; import net.raphimc.netminecraft.constants.MCPackets;
import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.constants.MCPipeline;
@ -61,6 +62,8 @@ import net.raphimc.viaproxy.util.logging.Logger;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnresolvedAddressException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -69,6 +72,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -239,8 +243,6 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "[" + clientVersion.getName() + " <-> " + serverVersion.getName() + "] Connecting to " + serverAddress.getAddress() + ":" + serverAddress.getPort()); Logger.u_info("connect", this.proxyConnection.getC2P().remoteAddress(), this.proxyConnection.getGameProfile(), "[" + clientVersion.getName() + " <-> " + serverVersion.getName() + "] Connecting to " + serverAddress.getAddress() + ":" + serverAddress.getPort());
try { try {
this.proxyConnection.connectToServer(serverAddress, serverVersion); this.proxyConnection.connectToServer(serverAddress, serverVersion);
this.proxyConnection.getChannel().writeAndFlush(new C2SHandshakePacket(clientVersion.getOriginalVersion(), serverAddress.getAddress(), serverAddress.getPort(), packet.intendedState)).await().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
this.proxyConnection.setConnectionState(packet.intendedState);
} catch (Throwable e) { } catch (Throwable e) {
if (e instanceof ConnectException || e instanceof UnresolvedAddressException) { // Trust me, this is not always false if (e instanceof ConnectException || e instanceof UnresolvedAddressException) { // Trust me, this is not always false
this.proxyConnection.kickClient("§cCould not connect to the backend server!\n§cTry again in a few seconds."); this.proxyConnection.kickClient("§cCould not connect to the backend server!\n§cTry again in a few seconds.");
@ -249,6 +251,19 @@ public class Client2ProxyHandler extends SimpleChannelInboundHandler<IPacket> {
this.proxyConnection.kickClient("§cAn error occurred while connecting to the backend server: " + e.getMessage() + "\n§cCheck the console for more information."); this.proxyConnection.kickClient("§cAn error occurred while connecting to the backend server: " + e.getMessage() + "\n§cCheck the console for more information.");
} }
} }
if (Options.HAPROXY_PROTOCOL) {
final InetSocketAddress sourceAddress = (InetSocketAddress) this.proxyConnection.getC2P().remoteAddress();
final InetSocketAddress targetAddress = (InetSocketAddress) this.proxyConnection.getChannel().remoteAddress();
final HAProxyProxiedProtocol protocol = sourceAddress.getAddress() instanceof Inet4Address ? HAProxyProxiedProtocol.TCP4 : HAProxyProxiedProtocol.TCP6;
final HAProxyTLV tlv = new HAProxyTLV((byte) 0xE0, Unpooled.buffer().writeInt(clientVersion.getOriginalVersion()));
final HAProxyMessage haProxyMessage = new HAProxyMessage(HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, protocol, sourceAddress.getAddress().getHostAddress(), targetAddress.getAddress().getHostAddress(), sourceAddress.getPort(), targetAddress.getPort(), Collections.singletonList(tlv));
this.proxyConnection.getChannel().writeAndFlush(haProxyMessage).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
this.proxyConnection.getChannel().writeAndFlush(new C2SHandshakePacket(clientVersion.getOriginalVersion(), serverAddress.getAddress(), serverAddress.getPort(), packet.intendedState)).await().addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
this.proxyConnection.setConnectionState(packet.intendedState);
} }
private void handleLoginHello(C2SLoginHelloPacket1_7 packet) throws NoSuchAlgorithmException, InvalidKeySpecException, AuthenticationException { private void handleLoginHello(C2SLoginHelloPacket1_7 packet) throws NoSuchAlgorithmException, InvalidKeySpecException, AuthenticationException {

View File

@ -22,6 +22,7 @@ import com.viaversion.viaversion.connection.UserConnectionImpl;
import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.handler.codec.haproxy.HAProxyMessageEncoder;
import io.netty.handler.proxy.HttpProxyHandler; import io.netty.handler.proxy.HttpProxyHandler;
import io.netty.handler.proxy.ProxyHandler; import io.netty.handler.proxy.ProxyHandler;
import io.netty.handler.proxy.Socks4ProxyHandler; import io.netty.handler.proxy.Socks4ProxyHandler;
@ -29,11 +30,11 @@ import io.netty.handler.proxy.Socks5ProxyHandler;
import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.netminecraft.constants.MCPipeline;
import net.raphimc.netminecraft.netty.connection.MinecraftChannelInitializer; import net.raphimc.netminecraft.netty.connection.MinecraftChannelInitializer;
import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil; import net.raphimc.netminecraft.packet.registry.PacketRegistryUtil;
import net.raphimc.viaprotocolhack.util.VersionEnum;
import net.raphimc.viaproxy.cli.options.Options; import net.raphimc.viaproxy.cli.options.Options;
import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.PluginManager;
import net.raphimc.viaproxy.plugins.events.Proxy2ServerChannelInitializeEvent; import net.raphimc.viaproxy.plugins.events.Proxy2ServerChannelInitializeEvent;
import net.raphimc.viaproxy.plugins.events.types.ITyped; import net.raphimc.viaproxy.plugins.events.types.ITyped;
import net.raphimc.viaproxy.protocolhack.impl.ViaProxyVPHPipeline;
import net.raphimc.viaproxy.proxy.session.ProxyConnection; import net.raphimc.viaproxy.proxy.session.ProxyConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@ -53,14 +54,18 @@ public class Proxy2ServerChannelInitializer extends MinecraftChannelInitializer
channel.close(); channel.close();
return; return;
} }
final ProxyConnection proxyConnection = ProxyConnection.fromChannel(channel);
final UserConnection user = new UserConnectionImpl(channel, true); final UserConnection user = new UserConnectionImpl(channel, true);
new ProtocolPipelineImpl(user); new ProtocolPipelineImpl(user);
ProxyConnection.fromChannel(channel).setUserConnection(user); proxyConnection.setUserConnection(user);
if (Options.PROXY_URL != null) { if (Options.PROXY_URL != null && !proxyConnection.getServerVersion().equals(VersionEnum.bedrockLatest)) {
channel.pipeline().addLast("viaproxy-proxy-handler", this.getProxyHandler()); channel.pipeline().addLast("viaproxy-proxy-handler", this.getProxyHandler());
} }
if (Options.HAPROXY_PROTOCOL) {
channel.pipeline().addLast("viaproxy-haproxy-encoder", HAProxyMessageEncoder.INSTANCE);
}
super.initChannel(channel); super.initChannel(channel);
channel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(true)); channel.attr(MCPipeline.PACKET_REGISTRY_ATTRIBUTE_KEY).set(PacketRegistryUtil.getHandshakeRegistry(true));