308 lines
15 KiB
Diff
308 lines
15 KiB
Diff
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrew Steinborn <git@steinborn.me>
|
||
|
Date: Mon, 8 Oct 2018 14:36:14 -0400
|
||
|
Subject: [PATCH] Add Velocity IP Forwarding Support
|
||
|
|
||
|
While Velocity supports BungeeCord-style IP forwarding, it is not secure. Users
|
||
|
have a lot of problems setting up firewalls or setting up plugins like IPWhitelist.
|
||
|
Further, the BungeeCord IP forwarding protocol still retains essentially its original
|
||
|
form, when there is brand new support for custom login plugin messages in 1.13.
|
||
|
|
||
|
Velocity's modern IP forwarding uses an HMAC-SHA256 code to ensure authenticity
|
||
|
of messages, is packed into a binary format that is smaller than BungeeCord's
|
||
|
forwarding, and is integrated into the Minecraft login process by using the 1.13
|
||
|
login plugin message packet.
|
||
|
|
||
|
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
index 7178b37f7978c7e9031a22726005c5099fd78fe0..3139c194f9b1bc3510d51a81f13ae43d00a3dc29 100644
|
||
|
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
|
||
|
@@ -8,6 +8,7 @@ import java.io.IOException;
|
||
|
import java.lang.reflect.InvocationTargetException;
|
||
|
import java.lang.reflect.Method;
|
||
|
import java.lang.reflect.Modifier;
|
||
|
+import java.nio.charset.StandardCharsets;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
@@ -252,7 +253,7 @@ public class PaperConfig {
|
||
|
}
|
||
|
|
||
|
public static boolean isProxyOnlineMode() {
|
||
|
- return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode);
|
||
|
+ return Bukkit.getOnlineMode() || (SpigotConfig.bungee && bungeeOnlineMode) || (velocitySupport && velocityOnlineMode);
|
||
|
}
|
||
|
|
||
|
public static int packetInSpamThreshold = 300;
|
||
|
@@ -324,4 +325,18 @@ public class PaperConfig {
|
||
|
}
|
||
|
tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit);
|
||
|
}
|
||
|
+
|
||
|
+ public static boolean velocitySupport;
|
||
|
+ public static boolean velocityOnlineMode;
|
||
|
+ public static byte[] velocitySecretKey;
|
||
|
+ private static void velocitySupport() {
|
||
|
+ velocitySupport = getBoolean("settings.velocity-support.enabled", false);
|
||
|
+ velocityOnlineMode = getBoolean("settings.velocity-support.online-mode", false);
|
||
|
+ String secret = getString("settings.velocity-support.secret", "");
|
||
|
+ if (velocitySupport && secret.isEmpty()) {
|
||
|
+ fatal("Velocity support is enabled, but no secret key was specified. A secret key is required!");
|
||
|
+ } else {
|
||
|
+ velocitySecretKey = secret.getBytes(StandardCharsets.UTF_8);
|
||
|
+ }
|
||
|
+ }
|
||
|
}
|
||
|
diff --git a/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java
|
||
|
new file mode 100644
|
||
|
index 0000000000000000000000000000000000000000..e6afaa41df086b1eb3950ce870c91dd5bf5a663b
|
||
|
--- /dev/null
|
||
|
+++ b/src/main/java/com/destroystokyo/paper/proxy/VelocityProxy.java
|
||
|
@@ -0,0 +1,67 @@
|
||
|
+package com.destroystokyo.paper.proxy;
|
||
|
+
|
||
|
+import com.destroystokyo.paper.PaperConfig;
|
||
|
+import com.google.common.net.InetAddresses;
|
||
|
+import com.mojang.authlib.GameProfile;
|
||
|
+import com.mojang.authlib.properties.Property;
|
||
|
+import net.minecraft.network.PacketDataSerializer;
|
||
|
+import net.minecraft.resources.MinecraftKey;
|
||
|
+
|
||
|
+import java.net.InetAddress;
|
||
|
+import java.security.InvalidKeyException;
|
||
|
+import java.security.MessageDigest;
|
||
|
+import java.security.NoSuchAlgorithmException;
|
||
|
+
|
||
|
+import javax.crypto.Mac;
|
||
|
+import javax.crypto.spec.SecretKeySpec;
|
||
|
+
|
||
|
+public class VelocityProxy {
|
||
|
+ private static final int SUPPORTED_FORWARDING_VERSION = 1;
|
||
|
+ public static final MinecraftKey PLAYER_INFO_CHANNEL = new MinecraftKey("velocity", "player_info");
|
||
|
+
|
||
|
+ public static boolean checkIntegrity(final PacketDataSerializer buf) {
|
||
|
+ final byte[] signature = new byte[32];
|
||
|
+ buf.readBytes(signature);
|
||
|
+
|
||
|
+ final byte[] data = new byte[buf.readableBytes()];
|
||
|
+ buf.getBytes(buf.readerIndex(), data);
|
||
|
+
|
||
|
+ try {
|
||
|
+ final Mac mac = Mac.getInstance("HmacSHA256");
|
||
|
+ mac.init(new SecretKeySpec(PaperConfig.velocitySecretKey, "HmacSHA256"));
|
||
|
+ final byte[] mySignature = mac.doFinal(data);
|
||
|
+ if (!MessageDigest.isEqual(signature, mySignature)) {
|
||
|
+ return false;
|
||
|
+ }
|
||
|
+ } catch (final InvalidKeyException | NoSuchAlgorithmException e) {
|
||
|
+ throw new AssertionError(e);
|
||
|
+ }
|
||
|
+
|
||
|
+ int version = buf.readVarInt();
|
||
|
+ if (version != SUPPORTED_FORWARDING_VERSION) {
|
||
|
+ throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + SUPPORTED_FORWARDING_VERSION);
|
||
|
+ }
|
||
|
+
|
||
|
+ return true;
|
||
|
+ }
|
||
|
+
|
||
|
+ public static InetAddress readAddress(final PacketDataSerializer buf) {
|
||
|
+ return InetAddresses.forString(buf.readUTF(Short.MAX_VALUE));
|
||
|
+ }
|
||
|
+
|
||
|
+ public static GameProfile createProfile(final PacketDataSerializer buf) {
|
||
|
+ final GameProfile profile = new GameProfile(buf.readUUID(), buf.readUTF(16));
|
||
|
+ readProperties(buf, profile);
|
||
|
+ return profile;
|
||
|
+ }
|
||
|
+
|
||
|
+ private static void readProperties(final PacketDataSerializer buf, final GameProfile profile) {
|
||
|
+ final int properties = buf.readVarInt();
|
||
|
+ for (int i1 = 0; i1 < properties; i1++) {
|
||
|
+ final String name = buf.readUTF(Short.MAX_VALUE);
|
||
|
+ final String value = buf.readUTF(Short.MAX_VALUE);
|
||
|
+ final String signature = buf.readBoolean() ? buf.readUTF(Short.MAX_VALUE) : null;
|
||
|
+ profile.getProperties().put(name, new Property(name, value, signature));
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
diff --git a/src/main/java/net/minecraft/network/PacketDataSerializer.java b/src/main/java/net/minecraft/network/PacketDataSerializer.java
|
||
|
index 5a1187b001004afe22d208bc5d7c288e796e16a6..579eb1260c7266cd41025cff177de4fb00ac0cec 100644
|
||
|
--- a/src/main/java/net/minecraft/network/PacketDataSerializer.java
|
||
|
+++ b/src/main/java/net/minecraft/network/PacketDataSerializer.java
|
||
|
@@ -192,6 +192,7 @@ public class PacketDataSerializer extends ByteBuf {
|
||
|
return this.d(oenum.ordinal());
|
||
|
}
|
||
|
|
||
|
+ public int readVarInt() { return i(); } // Paper - OBFHELPER
|
||
|
public int i() {
|
||
|
int i = 0;
|
||
|
int j = 0;
|
||
|
@@ -232,6 +233,7 @@ public class PacketDataSerializer extends ByteBuf {
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
+ public UUID readUUID() { return k(); } // Paper - OBFHELPER
|
||
|
public UUID k() {
|
||
|
return new UUID(this.readLong(), this.readLong());
|
||
|
}
|
||
|
@@ -359,6 +361,7 @@ public class PacketDataSerializer extends ByteBuf {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+ public String readUTF(int maxLength) { return this.e(maxLength); } // Paper - OBFHELPER
|
||
|
public String e(int i) {
|
||
|
int j = this.i();
|
||
|
|
||
|
diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java
|
||
|
index c1bac2d07e5107c1346f246f5d5d929c73912bfd..c47c2d774a1990ad5007bbdc7bd3a81b3f3817e3 100644
|
||
|
--- a/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java
|
||
|
+++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginInCustomPayload.java
|
||
|
@@ -6,8 +6,8 @@ import net.minecraft.network.protocol.Packet;
|
||
|
|
||
|
public class PacketLoginInCustomPayload implements Packet<PacketLoginInListener> {
|
||
|
|
||
|
- private int a;
|
||
|
- private PacketDataSerializer b;
|
||
|
+ private int a; public int getId() { return a; } // Paper - OBFHELPER
|
||
|
+ private PacketDataSerializer b; public PacketDataSerializer getBuf() { return b; } // Paper - OBFHELPER
|
||
|
|
||
|
public PacketLoginInCustomPayload() {}
|
||
|
|
||
|
diff --git a/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java
|
||
|
index eb970c1e954cb0aa83aa12e83c471778809e69b2..2d8c917509f10a96fc82404908b452cb385c7c60 100644
|
||
|
--- a/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java
|
||
|
+++ b/src/main/java/net/minecraft/network/protocol/login/PacketLoginOutCustomPayload.java
|
||
|
@@ -13,6 +13,14 @@ public class PacketLoginOutCustomPayload implements Packet<PacketLoginOutListene
|
||
|
|
||
|
public PacketLoginOutCustomPayload() {}
|
||
|
|
||
|
+ // Paper start
|
||
|
+ public PacketLoginOutCustomPayload(int id, MinecraftKey channel, PacketDataSerializer buf) {
|
||
|
+ this.a = id;
|
||
|
+ this.b = channel;
|
||
|
+ this.c = buf;
|
||
|
+ }
|
||
|
+ // Paper end
|
||
|
+
|
||
|
@Override
|
||
|
public void a(PacketDataSerializer packetdataserializer) throws IOException {
|
||
|
this.a = packetdataserializer.i();
|
||
|
diff --git a/src/main/java/net/minecraft/server/network/LoginListener.java b/src/main/java/net/minecraft/server/network/LoginListener.java
|
||
|
index 21e70a133278d85ecd65fec36a273ed4faabf6cc..b89a8ee2ea03d00a3d6af63d6e3e0fd62af31130 100644
|
||
|
--- a/src/main/java/net/minecraft/server/network/LoginListener.java
|
||
|
+++ b/src/main/java/net/minecraft/server/network/LoginListener.java
|
||
|
@@ -18,12 +18,14 @@ import javax.crypto.Cipher;
|
||
|
import javax.crypto.SecretKey;
|
||
|
import net.minecraft.DefaultUncaughtExceptionHandler;
|
||
|
import net.minecraft.network.NetworkManager;
|
||
|
+import net.minecraft.network.PacketDataSerializer;
|
||
|
import net.minecraft.network.chat.ChatMessage;
|
||
|
import net.minecraft.network.chat.IChatBaseComponent;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginInCustomPayload;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginInEncryptionBegin;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginInListener;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginInStart;
|
||
|
+import net.minecraft.network.protocol.login.PacketLoginOutCustomPayload;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginOutDisconnect;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin;
|
||
|
import net.minecraft.network.protocol.login.PacketLoginOutSetCompression;
|
||
|
@@ -45,6 +47,7 @@ import org.bukkit.craftbukkit.util.Waitable;
|
||
|
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
|
||
|
import org.bukkit.event.player.PlayerPreLoginEvent;
|
||
|
// CraftBukkit end
|
||
|
+import io.netty.buffer.Unpooled; // Paper
|
||
|
|
||
|
public class LoginListener implements PacketLoginInListener {
|
||
|
|
||
|
@@ -61,6 +64,7 @@ public class LoginListener implements PacketLoginInListener {
|
||
|
private SecretKey loginKey;
|
||
|
private EntityPlayer l;
|
||
|
public String hostname = ""; // CraftBukkit - add field
|
||
|
+ private int velocityLoginMessageId = -1; // Paper - Velocity support
|
||
|
|
||
|
public LoginListener(MinecraftServer minecraftserver, NetworkManager networkmanager) {
|
||
|
this.g = LoginListener.EnumProtocolState.HELLO;
|
||
|
@@ -213,6 +217,14 @@ public class LoginListener implements PacketLoginInListener {
|
||
|
this.g = LoginListener.EnumProtocolState.KEY;
|
||
|
this.networkManager.sendPacket(new PacketLoginOutEncryptionBegin("", this.server.getKeyPair().getPublic().getEncoded(), this.e));
|
||
|
} else {
|
||
|
+ // Paper start - Velocity support
|
||
|
+ if (com.destroystokyo.paper.PaperConfig.velocitySupport) {
|
||
|
+ this.velocityLoginMessageId = java.util.concurrent.ThreadLocalRandom.current().nextInt();
|
||
|
+ PacketLoginOutCustomPayload packet = new PacketLoginOutCustomPayload(this.velocityLoginMessageId, com.destroystokyo.paper.proxy.VelocityProxy.PLAYER_INFO_CHANNEL, new PacketDataSerializer(Unpooled.EMPTY_BUFFER));
|
||
|
+ this.networkManager.sendPacket(packet);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ // Paper end
|
||
|
// Spigot start
|
||
|
// Paper start - Cache authenticator threads
|
||
|
authenticatorPool.execute(new Runnable() {
|
||
|
@@ -314,6 +326,12 @@ public class LoginListener implements PacketLoginInListener {
|
||
|
public class LoginHandler {
|
||
|
|
||
|
public void fireEvents() throws Exception {
|
||
|
+ // Paper start - Velocity support
|
||
|
+ if (LoginListener.this.velocityLoginMessageId == -1 && com.destroystokyo.paper.PaperConfig.velocitySupport) {
|
||
|
+ disconnect("This server requires you to connect with Velocity.");
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ // Paper end
|
||
|
String playerName = i.getName();
|
||
|
java.net.InetAddress address = ((java.net.InetSocketAddress) networkManager.getSocketAddress()).getAddress();
|
||
|
java.util.UUID uniqueId = i.getId();
|
||
|
@@ -361,6 +379,40 @@ public class LoginListener implements PacketLoginInListener {
|
||
|
// Spigot end
|
||
|
|
||
|
public void a(PacketLoginInCustomPayload packetloginincustompayload) {
|
||
|
+ // Paper start - Velocity support
|
||
|
+ if (com.destroystokyo.paper.PaperConfig.velocitySupport && packetloginincustompayload.getId() == this.velocityLoginMessageId) {
|
||
|
+ PacketDataSerializer buf = packetloginincustompayload.getBuf();
|
||
|
+ if (buf == null) {
|
||
|
+ this.disconnect("This server requires you to connect with Velocity.");
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) {
|
||
|
+ this.disconnect("Unable to verify player details");
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ java.net.SocketAddress listening = this.networkManager.getSocketAddress();
|
||
|
+ int port = 0;
|
||
|
+ if (listening instanceof java.net.InetSocketAddress) {
|
||
|
+ port = ((java.net.InetSocketAddress) listening).getPort();
|
||
|
+ }
|
||
|
+ this.networkManager.socketAddress = new java.net.InetSocketAddress(com.destroystokyo.paper.proxy.VelocityProxy.readAddress(buf), port);
|
||
|
+
|
||
|
+ this.setGameProfile(com.destroystokyo.paper.proxy.VelocityProxy.createProfile(buf));
|
||
|
+
|
||
|
+ // Proceed with login
|
||
|
+ authenticatorPool.execute(() -> {
|
||
|
+ try {
|
||
|
+ new LoginHandler().fireEvents();
|
||
|
+ } catch (Exception ex) {
|
||
|
+ disconnect("Failed to verify username!");
|
||
|
+ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + i.getName(), ex);
|
||
|
+ }
|
||
|
+ });
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ // Paper end
|
||
|
this.disconnect(new ChatMessage("multiplayer.disconnect.unexpected_query_response"));
|
||
|
}
|
||
|
|
||
|
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
|
index ba982907b97faace89b73365aef1c91dab692ff1..22f9d8c0189293b88353bbe1bcc40dd1d431d458 100644
|
||
|
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
|
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
|
@@ -681,7 +681,7 @@ public final class CraftServer implements Server {
|
||
|
@Override
|
||
|
public long getConnectionThrottle() {
|
||
|
// Spigot Start - Automatically set connection throttle for bungee configurations
|
||
|
- if (org.spigotmc.SpigotConfig.bungee) {
|
||
|
+ if (org.spigotmc.SpigotConfig.bungee || com.destroystokyo.paper.PaperConfig.velocitySupport) { // Paper - Velocity support
|
||
|
return -1;
|
||
|
} else {
|
||
|
return this.configuration.getInt("settings.connection-throttle");
|