Rework forwarding

This commit is contained in:
Five (Xer) 2021-06-23 23:12:02 +02:00
parent acf737f35f
commit d7310c91ed
1 changed files with 353 additions and 0 deletions

View File

@ -0,0 +1,353 @@
From ba6e0e6d2baea3310434b8d2915b0d4a0f823ee0 Mon Sep 17 00:00:00 2001
From: "Five (Xer)" <admin@fivepb.me>
Date: Wed, 23 Jun 2021 23:10:02 +0200
Subject: [PATCH] Rework information forwarding
Enable Bungeeguard and Velocity/Modern forwarding modes natively
diff --git a/api/src/main/java/io/github/waterfallmc/waterfall/forwarding/ForwardingMode.java b/api/src/main/java/io/github/waterfallmc/waterfall/forwarding/ForwardingMode.java
new file mode 100644
index 00000000..67ed36d7
--- /dev/null
+++ b/api/src/main/java/io/github/waterfallmc/waterfall/forwarding/ForwardingMode.java
@@ -0,0 +1,11 @@
+package io.github.waterfallmc.waterfall.forwarding;
+
+
+/**
+ * This enum represents the forwarding modes supported by Waterfall.
+ */
+public enum ForwardingMode {
+ BUNGEECORD_LEGACY,
+ BUNGEEGUARD,
+ VELOCITY_MODERN
+}
\ No newline at end of file
diff --git a/api/src/main/java/net/md_5/bungee/Util.java b/api/src/main/java/net/md_5/bungee/Util.java
index 77eb64a1..8ffb37e7 100644
--- a/api/src/main/java/net/md_5/bungee/Util.java
+++ b/api/src/main/java/net/md_5/bungee/Util.java
@@ -8,6 +8,8 @@ import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
+import java.security.SecureRandom;
+import java.util.Random;
import java.util.UUID;
import io.github.waterfallmc.waterfall.utils.Hex;
@@ -106,4 +108,22 @@ public class Util
{
return new UUID( UnsignedLongs.parseUnsignedLong( uuid.substring( 0, 16 ), 16 ), UnsignedLongs.parseUnsignedLong( uuid.substring( 16 ), 16 ) );
}
+
+ // Waterfall start: Forwarding rework
+ /**
+ * Generates an alphanumeric A-Z,a-z,0-9 byte-sequence.
+ *
+ * @param len the length of the sequence
+ * @return a UTF/ASCII compatible alphanumeric byte-sequence
+ */
+ public static byte[] randomAlphanumericSequence(int len){
+ Random random = new SecureRandom();
+ byte[] ret = new byte[len];
+ for(int i = 0; i < len; i++){
+ int seq = random.nextInt(62);
+ ret[i] = (byte) (seq < 10 ? seq + 48 : seq < 36 ? seq + 55 : seq + 61) ;
+ }
+ return ret;
+ }
+ // Waterfall end: Forwarding rework
}
diff --git a/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java b/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java
index d69463f0..59f61e47 100644
--- a/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java
+++ b/api/src/main/java/net/md_5/bungee/api/ProxyConfig.java
@@ -2,6 +2,8 @@ package net.md_5.bungee.api;
import java.util.Collection;
import java.util.Map;
+
+import io.github.waterfallmc.waterfall.forwarding.ForwardingMode;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
@@ -261,4 +263,10 @@ public interface ProxyConfig
* @return {@code true} if tablist rewriting is disabled, {@code false} otherwise
*/
boolean isDisableTabListRewrite();
+
+ /**
+ * Represents the forwarding mode as configured.
+ * @return the mode set in the config
+ */
+ ForwardingMode getForwardingMode();
}
diff --git a/proxy/src/main/java/io/github/waterfallmc/waterfall/conf/WaterfallConfiguration.java b/proxy/src/main/java/io/github/waterfallmc/waterfall/conf/WaterfallConfiguration.java
index 527f310e..a15112af 100644
--- a/proxy/src/main/java/io/github/waterfallmc/waterfall/conf/WaterfallConfiguration.java
+++ b/proxy/src/main/java/io/github/waterfallmc/waterfall/conf/WaterfallConfiguration.java
@@ -1,11 +1,16 @@
package io.github.waterfallmc.waterfall.conf;
import com.google.common.base.Joiner;
+import io.github.waterfallmc.waterfall.forwarding.ForwardingMode;
+import net.md_5.bungee.Util;
import net.md_5.bungee.conf.Configuration;
import net.md_5.bungee.conf.YamlConfig;
import net.md_5.bungee.protocol.ProtocolConstants;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
import java.io.File;
+import java.nio.charset.StandardCharsets;
public class WaterfallConfiguration extends Configuration {
@@ -45,6 +50,11 @@ public class WaterfallConfiguration extends Configuration {
private boolean disableEntityMetadataRewrite = false;
private boolean disableTabListRewrite = false;
+ private ForwardingMode forwardingMode = ForwardingMode.BUNGEECORD_LEGACY;
+ private byte[] forwardingSecret = Util.randomAlphanumericSequence(12);
+
+ private static final Logger logger = LogManager.getLogger(WaterfallConfiguration.class);
+
@Override
public void load() {
super.load();
@@ -58,6 +68,35 @@ public class WaterfallConfiguration extends Configuration {
disableModernTabLimiter = config.getBoolean("disable_modern_tab_limiter", disableModernTabLimiter);
disableEntityMetadataRewrite = config.getBoolean("disable_entity_metadata_rewrite", disableEntityMetadataRewrite);
disableTabListRewrite = config.getBoolean("disable_tab_list_rewrite", disableTabListRewrite);
+ forwardingMode = ForwardingMode.valueOf(config.getString("forwarding_mode", ForwardingMode.BUNGEECORD_LEGACY.toString()).toUpperCase());
+
+ if(super.isIpForward()) {
+ switch(forwardingMode) {
+ case BUNGEECORD_LEGACY:
+ logger.info("Forwarding mode is set to Bungeecord/Legacy forwarding.");
+ logger.info("It is recommended to use another forwarding method to mitigate information spoofing attacks.");
+ break;
+ case BUNGEEGUARD:
+ logger.info("Forwarding mode is set to BungeeGuard forwarding.");
+ logger.info("Please ensure all connected servers make use of BungeeGuard for optimal security.");
+ break;
+ case VELOCITY_MODERN:
+ logger.info("Forwarding mode is set to Modern/Velocity forwarding.");
+ logger.info("If you need to use servers on versions older than 1.13 please use another forwarding type.");
+ break;
+ }
+ } else {
+ logger.warn("Information forwarding (ip-forwarding) is disabled. ");
+ logger.warn("Player UUIDs may not be consistent across the servers.");
+ logger.warn("For the best experience enable ip_forward in the config.yml.");
+ }
+
+ if(config.getString("forwarding_secret", "").isEmpty()) {
+ config.regenerateForwardingSecret();
+ logger.info("A new forwarding secret has been generated. If this was the");
+ logger.info("first start of the proxy please configure forwarding for your network.");
+ }
+ forwardingSecret = config.getString("forwarding_secret", "").getBytes(StandardCharsets.UTF_8);
}
@Override
@@ -94,4 +133,13 @@ public class WaterfallConfiguration extends Configuration {
public boolean isDisableTabListRewrite() {
return disableTabListRewrite;
}
+
+ @Override
+ public ForwardingMode getForwardingMode() {
+ return forwardingMode;
+ }
+
+ public byte[] getForwardingSecret() {
+ return forwardingSecret;
+ }
}
diff --git a/proxy/src/main/java/io/github/waterfallmc/waterfall/forwarding/VelocityForwardingUtil.java b/proxy/src/main/java/io/github/waterfallmc/waterfall/forwarding/VelocityForwardingUtil.java
new file mode 100644
index 00000000..d099bd13
--- /dev/null
+++ b/proxy/src/main/java/io/github/waterfallmc/waterfall/forwarding/VelocityForwardingUtil.java
@@ -0,0 +1,65 @@
+package io.github.waterfallmc.waterfall.forwarding;
+
+import io.github.waterfallmc.waterfall.conf.WaterfallConfiguration;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import net.md_5.bungee.BungeeCord;
+import net.md_5.bungee.connection.LoginResult;
+import net.md_5.bungee.protocol.DefinedPacket;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+
+public enum VelocityForwardingUtil {
+ ;
+ public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
+ public static final int FORWARDING_VERSION = 1;
+ public static final String MODERN_IP_FORWARDING_FAILURE = "Your server did not send a forwarding request to the proxy. Is it set up correctly?";
+
+
+ public static byte[] writeForwardingData(String address, String name, UUID playerUUID, LoginResult.Property[] properties) {
+ ByteBuf buf = Unpooled.buffer(2048);
+ try {
+ DefinedPacket.writeVarInt(FORWARDING_VERSION, buf);
+ DefinedPacket.writeString(address, buf);
+ DefinedPacket.writeUUID(playerUUID, buf);
+ DefinedPacket.writeString(name, buf);
+ DefinedPacket.writeVarInt(properties.length, buf);
+ for (LoginResult.Property property : properties) {
+ DefinedPacket.writeString(property.getName(), buf);
+ DefinedPacket.writeString(property.getValue(), buf);
+ String signature = property.getSignature();
+ if (signature != null && !signature.isEmpty()) {
+ buf.writeBoolean(true);
+ DefinedPacket.writeString(signature, buf);
+ } else {
+ buf.writeBoolean(false);
+ }
+ }
+
+ byte[] forwardingSecret = ((WaterfallConfiguration) BungeeCord.getInstance().config).getForwardingSecret();
+ SecretKey key = new SecretKeySpec(forwardingSecret, "HmacSHA256");
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(key);
+ mac.update(buf.array(), buf.arrayOffset(), buf.readableBytes());
+ byte[] sig = mac.doFinal();
+
+ ByteBuf finished = Unpooled.wrappedBuffer(Unpooled.wrappedBuffer(sig), buf);
+ byte[] encoded = ByteBufUtil.getBytes(finished);
+ finished.release();
+ return encoded;
+ } catch (InvalidKeyException e) {
+ buf.release();
+ throw new RuntimeException("Unable to authenticate data", e);
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen
+ buf.release();
+ throw new AssertionError(e);
+ }
+ }
+}
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 a5efb0af..30209520 100644
--- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java
+++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java
@@ -1,9 +1,15 @@
package net.md_5.bungee;
import com.google.common.base.Preconditions;
+import io.github.waterfallmc.waterfall.conf.WaterfallConfiguration;
+import io.github.waterfallmc.waterfall.forwarding.ForwardingMode;
+import io.github.waterfallmc.waterfall.forwarding.VelocityForwardingUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.Arrays; // Waterfall
import java.util.Queue;
@@ -70,6 +76,7 @@ public class ServerConnector extends PacketHandler
@Getter
private ForgeServerHandler handshakeHandler;
private boolean obsolete;
+ private boolean didForwardInformation = false; // Waterfall: Forwarding rework
private enum State
{
@@ -103,7 +110,7 @@ public class ServerConnector extends PacketHandler
this.handshakeHandler = new ForgeServerHandler( user, ch, target );
Handshake originalHandshake = user.getPendingConnection().getHandshake();
Handshake copiedHandshake = new Handshake( originalHandshake.getProtocolVersion(), originalHandshake.getHost(), originalHandshake.getPort(), 2 );
-
+ if(BungeeCord.getInstance().config.getForwardingMode() != ForwardingMode.VELOCITY_MODERN) // Waterfall: Forwarding rework
if ( BungeeCord.getInstance().config.isIpForward() && user.getSocketAddress() instanceof InetSocketAddress )
{
String newHost = copiedHandshake.getHost() + "\00" + AddressUtil.sanitizeAddress( user.getAddress() ) + "\00" + user.getUUID();
@@ -118,6 +125,16 @@ public class ServerConnector extends PacketHandler
properties = profile.getProperties();
}
+ // Waterfall start: Forwarding rework
+ if(BungeeCord.getInstance().config.getForwardingMode() == ForwardingMode.BUNGEEGUARD) {
+ List<LoginResult.Property> temp = new ArrayList<LoginResult.Property>();
+ temp.addAll(Arrays.asList(properties));
+ String token = new String(((WaterfallConfiguration)BungeeCord.getInstance().config).getForwardingSecret(), StandardCharsets.UTF_8);
+ temp.add(new LoginResult.Property("bungeeguard-token", token, null));
+ properties = temp.toArray(new LoginResult.Property[temp.size()]);
+ }
+ // Waterfall end: Forwarding rework
+
if ( user.getForgeClientHandler().isFmlTokenInHandshake() )
{
// Get the current properties and copy them into a slightly bigger array.
@@ -169,6 +186,12 @@ public class ServerConnector extends PacketHandler
@Override
public void handle(LoginSuccess loginSuccess) throws Exception
{
+ // Waterfall start: Forwarding rework
+ if ( !didForwardInformation && BungeeCord.getInstance().config.isIpForward()
+ && BungeeCord.getInstance().config.getForwardingMode() == ForwardingMode.VELOCITY_MODERN) {
+ throw new QuietException(VelocityForwardingUtil.MODERN_IP_FORWARDING_FAILURE);
+ }
+ // Waterfall end: Forwarding rework
Preconditions.checkState( thisState == State.LOGIN_SUCCESS, "Not expecting LOGIN_SUCCESS" );
ch.setProtocol( Protocol.GAME );
thisState = State.LOGIN;
@@ -470,6 +493,20 @@ public class ServerConnector extends PacketHandler
@Override
public void handle(LoginPayloadRequest loginPayloadRequest)
{
+ // Waterfall start: Forwarding rework
+ if ( !didForwardInformation && BungeeCord.getInstance().config.isIpForward()
+ && BungeeCord.getInstance().config.getForwardingMode() == ForwardingMode.VELOCITY_MODERN
+ && loginPayloadRequest.getChannel().equals(VelocityForwardingUtil.VELOCITY_IP_FORWARDING_CHANNEL)) {
+
+ byte[] forwardingData = VelocityForwardingUtil
+ .writeForwardingData(user.getAddress().getAddress().getHostAddress(),
+ user.getName(), user.getUniqueId(),
+ user.getPendingConnection().getLoginProfile().getProperties());
+ ch.write(new LoginPayloadResponse(loginPayloadRequest.getId(), forwardingData));
+ didForwardInformation = true;
+ return;
+ }
+ // Waterfall end: Forwarding rework
ch.write( new LoginPayloadResponse( loginPayloadRequest.getId(), null ) );
}
diff --git a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java
index 0644b8cd..201993d3 100644
--- a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java
+++ b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java
@@ -10,6 +10,7 @@ import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.SocketAddress;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -332,4 +333,10 @@ public class YamlConfig implements ConfigurationAdapter
Collection<String> permissions = get( "permissions." + group, null );
return ( permissions == null ) ? Collections.EMPTY_SET : permissions;
}
+
+ // Waterfall start: Forwarding rework
+ public void regenerateForwardingSecret(){
+ set("forwarding_secret", new String(Util.randomAlphanumericSequence(12), StandardCharsets.UTF_8));
+ }
+ // Waterfall end: Forwarding rework
}
--
2.30.0