Skip to content

Commit

Permalink
Velocity modern forward support
Browse files Browse the repository at this point in the history
  • Loading branch information
IzzelAliz committed Dec 30, 2023
1 parent 00dda7e commit 3803ece
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.mojang.authlib.properties.Property;
import com.mojang.util.UndashedUuid;
import io.izzel.arclight.common.bridge.core.network.NetworkManagerBridge;
import io.izzel.arclight.common.mod.util.VelocitySupport;
import net.minecraft.SharedConstants;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
Expand All @@ -15,6 +16,7 @@
import net.minecraft.server.network.ServerHandshakePacketListenerImpl;
import net.minecraft.server.network.ServerLoginPacketListenerImpl;
import net.minecraft.server.network.ServerStatusPacketListenerImpl;
import net.minecraftforge.network.NetworkContext;
import net.minecraftforge.server.ServerLifecycleHooks;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
Expand Down Expand Up @@ -94,26 +96,28 @@ public void handleIntention(ClientIntentionPacket packetIn) {
}
this.connection.setListener(new ServerLoginPacketListenerImpl(this.server, this.connection));

String[] split = packetIn.hostName().split("\00");
if (SpigotConfig.bungee) {
if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) {
((NetworkManagerBridge) this.connection).bridge$setHostname(split[0]);
this.connection.address = new InetSocketAddress(split[1], ((InetSocketAddress) this.connection.getRemoteAddress()).getPort());
((NetworkManagerBridge) this.connection).bridge$setSpoofedUUID(UndashedUuid.fromStringLenient(split[2]));
} else {
var component = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
if (!VelocitySupport.isEnabled()) {
String[] split = packetIn.hostName().split("\00");
if (SpigotConfig.bungee) {
if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) {
((NetworkManagerBridge) this.connection).bridge$setHostname(split[0]);
this.connection.address = new InetSocketAddress(split[1], ((InetSocketAddress) this.connection.getRemoteAddress()).getPort());
((NetworkManagerBridge) this.connection).bridge$setSpoofedUUID(UndashedUuid.fromStringLenient(split[2]));
} else {
var component = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!");
this.connection.send(new ClientboundLoginDisconnectPacket(component));
this.connection.disconnect(component);
return;
}
if (split.length == 4) {
((NetworkManagerBridge) this.connection).bridge$setSpoofedProfile(gson.fromJson(split[3], Property[].class));
}
} else if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) {
Component component = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
this.connection.send(new ClientboundLoginDisconnectPacket(component));
this.connection.disconnect(component);
return;
}
if (split.length == 4) {
((NetworkManagerBridge) this.connection).bridge$setSpoofedProfile(gson.fromJson(split[3], Property[].class));
}
} else if ((split.length == 3 || split.length == 4) && (HOST_PATTERN.matcher(split[1]).matches())) {
Component component = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?");
this.connection.send(new ClientboundLoginDisconnectPacket(component));
this.connection.disconnect(component);
return;
}

break;
Expand All @@ -139,7 +143,11 @@ public void handleIntention(ClientIntentionPacket packetIn) {

private boolean arclight$handleSpecialLogin(ClientIntentionPacket packet) {
String ip = packet.hostName();
if (SpigotConfig.bungee) {
if (VelocitySupport.isEnabled()) {
// as if forge client connects
var forgePacket = new ClientIntentionPacket(packet.protocolVersion(), NetworkContext.enhanceHostName(ip), packet.port(), packet.intention());
return ServerLifecycleHooks.handleServerLogin(forgePacket, this.connection);
} else if (SpigotConfig.bungee) {
String[] split = ip.split("\0");
if (split.length == 4) {
Property[] properties = GSON.fromJson(split[3], Property[].class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@
import io.izzel.arclight.common.bridge.core.network.common.ServerCommonPacketListenerBridge;
import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge;
import io.izzel.arclight.common.bridge.core.server.management.PlayerListBridge;
import io.izzel.arclight.common.mod.util.VelocitySupport;
import net.minecraft.DefaultUncaughtExceptionHandler;
import net.minecraft.Util;
import net.minecraft.core.UUIDUtil;
import net.minecraft.network.Connection;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
import net.minecraft.network.protocol.login.ClientboundHelloPacket;
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
import net.minecraft.network.protocol.login.ServerboundKeyPacket;
import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket;
import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.CommonListenerCookie;
Expand All @@ -25,6 +31,10 @@
import net.minecraft.util.CryptException;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.fml.util.thread.SidedThreadGroups;
import net.minecraftforge.network.ConnectionType;
import net.minecraftforge.network.NetworkContext;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.filters.NetworkFilters;
import org.apache.commons.lang3.Validate;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v.CraftServer;
Expand All @@ -36,6 +46,7 @@
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
Expand All @@ -52,6 +63,7 @@
import java.security.PrivateKey;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;

@Mixin(ServerLoginPacketListenerImpl.class)
Expand All @@ -75,6 +87,7 @@ public abstract class ServerLoginNetHandlerMixin {
private static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}");

private ServerPlayer player;
@Unique private int arclight$velocityLoginId = -1;

public void disconnect(final String s) {
this.disconnect(Component.literal(s));
Expand All @@ -97,6 +110,12 @@ public void handleHello(ServerboundHelloPacket packetIn) {
this.state = ServerLoginPacketListenerImpl.State.KEY;
this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge));
} else {
if (VelocitySupport.isEnabled()) {
this.arclight$velocityLoginId = ThreadLocalRandom.current().nextInt();
var packet = new ClientboundCustomQueryPacket(this.arclight$velocityLoginId, VelocitySupport.createPacket());
this.connection.send(packet);
return;
}
class Handler extends Thread {

Handler() {
Expand All @@ -109,7 +128,7 @@ public void run() {
var gameProfile = arclight$createOfflineProfile(connection, requestedUsername);
arclight$preLogin(gameProfile);
} catch (Exception ex) {
disconnect("Failed to verify username!");
disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
LOGGER.warn("Exception verifying {} ", requestedUsername, ex);
}
}
Expand Down Expand Up @@ -221,7 +240,7 @@ public void run() {
LOGGER.error("Couldn't verify username because servers are unavailable");
}
} catch (Exception e) {
disconnect("Failed to verify username!");
disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
LOGGER.error("Exception verifying " + name, e);
}

Expand All @@ -238,7 +257,101 @@ private InetAddress getAddress() {
thread.start();
}

private static final String EXTRA_DATA = "extraData";

@Inject(method = "handleCustomQueryPacket", cancellable = true, at = @At("HEAD"))
private void arclight$modernForwardReply(ServerboundCustomQueryAnswerPacket packet, CallbackInfo ci) {
if (VelocitySupport.isEnabled() && packet.transactionId() == this.arclight$velocityLoginId) {
if (!(packet.payload() instanceof DiscardedQueryAnswerPayload payload && payload.data() != null)) {
this.disconnect("This server requires you to connect with Velocity.");
ci.cancel();
return;
}
var buf = payload.data().readNullable(r -> {
int i = r.readableBytes();
if (i >= 0 && i <= 1048576) {
return new FriendlyByteBuf(r.readBytes(i));
} else {
throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes");
}
});
if (buf == null) {
this.disconnect("This server requires you to connect with Velocity.");
ci.cancel();
return;
}

if (!VelocitySupport.checkIntegrity(buf)) {
this.disconnect("Unable to verify player details");
ci.cancel();
return;
}

int version = buf.readVarInt();
if (version > VelocitySupport.MAX_SUPPORTED_FORWARDING_VERSION) {
throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted upto " + VelocitySupport.MAX_SUPPORTED_FORWARDING_VERSION);
}
java.net.SocketAddress listening = this.connection.getRemoteAddress();
int port = 0;
if (listening instanceof java.net.InetSocketAddress) {
port = ((java.net.InetSocketAddress) listening).getPort();
}
this.connection.address = new java.net.InetSocketAddress(VelocitySupport.readAddress(buf), port);
this.authenticatedProfile = VelocitySupport.createProfile(buf);

// late forge setup
boolean forwarded = false;
for (var property : this.authenticatedProfile.getProperties().values()) {
if (Objects.equals(property.name(), EXTRA_DATA)) {
String extraData = property.value().replace("\1", "\0");
var ctx = NetworkContext.get(this.connection);
ctx.processIntention(extraData);
if (ctx.getType() == ConnectionType.MODDED && ctx.getNetVersion() != NetworkContext.NET_VERSION) {
this.disconnect("This modded server is not impl compatible with your modded client. Please verify your Forge version closely matches the server. Got net version " + ctx.getNetVersion() + " this server is net version " + NetworkContext.NET_VERSION);
ci.cancel();
return;
}
if (ctx.getType() == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) {
this.disconnect("This server has mods that require Forge to be installed on the client. Contact your server admin for more details.");
ci.cancel();
return;
}
NetworkFilters.injectIfNecessary(this.connection);
forwarded = true;
break;
}
}
if (!forwarded) {
// considered vanilla
var ctx = NetworkContext.get(this.connection);
ctx.processIntention("");
if (ctx.getType() == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) {
this.disconnect("This server has mods that require Forge to be installed on the client. Contact your server admin for more details.");
ci.cancel();
return;
}
NetworkFilters.injectIfNecessary(this.connection);
}

// Proceed with login
Util.backgroundExecutor().execute(() -> {
try {
this.arclight$preLogin(this.authenticatedProfile);
} catch (Exception ex) {
disconnect(Component.translatable("multiplayer.disconnect.unverified_username"));
LOGGER.warn("Exception verifying {} ", this.authenticatedProfile.getName(), ex);
}
});
ci.cancel();
}
}

@Unique
void arclight$preLogin(GameProfile gameProfile) throws Exception {
if (this.arclight$velocityLoginId == -1 && VelocitySupport.isEnabled()) {
disconnect("This server requires you to connect with Velocity.");
return;
}
String playerName = gameProfile.getName();
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
UUID uniqueId = gameProfile.getId();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mojang.brigadier.arguments.ArgumentType;
import io.izzel.arclight.common.mod.ArclightMod;
import io.izzel.arclight.common.mod.util.VelocitySupport;
import io.netty.buffer.Unpooled;
import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.registries.BuiltInRegistries;
Expand All @@ -21,7 +22,7 @@ public class ClientboundCommandsPacket_ArgumentNodeStubMixin {
@Inject(method = "serializeCap(Lnet/minecraft/network/FriendlyByteBuf;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo;Lnet/minecraft/commands/synchronization/ArgumentTypeInfo$Template;)V",
cancellable = true, at = @At("HEAD"))
private static <A extends ArgumentType<?>, T extends ArgumentTypeInfo.Template<A>> void arclight$wrapArgument(FriendlyByteBuf buf, ArgumentTypeInfo<A, T> type, ArgumentTypeInfo.Template<A> node, CallbackInfo ci) {
if (!SpigotConfig.bungee) {
if (!(SpigotConfig.bungee || VelocitySupport.isEnabled())) {
return;
}
var key = ForgeRegistries.COMMAND_ARGUMENT_TYPES.getKey(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.izzel.arclight.common.bridge.core.server.MinecraftServerBridge;
import io.izzel.arclight.common.mod.ArclightMod;
import io.izzel.arclight.common.mod.server.api.DefaultArclightServer;
import io.izzel.arclight.common.mod.util.VelocitySupport;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer;
Expand All @@ -16,6 +17,7 @@
import org.bukkit.craftbukkit.v.CraftServer;
import org.bukkit.craftbukkit.v.command.ColouredConsoleSender;
import org.jetbrains.annotations.NotNull;
import org.spigotmc.SpigotConfig;

import java.io.File;
import java.util.Objects;
Expand All @@ -28,7 +30,8 @@

public class ArclightServer {

private interface ExecutorWithThread extends Executor, Supplier<Thread> {}
private interface ExecutorWithThread extends Executor, Supplier<Thread> {
}

private static final ExecutorWithThread mainThreadExecutor = new ExecutorWithThread() {
@Override
Expand Down Expand Up @@ -76,6 +79,9 @@ public static CraftServer createOrLoad(DedicatedServer console, PlayerList playe
BukkitRegistry.registerAll(console);
org.spigotmc.SpigotConfig.init(new File("./spigot.yml"));
org.spigotmc.SpigotConfig.registerCommands();
if (VelocitySupport.isEnabled()) {
SpigotConfig.bungee = true;
}
} catch (Throwable t) {
ArclightMod.LOGGER.error("registry.error", t);
throw t;
Expand Down
Loading

0 comments on commit 3803ece

Please sign in to comment.